mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-15 16:01:19 +10:00
add class HostedBluetooth
This commit is contained in:
@@ -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 <BLEAdvertising.h>
|
||||
#include <BLEDevice.h>
|
||||
#include <BLESecurity.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
#include <driver/gpio.h>
|
||||
#include <mutex>
|
||||
|
||||
#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<uint16_t> 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<int>(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<size_t> fromPhoneQueueSize{0};
|
||||
std::array<BLEValue, kBluetoothFromPhoneQueueSize> fromPhoneQueue{};
|
||||
|
||||
std::mutex toPhoneMutex;
|
||||
std::atomic<size_t> toPhoneQueueSize{0};
|
||||
std::array<std::array<uint8_t, meshtastic_FromRadio_size>, kBluetoothToPhoneQueueSize> toPhoneQueue{};
|
||||
std::array<size_t, kBluetoothToPhoneQueueSize> toPhoneQueueByteSizes{};
|
||||
std::atomic<bool> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> guard(bluetoothPhoneAPI->fromPhoneMutex);
|
||||
bluetoothPhoneAPI->fromPhoneQueueSize = 0;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<gpio_num_t>(CONFIG_ESP_HOSTED_GPIO_SLAVE_RESET_SLAVE);
|
||||
#elif defined(CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE)
|
||||
return static_cast<gpio_num_t>(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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user