4197 lines
126 KiB
Markdown
4197 lines
126 KiB
Markdown
# 自然写教室智能网关管理软件 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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <signal.h>
|
||
#include <unistd.h>
|
||
#include <pthread.h>
|
||
#include <sys/epoll.h>
|
||
#include <sys/time.h>
|
||
#include <syslog.h>
|
||
#include <errno.h>
|
||
|
||
/* 模块头文件 */
|
||
#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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <pthread.h>
|
||
#include <errno.h>
|
||
#include <syslog.h>
|
||
|
||
/* BlueZ D-Bus头文件 */
|
||
#include <bluetooth/bluetooth.h>
|
||
#include <bluetooth/hci.h>
|
||
#include <bluetooth/hci_lib.h>
|
||
|
||
/* 模块头文件 */
|
||
#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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
#include <time.h>
|
||
#include <pthread.h>
|
||
#include <sys/stat.h>
|
||
#include <unistd.h>
|
||
|
||
/* ======================== 常量定义 ======================== */
|
||
|
||
/* 离线缓存数据库路径 */
|
||
#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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
#include <pthread.h>
|
||
|
||
/* ======================== 常量定义 ======================== */
|
||
|
||
/* 默认缓冲区大小 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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
#include <time.h>
|
||
#include <sys/stat.h>
|
||
|
||
/* ======================== 常量定义 ======================== */
|
||
|
||
/* 配置文件路径 */
|
||
#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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
#include <time.h>
|
||
#include <pthread.h>
|
||
#include <unistd.h>
|
||
|
||
/* ======================== 常量定义 ======================== */
|
||
|
||
/* 最大设备数量 */
|
||
#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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <pthread.h>
|
||
#include <syslog.h>
|
||
#include <errno.h>
|
||
#include <time.h>
|
||
|
||
/* Mosquitto MQTT库 */
|
||
#include <mosquitto.h>
|
||
|
||
/* 模块头文件 */
|
||
#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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
#include <time.h>
|
||
#include <unistd.h>
|
||
#include <sys/stat.h>
|
||
#include <pthread.h>
|
||
|
||
/* ======================== 常量定义 ======================== */
|
||
|
||
/* 固件分区路径 */
|
||
#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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
#include <time.h>
|
||
#include <math.h>
|
||
|
||
/* ======================== 常量与类型定义 ======================== */
|
||
|
||
/* 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");
|
||
}
|
||
```
|
||
|