feat: Add v0.5 BLE scanning — NimBLE passive scan, BLE_DATA UDP stream

- NimBLE stack init with passive BLE scanning
- BLE ON/OFF command with NVS persistence
- BLE_DATA,<mac>,<rssi>,<pub|rnd>,<name> UDP packets
- 30s periodic scan restart to refresh duplicate filter
- ble= field in STATUS reply
- sdkconfig: enable BT+NimBLE, BLE-only mode, disable Bluedroid
This commit is contained in:
user
2026-02-04 17:40:20 +01:00
parent 8a60547fca
commit 81c4337646
2 changed files with 174 additions and 4 deletions

View File

@@ -42,6 +42,10 @@
#include "lwip/sockets.h" #include "lwip/sockets.h"
#include "ping/ping_sock.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 "protocol_examples_common.h"
#include "esp_csi_gain_ctrl.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_buf[WANDER_WINDOW];
static uint32_t s_energy_idx = 0; 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 */ /* UDP socket for CSI data transmission */
static int s_udp_socket = -1; static int s_udp_socket = -1;
static struct sockaddr_in s_dest_addr; 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) { if (nvs_get_i32(h, "threshold", &thresh) == ESP_OK && thresh > 0) {
s_motion_threshold = (float)thresh / 1000000.0f; 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); nvs_close(h);
ESP_LOGI(TAG, "NVS loaded: rate=%d tx_power=%d adaptive=%d threshold=%.6f", 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_send_frequency, s_tx_power_dbm, s_adaptive, s_motion_threshold, s_ble_enabled);
} else { } else {
ESP_LOGI(TAG, "NVS: no saved config, using defaults"); 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; 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 --- */ /* --- Adaptive sampling --- */
static void adaptive_task(void *arg) 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, 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, uptime_str, (unsigned long)heap, rssi, (int)s_tx_power_dbm,
s_send_frequency, CONFIG_CSI_HOSTNAME, app_desc->version, 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); return strlen(reply);
} }
@@ -617,6 +740,25 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size)
return strlen(reply); 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 */ /* ADAPTIVE ON/OFF */
if (strncmp(cmd, "ADAPTIVE ", 9) == 0) { if (strncmp(cmd, "ADAPTIVE ", 9) == 0) {
const char *arg = cmd + 9; const char *arg = cmd + 9;
@@ -769,6 +911,23 @@ void app_main()
ESP_ERROR_CHECK(esp_task_wdt_reconfigure(&wdt_cfg)); ESP_ERROR_CHECK(esp_task_wdt_reconfigure(&wdt_cfg));
ESP_LOGI(TAG, "Watchdog configured: 30s timeout"); 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; s_led_mode = LED_SLOW_BLINK;
udp_socket_init(); udp_socket_init();

View File

@@ -62,3 +62,14 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
# #
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP=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