diff --git a/get-started/csi_recv_router/main/app_main.c b/get-started/csi_recv_router/main/app_main.c index f7c74ea..7d09077 100644 --- a/get-started/csi_recv_router/main/app_main.c +++ b/get-started/csi_recv_router/main/app_main.c @@ -42,6 +42,10 @@ #include "lwip/sockets.h" #include "ping/ping_sock.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" #include "protocol_examples_common.h" #include "esp_csi_gain_ctrl.h" @@ -95,6 +99,11 @@ static volatile int64_t s_last_motion_time = 0; static uint32_t s_energy_buf[WANDER_WINDOW]; static uint32_t s_energy_idx = 0; +/* BLE scanning */ +#define BLE_SCAN_RESTART_US 30000000LL /* restart scan every 30s to refresh duplicate filter */ +static bool s_ble_enabled = false; +static uint8_t s_ble_own_addr_type; + /* UDP socket for CSI data transmission */ static int s_udp_socket = -1; static struct sockaddr_in s_dest_addr; @@ -122,9 +131,13 @@ static void config_load_nvs(void) if (nvs_get_i32(h, "threshold", &thresh) == ESP_OK && thresh > 0) { s_motion_threshold = (float)thresh / 1000000.0f; } + int8_t ble; + if (nvs_get_i8(h, "ble_scan", &ble) == ESP_OK) { + s_ble_enabled = (ble != 0); + } nvs_close(h); - ESP_LOGI(TAG, "NVS loaded: rate=%d tx_power=%d adaptive=%d threshold=%.6f", - s_send_frequency, s_tx_power_dbm, s_adaptive, s_motion_threshold); + ESP_LOGI(TAG, "NVS loaded: rate=%d tx_power=%d adaptive=%d threshold=%.6f ble=%d", + s_send_frequency, s_tx_power_dbm, s_adaptive, s_motion_threshold, s_ble_enabled); } else { ESP_LOGI(TAG, "NVS: no saved config, using defaults"); } @@ -426,6 +439,115 @@ static esp_err_t wifi_ping_router_start(void) return ESP_OK; } +/* --- BLE scanning --- */ + +static int ble_gap_event_cb(struct ble_gap_event *event, void *arg); + +static void ble_scan_start(void) +{ + struct ble_gap_disc_params disc_params = {0}; + disc_params.passive = 1; + disc_params.filter_duplicates = 1; + disc_params.itvl = 0; + disc_params.window = 0; + disc_params.filter_policy = 0; + disc_params.limited = 0; + + int rc = ble_gap_disc(s_ble_own_addr_type, BLE_HS_FOREVER, &disc_params, + ble_gap_event_cb, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "BLE: scan start failed rc=%d", rc); + } else { + ESP_LOGI(TAG, "BLE: scan started ok"); + } +} + +static int ble_gap_event_cb(struct ble_gap_event *event, void *arg) +{ + switch (event->type) { + case BLE_GAP_EVENT_DISC: { + struct ble_gap_disc_desc *disc = &event->disc; + ESP_LOGI(TAG, "BLE: disc event rssi=%d addr=%02x:%02x:%02x:%02x:%02x:%02x", + disc->rssi, disc->addr.val[5], disc->addr.val[4], disc->addr.val[3], + disc->addr.val[2], disc->addr.val[1], disc->addr.val[0]); + + /* Parse advertisement for device name */ + struct ble_hs_adv_fields fields; + int rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); + + char name[32] = ""; + if (rc == 0 && fields.name != NULL && fields.name_len > 0) { + int nlen = fields.name_len < (int)sizeof(name) - 1 ? fields.name_len : (int)sizeof(name) - 1; + memcpy(name, fields.name, nlen); + name[nlen] = '\0'; + } + + /* Send BLE_DATA via UDP */ + char buf[128]; + int len = snprintf(buf, sizeof(buf), + "BLE_DATA,%02x:%02x:%02x:%02x:%02x:%02x,%d,%s,%s\n", + disc->addr.val[5], disc->addr.val[4], disc->addr.val[3], + disc->addr.val[2], disc->addr.val[1], disc->addr.val[0], + disc->rssi, + disc->addr.type == BLE_ADDR_PUBLIC ? "pub" : "rnd", + name); + + if (s_udp_socket >= 0) { + sendto(s_udp_socket, buf, len, 0, + (struct sockaddr *)&s_dest_addr, sizeof(s_dest_addr)); + } + break; + } + case BLE_GAP_EVENT_DISC_COMPLETE: + if (s_ble_enabled) { + ble_scan_start(); + } + break; + default: + break; + } + return 0; +} + +static void ble_scan_restart_timer_cb(void *arg) +{ + if (s_ble_enabled) { + ble_gap_disc_cancel(); + ble_scan_start(); + } +} + +static void ble_on_sync(void) +{ + int rc = ble_hs_util_ensure_addr(0); + if (rc != 0) { + ESP_LOGE(TAG, "BLE: ensure addr failed rc=%d", rc); + return; + } + rc = ble_hs_id_infer_auto(0, &s_ble_own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "BLE: infer addr type failed rc=%d", rc); + return; + } + ESP_LOGI(TAG, "BLE: stack synced, addr_type=%d", s_ble_own_addr_type); + + if (s_ble_enabled) { + ble_scan_start(); + } +} + +static void ble_on_reset(int reason) +{ + ESP_LOGW(TAG, "BLE: stack reset reason=%d", reason); +} + +static void ble_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE host task started"); + nimble_port_run(); + nimble_port_freertos_deinit(); +} + /* --- Adaptive sampling --- */ static void adaptive_task(void *arg) @@ -577,10 +699,11 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size) } snprintf(reply, reply_size, - "OK STATUS uptime=%s heap=%lu rssi=%d tx_power=%d rate=%d hostname=%s version=%s adaptive=%s motion=%d", + "OK STATUS uptime=%s heap=%lu rssi=%d tx_power=%d rate=%d hostname=%s version=%s adaptive=%s motion=%d ble=%s", uptime_str, (unsigned long)heap, rssi, (int)s_tx_power_dbm, s_send_frequency, CONFIG_CSI_HOSTNAME, app_desc->version, - s_adaptive ? "on" : "off", s_motion_detected ? 1 : 0); + s_adaptive ? "on" : "off", s_motion_detected ? 1 : 0, + s_ble_enabled ? "on" : "off"); return strlen(reply); } @@ -617,6 +740,25 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size) return strlen(reply); } + /* BLE ON/OFF */ + if (strncmp(cmd, "BLE ", 4) == 0) { + const char *arg = cmd + 4; + if (strncmp(arg, "ON", 2) == 0) { + s_ble_enabled = true; + config_save_i8("ble_scan", 1); + ble_scan_start(); + snprintf(reply, reply_size, "OK BLE scanning on"); + } else if (strncmp(arg, "OFF", 3) == 0) { + s_ble_enabled = false; + config_save_i8("ble_scan", 0); + ble_gap_disc_cancel(); + snprintf(reply, reply_size, "OK BLE scanning off"); + } else { + snprintf(reply, reply_size, "ERR BLE ON or OFF"); + } + return strlen(reply); + } + /* ADAPTIVE ON/OFF */ if (strncmp(cmd, "ADAPTIVE ", 9) == 0) { const char *arg = cmd + 9; @@ -769,6 +911,23 @@ void app_main() ESP_ERROR_CHECK(esp_task_wdt_reconfigure(&wdt_cfg)); ESP_LOGI(TAG, "Watchdog configured: 30s timeout"); + /* BLE: Initialize NimBLE stack */ + ESP_ERROR_CHECK(nimble_port_init()); + ble_hs_cfg.reset_cb = ble_on_reset; + ble_hs_cfg.sync_cb = ble_on_sync; + nimble_port_freertos_init(ble_host_task); + + /* BLE: periodic scan restart to refresh duplicate filter */ + const esp_timer_create_args_t ble_timer_args = { + .callback = ble_scan_restart_timer_cb, + .name = "ble_scan", + }; + esp_timer_handle_t ble_timer; + esp_timer_create(&ble_timer_args, &ble_timer); + esp_timer_start_periodic(ble_timer, BLE_SCAN_RESTART_US); + + ESP_LOGI(TAG, "BLE: NimBLE initialized, scan=%s", s_ble_enabled ? "on" : "off"); + s_led_mode = LED_SLOW_BLINK; udp_socket_init(); diff --git a/get-started/csi_recv_router/sdkconfig.defaults b/get-started/csi_recv_router/sdkconfig.defaults index 91ef1e3..0098602 100644 --- a/get-started/csi_recv_router/sdkconfig.defaults +++ b/get-started/csi_recv_router/sdkconfig.defaults @@ -62,3 +62,14 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" # CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP=y + +# +# BLE (NimBLE, scan-only, WiFi coexistence) +# +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_ESP_WIFI_IRAM_OPT=n