433 lines
13 KiB
C
433 lines
13 KiB
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);
|
|
}
|