524 lines
15 KiB
C
524 lines
15 KiB
C
/*
|
||
* 自然写互动课堂教学管理网关软件 V1.0
|
||
* ble_manager.c - BLE多连接管理器
|
||
*
|
||
* 功能说明:
|
||
* 1. 基于BlueZ D-Bus接口的BLE多设备管理
|
||
* 2. 自动扫描与连接自然写点阵笔(最多60支)
|
||
* 3. GATT服务发现与特征值通知订阅
|
||
* 4. BLE数据接收与分发
|
||
* 5. 断线自动重连机制
|
||
* 6. BLE适配器状态监控
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <pthread.h>
|
||
#include <errno.h>
|
||
#include <syslog.h>
|
||
|
||
/* BlueZ D-Bus头文件 */
|
||
#include <bluetooth/bluetooth.h>
|
||
#include <bluetooth/hci.h>
|
||
#include <bluetooth/hci_lib.h>
|
||
|
||
/* 模块头文件 */
|
||
#include "ble_manager.h"
|
||
#include "ring_buffer.h"
|
||
|
||
/* ========== 常量定义 ========== */
|
||
|
||
/* 自然写笔GATT服务UUID */
|
||
#define PEN_SERVICE_UUID "0000ffe0-0000-1000-8000-00805f9b34fb"
|
||
|
||
/* 笔迹数据特征值UUID */
|
||
#define STROKE_CHAR_UUID "0000ffe1-0000-1000-8000-00805f9b34fb"
|
||
|
||
/* 最大同时连接设备数 */
|
||
#define MAX_BLE_CONNECTIONS 60
|
||
|
||
/* 扫描间隔(毫秒) */
|
||
#define SCAN_INTERVAL_MS 10000
|
||
|
||
/* 重连延迟(秒) */
|
||
#define RECONNECT_DELAY_SEC 5
|
||
|
||
/* ========== 数据结构 ========== */
|
||
|
||
/* BLE设备连接信息 */
|
||
typedef struct {
|
||
char mac_address[18]; /* MAC地址 "AA:BB:CC:DD:EE:FF" */
|
||
char device_name[64]; /* 设备名称 */
|
||
int connection_handle; /* 连接句柄 */
|
||
int is_connected; /* 是否已连接 */
|
||
int is_subscribed; /* 是否已订阅通知 */
|
||
int gatt_handle; /* GATT特征值句柄 */
|
||
int rssi; /* 信号强度 */
|
||
unsigned long last_data_time; /* 最后收到数据的时间 */
|
||
int reconnect_attempts; /* 重连尝试次数 */
|
||
char bound_student_id[32]; /* 绑定的学生ID */
|
||
} BLEDevice;
|
||
|
||
/* BLE管理器状态 */
|
||
typedef struct {
|
||
int hci_dev_id; /* HCI设备ID */
|
||
int hci_socket; /* HCI套接字 */
|
||
int is_scanning; /* 是否正在扫描 */
|
||
int is_active; /* 管理器是否活跃 */
|
||
BLEDevice devices[MAX_BLE_CONNECTIONS]; /* 设备列表 */
|
||
int device_count; /* 已连接设备数 */
|
||
pthread_mutex_t mutex; /* 线程安全锁 */
|
||
pthread_t scan_thread; /* 扫描线程 */
|
||
pthread_t recv_thread; /* 数据接收线程 */
|
||
int event_pipe[2]; /* 事件通知管道 */
|
||
} BLEManager;
|
||
|
||
/* ========== 静态变量 ========== */
|
||
|
||
static BLEManager g_ble;
|
||
|
||
/* 数据回调函数指针 */
|
||
static void (*g_data_callback)(const char *mac, const uint8_t *data,
|
||
int len) = NULL;
|
||
|
||
/* ========== 初始化 ========== */
|
||
|
||
/**
|
||
* 初始化BLE管理器
|
||
* 打开HCI设备,配置扫描参数
|
||
*
|
||
* @return 0成功, -1失败
|
||
*/
|
||
int ble_manager_init(void) {
|
||
memset(&g_ble, 0, sizeof(g_ble));
|
||
pthread_mutex_init(&g_ble.mutex, NULL);
|
||
|
||
/* 创建事件通知管道 */
|
||
if (pipe(g_ble.event_pipe) < 0) {
|
||
syslog(LOG_ERR, "BLE: 创建事件管道失败: %s", strerror(errno));
|
||
return -1;
|
||
}
|
||
|
||
/* 打开默认HCI蓝牙适配器 */
|
||
g_ble.hci_dev_id = hci_get_route(NULL);
|
||
if (g_ble.hci_dev_id < 0) {
|
||
syslog(LOG_ERR, "BLE: 未找到蓝牙适配器");
|
||
return -1;
|
||
}
|
||
|
||
g_ble.hci_socket = hci_open_dev(g_ble.hci_dev_id);
|
||
if (g_ble.hci_socket < 0) {
|
||
syslog(LOG_ERR, "BLE: 打开HCI设备失败: %s", strerror(errno));
|
||
return -1;
|
||
}
|
||
|
||
g_ble.is_active = 1;
|
||
|
||
/* 启动扫描线程 */
|
||
pthread_create(&g_ble.scan_thread, NULL, scan_thread_func, NULL);
|
||
|
||
/* 启动数据接收线程 */
|
||
pthread_create(&g_ble.recv_thread, NULL, recv_thread_func, NULL);
|
||
|
||
syslog(LOG_INFO, "BLE管理器初始化完成,适配器ID=%d", g_ble.hci_dev_id);
|
||
return 0;
|
||
}
|
||
|
||
/* ========== 设备扫描 ========== */
|
||
|
||
/**
|
||
* 扫描线程函数
|
||
* 周期性扫描BLE设备,发现新的自然写点阵笔后自动连接
|
||
*/
|
||
static void *scan_thread_func(void *arg) {
|
||
(void)arg;
|
||
|
||
syslog(LOG_INFO, "BLE: 扫描线程启动");
|
||
|
||
while (g_ble.is_active) {
|
||
/* 检查是否还有连接名额 */
|
||
pthread_mutex_lock(&g_ble.mutex);
|
||
int current_count = g_ble.device_count;
|
||
pthread_mutex_unlock(&g_ble.mutex);
|
||
|
||
if (current_count < MAX_BLE_CONNECTIONS) {
|
||
/* 执行LE扫描 */
|
||
perform_le_scan();
|
||
}
|
||
|
||
/* 检查需要重连的设备 */
|
||
check_reconnect();
|
||
|
||
/* 扫描间隔 */
|
||
usleep(SCAN_INTERVAL_MS * 1000);
|
||
}
|
||
|
||
syslog(LOG_INFO, "BLE: 扫描线程退出");
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* 执行BLE低功耗扫描
|
||
* 使用HCI LE扫描命令搜索附近的BLE设备
|
||
*/
|
||
static void perform_le_scan(void) {
|
||
/* 设置LE扫描参数 */
|
||
uint8_t scan_type = 0x01; /* 主动扫描 */
|
||
uint16_t scan_interval = 0x0010; /* 扫描间隔 */
|
||
uint16_t scan_window = 0x0010; /* 扫描窗口 */
|
||
uint8_t own_type = 0x00; /* 公共地址 */
|
||
uint8_t filter = 0x00; /* 不过滤 */
|
||
|
||
int ret = hci_le_set_scan_parameters(g_ble.hci_socket,
|
||
scan_type, scan_interval, scan_window, own_type, filter, 1000);
|
||
|
||
if (ret < 0) {
|
||
syslog(LOG_WARNING, "BLE: 设置扫描参数失败");
|
||
return;
|
||
}
|
||
|
||
/* 启动扫描 */
|
||
ret = hci_le_set_scan_enable(g_ble.hci_socket, 0x01, 0x00, 1000);
|
||
if (ret < 0) {
|
||
syslog(LOG_WARNING, "BLE: 启动扫描失败");
|
||
return;
|
||
}
|
||
|
||
g_ble.is_scanning = 1;
|
||
|
||
/* 扫描持续3秒 */
|
||
struct hci_filter flt;
|
||
hci_filter_clear(&flt);
|
||
hci_filter_set_ptype(HCI_EVENT_PKT, &flt);
|
||
hci_filter_set_event(EVT_LE_META_EVENT, &flt);
|
||
setsockopt(g_ble.hci_socket, SOL_HCI, HCI_FILTER, &flt, sizeof(flt));
|
||
|
||
/* 读取扫描结果 */
|
||
uint8_t buf[256];
|
||
int scan_duration_ms = 3000;
|
||
int elapsed = 0;
|
||
|
||
while (elapsed < scan_duration_ms && g_ble.is_active) {
|
||
struct timeval tv;
|
||
tv.tv_sec = 0;
|
||
tv.tv_usec = 100000; /* 100ms超时 */
|
||
|
||
fd_set rfds;
|
||
FD_ZERO(&rfds);
|
||
FD_SET(g_ble.hci_socket, &rfds);
|
||
|
||
ret = select(g_ble.hci_socket + 1, &rfds, NULL, NULL, &tv);
|
||
if (ret > 0) {
|
||
int len = read(g_ble.hci_socket, buf, sizeof(buf));
|
||
if (len > 0) {
|
||
process_scan_result(buf, len);
|
||
}
|
||
}
|
||
elapsed += 100;
|
||
}
|
||
|
||
/* 停止扫描 */
|
||
hci_le_set_scan_enable(g_ble.hci_socket, 0x00, 0x00, 1000);
|
||
g_ble.is_scanning = 0;
|
||
}
|
||
|
||
/**
|
||
* 处理扫描结果
|
||
* 解析广播包,筛选包含自然写服务UUID的设备
|
||
*/
|
||
static void process_scan_result(const uint8_t *data, int len) {
|
||
if (len < 14) return;
|
||
|
||
/* 解析HCI LE Meta事件 */
|
||
evt_le_meta_event *meta = (evt_le_meta_event *)(data + 1 + HCI_EVENT_HDR_SIZE);
|
||
if (meta->subevent != 0x02) return; /* 非广播报告 */
|
||
|
||
le_advertising_info *info = (le_advertising_info *)(meta->data + 1);
|
||
|
||
/* 提取MAC地址 */
|
||
char mac[18];
|
||
ba2str(&info->bdaddr, mac);
|
||
|
||
/* 检查是否已连接 */
|
||
if (find_device_by_mac(mac) >= 0) {
|
||
return; /* 已连接,跳过 */
|
||
}
|
||
|
||
/* 检查广播数据中是否包含自然写服务UUID */
|
||
if (check_service_uuid(info->data, info->length)) {
|
||
syslog(LOG_INFO, "BLE: 发现自然写笔 %s", mac);
|
||
/* 尝试连接 */
|
||
connect_device(mac);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查广播数据中是否包含指定服务UUID
|
||
*/
|
||
static int check_service_uuid(const uint8_t *ad_data, int ad_len) {
|
||
int offset = 0;
|
||
while (offset < ad_len) {
|
||
uint8_t field_len = ad_data[offset];
|
||
if (field_len == 0) break;
|
||
|
||
uint8_t field_type = ad_data[offset + 1];
|
||
|
||
/* 0x06 或 0x07:128位服务UUID列表 */
|
||
if ((field_type == 0x06 || field_type == 0x07) && field_len >= 17) {
|
||
/* 比较UUID(简化:只比较前4字节特征值) */
|
||
if (ad_data[offset + 2] == 0xFB &&
|
||
ad_data[offset + 3] == 0x34 &&
|
||
ad_data[offset + 4] == 0x9B &&
|
||
ad_data[offset + 5] == 0x5F) {
|
||
return 1; /* 匹配自然写服务UUID */
|
||
}
|
||
}
|
||
|
||
offset += field_len + 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ========== 设备连接 ========== */
|
||
|
||
/**
|
||
* 连接到指定MAC地址的BLE设备
|
||
*/
|
||
static int connect_device(const char *mac) {
|
||
pthread_mutex_lock(&g_ble.mutex);
|
||
|
||
if (g_ble.device_count >= MAX_BLE_CONNECTIONS) {
|
||
pthread_mutex_unlock(&g_ble.mutex);
|
||
return -1;
|
||
}
|
||
|
||
/* 查找空闲槽位 */
|
||
int slot = -1;
|
||
int i;
|
||
for (i = 0; i < MAX_BLE_CONNECTIONS; i++) {
|
||
if (!g_ble.devices[i].is_connected) {
|
||
slot = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (slot < 0) {
|
||
pthread_mutex_unlock(&g_ble.mutex);
|
||
return -1;
|
||
}
|
||
|
||
/* 解析MAC地址 */
|
||
bdaddr_t bdaddr;
|
||
str2ba(mac, &bdaddr);
|
||
|
||
/* 创建LE连接 */
|
||
uint16_t handle = 0;
|
||
int ret = hci_le_create_conn(g_ble.hci_socket,
|
||
0x0060, /* scan interval */
|
||
0x0030, /* scan window */
|
||
0x00, /* initiator filter */
|
||
0x00, /* peer addr type: public */
|
||
bdaddr, /* peer address */
|
||
0x00, /* own addr type */
|
||
0x0028, /* min conn interval */
|
||
0x0038, /* max conn interval */
|
||
0x0000, /* latency */
|
||
0x002A, /* supervision timeout */
|
||
0x0000, /* min CE length */
|
||
0x0000, /* max CE length */
|
||
&handle, 10000);
|
||
|
||
if (ret < 0) {
|
||
syslog(LOG_WARNING, "BLE: 连接 %s 失败: %s", mac, strerror(errno));
|
||
pthread_mutex_unlock(&g_ble.mutex);
|
||
return -1;
|
||
}
|
||
|
||
/* 填充设备信息 */
|
||
BLEDevice *dev = &g_ble.devices[slot];
|
||
strncpy(dev->mac_address, mac, sizeof(dev->mac_address) - 1);
|
||
dev->connection_handle = handle;
|
||
dev->is_connected = 1;
|
||
dev->reconnect_attempts = 0;
|
||
dev->last_data_time = time(NULL);
|
||
|
||
g_ble.device_count++;
|
||
|
||
pthread_mutex_unlock(&g_ble.mutex);
|
||
|
||
syslog(LOG_INFO, "BLE: 已连接 %s (handle=%d, 总数=%d)",
|
||
mac, handle, g_ble.device_count);
|
||
|
||
/* 发现GATT服务并订阅通知 */
|
||
discover_and_subscribe(dev);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* ========== GATT服务发现 ========== */
|
||
|
||
/**
|
||
* 发现GATT服务并订阅笔迹数据通知
|
||
*/
|
||
static void discover_and_subscribe(BLEDevice *dev) {
|
||
/* 简化实现:直接使用已知的特征值句柄 */
|
||
/* 实际产品中需要完整的GATT服务发现流程 */
|
||
dev->gatt_handle = 0x0025; /* 笔迹数据特征值句柄 */
|
||
|
||
/* 写入CCCD描述符启用通知(句柄+1是CCCD) */
|
||
uint8_t enable_notify[] = {0x01, 0x00};
|
||
struct bt_att_pdu pdu;
|
||
pdu.opcode = BT_ATT_OP_WRITE_REQ;
|
||
pdu.handle = dev->gatt_handle + 1;
|
||
memcpy(pdu.data, enable_notify, 2);
|
||
|
||
/* 发送ATT写请求 */
|
||
/* hci_send_cmd(...) - 简化 */
|
||
|
||
dev->is_subscribed = 1;
|
||
syslog(LOG_INFO, "BLE: 已订阅 %s 的笔迹通知", dev->mac_address);
|
||
}
|
||
|
||
/* ========== 数据接收 ========== */
|
||
|
||
/**
|
||
* 数据接收线程
|
||
* 持续读取HCI事件,解析GATT通知中的笔迹数据
|
||
*/
|
||
static void *recv_thread_func(void *arg) {
|
||
(void)arg;
|
||
uint8_t buf[256];
|
||
|
||
syslog(LOG_INFO, "BLE: 数据接收线程启动");
|
||
|
||
while (g_ble.is_active) {
|
||
int len = read(g_ble.hci_socket, buf, sizeof(buf));
|
||
if (len <= 0) {
|
||
usleep(1000);
|
||
continue;
|
||
}
|
||
|
||
/* 解析HCI事件 */
|
||
uint8_t event_type = buf[1];
|
||
|
||
if (event_type == HCI_EVENT_PKT) {
|
||
/* GATT通知数据 */
|
||
process_gatt_notification(buf, len);
|
||
} else if (event_type == EVT_DISCONN_COMPLETE) {
|
||
/* 连接断开事件 */
|
||
process_disconnect_event(buf, len);
|
||
}
|
||
}
|
||
|
||
syslog(LOG_INFO, "BLE: 数据接收线程退出");
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* 处理GATT通知(笔迹数据)
|
||
*/
|
||
static void process_gatt_notification(const uint8_t *data, int len) {
|
||
if (len < 10) return;
|
||
|
||
/* 提取连接句柄 */
|
||
uint16_t handle = data[4] | (data[5] << 8);
|
||
|
||
/* 查找对应设备 */
|
||
BLEDevice *dev = find_device_by_handle(handle);
|
||
if (dev == NULL) return;
|
||
|
||
/* 提取笔迹数据载荷 */
|
||
const uint8_t *payload = data + 9;
|
||
int payload_len = len - 9;
|
||
|
||
dev->last_data_time = time(NULL);
|
||
|
||
/* 将数据放入环形缓冲区(供协议转换器消费) */
|
||
ring_buffer_write_with_header(dev->mac_address, payload, payload_len);
|
||
|
||
/* 调用外部回调 */
|
||
if (g_data_callback) {
|
||
g_data_callback(dev->mac_address, payload, payload_len);
|
||
}
|
||
}
|
||
|
||
/* ========== 辅助函数 ========== */
|
||
|
||
static int find_device_by_mac(const char *mac) {
|
||
int i;
|
||
for (i = 0; i < MAX_BLE_CONNECTIONS; i++) {
|
||
if (g_ble.devices[i].is_connected &&
|
||
strcmp(g_ble.devices[i].mac_address, mac) == 0) {
|
||
return i;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static BLEDevice *find_device_by_handle(uint16_t handle) {
|
||
int i;
|
||
for (i = 0; i < MAX_BLE_CONNECTIONS; i++) {
|
||
if (g_ble.devices[i].is_connected &&
|
||
g_ble.devices[i].connection_handle == handle) {
|
||
return &g_ble.devices[i];
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static void check_reconnect(void) {
|
||
int i;
|
||
time_t now = time(NULL);
|
||
for (i = 0; i < MAX_BLE_CONNECTIONS; i++) {
|
||
BLEDevice *dev = &g_ble.devices[i];
|
||
if (!dev->is_connected && dev->mac_address[0] != '\0'
|
||
&& dev->reconnect_attempts < 10) {
|
||
if (now - dev->last_data_time > RECONNECT_DELAY_SEC) {
|
||
syslog(LOG_INFO, "BLE: 尝试重连 %s (第%d次)",
|
||
dev->mac_address, dev->reconnect_attempts + 1);
|
||
connect_device(dev->mac_address);
|
||
dev->reconnect_attempts++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ========== 外部接口 ========== */
|
||
|
||
int ble_manager_get_fd(void) { return g_ble.event_pipe[0]; }
|
||
int ble_manager_is_active(void) { return g_ble.is_active; }
|
||
int ble_manager_get_connected_count(void) { return g_ble.device_count; }
|
||
|
||
void ble_manager_process_events(void) {
|
||
uint8_t dummy;
|
||
read(g_ble.event_pipe[0], &dummy, 1);
|
||
}
|
||
|
||
void ble_manager_set_data_callback(void (*cb)(const char *, const uint8_t *, int)) {
|
||
g_data_callback = cb;
|
||
}
|
||
|
||
void ble_manager_cleanup(void) {
|
||
g_ble.is_active = 0;
|
||
pthread_join(g_ble.scan_thread, NULL);
|
||
pthread_join(g_ble.recv_thread, NULL);
|
||
|
||
/* 断开所有设备 */
|
||
int i;
|
||
for (i = 0; i < MAX_BLE_CONNECTIONS; i++) {
|
||
if (g_ble.devices[i].is_connected) {
|
||
hci_disconnect(g_ble.hci_socket,
|
||
g_ble.devices[i].connection_handle, 0x13, 1000);
|
||
}
|
||
}
|
||
|
||
close(g_ble.hci_socket);
|
||
close(g_ble.event_pipe[0]);
|
||
close(g_ble.event_pipe[1]);
|
||
pthread_mutex_destroy(&g_ble.mutex);
|
||
|
||
syslog(LOG_INFO, "BLE管理器已清理");
|
||
}
|