From dba60df7f9ef4ab38c726f8e6e2968b2dff1ab3d Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Sat, 14 Jun 2025 17:26:36 +0200 Subject: [PATCH 1/6] add BLE support with BleStream class --- main/CMakeLists.txt | 2 +- main/main.cpp | 11 + main/resources/CMakeLists.txt | 2 +- main/util/bleStream.h | 550 ++++++++++++++++++++++++++++++++++ 4 files changed, 563 insertions(+), 2 deletions(-) create mode 100644 main/util/bleStream.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 036aff2..62d5b1c 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -6,7 +6,7 @@ idf_component_register( INCLUDE_DIRS "" REQUIRES jac-dcore jac-machine jac-link driver pthread spiffs vfs fatfs - SmartLeds esp_timer Esp32-RBGridUI + SmartLeds esp_timer Esp32-RBGridUI bt ) target_compile_definitions(${COMPONENT_LIB} PRIVATE JAC_ESP32_VERSION="${JAC_ESP32_VERSION}") diff --git a/main/main.cpp b/main/main.cpp index a73b6d6..0cb037a 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -34,6 +34,7 @@ #include "util/uartStream.h" #include "util/tcpStream.h" +#include "util/bleStream.h" #include "resources/resources.h" @@ -124,6 +125,7 @@ jac::Device device( using Mux_t = jac::Mux; std::unique_ptr muxUart; std::unique_ptr muxTcp; +std::unique_ptr muxBle; #if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) std::unique_ptr muxJtag; @@ -214,6 +216,15 @@ int main() { muxTcp->bindRx(std::make_unique(std::move(handleTcp))); } + // initialize BLE stream + auto bleStream = std::make_unique("ESP32_JAC_BLE"); + bleStream->start(); + + muxBle = std::make_unique(std::move(bleStream)); + muxBle->setErrorHandler(reportMuxError); + auto handleBle = device.router().subscribeTx(4, *muxBle); + muxBle->bindRx(std::make_unique(std::move(handleBle))); + device.onConfigureMachine([&](Machine &machine) { device.machineIO().in->clear(); diff --git a/main/resources/CMakeLists.txt b/main/resources/CMakeLists.txt index 437a85f..99ef5a0 100644 --- a/main/resources/CMakeLists.txt +++ b/main/resources/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.10) set(ts_examples_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../ts-examples) set(ts_examples_tgz ts_examples.tar.gz) diff --git a/main/util/bleStream.h b/main/util/bleStream.h new file mode 100644 index 0000000..10fc535 --- /dev/null +++ b/main/util/bleStream.h @@ -0,0 +1,550 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_bt_defs.h" +#include "esp_gatt_common_api.h" +#include "nvs_flash.h" +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" + +// BLE Debug Logging Control +// Uncomment the next line to enable detailed BLE logging for debugging +// #define BLE_STREAM_DEBUG_LOGS + +#ifdef BLE_STREAM_DEBUG_LOGS + #define BLE_LOG_DEBUG(msg) jac::Logger::debug(msg) + #define BLE_LOG_ERROR(msg) jac::Logger::error(msg) + #define BLE_LOG_INFO(msg) jac::Logger::debug(msg) // Use debug for info when enabled +#else + #define BLE_LOG_DEBUG(msg) ((void)0) + #define BLE_LOG_ERROR(msg) jac::Logger::error(msg) // Keep errors always visible + #define BLE_LOG_INFO(msg) ((void)0) // Disable info messages when debugging is off +#endif + + +class BleStream : public jac::Duplex { + std::function _onData; + std::deque _buffer; + std::mutex _bufferMutex; + std::atomic _isConnected = false; + std::atomic _notifyEnabled = false; + std::atomic _isInitialized = false; + + // BLE configuration + static constexpr uint16_t GATTS_SERVICE_UUID = 0x00FF; + static constexpr uint16_t GATTS_CHAR_UUID = 0xFF01; + static constexpr uint16_t GATTS_DESCR_UUID = 0x2902; // Client Characteristic Configuration + static constexpr uint16_t GATTS_NUM_HANDLE = 4; + static constexpr const char* DEVICE_NAME = "ESP32_JAC_BLE"; + + // BLE handles + static inline esp_gatt_if_t gatts_if = ESP_GATT_IF_NONE; + static inline uint16_t service_handle = 0; + static inline uint16_t char_handle = 0; + static inline uint16_t descr_handle = 0; + static inline uint16_t conn_id = 0; + + // Global instance pointer for callbacks + static inline BleStream* instance = nullptr; + + // BLE advertising data + static esp_ble_adv_data_t adv_data; + static esp_ble_adv_params_t adv_params; + +public: + BleStream(const std::string& deviceName = "BLE_STREAM") { + if (instance != nullptr) { + throw std::runtime_error("Only one BleStream instance is allowed"); + } + instance = this; + // Note: deviceName could be used to customize the device name if needed + } + + BleStream(BleStream&&) = delete; + BleStream(const BleStream&) = delete; + BleStream& operator=(BleStream&&) = delete; + BleStream& operator=(const BleStream&) = delete; + + void start() { + if (_isInitialized) { + BLE_LOG_DEBUG("BLE Stream already initialized"); + return; + } + + BLE_LOG_INFO("Starting BLE Stream initialization..."); + esp_err_t ret; + + // Note: NVS is already initialized in main.cpp, so we skip it here + // Release Classic BT memory (only if not already released) + static bool bt_mem_released = false; + if (!bt_mem_released) { + BLE_LOG_DEBUG("Releasing Classic BT memory..."); + ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); + if (ret == ESP_OK) { + bt_mem_released = true; + BLE_LOG_DEBUG("Classic BT memory released successfully"); + } else { + BLE_LOG_ERROR("Failed to release Classic BT memory: " + std::string(esp_err_to_name(ret))); + } + } else { + BLE_LOG_DEBUG("Classic BT memory already released"); + } + + // Initialize BT controller + BLE_LOG_DEBUG("Initializing BT controller..."); + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + BLE_LOG_ERROR("BLE controller init failed: " + std::string(esp_err_to_name(ret))); + return; + } + BLE_LOG_DEBUG("BT controller initialized successfully"); + + BLE_LOG_DEBUG("Enabling BLE mode..."); + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + BLE_LOG_ERROR("BLE controller enable failed: " + std::string(esp_err_to_name(ret))); + return; + } + BLE_LOG_DEBUG("BLE mode enabled successfully"); + + // Initialize Bluedroid + BLE_LOG_DEBUG("Initializing Bluedroid stack..."); + ret = esp_bluedroid_init(); + if (ret) { + BLE_LOG_ERROR("Bluedroid init failed: " + std::string(esp_err_to_name(ret))); + return; + } + BLE_LOG_DEBUG("Bluedroid stack initialized successfully"); + + BLE_LOG_DEBUG("Enabling Bluedroid stack..."); + ret = esp_bluedroid_enable(); + if (ret) { + BLE_LOG_ERROR("Bluedroid enable failed: " + std::string(esp_err_to_name(ret))); + return; + } + BLE_LOG_DEBUG("Bluedroid stack enabled successfully"); + + // Register callbacks + BLE_LOG_DEBUG("Registering GATTS callback..."); + ret = esp_ble_gatts_register_callback(gatts_event_handler); + if (ret) { + BLE_LOG_ERROR("GATTS register callback failed: " + std::string(esp_err_to_name(ret))); + return; + } + BLE_LOG_DEBUG("GATTS callback registered successfully"); + + BLE_LOG_DEBUG("Registering GAP callback..."); + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret) { + BLE_LOG_ERROR("GAP register callback failed: " + std::string(esp_err_to_name(ret))); + return; + } + BLE_LOG_DEBUG("GAP callback registered successfully"); + + // Register GATT application + BLE_LOG_DEBUG("Registering GATT application..."); + ret = esp_ble_gatts_app_register(0); + if (ret) { + BLE_LOG_ERROR("GATTS app register failed: " + std::string(esp_err_to_name(ret))); + return; + } + BLE_LOG_DEBUG("GATT application registration initiated"); + + // Set MTU + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + if (local_mtu_ret) { + BLE_LOG_ERROR("Set local MTU failed: " + std::string(esp_err_to_name(local_mtu_ret))); + } + + _isInitialized = true; + BLE_LOG_INFO("BLE Stream initialized successfully"); + } + + bool put(uint8_t c) override { + return write(std::span(&c, 1)) == 1; + } + + size_t write(std::span data) override { + if (!_isConnected || !_notifyEnabled) { + return data.size(); // Pretend write succeeded + } + + // BLE has MTU limitations, so we may need to split large data + size_t written = 0; + const size_t maxChunkSize = 500; // Conservative MTU size + + while (written < data.size()) { + size_t chunkSize = std::min(maxChunkSize, data.size() - written); + + esp_err_t ret = esp_ble_gatts_send_indicate( + gatts_if, + conn_id, + char_handle, + chunkSize, + const_cast(data.data() + written), + false // notification, not indication + ); + + if (ret != ESP_OK) { + BLE_LOG_ERROR("BLE notification failed: " + std::string(esp_err_to_name(ret))); + break; + } + + written += chunkSize; + + // Small delay to avoid overwhelming the BLE stack + if (written < data.size()) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + } + + return written; + } + + int get() override { + std::lock_guard lock(_bufferMutex); + if (_buffer.empty()) { + return -1; + } + uint8_t c = _buffer.front(); + _buffer.pop_front(); + return c; + } + + size_t read(std::span data) override { + std::lock_guard lock(_bufferMutex); + size_t len = std::min(data.size(), _buffer.size()); + std::copy_n(_buffer.begin(), len, data.begin()); + _buffer.erase(_buffer.begin(), _buffer.begin() + len); + return len; + } + + bool flush() override { + return true; // BLE notifications are sent immediately + } + + void onData(std::function callback) override { + _onData = callback; + } + + ~BleStream() override { + if (_isInitialized) { + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); + } + if (instance == this) { + instance = nullptr; + } + } + +private: + // Add received data to buffer and trigger callback + void addReceivedData(const uint8_t* data, size_t len) { + { + std::lock_guard lock(_bufferMutex); + _buffer.insert(_buffer.end(), data, data + len); + } + + if (_onData) { + _onData(); + } + } + + // GAP event handler + static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + BLE_LOG_DEBUG("GAP Event: " + std::to_string(event)); + + switch (event) { + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + BLE_LOG_DEBUG("Advertisement data set complete, starting advertising..."); + esp_ble_gap_start_advertising(&adv_params); + break; + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: + if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + BLE_LOG_ERROR("Advertising start failed with status: " + std::to_string(param->adv_start_cmpl.status)); + } else { + BLE_LOG_INFO("BLE advertising started successfully! Device should be discoverable now."); + } + break; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { + BLE_LOG_ERROR("Advertising stop failed"); + } else { + BLE_LOG_DEBUG("BLE advertising stopped"); + } + break; + default: + BLE_LOG_DEBUG("Unhandled GAP event: " + std::to_string(event)); + break; + } + } + + // GATTS event handler + static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if_param, esp_ble_gatts_cb_param_t *param) { + if (!instance) return; + + BLE_LOG_DEBUG("GATTS Event: " + std::to_string(event)); + + switch (event) { + case ESP_GATTS_REG_EVT: + { + BLE_LOG_DEBUG("GATTS register event - Starting BLE service setup"); + gatts_if = gatts_if_param; + + BLE_LOG_DEBUG("Setting device name to: " + std::string(DEVICE_NAME)); + esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(DEVICE_NAME); + if (set_dev_name_ret) { + BLE_LOG_ERROR("Set device name failed: " + std::string(esp_err_to_name(set_dev_name_ret))); + } else { + BLE_LOG_DEBUG("Device name set successfully"); + } + + // Configure advertising data + esp_ble_adv_data_t adv_data = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = true, + .min_interval = 0x0006, + .max_interval = 0x0010, + .appearance = 0x00, + .manufacturer_len = 0, + .p_manufacturer_data = nullptr, + .service_data_len = 0, + .p_service_data = nullptr, + .service_uuid_len = 0, + .p_service_uuid = nullptr, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), + }; + + BLE_LOG_DEBUG("Configuring advertisement data..."); + esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data); + if (ret) { + BLE_LOG_ERROR("Config adv data failed: " + std::string(esp_err_to_name(ret))); + } else { + BLE_LOG_DEBUG("Advertisement data configured successfully"); + } + + // Create service + BLE_LOG_DEBUG("Creating GATT service with UUID: 0x" + std::to_string(GATTS_SERVICE_UUID)); + esp_gatt_srvc_id_t service_id; + service_id.is_primary = true; + service_id.id.inst_id = 0x00; + service_id.id.uuid.len = ESP_UUID_LEN_16; + service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID; + esp_err_t create_ret = esp_ble_gatts_create_service(gatts_if, &service_id, GATTS_NUM_HANDLE); + if (create_ret) { + BLE_LOG_ERROR("Create service failed: " + std::string(esp_err_to_name(create_ret))); + } else { + BLE_LOG_DEBUG("Service creation initiated"); + } + break; + } + + case ESP_GATTS_CREATE_EVT: + { + BLE_LOG_DEBUG("Service created with handle: " + std::to_string(param->create.service_handle)); + service_handle = param->create.service_handle; + + BLE_LOG_DEBUG("Starting service..."); + esp_err_t start_ret = esp_ble_gatts_start_service(service_handle); + if (start_ret) { + BLE_LOG_ERROR("Start service failed: " + std::string(esp_err_to_name(start_ret))); + } else { + BLE_LOG_DEBUG("Service start initiated"); + } + + // Add characteristic + BLE_LOG_DEBUG("Adding characteristic with UUID: 0x" + std::to_string(GATTS_CHAR_UUID)); + esp_bt_uuid_t char_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = GATTS_CHAR_UUID} + }; + + esp_gatt_char_prop_t char_property = ESP_GATT_CHAR_PROP_BIT_READ | + ESP_GATT_CHAR_PROP_BIT_WRITE | + ESP_GATT_CHAR_PROP_BIT_NOTIFY; + + esp_err_t add_char_ret = esp_ble_gatts_add_char(service_handle, &char_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + char_property, nullptr, nullptr); + if (add_char_ret) { + BLE_LOG_ERROR("Add characteristic failed: " + std::string(esp_err_to_name(add_char_ret))); + } else { + BLE_LOG_DEBUG("Characteristic addition initiated"); + } + break; + } + + case ESP_GATTS_ADD_CHAR_EVT: + { + BLE_LOG_DEBUG("Characteristic added with handle: " + std::to_string(param->add_char.attr_handle)); + char_handle = param->add_char.attr_handle; + + // Add descriptor for notifications + esp_bt_uuid_t descr_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = GATTS_DESCR_UUID} + }; + + BLE_LOG_DEBUG("Adding descriptor for notifications..."); + esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(service_handle, &descr_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + nullptr, nullptr); + if (add_descr_ret) { + BLE_LOG_ERROR("Add descriptor failed: " + std::string(esp_err_to_name(add_descr_ret))); + } else { + BLE_LOG_DEBUG("Descriptor addition initiated"); + } + break; + } + + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + { + BLE_LOG_DEBUG("Descriptor added with handle: " + std::to_string(param->add_char_descr.attr_handle)); + descr_handle = param->add_char_descr.attr_handle; + // Now that everything is set up, start advertising + BLE_LOG_DEBUG("All services set up, starting BLE advertising..."); + esp_err_t adv_start_ret = esp_ble_gap_start_advertising(&adv_params); + if (adv_start_ret) { + BLE_LOG_ERROR("Failed to start advertising: " + std::string(esp_err_to_name(adv_start_ret))); + } else { + BLE_LOG_DEBUG("Advertising start command sent successfully"); + } + break; + } + + case ESP_GATTS_START_EVT: + BLE_LOG_DEBUG("Service started"); + break; + + case ESP_GATTS_CONNECT_EVT: + { + BLE_LOG_INFO("BLE client connected"); + conn_id = param->connect.conn_id; + instance->_isConnected = true; + + // Update connection parameters + esp_ble_conn_update_params_t conn_params = {}; + memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + conn_params.latency = 0; + conn_params.max_int = 0x20; + conn_params.min_int = 0x10; + conn_params.timeout = 400; + esp_ble_gap_update_conn_params(&conn_params); + break; + } + + case ESP_GATTS_DISCONNECT_EVT: + BLE_LOG_INFO("BLE client disconnected"); + instance->_isConnected = false; + instance->_notifyEnabled = false; + esp_ble_gap_start_advertising(&adv_params); + break; + + case ESP_GATTS_WRITE_EVT: + { + if (param->write.handle == descr_handle && param->write.len == 2) { + // Client Characteristic Configuration descriptor written + uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0]; + if (descr_value == 0x0001) { + BLE_LOG_INFO("BLE notifications enabled"); + instance->_notifyEnabled = true; + } else { + BLE_LOG_DEBUG("BLE notifications disabled"); + instance->_notifyEnabled = false; + } + } else if (param->write.handle == char_handle) { + // Data written to characteristic + BLE_LOG_DEBUG("Received " + std::to_string(param->write.len) + " bytes via BLE"); + instance->addReceivedData(param->write.value, param->write.len); + } + + if (param->write.need_rsp) { + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, ESP_GATT_OK, nullptr); + } + break; + } + + case ESP_GATTS_READ_EVT: + { + BLE_LOG_DEBUG("Read request for handle: " + std::to_string(param->read.handle)); + + // Respond to read requests with empty data + esp_gatt_rsp_t rsp = {}; + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 0; + rsp.attr_value.value[0] = 0x00; // Set at least one byte + + esp_err_t rsp_err = esp_ble_gatts_send_response(gatts_if, param->read.conn_id, + param->read.trans_id, ESP_GATT_OK, &rsp); + if (rsp_err != ESP_OK) { + BLE_LOG_ERROR("Failed to send read response: " + std::string(esp_err_to_name(rsp_err))); + } + break; + } + + // Handle other events without error + case ESP_GATTS_EXEC_WRITE_EVT: + BLE_LOG_DEBUG("GATTS Execute Write Event"); + break; + case ESP_GATTS_MTU_EVT: + BLE_LOG_DEBUG("MTU updated to: " + std::to_string(param->mtu.mtu)); + break; + case ESP_GATTS_CONF_EVT: + // Confirmation received for notification/indication + break; + case ESP_GATTS_UNREG_EVT: + case ESP_GATTS_ADD_INCL_SRVC_EVT: + case ESP_GATTS_DELETE_EVT: + case ESP_GATTS_STOP_EVT: + case ESP_GATTS_OPEN_EVT: + case ESP_GATTS_CANCEL_OPEN_EVT: + case ESP_GATTS_CLOSE_EVT: + case ESP_GATTS_LISTEN_EVT: + case ESP_GATTS_CONGEST_EVT: + case ESP_GATTS_RESPONSE_EVT: + case ESP_GATTS_CREAT_ATTR_TAB_EVT: + case ESP_GATTS_SET_ATTR_VAL_EVT: + case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: + default: + break; + } + } +}; + +// Static member initialization +esp_ble_adv_data_t BleStream::adv_data = {}; +esp_ble_adv_params_t BleStream::adv_params = { + .adv_int_min = 0x20, + .adv_int_max = 0x40, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .peer_addr = {}, + .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; From ee6e160c359927dd6b4bee49d5dd426caaad8dcf Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Sat, 14 Jun 2025 17:38:48 +0200 Subject: [PATCH 2/6] refactor BleStream class: improve code structure, enhance device name generation, and clean up BLE resource management --- main/util/bleStream.h | 126 +++++++++++++++++++++++++++++++----------- 1 file changed, 93 insertions(+), 33 deletions(-) diff --git a/main/util/bleStream.h b/main/util/bleStream.h index 10fc535..3cfcdaf 100644 --- a/main/util/bleStream.h +++ b/main/util/bleStream.h @@ -4,12 +4,12 @@ #include #include +#include #include #include #include #include #include -#include #include #include "freertos/FreeRTOS.h" @@ -20,12 +20,8 @@ #include "esp_gatts_api.h" #include "esp_bt_defs.h" #include "esp_gatt_common_api.h" -#include "nvs_flash.h" -#include "esp_gap_ble_api.h" -#include "esp_gatts_api.h" -#include "esp_bt_defs.h" -#include "esp_bt_main.h" -#include "esp_gatt_common_api.h" +#include "esp_system.h" +#include "esp_mac.h" // BLE Debug Logging Control // Uncomment the next line to enable detailed BLE logging for debugging @@ -41,50 +37,79 @@ #define BLE_LOG_INFO(msg) ((void)0) // Disable info messages when debugging is off #endif +/** + * @brief BLE Stream implementation for ESP32 + * + * Provides a BLE GATT server that can be used as a duplex stream for communication. + * Integrates with the Jaculus-esp32 project as an additional transport method. + */ + class BleStream : public jac::Duplex { +private: + // Member variables std::function _onData; std::deque _buffer; std::mutex _bufferMutex; - std::atomic _isConnected = false; - std::atomic _notifyEnabled = false; - std::atomic _isInitialized = false; + std::atomic _isConnected{false}; + std::atomic _notifyEnabled{false}; + std::atomic _isInitialized{false}; - // BLE configuration + // BLE configuration constants static constexpr uint16_t GATTS_SERVICE_UUID = 0x00FF; static constexpr uint16_t GATTS_CHAR_UUID = 0xFF01; static constexpr uint16_t GATTS_DESCR_UUID = 0x2902; // Client Characteristic Configuration static constexpr uint16_t GATTS_NUM_HANDLE = 4; - static constexpr const char* DEVICE_NAME = "ESP32_JAC_BLE"; + static constexpr const char* DEVICE_NAME_PREFIX = "ESP32_JAC_BLE"; + static constexpr size_t MAX_CHUNK_SIZE = 500; // Conservative MTU size - // BLE handles + // BLE handles - shared across all instances static inline esp_gatt_if_t gatts_if = ESP_GATT_IF_NONE; static inline uint16_t service_handle = 0; static inline uint16_t char_handle = 0; static inline uint16_t descr_handle = 0; static inline uint16_t conn_id = 0; - // Global instance pointer for callbacks + // Global instance pointer for callbacks (singleton pattern) static inline BleStream* instance = nullptr; - // BLE advertising data + // BLE advertising configuration static esp_ble_adv_data_t adv_data; static esp_ble_adv_params_t adv_params; public: - BleStream(const std::string& deviceName = "BLE_STREAM") { + /** + * @brief Constructor + * @param deviceName Device name for BLE advertising (currently unused, uses DEVICE_NAME constant) + * @throws std::runtime_error if another instance already exists (singleton pattern) + */ + explicit BleStream(const std::string& deviceName = "BLE_STREAM") { if (instance != nullptr) { throw std::runtime_error("Only one BleStream instance is allowed"); } instance = this; - // Note: deviceName could be used to customize the device name if needed } - BleStream(BleStream&&) = delete; + // Disable copy and move operations BleStream(const BleStream&) = delete; - BleStream& operator=(BleStream&&) = delete; + BleStream(BleStream&&) = delete; BleStream& operator=(const BleStream&) = delete; + BleStream& operator=(BleStream&&) = delete; + + /** + * @brief Destructor - cleans up BLE resources + */ + ~BleStream() override { + cleanup(); + if (instance == this) { + instance = nullptr; + } + } + /** + * @brief Initialize and start the BLE stack + * @note This method is idempotent - calling it multiple times is safe + */ void start() { if (_isInitialized) { BLE_LOG_DEBUG("BLE Stream already initialized"); @@ -181,21 +206,20 @@ class BleStream : public jac::Duplex { BLE_LOG_INFO("BLE Stream initialized successfully"); } + // Duplex interface implementation bool put(uint8_t c) override { - return write(std::span(&c, 1)) == 1; + std::array arr{c}; + return write(std::span(arr)) == 1; } size_t write(std::span data) override { if (!_isConnected || !_notifyEnabled) { - return data.size(); // Pretend write succeeded + return data.size(); // Pretend write succeeded when not connected } - // BLE has MTU limitations, so we may need to split large data size_t written = 0; - const size_t maxChunkSize = 500; // Conservative MTU size - while (written < data.size()) { - size_t chunkSize = std::min(maxChunkSize, data.size() - written); + size_t chunkSize = std::min(MAX_CHUNK_SIZE, data.size() - written); esp_err_t ret = esp_ble_gatts_send_indicate( gatts_if, @@ -248,20 +272,39 @@ class BleStream : public jac::Duplex { _onData = callback; } - ~BleStream() override { +private: + /** + * @brief Clean up BLE resources + */ + void cleanup() { if (_isInitialized) { esp_bluedroid_disable(); esp_bluedroid_deinit(); esp_bt_controller_disable(); esp_bt_controller_deinit(); - } - if (instance == this) { - instance = nullptr; + _isInitialized = false; } } -private: - // Add received data to buffer and trigger callback + /** + * @brief Initialize the BT controller + * @return true on success, false on failure + */ + bool initBtController(); + + /** + * @brief Initialize the Bluedroid stack + * @return true on success, false on failure + */ + bool initBluedroid(); + + /** + * @brief Register BLE callbacks + * @return true on success, false on failure + */ + bool registerCallbacks(); + + // Private method implementations void addReceivedData(const uint8_t* data, size_t len) { { std::lock_guard lock(_bufferMutex); @@ -273,6 +316,21 @@ class BleStream : public jac::Duplex { } } + static std::string generateDeviceName() { + uint8_t mac[6]; + esp_err_t ret = esp_read_mac(mac, ESP_MAC_WIFI_STA); + if (ret != ESP_OK) { + BLE_LOG_ERROR("Failed to read MAC address: " + std::string(esp_err_to_name(ret))); + return std::string(DEVICE_NAME_PREFIX); + } + + // Use last 2 bytes of MAC address for uniqueness + char name_buffer[32]; + snprintf(name_buffer, sizeof(name_buffer), "%s_%02X%02X", + DEVICE_NAME_PREFIX, mac[4], mac[5]); + return std::string(name_buffer); + } + // GAP event handler static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { BLE_LOG_DEBUG("GAP Event: " + std::to_string(event)); @@ -314,8 +372,10 @@ class BleStream : public jac::Duplex { BLE_LOG_DEBUG("GATTS register event - Starting BLE service setup"); gatts_if = gatts_if_param; - BLE_LOG_DEBUG("Setting device name to: " + std::string(DEVICE_NAME)); - esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(DEVICE_NAME); + // Generate device name with MAC address + std::string device_name = generateDeviceName(); + BLE_LOG_DEBUG("Setting device name to: " + device_name); + esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(device_name.c_str()); if (set_dev_name_ret) { BLE_LOG_ERROR("Set device name failed: " + std::string(esp_err_to_name(set_dev_name_ret))); } else { From 2ca5310afc8d9cb43df0fd0fd9ab7235d12ec026 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Sat, 14 Jun 2025 17:44:19 +0200 Subject: [PATCH 3/6] refactor BLE Stream initialization: reduce debug logging for clarity and maintainability --- main/util/bleStream.h | 64 ------------------------------------------- 1 file changed, 64 deletions(-) diff --git a/main/util/bleStream.h b/main/util/bleStream.h index 3cfcdaf..ae1fb5b 100644 --- a/main/util/bleStream.h +++ b/main/util/bleStream.h @@ -112,89 +112,69 @@ class BleStream : public jac::Duplex { */ void start() { if (_isInitialized) { - BLE_LOG_DEBUG("BLE Stream already initialized"); return; } BLE_LOG_INFO("Starting BLE Stream initialization..."); esp_err_t ret; - // Note: NVS is already initialized in main.cpp, so we skip it here // Release Classic BT memory (only if not already released) static bool bt_mem_released = false; if (!bt_mem_released) { - BLE_LOG_DEBUG("Releasing Classic BT memory..."); ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); if (ret == ESP_OK) { bt_mem_released = true; - BLE_LOG_DEBUG("Classic BT memory released successfully"); } else { BLE_LOG_ERROR("Failed to release Classic BT memory: " + std::string(esp_err_to_name(ret))); } - } else { - BLE_LOG_DEBUG("Classic BT memory already released"); } // Initialize BT controller - BLE_LOG_DEBUG("Initializing BT controller..."); esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); ret = esp_bt_controller_init(&bt_cfg); if (ret) { BLE_LOG_ERROR("BLE controller init failed: " + std::string(esp_err_to_name(ret))); return; } - BLE_LOG_DEBUG("BT controller initialized successfully"); - BLE_LOG_DEBUG("Enabling BLE mode..."); ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); if (ret) { BLE_LOG_ERROR("BLE controller enable failed: " + std::string(esp_err_to_name(ret))); return; } - BLE_LOG_DEBUG("BLE mode enabled successfully"); // Initialize Bluedroid - BLE_LOG_DEBUG("Initializing Bluedroid stack..."); ret = esp_bluedroid_init(); if (ret) { BLE_LOG_ERROR("Bluedroid init failed: " + std::string(esp_err_to_name(ret))); return; } - BLE_LOG_DEBUG("Bluedroid stack initialized successfully"); - BLE_LOG_DEBUG("Enabling Bluedroid stack..."); ret = esp_bluedroid_enable(); if (ret) { BLE_LOG_ERROR("Bluedroid enable failed: " + std::string(esp_err_to_name(ret))); return; } - BLE_LOG_DEBUG("Bluedroid stack enabled successfully"); // Register callbacks - BLE_LOG_DEBUG("Registering GATTS callback..."); ret = esp_ble_gatts_register_callback(gatts_event_handler); if (ret) { BLE_LOG_ERROR("GATTS register callback failed: " + std::string(esp_err_to_name(ret))); return; } - BLE_LOG_DEBUG("GATTS callback registered successfully"); - BLE_LOG_DEBUG("Registering GAP callback..."); ret = esp_ble_gap_register_callback(gap_event_handler); if (ret) { BLE_LOG_ERROR("GAP register callback failed: " + std::string(esp_err_to_name(ret))); return; } - BLE_LOG_DEBUG("GAP callback registered successfully"); // Register GATT application - BLE_LOG_DEBUG("Registering GATT application..."); ret = esp_ble_gatts_app_register(0); if (ret) { BLE_LOG_ERROR("GATTS app register failed: " + std::string(esp_err_to_name(ret))); return; } - BLE_LOG_DEBUG("GATT application registration initiated"); // Set MTU esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); @@ -333,11 +313,8 @@ class BleStream : public jac::Duplex { // GAP event handler static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - BLE_LOG_DEBUG("GAP Event: " + std::to_string(event)); - switch (event) { case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: - BLE_LOG_DEBUG("Advertisement data set complete, starting advertising..."); esp_ble_gap_start_advertising(&adv_params); break; case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: @@ -350,12 +327,9 @@ class BleStream : public jac::Duplex { case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { BLE_LOG_ERROR("Advertising stop failed"); - } else { - BLE_LOG_DEBUG("BLE advertising stopped"); } break; default: - BLE_LOG_DEBUG("Unhandled GAP event: " + std::to_string(event)); break; } } @@ -364,22 +338,16 @@ class BleStream : public jac::Duplex { static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if_param, esp_ble_gatts_cb_param_t *param) { if (!instance) return; - BLE_LOG_DEBUG("GATTS Event: " + std::to_string(event)); - switch (event) { case ESP_GATTS_REG_EVT: { - BLE_LOG_DEBUG("GATTS register event - Starting BLE service setup"); gatts_if = gatts_if_param; // Generate device name with MAC address std::string device_name = generateDeviceName(); - BLE_LOG_DEBUG("Setting device name to: " + device_name); esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(device_name.c_str()); if (set_dev_name_ret) { BLE_LOG_ERROR("Set device name failed: " + std::string(esp_err_to_name(set_dev_name_ret))); - } else { - BLE_LOG_DEBUG("Device name set successfully"); } // Configure advertising data @@ -399,16 +367,12 @@ class BleStream : public jac::Duplex { .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), }; - BLE_LOG_DEBUG("Configuring advertisement data..."); esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data); if (ret) { BLE_LOG_ERROR("Config adv data failed: " + std::string(esp_err_to_name(ret))); - } else { - BLE_LOG_DEBUG("Advertisement data configured successfully"); } // Create service - BLE_LOG_DEBUG("Creating GATT service with UUID: 0x" + std::to_string(GATTS_SERVICE_UUID)); esp_gatt_srvc_id_t service_id; service_id.is_primary = true; service_id.id.inst_id = 0x00; @@ -417,27 +381,20 @@ class BleStream : public jac::Duplex { esp_err_t create_ret = esp_ble_gatts_create_service(gatts_if, &service_id, GATTS_NUM_HANDLE); if (create_ret) { BLE_LOG_ERROR("Create service failed: " + std::string(esp_err_to_name(create_ret))); - } else { - BLE_LOG_DEBUG("Service creation initiated"); } break; } case ESP_GATTS_CREATE_EVT: { - BLE_LOG_DEBUG("Service created with handle: " + std::to_string(param->create.service_handle)); service_handle = param->create.service_handle; - BLE_LOG_DEBUG("Starting service..."); esp_err_t start_ret = esp_ble_gatts_start_service(service_handle); if (start_ret) { BLE_LOG_ERROR("Start service failed: " + std::string(esp_err_to_name(start_ret))); - } else { - BLE_LOG_DEBUG("Service start initiated"); } // Add characteristic - BLE_LOG_DEBUG("Adding characteristic with UUID: 0x" + std::to_string(GATTS_CHAR_UUID)); esp_bt_uuid_t char_uuid = { .len = ESP_UUID_LEN_16, .uuid = {.uuid16 = GATTS_CHAR_UUID} @@ -452,15 +409,12 @@ class BleStream : public jac::Duplex { char_property, nullptr, nullptr); if (add_char_ret) { BLE_LOG_ERROR("Add characteristic failed: " + std::string(esp_err_to_name(add_char_ret))); - } else { - BLE_LOG_DEBUG("Characteristic addition initiated"); } break; } case ESP_GATTS_ADD_CHAR_EVT: { - BLE_LOG_DEBUG("Characteristic added with handle: " + std::to_string(param->add_char.attr_handle)); char_handle = param->add_char.attr_handle; // Add descriptor for notifications @@ -469,35 +423,27 @@ class BleStream : public jac::Duplex { .uuid = {.uuid16 = GATTS_DESCR_UUID} }; - BLE_LOG_DEBUG("Adding descriptor for notifications..."); esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(service_handle, &descr_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, nullptr, nullptr); if (add_descr_ret) { BLE_LOG_ERROR("Add descriptor failed: " + std::string(esp_err_to_name(add_descr_ret))); - } else { - BLE_LOG_DEBUG("Descriptor addition initiated"); } break; } case ESP_GATTS_ADD_CHAR_DESCR_EVT: { - BLE_LOG_DEBUG("Descriptor added with handle: " + std::to_string(param->add_char_descr.attr_handle)); descr_handle = param->add_char_descr.attr_handle; // Now that everything is set up, start advertising - BLE_LOG_DEBUG("All services set up, starting BLE advertising..."); esp_err_t adv_start_ret = esp_ble_gap_start_advertising(&adv_params); if (adv_start_ret) { BLE_LOG_ERROR("Failed to start advertising: " + std::string(esp_err_to_name(adv_start_ret))); - } else { - BLE_LOG_DEBUG("Advertising start command sent successfully"); } break; } case ESP_GATTS_START_EVT: - BLE_LOG_DEBUG("Service started"); break; case ESP_GATTS_CONNECT_EVT: @@ -533,12 +479,10 @@ class BleStream : public jac::Duplex { BLE_LOG_INFO("BLE notifications enabled"); instance->_notifyEnabled = true; } else { - BLE_LOG_DEBUG("BLE notifications disabled"); instance->_notifyEnabled = false; } } else if (param->write.handle == char_handle) { // Data written to characteristic - BLE_LOG_DEBUG("Received " + std::to_string(param->write.len) + " bytes via BLE"); instance->addReceivedData(param->write.value, param->write.len); } @@ -551,8 +495,6 @@ class BleStream : public jac::Duplex { case ESP_GATTS_READ_EVT: { - BLE_LOG_DEBUG("Read request for handle: " + std::to_string(param->read.handle)); - // Respond to read requests with empty data esp_gatt_rsp_t rsp = {}; rsp.attr_value.handle = param->read.handle; @@ -569,14 +511,8 @@ class BleStream : public jac::Duplex { // Handle other events without error case ESP_GATTS_EXEC_WRITE_EVT: - BLE_LOG_DEBUG("GATTS Execute Write Event"); - break; case ESP_GATTS_MTU_EVT: - BLE_LOG_DEBUG("MTU updated to: " + std::to_string(param->mtu.mtu)); - break; case ESP_GATTS_CONF_EVT: - // Confirmation received for notification/indication - break; case ESP_GATTS_UNREG_EVT: case ESP_GATTS_ADD_INCL_SRVC_EVT: case ESP_GATTS_DELETE_EVT: From d958bfeb62d619b95ee55a7e2d0dd4b6296556fc Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Sun, 15 Jun 2025 16:13:29 +0200 Subject: [PATCH 4/6] refactor BLE Stream - separate into two files --- main/util/bleStream.cpp | 410 +++++++++++++++++++++++++++++++++++++ main/util/bleStream.h | 437 ++-------------------------------------- 2 files changed, 429 insertions(+), 418 deletions(-) create mode 100644 main/util/bleStream.cpp diff --git a/main/util/bleStream.cpp b/main/util/bleStream.cpp new file mode 100644 index 0000000..6836694 --- /dev/null +++ b/main/util/bleStream.cpp @@ -0,0 +1,410 @@ +#include "bleStream.h" +#include + +// Static member initialization +esp_ble_adv_data_t BleStream::adv_data = {}; +esp_ble_adv_params_t BleStream::adv_params = { + .adv_int_min = 0x20, + .adv_int_max = 0x40, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .peer_addr = {}, + .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + +void BleStream::start() { + if (_isInitialized) { + return; + } + + BLE_LOG_INFO("Starting BLE Stream initialization..."); + esp_err_t ret; + + // Release Classic BT memory (only if not already released) + static bool bt_mem_released = false; + if (!bt_mem_released) { + ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); + if (ret == ESP_OK) { + bt_mem_released = true; + } else { + BLE_LOG_ERROR("Failed to release Classic BT memory: " + std::string(esp_err_to_name(ret))); + } + } + + // Initialize BT controller + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + BLE_LOG_ERROR("BLE controller init failed: " + std::string(esp_err_to_name(ret))); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + BLE_LOG_ERROR("BLE controller enable failed: " + std::string(esp_err_to_name(ret))); + return; + } + + // Initialize Bluedroid + ret = esp_bluedroid_init(); + if (ret) { + BLE_LOG_ERROR("Bluedroid init failed: " + std::string(esp_err_to_name(ret))); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + BLE_LOG_ERROR("Bluedroid enable failed: " + std::string(esp_err_to_name(ret))); + return; + } + + // Register callbacks + ret = esp_ble_gatts_register_callback(gatts_event_handler); + if (ret) { + BLE_LOG_ERROR("GATTS register callback failed: " + std::string(esp_err_to_name(ret))); + return; + } + + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret) { + BLE_LOG_ERROR("GAP register callback failed: " + std::string(esp_err_to_name(ret))); + return; + } + + // Register GATT application + ret = esp_ble_gatts_app_register(0); + if (ret) { + BLE_LOG_ERROR("GATTS app register failed: " + std::string(esp_err_to_name(ret))); + return; + } + + // Set MTU + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + if (local_mtu_ret) { + BLE_LOG_ERROR("Set local MTU failed: " + std::string(esp_err_to_name(local_mtu_ret))); + } + + _isInitialized = true; + BLE_LOG_INFO("BLE Stream initialized successfully"); +} + +bool BleStream::put(uint8_t c) { + std::array arr{c}; + return write(std::span(arr)) == 1; +} + +size_t BleStream::write(std::span data) { + if (!_isConnected || !_notifyEnabled) { + return data.size(); // Pretend write succeeded when not connected + } + + size_t written = 0; + while (written < data.size()) { + size_t chunkSize = std::min(MAX_CHUNK_SIZE, data.size() - written); + + esp_err_t ret = esp_ble_gatts_send_indicate( + gatts_if, + conn_id, + char_handle, + chunkSize, + const_cast(data.data() + written), + false // notification, not indication + ); + + if (ret != ESP_OK) { + BLE_LOG_ERROR("BLE notification failed: " + std::string(esp_err_to_name(ret))); + break; + } + + written += chunkSize; + + // Small delay to avoid overwhelming the BLE stack + if (written < data.size()) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + } + + return written; +} + +int BleStream::get() { + std::lock_guard lock(_bufferMutex); + if (_buffer.empty()) { + return -1; + } + uint8_t c = _buffer.front(); + _buffer.pop_front(); + return c; +} + +size_t BleStream::read(std::span data) { + std::lock_guard lock(_bufferMutex); + size_t len = std::min(data.size(), _buffer.size()); + std::copy_n(_buffer.begin(), len, data.begin()); + _buffer.erase(_buffer.begin(), _buffer.begin() + len); + return len; +} + +bool BleStream::flush() { + return true; // BLE notifications are sent immediately +} + +void BleStream::onData(std::function callback) { + _onData = callback; +} + +void BleStream::cleanup() { + if (_isInitialized) { + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); + _isInitialized = false; + } +} + +void BleStream::addReceivedData(const uint8_t* data, size_t len) { + { + std::lock_guard lock(_bufferMutex); + _buffer.insert(_buffer.end(), data, data + len); + } + + if (_onData) { + _onData(); + } +} + +std::string BleStream::generateDeviceName() { + uint8_t mac[6]; + esp_err_t ret = esp_read_mac(mac, ESP_MAC_WIFI_STA); + if (ret != ESP_OK) { + BLE_LOG_ERROR("Failed to read MAC address: " + std::string(esp_err_to_name(ret))); + return std::string(DEVICE_NAME_PREFIX); + } + + // Use last 2 bytes of MAC address for uniqueness + char name_buffer[32]; + snprintf(name_buffer, sizeof(name_buffer), "%s_%02X%02X", + DEVICE_NAME_PREFIX, mac[4], mac[5]); + return std::string(name_buffer); +} + +void BleStream::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + switch (event) { + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + esp_ble_gap_start_advertising(&adv_params); + break; + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: + if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + BLE_LOG_ERROR("Advertising start failed with status: " + std::to_string(param->adv_start_cmpl.status)); + } else { + BLE_LOG_INFO("BLE advertising started successfully! Device should be discoverable now."); + } + break; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { + BLE_LOG_ERROR("Advertising stop failed"); + } + break; + default: + break; + } +} + +void BleStream::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if_param, esp_ble_gatts_cb_param_t *param) { + if (!instance) return; + + switch (event) { + case ESP_GATTS_REG_EVT: + { + gatts_if = gatts_if_param; + + // Generate device name with MAC address + std::string device_name = generateDeviceName(); + esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(device_name.c_str()); + if (set_dev_name_ret) { + BLE_LOG_ERROR("Set device name failed: " + std::string(esp_err_to_name(set_dev_name_ret))); + } + + // Configure advertising data + esp_ble_adv_data_t adv_data = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = true, + .min_interval = 0x0006, + .max_interval = 0x0010, + .appearance = 0x00, + .manufacturer_len = 0, + .p_manufacturer_data = nullptr, + .service_data_len = 0, + .p_service_data = nullptr, + .service_uuid_len = 0, + .p_service_uuid = nullptr, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), + }; + + esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data); + if (ret) { + BLE_LOG_ERROR("Config adv data failed: " + std::string(esp_err_to_name(ret))); + } + + // Create service + esp_gatt_srvc_id_t service_id; + service_id.is_primary = true; + service_id.id.inst_id = 0x00; + service_id.id.uuid.len = ESP_UUID_LEN_16; + service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID; + esp_err_t create_ret = esp_ble_gatts_create_service(gatts_if, &service_id, GATTS_NUM_HANDLE); + if (create_ret) { + BLE_LOG_ERROR("Create service failed: " + std::string(esp_err_to_name(create_ret))); + } + break; + } + + case ESP_GATTS_CREATE_EVT: + { + service_handle = param->create.service_handle; + + esp_err_t start_ret = esp_ble_gatts_start_service(service_handle); + if (start_ret) { + BLE_LOG_ERROR("Start service failed: " + std::string(esp_err_to_name(start_ret))); + } + + // Add characteristic + esp_bt_uuid_t char_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = GATTS_CHAR_UUID} + }; + + esp_gatt_char_prop_t char_property = ESP_GATT_CHAR_PROP_BIT_READ | + ESP_GATT_CHAR_PROP_BIT_WRITE | + ESP_GATT_CHAR_PROP_BIT_NOTIFY; + + esp_err_t add_char_ret = esp_ble_gatts_add_char(service_handle, &char_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + char_property, nullptr, nullptr); + if (add_char_ret) { + BLE_LOG_ERROR("Add characteristic failed: " + std::string(esp_err_to_name(add_char_ret))); + } + break; + } + + case ESP_GATTS_ADD_CHAR_EVT: + { + char_handle = param->add_char.attr_handle; + + // Add descriptor for notifications + esp_bt_uuid_t descr_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = GATTS_DESCR_UUID} + }; + + esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(service_handle, &descr_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + nullptr, nullptr); + if (add_descr_ret) { + BLE_LOG_ERROR("Add descriptor failed: " + std::string(esp_err_to_name(add_descr_ret))); + } + break; + } + + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + { + descr_handle = param->add_char_descr.attr_handle; + // Now that everything is set up, start advertising + esp_err_t adv_start_ret = esp_ble_gap_start_advertising(&adv_params); + if (adv_start_ret) { + BLE_LOG_ERROR("Failed to start advertising: " + std::string(esp_err_to_name(adv_start_ret))); + } + break; + } + + case ESP_GATTS_START_EVT: + break; + + case ESP_GATTS_CONNECT_EVT: + { + BLE_LOG_INFO("BLE client connected"); + conn_id = param->connect.conn_id; + instance->_isConnected = true; + + // Update connection parameters + esp_ble_conn_update_params_t conn_params = {}; + memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + conn_params.latency = 0; + conn_params.max_int = 0x20; + conn_params.min_int = 0x10; + conn_params.timeout = 400; + esp_ble_gap_update_conn_params(&conn_params); + break; + } + + case ESP_GATTS_DISCONNECT_EVT: + BLE_LOG_INFO("BLE client disconnected"); + instance->_isConnected = false; + instance->_notifyEnabled = false; + esp_ble_gap_start_advertising(&adv_params); + break; + + case ESP_GATTS_WRITE_EVT: + { + if (param->write.handle == descr_handle && param->write.len == 2) { + // Client Characteristic Configuration descriptor written + uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0]; + if (descr_value == 0x0001) { + BLE_LOG_INFO("BLE notifications enabled"); + instance->_notifyEnabled = true; + } else { + instance->_notifyEnabled = false; + } + } else if (param->write.handle == char_handle) { + // Data written to characteristic + instance->addReceivedData(param->write.value, param->write.len); + } + + if (param->write.need_rsp) { + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, ESP_GATT_OK, nullptr); + } + break; + } + + case ESP_GATTS_READ_EVT: + { + // Respond to read requests with empty data + esp_gatt_rsp_t rsp = {}; + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 0; + rsp.attr_value.value[0] = 0x00; // Set at least one byte + + esp_err_t rsp_err = esp_ble_gatts_send_response(gatts_if, param->read.conn_id, + param->read.trans_id, ESP_GATT_OK, &rsp); + if (rsp_err != ESP_OK) { + BLE_LOG_ERROR("Failed to send read response: " + std::string(esp_err_to_name(rsp_err))); + } + break; + } + + // Handle other events without error + case ESP_GATTS_EXEC_WRITE_EVT: + case ESP_GATTS_MTU_EVT: + case ESP_GATTS_CONF_EVT: + case ESP_GATTS_UNREG_EVT: + case ESP_GATTS_ADD_INCL_SRVC_EVT: + case ESP_GATTS_DELETE_EVT: + case ESP_GATTS_STOP_EVT: + case ESP_GATTS_OPEN_EVT: + case ESP_GATTS_CANCEL_OPEN_EVT: + case ESP_GATTS_CLOSE_EVT: + case ESP_GATTS_LISTEN_EVT: + case ESP_GATTS_CONGEST_EVT: + case ESP_GATTS_RESPONSE_EVT: + case ESP_GATTS_CREAT_ATTR_TAB_EVT: + case ESP_GATTS_SET_ATTR_VAL_EVT: + case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: + default: + break; + } +} diff --git a/main/util/bleStream.h b/main/util/bleStream.h index ae1fb5b..741f0b3 100644 --- a/main/util/bleStream.h +++ b/main/util/bleStream.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -43,8 +44,6 @@ * Provides a BLE GATT server that can be used as a duplex stream for communication. * Integrates with the Jaculus-esp32 project as an additional transport method. */ - - class BleStream : public jac::Duplex { private: // Member variables @@ -110,437 +109,39 @@ class BleStream : public jac::Duplex { * @brief Initialize and start the BLE stack * @note This method is idempotent - calling it multiple times is safe */ - void start() { - if (_isInitialized) { - return; - } - - BLE_LOG_INFO("Starting BLE Stream initialization..."); - esp_err_t ret; - - // Release Classic BT memory (only if not already released) - static bool bt_mem_released = false; - if (!bt_mem_released) { - ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); - if (ret == ESP_OK) { - bt_mem_released = true; - } else { - BLE_LOG_ERROR("Failed to release Classic BT memory: " + std::string(esp_err_to_name(ret))); - } - } - - // Initialize BT controller - esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - ret = esp_bt_controller_init(&bt_cfg); - if (ret) { - BLE_LOG_ERROR("BLE controller init failed: " + std::string(esp_err_to_name(ret))); - return; - } - - ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); - if (ret) { - BLE_LOG_ERROR("BLE controller enable failed: " + std::string(esp_err_to_name(ret))); - return; - } - - // Initialize Bluedroid - ret = esp_bluedroid_init(); - if (ret) { - BLE_LOG_ERROR("Bluedroid init failed: " + std::string(esp_err_to_name(ret))); - return; - } - - ret = esp_bluedroid_enable(); - if (ret) { - BLE_LOG_ERROR("Bluedroid enable failed: " + std::string(esp_err_to_name(ret))); - return; - } - - // Register callbacks - ret = esp_ble_gatts_register_callback(gatts_event_handler); - if (ret) { - BLE_LOG_ERROR("GATTS register callback failed: " + std::string(esp_err_to_name(ret))); - return; - } - - ret = esp_ble_gap_register_callback(gap_event_handler); - if (ret) { - BLE_LOG_ERROR("GAP register callback failed: " + std::string(esp_err_to_name(ret))); - return; - } - - // Register GATT application - ret = esp_ble_gatts_app_register(0); - if (ret) { - BLE_LOG_ERROR("GATTS app register failed: " + std::string(esp_err_to_name(ret))); - return; - } - - // Set MTU - esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); - if (local_mtu_ret) { - BLE_LOG_ERROR("Set local MTU failed: " + std::string(esp_err_to_name(local_mtu_ret))); - } - - _isInitialized = true; - BLE_LOG_INFO("BLE Stream initialized successfully"); - } + void start(); // Duplex interface implementation - bool put(uint8_t c) override { - std::array arr{c}; - return write(std::span(arr)) == 1; - } - - size_t write(std::span data) override { - if (!_isConnected || !_notifyEnabled) { - return data.size(); // Pretend write succeeded when not connected - } - - size_t written = 0; - while (written < data.size()) { - size_t chunkSize = std::min(MAX_CHUNK_SIZE, data.size() - written); - - esp_err_t ret = esp_ble_gatts_send_indicate( - gatts_if, - conn_id, - char_handle, - chunkSize, - const_cast(data.data() + written), - false // notification, not indication - ); - - if (ret != ESP_OK) { - BLE_LOG_ERROR("BLE notification failed: " + std::string(esp_err_to_name(ret))); - break; - } - - written += chunkSize; - - // Small delay to avoid overwhelming the BLE stack - if (written < data.size()) { - vTaskDelay(pdMS_TO_TICKS(1)); - } - } - - return written; - } - - int get() override { - std::lock_guard lock(_bufferMutex); - if (_buffer.empty()) { - return -1; - } - uint8_t c = _buffer.front(); - _buffer.pop_front(); - return c; - } - - size_t read(std::span data) override { - std::lock_guard lock(_bufferMutex); - size_t len = std::min(data.size(), _buffer.size()); - std::copy_n(_buffer.begin(), len, data.begin()); - _buffer.erase(_buffer.begin(), _buffer.begin() + len); - return len; - } - - bool flush() override { - return true; // BLE notifications are sent immediately - } - - void onData(std::function callback) override { - _onData = callback; - } + bool put(uint8_t c) override; + size_t write(std::span data) override; + int get() override; + size_t read(std::span data) override; + bool flush() override; + void onData(std::function callback) override; private: /** * @brief Clean up BLE resources */ - void cleanup() { - if (_isInitialized) { - esp_bluedroid_disable(); - esp_bluedroid_deinit(); - esp_bt_controller_disable(); - esp_bt_controller_deinit(); - _isInitialized = false; - } - } + void cleanup(); /** - * @brief Initialize the BT controller - * @return true on success, false on failure + * @brief Add received data to buffer and trigger callback */ - bool initBtController(); + void addReceivedData(const uint8_t* data, size_t len); /** - * @brief Initialize the Bluedroid stack - * @return true on success, false on failure + * @brief Generate device name with MAC address suffix */ - bool initBluedroid(); + static std::string generateDeviceName(); /** - * @brief Register BLE callbacks - * @return true on success, false on failure + * @brief GAP event handler */ - bool registerCallbacks(); - - // Private method implementations - void addReceivedData(const uint8_t* data, size_t len) { - { - std::lock_guard lock(_bufferMutex); - _buffer.insert(_buffer.end(), data, data + len); - } - - if (_onData) { - _onData(); - } - } - - static std::string generateDeviceName() { - uint8_t mac[6]; - esp_err_t ret = esp_read_mac(mac, ESP_MAC_WIFI_STA); - if (ret != ESP_OK) { - BLE_LOG_ERROR("Failed to read MAC address: " + std::string(esp_err_to_name(ret))); - return std::string(DEVICE_NAME_PREFIX); - } - - // Use last 2 bytes of MAC address for uniqueness - char name_buffer[32]; - snprintf(name_buffer, sizeof(name_buffer), "%s_%02X%02X", - DEVICE_NAME_PREFIX, mac[4], mac[5]); - return std::string(name_buffer); - } - - // GAP event handler - static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - switch (event) { - case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: - esp_ble_gap_start_advertising(&adv_params); - break; - case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: - if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { - BLE_LOG_ERROR("Advertising start failed with status: " + std::to_string(param->adv_start_cmpl.status)); - } else { - BLE_LOG_INFO("BLE advertising started successfully! Device should be discoverable now."); - } - break; - case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: - if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { - BLE_LOG_ERROR("Advertising stop failed"); - } - break; - default: - break; - } - } - - // GATTS event handler - static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if_param, esp_ble_gatts_cb_param_t *param) { - if (!instance) return; - - switch (event) { - case ESP_GATTS_REG_EVT: - { - gatts_if = gatts_if_param; - - // Generate device name with MAC address - std::string device_name = generateDeviceName(); - esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(device_name.c_str()); - if (set_dev_name_ret) { - BLE_LOG_ERROR("Set device name failed: " + std::string(esp_err_to_name(set_dev_name_ret))); - } - - // Configure advertising data - esp_ble_adv_data_t adv_data = { - .set_scan_rsp = false, - .include_name = true, - .include_txpower = true, - .min_interval = 0x0006, - .max_interval = 0x0010, - .appearance = 0x00, - .manufacturer_len = 0, - .p_manufacturer_data = nullptr, - .service_data_len = 0, - .p_service_data = nullptr, - .service_uuid_len = 0, - .p_service_uuid = nullptr, - .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), - }; - - esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data); - if (ret) { - BLE_LOG_ERROR("Config adv data failed: " + std::string(esp_err_to_name(ret))); - } - - // Create service - esp_gatt_srvc_id_t service_id; - service_id.is_primary = true; - service_id.id.inst_id = 0x00; - service_id.id.uuid.len = ESP_UUID_LEN_16; - service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID; - esp_err_t create_ret = esp_ble_gatts_create_service(gatts_if, &service_id, GATTS_NUM_HANDLE); - if (create_ret) { - BLE_LOG_ERROR("Create service failed: " + std::string(esp_err_to_name(create_ret))); - } - break; - } - - case ESP_GATTS_CREATE_EVT: - { - service_handle = param->create.service_handle; - - esp_err_t start_ret = esp_ble_gatts_start_service(service_handle); - if (start_ret) { - BLE_LOG_ERROR("Start service failed: " + std::string(esp_err_to_name(start_ret))); - } + static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); - // Add characteristic - esp_bt_uuid_t char_uuid = { - .len = ESP_UUID_LEN_16, - .uuid = {.uuid16 = GATTS_CHAR_UUID} - }; - - esp_gatt_char_prop_t char_property = ESP_GATT_CHAR_PROP_BIT_READ | - ESP_GATT_CHAR_PROP_BIT_WRITE | - ESP_GATT_CHAR_PROP_BIT_NOTIFY; - - esp_err_t add_char_ret = esp_ble_gatts_add_char(service_handle, &char_uuid, - ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, - char_property, nullptr, nullptr); - if (add_char_ret) { - BLE_LOG_ERROR("Add characteristic failed: " + std::string(esp_err_to_name(add_char_ret))); - } - break; - } - - case ESP_GATTS_ADD_CHAR_EVT: - { - char_handle = param->add_char.attr_handle; - - // Add descriptor for notifications - esp_bt_uuid_t descr_uuid = { - .len = ESP_UUID_LEN_16, - .uuid = {.uuid16 = GATTS_DESCR_UUID} - }; - - esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(service_handle, &descr_uuid, - ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, - nullptr, nullptr); - if (add_descr_ret) { - BLE_LOG_ERROR("Add descriptor failed: " + std::string(esp_err_to_name(add_descr_ret))); - } - break; - } - - case ESP_GATTS_ADD_CHAR_DESCR_EVT: - { - descr_handle = param->add_char_descr.attr_handle; - // Now that everything is set up, start advertising - esp_err_t adv_start_ret = esp_ble_gap_start_advertising(&adv_params); - if (adv_start_ret) { - BLE_LOG_ERROR("Failed to start advertising: " + std::string(esp_err_to_name(adv_start_ret))); - } - break; - } - - case ESP_GATTS_START_EVT: - break; - - case ESP_GATTS_CONNECT_EVT: - { - BLE_LOG_INFO("BLE client connected"); - conn_id = param->connect.conn_id; - instance->_isConnected = true; - - // Update connection parameters - esp_ble_conn_update_params_t conn_params = {}; - memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); - conn_params.latency = 0; - conn_params.max_int = 0x20; - conn_params.min_int = 0x10; - conn_params.timeout = 400; - esp_ble_gap_update_conn_params(&conn_params); - break; - } - - case ESP_GATTS_DISCONNECT_EVT: - BLE_LOG_INFO("BLE client disconnected"); - instance->_isConnected = false; - instance->_notifyEnabled = false; - esp_ble_gap_start_advertising(&adv_params); - break; - - case ESP_GATTS_WRITE_EVT: - { - if (param->write.handle == descr_handle && param->write.len == 2) { - // Client Characteristic Configuration descriptor written - uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0]; - if (descr_value == 0x0001) { - BLE_LOG_INFO("BLE notifications enabled"); - instance->_notifyEnabled = true; - } else { - instance->_notifyEnabled = false; - } - } else if (param->write.handle == char_handle) { - // Data written to characteristic - instance->addReceivedData(param->write.value, param->write.len); - } - - if (param->write.need_rsp) { - esp_ble_gatts_send_response(gatts_if, param->write.conn_id, - param->write.trans_id, ESP_GATT_OK, nullptr); - } - break; - } - - case ESP_GATTS_READ_EVT: - { - // Respond to read requests with empty data - esp_gatt_rsp_t rsp = {}; - rsp.attr_value.handle = param->read.handle; - rsp.attr_value.len = 0; - rsp.attr_value.value[0] = 0x00; // Set at least one byte - - esp_err_t rsp_err = esp_ble_gatts_send_response(gatts_if, param->read.conn_id, - param->read.trans_id, ESP_GATT_OK, &rsp); - if (rsp_err != ESP_OK) { - BLE_LOG_ERROR("Failed to send read response: " + std::string(esp_err_to_name(rsp_err))); - } - break; - } - - // Handle other events without error - case ESP_GATTS_EXEC_WRITE_EVT: - case ESP_GATTS_MTU_EVT: - case ESP_GATTS_CONF_EVT: - case ESP_GATTS_UNREG_EVT: - case ESP_GATTS_ADD_INCL_SRVC_EVT: - case ESP_GATTS_DELETE_EVT: - case ESP_GATTS_STOP_EVT: - case ESP_GATTS_OPEN_EVT: - case ESP_GATTS_CANCEL_OPEN_EVT: - case ESP_GATTS_CLOSE_EVT: - case ESP_GATTS_LISTEN_EVT: - case ESP_GATTS_CONGEST_EVT: - case ESP_GATTS_RESPONSE_EVT: - case ESP_GATTS_CREAT_ATTR_TAB_EVT: - case ESP_GATTS_SET_ATTR_VAL_EVT: - case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: - default: - break; - } - } -}; - -// Static member initialization -esp_ble_adv_data_t BleStream::adv_data = {}; -esp_ble_adv_params_t BleStream::adv_params = { - .adv_int_min = 0x20, - .adv_int_max = 0x40, - .adv_type = ADV_TYPE_IND, - .own_addr_type = BLE_ADDR_TYPE_PUBLIC, - .peer_addr = {}, - .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, - .channel_map = ADV_CHNL_ALL, - .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, + /** + * @brief GATTS event handler + */ + static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if_param, esp_ble_gatts_cb_param_t *param); }; From ccf6112e5f513cc2ac2f645429a3da2b1041d1af Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Sun, 15 Jun 2025 16:20:43 +0200 Subject: [PATCH 5/6] Add BLE Stream support with configurable parameters and initialization --- main/CMakeLists.txt | 32 ++++++++++++++++--- main/Kconfig.projbuild | 69 +++++++++++++++++++++++++++++++++++++++++ main/main.cpp | 6 ++++ main/util/bleStream.cpp | 2 +- main/util/bleStream.h | 14 ++++----- 5 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 main/Kconfig.projbuild diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 62d5b1c..5f984b7 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,12 +1,34 @@ set(JAC_ESP32_VERSION "0.0.17") +# Base source files +set(COMPONENT_SRCS + "main.cpp" + "platform/espWifi.cpp" + "platform/espNvsKeyValue.cpp" + "espFeatures/gridui/gridUiFeature.cpp" + "espFeatures/gridui/widgets/_common.cpp" +) + +# Base requirements +set(COMPONENT_REQUIRES + jac-dcore jac-machine jac-link + driver pthread spiffs vfs fatfs + SmartLeds esp_timer Esp32-RBGridUI +) + +# Conditionally add BLE support +if(CONFIG_JAC_ESP32_ENABLE_BLE) + list(APPEND COMPONENT_SRCS "util/bleStream.cpp") + list(APPEND COMPONENT_REQUIRES "bt") + message(STATUS "BLE Stream support enabled") +else() + message(STATUS "BLE Stream support disabled") +endif() + idf_component_register( - SRCS "main.cpp" "platform/espWifi.cpp" "platform/espNvsKeyValue.cpp" - "espFeatures/gridui/gridUiFeature.cpp" "espFeatures/gridui/widgets/_common.cpp" + SRCS ${COMPONENT_SRCS} INCLUDE_DIRS "" - REQUIRES jac-dcore jac-machine jac-link - driver pthread spiffs vfs fatfs - SmartLeds esp_timer Esp32-RBGridUI bt + REQUIRES ${COMPONENT_REQUIRES} ) target_compile_definitions(${COMPONENT_LIB} PRIVATE JAC_ESP32_VERSION="${JAC_ESP32_VERSION}") diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild new file mode 100644 index 0000000..9c8197c --- /dev/null +++ b/main/Kconfig.projbuild @@ -0,0 +1,69 @@ +menu "Jaculus ESP32 Configuration" + + config JAC_ESP32_ENABLE_BLE + bool "Enable BLE Stream Support" + default y + help + Enable BLE (Bluetooth Low Energy) stream support for wireless communication. + This allows the ESP32 to act as a BLE GATT server that can be used + as a communication transport alongside UART, TCP, and JTAG streams. + + When enabled: + - BLE stack will be initialized + - GATT server with custom service will be created + - Device will be discoverable as "ESP32_JAC_BLE_XXXX" (XXXX = MAC suffix) + - BLE stream will be available on mux channel 4 + + When disabled: + - BLE functionality is completely removed from build + - Reduces binary size and memory usage + - BT component dependency is optional + + config JAC_ESP32_BLE_DEVICE_NAME + string "BLE Device Name Prefix" + depends on JAC_ESP32_ENABLE_BLE + default "ESP32_JAC_BLE" + help + The prefix for the BLE device name. The actual device name will be + this prefix followed by the last 2 bytes of the MAC address. + For example: "ESP32_JAC_BLE_A1B2" + + config JAC_ESP32_BLE_SERVICE_UUID + hex "BLE GATT Service UUID (16-bit)" + depends on JAC_ESP32_ENABLE_BLE + default 0x00FF + range 0x0001 0xFFFE + help + The 16-bit UUID for the custom BLE GATT service. + Default is 0x00FF (255 in decimal). + + config JAC_ESP32_BLE_CHARACTERISTIC_UUID + hex "BLE GATT Characteristic UUID (16-bit)" + depends on JAC_ESP32_ENABLE_BLE + default 0xFF01 + range 0x0001 0xFFFE + help + The 16-bit UUID for the BLE GATT characteristic used for data transfer. + Default is 0xFF01 (65281 in decimal). + + config JAC_ESP32_BLE_MTU_SIZE + int "BLE MTU Size" + depends on JAC_ESP32_ENABLE_BLE + default 500 + range 23 512 + help + Maximum Transmission Unit (MTU) size for BLE communication. + Larger values allow more data per packet but may not be supported + by all devices. Default is 500 bytes. + + config JAC_ESP32_BLE_DEBUG_LOGS + bool "Enable BLE Debug Logging" + depends on JAC_ESP32_ENABLE_BLE + default n + help + Enable detailed debug logging for BLE operations. + This will show step-by-step initialization, connection events, + and data transfer details. Useful for debugging but increases + log output significantly. + +endmenu diff --git a/main/main.cpp b/main/main.cpp index 0cb037a..b302bc1 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -34,7 +34,9 @@ #include "util/uartStream.h" #include "util/tcpStream.h" +#ifdef CONFIG_JAC_ESP32_ENABLE_BLE #include "util/bleStream.h" +#endif #include "resources/resources.h" @@ -125,7 +127,9 @@ jac::Device device( using Mux_t = jac::Mux; std::unique_ptr muxUart; std::unique_ptr muxTcp; +#ifdef CONFIG_JAC_ESP32_ENABLE_BLE std::unique_ptr muxBle; +#endif #if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) std::unique_ptr muxJtag; @@ -216,6 +220,7 @@ int main() { muxTcp->bindRx(std::make_unique(std::move(handleTcp))); } +#ifdef CONFIG_JAC_ESP32_ENABLE_BLE // initialize BLE stream auto bleStream = std::make_unique("ESP32_JAC_BLE"); bleStream->start(); @@ -224,6 +229,7 @@ int main() { muxBle->setErrorHandler(reportMuxError); auto handleBle = device.router().subscribeTx(4, *muxBle); muxBle->bindRx(std::make_unique(std::move(handleBle))); +#endif device.onConfigureMachine([&](Machine &machine) { diff --git a/main/util/bleStream.cpp b/main/util/bleStream.cpp index 6836694..ebec972 100644 --- a/main/util/bleStream.cpp +++ b/main/util/bleStream.cpp @@ -81,7 +81,7 @@ void BleStream::start() { } // Set MTU - esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(MAX_CHUNK_SIZE); if (local_mtu_ret) { BLE_LOG_ERROR("Set local MTU failed: " + std::string(esp_err_to_name(local_mtu_ret))); } diff --git a/main/util/bleStream.h b/main/util/bleStream.h index 741f0b3..3087a17 100644 --- a/main/util/bleStream.h +++ b/main/util/bleStream.h @@ -25,10 +25,8 @@ #include "esp_mac.h" // BLE Debug Logging Control -// Uncomment the next line to enable detailed BLE logging for debugging -// #define BLE_STREAM_DEBUG_LOGS - -#ifdef BLE_STREAM_DEBUG_LOGS +// Can be controlled via Kconfig (menuconfig) or by defining BLE_STREAM_DEBUG_LOGS +#if defined(CONFIG_JAC_ESP32_BLE_DEBUG_LOGS) || defined(BLE_STREAM_DEBUG_LOGS) #define BLE_LOG_DEBUG(msg) jac::Logger::debug(msg) #define BLE_LOG_ERROR(msg) jac::Logger::error(msg) #define BLE_LOG_INFO(msg) jac::Logger::debug(msg) // Use debug for info when enabled @@ -55,12 +53,12 @@ class BleStream : public jac::Duplex { std::atomic _isInitialized{false}; // BLE configuration constants - static constexpr uint16_t GATTS_SERVICE_UUID = 0x00FF; - static constexpr uint16_t GATTS_CHAR_UUID = 0xFF01; + static constexpr uint16_t GATTS_SERVICE_UUID = CONFIG_JAC_ESP32_BLE_SERVICE_UUID; + static constexpr uint16_t GATTS_CHAR_UUID = CONFIG_JAC_ESP32_BLE_CHARACTERISTIC_UUID; static constexpr uint16_t GATTS_DESCR_UUID = 0x2902; // Client Characteristic Configuration static constexpr uint16_t GATTS_NUM_HANDLE = 4; - static constexpr const char* DEVICE_NAME_PREFIX = "ESP32_JAC_BLE"; - static constexpr size_t MAX_CHUNK_SIZE = 500; // Conservative MTU size + static constexpr const char* DEVICE_NAME_PREFIX = CONFIG_JAC_ESP32_BLE_DEVICE_NAME; + static constexpr size_t MAX_CHUNK_SIZE = CONFIG_JAC_ESP32_BLE_MTU_SIZE; // Configurable MTU size // BLE handles - shared across all instances static inline esp_gatt_if_t gatts_if = ESP_GATT_IF_NONE; From 107800c3ddcfdeb63b6858b840219ea9d5ea53b4 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Sun, 15 Jun 2025 16:34:44 +0200 Subject: [PATCH 6/6] Update GitHub Actions workflow to trigger on all branches --- .github/workflows/esp-idf.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/esp-idf.yaml b/.github/workflows/esp-idf.yaml index f13f299..1f4f314 100644 --- a/.github/workflows/esp-idf.yaml +++ b/.github/workflows/esp-idf.yaml @@ -2,9 +2,9 @@ name: ESP-IDF on: push: - branches: ["master"] + branches: ["*"] pull_request: - branches: ["master"] + branches: ["*"] jobs: build: @@ -26,4 +26,4 @@ jobs: with: esp_idf_version: v5.2.2 target: ${{matrix.target}} - path: '.' + path: '.' \ No newline at end of file