From 1aaca3d83c821706cef48c77ac315ce80e2fedbf Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 24 Jun 2026 20:32:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20BLE=20=E5=8F=8C=E5=90=91?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=9B=9E=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/main.c | 16 ++++++++ main/metrics_ble.c | 56 ++++++++++++++++++++++++++++ main/metrics_ble.h | 2 + tools/pc-metrics/push-metrics-ble.py | 12 ++++++ 4 files changed, 86 insertions(+) diff --git a/main/main.c b/main/main.c index 4155f2d..598b163 100644 --- a/main/main.c +++ b/main/main.c @@ -82,6 +82,11 @@ void app_main(void) int frame_counter = 0; uint32_t ble_ui_update_count = 0; + TickType_t last_status_notify_tick = 0; + int latest_battery_percent = -1; + int latest_battery_mv = -1; + int latest_temperature_c10 = -999; + int latest_humidity_percent = -1; while (1) { vTaskDelay(pdMS_TO_TICKS(30)); @@ -101,15 +106,26 @@ void app_main(void) int battery_percent = 0; int battery_mv = 0; if (battery_monitor_take_latest(&battery_percent, &battery_mv)) { + latest_battery_percent = battery_percent; + latest_battery_mv = battery_mv; ui_screen_update_battery(battery_percent, battery_mv); } int temperature_c10 = 0; int humidity_percent = 0; if (temp_humidity_monitor_take_latest(&temperature_c10, &humidity_percent)) { + latest_temperature_c10 = temperature_c10; + latest_humidity_percent = humidity_percent; ui_screen_update_environment(temperature_c10, humidity_percent); } + TickType_t now_tick = xTaskGetTickCount(); + if ((now_tick - last_status_notify_tick) >= pdMS_TO_TICKS(5000)) { + last_status_notify_tick = now_tick; + metrics_ble_notify_status(latest_battery_percent, latest_battery_mv, + latest_temperature_c10, latest_humidity_percent); + } + lv_lock(); lv_timer_handler(); diff --git a/main/metrics_ble.c b/main/metrics_ble.c index bd335c4..ec2d179 100644 --- a/main/metrics_ble.c +++ b/main/metrics_ble.c @@ -1,6 +1,7 @@ #include "metrics_ble.h" #include +#include #include #include "esp_err.h" @@ -25,15 +26,22 @@ static const char *DEVICE_NAME = "RLCD-Metrics"; // Service: 9f4af548-1a14-4fc1-8f5a-4b9f00000001 // Write char: 9f4af548-1a14-4fc1-8f5a-4b9f00000002 +// Status notify char: 9f4af548-1a14-4fc1-8f5a-4b9f00000003 static const ble_uuid128_t metrics_svc_uuid = BLE_UUID128_INIT(0x01, 0x00, 0x00, 0x00, 0x9f, 0x4b, 0x5a, 0x8f, 0xc1, 0x4f, 0x14, 0x1a, 0x48, 0xf5, 0x4a, 0x9f); static const ble_uuid128_t metrics_chr_uuid = BLE_UUID128_INIT(0x02, 0x00, 0x00, 0x00, 0x9f, 0x4b, 0x5a, 0x8f, 0xc1, 0x4f, 0x14, 0x1a, 0x48, 0xf5, 0x4a, 0x9f); +static const ble_uuid128_t status_chr_uuid = + BLE_UUID128_INIT(0x03, 0x00, 0x00, 0x00, 0x9f, 0x4b, 0x5a, 0x8f, + 0xc1, 0x4f, 0x14, 0x1a, 0x48, 0xf5, 0x4a, 0x9f); static uint8_t own_addr_type; static uint16_t metrics_chr_val_handle; +static uint16_t status_chr_val_handle; +static uint16_t s_conn_handle; +static bool s_status_notify_enabled; static QueueHandle_t s_metrics_queue; static uint32_t s_ble_write_count; @@ -115,6 +123,12 @@ static const struct ble_gatt_svc_def metrics_gatt_svcs[] = { .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP, .val_handle = &metrics_chr_val_handle, }, + { + .uuid = &status_chr_uuid.u, + .access_cb = metrics_gatt_access, + .flags = BLE_GATT_CHR_F_NOTIFY, + .val_handle = &status_chr_val_handle, + }, {0}, }, }, @@ -160,8 +174,12 @@ static int metrics_gap_event(struct ble_gap_event *event, void *arg) case BLE_GAP_EVENT_CONNECT: if (event->connect.status == 0) { ESP_LOGI(TAG, "BLE connected"); + s_conn_handle = event->connect.conn_handle; + s_status_notify_enabled = false; metrics_request_2m_phy(event->connect.conn_handle); } else { + s_conn_handle = 0; + s_status_notify_enabled = false; metrics_advertise(); } return 0; @@ -175,9 +193,19 @@ static int metrics_gap_event(struct ble_gap_event *event, void *arg) case BLE_GAP_EVENT_DISCONNECT: ESP_LOGI(TAG, "BLE disconnected"); + s_conn_handle = 0; + s_status_notify_enabled = false; metrics_advertise(); return 0; + case BLE_GAP_EVENT_SUBSCRIBE: + if (event->subscribe.attr_handle == status_chr_val_handle) { + s_status_notify_enabled = event->subscribe.cur_notify; + ESP_LOGI(TAG, "status notify %s", + s_status_notify_enabled ? "enabled" : "disabled"); + } + return 0; + case BLE_GAP_EVENT_ADV_COMPLETE: metrics_advertise(); return 0; @@ -269,3 +297,31 @@ bool metrics_ble_take_latest(ui_metrics_t *metrics) return xQueueReceive(s_metrics_queue, metrics, 0) == pdTRUE; } + +bool metrics_ble_notify_status(int battery_percent, int battery_mv, + int temperature_c10, int humidity_percent) +{ + if (!s_status_notify_enabled || s_conn_handle == 0 || status_chr_val_handle == 0) { + return false; + } + + char line[96]; + int len = snprintf(line, sizeof(line), "STA,%d,%d,%d,%d,%lu", + battery_percent, battery_mv, temperature_c10, humidity_percent, + (unsigned long)s_ble_write_count); + if (len <= 0 || len >= (int)sizeof(line)) { + return false; + } + + struct os_mbuf *om = ble_hs_mbuf_from_flat(line, len); + if (om == NULL) { + return false; + } + + int rc = ble_gatts_notify_custom(s_conn_handle, status_chr_val_handle, om); + if (rc != 0) { + ESP_LOGW(TAG, "status notify failed: %d", rc); + return false; + } + return true; +} diff --git a/main/metrics_ble.h b/main/metrics_ble.h index 866e519..e00d168 100644 --- a/main/metrics_ble.h +++ b/main/metrics_ble.h @@ -10,6 +10,8 @@ extern "C" { void metrics_ble_start(void); bool metrics_ble_take_latest(ui_metrics_t *metrics); +bool metrics_ble_notify_status(int battery_percent, int battery_mv, + int temperature_c10, int humidity_percent); #ifdef __cplusplus } diff --git a/tools/pc-metrics/push-metrics-ble.py b/tools/pc-metrics/push-metrics-ble.py index a968d32..3ede5da 100644 --- a/tools/pc-metrics/push-metrics-ble.py +++ b/tools/pc-metrics/push-metrics-ble.py @@ -22,6 +22,7 @@ from bleak import BleakClient, BleakScanner DEFAULT_DEVICE_NAME = "RLCD-Metrics" DEFAULT_CHAR_UUID = "9f4af548-1a14-4fc1-8f5a-4b9f00000002" +DEFAULT_STATUS_CHAR_UUID = "9f4af548-1a14-4fc1-8f5a-4b9f00000003" MAX_DISK_ROWS = 4 MAX_DISK_TEXT = 124 CODEX_DAILY_BAR_COUNT = 14 @@ -1056,6 +1057,11 @@ async def find_device(name: str, address: str | None, timeout: float): async def push_loop(args) -> int: sampler = MetricsSampler(args.disk) + def status_callback(_sender, data: bytearray): + text = bytes(data).decode("ascii", errors="replace").strip() + if text: + print(f"BLE status: {text}", flush=True) + while True: print(f"Scanning for {args.name}...", flush=True) device = await find_device(args.name, args.address, args.scan_timeout) @@ -1070,6 +1076,11 @@ async def push_loop(args) -> int: try: async with BleakClient(device, timeout=args.connect_timeout) as client: print("BLE connected. Streaming metrics.", flush=True) + try: + await client.start_notify(args.status_char_uuid, status_callback) + print("BLE status notify subscribed.", flush=True) + except Exception as exc: + print(f"BLE status notify unavailable: {exc}", file=sys.stderr, flush=True) while True: line = sampler.sample_line() await client.write_gatt_char(args.char_uuid, line.encode("ascii"), response=True) @@ -1091,6 +1102,7 @@ def parse_args(): parser.add_argument("--name", default=DEFAULT_DEVICE_NAME) parser.add_argument("--address", default=None) parser.add_argument("--char-uuid", default=DEFAULT_CHAR_UUID) + parser.add_argument("--status-char-uuid", default=DEFAULT_STATUS_CHAR_UUID) parser.add_argument("--interval", type=float, default=1.0) parser.add_argument("--scan-timeout", type=float, default=20.0) parser.add_argument("--connect-timeout", type=float, default=20.0)