feat(gml670-v2): 添加 BLE NUS 传输层

BLE 广播/连接管理、NUS 上下行通信、下行参数解析
(Resistance/Spotter/Heartbeat),3 次重试发送机制。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-30 17:39:22 +08:00
parent 97f0d65783
commit 1ad7a9d4ad
2 changed files with 206 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
/**
* @brief 初始化 BLE 协议栈、NUS 服务和内部状态。
* @return 0 成功,负 errno 失败。
*/
int ble_transport_init(void);
/**
* @brief 启动 BLE 广播。
*/
void ble_transport_adv_start(void);
/**
* @brief 通过 NUS 发送二进制帧,自动重试最多 3 次。未连接或通知未开启时静默丢弃。
* @param data 待发送字节缓冲区。
* @param len 字节数。
*/
void ble_transport_send(const uint8_t *data, uint16_t len);
/**
* @brief 检查是否已连接且通知已使能。
*/
bool ble_transport_is_ready(void);
/* --- 下行参数 getter --- */
float ble_transport_get_resistance_K(void);
float ble_transport_get_resistance_B(void);
float ble_transport_get_resistance_Tau(void);
float ble_transport_get_spotter_threshold(void);
bool ble_transport_get_spotter_enabled(void);
int64_t ble_transport_get_last_heartbeat_ms(void);
+172
View File
@@ -0,0 +1,172 @@
#include "ble_transport.h"
#include "protocol.h"
#include <bluetooth/services/nus.h>
#include <string.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(ble_transport, LOG_LEVEL_INF);
#define BT_UUID_NUS_VAL BT_UUID_128_ENCODE(0x6e400001, 0xb5a3, 0xf393, 0xe0a9, 0xe50e24dcca9e)
/* --- 广播数据 --- */
/* 广播包只放 NUS UUID,让中央设备按服务快速筛到本设备 */
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_NUS_VAL),
};
/* 扫描响应单独带设备名 */
static const struct bt_data sd[] = {
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};
/* --- 连接状态 --- */
static struct bt_conn *current_conn;
static volatile bool nus_notification_enabled;
static struct k_work_delayable adv_work;
/* --- 下行参数 --- */
static float resistance_K;
static float resistance_B;
static float resistance_Tau;
static float spotter_threshold;
static bool spotter_enabled;
static int64_t last_heartbeat_ms;
void ble_transport_adv_start(void) {
bt_le_adv_start(BT_LE_ADV_CONN_FAST_2, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
}
static void adv_work_handler(struct k_work *work) {
ble_transport_adv_start();
}
static void connected(struct bt_conn *conn, uint8_t err) {
if (err) return;
current_conn = bt_conn_ref(conn);
k_work_cancel_delayable(&adv_work);
nus_notification_enabled = false;
/* 收紧连接参数,降低数据发送抖动 */
struct bt_le_conn_param param = { .interval_min = 6, .interval_max = 12, .latency = 0, .timeout = 400 };
bt_conn_le_param_update(conn, &param);
}
static void disconnected(struct bt_conn *conn, uint8_t reason) {
if (current_conn) {
bt_conn_unref(current_conn);
current_conn = NULL;
}
nus_notification_enabled = false;
/* 延迟 1 秒再重启广播,给协议栈留状态回收时间 */
k_work_schedule(&adv_work, K_MSEC(1000));
}
static void nus_send_enabled(enum bt_nus_send_status status) {
nus_notification_enabled = (status == BT_NUS_SEND_STATUS_ENABLED);
}
static void nus_received_cb(struct bt_conn *conn, const uint8_t *data, uint16_t len) {
if (len < 3 || data[0] != PROTO_SYNC0 || data[1] != PROTO_SYNC1) return;
switch (data[2]) {
case PROTO_TYPE_RESISTANCE:
if (len >= sizeof(struct resistance_frame_t)) {
union resistance_pkt_t pkt;
memcpy(pkt.bytes, data, sizeof(struct resistance_frame_t));
resistance_K = pkt.frame.K;
resistance_B = pkt.frame.B;
resistance_Tau = pkt.frame.Tau;
LOG_INF(
"RX Resistance: K=%.1f B=%.1f Tau=%.2f", (double)resistance_K, (double)resistance_B,
(double)resistance_Tau);
}
break;
case PROTO_TYPE_SPOTTER:
if (len >= sizeof(struct spotter_frame_t)) {
union spotter_pkt_t pkt;
memcpy(pkt.bytes, data, sizeof(struct spotter_frame_t));
spotter_threshold = pkt.frame.threshold;
spotter_enabled = pkt.frame.enable != 0;
LOG_INF("RX Spotter: threshold=%.1f enable=%d", (double)spotter_threshold, spotter_enabled);
}
break;
case PROTO_TYPE_HEARTBEAT:
if (len >= sizeof(struct heartbeat_frame_t)) {
last_heartbeat_ms = k_uptime_get();
LOG_DBG("RX Heartbeat");
}
break;
default:
break;
}
}
static struct bt_nus_cb nus_cb = {
.send_enabled = nus_send_enabled,
.received = nus_received_cb,
};
BT_CONN_CB_DEFINE(conn_callbacks) = { .connected = connected, .disconnected = disconnected };
int ble_transport_init(void) {
k_work_init_delayable(&adv_work, adv_work_handler);
int err = bt_enable(NULL);
if (err) {
LOG_ERR("Bluetooth init failed (err %d)", err);
return err;
}
err = bt_nus_init(&nus_cb);
if (err) {
LOG_ERR("NUS init failed (err %d)", err);
return err;
}
LOG_INF("BLE transport initialized");
return 0;
}
void ble_transport_send(const uint8_t *data, uint16_t len) {
if (!nus_notification_enabled || !current_conn) return;
/* NUS 偶发忙时短间隔重试减少丢包 */
for (int i = 0; i < 3; i++) {
if (bt_nus_send(current_conn, data, len) == 0) return;
k_usleep(500);
}
}
bool ble_transport_is_ready(void) {
return nus_notification_enabled && (current_conn != NULL);
}
float ble_transport_get_resistance_K(void) {
return resistance_K;
}
float ble_transport_get_resistance_B(void) {
return resistance_B;
}
float ble_transport_get_resistance_Tau(void) {
return resistance_Tau;
}
float ble_transport_get_spotter_threshold(void) {
return spotter_threshold;
}
bool ble_transport_get_spotter_enabled(void) {
return spotter_enabled;
}
int64_t ble_transport_get_last_heartbeat_ms(void) {
return last_heartbeat_ms;
}