diff --git a/inc/ble_transport.h b/inc/ble_transport.h new file mode 100644 index 0000000..4ba1450 --- /dev/null +++ b/inc/ble_transport.h @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +/** + * @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); diff --git a/src/ble_transport.c b/src/ble_transport.c new file mode 100644 index 0000000..c425ca5 --- /dev/null +++ b/src/ble_transport.c @@ -0,0 +1,172 @@ +#include "ble_transport.h" +#include "protocol.h" + +#include +#include +#include +#include +#include +#include +#include + +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, ¶m); +} + +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; +}