diff --git a/src/BluetoothCommon.cpp b/src/BluetoothCommon.cpp index d9502e4f5..923c3170f 100644 --- a/src/BluetoothCommon.cpp +++ b/src/BluetoothCommon.cpp @@ -14,4 +14,28 @@ const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, const uint8_t LEGACY_LOGRADIO_UUID_16[16u] = {0xe2, 0xf2, 0x1e, 0xbe, 0xc5, 0x15, 0xcf, 0xaa, 0x6b, 0x43, 0xfa, 0x78, 0x38, 0xd2, 0x6f, 0x6c}; const uint8_t LOGRADIO_UUID_16[16u] = {0x47, 0x95, 0xDF, 0x8C, 0xDE, 0xE9, 0x44, 0x99, - 0x23, 0x44, 0xE6, 0x06, 0x49, 0x6E, 0x3D, 0x5A}; \ No newline at end of file + 0x23, 0x44, 0xE6, 0x06, 0x49, 0x6E, 0x3D, 0x5A}; + +void BluetoothApi::setup() {} +void BluetoothApi::shutdown() {} +void BluetoothApi::deinit() {} +void BluetoothApi::clearBonds() {} +bool BluetoothApi::isActive() +{ + return false; +} +bool BluetoothApi::isConnected() +{ + return false; +} +void BluetoothApi::sendLog(const uint8_t *logMessage, size_t length) +{ + (void)logMessage; + (void)length; +} + +void updateBatteryLevel(uint8_t level) __attribute__((weak)); +void updateBatteryLevel(uint8_t level) +{ + (void)level; +} \ No newline at end of file diff --git a/src/BluetoothCommon.h b/src/BluetoothCommon.h index 440d13844..d117170f1 100644 --- a/src/BluetoothCommon.h +++ b/src/BluetoothCommon.h @@ -24,9 +24,14 @@ void updateBatteryLevel(uint8_t level); class BluetoothApi { public: + virtual ~BluetoothApi() = default; + virtual void setup(); virtual void shutdown(); + virtual void deinit(); virtual void clearBonds(); + virtual bool isActive(); virtual bool isConnected(); virtual int getRssi() = 0; + virtual void sendLog(const uint8_t *logMessage, size_t length); }; \ No newline at end of file diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 6ff7bbb18..f205ef378 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -222,7 +222,7 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ if (config.security.debug_log_api_enabled && !pauseBluetoothLogging) { bool isBleConnected = false; #ifdef ARCH_ESP32 - isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); + isBleConnected = bluetoothApi && bluetoothApi->isActive() && bluetoothApi->isConnected(); #elif defined(ARCH_NRF52) isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected(); #endif @@ -238,7 +238,7 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ auto buffer = std::unique_ptr(new uint8_t[meshtastic_LogRecord_size]); size_t size = pb_encode_to_bytes(buffer.get(), meshtastic_LogRecord_size, meshtastic_LogRecord_fields, &logRecord); #ifdef ARCH_ESP32 - nimbleBluetooth->sendLog(buffer.get(), size); + bluetoothApi->sendLog(buffer.get(), size); #elif defined(ARCH_NRF52) nrf52Bluetooth->sendLog(buffer.get(), size); #endif diff --git a/src/bluetooth/HostedBluetooth.cpp b/src/bluetooth/HostedBluetooth.cpp new file mode 100644 index 000000000..63f735deb --- /dev/null +++ b/src/bluetooth/HostedBluetooth.cpp @@ -0,0 +1,340 @@ +#include "bluetooth/HostedBluetooth.h" + +#if defined(CONFIG_IDF_TARGET_ESP32P4) && defined(CONFIG_ESP_HOSTED_ENABLED) && !MESHTASTIC_EXCLUDE_BLUETOOTH +#include "esp_err.h" +#include "esp_event.h" +#include "esp_hosted.h" +#include "esp_hosted_event.h" +#include "esp_hosted_misc.h" +#include "esp_log.h" + +namespace +{ +constexpr const char *kTag = "HostedBluetooth"; + +// Custom message IDs exchanged with co-processor firmware. +constexpr uint32_t kMsgLogRecord = 0x4D53484C; // "MSHL" +constexpr uint32_t kMsgClearBonds = 0x4D534842; // "MSHB" +constexpr uint32_t kMsgRssiRequest = 0x4D534852; // "MSHR" +constexpr uint32_t kMsgRssiUpdate = 0x4D535252; // "MSRR" + +HostedBluetooth *sHostedInstance = nullptr; + +void hostedCustomDataCallback(uint32_t msg_id, const uint8_t *data, size_t data_len) +{ + if (!sHostedInstance || !data) { + return; + } + + if (msg_id == kMsgRssiUpdate && data_len >= sizeof(int32_t)) { + int32_t rssi = 0; + memcpy(&rssi, data, sizeof(int32_t)); + sHostedInstance->setRssi(static_cast(rssi)); + } +} + +void hostedEventHandler(void *arg, esp_event_base_t eventBase, int32_t eventId, void *eventData) +{ + (void)eventData; + + auto *self = static_cast(arg); + if (!self || eventBase != ESP_HOSTED_EVENT) { + return; + } + + switch (eventId) { + case ESP_HOSTED_EVENT_TRANSPORT_UP: + ESP_LOGI(kTag, "ESP-Hosted transport is up"); + self->setConnected(true); + break; + case ESP_HOSTED_EVENT_TRANSPORT_DOWN: + ESP_LOGW(kTag, "ESP-Hosted transport is down"); + case ESP_HOSTED_EVENT_TRANSPORT_FAILURE: + if (eventId == ESP_HOSTED_EVENT_TRANSPORT_FAILURE) { + ESP_LOGE(kTag, "ESP-Hosted transport failure"); + } + self->setConnected(false); + break; + case ESP_HOSTED_EVENT_CP_INIT: + case ESP_HOSTED_EVENT_CP_HEARTBEAT: + default: + break; + } +} +} // namespace + +HostedBluetooth::HostedBluetooth() {} + +HostedBluetooth::~HostedBluetooth() +{ + deinit(); +} + +bool HostedBluetooth::registerCallbacks() +{ + if (callbacksRegistered) { + return true; + } + + // Defensive cleanup in case setup() is called again after an incomplete/early previous init. + // esp_event_handler_unregister can safely fail if the handler wasn't present. + esp_event_handler_unregister(ESP_HOSTED_EVENT, ESP_EVENT_ANY_ID, hostedEventHandler); + + esp_err_t err = esp_event_handler_register(ESP_HOSTED_EVENT, ESP_EVENT_ANY_ID, hostedEventHandler, this); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(kTag, "Failed to register hosted event handler: %s", esp_err_to_name(err)); + return false; + } + if (err == ESP_ERR_INVALID_STATE) { + ESP_LOGW(kTag, "Hosted event handler already registered, continuing"); + } + + err = esp_hosted_register_custom_callback(kMsgRssiUpdate, hostedCustomDataCallback); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGW(kTag, "Failed to register RSSI callback: %s", esp_err_to_name(err)); + } else if (err == ESP_ERR_INVALID_STATE) { + ESP_LOGW(kTag, "RSSI callback already registered, continuing"); + } + + sHostedInstance = this; + callbacksRegistered = true; + return true; +} + +void HostedBluetooth::unregisterCallbacks() +{ + if (!callbacksRegistered) { + return; + } + + esp_hosted_register_custom_callback(kMsgRssiUpdate, nullptr); + esp_event_handler_unregister(ESP_HOSTED_EVENT, ESP_EVENT_ANY_ID, hostedEventHandler); + + if (sHostedInstance == this) { + sHostedInstance = nullptr; + } + callbacksRegistered = false; +} + +void HostedBluetooth::setup() +{ + if (active) { + return; + } + + if (!registerCallbacks()) { + return; + } + + int rc = esp_hosted_init(); + if (rc != ESP_OK) { + ESP_LOGE(kTag, "esp_hosted_init failed: %d", rc); + unregisterCallbacks(); + return; + } + + rc = esp_hosted_connect_to_slave(); + if (rc != ESP_OK) { + ESP_LOGE(kTag, "esp_hosted_connect_to_slave failed: %d", rc); + esp_hosted_deinit(); + unregisterCallbacks(); + return; + } + + rc = esp_hosted_bt_controller_init(); + if (rc != ESP_OK) { + ESP_LOGE(kTag, "esp_hosted_bt_controller_init failed: %d", rc); + esp_hosted_deinit(); + unregisterCallbacks(); + return; + } + + rc = esp_hosted_bt_controller_enable(); + if (rc != ESP_OK) { + ESP_LOGE(kTag, "esp_hosted_bt_controller_enable failed: %d", rc); + esp_hosted_bt_controller_deinit(false); + esp_hosted_deinit(); + unregisterCallbacks(); + return; + } + + active = true; + firstRssiLogged.store(false); + ESP_LOGI(kTag, "ESP-Hosted Bluetooth ready"); +} + +void HostedBluetooth::shutdown() +{ + deinit(); +} + +void HostedBluetooth::deinit() +{ + if (!active && !callbacksRegistered) { + return; + } + + esp_err_t err = esp_hosted_bt_controller_disable(); + if (err != ESP_OK) { + ESP_LOGW(kTag, "esp_hosted_bt_controller_disable returned %s", esp_err_to_name(err)); + } + + err = esp_hosted_bt_controller_deinit(false); + if (err != ESP_OK) { + ESP_LOGW(kTag, "esp_hosted_bt_controller_deinit returned %s", esp_err_to_name(err)); + } + + int rc = esp_hosted_deinit(); + if (rc != ESP_OK) { + ESP_LOGW(kTag, "esp_hosted_deinit returned %d", rc); + } + + unregisterCallbacks(); + + connected.store(false); + active = false; + rssi.store(0); + firstRssiLogged.store(false); +} + +void HostedBluetooth::clearBonds() +{ + if (!active) { + return; + } + + const esp_err_t err = esp_hosted_send_custom_data(kMsgClearBonds, nullptr, 0); + if (err != ESP_OK) { + ESP_LOGW(kTag, "Failed to request hosted bond clear: %s", esp_err_to_name(err)); + } +} + +bool HostedBluetooth::isActive() +{ + return active; +} + +bool HostedBluetooth::isConnected() +{ + return connected.load(); +} + +int HostedBluetooth::getRssi() +{ + if (active) { + const esp_err_t err = esp_hosted_send_custom_data(kMsgRssiRequest, nullptr, 0); + if (err != ESP_OK) { + ESP_LOGD(kTag, "Hosted RSSI request failed: %s", esp_err_to_name(err)); + } + } + return rssi.load(); +} + +void HostedBluetooth::sendLog(const uint8_t *logMessage, size_t length) +{ + if (!active || !isConnected() || !logMessage || length == 0) { + return; + } + + const esp_err_t err = esp_hosted_send_custom_data(kMsgLogRecord, logMessage, length); + if (err != ESP_OK) { + ESP_LOGD(kTag, "Hosted BLE log send failed: %s", esp_err_to_name(err)); + } +} + +void HostedBluetooth::setConnected(bool value) +{ + connected.store(value); +} + +void HostedBluetooth::setRssi(int value) +{ + rssi.store(value); + maybeLogFirstRssi(value); +} + +void HostedBluetooth::maybeLogFirstRssi(int value) +{ + bool expected = false; + if (firstRssiLogged.compare_exchange_strong(expected, true)) { + ESP_LOGI("HostedBluetooth", "ESP-Hosted first RSSI update: %d dBm", value); + } +} + +#else + +HostedBluetooth::HostedBluetooth() {} + +HostedBluetooth::~HostedBluetooth() +{ + deinit(); +} + +bool HostedBluetooth::registerCallbacks() +{ + return false; +} + +void HostedBluetooth::unregisterCallbacks() {} + +void HostedBluetooth::setup() +{ + active = false; + connected.store(false); + rssi.store(0); + firstRssiLogged.store(false); +} + +void HostedBluetooth::shutdown() +{ + deinit(); +} + +void HostedBluetooth::deinit() +{ + connected.store(false); + active = false; + rssi.store(0); + firstRssiLogged.store(false); +} + +void HostedBluetooth::clearBonds() {} + +bool HostedBluetooth::isActive() +{ + return active; +} + +bool HostedBluetooth::isConnected() +{ + return connected.load(); +} + +int HostedBluetooth::getRssi() +{ + return rssi.load(); +} + +void HostedBluetooth::sendLog(const uint8_t *logMessage, size_t length) +{ + (void)logMessage; + (void)length; +} + +void HostedBluetooth::setConnected(bool value) +{ + connected.store(value); +} + +void HostedBluetooth::setRssi(int value) +{ + rssi.store(value); + maybeLogFirstRssi(value); +} + +void HostedBluetooth::maybeLogFirstRssi(int value) +{ + (void)value; +} + +#endif diff --git a/src/bluetooth/HostedBluetooth.h b/src/bluetooth/HostedBluetooth.h new file mode 100644 index 000000000..7803c383d --- /dev/null +++ b/src/bluetooth/HostedBluetooth.h @@ -0,0 +1,40 @@ +#pragma once + +#include "BluetoothCommon.h" +#include + +/** + * Placeholder backend for ESP32-P4 + ESP-Hosted BLE transport. + * + * This intentionally mirrors the NimbleBluetooth surface used by the firmware, + * so the caller side can stay stable while we implement esp-hosted internals. + */ +class HostedBluetooth : public BluetoothApi +{ + public: + HostedBluetooth(); + ~HostedBluetooth() override; + + void setup() override; + void shutdown() override; + void deinit() override; + void clearBonds() override; + bool isActive() override; + bool isConnected() override; + int getRssi() override; + void sendLog(const uint8_t *logMessage, size_t length) override; + + void setConnected(bool value); + void setRssi(int value); + + private: + void maybeLogFirstRssi(int value); + bool registerCallbacks(); + void unregisterCallbacks(); + + bool active = false; + std::atomic connected{false}; + std::atomic rssi{0}; + std::atomic firstRssiLogged{false}; + bool callbacksRegistered = false; +}; diff --git a/src/main.cpp b/src/main.cpp index 6f78c0b96..43338e7b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,8 +49,7 @@ #include "mesh/http/WebServer.h" #endif #if !MESHTASTIC_EXCLUDE_BLUETOOTH -#include "nimble/NimbleBluetooth.h" -NimbleBluetooth *nimbleBluetooth = nullptr; +BluetoothApi *bluetoothApi = nullptr; #endif #endif diff --git a/src/main.h b/src/main.h index 56f048134..c283dfc95 100644 --- a/src/main.h +++ b/src/main.h @@ -12,8 +12,8 @@ #include #include #if defined(ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) -#include "nimble/NimbleBluetooth.h" -extern NimbleBluetooth *nimbleBluetooth; +#include "BluetoothCommon.h" +extern BluetoothApi *bluetoothApi; #endif #ifdef ARCH_NRF52 #include "NRF52Bluetooth.h" diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index f7a8a87bf..0a4469cc0 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -1332,9 +1332,9 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r conn.has_bluetooth = true; conn.bluetooth.pin = config.bluetooth.fixed_pin; #ifdef ARCH_ESP32 - if (config.bluetooth.enabled && nimbleBluetooth) { - conn.bluetooth.is_connected = nimbleBluetooth->isConnected(); - conn.bluetooth.rssi = nimbleBluetooth->getRssi(); + if (config.bluetooth.enabled && bluetoothApi) { + conn.bluetooth.is_connected = bluetoothApi->isConnected(); + conn.bluetooth.rssi = bluetoothApi->getRssi(); } #elif defined(ARCH_NRF52) if (config.bluetooth.enabled && nrf52Bluetooth) { @@ -1582,8 +1582,8 @@ void disableBluetooth() { #if HAS_BLUETOOTH #ifdef ARCH_ESP32 - if (nimbleBluetooth) - nimbleBluetooth->deinit(); + if (bluetoothApi) + bluetoothApi->deinit(); #elif defined(ARCH_NRF52) if (nrf52Bluetooth) nrf52Bluetooth->shutdown(); diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 3bb4ce817..ec05837b3 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -1,5 +1,5 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_BLUETOOTH +#if !MESHTASTIC_EXCLUDE_BLUETOOTH && !defined(CONFIG_IDF_TARGET_ESP32P4) #include "BluetoothCommon.h" #include "NimbleBluetooth.h" #include "PowerFSM.h" @@ -575,13 +575,13 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks class NimbleBluetoothServerCallback : public NimBLEServerCallbacks { -#ifdef NIMBLE_TWO public: NimbleBluetoothServerCallback(NimbleBluetooth *ble) { this->ble = ble; } private: NimbleBluetooth *ble; +#ifdef NIMBLE_TWO virtual uint32_t onPassKeyDisplay() #else virtual uint32_t onPassKeyRequest() @@ -691,13 +691,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks { LOG_INFO("BLE disconnect"); #endif -#ifdef NIMBLE_TWO - if (ble->isDeInit) + if (ble && ble->isDeInit) return; -#else - if (nimbleBluetooth && nimbleBluetooth->isDeInit) - return; -#endif meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); bluetoothStatus->updateStatus(&newStatus); @@ -862,7 +857,7 @@ void NimbleBluetooth::setup() #ifdef NIMBLE_TWO NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this); #else - NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(); + NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this); #endif bleServer->setCallbacks(serverCallbacks, true); setupService(); @@ -961,7 +956,7 @@ void NimbleBluetooth::startAdvertising() /// Given a level between 0-100, update the BLE attribute void updateBatteryLevel(uint8_t level) { - if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) { + if ((config.bluetooth.enabled == true) && bleServer && bluetoothApi && bluetoothApi->isConnected()) { BatteryCharacteristic->setValue(&level, 1); #ifdef NIMBLE_TWO BatteryCharacteristic->notify(&level, 1, BLE_HS_CONN_HANDLE_NONE); @@ -996,4 +991,14 @@ void clearNVS() ESP.restart(); #endif } + +#else + +void updateBatteryLevel(uint8_t level) +{ + (void)level; +} + +void clearNVS() {} + #endif diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index 458fa4a67..230da0581 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -1,17 +1,17 @@ #pragma once #include "BluetoothCommon.h" -class NimbleBluetooth : BluetoothApi +class NimbleBluetooth : public BluetoothApi { public: - void setup(); - void shutdown(); - void deinit(); - void clearBonds(); + void setup() override; + void shutdown() override; + void deinit() override; + void clearBonds() override; bool isActive(); - bool isConnected(); - int getRssi(); - void sendLog(const uint8_t *logMessage, size_t length); + bool isConnected() override; + int getRssi() override; + void sendLog(const uint8_t *logMessage, size_t length) override; #if defined(NIMBLE_TWO) void startAdvertising(); #endif diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 01193b9a1..d1b2031fe 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -6,6 +6,9 @@ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH #include "nimble/NimbleBluetooth.h" +#if defined(CONFIG_IDF_TARGET_ESP32P4) +#include "bluetooth/HostedBluetooth.h" +#endif #endif #include @@ -40,16 +43,20 @@ void setBluetoothEnable(bool enable) if (config.bluetooth.enabled == true) #endif { - if (!nimbleBluetooth) { - nimbleBluetooth = new NimbleBluetooth(); + if (!bluetoothApi) { +#if defined(CONFIG_IDF_TARGET_ESP32P4) + bluetoothApi = new HostedBluetooth(); +#else + bluetoothApi = new NimbleBluetooth(); +#endif } - if (enable && !nimbleBluetooth->isActive()) { + if (enable && !bluetoothApi->isActive()) { powerMon->setState(meshtastic_PowerMon_State_BT_On); - nimbleBluetooth->setup(); + bluetoothApi->setup(); } // For ESP32, no way to recover from bluetooth shutdown without reboot // BLE advertising automatically stops when MCU enters light-sleep(?) - // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse + // For deep-sleep, shutdown hardware with bluetoothApi->deinit(). Requires reboot to reverse } } #else diff --git a/src/sleep.cpp b/src/sleep.cpp index cdc77ca60..8da9be797 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -234,8 +234,8 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH // Full shutdown of bluetooth hardware - if (nimbleBluetooth) - nimbleBluetooth->deinit(); + if (bluetoothApi) + bluetoothApi->deinit(); #endif #ifdef ARCH_ESP32