Files
system-design/software-copyright/04-writech-gateway/自然写教室智能网关管理软件-源程序.md
T
2026-03-22 15:24:40 +08:00

4197 lines
126 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 自然写教室智能网关管理软件 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 或 0x07128位服务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");
}
```