15static const char*
TAG =
"BT-Ctrl";
18#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ENABLED)
22#include "nimble/nimble_port.h"
23#include "nimble/nimble_port_freertos.h"
24#include "host/ble_hs.h"
25#include "host/util/util.h"
26#include "services/gap/ble_svc_gap.h"
27#include "services/gatt/ble_svc_gatt.h"
28#include "host/ble_uuid.h"
29#include "host/ble_att.h"
30#include "host/util/util.h"
31#include "host/ble_store.h"
32#include "store/config/ble_store_config.h"
35extern "C" void ble_store_config_init(
void);
36#include "freertos/FreeRTOS.h"
37#include "freertos/semphr.h"
46static void bleHostTask(
void* param);
51static int gattcSvcDiscCb(uint16_t connHandle,
const struct ble_gatt_error* error,
52 const struct ble_gatt_svc* service,
void* arg);
53static int gattcChrDiscCb(uint16_t connHandle,
const struct ble_gatt_error* error,
54 const struct ble_gatt_chr* chr,
void* arg);
55static int gattcReadCb(uint16_t connHandle,
const struct ble_gatt_error* error,
56 struct ble_gatt_attr* attr,
void* arg);
57static int gattcWriteCb(uint16_t connHandle,
const struct ble_gatt_error* error,
58 struct ble_gatt_attr* attr,
void* arg);
59static int gattcDscDiscCb(uint16_t connHandle,
const struct ble_gatt_error* error,
60 uint16_t chr_val_handle,
const struct ble_gatt_dsc* dsc,
void* arg);
68static constexpr uint8_t PLUGIN_SERVICE_SLOT = MAX_REGISTERED_SERVICES - 1;
70static constexpr uint8_t MAX_DESCRIPTORS_PER_CHAR = 2;
71static constexpr uint8_t MAX_ADV_UUIDS = 4;
72static constexpr uint8_t MAX_CONN_CALLBACKS = 6;
73static constexpr uint8_t MAX_CONNECTIONS = 2;
74static constexpr uint8_t MAX_SUBSCRIBE_ENTRIES = 16;
75static constexpr uint8_t MAX_BONDS = 5;
78static constexpr uint32_t kConnectSettleMs = 50;
80static constexpr uint32_t kDisconnectDrainPollMs = 20;
82static constexpr uint32_t kDisconnectDrainTimeoutMs = 1000;
87struct InternalService {
91 ble_uuid_any_t svcUuid;
92 ble_uuid_any_t charUuids[MAX_CHARS_PER_SERVICE];
95 ble_gatt_chr_def nimbleChars[MAX_CHARS_PER_SERVICE + 1];
96 ble_gatt_svc_def nimbleSvcs[2];
100 ble_uuid_any_t dscUuids[MAX_CHARS_PER_SERVICE][MAX_DESCRIPTORS_PER_CHAR];
101 ble_gatt_dsc_def dscDefs[MAX_CHARS_PER_SERVICE][MAX_DESCRIPTORS_PER_CHAR + 1];
102 uint8_t dscPacked[MAX_CHARS_PER_SERVICE][MAX_DESCRIPTORS_PER_CHAR][5];
105 GattWriteCallback writeCallbacks[MAX_CHARS_PER_SERVICE];
106 GattReadCallback readCallbacks[MAX_CHARS_PER_SERVICE];
107 uint8_t numChars = 0;
110EXT_RAM_BSS_ATTR
static InternalService s_services[MAX_REGISTERED_SERVICES];
118static void convertUuid(
const BleUuid& src, ble_uuid_any_t& dst) {
120 dst.u.type = BLE_UUID_TYPE_16;
121 dst.u16.u.type = BLE_UUID_TYPE_16;
122 dst.u16.value = src.
u16;
124 dst.u.type = BLE_UUID_TYPE_128;
125 dst.u128.u.type = BLE_UUID_TYPE_128;
126 memcpy(dst.u128.value, src.
u128, 16);
136static ble_gatt_chr_flags mapProperties(uint8_t props, uint8_t perms) {
137 ble_gatt_chr_flags
flags = 0;
166EXT_RAM_BSS_ATTR
static uint8_t s_gattAccessBuf[512];
168static int gattServiceAccessCb(uint16_t connHandle, uint16_t attrHandle,
169 struct ble_gatt_access_ctxt* ctxt,
void* arg) {
170 auto* svc =
static_cast<InternalService*
>(arg);
171 if (!svc)
return BLE_ATT_ERR_UNLIKELY;
174 for (uint8_t i = 0; i < svc->numChars; i++) {
175 if (ble_uuid_cmp(ctxt->chr->uuid, &svc->charUuids[i].u) == 0) {
176 if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR && svc->writeCallbacks[i]) {
177 uint16_t len = OS_MBUF_PKTLEN(ctxt->om);
178 if (len >
sizeof(s_gattAccessBuf)) len =
sizeof(s_gattAccessBuf);
179 ble_hs_mbuf_to_flat(ctxt->om, s_gattAccessBuf, len,
nullptr);
180 return svc->writeCallbacks[i](connHandle, attrHandle, s_gattAccessBuf, len);
182 if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR && svc->readCallbacks[i]) {
183 uint16_t len =
sizeof(s_gattAccessBuf);
184 int rc = svc->readCallbacks[i](connHandle, attrHandle, s_gattAccessBuf, &len);
185 if (rc == 0 && len > 0) {
186 os_mbuf_append(ctxt->om, s_gattAccessBuf, len);
194 return BLE_ATT_ERR_UNLIKELY;
204static int gattStaticDescriptorAccessCb(uint16_t connHandle, uint16_t attrHandle,
205 struct ble_gatt_access_ctxt* ctxt,
void* arg) {
208 if (ctxt->op != BLE_GATT_ACCESS_OP_READ_DSC)
return BLE_ATT_ERR_UNLIKELY;
209 if (!arg)
return BLE_ATT_ERR_UNLIKELY;
212 const uint8_t* packed =
static_cast<const uint8_t*
>(arg);
213 uint8_t len = packed[0];
214 return os_mbuf_append(ctxt->om, packed + 1, len) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
222 BluetoothController() {
224 lifecycleMutex_ = xSemaphoreCreateRecursiveMutex();
231 bool init()
override;
232 bool start()
override;
233 void stop()
override;
234 core::ServiceState getState()
const override {
return state_; }
235 const char* getName()
const override {
return "bluetooth"; }
242 bool enable()
override;
243 void disable()
override;
244 void notifySystemReady()
override;
245 bool isEnabled()
const override {
return enabled_; }
246 bool getMacAddress(uint8_t* mac)
const override;
247 void setDeviceName(
const char*
name)
override;
248 const char* getDeviceName()
const override {
return deviceName_; }
249 bool isConnected()
const override;
250 void disconnect()
override;
251 int8_t getRssi()
const override;
257 void startAdvertising()
override;
258 void stopAdvertising()
override;
259 bool isAdvertising()
const override {
return ble_gap_adv_active() != 0; }
260 bool addAdvertisingUuid(
const BleUuid& uuid)
override;
261 void removeAdvertisingUuid(
const BleUuid& uuid)
override;
262 bool setAdvertisingManufacturerData(uint16_t companyId,
263 const uint8_t* data, uint16_t len)
override;
264 void clearAdvertisingManufacturerData()
override;
265 void setAppearance(uint16_t appearance)
override;
272 bool startScan(uint32_t durationMs,
bool keepAdvertising)
override;
273 void stopScan()
override;
274 bool isScanComplete()
const override {
return !scanning_; }
275 uint8_t getScanResults(BleScanResult* results, uint8_t maxResults)
override;
282 bool registerGattService(
const GattServiceDef& service,
283 bool pluginReserved =
false)
override;
284 bool unregisterGattService(
const BleUuid& serviceUuid)
override;
285 bool sendNotification(uint16_t connHandle, uint16_t attrHandle,
286 const uint8_t* data, uint16_t len)
override;
287 uint16_t getMtu()
const override;
288 uint16_t getConnectionHandle()
const override {
return primaryConnHandle(); }
289 void clearAllBonds()
override;
296 bool connect(
const uint8_t* addr, uint8_t addrType)
override;
297 void cancelConnect()
override;
298 bool discoverServiceByUuid(uint16_t connHandle,
const BleUuid& uuid)
override;
299 bool writeCharacteristic(uint16_t connHandle, uint16_t attrHandle,
300 const uint8_t* data, uint16_t len,
301 bool withResponse)
override;
302 bool readCharacteristic(uint16_t connHandle, uint16_t attrHandle)
override;
303 bool enableNotifications(uint16_t connHandle, uint16_t cccdHandle)
override;
304 bool subscribeToCharacteristic(uint16_t connHandle, uint16_t valueHandle)
override;
305 void disconnectHandle(uint16_t connHandle)
override;
306 ListenerToken addServiceDiscoveryCallback(ServiceDiscoveryCallback cb)
override;
307 ListenerToken addCharacteristicReadCallback(CharacteristicReadCallback cb)
override;
308 ListenerToken addNotificationCallback(NotificationCallback cb)
override;
309 ListenerToken addWriteCompleteCallback(WriteCompleteCallback cb)
override;
310 void removeServiceDiscoveryCallback(ListenerToken token)
override;
311 void removeCharacteristicReadCallback(ListenerToken token)
override;
312 void removeNotificationCallback(ListenerToken token)
override;
313 void removeWriteCompleteCallback(ListenerToken token)
override;
314 void setServiceDiscoveryCallback(ServiceDiscoveryCallback cb)
override;
315 void setCharacteristicReadCallback(CharacteristicReadCallback cb)
override;
316 void setNotificationCallback(NotificationCallback cb)
override;
317 void setWriteCompleteCallback(WriteCompleteCallback cb)
override;
324 ListenerToken addConnectionCallback(ConnectionCallback cb)
override;
325 ListenerToken addDisconnectionCallback(DisconnectionCallback cb)
override;
326 void removeConnectionCallback(ListenerToken token)
override;
327 void removeDisconnectionCallback(ListenerToken token)
override;
334 ListenerToken addNumericComparisonCallback(NumericComparisonCallback cb)
override;
335 void removeNumericComparisonCallback(ListenerToken token)
override;
336 void setNumericComparisonCallback(NumericComparisonCallback cb)
override;
337 void respondToNumericComparison(uint16_t connHandle,
bool accept)
override;
338 void setPasskeyCallback(PasskeyCallback cb)
override;
339 void setAuthCompleteCallback(AuthCompleteCallback cb)
override;
340 ListenerToken addEncryptionChangeCallback(EncChangeCallback cb)
override;
341 void removeEncryptionChangeCallback(ListenerToken token)
override;
342 bool initiateSecurity(uint16_t connHandle)
override;
343 bool getPeerIdAddr(uint16_t connHandle, uint8_t addr[6], uint8_t* addrType)
const override;
344 void forgetBond(
const uint8_t addr[6], uint8_t addrType)
override;
345 uint8_t getBondedDevices(BleBondInfo* out, uint8_t maxCount)
const override;
352 void onConnect(uint16_t connHandle,
bool isPeripheral);
353 void onDisconnect(uint16_t connHandle,
int reason);
355 void onScanResult(
const ble_gap_disc_desc* disc);
356 void onScanComplete();
357 void onAdvComplete();
358 void onPasskeyAction(uint16_t connHandle,
const ble_gap_passkey_params* params);
359 void onEncChange(uint16_t connHandle,
int status);
360 void onSubscribe(
const struct ble_gap_event* event);
361 void onMtuExchange(uint16_t connHandle, uint16_t mtu);
367 template <
typename CB>
368 struct ListenerSlot {
376 struct ConnectionState {
378 uint16_t handle = BLE_HS_CONN_HANDLE_NONE;
379 bool isPeripheral =
false;
386 struct SubscribeEntry {
394 uint16_t primaryConnHandle()
const;
395 int8_t findConnectionSlot(uint16_t handle)
const;
401 core::ServiceState state_ = core::ServiceState::UNINITIALIZED;
408 SemaphoreHandle_t lifecycleMutex_ =
nullptr;
410 bool enabled_ =
false;
411 bool synced_ =
false;
412 bool systemReady_ =
false;
413 bool pendingEnable_ =
false;
414 bool advertising_ =
false;
415 bool scanning_ =
false;
416 bool scanWasAdvertising_ =
false;
417 char deviceName_[32] =
"CDC Badge";
418 uint8_t ownAddrType_ = BLE_OWN_ADDR_PUBLIC;
421 ConnectionState connections_[MAX_CONNECTIONS] = {};
424 SubscribeEntry subscribes_[MAX_SUBSCRIBE_ENTRIES] = {};
427 BleScanResult scanResults_[MAX_SCAN_RESULTS] = {};
430 bool scanNameComplete_[MAX_SCAN_RESULTS] = {};
431 uint8_t scanResultCount_ = 0;
434 BleUuid advUuids_[MAX_ADV_UUIDS] = {};
435 uint8_t advUuidCount_ = 0;
438 uint16_t appearance_ = 0;
440 ListenerSlot<ConnectionCallback> connCallbacks_[MAX_CONN_CALLBACKS] = {};
441 ListenerSlot<DisconnectionCallback> disconnCallbacks_[MAX_CONN_CALLBACKS] = {};
442 ListenerSlot<NumericComparisonCallback> numCmpCallbacks_[MAX_CONN_CALLBACKS] = {};
443 ListenerSlot<ServiceDiscoveryCallback> svcDiscoveryCallbacks_[MAX_CONN_CALLBACKS] = {};
444 ListenerSlot<CharacteristicReadCallback> charReadCallbacks_[MAX_CONN_CALLBACKS] = {};
445 ListenerSlot<NotificationCallback> notifyCallbacks_[MAX_CONN_CALLBACKS] = {};
446 ListenerSlot<WriteCompleteCallback> writeCompleteCallbacks_[MAX_CONN_CALLBACKS] = {};
447 ListenerSlot<EncChangeCallback> encChangeCallbacks_[MAX_CONN_CALLBACKS] = {};
450 PasskeyCallback passkeyCb_;
451 AuthCompleteCallback authCompleteCb_;
454 DiscoveredService discoveredSvc_ = {};
455 BleUuid discoverTargetUuid_ = {};
456 uint16_t discoverSvcStart_ = 0;
457 uint16_t discoverSvcEnd_ = 0;
460 uint16_t pendingSubValueHandle_ = 0;
461 uint16_t pendingSubCccdHandle_ = 0;
464 uint8_t mfgData_[31] = {};
465 uint16_t mfgDataLen_ = 0;
466 uint16_t mfgCompanyId_ = 0;
467 bool mfgDataSet_ =
false;
471 static BluetoothController* instance_;
472 friend void bleHostTask(
void* param);
473 friend int bleGapEventCallback(
struct ble_gap_event* event,
void* arg);
474 friend int gattcSvcDiscCb(uint16_t,
const struct ble_gatt_error*,
475 const struct ble_gatt_svc*,
void*);
476 friend int gattcChrDiscCb(uint16_t,
const struct ble_gatt_error*,
477 const struct ble_gatt_chr*,
void*);
478 friend int gattcReadCb(uint16_t,
const struct ble_gatt_error*,
479 struct ble_gatt_attr*,
void*);
480 friend int gattcWriteCb(uint16_t,
const struct ble_gatt_error*,
481 struct ble_gatt_attr*,
void*);
482 friend int gattcDscDiscCb(uint16_t,
const struct ble_gatt_error*,
483 uint16_t,
const struct ble_gatt_dsc*,
void*);
489BluetoothController* BluetoothController::instance_ =
nullptr;
497int bleGapEventCallback(
struct ble_gap_event* event,
void* arg) {
499 auto* ctrl = BluetoothController::instance_;
502 switch (event->type) {
503 case BLE_GAP_EVENT_CONNECT:
504 if (event->connect.status == 0) {
505 struct ble_gap_conn_desc desc;
506 bool isPeripheral =
true;
507 if (ble_gap_conn_find(event->connect.conn_handle, &desc) == 0) {
508 isPeripheral = (desc.role == BLE_GAP_ROLE_SLAVE);
510 ctrl->onConnect(event->connect.conn_handle, isPeripheral);
512 LOG_W(
TAG,
"Connection failed, status=%d", event->connect.status);
516 case BLE_GAP_EVENT_DISCONNECT:
517 ctrl->onDisconnect(event->disconnect.conn.conn_handle,
518 event->disconnect.reason);
521 case BLE_GAP_EVENT_CONN_UPDATE:
525 case BLE_GAP_EVENT_ADV_COMPLETE:
527 ctrl->onAdvComplete();
530 case BLE_GAP_EVENT_MTU:
531 LOG_I(
TAG,
"MTU updated: handle=%d value=%d",
532 event->mtu.conn_handle, event->mtu.value);
533 ctrl->onMtuExchange(event->mtu.conn_handle, event->mtu.value);
536 case BLE_GAP_EVENT_DISC:
537 ctrl->onScanResult(&event->disc);
540 case BLE_GAP_EVENT_DISC_COMPLETE:
542 ctrl->onScanComplete();
545 case BLE_GAP_EVENT_PASSKEY_ACTION:
546 ctrl->onPasskeyAction(event->passkey.conn_handle,
547 &event->passkey.params);
550 case BLE_GAP_EVENT_ENC_CHANGE:
551 ctrl->onEncChange(event->enc_change.conn_handle,
552 event->enc_change.status);
555 case BLE_GAP_EVENT_REPEAT_PAIRING: {
557 struct ble_gap_conn_desc desc;
558 if (ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc) == 0) {
559 ble_store_util_delete_peer(&desc.peer_id_addr);
561 return BLE_GAP_REPEAT_PAIRING_RETRY;
564 case BLE_GAP_EVENT_SUBSCRIBE:
565 ctrl->onSubscribe(event);
568 case BLE_GAP_EVENT_NOTIFY_RX:
569 if (event->notify_rx.om) {
570 uint16_t len = OS_MBUF_PKTLEN(event->notify_rx.om);
571 if (len >
sizeof(s_gattAccessBuf)) len =
sizeof(s_gattAccessBuf);
572 ble_hs_mbuf_to_flat(event->notify_rx.om, s_gattAccessBuf, len,
nullptr);
573 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
574 if (ctrl->notifyCallbacks_[i].active &&
575 ctrl->notifyCallbacks_[i].callback) {
576 ctrl->notifyCallbacks_[i].callback(
577 event->notify_rx.conn_handle,
578 event->notify_rx.attr_handle,
579 s_gattAccessBuf, len);
596static void bleSyncCallback() {
597 if (BluetoothController::instance_) {
598 BluetoothController::instance_->onSync();
607static void bleResetCallback(
int reason) {
608 LOG_E(
TAG,
"BLE host reset, reason=%d", reason);
616static void bleHostTask(
void* param) {
618 LOG_I(
TAG,
"NimBLE host task started");
620 nimble_port_freertos_deinit();
627bool BluetoothController::init() {
638 ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
641 LOG_I(
TAG,
"Bluetooth controller initialized");
649bool BluetoothController::start() {
661void BluetoothController::stop() {
662 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
680bool BluetoothController::enable() {
681 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
686 pendingEnable_ =
true;
687 LOG_I(
TAG,
"BLE enable deferred until system ready");
696void BluetoothController::notifySystemReady() {
697 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
699 if (pendingEnable_ && !enabled_) {
700 pendingEnable_ =
false;
709bool BluetoothController::enableNow() {
710 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
716 LOG_E(
TAG,
"Cannot enable - service not started");
721 esp_err_t ret = nimble_port_init();
723 LOG_E(
TAG,
"nimble_port_init failed: %d", ret);
728 ble_hs_cfg.reset_cb = bleResetCallback;
729 ble_hs_cfg.sync_cb = bleSyncCallback;
730 ble_hs_cfg.gatts_register_cb =
nullptr;
731 ble_hs_cfg.store_status_cb =
nullptr;
734 ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_YES_NO;
735 ble_hs_cfg.sm_bonding = 1;
736 ble_hs_cfg.sm_mitm = 1;
737 ble_hs_cfg.sm_sc = 1;
738 ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
739 ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
747 for (
int i = 0; i < MAX_REGISTERED_SERVICES; i++) {
748 if (!s_services[i].active)
continue;
749 int grc = ble_gatts_count_cfg(s_services[i].nimbleSvcs);
750 if (grc == 0) grc = ble_gatts_add_svcs(s_services[i].nimbleSvcs);
752 LOG_E(
TAG,
"Deferred GATT service slot %d commit failed: %d", i, grc);
754 LOG_I(
TAG,
"Committed GATT service slot %d", i);
759 ble_store_config_init();
762 ble_svc_gap_device_name_set(deviceName_);
765 nimble_port_freertos_init(bleHostTask);
775void BluetoothController::disable() {
776 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
789 advertising_ =
false;
795 ble_gap_disc_cancel();
797 scanWasAdvertising_ =
false;
799 ble_gap_conn_cancel();
807 vTaskDelay(pdMS_TO_TICKS(kConnectSettleMs));
809 for (;; waited += kDisconnectDrainPollMs) {
810 bool anyActive =
false;
811 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) {
812 if (connections_[i].active) {
813 ble_gap_terminate(connections_[i].handle, BLE_ERR_REM_USER_CONN_TERM);
817 if (!anyActive)
break;
818 if (waited >= kDisconnectDrainTimeoutMs) {
819 LOG_W(
TAG,
"Disconnect drain timed out, forcing BLE shutdown");
822 vTaskDelay(pdMS_TO_TICKS(kDisconnectDrainPollMs));
826 int rc = nimble_port_stop();
829 vTaskDelay(pdMS_TO_TICKS(50));
830 nimble_port_deinit();
833 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) connections_[i].active =
false;
834 for (uint8_t i = 0; i < MAX_SUBSCRIBE_ENTRIES; i++) subscribes_[i].active =
false;
844bool BluetoothController::getMacAddress(uint8_t* mac)
const {
845 if (!mac)
return false;
847 if (enabled_ && synced_) {
849 int rc = ble_hs_id_copy_addr(ownAddrType_, mac,
nullptr);
854 esp_read_mac(mac, ESP_MAC_BT);
862void BluetoothController::setDeviceName(
const char*
name) {
865 strncpy(deviceName_,
name,
sizeof(deviceName_) - 1);
866 deviceName_[
sizeof(deviceName_) - 1] =
'\0';
868 if (enabled_ && synced_) {
869 ble_svc_gap_device_name_set(deviceName_);
876void BluetoothController::disconnect() {
877 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) {
878 if (connections_[i].active) {
879 ble_gap_terminate(connections_[i].handle, BLE_ERR_REM_USER_CONN_TERM);
884bool BluetoothController::isConnected()
const {
885 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) {
886 if (connections_[i].active)
return true;
891uint16_t BluetoothController::primaryConnHandle()
const {
892 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) {
893 if (connections_[i].active)
return connections_[i].handle;
895 return BLE_HS_CONN_HANDLE_NONE;
898int8_t BluetoothController::findConnectionSlot(uint16_t handle)
const {
899 for (int8_t i = 0; i < (int8_t)MAX_CONNECTIONS; i++) {
900 if (connections_[i].active && connections_[i].handle == handle)
return i;
905void BluetoothController::clearAllBonds() {
906 int rc = ble_store_clear();
908 LOG_E(
TAG,
"ble_store_clear failed: %d", rc);
914void BluetoothController::onEncChange(uint16_t connHandle,
int status) {
915 LOG_I(
TAG,
"Encryption %s on handle %d (status=%d)",
916 status == 0 ?
"established" :
"failed", connHandle, status);
917 if (authCompleteCb_) {
918 authCompleteCb_(status == 0);
920 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
921 if (encChangeCallbacks_[i].active && encChangeCallbacks_[i].callback) {
922 encChangeCallbacks_[i].callback(connHandle, status);
927void BluetoothController::onMtuExchange(uint16_t connHandle, uint16_t mtu) {
928 int8_t slot = findConnectionSlot(connHandle);
930 connections_[slot].mtu = mtu;
934void BluetoothController::onSubscribe(
const struct ble_gap_event* event) {
936 LOG_I(
TAG,
"Subscribe: handle=%d attr=%d notify=%d indicate=%d",
937 event->subscribe.conn_handle, event->subscribe.attr_handle,
938 event->subscribe.cur_notify, event->subscribe.cur_indicate);
942 for (
int i = 0; i < MAX_SUBSCRIBE_ENTRIES; i++) {
943 if (subscribes_[i].active &&
944 subscribes_[i].connHandle == event->subscribe.conn_handle &&
945 subscribes_[i].attrHandle == event->subscribe.attr_handle) {
951 for (
int i = 0; i < MAX_SUBSCRIBE_ENTRIES; i++) {
952 if (!subscribes_[i].active) { slot = i;
break; }
959 subscribes_[slot].active =
true;
960 subscribes_[slot].connHandle =
event->subscribe.conn_handle;
961 subscribes_[slot].attrHandle =
event->subscribe.attr_handle;
962 subscribes_[slot].notify =
event->subscribe.cur_notify != 0;
963 subscribes_[slot].indicate =
event->subscribe.cur_indicate != 0;
964 if (!subscribes_[slot].notify && !subscribes_[slot].indicate) {
965 subscribes_[slot].active =
false;
973int8_t BluetoothController::getRssi()
const {
974 uint16_t handle = primaryConnHandle();
975 if (handle == BLE_HS_CONN_HANDLE_NONE) {
980 int rc = ble_gap_conn_rssi(handle, &rssi);
981 return (rc == 0) ? rssi : 0;
988void BluetoothController::onConnect(uint16_t connHandle,
bool isPeripheral) {
989 advertising_ =
false;
990 LOG_I(
TAG,
"Device connected (handle=%d, role=%s)",
991 connHandle, isPeripheral ?
"peripheral" :
"central");
995 for (
int i = 0; i < MAX_CONNECTIONS; i++) {
996 if (!connections_[i].active) { slot = i;
break; }
999 connections_[slot].active =
true;
1000 connections_[slot].handle = connHandle;
1001 connections_[slot].isPeripheral = isPeripheral;
1002 connections_[slot].mtu = 23;
1004 LOG_W(
TAG,
"Connection table full, dropping handle %d", connHandle);
1011 if (!isPeripheral) {
1012 ble_gattc_exchange_mtu(connHandle,
nullptr,
nullptr);
1016 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
1017 if (connCallbacks_[i].active && connCallbacks_[i].callback) {
1018 connCallbacks_[i].callback(connHandle);
1028void BluetoothController::onDisconnect(uint16_t connHandle,
int reason) {
1029 LOG_I(
TAG,
"Device disconnected (handle=%d reason=%d)", connHandle, reason);
1031 bool wasPeripheral =
true;
1032 int8_t slot = findConnectionSlot(connHandle);
1034 wasPeripheral = connections_[slot].isPeripheral;
1035 connections_[slot].active =
false;
1039 for (uint8_t i = 0; i < MAX_SUBSCRIBE_ENTRIES; i++) {
1040 if (subscribes_[i].active && subscribes_[i].connHandle == connHandle) {
1041 subscribes_[i].active =
false;
1046 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
1047 if (disconnCallbacks_[i].active && disconnCallbacks_[i].callback) {
1048 disconnCallbacks_[i].callback(connHandle, reason);
1053 advertising_ =
false;
1054 if (wasPeripheral) {
1062void BluetoothController::onSync() {
1066 int rc = ble_hs_util_ensure_addr(0);
1068 LOG_E(
TAG,
"Failed to ensure address: %d", rc);
1072 rc = ble_hs_id_infer_auto(0, &ownAddrType_);
1074 LOG_E(
TAG,
"Failed to infer address type: %d", rc);
1075 ownAddrType_ = BLE_OWN_ADDR_PUBLIC;
1079 ble_hs_id_copy_addr(ownAddrType_, addr,
nullptr);
1080 LOG_I(
TAG,
"BLE synced, addr=%02X:%02X:%02X:%02X:%02X:%02X",
1081 addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
1094void BluetoothController::startAdvertising() {
1095 if (!enabled_ || !synced_)
return;
1102 if (ble_gap_adv_active()) {
1105 advertising_ =
false;
1107 struct ble_gap_adv_params advParams = {};
1108 advParams.conn_mode = BLE_GAP_CONN_MODE_UND;
1109 advParams.disc_mode = BLE_GAP_DISC_MODE_GEN;
1110 advParams.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
1111 advParams.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MAX;
1117 ble_uuid16_t uuid16s[MAX_ADV_UUIDS];
1118 ble_uuid128_t uuid128s[MAX_ADV_UUIDS];
1119 uint8_t num16 = 0, num128 = 0;
1120 for (uint8_t i = 0; i < advUuidCount_; i++) {
1122 uuid16s[num16].u.type = BLE_UUID_TYPE_16;
1123 uuid16s[num16].value = advUuids_[i].u16;
1126 uuid128s[num128].u.type = BLE_UUID_TYPE_128;
1127 memcpy(uuid128s[num128].value, advUuids_[i].u128, 16);
1133 struct ble_hs_adv_fields fields = {};
1134 fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
1135 fields.tx_pwr_lvl_is_present = 1;
1136 fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
1137 if (appearance_ != 0) {
1138 fields.appearance = appearance_;
1139 fields.appearance_is_present = 1;
1142 fields.uuids16 = uuid16s;
1143 fields.num_uuids16 = num16;
1144 fields.uuids16_is_complete = 1;
1147 fields.uuids128 = uuid128s;
1148 fields.num_uuids128 = num128;
1149 fields.uuids128_is_complete = 1;
1151 fields.name = (uint8_t*)deviceName_;
1152 fields.name_len = strlen(deviceName_);
1153 fields.name_is_complete = 1;
1158 bool nameInScanRsp =
false;
1159 bool uuid128InScanRsp =
false;
1160 int rc = ble_gap_adv_set_fields(&fields);
1161 if (rc == BLE_HS_EMSGSIZE) {
1162 fields.name =
nullptr;
1163 fields.name_len = 0;
1164 fields.name_is_complete = 0;
1165 nameInScanRsp =
true;
1166 rc = ble_gap_adv_set_fields(&fields);
1168 if (rc == BLE_HS_EMSGSIZE && num128 > 0) {
1169 fields.uuids128 =
nullptr;
1170 fields.num_uuids128 = 0;
1171 fields.uuids128_is_complete = 0;
1172 uuid128InScanRsp =
true;
1173 rc = ble_gap_adv_set_fields(&fields);
1176 LOG_E(
TAG,
"Failed to set adv fields: %d", rc);
1182 if (nameInScanRsp || uuid128InScanRsp || mfgDataSet_) {
1183 struct ble_hs_adv_fields rsp = {};
1185 if (nameInScanRsp) {
1186 rsp.name = (uint8_t*)deviceName_;
1187 rsp.name_len = strlen(deviceName_);
1188 rsp.name_is_complete = 1;
1190 if (uuid128InScanRsp) {
1191 rsp.uuids128 = uuid128s;
1192 rsp.num_uuids128 = num128;
1193 rsp.uuids128_is_complete = 1;
1197 uint8_t mfgAdvBuf[33];
1199 mfgAdvBuf[0] = mfgCompanyId_ & 0xFF;
1200 mfgAdvBuf[1] = (mfgCompanyId_ >> 8) & 0xFF;
1201 memcpy(mfgAdvBuf + 2, mfgData_, mfgDataLen_);
1202 rsp.mfg_data = mfgAdvBuf;
1203 rsp.mfg_data_len = mfgDataLen_ + 2;
1206 rc = ble_gap_adv_rsp_set_fields(&rsp);
1208 LOG_W(
TAG,
"Failed to set scan response: %d (continuing without)", rc);
1212 rc = ble_gap_adv_start(ownAddrType_,
nullptr, BLE_HS_FOREVER,
1213 &advParams, bleGapEventCallback,
nullptr);
1214 if (rc != 0 && rc != BLE_HS_EALREADY) {
1215 LOG_E(
TAG,
"Failed to start advertising: %d", rc);
1219 advertising_ =
true;
1220 LOG_I(
TAG,
"Advertising started (%d service UUIDs)", advUuidCount_);
1226void BluetoothController::stopAdvertising() {
1227 if (!advertising_)
return;
1230 advertising_ =
false;
1237void BluetoothController::onAdvComplete() {
1238 advertising_ =
false;
1245void BluetoothController::setAppearance(uint16_t appearance) {
1246 if (appearance_ == appearance)
return;
1247 appearance_ = appearance;
1248 if (enabled_ && synced_ && advertising_) {
1262bool BluetoothController::startScan(uint32_t durationMs,
bool keepAdvertising) {
1263 if (!enabled_ || !synced_ || scanning_)
return false;
1268 bool wasAdvertising = advertising_;
1269 if (advertising_ && !keepAdvertising) {
1271 advertising_ =
false;
1272 LOG_D(
TAG,
"Stopped advertising for scan");
1276 scanResultCount_ = 0;
1277 memset(scanResults_, 0,
sizeof(scanResults_));
1278 memset(scanNameComplete_, 0,
sizeof(scanNameComplete_));
1280 struct ble_gap_disc_params discParams = {};
1281 discParams.filter_duplicates = 0;
1282 discParams.passive = 0;
1283 discParams.itvl = 0;
1284 discParams.window = 0;
1285 discParams.filter_policy = 0;
1286 discParams.limited = 0;
1289 int32_t duration = (durationMs == 0) ? BLE_HS_FOREVER
1290 : static_cast<int32_t>(durationMs);
1291 int rc = ble_gap_disc(ownAddrType_, duration, &discParams,
1292 bleGapEventCallback,
nullptr);
1294 LOG_E(
TAG,
"Failed to start scan: %d", rc);
1296 if (wasAdvertising && !keepAdvertising) {
1303 scanWasAdvertising_ = keepAdvertising ? false : wasAdvertising;
1305 LOG_I(
TAG,
"Scan started (%lu ms%s)", (
unsigned long)durationMs,
1306 keepAdvertising ?
", adv kept" :
"");
1313void BluetoothController::stopScan() {
1314 if (!scanning_)
return;
1316 ble_gap_disc_cancel();
1333static bool parseAdvName(
const uint8_t* data, uint8_t dataLen,
1334 char*
name,
size_t nameMaxLen,
bool* isComplete =
nullptr) {
1336 while (pos < dataLen) {
1337 uint8_t len = data[pos];
1338 if (len == 0 || pos + len > dataLen)
break;
1339 uint8_t type = data[pos + 1];
1341 if (type == 0x09 || type == 0x08) {
1342 uint8_t nameLen = len - 1;
1343 size_t copyLen = (nameLen < nameMaxLen - 1) ? nameLen : nameMaxLen - 1;
1344 memcpy(
name, &data[pos + 2], copyLen);
1345 name[copyLen] =
'\0';
1346 if (isComplete) *isComplete = (type == 0x09);
1362static void fillScanResult(
BleScanResult& result,
const ble_gap_disc_desc* disc,
1363 bool* outNameComplete) {
1364 memcpy(result.
mac, disc->addr.val, 6);
1366 result.
rssi = disc->rssi;
1367 result.
name[0] =
'\0';
1370 ? disc->length_data : sizeof(result.advData);
1373 bool isComplete =
false;
1374 parseAdvName(disc->data, disc->length_data, result.
name,
sizeof(result.
name), &isComplete);
1375 const bool haveName = (result.
name[0] !=
'\0');
1376 if (outNameComplete) *outNameComplete = haveName && isComplete;
1378 snprintf(result.
name,
sizeof(result.
name),
"%02X:%02X:%02X:%02X:%02X:%02X",
1379 result.
mac[5], result.
mac[4], result.
mac[3],
1380 result.
mac[2], result.
mac[1], result.
mac[0]);
1384void BluetoothController::onScanResult(
const ble_gap_disc_desc* disc) {
1388 for (uint8_t i = 0; i < scanResultCount_; i++) {
1389 if (memcmp(scanResults_[i].mac, disc->addr.val, 6) == 0) {
1390 if (disc->rssi > scanResults_[i].rssi) {
1391 scanResults_[i].rssi = disc->rssi;
1397 char parsedName[32];
1398 bool isComplete =
false;
1399 if (parseAdvName(disc->data, disc->length_data, parsedName,
sizeof(parsedName), &isComplete) &&
1400 parsedName[0] !=
'\0' &&
1401 (isComplete || !scanNameComplete_[i]) &&
1402 strcmp(parsedName, scanResults_[i].
name) != 0) {
1403 strncpy(scanResults_[i].
name, parsedName,
sizeof(scanResults_[i].
name) - 1);
1404 scanResults_[i].name[
sizeof(scanResults_[i].name) - 1] =
'\0';
1405 scanNameComplete_[i] = isComplete;
1406 LOG_D(
TAG,
"Name updated: %s (evt=0x%02X)", parsedName, disc->event_type);
1415 if (scanResultCount_ < MAX_SCAN_RESULTS) {
1416 slot = scanResultCount_++;
1418 uint8_t weakest = 0;
1419 for (uint8_t i = 1; i < scanResultCount_; i++) {
1420 if (scanResults_[i].rssi < scanResults_[weakest].rssi) weakest = i;
1422 if (disc->rssi <= scanResults_[weakest].rssi)
return;
1426 bool nameComplete =
false;
1427 fillScanResult(scanResults_[slot], disc, &nameComplete);
1428 scanNameComplete_[slot] = nameComplete;
1429 LOG_D(
TAG,
"Found: %s (RSSI %d) evt=0x%02X dlen=%d",
1430 scanResults_[slot].
name, scanResults_[slot].rssi,
1431 disc->event_type, disc->length_data);
1437void BluetoothController::onScanComplete() {
1439 LOG_I(
TAG,
"Scan complete, found %d devices", scanResultCount_);
1442 if (scanWasAdvertising_) {
1443 scanWasAdvertising_ =
false;
1454uint8_t BluetoothController::getScanResults(
BleScanResult* results, uint8_t maxResults) {
1455 if (!results || maxResults == 0)
return 0;
1457 uint8_t count = (scanResultCount_ < maxResults) ? scanResultCount_ : maxResults;
1458 memcpy(results, scanResults_, count *
sizeof(
BleScanResult));
1471bool BluetoothController::registerGattService(
const GattServiceDef& service,
1472 bool pluginReserved) {
1473 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
1479 ble_uuid_any_t wantUuid;
1480 convertUuid(service.
uuid, wantUuid);
1481 const int firstSlot = pluginReserved ? PLUGIN_SERVICE_SLOT : 0;
1482 const int lastSlot = pluginReserved ? MAX_REGISTERED_SERVICES : PLUGIN_SERVICE_SLOT;
1484 for (
int i = firstSlot; i < lastSlot; i++) {
1485 if (s_services[i].active && ble_uuid_cmp(&s_services[i].svcUuid.u, &wantUuid.u) == 0) {
1490 for (
int i = firstSlot; i < lastSlot; i++) {
1491 if (!s_services[i].active) { slot = i;
break; }
1495 LOG_E(
TAG,
"No free GATT service slots (max %d)", MAX_REGISTERED_SERVICES);
1499 auto& s = s_services[slot];
1500 memset(&s, 0,
sizeof(InternalService));
1503 convertUuid(service.
uuid, s.svcUuid);
1506 uint8_t numChars = std::min(service.
numCharacteristics, (uint8_t)MAX_CHARS_PER_SERVICE);
1507 s.numChars = numChars;
1509 for (uint8_t i = 0; i < numChars; i++) {
1511 auto& dst = s.nimbleChars[i];
1513 convertUuid(src.uuid, s.charUuids[i]);
1515 dst.uuid = &s.charUuids[i].u;
1516 dst.access_cb = gattServiceAccessCb;
1518 dst.descriptors =
nullptr;
1519 dst.flags = mapProperties(src.properties, src.permissions);
1520 dst.min_key_size = 0;
1521 dst.val_handle = src.valueHandle;
1524 uint8_t numDsc = src.numDescriptors;
1525 if (numDsc > MAX_DESCRIPTORS_PER_CHAR) numDsc = MAX_DESCRIPTORS_PER_CHAR;
1526 for (uint8_t d = 0; d < numDsc; d++) {
1528 uint16_t uuid16 = 0;
1531 default: uuid16 = 0;
break;
1533 if (uuid16 == 0)
continue;
1535 s.dscUuids[i][d].u.
type = BLE_UUID_TYPE_16;
1536 s.dscUuids[i][d].u16.u.type = BLE_UUID_TYPE_16;
1537 s.dscUuids[i][d].u16.value = uuid16;
1540 uint8_t copyLen = (gd.dataLen <= 4) ? gd.dataLen : 4;
1541 s.dscPacked[i][d][0] = copyLen;
1542 memcpy(&s.dscPacked[i][d][1], gd.data, copyLen);
1544 s.dscDefs[i][d].uuid = &s.dscUuids[i][d].u;
1545 s.dscDefs[i][d].att_flags = BLE_ATT_F_READ;
1546 s.dscDefs[i][d].min_key_size = 0;
1547 s.dscDefs[i][d].access_cb = gattStaticDescriptorAccessCb;
1548 s.dscDefs[i][d].arg = s.dscPacked[i][d];
1552 memset(&s.dscDefs[i][numDsc], 0,
sizeof(ble_gatt_dsc_def));
1553 dst.descriptors = s.dscDefs[i];
1556 s.writeCallbacks[i] = src.onWrite;
1557 s.readCallbacks[i] = src.onRead;
1561 memset(&s.nimbleChars[numChars], 0,
sizeof(ble_gatt_chr_def));
1564 s.nimbleSvcs[0].type = BLE_GATT_SVC_TYPE_PRIMARY;
1565 s.nimbleSvcs[0].uuid = &s.svcUuid.u;
1566 s.nimbleSvcs[0].includes =
nullptr;
1567 s.nimbleSvcs[0].characteristics = s.nimbleChars;
1568 memset(&s.nimbleSvcs[1], 0,
sizeof(ble_gatt_svc_def));
1576 LOG_I(
TAG,
"GATT service stored, deferred until BLE enable (slot %d)", slot);
1587 LOG_E(
TAG,
"GATT service register: BLE restart failed (slot %d)", slot);
1590 LOG_I(
TAG,
"GATT service registered via BLE restart (slot %d, %d chars)", slot, numChars);
1594bool BluetoothController::unregisterGattService(
const BleUuid& serviceUuid) {
1595 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
1596 ble_uuid_any_t wantUuid;
1597 convertUuid(serviceUuid, wantUuid);
1599 for (
int i = 0; i < MAX_REGISTERED_SERVICES; i++) {
1600 if (s_services[i].active && ble_uuid_cmp(&s_services[i].svcUuid.u, &wantUuid.u) == 0) {
1604 if (slot < 0)
return false;
1606 memset(&s_services[slot], 0,
sizeof(InternalService));
1611 LOG_I(
TAG,
"GATT service unregistered (slot %d, deferred)", slot);
1620 LOG_E(
TAG,
"GATT service unregister: BLE restart failed (slot %d)", slot);
1623 LOG_I(
TAG,
"GATT service unregistered via BLE restart (slot %d)", slot);
1635bool BluetoothController::sendNotification(uint16_t connHandle, uint16_t attrHandle,
1636 const uint8_t* data, uint16_t len) {
1637 if (!enabled_ || !data || len == 0) {
1641 auto notifyOne = [&](uint16_t handle) ->
bool {
1643 bool subscribed =
false;
1644 for (uint8_t i = 0; i < MAX_SUBSCRIBE_ENTRIES; i++) {
1645 if (subscribes_[i].active &&
1646 subscribes_[i].connHandle == handle &&
1647 subscribes_[i].attrHandle == attrHandle &&
1648 subscribes_[i].notify) {
1653 if (!subscribed)
return false;
1655 struct os_mbuf* om = ble_hs_mbuf_from_flat(data, len);
1657 LOG_E(
TAG,
"Failed to allocate mbuf for notification");
1660 int rc = ble_gatts_notify_custom(handle, attrHandle, om);
1664 os_mbuf_free_chain(om);
1670 if (connHandle == 0xFFFF || connHandle == BLE_HS_CONN_HANDLE_NONE) {
1672 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) {
1673 if (connections_[i].active) {
1674 if (notifyOne(connections_[i].handle)) any =
true;
1679 return notifyOne(connHandle);
1686uint16_t BluetoothController::getMtu()
const {
1687 uint16_t handle = primaryConnHandle();
1688 if (handle == BLE_HS_CONN_HANDLE_NONE) {
1691 uint16_t mtu = ble_att_mtu(handle);
1692 return (mtu > 3) ? (mtu - 3) : 20;
1704bool BluetoothController::addAdvertisingUuid(
const BleUuid& uuid) {
1706 for (uint8_t i = 0; i < advUuidCount_; i++) {
1707 if (advUuids_[i] == uuid)
return true;
1710 if (advUuidCount_ >= MAX_ADV_UUIDS) {
1711 LOG_E(
TAG,
"Max advertising UUIDs reached (%d)", MAX_ADV_UUIDS);
1715 advUuids_[advUuidCount_++] = uuid;
1716 LOG_I(
TAG,
"Advertising UUID registered (%d total)", advUuidCount_);
1719 if (enabled_ && synced_) {
1733void BluetoothController::removeAdvertisingUuid(
const BleUuid& uuid) {
1734 for (uint8_t i = 0; i < advUuidCount_; i++) {
1735 if (advUuids_[i] == uuid) {
1737 for (uint8_t j = i; j < advUuidCount_ - 1; j++) {
1738 advUuids_[j] = advUuids_[j + 1];
1741 LOG_I(
TAG,
"Advertising UUID removed (%d remaining)", advUuidCount_);
1761template <
typename CB,
size_t N>
1763 BluetoothController::ListenerSlot<CB> (&slots)[N], CB cb) {
1765 for (
size_t i = 0; i < N; i++) {
1766 if (!slots[i].active) {
1767 slots[i].active =
true;
1768 slots[i].callback = cb;
1778template <
typename CB,
size_t N>
1779static void removeListener(
1780 BluetoothController::ListenerSlot<CB> (&slots)[N],
1782 if (token >= N)
return;
1783 slots[token].active =
false;
1784 slots[token].callback =
nullptr;
1788BluetoothController::addConnectionCallback(ConnectionCallback cb) {
1789 return addListener(connCallbacks_, cb);
1793BluetoothController::addDisconnectionCallback(DisconnectionCallback cb) {
1794 return addListener(disconnCallbacks_, cb);
1797void BluetoothController::removeConnectionCallback(
ListenerToken token) {
1798 removeListener(connCallbacks_, token);
1801void BluetoothController::removeDisconnectionCallback(
ListenerToken token) {
1802 removeListener(disconnCallbacks_, token);
1813void BluetoothController::setPasskeyCallback(PasskeyCallback cb) {
1821void BluetoothController::setAuthCompleteCallback(AuthCompleteCallback cb) {
1822 authCompleteCb_ = cb;
1826BluetoothController::addNumericComparisonCallback(NumericComparisonCallback cb) {
1827 return addListener(numCmpCallbacks_, cb);
1830void BluetoothController::removeNumericComparisonCallback(
ListenerToken token) {
1831 removeListener(numCmpCallbacks_, token);
1834void BluetoothController::setNumericComparisonCallback(NumericComparisonCallback cb) {
1835 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) numCmpCallbacks_[i].active =
false;
1836 if (cb) addListener(numCmpCallbacks_, cb);
1840BluetoothController::addEncryptionChangeCallback(EncChangeCallback cb) {
1841 return addListener(encChangeCallbacks_, cb);
1844void BluetoothController::removeEncryptionChangeCallback(
ListenerToken token) {
1845 removeListener(encChangeCallbacks_, token);
1853bool BluetoothController::initiateSecurity(uint16_t connHandle) {
1854 int rc = ble_gap_security_initiate(connHandle);
1856 if (rc != 0 && rc != BLE_HS_EALREADY) {
1857 LOG_W(
TAG,
"ble_gap_security_initiate failed: %d", rc);
1870bool BluetoothController::getPeerIdAddr(uint16_t connHandle, uint8_t addr[6],
1871 uint8_t* addrType)
const {
1872 if (!addr || !addrType)
return false;
1873 struct ble_gap_conn_desc desc = {};
1874 if (ble_gap_conn_find(connHandle, &desc) != 0)
return false;
1875 std::memcpy(addr, desc.peer_id_addr.val, 6);
1876 *addrType = desc.peer_id_addr.type;
1885void BluetoothController::forgetBond(
const uint8_t addr[6], uint8_t addrType) {
1887 ble_addr_t peer = {};
1888 peer.type = addrType;
1889 std::memcpy(peer.val, addr, 6);
1894 int rc = ble_store_util_delete_peer(&peer);
1896 LOG_W(
TAG,
"ble_store_util_delete_peer failed: %d", rc);
1908uint8_t BluetoothController::getBondedDevices(
BleBondInfo* out, uint8_t maxCount)
const {
1909 if (!out || maxCount == 0)
return 0;
1911 ble_addr_t peers[MAX_BONDS] = {};
1913 if (ble_store_util_bonded_peers(peers, &numPeers, MAX_BONDS) != 0) {
1918 for (
int i = 0; i < numPeers && count < maxCount; i++) {
1919 std::memcpy(out[count].addr, peers[i].val, 6);
1920 out[count].addrType = peers[i].type;
1921 out[count].connected =
false;
1922 for (uint8_t c = 0; c < MAX_CONNECTIONS; c++) {
1923 if (!connections_[c].active)
continue;
1924 struct ble_gap_conn_desc desc = {};
1925 if (ble_gap_conn_find(connections_[c].handle, &desc) != 0)
continue;
1926 if (desc.peer_id_addr.type == peers[i].type &&
1927 std::memcmp(desc.peer_id_addr.val, peers[i].val, 6) == 0) {
1928 out[count].connected =
true;
1942void BluetoothController::respondToNumericComparison(uint16_t connHandle,
bool accept) {
1943 struct ble_sm_io pkey = {};
1944 pkey.action = BLE_SM_IOACT_NUMCMP;
1945 pkey.numcmp_accept = accept ? 1 : 0;
1946 int rc = ble_sm_inject_io(connHandle, &pkey);
1948 LOG_E(
TAG,
"ble_sm_inject_io failed: %d", rc);
1950 LOG_I(
TAG,
"Pairing %s", accept ?
"accepted" :
"rejected");
1958void BluetoothController::onPasskeyAction(uint16_t connHandle,
1959 const ble_gap_passkey_params* params) {
1960 if (!params)
return;
1962 switch (params->action) {
1963 case BLE_SM_IOACT_NUMCMP: {
1964 LOG_I(
TAG,
"Numeric comparison: %06lu", (
unsigned long)params->numcmp);
1965 LOG_I(
TAG,
"%s stack free: %lu words", pcTaskGetName(
nullptr),
1966 (
unsigned long)uxTaskGetStackHighWaterMark(
nullptr));
1967 bool dispatched =
false;
1968 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
1969 if (numCmpCallbacks_[i].active && numCmpCallbacks_[i].callback) {
1970 numCmpCallbacks_[i].callback(connHandle, params->numcmp);
1976 LOG_W(
TAG,
"No numeric comparison callback registered, rejecting");
1977 respondToNumericComparison(connHandle,
false);
1982 case BLE_SM_IOACT_DISP:
1983 LOG_I(
TAG,
"Display passkey: %06lu", (
unsigned long)params->numcmp);
1985 passkeyCb_(params->numcmp);
1990 LOG_W(
TAG,
"Unhandled passkey action: %d", params->action);
2006bool BluetoothController::setAdvertisingManufacturerData(uint16_t companyId,
2007 const uint8_t* data, uint16_t len) {
2008 if (!data || len >
sizeof(mfgData_))
return false;
2010 memcpy(mfgData_, data, len);
2012 mfgCompanyId_ = companyId;
2025void BluetoothController::clearAdvertisingManufacturerData() {
2026 mfgDataSet_ =
false;
2047int gattcChrDiscCb(uint16_t connHandle,
const struct ble_gatt_error* error,
2048 const struct ble_gatt_chr* chr,
void* arg) {
2050 auto* ctrl = BluetoothController::instance_;
2051 if (!ctrl)
return 0;
2053 if (error->status == 0 && chr) {
2054 auto& svc = ctrl->discoveredSvc_;
2056 auto& dc = svc.characteristics[svc.numCharacteristics];
2060 if (chr->uuid.u.type == BLE_UUID_TYPE_16) {
2062 }
else if (chr->uuid.u.type == BLE_UUID_TYPE_32) {
2064 uint8_t u128[16] = { 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00,
2065 0x00, 0x80, 0x00, 0x10, 0x00, 0x00,
2067 uint32_t v = chr->uuid.u32.value;
2068 u128[12] = v & 0xFF;
2069 u128[13] = (v >> 8) & 0xFF;
2070 u128[14] = (v >> 16) & 0xFF;
2071 u128[15] = (v >> 24) & 0xFF;
2076 dc.valueHandle = chr->val_handle;
2077 dc.properties = chr->properties;
2078 svc.numCharacteristics++;
2080 }
else if (error->status == BLE_HS_EDONE) {
2081 LOG_I(
TAG,
"Char discovery done (%d chars)", ctrl->discoveredSvc_.numCharacteristics);
2082 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2083 if (ctrl->svcDiscoveryCallbacks_[i].active &&
2084 ctrl->svcDiscoveryCallbacks_[i].callback) {
2085 ctrl->svcDiscoveryCallbacks_[i].callback(
2086 connHandle, &ctrl->discoveredSvc_,
true);
2090 LOG_E(
TAG,
"Char discovery error: %d", error->status);
2091 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2092 if (ctrl->svcDiscoveryCallbacks_[i].active &&
2093 ctrl->svcDiscoveryCallbacks_[i].callback) {
2094 ctrl->svcDiscoveryCallbacks_[i].callback(connHandle,
nullptr,
true);
2110int gattcSvcDiscCb(uint16_t connHandle,
const struct ble_gatt_error* error,
2111 const struct ble_gatt_svc* service,
void* arg) {
2113 auto* ctrl = BluetoothController::instance_;
2114 if (!ctrl)
return 0;
2116 auto dispatchFailure = [&]() {
2117 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2118 if (ctrl->svcDiscoveryCallbacks_[i].active &&
2119 ctrl->svcDiscoveryCallbacks_[i].callback) {
2120 ctrl->svcDiscoveryCallbacks_[i].callback(connHandle,
nullptr,
true);
2125 if (error->status == 0 && service) {
2126 ctrl->discoverSvcStart_ = service->start_handle;
2127 ctrl->discoverSvcEnd_ = service->end_handle;
2128 LOG_I(
TAG,
"Service found: handles %d-%d",
2129 service->start_handle, service->end_handle);
2130 }
else if (error->status == BLE_HS_EDONE) {
2131 if (ctrl->discoverSvcStart_ != 0) {
2132 int rc = ble_gattc_disc_all_chrs(connHandle,
2133 ctrl->discoverSvcStart_,
2134 ctrl->discoverSvcEnd_,
2135 gattcChrDiscCb,
nullptr);
2137 LOG_E(
TAG,
"ble_gattc_disc_all_chrs failed: %d", rc);
2141 LOG_W(
TAG,
"Service not found on remote device");
2145 LOG_E(
TAG,
"Service discovery error: %d", error->status);
2160int gattcReadCb(uint16_t connHandle,
const struct ble_gatt_error* error,
2161 struct ble_gatt_attr* attr,
void* arg) {
2163 auto* ctrl = BluetoothController::instance_;
2164 if (!ctrl)
return 0;
2166 if (error->status == 0 && attr && attr->om) {
2167 uint16_t len = OS_MBUF_PKTLEN(attr->om);
2168 if (len >
sizeof(s_gattAccessBuf)) len =
sizeof(s_gattAccessBuf);
2169 ble_hs_mbuf_to_flat(attr->om, s_gattAccessBuf, len,
nullptr);
2170 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2171 if (ctrl->charReadCallbacks_[i].active &&
2172 ctrl->charReadCallbacks_[i].callback) {
2173 ctrl->charReadCallbacks_[i].callback(
2174 connHandle, attr->handle, s_gattAccessBuf, len);
2178 LOG_E(
TAG,
"GATT read failed: %d", error->status);
2179 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2180 if (ctrl->charReadCallbacks_[i].active &&
2181 ctrl->charReadCallbacks_[i].callback) {
2182 ctrl->charReadCallbacks_[i].callback(connHandle, 0,
nullptr, 0);
2198int gattcWriteCb(uint16_t connHandle,
const struct ble_gatt_error* error,
2199 struct ble_gatt_attr* attr,
void* arg) {
2201 auto* ctrl = BluetoothController::instance_;
2202 if (!ctrl)
return 0;
2204 uint16_t handle = attr ? attr->handle : 0;
2205 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2206 if (ctrl->writeCompleteCallbacks_[i].active &&
2207 ctrl->writeCompleteCallbacks_[i].callback) {
2208 ctrl->writeCompleteCallbacks_[i].callback(connHandle, handle, error->status);
2225bool BluetoothController::connect(
const uint8_t* addr, uint8_t addrType) {
2226 if (!enabled_ || !synced_ || !addr)
return false;
2231 advertising_ =
false;
2235 bleAddr.type = addrType;
2236 memcpy(bleAddr.val, addr, 6);
2238 int rc = ble_gap_connect(ownAddrType_, &bleAddr, 10000,
nullptr,
2239 bleGapEventCallback,
nullptr);
2241 LOG_E(
TAG,
"ble_gap_connect failed: %d", rc);
2246 LOG_I(
TAG,
"Connecting to %02X:%02X:%02X:%02X:%02X:%02X",
2247 addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
2254void BluetoothController::cancelConnect() {
2255 ble_gap_conn_cancel();
2264bool BluetoothController::discoverServiceByUuid(uint16_t connHandle,
const BleUuid& uuid) {
2265 if (!enabled_)
return false;
2268 memset(&discoveredSvc_, 0,
sizeof(discoveredSvc_));
2269 discoveredSvc_.uuid = uuid;
2270 discoverTargetUuid_ = uuid;
2271 discoverSvcStart_ = 0;
2272 discoverSvcEnd_ = 0;
2274 ble_uuid_any_t nimbleUuid;
2275 convertUuid(uuid, nimbleUuid);
2277 int rc = ble_gattc_disc_svc_by_uuid(connHandle, &nimbleUuid.u,
2278 gattcSvcDiscCb,
nullptr);
2280 LOG_E(
TAG,
"ble_gattc_disc_svc_by_uuid failed: %d", rc);
2284 LOG_I(
TAG,
"Service discovery started (connHandle=%d)", connHandle);
2297bool BluetoothController::writeCharacteristic(uint16_t connHandle, uint16_t attrHandle,
2298 const uint8_t* data, uint16_t len,
2299 bool withResponse) {
2300 if (!enabled_ || !data || len == 0)
return false;
2304 rc = ble_gattc_write_flat(connHandle, attrHandle, data, len,
2305 gattcWriteCb,
nullptr);
2310 rc = ble_gattc_write_no_rsp_flat(connHandle, attrHandle, data, len);
2314 LOG_E(
TAG,
"GATT write failed: %d", rc);
2327bool BluetoothController::readCharacteristic(uint16_t connHandle, uint16_t attrHandle) {
2328 if (!enabled_)
return false;
2330 int rc = ble_gattc_read(connHandle, attrHandle, gattcReadCb,
nullptr);
2332 LOG_E(
TAG,
"ble_gattc_read failed: %d", rc);
2345bool BluetoothController::enableNotifications(uint16_t connHandle, uint16_t cccdHandle) {
2346 if (!enabled_)
return false;
2348 uint8_t val[2] = { 0x01, 0x00 };
2349 int rc = ble_gattc_write_flat(connHandle, cccdHandle, val,
sizeof(val),
2350 gattcWriteCb,
nullptr);
2352 LOG_E(
TAG,
"Enable notifications failed: %d", rc);
2356 LOG_I(
TAG,
"Notifications enabled (cccd=%d)", cccdHandle);
2373int gattcDscDiscCb(uint16_t connHandle,
const struct ble_gatt_error* error,
2374 uint16_t chr_val_handle,
const struct ble_gatt_dsc* dsc,
void* arg) {
2376 (void)chr_val_handle;
2377 auto* ctrl = BluetoothController::instance_;
2378 if (!ctrl)
return 0;
2380 if (error->status == 0 && dsc) {
2381 if (dsc->uuid.u.type == BLE_UUID_TYPE_16 && dsc->uuid.u16.value == 0x2902 &&
2382 ctrl->pendingSubCccdHandle_ == 0) {
2383 ctrl->pendingSubCccdHandle_ = dsc->handle;
2389 uint16_t cccd = ctrl->pendingSubCccdHandle_;
2391 cccd =
static_cast<uint16_t
>(ctrl->pendingSubValueHandle_ + 1);
2392 LOG_W(
TAG,
"No CCCD found for handle %d, falling back to %d",
2393 ctrl->pendingSubValueHandle_, cccd);
2395 ctrl->pendingSubValueHandle_ = 0;
2396 ctrl->pendingSubCccdHandle_ = 0;
2397 ctrl->enableNotifications(connHandle, cccd);
2407bool BluetoothController::subscribeToCharacteristic(uint16_t connHandle, uint16_t valueHandle) {
2408 if (!enabled_ || valueHandle == 0)
return false;
2412 if (discoverSvcEnd_ == 0 || valueHandle >= discoverSvcEnd_) {
2413 return enableNotifications(connHandle,
static_cast<uint16_t
>(valueHandle + 1));
2416 pendingSubValueHandle_ = valueHandle;
2417 pendingSubCccdHandle_ = 0;
2418 int rc = ble_gattc_disc_all_dscs(connHandle, valueHandle, discoverSvcEnd_,
2419 gattcDscDiscCb,
nullptr);
2421 LOG_E(
TAG,
"ble_gattc_disc_all_dscs failed: %d", rc);
2422 pendingSubValueHandle_ = 0;
2423 return enableNotifications(connHandle,
static_cast<uint16_t
>(valueHandle + 1));
2432void BluetoothController::disconnectHandle(uint16_t connHandle) {
2433 ble_gap_terminate(connHandle, BLE_ERR_REM_USER_CONN_TERM);
2437BluetoothController::addServiceDiscoveryCallback(ServiceDiscoveryCallback cb) {
2438 return addListener(svcDiscoveryCallbacks_, cb);
2441BluetoothController::addCharacteristicReadCallback(CharacteristicReadCallback cb) {
2442 return addListener(charReadCallbacks_, cb);
2445BluetoothController::addNotificationCallback(NotificationCallback cb) {
2446 return addListener(notifyCallbacks_, cb);
2449BluetoothController::addWriteCompleteCallback(WriteCompleteCallback cb) {
2450 return addListener(writeCompleteCallbacks_, cb);
2453void BluetoothController::removeServiceDiscoveryCallback(
ListenerToken t) {
2454 removeListener(svcDiscoveryCallbacks_, t);
2456void BluetoothController::removeCharacteristicReadCallback(
ListenerToken t) {
2457 removeListener(charReadCallbacks_, t);
2459void BluetoothController::removeNotificationCallback(
ListenerToken t) {
2460 removeListener(notifyCallbacks_, t);
2462void BluetoothController::removeWriteCompleteCallback(
ListenerToken t) {
2463 removeListener(writeCompleteCallbacks_, t);
2466void BluetoothController::setServiceDiscoveryCallback(ServiceDiscoveryCallback cb) {
2467 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) svcDiscoveryCallbacks_[i].active =
false;
2468 if (cb) addListener(svcDiscoveryCallbacks_, cb);
2470void BluetoothController::setCharacteristicReadCallback(CharacteristicReadCallback cb) {
2471 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) charReadCallbacks_[i].active =
false;
2472 if (cb) addListener(charReadCallbacks_, cb);
2474void BluetoothController::setNotificationCallback(NotificationCallback cb) {
2475 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) notifyCallbacks_[i].active =
false;
2476 if (cb) addListener(notifyCallbacks_, cb);
2478void BluetoothController::setWriteCompleteCallback(WriteCompleteCallback cb) {
2479 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) writeCompleteCallbacks_[i].active =
false;
2480 if (cb) addListener(writeCompleteCallbacks_, cb);
2511 LOG_W(
TAG,
"Bluetooth disabled (NimBLE not configured)");
2525 const char*
getName()
const override {
return "bluetooth"; }
2528 LOG_W(
TAG,
"Cannot enable - NimBLE not configured in sdkconfig");
2539 if (mac) esp_read_mac(mac, ESP_MAC_BT);
2540 return mac !=
nullptr;
2548 strncpy(deviceName_,
name,
sizeof(deviceName_) - 1);
2558 char deviceName_[32] =
"CDC Badge";
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
Shared RAII wrappers for firmware resources.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_D(tag, fmt,...)
#define LOG_I(tag, fmt,...)
#define LOG_E(tag, fmt,...)
static constexpr uint8_t MAX_CHARS_PER_SERVICE
static constexpr uint8_t MAX_REGISTERED_SERVICES
bool getMacAddress(uint8_t *mac) const override
Returns BLE MAC address using efuse fallback.
int8_t getRssi() const override
const char * getName() const override
bool isConnected() const override
bool isEnabled() const override
void setDeviceName(const char *name) override
Stores requested device name in local stub buffer.
core::ServiceState getState() const override
const char * getDeviceName() const override
bool start() override
Starts stub controller state.
bool init() override
Initializes stub controller state.
void disconnect() override
static constexpr ListenerToken INVALID_LISTENER
constexpr uint8_t READ_ENC
constexpr uint8_t WRITE_ENC
constexpr uint8_t INDICATE
constexpr uint8_t WRITE_NO_RSP
static BluetoothControllerStub g_bluetoothController
IBluetoothController * getBluetoothControllerInstance()
Returns singleton Bluetooth stub when NimBLE is unavailable.
IBluetoothController::ListenerToken ListenerToken
void init(hal::IDisplay *display, hal::ISleepController *sleep, LockScreenView *lockScreen)
Initializes shared dependencies used by the settings handlers.
uint8_t numCharacteristics
GattCharacteristic * characteristics
static BleUuid from16(uint16_t v)
static BleUuid from128(const uint8_t v[16])
static constexpr uint8_t MAX_DISCOVERED_CHARS