From 597bfeee28fe8bb391219f2a4febec3b26173f00 Mon Sep 17 00:00:00 2001 From: Peter Siegmund Date: Sat, 30 Aug 2025 01:13:45 +0200 Subject: [PATCH] scan ble advertise packages Signed-off-by: Peter Siegmund --- .github/workflows/esp32_build.yml | 20 +- .../{CMakeLists.txt => CMakeLists_.txt} | 0 .../components/ble-manager/CMakeLists.txt | 10 + .../ble-manager/include/ble_manager.h | 10 + .../components/ble-manager/src/ble_manager.c | 335 ++++++++++++++++++ .../led-manager/Kconfig} | 0 .../led-manager/src/hal_esp32/led_status.cpp | 2 +- .../src/hal_esp32/PersistenceManager.cpp | 16 +- firmware/main/CMakeLists.txt | 2 + firmware/main/idf_component.yml | 1 + firmware/main/main.cpp | 37 +- firmware/sdkconfig.defaults | 7 - firmware/sdkconfig.defaults.esp32c3 | 2 - firmware/sdkconfig.defaults.esp32c6 | 2 + firmware/sdkconfig.defaults.esp32h2 | 2 - firmware/sdkconfig.defaults.esp32p4 | 2 - firmware/sdkconfig.release | 10 + 17 files changed, 419 insertions(+), 39 deletions(-) rename firmware/components/{CMakeLists.txt => CMakeLists_.txt} (100%) create mode 100644 firmware/components/ble-manager/CMakeLists.txt create mode 100644 firmware/components/ble-manager/include/ble_manager.h create mode 100644 firmware/components/ble-manager/src/ble_manager.c rename firmware/{main/Kconfig.projbuild => components/led-manager/Kconfig} (100%) delete mode 100644 firmware/sdkconfig.defaults.esp32c3 create mode 100644 firmware/sdkconfig.defaults.esp32c6 delete mode 100644 firmware/sdkconfig.defaults.esp32h2 delete mode 100644 firmware/sdkconfig.defaults.esp32p4 diff --git a/.github/workflows/esp32_build.yml b/.github/workflows/esp32_build.yml index 574741b..c06f17a 100644 --- a/.github/workflows/esp32_build.yml +++ b/.github/workflows/esp32_build.yml @@ -1,16 +1,20 @@ name: ESP-IDF Build +on: + push: + paths: + - "firmware/**" + - ".github/workflows/esp32_build.yml" + pull_request: + merge_group: + workflow_dispatch: + schedule: + - cron: "0 5 * * 3" + permissions: contents: read pull-requests: write -on: - push: - pull_request: - merge_group: - schedule: - - cron: "0 5 * * 3" - concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -20,7 +24,7 @@ jobs: strategy: matrix: idf_ver: [release-v5.4, release-v5.5, latest] - idf_target: [esp32s3] + idf_target: [esp32c6, esp32s3] runs-on: ubuntu-latest timeout-minutes: 30 diff --git a/firmware/components/CMakeLists.txt b/firmware/components/CMakeLists_.txt similarity index 100% rename from firmware/components/CMakeLists.txt rename to firmware/components/CMakeLists_.txt diff --git a/firmware/components/ble-manager/CMakeLists.txt b/firmware/components/ble-manager/CMakeLists.txt new file mode 100644 index 0000000..8b45e39 --- /dev/null +++ b/firmware/components/ble-manager/CMakeLists.txt @@ -0,0 +1,10 @@ +if (DEFINED ENV{IDF_PATH}) + idf_component_register(SRCS + src/ble_manager.c + INCLUDE_DIRS "include" + PRIV_REQUIRES + bt + nvs_flash + ) + return() +endif () diff --git a/firmware/components/ble-manager/include/ble_manager.h b/firmware/components/ble-manager/include/ble_manager.h new file mode 100644 index 0000000..bdf03d9 --- /dev/null +++ b/firmware/components/ble-manager/include/ble_manager.h @@ -0,0 +1,10 @@ +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + void ble_manager_task(void *pvParameter); +#ifdef __cplusplus +} +#endif diff --git a/firmware/components/ble-manager/src/ble_manager.c b/firmware/components/ble-manager/src/ble_manager.c new file mode 100644 index 0000000..5102074 --- /dev/null +++ b/firmware/components/ble-manager/src/ble_manager.c @@ -0,0 +1,335 @@ +#include "ble_manager.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "services/gap/ble_svc_gap.h" + +static const char *TAG = "ble_manager"; + +// List of allowed manufacturer IDs +static const uint16_t ALLOWED_MANUFACTURERS[] = { + 0xC0DE, // mars3142 +}; +static const size_t NUM_MANUFACTURERS = sizeof(ALLOWED_MANUFACTURERS) / sizeof(uint16_t); + +// Structure to cache device data +typedef struct +{ + ble_addr_t addr; + uint16_t manufacturer_id; + uint8_t manufacturer_data[31]; // Max. length of manufacturer data + uint8_t manufacturer_data_len; + char name[32]; + uint16_t service_uuids_16[10]; // Up to 10 16-bit Service UUIDs + uint8_t service_uuids_16_count; + ble_uuid128_t service_uuids_128[5]; // Up to 5 128-bit Service UUIDs + uint8_t service_uuids_128_count; + bool has_manufacturer; + bool has_name; + int8_t rssi; +} device_info_t; + +static bool is_manufacturer_allowed(uint16_t company_id) +{ + for (size_t i = 0; i < NUM_MANUFACTURERS; i++) + { + if (ALLOWED_MANUFACTURERS[i] == company_id) + { + return true; + } + } + return false; +} + +static int ble_central_gap_event(struct ble_gap_event *event, void *arg); + +/** + * Starts the BLE scan process. + */ +static void start_scan(void) +{ + struct ble_gap_disc_params disc_params = { + .filter_policy = 0, + .limited = 0, + .passive = 0, + .filter_duplicates = 1, + }; + + int rc = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER, &disc_params, ble_central_gap_event, NULL); + if (rc != 0) + { + ESP_LOGE(TAG, "Error starting scan; rc=%d", rc); + } +} + +#define MAX_DEVICES 10 +static device_info_t devices[MAX_DEVICES]; +static int device_count = 0; + +// Helper function to find or create a device entry +static device_info_t *find_or_create_device(const ble_addr_t *addr) +{ + // Search for existing device + for (int i = 0; i < device_count; i++) + { + if (memcmp(&devices[i].addr, addr, sizeof(ble_addr_t)) == 0) + { + return &devices[i]; + } + } + + // Add new device + if (device_count < MAX_DEVICES) + { + memset(&devices[device_count], 0, sizeof(device_info_t)); + memcpy(&devices[device_count].addr, addr, sizeof(ble_addr_t)); + devices[device_count].has_manufacturer = false; + devices[device_count].has_name = false; + strcpy(devices[device_count].name, "Unknown"); + return &devices[device_count++]; + } + + return NULL; +} + +static int ble_central_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_disc_desc *disc; + struct ble_hs_adv_fields fields; + + switch (event->type) + { + case BLE_GAP_EVENT_DISC: { + disc = &event->disc; + + // Find or create device + device_info_t *device = find_or_create_device(&disc->addr); + if (device == NULL) + { + return 0; + } + + // Update RSSI + device->rssi = disc->rssi; + + // Parse advertising data + memset(&fields, 0, sizeof(fields)); + ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); + + // Process manufacturer data + if (fields.mfg_data != NULL && fields.mfg_data_len >= 2) + { + uint16_t company_id = fields.mfg_data[0] | (fields.mfg_data[1] << 8); + device->manufacturer_id = company_id; + device->has_manufacturer = true; + + // Store complete manufacturer data (incl. Company ID) + device->manufacturer_data_len = fields.mfg_data_len; + memcpy(device->manufacturer_data, fields.mfg_data, fields.mfg_data_len); + } + + // Process name + if (fields.name != NULL && fields.name_len > 0) + { + size_t copy_len = fields.name_len < sizeof(device->name) - 1 ? fields.name_len : sizeof(device->name) - 1; + memcpy(device->name, fields.name, copy_len); + device->name[copy_len] = '\0'; + device->has_name = true; + } + + // Process 16-bit Service UUIDs + if (fields.uuids16 != NULL && fields.num_uuids16 > 0) + { + for (int i = 0; i < fields.num_uuids16 && device->service_uuids_16_count < 10; i++) + { + // Check if UUID already exists + bool exists = false; + for (int j = 0; j < device->service_uuids_16_count; j++) + { + if (device->service_uuids_16[j] == fields.uuids16[i].value) + { + exists = true; + break; + } + } + if (!exists) + { + device->service_uuids_16[device->service_uuids_16_count++] = fields.uuids16[i].value; + } + } + } + + // Process 128-bit Service UUIDs + if (fields.uuids128 != NULL && fields.num_uuids128 > 0) + { + for (int i = 0; i < fields.num_uuids128 && device->service_uuids_128_count < 5; i++) + { + // Check if UUID already exists + bool exists = false; + for (int j = 0; j < device->service_uuids_128_count; j++) + { + if (memcmp(&device->service_uuids_128[j], &fields.uuids128[i], sizeof(ble_uuid128_t)) == 0) + { + exists = true; + break; + } + } + if (!exists) + { + memcpy(&device->service_uuids_128[device->service_uuids_128_count++], &fields.uuids128[i], + sizeof(ble_uuid128_t)); + } + } + } + + // Check if we have all data and the device is allowed + if (device->has_manufacturer && is_manufacturer_allowed(device->manufacturer_id)) + { + ESP_LOGI(TAG, "*** Allowed device found ***"); + ESP_LOGI(TAG, " Name: %s", device->name); + ESP_LOGI(TAG, " Address: %02X:%02X:%02X:%02X:%02X:%02X", device->addr.val[5], device->addr.val[4], + device->addr.val[3], device->addr.val[2], device->addr.val[1], device->addr.val[0]); + ESP_LOGI(TAG, " Manufacturer ID: 0x%04X", device->manufacturer_id); + ESP_LOGI(TAG, " RSSI: %d dBm", device->rssi); + + // Print Service UUIDs + if (device->service_uuids_16_count > 0) + { + ESP_LOGI(TAG, " 16-bit Service UUIDs (%d):", device->service_uuids_16_count); + for (int i = 0; i < device->service_uuids_16_count; i++) + { + const char *name = ""; + // Known Service UUIDs + switch (device->service_uuids_16[i]) + { + case 0x180A: + name = " (Device Information)"; + break; + case 0x180F: + name = " (Battery Service)"; + break; + case 0x1801: + name = " (Generic Attribute)"; + break; + case 0x1800: + name = " (Generic Access)"; + break; + case 0x181A: + name = " (Environmental Sensing)"; + break; + default: + if (device->service_uuids_16[i] >= 0xA000) + { + name = " (Custom)"; + } + break; + } + ESP_LOGI(TAG, " - 0x%04X%s", device->service_uuids_16[i], name); + } + } + + if (device->service_uuids_128_count > 0) + { + ESP_LOGI(TAG, " 128-bit Service UUIDs (%d):", device->service_uuids_128_count); + for (int i = 0; i < device->service_uuids_128_count; i++) + { + char uuid_str[37]; // UUID string format + snprintf(uuid_str, sizeof(uuid_str), + "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", + device->service_uuids_128[i].value[15], device->service_uuids_128[i].value[14], + device->service_uuids_128[i].value[13], device->service_uuids_128[i].value[12], + device->service_uuids_128[i].value[11], device->service_uuids_128[i].value[10], + device->service_uuids_128[i].value[9], device->service_uuids_128[i].value[8], + device->service_uuids_128[i].value[7], device->service_uuids_128[i].value[6], + device->service_uuids_128[i].value[5], device->service_uuids_128[i].value[4], + device->service_uuids_128[i].value[3], device->service_uuids_128[i].value[2], + device->service_uuids_128[i].value[1], device->service_uuids_128[i].value[0]); + ESP_LOGI(TAG, " - %s", uuid_str); + } + } + + // Print manufacturer data (without Company ID, i.e., from byte 2) + if (device->manufacturer_data_len > 2) + { + int payload_len = device->manufacturer_data_len - 2; + ESP_LOGI(TAG, " Manufacturer Data (%d bytes):", payload_len); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, &device->manufacturer_data[2], payload_len, ESP_LOG_INFO); + + // Print data byte by byte for better readability + ESP_LOGI(TAG, " Data interpretation:"); + for (int i = 0; i < payload_len; i++) + { + ESP_LOGI(TAG, " - Byte %d: 0x%02X (%d)", i, device->manufacturer_data[i + 2], + device->manufacturer_data[i + 2]); + } + + // Optional: Interpret data as 16-bit values (if the number of bytes is even) + if (payload_len >= 2 && (payload_len % 2 == 0)) + { + ESP_LOGI(TAG, " As 16-bit values:"); + for (int i = 0; i < payload_len; i += 2) + { + uint16_t value = device->manufacturer_data[i + 2] | (device->manufacturer_data[i + 3] << 8); + ESP_LOGI(TAG, " - Word %d: 0x%04X (%d)", i / 2, value, value); + } + } + } + else + { + ESP_LOGI(TAG, " No manufacturer payload data"); + } + } + + return 0; + } + + case BLE_GAP_EVENT_DISC_COMPLETE: { + ESP_LOGI(TAG, "Discovery complete, restarting scan..."); + // Optional: Reset device list + device_count = 0; + start_scan(); + return 0; + } + + default: + break; + } + return 0; +} + +/** + * Callback that is called when the NimBLE stack is synchronized and ready. + */ +static void on_sync(void) +{ + // The stack is ready, we can start the scan. + start_scan(); +} + +static void ble_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + nimble_port_run(); // This blocks until the stack is stopped + nimble_port_freertos_deinit(); +} + +void ble_manager_task(void *pvParameter) +{ + // Initialize and start the NimBLE stack + nimble_port_init(); + + // Host configuration with our sync callback + ble_hs_cfg.sync_cb = on_sync; + + // Configure GAP service for central mode + ble_svc_gap_init(); + + // Start the NimBLE host task + nimble_port_freertos_init(ble_host_task); // Not a separate task, can run in the app task + + vTaskDelete(NULL); +} diff --git a/firmware/main/Kconfig.projbuild b/firmware/components/led-manager/Kconfig similarity index 100% rename from firmware/main/Kconfig.projbuild rename to firmware/components/led-manager/Kconfig diff --git a/firmware/components/led-manager/src/hal_esp32/led_status.cpp b/firmware/components/led-manager/src/hal_esp32/led_status.cpp index 8e0ea29..dcc6e34 100644 --- a/firmware/components/led-manager/src/hal_esp32/led_status.cpp +++ b/firmware/components/led-manager/src/hal_esp32/led_status.cpp @@ -27,7 +27,7 @@ static void led_status_task(void *pvParameters) { ESP_LOGI(TAG, "Led Status Task started."); - while (1) + while (true) { uint64_t now_us = esp_timer_get_time(); diff --git a/firmware/components/persistence-manager/src/hal_esp32/PersistenceManager.cpp b/firmware/components/persistence-manager/src/hal_esp32/PersistenceManager.cpp index f1d9c0b..fca3591 100644 --- a/firmware/components/persistence-manager/src/hal_esp32/PersistenceManager.cpp +++ b/firmware/components/persistence-manager/src/hal_esp32/PersistenceManager.cpp @@ -23,22 +23,8 @@ bool PersistenceManager::Initialize() return true; } - // Initialize NVS - esp_err_t err = nvs_flash_init(); - if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) - { - ESP_ERROR_CHECK(nvs_flash_erase()); - err = nvs_flash_init(); - } - - if (err != ESP_OK) - { - ESP_LOGE(TAG, "Failed to initialize NVS flash: %s", esp_err_to_name(err)); - return false; - } - // Open NVS handle - err = nvs_open(namespace_.c_str(), NVS_READWRITE, &nvs_handle_); + esp_err_t err = nvs_open(namespace_.c_str(), NVS_READWRITE, &nvs_handle_); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(err)); diff --git a/firmware/main/CMakeLists.txt b/firmware/main/CMakeLists.txt index e9b186c..9b036b4 100755 --- a/firmware/main/CMakeLists.txt +++ b/firmware/main/CMakeLists.txt @@ -6,9 +6,11 @@ idf_component_register(SRCS INCLUDE_DIRS "." PRIV_REQUIRES insa + ble-manager led-manager persistence-manager u8g2 + nvs_flash driver esp_timer esp_event diff --git a/firmware/main/idf_component.yml b/firmware/main/idf_component.yml index e8a375e..7d3b332 100755 --- a/firmware/main/idf_component.yml +++ b/firmware/main/idf_component.yml @@ -4,3 +4,4 @@ dependencies: # u8g2_hal: # git: https://github.com/mkfrey/u8g2-hal-esp-idf.git espressif/button: ^4.1.3 + espressif/esp_insights: ^1.2.7 diff --git a/firmware/main/main.cpp b/firmware/main/main.cpp index a16e5d4..d334e89 100644 --- a/firmware/main/main.cpp +++ b/firmware/main/main.cpp @@ -1,17 +1,48 @@ #include "app_task.h" +#include "ble_manager.h" #include "esp_event.h" +#include "esp_insights.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "led_manager.h" #include "led_status.h" +#include "nvs_flash.h" #include "sdkconfig.h" +#define ESP_INSIGHTS_AUTH_KEY \ + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." \ + "eyJ1c2VyIjoiZTYzNTNmOTUtN2I2Ni00M2U0LTgyM2UtOTlkYzAxNTYyN2NmIiwiaXNzIjoiZTMyMmI1OWMtNjNjYy00ZTQwLThlYTItNGU3NzY2" \ + "NTQ1Y2NhIiwic3ViIjoiMjE2YWJhNmYtZmI5Zi00ZTM3LWEzMDMtOTliZmNlODU1NWJiIiwiZXhwIjoyMDcxNDIzNjk0LCJpYXQiOjE3NTYwNjM2" \ + "OTR9.eG2musOILUiWUzE3AwWWx-_vOLeIoUlmL9LMaDrHYC6h_" \ + "YOYT4Fqtvytgv1qAI0jxXQmijoQdpoQrlNYQwJlH1gRpILcvlFdL1YkBjzfKXgo_" \ + "jJaOlmHv2tkd54FAg49DmG4j0BY3xAnhz5y0XBHsXWiFKwpZHWy0q5IuKyVJ3syNzmTg2LwVBVu8gU2EoGikdVKNazRC1BwPLz_" \ + "KNWdW03WVCniun_" \ + "2nVyZI5Y253Nch6MaeBpvrfRXhUI6uWXZuSDa3nrS5MmtElZgQjEyAJSX5lfhRwEc2Qi2LlHc4LHPD0YvO1JhSF4N6Rwf1FrJZ1qU_" \ + "IxNTdTxtzLC0BUcYA" + #ifdef __cplusplus extern "C" { #endif void app_main(void) { + // Initialize NVS + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) + { + ESP_ERROR_CHECK(nvs_flash_erase()); + ESP_ERROR_CHECK(nvs_flash_init()); + } + + esp_insights_config_t config = { + .log_type = ESP_DIAG_LOG_TYPE_ERROR, + .node_id = nullptr, + .auth_key = ESP_INSIGHTS_AUTH_KEY, + .alloc_ext_ram = false, + }; + + esp_insights_init(&config); + led_status_init(CONFIG_STATUS_WLED_PIN); // LED 0: solid red @@ -28,10 +59,12 @@ extern "C" .mode = LED_MODE_BLINK, .color = {.r = 0, .g = 0, .b = 50}, .on_time_ms = 1000, .off_time_ms = 500}; led_status_set_behavior(2, led2_blink_blue); - xTaskCreatePinnedToCore(app_task, "main_loop", 4096, NULL, tskIDLE_PRIORITY + 1, NULL, portNUM_PROCESSORS - 1); - wled_init(); register_handler(); + + xTaskCreatePinnedToCore(app_task, "main_loop", 4096, NULL, tskIDLE_PRIORITY + 1, NULL, portNUM_PROCESSORS - 1); + xTaskCreatePinnedToCore(ble_manager_task, "ble_manager", 4096, NULL, tskIDLE_PRIORITY + 1, NULL, + portNUM_PROCESSORS - 1); } #ifdef __cplusplus } diff --git a/firmware/sdkconfig.defaults b/firmware/sdkconfig.defaults index a9ba1bb..5b88704 100755 --- a/firmware/sdkconfig.defaults +++ b/firmware/sdkconfig.defaults @@ -13,10 +13,3 @@ CONFIG_ESPTOOLPY_FLASHSIZE="4MB" # Partitions CONFIG_PARTITION_TABLE_CUSTOM=y - -# Build type -CONFIG_APP_REPRODUCIBLE_BUILD=y - -# Core dump -CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y -CONFIG_ESP_COREDUMP_CAPTURE_DRAM=y diff --git a/firmware/sdkconfig.defaults.esp32c3 b/firmware/sdkconfig.defaults.esp32c3 deleted file mode 100644 index 86a5bac..0000000 --- a/firmware/sdkconfig.defaults.esp32c3 +++ /dev/null @@ -1,2 +0,0 @@ -# default ESP target -CONFIG_IDF_TARGET="esp32c3" diff --git a/firmware/sdkconfig.defaults.esp32c6 b/firmware/sdkconfig.defaults.esp32c6 new file mode 100644 index 0000000..a7b537a --- /dev/null +++ b/firmware/sdkconfig.defaults.esp32c6 @@ -0,0 +1,2 @@ +# default ESP target +CONFIG_IDF_TARGET="esp32c6" diff --git a/firmware/sdkconfig.defaults.esp32h2 b/firmware/sdkconfig.defaults.esp32h2 deleted file mode 100644 index 04069a9..0000000 --- a/firmware/sdkconfig.defaults.esp32h2 +++ /dev/null @@ -1,2 +0,0 @@ -# default ESP target -CONFIG_IDF_TARGET="esp32h2" diff --git a/firmware/sdkconfig.defaults.esp32p4 b/firmware/sdkconfig.defaults.esp32p4 deleted file mode 100644 index 091f4b8..0000000 --- a/firmware/sdkconfig.defaults.esp32p4 +++ /dev/null @@ -1,2 +0,0 @@ -# default ESP target -CONFIG_IDF_TARGET="esp32p4" diff --git a/firmware/sdkconfig.release b/firmware/sdkconfig.release index e69de29..d02ef38 100644 --- a/firmware/sdkconfig.release +++ b/firmware/sdkconfig.release @@ -0,0 +1,10 @@ +# Core dump +CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y +CONFIG_ESP_COREDUMP_CAPTURE_DRAM=y + +# Build type +CONFIG_APP_REPRODUCIBLE_BUILD=y + +# Compiler options +CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y