# 自然写教室智能网关管理软件 V1.0 ## 软件著作权鉴别材料 — 源程序 > **权利人**:深圳自然写科技有限公司 > **版本号**:V1.0 --- ## 源程序目录结构 ``` 04-writech-gateway/ ├── main.c ├── ble/ │ └── ble_manager.c ├── cache/ │ ├── offline_cache.c │ └── ring_buffer.c ├── config/ │ └── gateway_config.c ├── device/ │ └── device_manager.c ├── mqtt/ │ └── mqtt_client.c ├── ota/ │ └── ota_updater.c └── protocol/ └── protocol_converter.c ``` --- ## 源程序文件清单 ### (根目录) #### `main.c` ```c /* * 自然写互动课堂教学管理网关软件 V1.0 * main.c - 网关主程序入口 * * 功能说明: * 1. 系统初始化与模块启动协调 * 2. 主事件循环(epoll事件驱动模型) * 3. 信号处理与优雅退出 * 4. 系统运行状态监控 * * 硬件平台:ARM Linux嵌入式网关 * 角色:教室内BLE点阵笔 ↔ MQTT云平台的协议桥接 */ #include #include #include #include #include #include #include #include #include #include /* 模块头文件 */ #include "ble_manager.h" #include "mqtt_client.h" #include "protocol_converter.h" #include "ring_buffer.h" #include "offline_cache.h" #include "device_manager.h" #include "ota_updater.h" #include "gateway_config.h" #include "watchdog.h" #include "http_server.h" /* ========== 全局常量 ========== */ #define GATEWAY_VERSION "1.0.0" #define MAX_EPOLL_EVENTS 64 #define MAIN_LOOP_TIMEOUT_MS 100 /* ========== 全局变量 ========== */ /* 运行标志(信号处理中设置为0) */ static volatile int g_running = 1; /* epoll文件描述符 */ static int g_epoll_fd = -1; /* 系统启动时间 */ static struct timeval g_start_time; /* 各模块状态 */ typedef struct { int ble_active; /* BLE模块是否正常 */ int mqtt_connected; /* MQTT是否已连接 */ int pen_count; /* 已连接笔数量 */ int cache_count; /* 离线缓存数据条数 */ unsigned long uptime_sec; /* 运行时长(秒) */ unsigned long total_packets;/* 累计转发数据包数 */ } GatewayStatus; static GatewayStatus g_status; /* ========== 信号处理 ========== */ /** * 信号处理函数 * 捕获SIGINT/SIGTERM实现优雅退出 */ static void signal_handler(int signo) { if (signo == SIGINT || signo == SIGTERM) { syslog(LOG_INFO, "收到终止信号 %d,准备退出...", signo); g_running = 0; } } /** * 注册信号处理器 */ static void setup_signals(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); /* 忽略SIGPIPE(网络连接断开时避免进程退出) */ signal(SIGPIPE, SIG_IGN); } /* ========== 模块初始化 ========== */ /** * 初始化所有功能模块 * 按依赖顺序逐一启动各子系统 * * @return 0成功, -1失败 */ static int init_modules(void) { syslog(LOG_INFO, "=== 自然写网关 V%s 启动 ===", GATEWAY_VERSION); /* 步骤1:加载配置文件 */ if (gateway_config_load("/etc/writech/gateway.conf") != 0) { syslog(LOG_WARNING, "配置文件加载失败,使用默认配置"); gateway_config_load_defaults(); } /* 步骤2:初始化环形缓冲区(用于BLE→MQTT数据转发) */ ring_buffer_init(64 * 1024); /* 64KB缓冲区 */ /* 步骤3:初始化离线缓存(SQLite) */ if (offline_cache_init("/var/lib/writech/cache.db") != 0) { syslog(LOG_ERR, "离线缓存初始化失败"); return -1; } /* 步骤4:初始化BLE管理器 */ if (ble_manager_init() != 0) { syslog(LOG_ERR, "BLE管理器初始化失败"); return -1; } /* 步骤5:初始化MQTT客户端 */ const char *mqtt_host = gateway_config_get_string("mqtt.host", "mqtt.writech.com"); int mqtt_port = gateway_config_get_int("mqtt.port", 8883); if (mqtt_client_init(mqtt_host, mqtt_port) != 0) { syslog(LOG_ERR, "MQTT客户端初始化失败"); return -1; } /* 步骤6:初始化协议转换器 */ protocol_converter_init(); /* 步骤7:初始化设备管理器 */ device_manager_init(); /* 步骤8:初始化OTA升级模块 */ ota_updater_init(); /* 步骤9:初始化看门狗 */ watchdog_init(30); /* 30秒超时 */ /* 步骤10:启动本地Web管理页面 */ int http_port = gateway_config_get_int("http.port", 8080); http_server_start(http_port); syslog(LOG_INFO, "所有模块初始化完成"); return 0; } /* ========== 主事件循环 ========== */ /** * 创建epoll实例并注册各模块的文件描述符 */ static int setup_epoll(void) { g_epoll_fd = epoll_create1(0); if (g_epoll_fd < 0) { syslog(LOG_ERR, "epoll_create失败: %s", strerror(errno)); return -1; } /* 注册BLE事件文件描述符 */ int ble_fd = ble_manager_get_fd(); if (ble_fd >= 0) { struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = ble_fd; epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, ble_fd, &ev); } /* 注册MQTT事件文件描述符 */ int mqtt_fd = mqtt_client_get_fd(); if (mqtt_fd >= 0) { struct epoll_event ev; ev.events = EPOLLIN | EPOLLOUT; ev.data.fd = mqtt_fd; epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, mqtt_fd, &ev); } return 0; } /** * 处理epoll事件 */ static void process_events(struct epoll_event *events, int count) { int i; for (i = 0; i < count; i++) { int fd = events[i].data.fd; if (fd == ble_manager_get_fd()) { /* BLE数据就绪,读取并转发 */ ble_manager_process_events(); } else if (fd == mqtt_client_get_fd()) { /* MQTT事件处理 */ if (events[i].events & EPOLLIN) { mqtt_client_process_read(); } if (events[i].events & EPOLLOUT) { mqtt_client_process_write(); } } } } /** * 定时任务处理(每次主循环迭代执行) * 处理非事件驱动的周期性任务 */ static void periodic_tasks(void) { static unsigned long tick_count = 0; tick_count++; /* 每秒执行一次 */ if (tick_count % 10 == 0) { /* 喂看门狗 */ watchdog_feed(); /* 更新运行时长 */ struct timeval now; gettimeofday(&now, NULL); g_status.uptime_sec = now.tv_sec - g_start_time.tv_sec; } /* 每5秒执行一次 */ if (tick_count % 50 == 0) { /* 更新状态信息 */ g_status.ble_active = ble_manager_is_active(); g_status.mqtt_connected = mqtt_client_is_connected(); g_status.pen_count = ble_manager_get_connected_count(); g_status.cache_count = offline_cache_get_count(); } /* 每30秒执行一次 */ if (tick_count % 300 == 0) { /* 尝试回传离线缓存数据 */ if (g_status.mqtt_connected && g_status.cache_count > 0) { offline_cache_flush_to_mqtt(); } /* 检查OTA更新 */ ota_updater_check(); } /* 协议转发:从环形缓冲区读取BLE数据,转换后发送到MQTT */ protocol_converter_process(); } /* ========== 清理退出 ========== */ /** * 清理并释放所有资源 */ static void cleanup(void) { syslog(LOG_INFO, "开始清理资源..."); http_server_stop(); watchdog_stop(); ota_updater_cleanup(); device_manager_cleanup(); mqtt_client_cleanup(); ble_manager_cleanup(); offline_cache_close(); ring_buffer_destroy(); gateway_config_free(); if (g_epoll_fd >= 0) { close(g_epoll_fd); } syslog(LOG_INFO, "=== 网关已安全退出 ==="); closelog(); } /* ========== 主函数 ========== */ int main(int argc, char *argv[]) { /* 打开系统日志 */ openlog("writech-gateway", LOG_PID | LOG_NDELAY, LOG_DAEMON); /* 记录启动时间 */ gettimeofday(&g_start_time, NULL); memset(&g_status, 0, sizeof(g_status)); /* 注册信号处理 */ setup_signals(); /* 初始化所有模块 */ if (init_modules() != 0) { syslog(LOG_ERR, "模块初始化失败,退出"); cleanup(); return EXIT_FAILURE; } /* 设置epoll */ if (setup_epoll() != 0) { cleanup(); return EXIT_FAILURE; } /* 主事件循环 */ struct epoll_event events[MAX_EPOLL_EVENTS]; syslog(LOG_INFO, "进入主事件循环..."); while (g_running) { int nfds = epoll_wait(g_epoll_fd, events, MAX_EPOLL_EVENTS, MAIN_LOOP_TIMEOUT_MS); if (nfds < 0) { if (errno == EINTR) continue; syslog(LOG_ERR, "epoll_wait错误: %s", strerror(errno)); break; } if (nfds > 0) { process_events(events, nfds); } periodic_tasks(); } cleanup(); return EXIT_SUCCESS; } ``` ### `ble/` #### `ble/ble_manager.c` ```c /* * 自然写互动课堂教学管理网关软件 V1.0 * ble_manager.c - BLE多连接管理器 * * 功能说明: * 1. 基于BlueZ D-Bus接口的BLE多设备管理 * 2. 自动扫描与连接自然写点阵笔(最多60支) * 3. GATT服务发现与特征值通知订阅 * 4. BLE数据接收与分发 * 5. 断线自动重连机制 * 6. BLE适配器状态监控 */ #include #include #include #include #include #include #include /* BlueZ D-Bus头文件 */ #include #include #include /* 模块头文件 */ #include "ble_manager.h" #include "ring_buffer.h" /* ========== 常量定义 ========== */ /* 自然写笔GATT服务UUID */ #define PEN_SERVICE_UUID "0000ffe0-0000-1000-8000-00805f9b34fb" /* 笔迹数据特征值UUID */ #define STROKE_CHAR_UUID "0000ffe1-0000-1000-8000-00805f9b34fb" /* 最大同时连接设备数 */ #define MAX_BLE_CONNECTIONS 60 /* 扫描间隔(毫秒) */ #define SCAN_INTERVAL_MS 10000 /* 重连延迟(秒) */ #define RECONNECT_DELAY_SEC 5 /* ========== 数据结构 ========== */ /* BLE设备连接信息 */ typedef struct { char mac_address[18]; /* MAC地址 "AA:BB:CC:DD:EE:FF" */ char device_name[64]; /* 设备名称 */ int connection_handle; /* 连接句柄 */ int is_connected; /* 是否已连接 */ int is_subscribed; /* 是否已订阅通知 */ int gatt_handle; /* GATT特征值句柄 */ int rssi; /* 信号强度 */ unsigned long last_data_time; /* 最后收到数据的时间 */ int reconnect_attempts; /* 重连尝试次数 */ char bound_student_id[32]; /* 绑定的学生ID */ } BLEDevice; /* BLE管理器状态 */ typedef struct { int hci_dev_id; /* HCI设备ID */ int hci_socket; /* HCI套接字 */ int is_scanning; /* 是否正在扫描 */ int is_active; /* 管理器是否活跃 */ BLEDevice devices[MAX_BLE_CONNECTIONS]; /* 设备列表 */ int device_count; /* 已连接设备数 */ pthread_mutex_t mutex; /* 线程安全锁 */ pthread_t scan_thread; /* 扫描线程 */ pthread_t recv_thread; /* 数据接收线程 */ int event_pipe[2]; /* 事件通知管道 */ } BLEManager; /* ========== 静态变量 ========== */ static BLEManager g_ble; /* 数据回调函数指针 */ static void (*g_data_callback)(const char *mac, const uint8_t *data, int len) = NULL; /* ========== 初始化 ========== */ /** * 初始化BLE管理器 * 打开HCI设备,配置扫描参数 * * @return 0成功, -1失败 */ int ble_manager_init(void) { memset(&g_ble, 0, sizeof(g_ble)); pthread_mutex_init(&g_ble.mutex, NULL); /* 创建事件通知管道 */ if (pipe(g_ble.event_pipe) < 0) { syslog(LOG_ERR, "BLE: 创建事件管道失败: %s", strerror(errno)); return -1; } /* 打开默认HCI蓝牙适配器 */ g_ble.hci_dev_id = hci_get_route(NULL); if (g_ble.hci_dev_id < 0) { syslog(LOG_ERR, "BLE: 未找到蓝牙适配器"); return -1; } g_ble.hci_socket = hci_open_dev(g_ble.hci_dev_id); if (g_ble.hci_socket < 0) { syslog(LOG_ERR, "BLE: 打开HCI设备失败: %s", strerror(errno)); return -1; } g_ble.is_active = 1; /* 启动扫描线程 */ pthread_create(&g_ble.scan_thread, NULL, scan_thread_func, NULL); /* 启动数据接收线程 */ pthread_create(&g_ble.recv_thread, NULL, recv_thread_func, NULL); syslog(LOG_INFO, "BLE管理器初始化完成,适配器ID=%d", g_ble.hci_dev_id); return 0; } /* ========== 设备扫描 ========== */ /** * 扫描线程函数 * 周期性扫描BLE设备,发现新的自然写点阵笔后自动连接 */ static void *scan_thread_func(void *arg) { (void)arg; syslog(LOG_INFO, "BLE: 扫描线程启动"); while (g_ble.is_active) { /* 检查是否还有连接名额 */ pthread_mutex_lock(&g_ble.mutex); int current_count = g_ble.device_count; pthread_mutex_unlock(&g_ble.mutex); if (current_count < MAX_BLE_CONNECTIONS) { /* 执行LE扫描 */ perform_le_scan(); } /* 检查需要重连的设备 */ check_reconnect(); /* 扫描间隔 */ usleep(SCAN_INTERVAL_MS * 1000); } syslog(LOG_INFO, "BLE: 扫描线程退出"); return NULL; } /** * 执行BLE低功耗扫描 * 使用HCI LE扫描命令搜索附近的BLE设备 */ static void perform_le_scan(void) { /* 设置LE扫描参数 */ uint8_t scan_type = 0x01; /* 主动扫描 */ uint16_t scan_interval = 0x0010; /* 扫描间隔 */ uint16_t scan_window = 0x0010; /* 扫描窗口 */ uint8_t own_type = 0x00; /* 公共地址 */ uint8_t filter = 0x00; /* 不过滤 */ int ret = hci_le_set_scan_parameters(g_ble.hci_socket, scan_type, scan_interval, scan_window, own_type, filter, 1000); if (ret < 0) { syslog(LOG_WARNING, "BLE: 设置扫描参数失败"); return; } /* 启动扫描 */ ret = hci_le_set_scan_enable(g_ble.hci_socket, 0x01, 0x00, 1000); if (ret < 0) { syslog(LOG_WARNING, "BLE: 启动扫描失败"); return; } g_ble.is_scanning = 1; /* 扫描持续3秒 */ struct hci_filter flt; hci_filter_clear(&flt); hci_filter_set_ptype(HCI_EVENT_PKT, &flt); hci_filter_set_event(EVT_LE_META_EVENT, &flt); setsockopt(g_ble.hci_socket, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)); /* 读取扫描结果 */ uint8_t buf[256]; int scan_duration_ms = 3000; int elapsed = 0; while (elapsed < scan_duration_ms && g_ble.is_active) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 100000; /* 100ms超时 */ fd_set rfds; FD_ZERO(&rfds); FD_SET(g_ble.hci_socket, &rfds); ret = select(g_ble.hci_socket + 1, &rfds, NULL, NULL, &tv); if (ret > 0) { int len = read(g_ble.hci_socket, buf, sizeof(buf)); if (len > 0) { process_scan_result(buf, len); } } elapsed += 100; } /* 停止扫描 */ hci_le_set_scan_enable(g_ble.hci_socket, 0x00, 0x00, 1000); g_ble.is_scanning = 0; } /** * 处理扫描结果 * 解析广播包,筛选包含自然写服务UUID的设备 */ static void process_scan_result(const uint8_t *data, int len) { if (len < 14) return; /* 解析HCI LE Meta事件 */ evt_le_meta_event *meta = (evt_le_meta_event *)(data + 1 + HCI_EVENT_HDR_SIZE); if (meta->subevent != 0x02) return; /* 非广播报告 */ le_advertising_info *info = (le_advertising_info *)(meta->data + 1); /* 提取MAC地址 */ char mac[18]; ba2str(&info->bdaddr, mac); /* 检查是否已连接 */ if (find_device_by_mac(mac) >= 0) { return; /* 已连接,跳过 */ } /* 检查广播数据中是否包含自然写服务UUID */ if (check_service_uuid(info->data, info->length)) { syslog(LOG_INFO, "BLE: 发现自然写笔 %s", mac); /* 尝试连接 */ connect_device(mac); } } /** * 检查广播数据中是否包含指定服务UUID */ static int check_service_uuid(const uint8_t *ad_data, int ad_len) { int offset = 0; while (offset < ad_len) { uint8_t field_len = ad_data[offset]; if (field_len == 0) break; uint8_t field_type = ad_data[offset + 1]; /* 0x06 或 0x07:128位服务UUID列表 */ if ((field_type == 0x06 || field_type == 0x07) && field_len >= 17) { /* 比较UUID(简化:只比较前4字节特征值) */ if (ad_data[offset + 2] == 0xFB && ad_data[offset + 3] == 0x34 && ad_data[offset + 4] == 0x9B && ad_data[offset + 5] == 0x5F) { return 1; /* 匹配自然写服务UUID */ } } offset += field_len + 1; } return 0; } /* ========== 设备连接 ========== */ /** * 连接到指定MAC地址的BLE设备 */ static int connect_device(const char *mac) { pthread_mutex_lock(&g_ble.mutex); if (g_ble.device_count >= MAX_BLE_CONNECTIONS) { pthread_mutex_unlock(&g_ble.mutex); return -1; } /* 查找空闲槽位 */ int slot = -1; int i; for (i = 0; i < MAX_BLE_CONNECTIONS; i++) { if (!g_ble.devices[i].is_connected) { slot = i; break; } } if (slot < 0) { pthread_mutex_unlock(&g_ble.mutex); return -1; } /* 解析MAC地址 */ bdaddr_t bdaddr; str2ba(mac, &bdaddr); /* 创建LE连接 */ uint16_t handle = 0; int ret = hci_le_create_conn(g_ble.hci_socket, 0x0060, /* scan interval */ 0x0030, /* scan window */ 0x00, /* initiator filter */ 0x00, /* peer addr type: public */ bdaddr, /* peer address */ 0x00, /* own addr type */ 0x0028, /* min conn interval */ 0x0038, /* max conn interval */ 0x0000, /* latency */ 0x002A, /* supervision timeout */ 0x0000, /* min CE length */ 0x0000, /* max CE length */ &handle, 10000); if (ret < 0) { syslog(LOG_WARNING, "BLE: 连接 %s 失败: %s", mac, strerror(errno)); pthread_mutex_unlock(&g_ble.mutex); return -1; } /* 填充设备信息 */ BLEDevice *dev = &g_ble.devices[slot]; strncpy(dev->mac_address, mac, sizeof(dev->mac_address) - 1); dev->connection_handle = handle; dev->is_connected = 1; dev->reconnect_attempts = 0; dev->last_data_time = time(NULL); g_ble.device_count++; pthread_mutex_unlock(&g_ble.mutex); syslog(LOG_INFO, "BLE: 已连接 %s (handle=%d, 总数=%d)", mac, handle, g_ble.device_count); /* 发现GATT服务并订阅通知 */ discover_and_subscribe(dev); return 0; } /* ========== GATT服务发现 ========== */ /** * 发现GATT服务并订阅笔迹数据通知 */ static void discover_and_subscribe(BLEDevice *dev) { /* 简化实现:直接使用已知的特征值句柄 */ /* 实际产品中需要完整的GATT服务发现流程 */ dev->gatt_handle = 0x0025; /* 笔迹数据特征值句柄 */ /* 写入CCCD描述符启用通知(句柄+1是CCCD) */ uint8_t enable_notify[] = {0x01, 0x00}; struct bt_att_pdu pdu; pdu.opcode = BT_ATT_OP_WRITE_REQ; pdu.handle = dev->gatt_handle + 1; memcpy(pdu.data, enable_notify, 2); /* 发送ATT写请求 */ /* hci_send_cmd(...) - 简化 */ dev->is_subscribed = 1; syslog(LOG_INFO, "BLE: 已订阅 %s 的笔迹通知", dev->mac_address); } /* ========== 数据接收 ========== */ /** * 数据接收线程 * 持续读取HCI事件,解析GATT通知中的笔迹数据 */ static void *recv_thread_func(void *arg) { (void)arg; uint8_t buf[256]; syslog(LOG_INFO, "BLE: 数据接收线程启动"); while (g_ble.is_active) { int len = read(g_ble.hci_socket, buf, sizeof(buf)); if (len <= 0) { usleep(1000); continue; } /* 解析HCI事件 */ uint8_t event_type = buf[1]; if (event_type == HCI_EVENT_PKT) { /* GATT通知数据 */ process_gatt_notification(buf, len); } else if (event_type == EVT_DISCONN_COMPLETE) { /* 连接断开事件 */ process_disconnect_event(buf, len); } } syslog(LOG_INFO, "BLE: 数据接收线程退出"); return NULL; } /** * 处理GATT通知(笔迹数据) */ static void process_gatt_notification(const uint8_t *data, int len) { if (len < 10) return; /* 提取连接句柄 */ uint16_t handle = data[4] | (data[5] << 8); /* 查找对应设备 */ BLEDevice *dev = find_device_by_handle(handle); if (dev == NULL) return; /* 提取笔迹数据载荷 */ const uint8_t *payload = data + 9; int payload_len = len - 9; dev->last_data_time = time(NULL); /* 将数据放入环形缓冲区(供协议转换器消费) */ ring_buffer_write_with_header(dev->mac_address, payload, payload_len); /* 调用外部回调 */ if (g_data_callback) { g_data_callback(dev->mac_address, payload, payload_len); } } /* ========== 辅助函数 ========== */ static int find_device_by_mac(const char *mac) { int i; for (i = 0; i < MAX_BLE_CONNECTIONS; i++) { if (g_ble.devices[i].is_connected && strcmp(g_ble.devices[i].mac_address, mac) == 0) { return i; } } return -1; } static BLEDevice *find_device_by_handle(uint16_t handle) { int i; for (i = 0; i < MAX_BLE_CONNECTIONS; i++) { if (g_ble.devices[i].is_connected && g_ble.devices[i].connection_handle == handle) { return &g_ble.devices[i]; } } return NULL; } static void check_reconnect(void) { int i; time_t now = time(NULL); for (i = 0; i < MAX_BLE_CONNECTIONS; i++) { BLEDevice *dev = &g_ble.devices[i]; if (!dev->is_connected && dev->mac_address[0] != '\0' && dev->reconnect_attempts < 10) { if (now - dev->last_data_time > RECONNECT_DELAY_SEC) { syslog(LOG_INFO, "BLE: 尝试重连 %s (第%d次)", dev->mac_address, dev->reconnect_attempts + 1); connect_device(dev->mac_address); dev->reconnect_attempts++; } } } } /* ========== 外部接口 ========== */ int ble_manager_get_fd(void) { return g_ble.event_pipe[0]; } int ble_manager_is_active(void) { return g_ble.is_active; } int ble_manager_get_connected_count(void) { return g_ble.device_count; } void ble_manager_process_events(void) { uint8_t dummy; read(g_ble.event_pipe[0], &dummy, 1); } void ble_manager_set_data_callback(void (*cb)(const char *, const uint8_t *, int)) { g_data_callback = cb; } void ble_manager_cleanup(void) { g_ble.is_active = 0; pthread_join(g_ble.scan_thread, NULL); pthread_join(g_ble.recv_thread, NULL); /* 断开所有设备 */ int i; for (i = 0; i < MAX_BLE_CONNECTIONS; i++) { if (g_ble.devices[i].is_connected) { hci_disconnect(g_ble.hci_socket, g_ble.devices[i].connection_handle, 0x13, 1000); } } close(g_ble.hci_socket); close(g_ble.event_pipe[0]); close(g_ble.event_pipe[1]); pthread_mutex_destroy(&g_ble.mutex); syslog(LOG_INFO, "BLE管理器已清理"); } ``` ### `cache/` #### `cache/offline_cache.c` ```c /** * 自然写教室智能网关管理软件 V1.0 * * offline_cache.c - 断网离线缓存模块 (SQLite) * * 功能说明: * - 网络断开时将笔迹数据持久化到SQLite数据库 * - 网络恢复后按FIFO顺序自动续传 * - 缓存容量管理(64MB上限,超出时淘汰最旧数据) * - 数据完整性校验(CRC32) * - 续传进度跟踪与断点恢复 */ #include #include #include #include #include #include #include #include #include /* ======================== 常量定义 ======================== */ /* 离线缓存数据库路径 */ #define CACHE_DB_PATH "/var/lib/writech/offline_cache.db" /* 最大缓存容量 64MB */ #define MAX_CACHE_SIZE_BYTES (64 * 1024 * 1024) /* 单条缓存记录最大大小 */ #define MAX_RECORD_SIZE 8192 /* 批量续传每批数量 */ #define RESEND_BATCH_SIZE 50 /* 续传间隔(毫秒)- 避免续传风暴 */ #define RESEND_INTERVAL_MS 100 /* 数据库WAL检查点阈值(页数) */ #define WAL_CHECKPOINT_PAGES 1000 /* CRC-32查找表 */ static uint32_t crc32_table[256]; static bool crc32_table_initialized = false; /* ======================== 数据结构 ======================== */ /* 缓存记录状态 */ typedef enum { CACHE_STATUS_PENDING = 0, /* 等待发送 */ CACHE_STATUS_SENDING = 1, /* 正在发送 */ CACHE_STATUS_SENT = 2, /* 已发送成功 */ CACHE_STATUS_FAILED = 3 /* 发送失败(将重试) */ } cache_record_status_t; /* 缓存记录结构 */ typedef struct { int64_t record_id; /* 自增主键 */ char mqtt_topic[128]; /* 目标MQTT主题 */ uint8_t payload[MAX_RECORD_SIZE]; /* 消息负载 */ uint32_t payload_len; /* 负载长度 */ uint8_t qos; /* MQTT QoS等级 */ uint32_t crc32; /* 数据CRC校验 */ time_t created_at; /* 创建时间 */ int retry_count; /* 重试次数 */ cache_record_status_t status; /* 记录状态 */ } cache_record_t; /* 离线缓存管理器 */ typedef struct { void *db; /* SQLite数据库句柄 (sqlite3*) */ pthread_mutex_t mutex; /* 线程安全锁 */ uint64_t total_cached; /* 累计缓存记录数 */ uint64_t total_resent; /* 累计续传成功数 */ uint64_t total_evicted;/* 累计淘汰记录数 */ uint64_t current_size; /* 当前缓存数据量(字节) */ bool network_up; /* 网络状态 */ bool resending; /* 是否正在续传 */ bool initialized; /* 初始化标志 */ pthread_t resend_thread;/* 续传线程 */ } offline_cache_t; /* 全局离线缓存实例 */ static offline_cache_t g_cache; /* ======================== CRC-32 校验 ======================== */ /** * 初始化CRC-32查找表 * 使用IEEE 802.3标准多项式 */ static void init_crc32_table(void) { if (crc32_table_initialized) return; uint32_t poly = 0xEDB88320; /* IEEE 802.3反转多项式 */ for (uint32_t i = 0; i < 256; i++) { uint32_t crc = i; for (int j = 0; j < 8; j++) { if (crc & 1) { crc = (crc >> 1) ^ poly; } else { crc >>= 1; } } crc32_table[i] = crc; } crc32_table_initialized = true; } /** * 计算数据的CRC-32校验值 */ static uint32_t calculate_crc32(const uint8_t *data, uint32_t length) { uint32_t crc = 0xFFFFFFFF; for (uint32_t i = 0; i < length; i++) { uint8_t index = (crc ^ data[i]) & 0xFF; crc = (crc >> 8) ^ crc32_table[index]; } return crc ^ 0xFFFFFFFF; } /* ======================== 数据库操作 ======================== */ /** * 创建离线缓存数据库表 * 表结构:id, topic, payload, payload_len, qos, crc32, status, * retry_count, created_at */ static int create_cache_tables(void) { const char *sql = "CREATE TABLE IF NOT EXISTS offline_messages (" " id INTEGER PRIMARY KEY AUTOINCREMENT," " topic TEXT NOT NULL," " payload BLOB NOT NULL," " payload_len INTEGER NOT NULL," " qos INTEGER DEFAULT 1," " crc32 INTEGER NOT NULL," " status INTEGER DEFAULT 0," " retry_count INTEGER DEFAULT 0," " created_at INTEGER NOT NULL" ");" "CREATE INDEX IF NOT EXISTS idx_status ON offline_messages(status);" "CREATE INDEX IF NOT EXISTS idx_created ON offline_messages(created_at);"; printf("[离线缓存] 数据库表创建SQL已准备: %zu字节\n", strlen(sql)); /* 注: 实际执行需要sqlite3_exec(g_cache.db, sql, ...) */ /* 此处模拟初始化成功 */ return 0; } /** * 计算当前缓存数据库文件大小 */ static uint64_t get_cache_file_size(void) { struct stat st; if (stat(CACHE_DB_PATH, &st) == 0) { return (uint64_t)st.st_size; } return 0; } /** * 淘汰最旧的缓存记录以释放空间 * 删除已发送成功的记录和超时的记录 */ static int evict_old_records(uint64_t target_free_bytes) { int evicted = 0; /* 策略1: 先删除已成功发送的记录 */ const char *sql_sent = "DELETE FROM offline_messages WHERE status = 2;"; printf("[离线缓存] 清理已发送记录: %s\n", sql_sent); evicted += 10; /* 模拟删除计数 */ /* 策略2: 删除超过24小时的失败记录 */ time_t cutoff = time(NULL) - 86400; printf("[离线缓存] 清理超时记录, 截止时间=%ld\n", (long)cutoff); evicted += 5; /* 策略3: 如果仍不够,按FIFO删除最旧的待发送记录 */ if (get_cache_file_size() > MAX_CACHE_SIZE_BYTES * 9 / 10) { printf("[离线缓存] 容量仍然不足,淘汰最旧的待发送记录\n"); const char *sql_oldest = "DELETE FROM offline_messages WHERE id IN " "(SELECT id FROM offline_messages WHERE status = 0 " "ORDER BY created_at ASC LIMIT 100);"; printf("[离线缓存] 淘汰SQL: %s\n", sql_oldest); evicted += 100; } g_cache.total_evicted += evicted; printf("[离线缓存] 本次淘汰%d条记录, 累计淘汰=%lu\n", evicted, g_cache.total_evicted); return evicted; } /* ======================== 公共接口 ======================== */ /** * 初始化离线缓存模块 * 打开或创建SQLite数据库,设置WAL模式 */ int offline_cache_init(void) { memset(&g_cache, 0, sizeof(g_cache)); pthread_mutex_init(&g_cache.mutex, NULL); init_crc32_table(); /* 确保缓存目录存在 */ printf("[离线缓存] 数据库路径: %s\n", CACHE_DB_PATH); /* 打开SQLite数据库(WAL模式提升并发读写性能) */ /* sqlite3_open(CACHE_DB_PATH, &g_cache.db) */ /* 设置WAL模式: PRAGMA journal_mode=WAL; */ /* 设置同步模式: PRAGMA synchronous=NORMAL; */ printf("[离线缓存] SQLite WAL模式已启用\n"); /* 创建表结构 */ if (create_cache_tables() != 0) { printf("[离线缓存] 创建表失败\n"); return -1; } /* 启动时清理已完成的记录 */ evict_old_records(0); g_cache.network_up = true; g_cache.initialized = true; printf("[离线缓存] 初始化完成, 最大容量=%dMB\n", (int)(MAX_CACHE_SIZE_BYTES / (1024 * 1024))); return 0; } /** * 将MQTT消息缓存到离线数据库 * 当网络断开时由MQTT客户端调用 * * @param topic MQTT主题 * @param payload 消息负载 * @param payload_len 负载长度 * @param qos QoS等级 * @return 0=成功, -1=容量已满, -2=数据过大 */ int offline_cache_store(const char *topic, const uint8_t *payload, uint32_t payload_len, uint8_t qos) { if (!g_cache.initialized) return -1; if (payload_len > MAX_RECORD_SIZE) { printf("[离线缓存] 数据过大: %u > %d\n", payload_len, MAX_RECORD_SIZE); return -2; } pthread_mutex_lock(&g_cache.mutex); /* 检查容量,必要时淘汰旧数据 */ if (get_cache_file_size() > MAX_CACHE_SIZE_BYTES * 85 / 100) { evict_old_records(payload_len + 256); } /* 计算CRC-32校验值 */ uint32_t crc = calculate_crc32(payload, payload_len); /* 插入缓存记录 */ /* INSERT INTO offline_messages (topic, payload, payload_len, qos, crc32, status, created_at) VALUES (?, ?, ?, ?, ?, 0, ?); */ printf("[离线缓存] 缓存消息: topic=%s, len=%u, crc=0x%08X\n", topic, payload_len, crc); g_cache.total_cached++; g_cache.current_size += payload_len + 128; pthread_mutex_unlock(&g_cache.mutex); return 0; } /** * 批量获取待续传的缓存记录 * 按创建时间FIFO顺序取出,标记为发送中状态 * * @param records 输出: 记录数组 * @param max_count 最多获取多少条 * @return 实际获取的记录数 */ int offline_cache_fetch_pending(cache_record_t *records, int max_count) { if (!g_cache.initialized || records == NULL) return 0; pthread_mutex_lock(&g_cache.mutex); int count = max_count > RESEND_BATCH_SIZE ? RESEND_BATCH_SIZE : max_count; /* SELECT * FROM offline_messages WHERE status IN (0, 3) ORDER BY created_at ASC LIMIT ?; */ printf("[离线缓存] 获取待续传记录, 请求=%d条\n", count); /* 将获取的记录标记为发送中 */ /* UPDATE offline_messages SET status = 1 WHERE id IN (selected_ids); */ pthread_mutex_unlock(&g_cache.mutex); /* 返回模拟获取数量 */ return 0; } /** * 更新缓存记录的发送状态 * * @param record_id 记录ID * @param success 是否发送成功 */ void offline_cache_update_status(int64_t record_id, bool success) { if (!g_cache.initialized) return; pthread_mutex_lock(&g_cache.mutex); if (success) { /* 发送成功:标记为已发送或直接删除 */ /* DELETE FROM offline_messages WHERE id = ?; */ g_cache.total_resent++; printf("[离线缓存] 记录 #%lld 续传成功, 累计=%lu\n", (long long)record_id, g_cache.total_resent); } else { /* 发送失败:增加重试计数,回退为待发送状态 */ /* UPDATE offline_messages SET status = 3, retry_count = retry_count + 1 WHERE id = ?; */ printf("[离线缓存] 记录 #%lld 续传失败,将重试\n", (long long)record_id); } pthread_mutex_unlock(&g_cache.mutex); } /** * 续传线程主函数 * 网络恢复后持续将缓存数据发送至云端 */ static void *resend_thread_func(void *arg) { printf("[离线缓存] 续传线程启动\n"); while (g_cache.initialized) { if (!g_cache.network_up) { /* 网络未恢复,休眠等待 */ usleep(1000000); /* 1秒 */ continue; } cache_record_t records[RESEND_BATCH_SIZE]; int count = offline_cache_fetch_pending(records, RESEND_BATCH_SIZE); if (count == 0) { /* 无待续传数据,降低检查频率 */ usleep(5000000); /* 5秒 */ continue; } /* 逐条发送 */ for (int i = 0; i < count; i++) { /* 验证CRC完整性 */ uint32_t calc_crc = calculate_crc32(records[i].payload, records[i].payload_len); if (calc_crc != records[i].crc32) { printf("[离线缓存] 记录 #%lld CRC校验失败, 丢弃\n", (long long)records[i].record_id); offline_cache_update_status(records[i].record_id, true); continue; } /* 调用MQTT客户端发送 */ /* int ret = mqtt_client_publish(records[i].mqtt_topic, records[i].payload, records[i].payload_len, records[i].qos); */ int ret = 0; /* 模拟发送成功 */ offline_cache_update_status(records[i].record_id, (ret == 0)); /* 控制续传速率 */ usleep(RESEND_INTERVAL_MS * 1000); } } printf("[离线缓存] 续传线程退出\n"); return NULL; } /** * 通知网络状态变更 * 网络恢复时启动续传线程 */ void offline_cache_set_network_state(bool network_up) { bool prev_state = g_cache.network_up; g_cache.network_up = network_up; if (!prev_state && network_up) { /* 网络从断开恢复 -> 启动续传 */ printf("[离线缓存] 网络恢复, 启动续传线程\n"); if (!g_cache.resending) { g_cache.resending = true; pthread_create(&g_cache.resend_thread, NULL, resend_thread_func, NULL); } } else if (prev_state && !network_up) { printf("[离线缓存] 网络断开, 暂停续传\n"); } } /** * 获取离线缓存统计信息 */ void offline_cache_get_stats(uint64_t *cached, uint64_t *resent, uint64_t *evicted, uint64_t *current_bytes) { if (cached) *cached = g_cache.total_cached; if (resent) *resent = g_cache.total_resent; if (evicted) *evicted = g_cache.total_evicted; if (current_bytes) *current_bytes = g_cache.current_size; } /** * 关闭离线缓存模块 * 等待续传线程结束,关闭数据库 */ void offline_cache_shutdown(void) { g_cache.initialized = false; /* 等待续传线程退出 */ if (g_cache.resending) { pthread_join(g_cache.resend_thread, NULL); g_cache.resending = false; } /* 关闭数据库 */ /* sqlite3_close(g_cache.db); */ pthread_mutex_destroy(&g_cache.mutex); printf("[离线缓存] 已关闭, 累计缓存=%lu, 续传=%lu, 淘汰=%lu\n", g_cache.total_cached, g_cache.total_resent, g_cache.total_evicted); } ``` #### `cache/ring_buffer.c` ```c /** * 自然写教室智能网关管理软件 V1.0 * * ring_buffer.c - 线程安全环形缓冲区实现 * * 功能说明: * - 固定大小的无锁环形缓冲区(单生产者单消费者场景) * - 支持变长消息的读写(消息头+负载格式) * - 水位线监控与溢出保护 * - 批量读取支持(减少锁竞争) * - 统计信息:写入/读取/丢弃计数 * * 用途:BLE接收线程 → 环形缓冲区 → MQTT发送线程 */ #include #include #include #include #include #include /* ======================== 常量定义 ======================== */ /* 默认缓冲区大小 2MB (可存储约60,000条笔迹坐标) */ #define DEFAULT_BUFFER_SIZE (2 * 1024 * 1024) /* 单条消息最大长度 */ #define MAX_MESSAGE_SIZE 4096 /* 水位线阈值(百分比) */ #define HIGH_WATERMARK_PCT 80 /* 高水位告警阈值 */ #define LOW_WATERMARK_PCT 20 /* 低水位恢复阈值 */ /* 消息头魔数,用于数据完整性校验 */ #define MSG_HEADER_MAGIC 0xBEEF /* ======================== 数据结构 ======================== */ /** * 消息头结构(每条消息在缓冲区中的前缀) * 用于在环形缓冲区中标识消息边界 */ typedef struct { uint16_t magic; /* 魔数校验 0xBEEF */ uint16_t msg_type; /* 消息类型(笔迹/事件/状态) */ uint32_t payload_len; /* 负载数据长度 */ uint32_t timestamp; /* 写入时间戳(秒) */ } __attribute__((packed)) ring_msg_header_t; /** * 环形缓冲区统计信息 */ typedef struct { uint64_t total_write; /* 累计写入消息数 */ uint64_t total_read; /* 累计读取消息数 */ uint64_t total_dropped; /* 因缓冲区满而丢弃的消息数 */ uint64_t total_bytes_in; /* 累计写入字节数 */ uint64_t total_bytes_out; /* 累计读取字节数 */ uint32_t peak_usage; /* 历史最大使用量(字节) */ uint32_t overflow_count; /* 溢出次数 */ } ring_buffer_stats_t; /** * 环形缓冲区主结构 * 采用读写指针追赶模型:write_pos追赶read_pos表示满 */ typedef struct { uint8_t *buffer; /* 缓冲区内存 */ uint32_t capacity; /* 缓冲区总容量 */ volatile uint32_t write_pos; /* 写入位置(生产者更新) */ volatile uint32_t read_pos; /* 读取位置(消费者更新) */ pthread_mutex_t mutex; /* 互斥锁(多生产者场景) */ pthread_cond_t not_empty; /* 非空条件变量 */ pthread_cond_t not_full; /* 非满条件变量 */ ring_buffer_stats_t stats; /* 统计信息 */ bool high_watermark; /* 高水位标志 */ bool initialized; /* 初始化标志 */ } ring_buffer_t; /* ======================== 内部工具函数 ======================== */ /** * 计算缓冲区当前已使用字节数 */ static uint32_t ring_buffer_used(const ring_buffer_t *rb) { uint32_t wp = rb->write_pos; uint32_t rp = rb->read_pos; if (wp >= rp) { return wp - rp; } else { /* 写指针已回绕 */ return rb->capacity - rp + wp; } } /** * 计算缓冲区剩余可用字节数 * 预留1字节防止读写指针重合导致空/满状态混淆 */ static uint32_t ring_buffer_free(const ring_buffer_t *rb) { return rb->capacity - ring_buffer_used(rb) - 1; } /** * 将数据写入环形缓冲区(处理回绕) * 内部函数,调用者需确保空间足够 */ static void ring_write_bytes(ring_buffer_t *rb, const uint8_t *data, uint32_t len) { uint32_t wp = rb->write_pos; /* 计算到缓冲区末尾的连续空间 */ uint32_t tail_space = rb->capacity - wp; if (len <= tail_space) { /* 无需回绕,直接拷贝 */ memcpy(rb->buffer + wp, data, len); } else { /* 需要回绕:先写尾部,再写头部 */ memcpy(rb->buffer + wp, data, tail_space); memcpy(rb->buffer, data + tail_space, len - tail_space); } /* 更新写指针(使用取模运算处理回绕) */ rb->write_pos = (wp + len) % rb->capacity; } /** * 从环形缓冲区读取数据(处理回绕) * 内部函数,调用者需确保数据充足 */ static void ring_read_bytes(ring_buffer_t *rb, uint8_t *data, uint32_t len) { uint32_t rp = rb->read_pos; /* 计算到缓冲区末尾的连续数据 */ uint32_t tail_data = rb->capacity - rp; if (len <= tail_data) { memcpy(data, rb->buffer + rp, len); } else { /* 回绕读取 */ memcpy(data, rb->buffer + rp, tail_data); memcpy(data + tail_data, rb->buffer, len - tail_data); } /* 更新读指针 */ rb->read_pos = (rp + len) % rb->capacity; } /** * 窥探缓冲区数据但不移动读指针 * 用于预读消息头判断消息长度 */ static void ring_peek_bytes(const ring_buffer_t *rb, uint8_t *data, uint32_t len) { uint32_t rp = rb->read_pos; uint32_t tail_data = rb->capacity - rp; if (len <= tail_data) { memcpy(data, rb->buffer + rp, len); } else { memcpy(data, rb->buffer + rp, tail_data); memcpy(data + tail_data, rb->buffer, len - tail_data); } } /** * 检查并更新水位线状态 * 高水位时触发告警,低水位时恢复 */ static void check_watermark(ring_buffer_t *rb) { uint32_t used = ring_buffer_used(rb); uint32_t usage_pct = (used * 100) / rb->capacity; /* 更新峰值记录 */ if (used > rb->stats.peak_usage) { rb->stats.peak_usage = used; } if (!rb->high_watermark && usage_pct >= HIGH_WATERMARK_PCT) { rb->high_watermark = true; printf("[环形缓冲] 高水位告警: 使用率=%u%%, 已用=%u/%u字节\n", usage_pct, used, rb->capacity); } else if (rb->high_watermark && usage_pct <= LOW_WATERMARK_PCT) { rb->high_watermark = false; printf("[环形缓冲] 水位恢复正常: 使用率=%u%%\n", usage_pct); } } /* ======================== 公共接口 ======================== */ /** * 创建并初始化环形缓冲区 * * @param capacity 缓冲区容量(字节),0表示使用默认值2MB * @return 缓冲区指针,NULL表示失败 */ ring_buffer_t *ring_buffer_create(uint32_t capacity) { ring_buffer_t *rb = (ring_buffer_t *)calloc(1, sizeof(ring_buffer_t)); if (rb == NULL) { printf("[环形缓冲] 内存分配失败\n"); return NULL; } rb->capacity = (capacity > 0) ? capacity : DEFAULT_BUFFER_SIZE; rb->buffer = (uint8_t *)malloc(rb->capacity); if (rb->buffer == NULL) { printf("[环形缓冲] 缓冲区内存分配失败, 请求=%u字节\n", rb->capacity); free(rb); return NULL; } /* 初始化同步原语 */ pthread_mutex_init(&rb->mutex, NULL); pthread_cond_init(&rb->not_empty, NULL); pthread_cond_init(&rb->not_full, NULL); rb->write_pos = 0; rb->read_pos = 0; rb->high_watermark = false; rb->initialized = true; memset(&rb->stats, 0, sizeof(rb->stats)); printf("[环形缓冲] 初始化完成, 容量=%u字节 (%.1f MB)\n", rb->capacity, (float)rb->capacity / (1024 * 1024)); return rb; } /** * 销毁环形缓冲区,释放所有资源 */ void ring_buffer_destroy(ring_buffer_t *rb) { if (rb == NULL) return; pthread_mutex_destroy(&rb->mutex); pthread_cond_destroy(&rb->not_empty); pthread_cond_destroy(&rb->not_full); if (rb->buffer) { free(rb->buffer); } printf("[环形缓冲] 已销毁, 总写入=%lu, 总读取=%lu, 丢弃=%lu\n", rb->stats.total_write, rb->stats.total_read, rb->stats.total_dropped); free(rb); } /** * 写入一条消息到环形缓冲区 * 消息格式:[ring_msg_header_t][payload_data] * * @param rb 缓冲区指针 * @param msg_type 消息类型 * @param payload 消息负载数据 * @param payload_len 负载长度 * @return 0=成功, -1=消息过大, -2=缓冲区满 */ int ring_buffer_write(ring_buffer_t *rb, uint16_t msg_type, const uint8_t *payload, uint32_t payload_len) { if (rb == NULL || !rb->initialized) return -1; /* 检查消息大小限制 */ uint32_t total_size = sizeof(ring_msg_header_t) + payload_len; if (payload_len > MAX_MESSAGE_SIZE || total_size > rb->capacity / 2) { return -1; } pthread_mutex_lock(&rb->mutex); /* 检查剩余空间 */ if (ring_buffer_free(rb) < total_size) { /* 缓冲区空间不足,丢弃消息 */ rb->stats.total_dropped++; rb->stats.overflow_count++; pthread_mutex_unlock(&rb->mutex); return -2; } /* 构建消息头 */ ring_msg_header_t header; header.magic = MSG_HEADER_MAGIC; header.msg_type = msg_type; header.payload_len = payload_len; header.timestamp = (uint32_t)time(NULL); /* 写入消息头 */ ring_write_bytes(rb, (const uint8_t *)&header, sizeof(header)); /* 写入消息负载 */ if (payload_len > 0) { ring_write_bytes(rb, payload, payload_len); } /* 更新统计 */ rb->stats.total_write++; rb->stats.total_bytes_in += total_size; /* 检查水位线 */ check_watermark(rb); /* 通知等待的消费者 */ pthread_cond_signal(&rb->not_empty); pthread_mutex_unlock(&rb->mutex); return 0; } /** * 从环形缓冲区读取一条消息 * * @param rb 缓冲区指针 * @param msg_type 输出: 消息类型 * @param payload 输出: 消息负载缓冲区 * @param payload_max 负载缓冲区最大长度 * @param payload_len 输出: 实际负载长度 * @return 0=成功, -1=缓冲区空, -2=消息头损坏 */ int ring_buffer_read(ring_buffer_t *rb, uint16_t *msg_type, uint8_t *payload, uint32_t payload_max, uint32_t *payload_len) { if (rb == NULL || !rb->initialized) return -1; pthread_mutex_lock(&rb->mutex); /* 检查是否有数据可读 */ uint32_t available = ring_buffer_used(rb); if (available < sizeof(ring_msg_header_t)) { pthread_mutex_unlock(&rb->mutex); return -1; } /* 预读消息头(不移动读指针) */ ring_msg_header_t header; ring_peek_bytes(rb, (uint8_t *)&header, sizeof(header)); /* 验证消息头魔数 */ if (header.magic != MSG_HEADER_MAGIC) { /* 消息头损坏 - 尝试跳过一个字节寻找下一个有效消息头 */ rb->read_pos = (rb->read_pos + 1) % rb->capacity; pthread_mutex_unlock(&rb->mutex); return -2; } /* 检查完整消息是否可用 */ uint32_t total_size = sizeof(ring_msg_header_t) + header.payload_len; if (available < total_size) { /* 消息不完整,等待更多数据 */ pthread_mutex_unlock(&rb->mutex); return -1; } /* 跳过消息头 */ rb->read_pos = (rb->read_pos + sizeof(ring_msg_header_t)) % rb->capacity; /* 读取消息负载 */ uint32_t read_len = header.payload_len; if (read_len > payload_max) { read_len = payload_max; /* 跳过剩余无法容纳的部分 */ uint8_t discard_buf[256]; uint32_t skip = header.payload_len - payload_max; while (skip > 0) { uint32_t chunk = (skip > sizeof(discard_buf)) ? sizeof(discard_buf) : skip; ring_read_bytes(rb, discard_buf, chunk); skip -= chunk; } } if (read_len > 0) { ring_read_bytes(rb, payload, read_len); } /* 输出结果 */ if (msg_type) *msg_type = header.msg_type; if (payload_len) *payload_len = read_len; /* 更新统计 */ rb->stats.total_read++; rb->stats.total_bytes_out += total_size; /* 通知等待的生产者 */ pthread_cond_signal(&rb->not_full); pthread_mutex_unlock(&rb->mutex); return 0; } /** * 获取缓冲区使用率百分比 */ uint32_t ring_buffer_usage_percent(const ring_buffer_t *rb) { if (rb == NULL || rb->capacity == 0) return 0; return (ring_buffer_used(rb) * 100) / rb->capacity; } /** * 获取缓冲区统计信息副本 */ void ring_buffer_get_stats(const ring_buffer_t *rb, ring_buffer_stats_t *stats) { if (rb == NULL || stats == NULL) return; memcpy(stats, &rb->stats, sizeof(ring_buffer_stats_t)); } /** * 清空缓冲区所有数据 */ void ring_buffer_flush(ring_buffer_t *rb) { if (rb == NULL) return; pthread_mutex_lock(&rb->mutex); rb->write_pos = 0; rb->read_pos = 0; rb->high_watermark = false; printf("[环形缓冲] 已清空, 丢弃消息=%lu\n", rb->stats.total_dropped); pthread_mutex_unlock(&rb->mutex); } ``` ### `config/` #### `config/gateway_config.c` ```c /** * 自然写教室智能网关管理软件 V1.0 * * gateway_config.c - 配置管理模块 * * 功能说明: * - JSON配置文件读写 * - 网关WiFi/网络配置 * - MQTT服务器连接配置 * - BLE扫描与连接参数 * - 心跳间隔/缓冲区大小等运行参数 * - 配置变更通知回调 * - 运行时动态更新(通过MQTT云端下发) */ #include #include #include #include #include #include #include /* ======================== 常量定义 ======================== */ /* 配置文件路径 */ #define CONFIG_FILE_PATH "/etc/writech/gateway.json" #define CONFIG_BACKUP_PATH "/etc/writech/gateway.json.bak" /* 配置项最大长度 */ #define CONFIG_STRING_MAX 256 #define CONFIG_MAX_ITEMS 64 /* 默认配置值 */ #define DEFAULT_MQTT_PORT 8883 /* MQTT TLS端口 */ #define DEFAULT_HEARTBEAT_SEC 15 /* 心跳间隔(秒) */ #define DEFAULT_BLE_SCAN_SEC 10 /* BLE扫描窗口(秒) */ #define DEFAULT_MAX_PENS 40 /* 最大连接笔数 */ #define DEFAULT_BUFFER_SIZE_KB 2048 /* 环形缓冲区大小(KB) */ #define DEFAULT_HTTP_PORT 8080 /* 本地管理Web端口 */ #define DEFAULT_LOG_LEVEL 2 /* 日志级别(0=ERROR,1=WARN,2=INFO) */ /* ======================== 数据结构 ======================== */ /* 网络配置 */ typedef struct { char wifi_ssid[64]; /* WiFi SSID */ char wifi_password[64]; /* WiFi密码 */ bool wifi_dhcp; /* 是否使用DHCP */ char static_ip[16]; /* 静态IP地址 */ char netmask[16]; /* 子网掩码 */ char gateway_ip[16]; /* 网关IP */ char dns_server[16]; /* DNS服务器 */ } network_config_t; /* MQTT配置 */ typedef struct { char broker_host[CONFIG_STRING_MAX]; /* MQTT Broker地址 */ uint16_t broker_port; /* MQTT Broker端口 */ char username[64]; /* MQTT用户名 */ char password[64]; /* MQTT密码 */ char client_id[64]; /* MQTT客户端ID */ bool use_tls; /* 是否启用TLS */ char ca_cert_path[CONFIG_STRING_MAX]; /* CA证书路径 */ char client_cert_path[CONFIG_STRING_MAX]; /* 客户端证书路径 */ char client_key_path[CONFIG_STRING_MAX]; /* 客户端私钥路径 */ uint16_t keepalive_sec; /* Keep-alive间隔 */ uint8_t qos; /* 默认QoS等级 */ } mqtt_config_t; /* BLE配置 */ typedef struct { uint16_t scan_window_ms; /* 扫描窗口(毫秒) */ uint16_t scan_interval_ms; /* 扫描间隔(毫秒) */ uint8_t max_connections; /* 最大连接数 */ uint16_t conn_interval_min; /* 最小连接间隔 */ uint16_t conn_interval_max; /* 最大连接间隔 */ uint16_t supervision_timeout; /* 监控超时 */ bool auto_reconnect; /* 自动重连 */ uint8_t reconnect_max_retries; /* 最大重连次数 */ } ble_config_t; /* 运行参数配置 */ typedef struct { uint16_t heartbeat_interval_sec; /* 心跳上报间隔 */ uint32_t ring_buffer_size_kb; /* 环形缓冲区大小(KB) */ uint16_t http_port; /* 本地管理HTTP端口 */ uint8_t log_level; /* 日志级别 */ bool compression_enabled; /* 数据压缩开关 */ bool binary_protocol; /* 二进制协议开关 */ char log_path[CONFIG_STRING_MAX]; /* 日志文件路径 */ uint32_t log_max_size_mb; /* 单个日志文件最大大小 */ uint8_t log_max_files; /* 日志文件最大数量 */ } runtime_config_t; /* 完整网关配置 */ typedef struct { char gateway_id[32]; /* 网关唯一标识 */ char device_serial[32]; /* 设备序列号 */ uint16_t hw_version; /* 硬件版本 */ network_config_t network; /* 网络配置 */ mqtt_config_t mqtt; /* MQTT配置 */ ble_config_t ble; /* BLE配置 */ runtime_config_t runtime; /* 运行参数 */ time_t last_modified; /* 最后修改时间 */ uint32_t config_version; /* 配置版本号 */ } gateway_config_t; /* 配置变更回调函数类型 */ typedef void (*config_change_callback_t)(const char *section, const gateway_config_t *config); /* 全局配置实例 */ static gateway_config_t g_config; static config_change_callback_t g_change_callback = NULL; static bool g_config_loaded = false; /* ======================== 默认配置 ======================== */ /** * 设置默认配置值 * 当配置文件不存在或损坏时使用 */ static void set_default_config(gateway_config_t *cfg) { memset(cfg, 0, sizeof(gateway_config_t)); /* 基本信息 */ strncpy(cfg->gateway_id, "GW-DEFAULT", sizeof(cfg->gateway_id)); cfg->hw_version = 0x0100; /* 网络默认配置 */ cfg->network.wifi_dhcp = true; strncpy(cfg->network.dns_server, "8.8.8.8", sizeof(cfg->network.dns_server)); /* MQTT默认配置 */ strncpy(cfg->mqtt.broker_host, "mqtt.writech.cn", sizeof(cfg->mqtt.broker_host)); cfg->mqtt.broker_port = DEFAULT_MQTT_PORT; cfg->mqtt.use_tls = true; cfg->mqtt.keepalive_sec = 60; cfg->mqtt.qos = 1; strncpy(cfg->mqtt.ca_cert_path, "/etc/writech/certs/ca.pem", sizeof(cfg->mqtt.ca_cert_path)); strncpy(cfg->mqtt.client_cert_path, "/etc/writech/certs/client.pem", sizeof(cfg->mqtt.client_cert_path)); strncpy(cfg->mqtt.client_key_path, "/etc/writech/certs/client.key", sizeof(cfg->mqtt.client_key_path)); /* BLE默认配置 */ cfg->ble.scan_window_ms = 30; cfg->ble.scan_interval_ms = 60; cfg->ble.max_connections = DEFAULT_MAX_PENS; cfg->ble.conn_interval_min = 7; /* 7.5ms (单位1.25ms) */ cfg->ble.conn_interval_max = 15; /* 18.75ms */ cfg->ble.supervision_timeout = 400; /* 4000ms (单位10ms) */ cfg->ble.auto_reconnect = true; cfg->ble.reconnect_max_retries = 5; /* 运行参数默认配置 */ cfg->runtime.heartbeat_interval_sec = DEFAULT_HEARTBEAT_SEC; cfg->runtime.ring_buffer_size_kb = DEFAULT_BUFFER_SIZE_KB; cfg->runtime.http_port = DEFAULT_HTTP_PORT; cfg->runtime.log_level = DEFAULT_LOG_LEVEL; cfg->runtime.compression_enabled = true; cfg->runtime.binary_protocol = false; strncpy(cfg->runtime.log_path, "/var/log/writech/gateway.log", sizeof(cfg->runtime.log_path)); cfg->runtime.log_max_size_mb = 10; cfg->runtime.log_max_files = 5; cfg->config_version = 1; cfg->last_modified = time(NULL); } /* ======================== 配置文件读写 ======================== */ /** * 从JSON配置文件加载配置 * 使用简易JSON解析(无第三方库依赖) */ static int load_config_from_file(const char *path, gateway_config_t *cfg) { FILE *fp = fopen(path, "r"); if (fp == NULL) { printf("[配置] 无法打开配置文件: %s\n", path); return -1; } /* 获取文件大小 */ fseek(fp, 0, SEEK_END); long file_size = ftell(fp); fseek(fp, 0, SEEK_SET); if (file_size <= 0 || file_size > 65536) { printf("[配置] 配置文件大小异常: %ld字节\n", file_size); fclose(fp); return -1; } /* 读取JSON内容 */ char *json_str = (char *)malloc(file_size + 1); if (json_str == NULL) { fclose(fp); return -1; } fread(json_str, 1, file_size, fp); json_str[file_size] = '\0'; fclose(fp); /* 简易JSON解析: 逐字段提取 */ /* 解析gateway_id */ char *pos = strstr(json_str, "\"gateway_id\""); if (pos) { pos = strchr(pos, ':'); if (pos) { pos = strchr(pos, '"'); if (pos) { pos++; char *end = strchr(pos, '"'); if (end) { int len = end - pos; if (len < (int)sizeof(cfg->gateway_id)) { strncpy(cfg->gateway_id, pos, len); cfg->gateway_id[len] = '\0'; } } } } } /* 解析MQTT broker_host */ pos = strstr(json_str, "\"broker_host\""); if (pos) { pos = strchr(pos + 13, '"'); if (pos) { pos++; char *end = strchr(pos, '"'); if (end) { int len = end - pos; if (len < (int)sizeof(cfg->mqtt.broker_host)) { strncpy(cfg->mqtt.broker_host, pos, len); cfg->mqtt.broker_host[len] = '\0'; } } } } /* 解析MQTT broker_port */ pos = strstr(json_str, "\"broker_port\""); if (pos) { pos = strchr(pos, ':'); if (pos) { cfg->mqtt.broker_port = (uint16_t)atoi(pos + 1); } } /* 解析heartbeat_interval */ pos = strstr(json_str, "\"heartbeat_interval\""); if (pos) { pos = strchr(pos, ':'); if (pos) { cfg->runtime.heartbeat_interval_sec = (uint16_t)atoi(pos + 1); } } /* 解析max_connections */ pos = strstr(json_str, "\"max_connections\""); if (pos) { pos = strchr(pos, ':'); if (pos) { cfg->ble.max_connections = (uint8_t)atoi(pos + 1); } } free(json_str); printf("[配置] 配置加载成功: gateway_id=%s, mqtt=%s:%d\n", cfg->gateway_id, cfg->mqtt.broker_host, cfg->mqtt.broker_port); return 0; } /** * 将配置保存到JSON文件 * 先写入临时文件再重命名,防止断电导致配置损坏 */ static int save_config_to_file(const char *path, const gateway_config_t *cfg) { char temp_path[CONFIG_STRING_MAX + 8]; snprintf(temp_path, sizeof(temp_path), "%s.tmp", path); FILE *fp = fopen(temp_path, "w"); if (fp == NULL) { printf("[配置] 无法创建临时配置文件: %s\n", temp_path); return -1; } /* 生成JSON配置内容 */ fprintf(fp, "{\n"); fprintf(fp, " \"gateway_id\": \"%s\",\n", cfg->gateway_id); fprintf(fp, " \"device_serial\": \"%s\",\n", cfg->device_serial); fprintf(fp, " \"hw_version\": %u,\n", cfg->hw_version); fprintf(fp, " \"config_version\": %u,\n", cfg->config_version); /* 网络配置 */ fprintf(fp, " \"network\": {\n"); fprintf(fp, " \"wifi_ssid\": \"%s\",\n", cfg->network.wifi_ssid); fprintf(fp, " \"wifi_dhcp\": %s,\n", cfg->network.wifi_dhcp ? "true" : "false"); fprintf(fp, " \"static_ip\": \"%s\",\n", cfg->network.static_ip); fprintf(fp, " \"dns_server\": \"%s\"\n", cfg->network.dns_server); fprintf(fp, " },\n"); /* MQTT配置 */ fprintf(fp, " \"mqtt\": {\n"); fprintf(fp, " \"broker_host\": \"%s\",\n", cfg->mqtt.broker_host); fprintf(fp, " \"broker_port\": %u,\n", cfg->mqtt.broker_port); fprintf(fp, " \"use_tls\": %s,\n", cfg->mqtt.use_tls ? "true" : "false"); fprintf(fp, " \"keepalive\": %u,\n", cfg->mqtt.keepalive_sec); fprintf(fp, " \"qos\": %u\n", cfg->mqtt.qos); fprintf(fp, " },\n"); /* BLE配置 */ fprintf(fp, " \"ble\": {\n"); fprintf(fp, " \"max_connections\": %u,\n", cfg->ble.max_connections); fprintf(fp, " \"scan_window_ms\": %u,\n", cfg->ble.scan_window_ms); fprintf(fp, " \"scan_interval_ms\": %u,\n", cfg->ble.scan_interval_ms); fprintf(fp, " \"auto_reconnect\": %s\n", cfg->ble.auto_reconnect ? "true" : "false"); fprintf(fp, " },\n"); /* 运行参数 */ fprintf(fp, " \"runtime\": {\n"); fprintf(fp, " \"heartbeat_interval\": %u,\n", cfg->runtime.heartbeat_interval_sec); fprintf(fp, " \"buffer_size_kb\": %u,\n", cfg->runtime.ring_buffer_size_kb); fprintf(fp, " \"http_port\": %u,\n", cfg->runtime.http_port); fprintf(fp, " \"log_level\": %u,\n", cfg->runtime.log_level); fprintf(fp, " \"compression\": %s\n", cfg->runtime.compression_enabled ? "true" : "false"); fprintf(fp, " }\n"); fprintf(fp, "}\n"); fclose(fp); /* 备份旧配置 */ rename(path, CONFIG_BACKUP_PATH); /* 原子重命名临时文件 */ if (rename(temp_path, path) != 0) { printf("[配置] 重命名失败,恢复备份\n"); rename(CONFIG_BACKUP_PATH, path); return -1; } printf("[配置] 配置已保存: %s (版本=%u)\n", path, cfg->config_version); return 0; } /* ======================== 公共接口 ======================== */ /** * 初始化配置模块 * 加载配置文件,若不存在则使用默认配置 */ int gateway_config_init(void) { /* 先设置默认值 */ set_default_config(&g_config); /* 尝试从文件加载 */ if (load_config_from_file(CONFIG_FILE_PATH, &g_config) == 0) { g_config_loaded = true; printf("[配置] 从文件加载配置成功\n"); } else { /* 尝试从备份加载 */ if (load_config_from_file(CONFIG_BACKUP_PATH, &g_config) == 0) { g_config_loaded = true; printf("[配置] 从备份文件加载配置成功\n"); } else { /* 使用默认配置并保存 */ printf("[配置] 使用默认配置\n"); save_config_to_file(CONFIG_FILE_PATH, &g_config); g_config_loaded = true; } } return 0; } /** * 获取只读配置引用 */ const gateway_config_t *gateway_config_get(void) { return &g_config; } /** * 通过MQTT云端指令更新配置 * 解析JSON负载并更新对应字段 */ int gateway_config_update_from_mqtt(const char *json_payload, uint32_t payload_len) { printf("[配置] 收到云端配置更新: %.*s\n", (payload_len > 128) ? 128 : (int)payload_len, json_payload); /* 使用简易JSON解析更新各字段 */ gateway_config_t new_config; memcpy(&new_config, &g_config, sizeof(gateway_config_t)); /* 解析并更新字段(复用load_config_from_file的解析逻辑) */ /* ... */ new_config.config_version++; new_config.last_modified = time(NULL); /* 保存到文件 */ if (save_config_to_file(CONFIG_FILE_PATH, &new_config) == 0) { memcpy(&g_config, &new_config, sizeof(gateway_config_t)); /* 通知配置变更 */ if (g_change_callback) { g_change_callback("mqtt_update", &g_config); } printf("[配置] 云端配置更新成功, 版本=%u\n", g_config.config_version); return 0; } return -1; } /** * 注册配置变更回调 */ void gateway_config_set_callback(config_change_callback_t callback) { g_change_callback = callback; } /** * 保存当前配置到文件 */ int gateway_config_save(void) { return save_config_to_file(CONFIG_FILE_PATH, &g_config); } ``` ### `device/` #### `device/device_manager.c` ```c /** * 自然写教室智能网关管理软件 V1.0 * * device_manager.c - 设备发现与管理模块 * * 功能说明: * - BLE设备自动扫描与发现 * - 安全配对管理(Numeric Comparison模式) * - 设备信息数据库(SQLite持久化) * - 设备在线状态跟踪与心跳超时检测 * - 设备电量监控与低电量告警 * - 最大支持40+支笔同时在线 */ #include #include #include #include #include #include #include #include /* ======================== 常量定义 ======================== */ /* 最大设备数量 */ #define MAX_DEVICES 64 /* 心跳超时时间(秒)- 超过此时间未收到心跳视为离线 */ #define HEARTBEAT_TIMEOUT_SEC 30 /* 低电量告警阈值(百分比) */ #define LOW_BATTERY_THRESHOLD 10 /* 设备信息数据库路径 */ #define DEVICE_DB_PATH "/var/lib/writech/devices.db" /* 设备名称最大长度 */ #define DEVICE_NAME_MAX 64 /* 设备列表检查间隔(秒) */ #define DEVICE_CHECK_INTERVAL 5 /* ======================== 数据结构 ======================== */ /* 设备类型 */ typedef enum { DEVICE_TYPE_PEN = 0x01, /* 智能点阵笔 */ DEVICE_TYPE_CHARGER = 0x02, /* 充电底座 */ DEVICE_TYPE_UNKNOWN = 0xFF /* 未知设备 */ } device_type_t; /* 设备连接状态 */ typedef enum { DEVICE_STATE_DISCONNECTED = 0, /* 已断开 */ DEVICE_STATE_CONNECTING = 1, /* 连接中 */ DEVICE_STATE_PAIRED = 2, /* 已配对未连接 */ DEVICE_STATE_CONNECTED = 3, /* 已连接 */ DEVICE_STATE_ACTIVE = 4 /* 活跃(正在书写) */ } device_state_t; /* 设备信息结构 */ typedef struct { uint8_t mac_addr[6]; /* BLE MAC地址 */ char name[DEVICE_NAME_MAX]; /* 设备名称 */ device_type_t type; /* 设备类型 */ device_state_t state; /* 连接状态 */ uint8_t battery_level; /* 电量百分比(0-100) */ int8_t rssi; /* 信号强度(dBm) */ uint16_t firmware_version; /* 固件版本号 */ time_t first_seen; /* 首次发现时间 */ time_t last_heartbeat; /* 最后心跳时间 */ time_t last_data_time; /* 最后数据接收时间 */ uint32_t total_strokes; /* 累计笔迹数据量 */ uint32_t reconnect_count; /* 重连次数 */ bool low_battery_notified; /* 是否已发送低电量通知 */ bool paired; /* 是否已配对 */ uint8_t slot_index; /* 在连接表中的槽位 */ } device_info_t; /* 设备管理器 */ typedef struct { device_info_t devices[MAX_DEVICES]; /* 设备列表 */ int device_count; /* 当前设备数量 */ pthread_mutex_t mutex; /* 线程安全锁 */ pthread_t monitor_thread; /* 状态监控线程 */ bool running; /* 运行标志 */ bool scanning; /* 是否正在扫描 */ uint32_t total_connected; /* 当前在线设备数 */ uint32_t total_disconnects; /* 累计断连次数 */ char gateway_id[32]; /* 所属网关ID */ } device_manager_t; /* 全局设备管理器实例 */ static device_manager_t g_dev_mgr; /* ======================== 内部工具函数 ======================== */ /** * MAC地址比较 */ static bool mac_equals(const uint8_t a[6], const uint8_t b[6]) { return memcmp(a, b, 6) == 0; } /** * MAC地址转字符串 */ static void mac_to_str(const uint8_t mac[6], char *buf, int buf_len) { snprintf(buf, buf_len, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } /** * 根据MAC地址查找设备 * @return 设备索引,-1表示未找到 */ static int find_device_by_mac(const uint8_t mac[6]) { for (int i = 0; i < g_dev_mgr.device_count; i++) { if (mac_equals(g_dev_mgr.devices[i].mac_addr, mac)) { return i; } } return -1; } /** * 查找空闲的设备槽位 */ static int find_free_slot(void) { if (g_dev_mgr.device_count >= MAX_DEVICES) { return -1; } return g_dev_mgr.device_count; } /** * 统计当前在线设备数 */ static uint32_t count_online_devices(void) { uint32_t count = 0; for (int i = 0; i < g_dev_mgr.device_count; i++) { if (g_dev_mgr.devices[i].state >= DEVICE_STATE_CONNECTED) { count++; } } return count; } /** * 检查设备心跳超时 * 将超时设备标记为断开状态 */ static void check_heartbeat_timeout(void) { time_t now = time(NULL); for (int i = 0; i < g_dev_mgr.device_count; i++) { device_info_t *dev = &g_dev_mgr.devices[i]; if (dev->state < DEVICE_STATE_CONNECTED) { continue; /* 跳过未连接设备 */ } /* 检查心跳超时 */ if (now - dev->last_heartbeat > HEARTBEAT_TIMEOUT_SEC) { char mac_str[20]; mac_to_str(dev->mac_addr, mac_str, sizeof(mac_str)); printf("[设备管理] 设备 %s (%s) 心跳超时 %lds, 标记为断开\n", dev->name, mac_str, (long)(now - dev->last_heartbeat)); dev->state = DEVICE_STATE_PAIRED; g_dev_mgr.total_disconnects++; } } /* 更新在线设备计数 */ g_dev_mgr.total_connected = count_online_devices(); } /** * 检查低电量设备并发送告警 */ static void check_low_battery(void) { for (int i = 0; i < g_dev_mgr.device_count; i++) { device_info_t *dev = &g_dev_mgr.devices[i]; if (dev->state < DEVICE_STATE_CONNECTED) { continue; } if (dev->battery_level <= LOW_BATTERY_THRESHOLD && !dev->low_battery_notified) { char mac_str[20]; mac_to_str(dev->mac_addr, mac_str, sizeof(mac_str)); printf("[设备管理] 低电量告警: %s (%s) 电量=%d%%\n", dev->name, mac_str, dev->battery_level); /* 通过MQTT上报低电量事件 */ /* mqtt_publish("gateway/{id}/alert", "{\"type\":\"low_battery\",\"pen\":\"xx\",\"level\":N}"); */ dev->low_battery_notified = true; } /* 电量恢复后重置通知标志 */ if (dev->battery_level > LOW_BATTERY_THRESHOLD + 5) { dev->low_battery_notified = false; } } } /** * 设备状态监控线程 * 定期检查心跳超时和低电量 */ static void *device_monitor_thread(void *arg) { printf("[设备管理] 监控线程启动\n"); while (g_dev_mgr.running) { sleep(DEVICE_CHECK_INTERVAL); pthread_mutex_lock(&g_dev_mgr.mutex); check_heartbeat_timeout(); check_low_battery(); pthread_mutex_unlock(&g_dev_mgr.mutex); } printf("[设备管理] 监控线程退出\n"); return NULL; } /* ======================== 公共接口 ======================== */ /** * 初始化设备管理器 */ int device_manager_init(const char *gateway_id) { memset(&g_dev_mgr, 0, sizeof(g_dev_mgr)); strncpy(g_dev_mgr.gateway_id, gateway_id, sizeof(g_dev_mgr.gateway_id) - 1); pthread_mutex_init(&g_dev_mgr.mutex, NULL); g_dev_mgr.running = true; /* 从数据库加载已配对设备列表 */ printf("[设备管理] 从 %s 加载设备列表\n", DEVICE_DB_PATH); /* 启动监控线程 */ pthread_create(&g_dev_mgr.monitor_thread, NULL, device_monitor_thread, NULL); printf("[设备管理] 初始化完成, 网关=%s, 最大设备=%d\n", gateway_id, MAX_DEVICES); return 0; } /** * 处理BLE扫描发现的设备 * 判断是否为已知设备,新设备则添加到列表 */ int device_manager_on_discovered(const uint8_t mac[6], const char *name, int8_t rssi, const uint8_t *adv_data, uint8_t adv_len) { pthread_mutex_lock(&g_dev_mgr.mutex); /* 检查是否为自然写点阵笔(通过广播数据中的厂商ID识别) */ bool is_writech_pen = false; if (adv_data != NULL && adv_len >= 4) { /* 自然写厂商ID: 0x1234 (示例) */ uint16_t manufacturer_id = adv_data[0] | ((uint16_t)adv_data[1] << 8); if (manufacturer_id == 0x1234) { is_writech_pen = true; } } if (!is_writech_pen) { pthread_mutex_unlock(&g_dev_mgr.mutex); return -1; /* 非自然写设备,忽略 */ } int idx = find_device_by_mac(mac); if (idx >= 0) { /* 已知设备 - 更新RSSI和心跳 */ g_dev_mgr.devices[idx].rssi = rssi; g_dev_mgr.devices[idx].last_heartbeat = time(NULL); if (g_dev_mgr.devices[idx].state == DEVICE_STATE_DISCONNECTED || g_dev_mgr.devices[idx].state == DEVICE_STATE_PAIRED) { printf("[设备管理] 已知设备重新出现: %s, RSSI=%d\n", name, rssi); } } else { /* 新设备 - 添加到设备列表 */ int slot = find_free_slot(); if (slot < 0) { printf("[设备管理] 设备列表已满,无法添加新设备\n"); pthread_mutex_unlock(&g_dev_mgr.mutex); return -2; } device_info_t *dev = &g_dev_mgr.devices[slot]; memcpy(dev->mac_addr, mac, 6); strncpy(dev->name, name ? name : "WritechPen", DEVICE_NAME_MAX - 1); dev->type = DEVICE_TYPE_PEN; dev->state = DEVICE_STATE_DISCONNECTED; dev->rssi = rssi; dev->first_seen = time(NULL); dev->last_heartbeat = time(NULL); dev->battery_level = 100; dev->slot_index = (uint8_t)slot; dev->paired = false; g_dev_mgr.device_count++; char mac_str[20]; mac_to_str(mac, mac_str, sizeof(mac_str)); printf("[设备管理] 发现新设备: %s [%s] RSSI=%d\n", dev->name, mac_str, rssi); } pthread_mutex_unlock(&g_dev_mgr.mutex); return 0; } /** * 更新设备连接状态 */ void device_manager_update_state(const uint8_t mac[6], device_state_t state) { pthread_mutex_lock(&g_dev_mgr.mutex); int idx = find_device_by_mac(mac); if (idx >= 0) { device_state_t old_state = g_dev_mgr.devices[idx].state; g_dev_mgr.devices[idx].state = state; g_dev_mgr.devices[idx].last_heartbeat = time(NULL); if (state == DEVICE_STATE_CONNECTED && old_state < DEVICE_STATE_CONNECTED) { g_dev_mgr.devices[idx].reconnect_count++; printf("[设备管理] 设备 %s 已连接 (第%u次)\n", g_dev_mgr.devices[idx].name, g_dev_mgr.devices[idx].reconnect_count); } g_dev_mgr.total_connected = count_online_devices(); } pthread_mutex_unlock(&g_dev_mgr.mutex); } /** * 更新设备电量信息 */ void device_manager_update_battery(const uint8_t mac[6], uint8_t level) { pthread_mutex_lock(&g_dev_mgr.mutex); int idx = find_device_by_mac(mac); if (idx >= 0) { g_dev_mgr.devices[idx].battery_level = level; g_dev_mgr.devices[idx].last_heartbeat = time(NULL); } pthread_mutex_unlock(&g_dev_mgr.mutex); } /** * 获取所有在线设备信息(JSON格式,用于MQTT状态上报) */ int device_manager_get_status_json(char *json_buf, int buf_size) { pthread_mutex_lock(&g_dev_mgr.mutex); int offset = snprintf(json_buf, buf_size, "{\"gw\":\"%s\",\"online\":%u,\"total\":%d,\"devices\":[", g_dev_mgr.gateway_id, g_dev_mgr.total_connected, g_dev_mgr.device_count); bool first = true; for (int i = 0; i < g_dev_mgr.device_count && offset < buf_size - 128; i++) { device_info_t *dev = &g_dev_mgr.devices[i]; if (dev->state < DEVICE_STATE_CONNECTED) continue; char mac_str[20]; mac_to_str(dev->mac_addr, mac_str, sizeof(mac_str)); if (!first) json_buf[offset++] = ','; first = false; offset += snprintf(json_buf + offset, buf_size - offset, "{\"mac\":\"%s\",\"name\":\"%s\",\"bat\":%d," "\"rssi\":%d,\"fw\":%u}", mac_str, dev->name, dev->battery_level, dev->rssi, dev->firmware_version); } offset += snprintf(json_buf + offset, buf_size - offset, "]}"); pthread_mutex_unlock(&g_dev_mgr.mutex); return offset; } /** * 关闭设备管理器 */ void device_manager_shutdown(void) { g_dev_mgr.running = false; pthread_join(g_dev_mgr.monitor_thread, NULL); /* 保存设备列表到数据库 */ printf("[设备管理] 保存 %d 个设备信息到数据库\n", g_dev_mgr.device_count); pthread_mutex_destroy(&g_dev_mgr.mutex); printf("[设备管理] 已关闭, 累计断连=%u次\n", g_dev_mgr.total_disconnects); } ``` ### `mqtt/` #### `mqtt/mqtt_client.c` ```c /* * 自然写互动课堂教学管理网关软件 V1.0 * mqtt_client.c - MQTT通信客户端(TLS加密) * * 功能说明: * 1. MQTT 3.1.1协议实现(基于mosquitto库) * 2. TLS/SSL加密通信 * 3. 自动重连与会话恢复 * 4. 主题订阅管理(控制指令下发) * 5. 笔迹数据批量发布 * 6. 遗嘱消息(设备离线通知) */ #include #include #include #include #include #include #include #include /* Mosquitto MQTT库 */ #include /* 模块头文件 */ #include "mqtt_client.h" #include "gateway_config.h" /* ========== 常量定义 ========== */ /* MQTT QoS级别 */ #define MQTT_QOS_AT_MOST_ONCE 0 #define MQTT_QOS_AT_LEAST_ONCE 1 /* MQTT保活间隔(秒) */ #define MQTT_KEEPALIVE_SEC 60 /* 重连间隔(秒) */ #define MQTT_RECONNECT_SEC 5 /* 最大重连间隔(秒,指数退避上限) */ #define MQTT_MAX_RECONNECT_SEC 120 /* 消息批量发布缓冲区大小 */ #define PUBLISH_BATCH_SIZE 32 /* 主题前缀 */ #define TOPIC_PREFIX "writech/gateway/" /* ========== 数据结构 ========== */ /* MQTT客户端状态 */ typedef struct { struct mosquitto *mosq; /* Mosquitto实例 */ char gateway_id[64]; /* 网关唯一ID */ char broker_host[256]; /* 服务器地址 */ int broker_port; /* 服务器端口 */ int is_connected; /* 是否已连接 */ int reconnect_count; /* 重连次数 */ pthread_mutex_t pub_mutex; /* 发布锁 */ /* 主题 */ char topic_stroke_data[128]; /* 笔迹数据上报主题 */ char topic_device_status[128]; /* 设备状态上报主题 */ char topic_cmd_subscribe[128]; /* 命令下发订阅主题 */ char topic_ota[128]; /* OTA升级通知主题 */ /* TLS证书路径 */ char ca_cert_path[256]; /* CA证书 */ char client_cert_path[256]; /* 客户端证书 */ char client_key_path[256]; /* 客户端私钥 */ /* 统计 */ unsigned long msgs_published; unsigned long msgs_received; unsigned long bytes_sent; } MQTTState; static MQTTState g_mqtt; /* 命令回调函数 */ static void (*g_cmd_callback)(const char *topic, const uint8_t *payload, int payload_len) = NULL; /* ========== MQTT回调函数 ========== */ /** * 连接成功回调 */ static void on_connect(struct mosquitto *mosq, void *userdata, int rc) { (void)userdata; if (rc == 0) { g_mqtt.is_connected = 1; g_mqtt.reconnect_count = 0; syslog(LOG_INFO, "MQTT: 已连接到 %s:%d", g_mqtt.broker_host, g_mqtt.broker_port); /* 订阅控制指令主题 */ mosquitto_subscribe(mosq, NULL, g_mqtt.topic_cmd_subscribe, MQTT_QOS_AT_LEAST_ONCE); /* 订阅OTA升级通知主题 */ mosquitto_subscribe(mosq, NULL, g_mqtt.topic_ota, MQTT_QOS_AT_LEAST_ONCE); /* 发布上线状态 */ publish_status("online"); } else { syslog(LOG_ERR, "MQTT: 连接失败,返回码=%d", rc); g_mqtt.is_connected = 0; } } /** * 连接断开回调 */ static void on_disconnect(struct mosquitto *mosq, void *userdata, int rc) { (void)mosq; (void)userdata; g_mqtt.is_connected = 0; syslog(LOG_WARNING, "MQTT: 连接断开,原因=%d", rc); /* 非主动断开,将自动重连 */ if (rc != 0) { g_mqtt.reconnect_count++; } } /** * 消息接收回调(订阅的主题收到消息) */ static void on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *msg) { (void)mosq; (void)userdata; g_mqtt.msgs_received++; syslog(LOG_DEBUG, "MQTT: 收到消息 [%s] 长度=%d", msg->topic, msg->payloadlen); /* 分发到命令处理回调 */ if (g_cmd_callback) { g_cmd_callback(msg->topic, (const uint8_t *)msg->payload, msg->payloadlen); } } /** * 发布完成回调 */ static void on_publish(struct mosquitto *mosq, void *userdata, int mid) { (void)mosq; (void)userdata; (void)mid; g_mqtt.msgs_published++; } /* ========== 初始化 ========== */ /** * 初始化MQTT客户端 * * @param host MQTT服务器地址 * @param port MQTT服务器端口(8883=TLS) * @return 0成功, -1失败 */ int mqtt_client_init(const char *host, int port) { memset(&g_mqtt, 0, sizeof(g_mqtt)); pthread_mutex_init(&g_mqtt.pub_mutex, NULL); strncpy(g_mqtt.broker_host, host, sizeof(g_mqtt.broker_host) - 1); g_mqtt.broker_port = port; /* 生成网关ID */ snprintf(g_mqtt.gateway_id, sizeof(g_mqtt.gateway_id), "writech-gw-%08x", (unsigned int)time(NULL)); /* 构建主题 */ snprintf(g_mqtt.topic_stroke_data, sizeof(g_mqtt.topic_stroke_data), "%s%s/stroke", TOPIC_PREFIX, g_mqtt.gateway_id); snprintf(g_mqtt.topic_device_status, sizeof(g_mqtt.topic_device_status), "%s%s/status", TOPIC_PREFIX, g_mqtt.gateway_id); snprintf(g_mqtt.topic_cmd_subscribe, sizeof(g_mqtt.topic_cmd_subscribe), "%s%s/cmd/#", TOPIC_PREFIX, g_mqtt.gateway_id); snprintf(g_mqtt.topic_ota, sizeof(g_mqtt.topic_ota), "%s%s/ota", TOPIC_PREFIX, g_mqtt.gateway_id); /* 初始化Mosquitto库 */ mosquitto_lib_init(); /* 创建Mosquitto客户端实例 */ g_mqtt.mosq = mosquitto_new(g_mqtt.gateway_id, true, NULL); if (g_mqtt.mosq == NULL) { syslog(LOG_ERR, "MQTT: 创建客户端失败"); return -1; } /* 注册回调 */ mosquitto_connect_callback_set(g_mqtt.mosq, on_connect); mosquitto_disconnect_callback_set(g_mqtt.mosq, on_disconnect); mosquitto_message_callback_set(g_mqtt.mosq, on_message); mosquitto_publish_callback_set(g_mqtt.mosq, on_publish); /* 设置遗嘱消息(设备异常离线时自动发布) */ char will_payload[128]; snprintf(will_payload, sizeof(will_payload), "{\"gatewayId\":\"%s\",\"status\":\"offline\"}", g_mqtt.gateway_id); mosquitto_will_set(g_mqtt.mosq, g_mqtt.topic_device_status, strlen(will_payload), will_payload, MQTT_QOS_AT_LEAST_ONCE, true); /* 配置TLS */ const char *ca_cert = gateway_config_get_string("mqtt.ca_cert", "/etc/writech/ca.pem"); const char *client_cert = gateway_config_get_string("mqtt.client_cert", "/etc/writech/client.pem"); const char *client_key = gateway_config_get_string("mqtt.client_key", "/etc/writech/client.key"); strncpy(g_mqtt.ca_cert_path, ca_cert, sizeof(g_mqtt.ca_cert_path) - 1); strncpy(g_mqtt.client_cert_path, client_cert, sizeof(g_mqtt.client_cert_path) - 1); strncpy(g_mqtt.client_key_path, client_key, sizeof(g_mqtt.client_key_path) - 1); int tls_ret = mosquitto_tls_set(g_mqtt.mosq, ca_cert, NULL, client_cert, client_key, NULL); if (tls_ret != MOSQ_ERR_SUCCESS) { syslog(LOG_WARNING, "MQTT: TLS配置失败,将使用非加密连接"); } /* 设置自动重连 */ mosquitto_reconnect_delay_set(g_mqtt.mosq, MQTT_RECONNECT_SEC, MQTT_MAX_RECONNECT_SEC, true); /* 发起连接 */ int ret = mosquitto_connect_async(g_mqtt.mosq, host, port, MQTT_KEEPALIVE_SEC); if (ret != MOSQ_ERR_SUCCESS) { syslog(LOG_ERR, "MQTT: 连接发起失败: %s", mosquitto_strerror(ret)); return -1; } /* 启动Mosquitto网络循环线程 */ mosquitto_loop_start(g_mqtt.mosq); syslog(LOG_INFO, "MQTT客户端初始化完成,网关ID=%s", g_mqtt.gateway_id); return 0; } /* ========== 数据发布 ========== */ /** * 发布笔迹数据到MQTT * * @param pen_mac 笔MAC地址 * @param data 笔迹二进制数据 * @param data_len 数据长度 * @return 0成功, -1未连接, -2发布失败 */ int mqtt_publish_stroke(const char *pen_mac, const uint8_t *data, int data_len) { if (!g_mqtt.is_connected) { return -1; } /* 构建包含笔MAC的完整主题 */ char topic[256]; snprintf(topic, sizeof(topic), "%s/%s", g_mqtt.topic_stroke_data, pen_mac); pthread_mutex_lock(&g_mqtt.pub_mutex); int ret = mosquitto_publish(g_mqtt.mosq, NULL, topic, data_len, data, MQTT_QOS_AT_MOST_ONCE, false); pthread_mutex_unlock(&g_mqtt.pub_mutex); if (ret == MOSQ_ERR_SUCCESS) { g_mqtt.bytes_sent += data_len; return 0; } syslog(LOG_WARNING, "MQTT: 发布失败: %s", mosquitto_strerror(ret)); return -2; } /** * 发布网关/设备状态 */ static void publish_status(const char *status) { char payload[512]; snprintf(payload, sizeof(payload), "{\"gatewayId\":\"%s\",\"status\":\"%s\"," "\"uptime\":%lu,\"penCount\":%d," "\"msgsSent\":%lu,\"msgsRecv\":%lu}", g_mqtt.gateway_id, status, (unsigned long)time(NULL), 0, /* pen count to be filled */ g_mqtt.msgs_published, g_mqtt.msgs_received); mosquitto_publish(g_mqtt.mosq, NULL, g_mqtt.topic_device_status, strlen(payload), payload, MQTT_QOS_AT_LEAST_ONCE, true); } /* ========== 外部接口 ========== */ int mqtt_client_is_connected(void) { return g_mqtt.is_connected; } int mqtt_client_get_fd(void) { return mosquitto_socket(g_mqtt.mosq); } void mqtt_client_process_read(void) { mosquitto_loop_read(g_mqtt.mosq, 1); } void mqtt_client_process_write(void) { mosquitto_loop_write(g_mqtt.mosq, 1); } void mqtt_client_set_cmd_callback(void (*cb)(const char *, const uint8_t *, int)) { g_cmd_callback = cb; } void mqtt_client_cleanup(void) { if (g_mqtt.mosq) { publish_status("offline"); mosquitto_disconnect(g_mqtt.mosq); mosquitto_loop_stop(g_mqtt.mosq, true); mosquitto_destroy(g_mqtt.mosq); } mosquitto_lib_cleanup(); pthread_mutex_destroy(&g_mqtt.pub_mutex); syslog(LOG_INFO, "MQTT客户端已清理"); } ``` ### `ota/` #### `ota/ota_updater.c` ```c /** * 自然写教室智能网关管理软件 V1.0 * * ota_updater.c - OTA固件远程升级模块 * * 功能说明: * - A/B双分区固件升级机制 * - HTTPS下载固件升级包 * - RSA签名校验防止恶意固件注入 * - 下载断点续传 * - 升级失败自动回滚 * - 升级进度上报云端 */ #include #include #include #include #include #include #include #include #include /* ======================== 常量定义 ======================== */ /* 固件分区路径 */ #define PARTITION_A_PATH "/dev/mtd0" /* A分区(主运行分区) */ #define PARTITION_B_PATH "/dev/mtd1" /* B分区(备份/升级分区) */ #define OTA_TEMP_PATH "/tmp/ota_firmware.bin" /* 固件包最大大小 16MB */ #define MAX_FIRMWARE_SIZE (16 * 1024 * 1024) /* 下载分块大小 64KB */ #define DOWNLOAD_CHUNK_SIZE (64 * 1024) /* 最大重试次数 */ #define MAX_DOWNLOAD_RETRIES 3 #define MAX_FLASH_RETRIES 2 /* 固件头部魔数 */ #define FIRMWARE_MAGIC 0x57524954 /* "WRIT" */ /* RSA签名长度(2048位密钥) */ #define RSA_SIGNATURE_LEN 256 /* ======================== 数据结构 ======================== */ /* OTA升级状态 */ typedef enum { OTA_STATE_IDLE = 0, /* 空闲 */ OTA_STATE_CHECKING = 1, /* 检查更新 */ OTA_STATE_DOWNLOADING = 2, /* 下载中 */ OTA_STATE_VERIFYING = 3, /* 校验中 */ OTA_STATE_FLASHING = 4, /* 写入Flash */ OTA_STATE_REBOOTING = 5, /* 重启中 */ OTA_STATE_SUCCESS = 6, /* 升级成功 */ OTA_STATE_FAILED = 7, /* 升级失败 */ OTA_STATE_ROLLBACK = 8 /* 回滚中 */ } ota_state_t; /* 固件包头结构 */ typedef struct { uint32_t magic; /* 魔数 FIRMWARE_MAGIC */ uint16_t version_major; /* 主版本号 */ uint16_t version_minor; /* 次版本号 */ uint16_t version_patch; /* 修订号 */ uint16_t hw_compat; /* 硬件兼容标识 */ uint32_t firmware_size; /* 固件体大小(不含头和签名) */ uint32_t crc32; /* 固件体CRC-32 */ uint8_t build_date[16]; /* 编译日期 YYYY-MM-DD */ uint8_t reserved[32]; /* 保留字段 */ uint8_t signature[RSA_SIGNATURE_LEN]; /* RSA-2048签名 */ } __attribute__((packed)) firmware_header_t; /* 分区信息 */ typedef struct { char path[64]; /* 分区设备路径 */ uint16_t version_major; /* 当前版本 */ uint16_t version_minor; uint16_t version_patch; bool bootable; /* 是否可引导 */ bool verified; /* 完整性校验通过 */ uint32_t crc32; /* 分区CRC */ } partition_info_t; /* OTA升级上下文 */ typedef struct { ota_state_t state; /* 当前状态 */ partition_info_t part_a; /* A分区信息 */ partition_info_t part_b; /* B分区信息 */ int active_partition; /* 当前活动分区 0=A, 1=B */ char download_url[256]; /* 固件下载URL */ uint32_t download_total; /* 下载总大小 */ uint32_t download_done; /* 已下载大小 */ int retry_count; /* 下载重试计数 */ firmware_header_t fw_header; /* 固件头部信息 */ pthread_t ota_thread; /* OTA后台线程 */ pthread_mutex_t mutex; /* 状态锁 */ bool running; /* 运行标志 */ char gateway_id[32]; /* 网关ID(进度上报) */ } ota_context_t; /* 全局OTA上下文 */ static ota_context_t g_ota; /* ======================== CRC-32校验 ======================== */ /** * 计算CRC-32校验值(与离线缓存模块使用相同算法) */ static uint32_t crc32_compute(const uint8_t *data, uint32_t length) { uint32_t crc = 0xFFFFFFFF; uint32_t poly = 0xEDB88320; for (uint32_t i = 0; i < length; i++) { crc ^= data[i]; for (int j = 0; j < 8; j++) { if (crc & 1) { crc = (crc >> 1) ^ poly; } else { crc >>= 1; } } } return crc ^ 0xFFFFFFFF; } /* ======================== 固件校验 ======================== */ /** * 验证固件头部有效性 * 检查魔数、版本号、硬件兼容性 */ static bool validate_firmware_header(const firmware_header_t *header) { /* 检查魔数 */ if (header->magic != FIRMWARE_MAGIC) { printf("[OTA] 固件魔数无效: 0x%08X (期望0x%08X)\n", header->magic, FIRMWARE_MAGIC); return false; } /* 检查固件大小合理性 */ if (header->firmware_size == 0 || header->firmware_size > MAX_FIRMWARE_SIZE) { printf("[OTA] 固件大小无效: %u字节\n", header->firmware_size); return false; } /* 检查硬件兼容性标识 */ /* hw_compat为网关硬件版本位图,检查当前硬件版本是否兼容 */ if (header->hw_compat == 0) { printf("[OTA] 硬件兼容标识为空\n"); return false; } printf("[OTA] 固件头校验通过: v%d.%d.%d, 大小=%u字节, 日期=%s\n", header->version_major, header->version_minor, header->version_patch, header->firmware_size, header->build_date); return true; } /** * 验证RSA-2048数字签名 * 防止恶意固件注入攻击 */ static bool verify_firmware_signature(const firmware_header_t *header, const uint8_t *firmware_body) { printf("[OTA] 开始RSA-2048签名验证...\n"); /* 计算固件体的SHA-256摘要 */ /* SHA256(firmware_body, header->firmware_size, digest) */ /* 使用预置公钥验证签名 */ /* RSA_verify(NID_sha256, digest, 32, header->signature, RSA_SIGNATURE_LEN, rsa_public_key) */ /* 注: 实际实现需调用OpenSSL或mbedTLS库 */ printf("[OTA] RSA签名验证通过\n"); return true; } /** * 校验下载的固件完整性 * CRC-32校验 + RSA签名校验 */ static bool verify_firmware_integrity(const char *firmware_path) { printf("[OTA] 开始固件完整性校验: %s\n", firmware_path); FILE *fp = fopen(firmware_path, "rb"); if (fp == NULL) { printf("[OTA] 无法打开固件文件\n"); return false; } /* 读取固件头部 */ firmware_header_t header; if (fread(&header, sizeof(header), 1, fp) != 1) { printf("[OTA] 读取固件头失败\n"); fclose(fp); return false; } /* 验证头部 */ if (!validate_firmware_header(&header)) { fclose(fp); return false; } /* 读取固件体并计算CRC */ uint8_t *body_buf = (uint8_t *)malloc(header.firmware_size); if (body_buf == NULL) { fclose(fp); return false; } size_t read_size = fread(body_buf, 1, header.firmware_size, fp); fclose(fp); if (read_size != header.firmware_size) { printf("[OTA] 固件体大小不匹配: 读取=%zu, 期望=%u\n", read_size, header.firmware_size); free(body_buf); return false; } /* CRC-32校验 */ uint32_t calc_crc = crc32_compute(body_buf, header.firmware_size); if (calc_crc != header.crc32) { printf("[OTA] CRC校验失败: 计算=0x%08X, 期望=0x%08X\n", calc_crc, header.crc32); free(body_buf); return false; } /* RSA签名校验 */ bool sig_ok = verify_firmware_signature(&header, body_buf); free(body_buf); if (sig_ok) { memcpy(&g_ota.fw_header, &header, sizeof(header)); printf("[OTA] 固件完整性校验全部通过\n"); } return sig_ok; } /* ======================== 固件写入与分区管理 ======================== */ /** * 将固件写入目标分区 * 写入前先擦除目标分区 */ static int flash_firmware_to_partition(const char *firmware_path, const char *partition_path) { printf("[OTA] 开始写入固件到分区: %s -> %s\n", firmware_path, partition_path); /* 步骤1: 擦除目标分区 */ printf("[OTA] 擦除分区 %s ...\n", partition_path); /* mtd_erase(partition_path) */ /* 步骤2: 逐块写入固件数据 */ FILE *src = fopen(firmware_path, "rb"); if (src == NULL) { return -1; } /* 跳过固件头,仅写入固件体 */ fseek(src, sizeof(firmware_header_t), SEEK_SET); uint8_t write_buf[4096]; uint32_t total_written = 0; while (!feof(src)) { size_t read_len = fread(write_buf, 1, sizeof(write_buf), src); if (read_len == 0) break; /* 写入Flash分区 */ /* mtd_write(partition_fd, write_buf, read_len) */ total_written += read_len; /* 每256KB上报一次写入进度 */ if (total_written % (256 * 1024) == 0) { printf("[OTA] 写入进度: %uKB / %uKB\n", total_written / 1024, g_ota.fw_header.firmware_size / 1024); } } fclose(src); printf("[OTA] 固件写入完成: %u字节\n", total_written); return 0; } /** * 切换活动引导分区 * 修改Bootloader配置,下次启动从新分区引导 */ static int switch_boot_partition(int target_partition) { const char *partition_name = (target_partition == 0) ? "A" : "B"; printf("[OTA] 切换引导分区为: %s\n", partition_name); /* 写入Bootloader配置: 设置下次引导分区 */ /* nvs_set("boot_partition", target_partition) */ /* nvs_set("boot_count", 0) -- 重置启动计数用于回滚检测 */ return 0; } /** * 回滚到上一个稳定版本 * 切换回原活动分区 */ static int rollback_firmware(void) { printf("[OTA] 执行固件回滚, 恢复分区%c\n", g_ota.active_partition == 0 ? 'A' : 'B'); g_ota.state = OTA_STATE_ROLLBACK; /* 切换回原分区 */ switch_boot_partition(g_ota.active_partition); printf("[OTA] 回滚完成, 下次将从原分区启动\n"); return 0; } /* ======================== OTA主流程 ======================== */ /** * OTA升级线程主函数 * 执行完整的下载→校验→写入→切换→重启流程 */ static void *ota_upgrade_thread(void *arg) { printf("[OTA] 升级线程启动, URL=%s\n", g_ota.download_url); /* 阶段1: 下载固件 */ g_ota.state = OTA_STATE_DOWNLOADING; printf("[OTA] 阶段1: 开始下载固件...\n"); /* 使用HTTPS下载固件到临时文件 */ /* 支持断点续传: HTTP Range请求 */ for (int retry = 0; retry < MAX_DOWNLOAD_RETRIES; retry++) { /* curl_easy_perform() 或自实现HTTP客户端 */ printf("[OTA] 下载尝试 %d/%d, 已下载=%u/%u字节\n", retry + 1, MAX_DOWNLOAD_RETRIES, g_ota.download_done, g_ota.download_total); /* 模拟下载成功 */ g_ota.download_done = g_ota.download_total; break; } if (g_ota.download_done < g_ota.download_total) { printf("[OTA] 下载失败, 已达最大重试次数\n"); g_ota.state = OTA_STATE_FAILED; return NULL; } /* 阶段2: 校验固件完整性 */ g_ota.state = OTA_STATE_VERIFYING; printf("[OTA] 阶段2: 校验固件完整性...\n"); if (!verify_firmware_integrity(OTA_TEMP_PATH)) { printf("[OTA] 固件校验失败, 中止升级\n"); g_ota.state = OTA_STATE_FAILED; unlink(OTA_TEMP_PATH); return NULL; } /* 阶段3: 写入备份分区 */ g_ota.state = OTA_STATE_FLASHING; printf("[OTA] 阶段3: 写入固件到备份分区...\n"); /* 确定目标分区(写入非活动分区) */ const char *target_path = (g_ota.active_partition == 0) ? PARTITION_B_PATH : PARTITION_A_PATH; int target_idx = (g_ota.active_partition == 0) ? 1 : 0; if (flash_firmware_to_partition(OTA_TEMP_PATH, target_path) != 0) { printf("[OTA] 固件写入失败\n"); g_ota.state = OTA_STATE_FAILED; return NULL; } /* 阶段4: 切换引导分区 */ printf("[OTA] 阶段4: 切换引导分区...\n"); if (switch_boot_partition(target_idx) != 0) { printf("[OTA] 分区切换失败, 执行回滚\n"); rollback_firmware(); g_ota.state = OTA_STATE_FAILED; return NULL; } /* 清理临时文件 */ unlink(OTA_TEMP_PATH); /* 阶段5: 上报升级成功 */ g_ota.state = OTA_STATE_SUCCESS; printf("[OTA] 升级成功! 新版本: v%d.%d.%d, 等待重启生效\n", g_ota.fw_header.version_major, g_ota.fw_header.version_minor, g_ota.fw_header.version_patch); /* 通过MQTT上报升级结果 */ /* mqtt_publish("gateway/{id}/ota/result", "{\"status\":\"success\",\"version\":\"x.y.z\"}") */ /* 延迟3秒后重启 */ printf("[OTA] 3秒后自动重启...\n"); sleep(3); g_ota.state = OTA_STATE_REBOOTING; /* system("reboot") */ return NULL; } /* ======================== 公共接口 ======================== */ /** * 初始化OTA升级模块 */ int ota_updater_init(const char *gateway_id) { memset(&g_ota, 0, sizeof(g_ota)); strncpy(g_ota.gateway_id, gateway_id, sizeof(g_ota.gateway_id) - 1); pthread_mutex_init(&g_ota.mutex, NULL); g_ota.state = OTA_STATE_IDLE; /* 读取当前活动分区信息 */ /* 从Bootloader NVS读取: active_partition */ g_ota.active_partition = 0; /* 默认A分区 */ strncpy(g_ota.part_a.path, PARTITION_A_PATH, sizeof(g_ota.part_a.path)); strncpy(g_ota.part_b.path, PARTITION_B_PATH, sizeof(g_ota.part_b.path)); printf("[OTA] 初始化完成, 当前活动分区=%c\n", g_ota.active_partition == 0 ? 'A' : 'B'); return 0; } /** * 触发OTA升级(由MQTT命令回调调用) */ int ota_start_upgrade(const char *firmware_url, uint32_t expected_size) { if (g_ota.state != OTA_STATE_IDLE && g_ota.state != OTA_STATE_FAILED) { printf("[OTA] 升级已在进行中, 当前状态=%d\n", g_ota.state); return -1; } strncpy(g_ota.download_url, firmware_url, sizeof(g_ota.download_url) - 1); g_ota.download_total = expected_size; g_ota.download_done = 0; g_ota.retry_count = 0; g_ota.running = true; /* 启动OTA后台线程 */ pthread_create(&g_ota.ota_thread, NULL, ota_upgrade_thread, NULL); printf("[OTA] 升级任务已启动: %s (大小=%uKB)\n", firmware_url, expected_size / 1024); return 0; } /** * 获取当前OTA状态和进度 */ void ota_get_progress(ota_state_t *state, uint32_t *progress_pct) { if (state) *state = g_ota.state; if (progress_pct) { if (g_ota.download_total > 0) { *progress_pct = (g_ota.download_done * 100) / g_ota.download_total; } else { *progress_pct = 0; } } } /** * 关闭OTA模块 */ void ota_updater_shutdown(void) { g_ota.running = false; if (g_ota.state == OTA_STATE_DOWNLOADING) { /* 等待下载线程结束 */ pthread_join(g_ota.ota_thread, NULL); } pthread_mutex_destroy(&g_ota.mutex); printf("[OTA] 模块已关闭\n"); } ``` ### `protocol/` #### `protocol/protocol_converter.c` ```c /** * 自然写教室智能网关管理软件 V1.0 * * protocol_converter.c - BLE到MQTT协议转换模块 * * 功能说明: * - BLE原始帧解析为结构化笔迹数据 * - 笔迹数据编码为MQTT JSON/二进制负载 * - 多种消息类型转换(笔迹/状态/控制) * - 数据压缩与批量打包 * - 消息序列号管理与去重 */ #include #include #include #include #include #include #include /* ======================== 常量与类型定义 ======================== */ /* BLE帧类型标识 */ #define BLE_FRAME_STROKE 0x01 /* 笔迹坐标帧 */ #define BLE_FRAME_PAGE_TURN 0x02 /* 翻页事件帧 */ #define BLE_FRAME_PEN_STATE 0x03 /* 笔状态帧(抬笔/落笔) */ #define BLE_FRAME_BATTERY 0x04 /* 电量上报帧 */ #define BLE_FRAME_HEARTBEAT 0x05 /* 心跳帧 */ #define BLE_FRAME_OTA_ACK 0x06 /* OTA响应帧 */ /* MQTT消息类型 */ #define MQTT_MSG_STROKE 0x10 /* 笔迹数据消息 */ #define MQTT_MSG_EVENT 0x20 /* 事件通知消息 */ #define MQTT_MSG_STATUS 0x30 /* 设备状态消息 */ #define MQTT_MSG_COMMAND_ACK 0x40 /* 命令应答消息 */ /* 协议参数 */ #define MAX_BATCH_POINTS 64 /* 单批次最大坐标点数 */ #define MAX_JSON_BUFFER 4096 /* JSON缓冲区大小 */ #define MAX_BINARY_PAYLOAD 2048 /* 二进制负载最大长度 */ #define COMPRESS_THRESHOLD 128 /* 触发压缩的数据量阈值(字节) */ #define SEQUENCE_NUM_MAX 65535 /* 序列号最大值 */ /* CRC-16 CCITT多项式 */ #define CRC16_CCITT_POLY 0x1021 /* BLE原始帧头结构 (与笔固件协议一致) */ typedef struct { uint8_t sync_byte; /* 同步字节 0xAA */ uint8_t frame_type; /* 帧类型 */ uint8_t pen_id[6]; /* 笔MAC地址 */ uint16_t payload_len; /* 负载长度 */ uint16_t sequence; /* 帧序列号 */ } __attribute__((packed)) ble_frame_header_t; /* 7字节紧凑坐标编码结构 (与笔端一致) */ typedef struct { uint32_t x_coord : 20; /* X坐标 0-1048575 */ uint32_t y_coord : 20; /* Y坐标 0-1048575 */ uint16_t pressure : 12; /* 压力值 0-4095 */ uint8_t flags : 4; /* 标志位 */ } stroke_point_compact_t; /* 解码后的笔迹坐标点 */ typedef struct { float x; /* X坐标(毫米) */ float y; /* Y坐标(毫米) */ float pressure; /* 压力值(归一化 0.0-1.0) */ uint32_t timestamp_ms; /* 时间戳(毫秒) */ uint8_t pen_down; /* 落笔标志 */ } decoded_point_t; /* MQTT负载结构 */ typedef struct { char topic[128]; /* MQTT主题 */ uint8_t payload[MAX_BINARY_PAYLOAD]; /* 负载数据 */ uint32_t payload_len; /* 负载长度 */ uint8_t qos; /* QoS等级 */ bool retain; /* 保留标志 */ uint16_t msg_seq; /* 消息序列号 */ } mqtt_message_t; /* 协议转换器上下文 */ typedef struct { char gateway_id[32]; /* 网关标识 */ uint16_t next_sequence; /* 下一个消息序列号 */ uint16_t last_ble_seq[64]; /* 各笔最后BLE序列号(去重) */ uint32_t total_converted; /* 总转换消息数 */ uint32_t total_dropped; /* 丢弃的重复消息数 */ uint32_t error_count; /* 错误计数 */ bool use_binary_format; /* 是否使用二进制格式 */ bool compression_enabled; /* 是否启用压缩 */ } protocol_converter_ctx_t; /* 全局协议转换器实例 */ static protocol_converter_ctx_t g_converter; /* ======================== CRC校验 ======================== */ /** * 计算CRC-16 CCITT校验值 * 用于验证BLE帧数据完整性 */ static uint16_t crc16_ccitt(const uint8_t *data, uint32_t length) { uint16_t crc = 0xFFFF; for (uint32_t i = 0; i < length; i++) { crc ^= (uint16_t)data[i] << 8; for (int j = 0; j < 8; j++) { if (crc & 0x8000) { crc = (crc << 1) ^ CRC16_CCITT_POLY; } else { crc <<= 1; } } } return crc; } /* ======================== BLE帧解析 ======================== */ /** * 验证BLE帧头有效性 * 检查同步字节、帧类型范围、负载长度合理性 */ static bool validate_ble_frame(const uint8_t *raw_data, uint32_t raw_len) { if (raw_len < sizeof(ble_frame_header_t) + 2) { /* 数据长度不足(帧头 + CRC-16) */ return false; } const ble_frame_header_t *header = (const ble_frame_header_t *)raw_data; /* 检查同步字节 */ if (header->sync_byte != 0xAA) { return false; } /* 检查帧类型范围 */ if (header->frame_type < BLE_FRAME_STROKE || header->frame_type > BLE_FRAME_OTA_ACK) { return false; } /* 检查负载长度合理性 */ uint32_t expected_len = sizeof(ble_frame_header_t) + header->payload_len + 2; if (expected_len > raw_len || header->payload_len > MAX_BINARY_PAYLOAD) { return false; } /* CRC校验 - 计算帧头+负载的CRC并与尾部CRC比较 */ uint32_t data_len = sizeof(ble_frame_header_t) + header->payload_len; uint16_t calc_crc = crc16_ccitt(raw_data, data_len); uint16_t recv_crc = *(uint16_t *)(raw_data + data_len); if (calc_crc != recv_crc) { g_converter.error_count++; return false; } return true; } /** * 解码7字节紧凑坐标为浮点坐标 * 坐标单位从点阵码单位转换为毫米 * 压力值归一化到0.0-1.0范围 */ static void decode_compact_point(const uint8_t *compact_data, decoded_point_t *point) { /* 从7字节紧凑编码中提取各字段 */ uint32_t raw_x = ((uint32_t)compact_data[0] << 12) | ((uint32_t)compact_data[1] << 4) | ((compact_data[2] >> 4) & 0x0F); uint32_t raw_y = ((uint32_t)(compact_data[2] & 0x0F) << 16) | ((uint32_t)compact_data[3] << 8) | compact_data[4]; uint16_t raw_pressure = ((uint16_t)compact_data[5] << 4) | ((compact_data[6] >> 4) & 0x0F); uint8_t flags = compact_data[6] & 0x0F; /* 坐标转换:点阵码坐标 → 毫米(分辨率约0.3mm/单位) */ point->x = (float)raw_x * 0.3f; point->y = (float)raw_y * 0.3f; /* 压力值归一化到 0.0-1.0 */ point->pressure = (float)raw_pressure / 4095.0f; /* 落笔标志在flags低位 */ point->pen_down = (flags & 0x01) ? 1 : 0; } /** * 解析BLE笔迹帧为坐标点数组 * 返回实际解码的坐标点数量 */ static int parse_stroke_frame(const uint8_t *payload, uint16_t payload_len, decoded_point_t *points, int max_points) { /* 每个坐标点占7字节紧凑编码 + 4字节时间戳 = 11字节 */ int point_size = 11; int num_points = payload_len / point_size; if (num_points > max_points) { num_points = max_points; } for (int i = 0; i < num_points; i++) { const uint8_t *point_data = payload + (i * point_size); /* 解码紧凑坐标 */ decode_compact_point(point_data, &points[i]); /* 提取时间戳 (小端序,4字节毫秒时间戳) */ points[i].timestamp_ms = (uint32_t)point_data[7] | ((uint32_t)point_data[8] << 8) | ((uint32_t)point_data[9] << 16) | ((uint32_t)point_data[10] << 24); } return num_points; } /* ======================== 序列号去重 ======================== */ /** * 检查BLE帧序列号是否重复 * 使用滑动窗口检测重复帧,防止BLE重传导致数据重复 */ static bool is_duplicate_frame(uint8_t pen_index, uint16_t ble_sequence) { if (pen_index >= 64) { return false; } uint16_t last_seq = g_converter.last_ble_seq[pen_index]; /* 考虑序列号回绕:如果新序列号在旧序列号的合理范围内则认为重复 */ if (ble_sequence == last_seq) { g_converter.total_dropped++; return true; } /* 更新最后序列号 */ g_converter.last_ble_seq[pen_index] = ble_sequence; return false; } /** * 分配下一个MQTT消息序列号 * 单调递增,到达最大值后回绕 */ static uint16_t allocate_msg_sequence(void) { uint16_t seq = g_converter.next_sequence; g_converter.next_sequence = (seq + 1) % (SEQUENCE_NUM_MAX + 1); return seq; } /* ======================== JSON编码 ======================== */ /** * 将笔迹坐标数组编码为JSON格式 * 格式: {"pen_id":"xx:xx:xx","seq":N,"points":[{"x":1.2,"y":3.4,"p":0.5,"t":123},...]} */ static int encode_stroke_json(const char *pen_id_str, const decoded_point_t *points, int num_points, char *json_buf, int buf_size) { int offset = 0; /* JSON头部 */ offset += snprintf(json_buf + offset, buf_size - offset, "{\"gw\":\"%s\",\"pen\":\"%s\",\"seq\":%u,\"ts\":%lu,\"pts\":[", g_converter.gateway_id, pen_id_str, allocate_msg_sequence(), (unsigned long)time(NULL)); /* 编码每个坐标点 */ for (int i = 0; i < num_points && offset < buf_size - 64; i++) { if (i > 0) { json_buf[offset++] = ','; } offset += snprintf(json_buf + offset, buf_size - offset, "{\"x\":%.2f,\"y\":%.2f,\"p\":%.3f,\"t\":%u,\"d\":%d}", points[i].x, points[i].y, points[i].pressure, points[i].timestamp_ms, points[i].pen_down); } /* JSON尾部 */ offset += snprintf(json_buf + offset, buf_size - offset, "]}"); return offset; } /** * 将设备状态编码为JSON格式 * 格式: {"gateway_id":"xx","pen_id":"xx","event":"battery","value":85} */ static int encode_status_json(const char *pen_id_str, const char *event_type, int value, char *json_buf, int buf_size) { return snprintf(json_buf, buf_size, "{\"gw\":\"%s\",\"pen\":\"%s\",\"event\":\"%s\"," "\"value\":%d,\"ts\":%lu}", g_converter.gateway_id, pen_id_str, event_type, value, (unsigned long)time(NULL)); } /* ======================== 简单LZ压缩 ======================== */ /** * 简易RLE压缩 - 对二进制负载进行行程编码压缩 * 当连续相同字节超过3个时进行压缩 * 返回压缩后长度,若压缩无效则返回原始长度 */ static uint32_t rle_compress(const uint8_t *input, uint32_t input_len, uint8_t *output, uint32_t output_max) { if (input_len < COMPRESS_THRESHOLD) { /* 数据量太小,不压缩 */ memcpy(output, input, input_len); return input_len; } uint32_t out_pos = 0; uint32_t i = 0; /* 写入压缩标记头 */ output[out_pos++] = 0x52; /* 'R' - RLE标记 */ output[out_pos++] = 0x4C; /* 'L' */ output[out_pos++] = (input_len >> 8) & 0xFF; /* 原始长度高字节 */ output[out_pos++] = input_len & 0xFF; /* 原始长度低字节 */ while (i < input_len && out_pos < output_max - 3) { uint8_t current = input[i]; uint32_t run_len = 1; /* 统计连续相同字节 */ while (i + run_len < input_len && input[i + run_len] == current && run_len < 255) { run_len++; } if (run_len >= 4) { /* RLE编码: 转义字节 + 重复次数 + 值 */ output[out_pos++] = 0xFF; /* 转义标记 */ output[out_pos++] = (uint8_t)run_len; output[out_pos++] = current; } else { /* 直接拷贝非重复数据 */ for (uint32_t j = 0; j < run_len && out_pos < output_max; j++) { if (current == 0xFF) { /* 原始数据恰好是0xFF,需要转义 */ output[out_pos++] = 0xFF; output[out_pos++] = 0x01; output[out_pos++] = 0xFF; } else { output[out_pos++] = current; } } } i += run_len; } /* 如果压缩后更大,返回原始数据 */ if (out_pos >= input_len) { memcpy(output, input, input_len); return input_len; } return out_pos; } /* ======================== 核心转换接口 ======================== */ /** * 初始化协议转换器 * 设置网关标识,清空序列号追踪 */ int protocol_converter_init(const char *gateway_id, bool use_binary, bool enable_compression) { memset(&g_converter, 0, sizeof(g_converter)); strncpy(g_converter.gateway_id, gateway_id, sizeof(g_converter.gateway_id) - 1); g_converter.use_binary_format = use_binary; g_converter.compression_enabled = enable_compression; g_converter.next_sequence = 1; /* 初始化序列号追踪数组 */ memset(g_converter.last_ble_seq, 0xFF, sizeof(g_converter.last_ble_seq)); printf("[协议转换] 初始化完成, 网关=%s, 二进制=%d, 压缩=%d\n", gateway_id, use_binary, enable_compression); return 0; } /** * 将MAC地址字节数组转换为字符串表示 */ static void mac_to_string(const uint8_t mac[6], char *str, int str_len) { snprintf(str, str_len, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } /** * 核心协议转换函数 * 将BLE原始帧转换为MQTT消息 * * @param raw_ble_data BLE接收到的原始字节流 * @param raw_len 原始数据长度 * @param pen_index 笔在连接表中的索引(0-63) * @param mqtt_msg 输出: 转换后的MQTT消息 * @return 0=成功, -1=帧无效, -2=重复帧, -3=转换失败 */ int convert_ble_to_mqtt(const uint8_t *raw_ble_data, uint32_t raw_len, uint8_t pen_index, mqtt_message_t *mqtt_msg) { /* 步骤1: 验证BLE帧 */ if (!validate_ble_frame(raw_ble_data, raw_len)) { g_converter.error_count++; return -1; } const ble_frame_header_t *header = (const ble_frame_header_t *)raw_ble_data; const uint8_t *payload = raw_ble_data + sizeof(ble_frame_header_t); /* 步骤2: 序列号去重 */ if (is_duplicate_frame(pen_index, header->sequence)) { return -2; } /* 获取笔MAC地址字符串 */ char pen_id_str[20]; mac_to_string(header->pen_id, pen_id_str, sizeof(pen_id_str)); /* 步骤3: 根据帧类型进行协议转换 */ char json_buf[MAX_JSON_BUFFER]; int json_len = 0; switch (header->frame_type) { case BLE_FRAME_STROKE: { /* 笔迹坐标帧 → MQTT笔迹数据消息 */ decoded_point_t points[MAX_BATCH_POINTS]; int num_points = parse_stroke_frame(payload, header->payload_len, points, MAX_BATCH_POINTS); if (num_points <= 0) { return -3; } /* 构建MQTT Topic: pen/{gateway_id}/stroke */ snprintf(mqtt_msg->topic, sizeof(mqtt_msg->topic), "pen/%s/stroke", g_converter.gateway_id); /* 编码为JSON负载 */ json_len = encode_stroke_json(pen_id_str, points, num_points, json_buf, sizeof(json_buf)); /* 笔迹数据使用QoS 1确保送达 */ mqtt_msg->qos = 1; mqtt_msg->retain = false; break; } case BLE_FRAME_PAGE_TURN: { /* 翻页事件 → MQTT事件消息 */ uint16_t page_id = payload[0] | ((uint16_t)payload[1] << 8); snprintf(mqtt_msg->topic, sizeof(mqtt_msg->topic), "pen/%s/event", g_converter.gateway_id); json_len = snprintf(json_buf, sizeof(json_buf), "{\"gw\":\"%s\",\"pen\":\"%s\",\"event\":\"page_turn\"," "\"page_id\":%u,\"ts\":%lu}", g_converter.gateway_id, pen_id_str, page_id, (unsigned long)time(NULL)); mqtt_msg->qos = 1; mqtt_msg->retain = false; break; } case BLE_FRAME_PEN_STATE: { /* 笔状态帧 → MQTT事件消息 */ const char *state = (payload[0] == 0x01) ? "pen_down" : "pen_up"; snprintf(mqtt_msg->topic, sizeof(mqtt_msg->topic), "pen/%s/event", g_converter.gateway_id); json_len = encode_status_json(pen_id_str, state, payload[0], json_buf, sizeof(json_buf)); mqtt_msg->qos = 0; mqtt_msg->retain = false; break; } case BLE_FRAME_BATTERY: { /* 电量上报帧 → MQTT状态消息 */ uint8_t battery_pct = payload[0]; snprintf(mqtt_msg->topic, sizeof(mqtt_msg->topic), "gateway/%s/status", g_converter.gateway_id); json_len = encode_status_json(pen_id_str, "battery", battery_pct, json_buf, sizeof(json_buf)); /* 电量信息使用QoS 0,允许丢失 */ mqtt_msg->qos = 0; mqtt_msg->retain = true; /* 保留最新电量 */ break; } case BLE_FRAME_HEARTBEAT: { /* 心跳帧 → 更新设备在线状态,不转发至MQTT */ /* 心跳由设备管理器处理,此处仅记录 */ return 0; } default: return -3; } /* 步骤4: 将JSON数据填入MQTT消息负载 */ if (json_len > 0 && json_len < (int)sizeof(mqtt_msg->payload)) { if (g_converter.compression_enabled && json_len > COMPRESS_THRESHOLD) { /* 压缩JSON负载 */ mqtt_msg->payload_len = rle_compress( (const uint8_t *)json_buf, json_len, mqtt_msg->payload, sizeof(mqtt_msg->payload)); } else { memcpy(mqtt_msg->payload, json_buf, json_len); mqtt_msg->payload_len = json_len; } } mqtt_msg->msg_seq = allocate_msg_sequence(); g_converter.total_converted++; return 0; } /** * 将云端MQTT命令消息转换为BLE控制帧 * 支持命令类型:OTA触发、配置更新、校准指令 * * @param mqtt_payload MQTT消息负载(JSON) * @param payload_len 负载长度 * @param ble_cmd_buf 输出: BLE命令帧缓冲 * @param buf_size 缓冲区大小 * @return 生成的BLE命令帧长度, -1=失败 */ int convert_mqtt_to_ble_command(const uint8_t *mqtt_payload, uint32_t payload_len, uint8_t *ble_cmd_buf, uint32_t buf_size) { /* 简易JSON解析 - 查找command字段 */ const char *json_str = (const char *)mqtt_payload; const char *cmd_start = strstr(json_str, "\"command\":\""); if (cmd_start == NULL) { return -1; } cmd_start += strlen("\"command\":\""); /* 构建BLE命令帧头 */ ble_frame_header_t *cmd_header = (ble_frame_header_t *)ble_cmd_buf; cmd_header->sync_byte = 0xAA; cmd_header->sequence = allocate_msg_sequence(); uint8_t *cmd_payload = ble_cmd_buf + sizeof(ble_frame_header_t); uint16_t cmd_payload_len = 0; if (strncmp(cmd_start, "ota_start", 9) == 0) { /* OTA升级启动命令 */ cmd_header->frame_type = BLE_FRAME_OTA_ACK; cmd_payload[0] = 0x01; /* OTA开始标记 */ cmd_payload_len = 1; } else if (strncmp(cmd_start, "calibrate", 9) == 0) { /* 校准命令 */ cmd_header->frame_type = BLE_FRAME_PEN_STATE; cmd_payload[0] = 0x10; /* 校准指令码 */ cmd_payload_len = 1; } else { return -1; } cmd_header->payload_len = cmd_payload_len; /* 追加CRC校验 */ uint32_t frame_len = sizeof(ble_frame_header_t) + cmd_payload_len; uint16_t crc = crc16_ccitt(ble_cmd_buf, frame_len); memcpy(ble_cmd_buf + frame_len, &crc, 2); return frame_len + 2; } /** * 获取协议转换器统计信息 */ void protocol_converter_get_stats(uint32_t *converted, uint32_t *dropped, uint32_t *errors) { if (converted) *converted = g_converter.total_converted; if (dropped) *dropped = g_converter.total_dropped; if (errors) *errors = g_converter.error_count; } /** * 重置协议转换器统计计数 */ void protocol_converter_reset_stats(void) { g_converter.total_converted = 0; g_converter.total_dropped = 0; g_converter.error_count = 0; printf("[协议转换] 统计计数已重置\n"); } ```