From 5ae50b607dde0b8e000eb7f97b16e455ab39836d Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Thu, 16 Apr 2026 22:38:42 +0200 Subject: [PATCH] add class HostedBluetooth --- src/bluetooth/HostedBluetooth.cpp | 679 +++++++++++++++++++++++++----- src/bluetooth/HostedBluetooth.h | 3 + src/platform/esp32/main-esp32.cpp | 10 + 3 files changed, 594 insertions(+), 98 deletions(-) diff --git a/src/bluetooth/HostedBluetooth.cpp b/src/bluetooth/HostedBluetooth.cpp index 63f735deb..4bd429609 100644 --- a/src/bluetooth/HostedBluetooth.cpp +++ b/src/bluetooth/HostedBluetooth.cpp @@ -1,36 +1,377 @@ -#include "bluetooth/HostedBluetooth.h" +#include "configuration.h" #if defined(CONFIG_IDF_TARGET_ESP32P4) && defined(CONFIG_ESP_HOSTED_ENABLED) && !MESHTASTIC_EXCLUDE_BLUETOOTH +#include "BluetoothStatus.h" +#include "PowerFSM.h" +#include "bluetooth/HostedBluetooth.h" +#include "concurrency/OSThread.h" +#include "esp32-hal-hosted.h" #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" +#include "main.h" +#include "mesh/PhoneAPI.h" +#include "mesh/mesh-pb-constants.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "host/ble_gap.h" +#include "host/ble_store.h" namespace { -constexpr const char *kTag = "HostedBluetooth"; +constexpr uint16_t kPreferredBleMtu = 517; +constexpr uint16_t kPreferredBleTxOctets = 251; +constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8; +constexpr size_t kBluetoothToPhoneQueueSize = 3; +constexpr size_t kBluetoothFromPhoneQueueSize = 3; -// 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" +BLECharacteristic *fromNumCharacteristic = nullptr; +BLECharacteristic *logRadioCharacteristic = nullptr; +BLEServer *bleServer = nullptr; -HostedBluetooth *sHostedInstance = nullptr; +static bool passkeyShowing = false; +std::atomic hostedConnHandle{BLE_HS_CONN_HANDLE_NONE}; -void hostedCustomDataCallback(uint32_t msg_id, const uint8_t *data, size_t data_len) +void clearPairingDisplay() { - if (!sHostedInstance || !data) { + if (!passkeyShowing) { 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)); + passkeyShowing = false; +#if HAS_SCREEN + if (screen) { + screen->endAlert(); } +#endif +} + +class HostedBluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread +{ + public: + HostedBluetoothPhoneAPI() : concurrency::OSThread("HostedBluetooth") { api_type = TYPE_BLE; } + + std::mutex fromPhoneMutex; + std::atomic fromPhoneQueueSize{0}; + std::array fromPhoneQueue{}; + + std::mutex toPhoneMutex; + std::atomic toPhoneQueueSize{0}; + std::array, kBluetoothToPhoneQueueSize> toPhoneQueue{}; + std::array toPhoneQueueByteSizes{}; + std::atomic onReadCallbackIsWaitingForData{false}; + + protected: + int32_t runOnce() override + { + while (fromPhoneQueueSize > 0 || onReadCallbackIsWaitingForData) { + runOnceHandleFromPhoneQueue(); + runOnceHandleToPhoneQueue(); + } + return INT32_MAX; + } + + void onNowHasData(uint32_t fromRadioNum) override + { + PhoneAPI::onNowHasData(fromRadioNum); + + if (!fromNumCharacteristic || !bleServer || bleServer->getConnectedCount() == 0) { + return; + } + + uint8_t val[4]; + put_le32(val, fromRadioNum); + fromNumCharacteristic->setValue(val, sizeof(val)); + fromNumCharacteristic->notify(); + } + + bool checkIsConnected() override { return bleServer && bleServer->getConnectedCount() > 0; } + + private: + void runOnceHandleToPhoneQueue() + { + if (!onReadCallbackIsWaitingForData) { + return; + } + + uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0}; + size_t numBytes = getFromRadio(fromRadioBytes); + + if (numBytes > 0 && toPhoneQueueSize < kBluetoothToPhoneQueueSize) { + std::lock_guard guard(toPhoneMutex); + const size_t storeAtIndex = toPhoneQueueSize.load(); + memcpy(toPhoneQueue[storeAtIndex].data(), fromRadioBytes, numBytes); + toPhoneQueueByteSizes[storeAtIndex] = numBytes; + toPhoneQueueSize++; + } + + onReadCallbackIsWaitingForData = false; + } + + void runOnceHandleFromPhoneQueue() + { + if (fromPhoneQueueSize == 0) { + return; + } + + BLEValue val; + { + std::lock_guard guard(fromPhoneMutex); + val = fromPhoneQueue[0]; + for (size_t i = 1; i < fromPhoneQueueSize; ++i) { + fromPhoneQueue[i - 1] = fromPhoneQueue[i]; + } + fromPhoneQueueSize--; + } + + handleToRadio(val.getData(), val.getLength()); + } +}; + +static HostedBluetoothPhoneAPI *bluetoothPhoneAPI = nullptr; +uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE] = {0}; + +class HostedBluetoothToRadioCallback : public BLECharacteristicCallbacks +{ + public: + void onWrite(BLECharacteristic *pCharacteristic) override + { + if (!bluetoothPhoneAPI) { + return; + } + + BLEValue val; + val.setValue(pCharacteristic->getData(), pCharacteristic->getLength()); + + if (memcmp(lastToRadio, val.getData(), val.getLength()) == 0) { + return; + } + + if (bluetoothPhoneAPI->fromPhoneQueueSize >= kBluetoothFromPhoneQueueSize) { + LOG_WARN("Hosted BLE onWrite drop: queue full (%u bytes)", val.getLength()); + return; + } + + memcpy(lastToRadio, val.getData(), val.getLength()); + + { + std::lock_guard guard(bluetoothPhoneAPI->fromPhoneMutex); + bluetoothPhoneAPI->fromPhoneQueue.at(bluetoothPhoneAPI->fromPhoneQueueSize) = val; + bluetoothPhoneAPI->fromPhoneQueueSize++; + } + + bluetoothPhoneAPI->setIntervalFromNow(0); + concurrency::mainDelay.interrupt(); + } +}; + +class HostedBluetoothFromRadioCallback : public BLECharacteristicCallbacks +{ + public: + void onRead(BLECharacteristic *pCharacteristic) override + { + if (!bluetoothPhoneAPI) { + return; + } + + if (bluetoothPhoneAPI->toPhoneQueueSize == 0) { + bluetoothPhoneAPI->onReadCallbackIsWaitingForData = true; + bluetoothPhoneAPI->setIntervalFromNow(0); + concurrency::mainDelay.interrupt(); + + int tries = 0; + while (bluetoothPhoneAPI->onReadCallbackIsWaitingForData && tries < 4000) { + delay(tries < 20 ? 1 : 5); + tries++; + if (tries == 4000) { + LOG_WARN("BLE onRead: timeout waiting for data after %d tries, giving up and returning 0-size response", + tries); + } + } + } + + uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0}; + size_t numBytes = 0; + + { + std::lock_guard guard(bluetoothPhoneAPI->toPhoneMutex); + size_t pending = bluetoothPhoneAPI->toPhoneQueueSize.load(); + if (pending > 0) { + numBytes = bluetoothPhoneAPI->toPhoneQueueByteSizes[0]; + memcpy(fromRadioBytes, bluetoothPhoneAPI->toPhoneQueue[0].data(), numBytes); + + for (size_t i = 1; i < pending; ++i) { + memcpy(bluetoothPhoneAPI->toPhoneQueue[i - 1].data(), bluetoothPhoneAPI->toPhoneQueue[i].data(), + bluetoothPhoneAPI->toPhoneQueueByteSizes[i]); + bluetoothPhoneAPI->toPhoneQueueByteSizes[i - 1] = bluetoothPhoneAPI->toPhoneQueueByteSizes[i]; + } + + bluetoothPhoneAPI->toPhoneQueueSize--; + } + } + + pCharacteristic->setValue(fromRadioBytes, numBytes); + } +}; + +class HostedBluetoothServerCallback : public BLEServerCallbacks +{ + public: + explicit HostedBluetoothServerCallback(HostedBluetooth *owner) : owner(owner) {} + + private: + HostedBluetooth *owner; + + void onConnect(BLEServer *pServer, struct ble_gap_conn_desc *desc) override + { + BLEAddress peerAddr(desc->peer_id_addr); + LOG_INFO("Hosted BLE incoming connection %s", peerAddr.toString().c_str()); + owner->setConnected(true); + hostedConnHandle = desc->conn_handle; + + const int dataLenResult = ble_gap_set_data_len(desc->conn_handle, kPreferredBleTxOctets, kPreferredBleTxTimeUs); + if (dataLenResult != 0) { + LOG_WARN("Hosted BLE failed to raise data length rc=%d", dataLenResult); + } + + pServer->updateConnParams(desc->conn_handle, 6, 12, 0, 200); + } + + void onDisconnect(BLEServer *pServer, struct ble_gap_conn_desc *desc) override + { + (void)pServer; + (void)desc; + LOG_INFO("Hosted BLE disconnected"); + owner->setConnected(false); + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); + bluetoothStatus->updateStatus(&newStatus); + clearPairingDisplay(); + hostedConnHandle = BLE_HS_CONN_HANDLE_NONE; + memset(lastToRadio, 0, sizeof(lastToRadio)); + + if (bluetoothPhoneAPI) { + bluetoothPhoneAPI->close(); + { + std::lock_guard guard(bluetoothPhoneAPI->fromPhoneMutex); + bluetoothPhoneAPI->fromPhoneQueueSize = 0; + } + { + std::lock_guard guard(bluetoothPhoneAPI->toPhoneMutex); + bluetoothPhoneAPI->toPhoneQueueSize = 0; + } + bluetoothPhoneAPI->onReadCallbackIsWaitingForData = false; + } + + owner->startAdvertising(); + } +}; + +class HostedBluetoothSecurityCallback : public BLESecurityCallbacks +{ + public: + void onPassKeyNotify(uint32_t passkey) override + { + LOG_INFO("*** Enter passkey %06u on the peer side ***", passkey); + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); + + meshtastic::BluetoothStatus newStatus(std::to_string(passkey)); + bluetoothStatus->updateStatus(&newStatus); + +#if HAS_SCREEN + if (screen) { + screen->startAlert([passkey](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + char btPIN[16] = "888888"; + snprintf(btPIN, sizeof(btPIN), "%06u", passkey); + int x_offset = display->width() / 2; + int y_offset = display->height() <= 80 ? 0 : 12; + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(x_offset + x, y_offset + y, "Bluetooth"); +#if !defined(M5STACK_UNITC6L) + display->setFont(FONT_SMALL); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5; + display->drawString(x_offset + x, y_offset + y, "Enter this code"); +#endif + display->setFont(FONT_LARGE); + char pin[8]; + snprintf(pin, sizeof(pin), "%.3s %.3s", btPIN, btPIN + 3); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5; + display->drawString(x_offset + x, y_offset + y, pin); + + display->setFont(FONT_SMALL); + char deviceName[64]; + snprintf(deviceName, sizeof(deviceName), "Name: %s", getDeviceName()); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; + display->drawString(x_offset + x, y_offset + y, deviceName); + }); + } +#endif + + passkeyShowing = true; + } + + void onAuthenticationComplete(ble_gap_conn_desc *desc) override + { + (void)desc; + LOG_INFO("Hosted BLE authentication complete"); + + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); + bluetoothStatus->updateStatus(&newStatus); + clearPairingDisplay(); + } +}; + +HostedBluetoothToRadioCallback *toRadioCallbacks = nullptr; +HostedBluetoothFromRadioCallback *fromRadioCallbacks = nullptr; +HostedBluetoothSecurityCallback *securityCallbacks = nullptr; + +gpio_num_t getSlaveResetGpio() +{ +#if defined(CONFIG_ESP_HOSTED_GPIO_SLAVE_RESET_SLAVE) + return static_cast(CONFIG_ESP_HOSTED_GPIO_SLAVE_RESET_SLAVE); +#elif defined(CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE) + return static_cast(CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE); +#else + return GPIO_NUM_NC; +#endif +} + +void setSlaveResetLine(bool assertReset) +{ + const gpio_num_t gpioNum = getSlaveResetGpio(); + if (gpioNum == GPIO_NUM_NC || gpioNum < 0) { + return; + } + + if (gpio_reset_pin(gpioNum) != ESP_OK) { + return; + } + if (gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT) != ESP_OK) { + return; + } + + const int activeLevel = +#if defined(CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_HIGH) + 1; +#else + 0; +#endif + const int inactiveLevel = activeLevel ? 0 : 1; + + gpio_set_level(gpioNum, assertReset ? activeLevel : inactiveLevel); + LOG_DEBUG("[HostedBluetooth] setSlaveResetLine: GPIO[%d] -> %d (assertReset=%d, activeLevel=%d)", gpioNum, + assertReset ? activeLevel : inactiveLevel, assertReset, activeLevel); } void hostedEventHandler(void *arg, esp_event_base_t eventBase, int32_t eventId, void *eventData) @@ -44,14 +385,15 @@ void hostedEventHandler(void *arg, esp_event_base_t eventBase, int32_t eventId, switch (eventId) { case ESP_HOSTED_EVENT_TRANSPORT_UP: - ESP_LOGI(kTag, "ESP-Hosted transport is up"); + LOG_INFO("ESP-Hosted transport is up"); self->setConnected(true); break; case ESP_HOSTED_EVENT_TRANSPORT_DOWN: - ESP_LOGW(kTag, "ESP-Hosted transport is down"); + LOG_WARN("ESP-Hosted transport is down"); + [[fallthrough]]; case ESP_HOSTED_EVENT_TRANSPORT_FAILURE: if (eventId == ESP_HOSTED_EVENT_TRANSPORT_FAILURE) { - ESP_LOGE(kTag, "ESP-Hosted transport failure"); + LOG_ERROR("ESP-Hosted transport failure"); } self->setConnected(false); break; @@ -82,21 +424,13 @@ bool HostedBluetooth::registerCallbacks() 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)); + LOG_ERROR("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"); + LOG_WARN("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; } @@ -107,12 +441,8 @@ void HostedBluetooth::unregisterCallbacks() 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; } @@ -122,45 +452,22 @@ void HostedBluetooth::setup() return; } + // Ensure co-processor is released from reset before bringing transport back up. + setSlaveResetLine(false); + LOG_DEBUG("[HostedBluetooth] setup(): Released co-processor from reset"); + 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; + active = setupGatt(); firstRssiLogged.store(false); - ESP_LOGI(kTag, "ESP-Hosted Bluetooth ready"); + if (active) { + LOG_INFO("ESP-Hosted Bluetooth ready"); + } else { + LOG_ERROR("Hosted BLE setup failed in hosted mode"); + deinit(); + } } void HostedBluetooth::shutdown() @@ -174,20 +481,15 @@ void HostedBluetooth::deinit() 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)); + shutdownGatt(); + if (BLEDevice::getInitialized()) { + BLEDevice::deinit(false); + } else { + hostedDeinitBLE(); } - 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); - } + // Hold co-processor in reset when hosted transport is disabled to reduce idle draw. + setSlaveResetLine(true); unregisterCallbacks(); @@ -199,14 +501,8 @@ void HostedBluetooth::deinit() 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)); - } + ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_PEER_SEC, nullptr); + ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_CCCD, nullptr); } bool HostedBluetooth::isActive() @@ -216,30 +512,48 @@ bool HostedBluetooth::isActive() bool HostedBluetooth::isConnected() { - return connected.load(); + if (!connected.load()) { + return false; + } + return bleServer && bleServer->getConnectedCount() > 0; } 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)); + if (!bleServer || !isConnected()) { + return 0; + } + + uint16_t connHandle = hostedConnHandle.load(); + if (connHandle == BLE_HS_CONN_HANDLE_NONE) { + const auto peers = bleServer->getPeerDevices(true); + if (!peers.empty()) { + connHandle = peers.begin()->first; + hostedConnHandle = connHandle; } } + if (connHandle == BLE_HS_CONN_HANDLE_NONE) { + return 0; + } + + int8_t currentRssi = 0; + const int rc = ble_gap_conn_rssi(connHandle, ¤tRssi); + if (rc == 0) { + setRssi(currentRssi); + return currentRssi; + } + return rssi.load(); } void HostedBluetooth::sendLog(const uint8_t *logMessage, size_t length) { - if (!active || !isConnected() || !logMessage || length == 0) { + if (!logRadioCharacteristic || !isConnected() || length > 512) { 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)); - } + logRadioCharacteristic->setValue(logMessage, length); + logRadioCharacteristic->notify(); } void HostedBluetooth::setConnected(bool value) @@ -257,7 +571,176 @@ 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); + LOG_INFO("ESP-Hosted first RSSI update: %d dBm", value); + } +} + +bool HostedBluetooth::setupGatt() +{ + memset(lastToRadio, 0, sizeof(lastToRadio)); + hostedConnHandle = BLE_HS_CONN_HANDLE_NONE; + + if (!BLEDevice::init(getDeviceName())) { + LOG_ERROR("Hosted BLE init failed"); + return false; + } +#ifdef ESP_PWR_LVL_P9 + BLEDevice::setPower(ESP_PWR_LVL_P9); +#endif + + BLESecurity *security = new BLESecurity(); + security->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + security->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { + security->setCapability(ESP_IO_CAP_OUT); + if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN) { + LOG_INFO("Hosted BLE using random passkey"); + security->setPassKey(false); + } else { + LOG_INFO("Hosted BLE using fixed passkey"); + security->setPassKey(true, config.bluetooth.fixed_pin); + } + // Enable authorization requirements: + // - bonding: true (for persistent storage of the keys) + // - MITM: true (enables Man-In-The-Middle protection for password prompts) + // - secure connection: true (enables secure connection for encryption) + security->setAuthenticationMode(true, true, true); + } else { + security->setCapability(ESP_IO_CAP_NONE); + security->setAuthenticationMode(true, false, false); + } + + if (!securityCallbacks) { + securityCallbacks = new HostedBluetoothSecurityCallback(); + } + BLEDevice::setSecurityCallbacks(securityCallbacks); + + const int mtuResult = BLEDevice::setMTU(kPreferredBleMtu); + if (mtuResult == 0) { + LOG_INFO("Hosted BLE MTU request set to %u", kPreferredBleMtu); + } else { + LOG_WARN("Hosted BLE unable to request MTU %u, rc=%d", kPreferredBleMtu, mtuResult); + } + + bleServer = BLEDevice::createServer(); + if (!bleServer) { + LOG_ERROR("Hosted BLE createServer failed"); + return false; + } + + const int nameRc = ble_svc_gap_device_name_set(BLEDevice::getDeviceName().c_str()); + if (nameRc != 0) { + LOG_WARN("Hosted BLE device_name_set rc=%d %s", nameRc, BLEUtils::returnCodeToString(nameRc)); + } + + bleServer->setCallbacks(new HostedBluetoothServerCallback(this)); + + BLEService *meshService = bleServer->createService(MESH_SERVICE_UUID); + if (!meshService) { + LOG_ERROR("Hosted BLE mesh service creation failed"); + return false; + } + + BLECharacteristic *toRadioCharacteristic = nullptr; + BLECharacteristic *fromRadioCharacteristic = nullptr; + if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { + toRadioCharacteristic = meshService->createCharacteristic(TORADIO_UUID, BLECharacteristic::PROPERTY_WRITE); + fromRadioCharacteristic = meshService->createCharacteristic(FROMRADIO_UUID, BLECharacteristic::PROPERTY_READ); + fromNumCharacteristic = meshService->createCharacteristic(FROMNUM_UUID, BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_READ); + logRadioCharacteristic = meshService->createCharacteristic(LOGRADIO_UUID, BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_READ); + } else { + toRadioCharacteristic = meshService->createCharacteristic(TORADIO_UUID, BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_WRITE_AUTHEN | + BLECharacteristic::PROPERTY_WRITE_ENC); + fromRadioCharacteristic = meshService->createCharacteristic(FROMRADIO_UUID, BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_READ_AUTHEN | + BLECharacteristic::PROPERTY_READ_ENC); + fromNumCharacteristic = meshService->createCharacteristic( + FROMNUM_UUID, BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_READ_ENC); + logRadioCharacteristic = meshService->createCharacteristic( + LOGRADIO_UUID, BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_READ_ENC); + } + + if (!toRadioCharacteristic || !fromRadioCharacteristic || !fromNumCharacteristic || !logRadioCharacteristic) { + LOG_ERROR("Hosted BLE characteristic creation failed"); + return false; + } + + if (!bluetoothPhoneAPI) { + bluetoothPhoneAPI = new HostedBluetoothPhoneAPI(); + } + + if (!toRadioCallbacks) { + toRadioCallbacks = new HostedBluetoothToRadioCallback(); + } + if (!fromRadioCallbacks) { + fromRadioCallbacks = new HostedBluetoothFromRadioCallback(); + } + + toRadioCharacteristic->setCallbacks(toRadioCallbacks); + fromRadioCharacteristic->setCallbacks(fromRadioCallbacks); + meshService->start(); + + startAdvertising(); + return true; +} + +void HostedBluetooth::shutdownGatt() +{ + if (bluetoothPhoneAPI) { + bluetoothPhoneAPI->close(); + delete bluetoothPhoneAPI; + bluetoothPhoneAPI = nullptr; + } + + delete toRadioCallbacks; + toRadioCallbacks = nullptr; + + delete fromRadioCallbacks; + fromRadioCallbacks = nullptr; + + delete securityCallbacks; + securityCallbacks = nullptr; + + if (bleServer) { + BLEAdvertising *advertising = BLEDevice::getAdvertising(); + if (advertising) { + advertising->stop(); + } + } + + fromNumCharacteristic = nullptr; + logRadioCharacteristic = nullptr; + bleServer = nullptr; + hostedConnHandle = BLE_HS_CONN_HANDLE_NONE; +} + +void HostedBluetooth::startAdvertising() +{ + BLEAdvertising *advertising = BLEDevice::getAdvertising(); + if (!advertising) { + LOG_ERROR("Hosted BLE getAdvertising failed"); + return; + } + + advertising->stop(); + advertising->reset(); + advertising->addServiceUUID(MESH_SERVICE_UUID); + + BLEAdvertisementData scan; + scan.setName(getDeviceName()); + advertising->setScanResponseData(scan); + advertising->setMinPreferred(0x06); + advertising->setMaxPreferred(0x12); + + if (!advertising->start(0)) { + LOG_ERROR("Hosted BLE failed to start advertising"); + } else { + LOG_INFO("Hosted BLE advertising started"); } } diff --git a/src/bluetooth/HostedBluetooth.h b/src/bluetooth/HostedBluetooth.h index 7803c383d..288721742 100644 --- a/src/bluetooth/HostedBluetooth.h +++ b/src/bluetooth/HostedBluetooth.h @@ -23,11 +23,14 @@ class HostedBluetooth : public BluetoothApi bool isConnected() override; int getRssi() override; void sendLog(const uint8_t *logMessage, size_t length) override; + void startAdvertising(); void setConnected(bool value); void setRssi(int value); private: + bool setupGatt(); + void shutdownGatt(); void maybeLogFirstRssi(int value); bool registerCallbacks(); void unregisterCallbacks(); diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 2f39c11f5..c45e0cdcd 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -43,6 +43,16 @@ void setBluetoothEnable(bool enable) if (config.bluetooth.enabled == true) #endif { +#if defined(CONFIG_IDF_TARGET_ESP32P4) + if (!enable) { + if (bluetoothApi && bluetoothApi->isActive()) { + bluetoothApi->shutdown(); + powerMon->clearState(meshtastic_PowerMon_State_BT_On); + } + return; + } +#endif + if (!bluetoothApi) { #if defined(CONFIG_IDF_TARGET_ESP32P4) bluetoothApi = new HostedBluetooth();