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);
66static constexpr uint8_t PLUGIN_SERVICE_SLOT = MAX_REGISTERED_SERVICES - 1;
68static constexpr uint8_t MAX_DESCRIPTORS_PER_CHAR = 2;
69static constexpr uint8_t MAX_ADV_UUIDS = 4;
70static constexpr uint8_t MAX_CONN_CALLBACKS = 6;
71static constexpr uint8_t MAX_CONNECTIONS = 2;
72static constexpr uint8_t MAX_SUBSCRIBE_ENTRIES = 16;
73static constexpr uint8_t MAX_BONDS = 5;
76static constexpr uint32_t kConnectSettleMs = 50;
78static constexpr uint32_t kDisconnectDrainPollMs = 20;
80static constexpr uint32_t kDisconnectDrainTimeoutMs = 1000;
85struct InternalService {
89 ble_uuid_any_t svcUuid;
90 ble_uuid_any_t charUuids[MAX_CHARS_PER_SERVICE];
93 ble_gatt_chr_def nimbleChars[MAX_CHARS_PER_SERVICE + 1];
94 ble_gatt_svc_def nimbleSvcs[2];
98 ble_uuid_any_t dscUuids[MAX_CHARS_PER_SERVICE][MAX_DESCRIPTORS_PER_CHAR];
99 ble_gatt_dsc_def dscDefs[MAX_CHARS_PER_SERVICE][MAX_DESCRIPTORS_PER_CHAR + 1];
100 uint8_t dscPacked[MAX_CHARS_PER_SERVICE][MAX_DESCRIPTORS_PER_CHAR][5];
103 GattWriteCallback writeCallbacks[MAX_CHARS_PER_SERVICE];
104 GattReadCallback readCallbacks[MAX_CHARS_PER_SERVICE];
105 uint8_t numChars = 0;
108EXT_RAM_BSS_ATTR
static InternalService s_services[MAX_REGISTERED_SERVICES];
116static void convertUuid(
const BleUuid& src, ble_uuid_any_t& dst) {
118 dst.u.type = BLE_UUID_TYPE_16;
119 dst.u16.u.type = BLE_UUID_TYPE_16;
120 dst.u16.value = src.
u16;
122 dst.u.type = BLE_UUID_TYPE_128;
123 dst.u128.u.type = BLE_UUID_TYPE_128;
124 memcpy(dst.u128.value, src.
u128, 16);
134static ble_gatt_chr_flags mapProperties(uint8_t props, uint8_t perms) {
135 ble_gatt_chr_flags
flags = 0;
164EXT_RAM_BSS_ATTR
static uint8_t s_gattAccessBuf[512];
166static int gattServiceAccessCb(uint16_t connHandle, uint16_t attrHandle,
167 struct ble_gatt_access_ctxt* ctxt,
void* arg) {
168 auto* svc =
static_cast<InternalService*
>(arg);
169 if (!svc)
return BLE_ATT_ERR_UNLIKELY;
172 for (uint8_t i = 0; i < svc->numChars; i++) {
173 if (ble_uuid_cmp(ctxt->chr->uuid, &svc->charUuids[i].u) == 0) {
174 if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR && svc->writeCallbacks[i]) {
175 uint16_t len = OS_MBUF_PKTLEN(ctxt->om);
176 if (len >
sizeof(s_gattAccessBuf)) len =
sizeof(s_gattAccessBuf);
177 ble_hs_mbuf_to_flat(ctxt->om, s_gattAccessBuf, len,
nullptr);
178 return svc->writeCallbacks[i](connHandle, attrHandle, s_gattAccessBuf, len);
180 if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR && svc->readCallbacks[i]) {
181 uint16_t len =
sizeof(s_gattAccessBuf);
182 int rc = svc->readCallbacks[i](connHandle, attrHandle, s_gattAccessBuf, &len);
183 if (rc == 0 && len > 0) {
184 os_mbuf_append(ctxt->om, s_gattAccessBuf, len);
192 return BLE_ATT_ERR_UNLIKELY;
202static int gattStaticDescriptorAccessCb(uint16_t connHandle, uint16_t attrHandle,
203 struct ble_gatt_access_ctxt* ctxt,
void* arg) {
206 if (ctxt->op != BLE_GATT_ACCESS_OP_READ_DSC)
return BLE_ATT_ERR_UNLIKELY;
207 if (!arg)
return BLE_ATT_ERR_UNLIKELY;
210 const uint8_t* packed =
static_cast<const uint8_t*
>(arg);
211 uint8_t len = packed[0];
212 return os_mbuf_append(ctxt->om, packed + 1, len) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
220 BluetoothController() {
222 lifecycleMutex_ = xSemaphoreCreateRecursiveMutex();
229 bool init()
override;
230 bool start()
override;
231 void stop()
override;
232 core::ServiceState getState()
const override {
return state_; }
233 const char* getName()
const override {
return "bluetooth"; }
240 bool enable()
override;
241 void disable()
override;
242 bool isEnabled()
const override {
return enabled_; }
243 bool getMacAddress(uint8_t* mac)
const override;
244 void setDeviceName(
const char*
name)
override;
245 const char* getDeviceName()
const override {
return deviceName_; }
246 bool isConnected()
const override;
247 void disconnect()
override;
248 int8_t getRssi()
const override;
254 void startAdvertising()
override;
255 void stopAdvertising()
override;
256 bool isAdvertising()
const override {
return advertising_; }
257 bool addAdvertisingUuid(
const BleUuid& uuid)
override;
258 void removeAdvertisingUuid(
const BleUuid& uuid)
override;
259 bool setAdvertisingManufacturerData(uint16_t companyId,
260 const uint8_t* data, uint16_t len)
override;
261 void clearAdvertisingManufacturerData()
override;
262 void setAppearance(uint16_t appearance)
override;
269 bool startScan(uint32_t durationMs,
bool keepAdvertising)
override;
270 void stopScan()
override;
271 bool isScanComplete()
const override {
return !scanning_; }
272 uint8_t getScanResults(BleScanResult* results, uint8_t maxResults)
override;
279 bool registerGattService(
const GattServiceDef& service,
280 bool pluginReserved =
false)
override;
281 bool unregisterGattService(
const BleUuid& serviceUuid)
override;
282 bool sendNotification(uint16_t connHandle, uint16_t attrHandle,
283 const uint8_t* data, uint16_t len)
override;
284 uint16_t getMtu()
const override;
285 uint16_t getConnectionHandle()
const override {
return primaryConnHandle(); }
286 void clearAllBonds()
override;
293 bool connect(
const uint8_t* addr, uint8_t addrType)
override;
294 void cancelConnect()
override;
295 bool discoverServiceByUuid(uint16_t connHandle,
const BleUuid& uuid)
override;
296 bool writeCharacteristic(uint16_t connHandle, uint16_t attrHandle,
297 const uint8_t* data, uint16_t len,
298 bool withResponse)
override;
299 bool readCharacteristic(uint16_t connHandle, uint16_t attrHandle)
override;
300 bool enableNotifications(uint16_t connHandle, uint16_t cccdHandle)
override;
301 void disconnectHandle(uint16_t connHandle)
override;
302 ListenerToken addServiceDiscoveryCallback(ServiceDiscoveryCallback cb)
override;
303 ListenerToken addCharacteristicReadCallback(CharacteristicReadCallback cb)
override;
304 ListenerToken addNotificationCallback(NotificationCallback cb)
override;
305 ListenerToken addWriteCompleteCallback(WriteCompleteCallback cb)
override;
306 void removeServiceDiscoveryCallback(ListenerToken token)
override;
307 void removeCharacteristicReadCallback(ListenerToken token)
override;
308 void removeNotificationCallback(ListenerToken token)
override;
309 void removeWriteCompleteCallback(ListenerToken token)
override;
310 void setServiceDiscoveryCallback(ServiceDiscoveryCallback cb)
override;
311 void setCharacteristicReadCallback(CharacteristicReadCallback cb)
override;
312 void setNotificationCallback(NotificationCallback cb)
override;
313 void setWriteCompleteCallback(WriteCompleteCallback cb)
override;
320 ListenerToken addConnectionCallback(ConnectionCallback cb)
override;
321 ListenerToken addDisconnectionCallback(DisconnectionCallback cb)
override;
322 void removeConnectionCallback(ListenerToken token)
override;
323 void removeDisconnectionCallback(ListenerToken token)
override;
330 ListenerToken addNumericComparisonCallback(NumericComparisonCallback cb)
override;
331 void removeNumericComparisonCallback(ListenerToken token)
override;
332 void setNumericComparisonCallback(NumericComparisonCallback cb)
override;
333 void respondToNumericComparison(uint16_t connHandle,
bool accept)
override;
334 void setPasskeyCallback(PasskeyCallback cb)
override;
335 void setAuthCompleteCallback(AuthCompleteCallback cb)
override;
336 ListenerToken addEncryptionChangeCallback(EncChangeCallback cb)
override;
337 void removeEncryptionChangeCallback(ListenerToken token)
override;
338 bool initiateSecurity(uint16_t connHandle)
override;
339 bool getPeerIdAddr(uint16_t connHandle, uint8_t addr[6], uint8_t* addrType)
const override;
340 void forgetBond(
const uint8_t addr[6], uint8_t addrType)
override;
341 uint8_t getBondedDevices(BleBondInfo* out, uint8_t maxCount)
const override;
348 void onConnect(uint16_t connHandle,
bool isPeripheral);
349 void onDisconnect(uint16_t connHandle,
int reason);
351 void onScanResult(
const ble_gap_disc_desc* disc);
352 void onScanComplete();
353 void onAdvComplete();
354 void onPasskeyAction(uint16_t connHandle,
const ble_gap_passkey_params* params);
355 void onEncChange(uint16_t connHandle,
int status);
356 void onSubscribe(
const struct ble_gap_event* event);
357 void onMtuExchange(uint16_t connHandle, uint16_t mtu);
363 template <
typename CB>
364 struct ListenerSlot {
372 struct ConnectionState {
374 uint16_t handle = BLE_HS_CONN_HANDLE_NONE;
375 bool isPeripheral =
false;
382 struct SubscribeEntry {
390 uint16_t primaryConnHandle()
const;
391 int8_t findConnectionSlot(uint16_t handle)
const;
394 core::ServiceState state_ = core::ServiceState::UNINITIALIZED;
401 SemaphoreHandle_t lifecycleMutex_ =
nullptr;
403 bool enabled_ =
false;
404 bool synced_ =
false;
405 bool advertising_ =
false;
406 bool scanning_ =
false;
407 bool scanWasAdvertising_ =
false;
408 char deviceName_[32] =
"CDC Badge";
409 uint8_t ownAddrType_ = BLE_OWN_ADDR_PUBLIC;
412 ConnectionState connections_[MAX_CONNECTIONS] = {};
415 SubscribeEntry subscribes_[MAX_SUBSCRIBE_ENTRIES] = {};
418 BleScanResult scanResults_[MAX_SCAN_RESULTS] = {};
421 bool scanNameComplete_[MAX_SCAN_RESULTS] = {};
422 uint8_t scanResultCount_ = 0;
425 BleUuid advUuids_[MAX_ADV_UUIDS] = {};
426 uint8_t advUuidCount_ = 0;
429 uint16_t appearance_ = 0;
431 ListenerSlot<ConnectionCallback> connCallbacks_[MAX_CONN_CALLBACKS] = {};
432 ListenerSlot<DisconnectionCallback> disconnCallbacks_[MAX_CONN_CALLBACKS] = {};
433 ListenerSlot<NumericComparisonCallback> numCmpCallbacks_[MAX_CONN_CALLBACKS] = {};
434 ListenerSlot<ServiceDiscoveryCallback> svcDiscoveryCallbacks_[MAX_CONN_CALLBACKS] = {};
435 ListenerSlot<CharacteristicReadCallback> charReadCallbacks_[MAX_CONN_CALLBACKS] = {};
436 ListenerSlot<NotificationCallback> notifyCallbacks_[MAX_CONN_CALLBACKS] = {};
437 ListenerSlot<WriteCompleteCallback> writeCompleteCallbacks_[MAX_CONN_CALLBACKS] = {};
438 ListenerSlot<EncChangeCallback> encChangeCallbacks_[MAX_CONN_CALLBACKS] = {};
441 PasskeyCallback passkeyCb_;
442 AuthCompleteCallback authCompleteCb_;
445 DiscoveredService discoveredSvc_ = {};
446 BleUuid discoverTargetUuid_ = {};
447 uint16_t discoverSvcStart_ = 0;
448 uint16_t discoverSvcEnd_ = 0;
451 uint8_t mfgData_[31] = {};
452 uint16_t mfgDataLen_ = 0;
453 uint16_t mfgCompanyId_ = 0;
454 bool mfgDataSet_ =
false;
458 static BluetoothController* instance_;
459 friend void bleHostTask(
void* param);
460 friend int bleGapEventCallback(
struct ble_gap_event* event,
void* arg);
461 friend int gattcSvcDiscCb(uint16_t,
const struct ble_gatt_error*,
462 const struct ble_gatt_svc*,
void*);
463 friend int gattcChrDiscCb(uint16_t,
const struct ble_gatt_error*,
464 const struct ble_gatt_chr*,
void*);
465 friend int gattcReadCb(uint16_t,
const struct ble_gatt_error*,
466 struct ble_gatt_attr*,
void*);
467 friend int gattcWriteCb(uint16_t,
const struct ble_gatt_error*,
468 struct ble_gatt_attr*,
void*);
474BluetoothController* BluetoothController::instance_ =
nullptr;
482int bleGapEventCallback(
struct ble_gap_event* event,
void* arg) {
484 auto* ctrl = BluetoothController::instance_;
487 switch (event->type) {
488 case BLE_GAP_EVENT_CONNECT:
489 if (event->connect.status == 0) {
490 struct ble_gap_conn_desc desc;
491 bool isPeripheral =
true;
492 if (ble_gap_conn_find(event->connect.conn_handle, &desc) == 0) {
493 isPeripheral = (desc.role == BLE_GAP_ROLE_SLAVE);
495 ctrl->onConnect(event->connect.conn_handle, isPeripheral);
497 LOG_W(
TAG,
"Connection failed, status=%d", event->connect.status);
501 case BLE_GAP_EVENT_DISCONNECT:
502 ctrl->onDisconnect(event->disconnect.conn.conn_handle,
503 event->disconnect.reason);
506 case BLE_GAP_EVENT_CONN_UPDATE:
510 case BLE_GAP_EVENT_ADV_COMPLETE:
512 ctrl->onAdvComplete();
515 case BLE_GAP_EVENT_MTU:
516 LOG_I(
TAG,
"MTU updated: handle=%d value=%d",
517 event->mtu.conn_handle, event->mtu.value);
518 ctrl->onMtuExchange(event->mtu.conn_handle, event->mtu.value);
521 case BLE_GAP_EVENT_DISC:
522 ctrl->onScanResult(&event->disc);
525 case BLE_GAP_EVENT_DISC_COMPLETE:
527 ctrl->onScanComplete();
530 case BLE_GAP_EVENT_PASSKEY_ACTION:
531 ctrl->onPasskeyAction(event->passkey.conn_handle,
532 &event->passkey.params);
535 case BLE_GAP_EVENT_ENC_CHANGE:
536 ctrl->onEncChange(event->enc_change.conn_handle,
537 event->enc_change.status);
540 case BLE_GAP_EVENT_REPEAT_PAIRING: {
542 struct ble_gap_conn_desc desc;
543 if (ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc) == 0) {
544 ble_store_util_delete_peer(&desc.peer_id_addr);
546 return BLE_GAP_REPEAT_PAIRING_RETRY;
549 case BLE_GAP_EVENT_SUBSCRIBE:
550 ctrl->onSubscribe(event);
553 case BLE_GAP_EVENT_NOTIFY_RX:
554 if (event->notify_rx.om) {
555 uint16_t len = OS_MBUF_PKTLEN(event->notify_rx.om);
556 if (len >
sizeof(s_gattAccessBuf)) len =
sizeof(s_gattAccessBuf);
557 ble_hs_mbuf_to_flat(event->notify_rx.om, s_gattAccessBuf, len,
nullptr);
558 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
559 if (ctrl->notifyCallbacks_[i].active &&
560 ctrl->notifyCallbacks_[i].callback) {
561 ctrl->notifyCallbacks_[i].callback(
562 event->notify_rx.conn_handle,
563 event->notify_rx.attr_handle,
564 s_gattAccessBuf, len);
581static void bleSyncCallback() {
582 if (BluetoothController::instance_) {
583 BluetoothController::instance_->onSync();
592static void bleResetCallback(
int reason) {
593 LOG_E(
TAG,
"BLE host reset, reason=%d", reason);
601static void bleHostTask(
void* param) {
603 LOG_I(
TAG,
"NimBLE host task started");
605 nimble_port_freertos_deinit();
612bool BluetoothController::init() {
623 ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
626 LOG_I(
TAG,
"Bluetooth controller initialized");
634bool BluetoothController::start() {
646void BluetoothController::stop() {
647 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
660bool BluetoothController::enable() {
661 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
667 LOG_E(
TAG,
"Cannot enable - service not started");
672 esp_err_t ret = nimble_port_init();
674 LOG_E(
TAG,
"nimble_port_init failed: %d", ret);
679 ble_hs_cfg.reset_cb = bleResetCallback;
680 ble_hs_cfg.sync_cb = bleSyncCallback;
681 ble_hs_cfg.gatts_register_cb =
nullptr;
682 ble_hs_cfg.store_status_cb =
nullptr;
685 ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_YES_NO;
686 ble_hs_cfg.sm_bonding = 1;
687 ble_hs_cfg.sm_mitm = 1;
688 ble_hs_cfg.sm_sc = 1;
689 ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
690 ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
698 for (
int i = 0; i < MAX_REGISTERED_SERVICES; i++) {
699 if (!s_services[i].active)
continue;
700 int grc = ble_gatts_count_cfg(s_services[i].nimbleSvcs);
701 if (grc == 0) grc = ble_gatts_add_svcs(s_services[i].nimbleSvcs);
703 LOG_E(
TAG,
"Deferred GATT service slot %d commit failed: %d", i, grc);
705 LOG_I(
TAG,
"Committed GATT service slot %d", i);
710 ble_store_config_init();
713 ble_svc_gap_device_name_set(deviceName_);
716 nimble_port_freertos_init(bleHostTask);
726void BluetoothController::disable() {
727 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
740 advertising_ =
false;
746 ble_gap_disc_cancel();
748 scanWasAdvertising_ =
false;
750 ble_gap_conn_cancel();
758 vTaskDelay(pdMS_TO_TICKS(kConnectSettleMs));
760 for (;; waited += kDisconnectDrainPollMs) {
761 bool anyActive =
false;
762 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) {
763 if (connections_[i].active) {
764 ble_gap_terminate(connections_[i].handle, BLE_ERR_REM_USER_CONN_TERM);
768 if (!anyActive)
break;
769 if (waited >= kDisconnectDrainTimeoutMs) {
770 LOG_W(
TAG,
"Disconnect drain timed out, forcing BLE shutdown");
773 vTaskDelay(pdMS_TO_TICKS(kDisconnectDrainPollMs));
777 int rc = nimble_port_stop();
780 vTaskDelay(pdMS_TO_TICKS(50));
781 nimble_port_deinit();
784 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) connections_[i].active =
false;
785 for (uint8_t i = 0; i < MAX_SUBSCRIBE_ENTRIES; i++) subscribes_[i].active =
false;
795bool BluetoothController::getMacAddress(uint8_t* mac)
const {
796 if (!mac)
return false;
798 if (enabled_ && synced_) {
800 int rc = ble_hs_id_copy_addr(ownAddrType_, mac,
nullptr);
805 esp_read_mac(mac, ESP_MAC_BT);
813void BluetoothController::setDeviceName(
const char*
name) {
816 strncpy(deviceName_,
name,
sizeof(deviceName_) - 1);
817 deviceName_[
sizeof(deviceName_) - 1] =
'\0';
819 if (enabled_ && synced_) {
820 ble_svc_gap_device_name_set(deviceName_);
827void BluetoothController::disconnect() {
828 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) {
829 if (connections_[i].active) {
830 ble_gap_terminate(connections_[i].handle, BLE_ERR_REM_USER_CONN_TERM);
835bool BluetoothController::isConnected()
const {
836 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) {
837 if (connections_[i].active)
return true;
842uint16_t BluetoothController::primaryConnHandle()
const {
843 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) {
844 if (connections_[i].active)
return connections_[i].handle;
846 return BLE_HS_CONN_HANDLE_NONE;
849int8_t BluetoothController::findConnectionSlot(uint16_t handle)
const {
850 for (int8_t i = 0; i < (int8_t)MAX_CONNECTIONS; i++) {
851 if (connections_[i].active && connections_[i].handle == handle)
return i;
856void BluetoothController::clearAllBonds() {
857 int rc = ble_store_clear();
859 LOG_E(
TAG,
"ble_store_clear failed: %d", rc);
865void BluetoothController::onEncChange(uint16_t connHandle,
int status) {
866 LOG_I(
TAG,
"Encryption %s on handle %d (status=%d)",
867 status == 0 ?
"established" :
"failed", connHandle, status);
868 if (authCompleteCb_) {
869 authCompleteCb_(status == 0);
871 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
872 if (encChangeCallbacks_[i].active && encChangeCallbacks_[i].callback) {
873 encChangeCallbacks_[i].callback(connHandle, status);
878void BluetoothController::onMtuExchange(uint16_t connHandle, uint16_t mtu) {
879 int8_t slot = findConnectionSlot(connHandle);
881 connections_[slot].mtu = mtu;
885void BluetoothController::onSubscribe(
const struct ble_gap_event* event) {
887 LOG_I(
TAG,
"Subscribe: handle=%d attr=%d notify=%d indicate=%d",
888 event->subscribe.conn_handle, event->subscribe.attr_handle,
889 event->subscribe.cur_notify, event->subscribe.cur_indicate);
893 for (
int i = 0; i < MAX_SUBSCRIBE_ENTRIES; i++) {
894 if (subscribes_[i].active &&
895 subscribes_[i].connHandle == event->subscribe.conn_handle &&
896 subscribes_[i].attrHandle == event->subscribe.attr_handle) {
902 for (
int i = 0; i < MAX_SUBSCRIBE_ENTRIES; i++) {
903 if (!subscribes_[i].active) { slot = i;
break; }
910 subscribes_[slot].active =
true;
911 subscribes_[slot].connHandle =
event->subscribe.conn_handle;
912 subscribes_[slot].attrHandle =
event->subscribe.attr_handle;
913 subscribes_[slot].notify =
event->subscribe.cur_notify != 0;
914 subscribes_[slot].indicate =
event->subscribe.cur_indicate != 0;
915 if (!subscribes_[slot].notify && !subscribes_[slot].indicate) {
916 subscribes_[slot].active =
false;
924int8_t BluetoothController::getRssi()
const {
925 uint16_t handle = primaryConnHandle();
926 if (handle == BLE_HS_CONN_HANDLE_NONE) {
931 int rc = ble_gap_conn_rssi(handle, &rssi);
932 return (rc == 0) ? rssi : 0;
939void BluetoothController::onConnect(uint16_t connHandle,
bool isPeripheral) {
940 advertising_ =
false;
941 LOG_I(
TAG,
"Device connected (handle=%d, role=%s)",
942 connHandle, isPeripheral ?
"peripheral" :
"central");
946 for (
int i = 0; i < MAX_CONNECTIONS; i++) {
947 if (!connections_[i].active) { slot = i;
break; }
950 connections_[slot].active =
true;
951 connections_[slot].handle = connHandle;
952 connections_[slot].isPeripheral = isPeripheral;
953 connections_[slot].mtu = 23;
955 LOG_W(
TAG,
"Connection table full, dropping handle %d", connHandle);
963 ble_gattc_exchange_mtu(connHandle,
nullptr,
nullptr);
967 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
968 if (connCallbacks_[i].active && connCallbacks_[i].callback) {
969 connCallbacks_[i].callback(connHandle);
979void BluetoothController::onDisconnect(uint16_t connHandle,
int reason) {
980 LOG_I(
TAG,
"Device disconnected (handle=%d reason=%d)", connHandle, reason);
982 bool wasPeripheral =
true;
983 int8_t slot = findConnectionSlot(connHandle);
985 wasPeripheral = connections_[slot].isPeripheral;
986 connections_[slot].active =
false;
990 for (uint8_t i = 0; i < MAX_SUBSCRIBE_ENTRIES; i++) {
991 if (subscribes_[i].active && subscribes_[i].connHandle == connHandle) {
992 subscribes_[i].active =
false;
997 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
998 if (disconnCallbacks_[i].active && disconnCallbacks_[i].callback) {
999 disconnCallbacks_[i].callback(connHandle, reason);
1004 advertising_ =
false;
1005 if (wasPeripheral) {
1013void BluetoothController::onSync() {
1017 int rc = ble_hs_util_ensure_addr(0);
1019 LOG_E(
TAG,
"Failed to ensure address: %d", rc);
1023 rc = ble_hs_id_infer_auto(0, &ownAddrType_);
1025 LOG_E(
TAG,
"Failed to infer address type: %d", rc);
1026 ownAddrType_ = BLE_OWN_ADDR_PUBLIC;
1030 ble_hs_id_copy_addr(ownAddrType_, addr,
nullptr);
1031 LOG_I(
TAG,
"BLE synced, addr=%02X:%02X:%02X:%02X:%02X:%02X",
1032 addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
1045void BluetoothController::startAdvertising() {
1046 if (!enabled_ || !synced_)
return;
1051 advertising_ =
false;
1054 struct ble_gap_adv_params advParams = {};
1055 advParams.conn_mode = BLE_GAP_CONN_MODE_UND;
1056 advParams.disc_mode = BLE_GAP_DISC_MODE_GEN;
1057 advParams.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
1058 advParams.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MAX;
1064 ble_uuid16_t uuid16s[MAX_ADV_UUIDS];
1065 ble_uuid128_t uuid128s[MAX_ADV_UUIDS];
1066 uint8_t num16 = 0, num128 = 0;
1067 for (uint8_t i = 0; i < advUuidCount_; i++) {
1069 uuid16s[num16].u.type = BLE_UUID_TYPE_16;
1070 uuid16s[num16].value = advUuids_[i].u16;
1073 uuid128s[num128].u.type = BLE_UUID_TYPE_128;
1074 memcpy(uuid128s[num128].value, advUuids_[i].u128, 16);
1080 struct ble_hs_adv_fields fields = {};
1081 fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
1082 fields.tx_pwr_lvl_is_present = 1;
1083 fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
1084 if (appearance_ != 0) {
1085 fields.appearance = appearance_;
1086 fields.appearance_is_present = 1;
1089 fields.uuids16 = uuid16s;
1090 fields.num_uuids16 = num16;
1091 fields.uuids16_is_complete = 1;
1094 fields.uuids128 = uuid128s;
1095 fields.num_uuids128 = num128;
1096 fields.uuids128_is_complete = 1;
1098 fields.name = (uint8_t*)deviceName_;
1099 fields.name_len = strlen(deviceName_);
1100 fields.name_is_complete = 1;
1105 bool nameInScanRsp =
false;
1106 bool uuid128InScanRsp =
false;
1107 int rc = ble_gap_adv_set_fields(&fields);
1108 if (rc == BLE_HS_EMSGSIZE) {
1109 fields.name =
nullptr;
1110 fields.name_len = 0;
1111 fields.name_is_complete = 0;
1112 nameInScanRsp =
true;
1113 rc = ble_gap_adv_set_fields(&fields);
1115 if (rc == BLE_HS_EMSGSIZE && num128 > 0) {
1116 fields.uuids128 =
nullptr;
1117 fields.num_uuids128 = 0;
1118 fields.uuids128_is_complete = 0;
1119 uuid128InScanRsp =
true;
1120 rc = ble_gap_adv_set_fields(&fields);
1123 LOG_E(
TAG,
"Failed to set adv fields: %d", rc);
1129 if (nameInScanRsp || uuid128InScanRsp || mfgDataSet_) {
1130 struct ble_hs_adv_fields rsp = {};
1132 if (nameInScanRsp) {
1133 rsp.name = (uint8_t*)deviceName_;
1134 rsp.name_len = strlen(deviceName_);
1135 rsp.name_is_complete = 1;
1137 if (uuid128InScanRsp) {
1138 rsp.uuids128 = uuid128s;
1139 rsp.num_uuids128 = num128;
1140 rsp.uuids128_is_complete = 1;
1144 uint8_t mfgAdvBuf[33];
1146 mfgAdvBuf[0] = mfgCompanyId_ & 0xFF;
1147 mfgAdvBuf[1] = (mfgCompanyId_ >> 8) & 0xFF;
1148 memcpy(mfgAdvBuf + 2, mfgData_, mfgDataLen_);
1149 rsp.mfg_data = mfgAdvBuf;
1150 rsp.mfg_data_len = mfgDataLen_ + 2;
1153 rc = ble_gap_adv_rsp_set_fields(&rsp);
1155 LOG_W(
TAG,
"Failed to set scan response: %d (continuing without)", rc);
1159 rc = ble_gap_adv_start(ownAddrType_,
nullptr, BLE_HS_FOREVER,
1160 &advParams, bleGapEventCallback,
nullptr);
1162 LOG_E(
TAG,
"Failed to start advertising: %d", rc);
1166 advertising_ =
true;
1167 LOG_I(
TAG,
"Advertising started (%d service UUIDs)", advUuidCount_);
1173void BluetoothController::stopAdvertising() {
1174 if (!advertising_)
return;
1177 advertising_ =
false;
1184void BluetoothController::onAdvComplete() {
1185 advertising_ =
false;
1192void BluetoothController::setAppearance(uint16_t appearance) {
1193 if (appearance_ == appearance)
return;
1194 appearance_ = appearance;
1195 if (enabled_ && synced_ && advertising_) {
1209bool BluetoothController::startScan(uint32_t durationMs,
bool keepAdvertising) {
1210 if (!enabled_ || !synced_ || scanning_)
return false;
1215 bool wasAdvertising = advertising_;
1216 if (advertising_ && !keepAdvertising) {
1218 advertising_ =
false;
1219 LOG_D(
TAG,
"Stopped advertising for scan");
1223 scanResultCount_ = 0;
1224 memset(scanResults_, 0,
sizeof(scanResults_));
1225 memset(scanNameComplete_, 0,
sizeof(scanNameComplete_));
1227 struct ble_gap_disc_params discParams = {};
1228 discParams.filter_duplicates = 0;
1229 discParams.passive = 0;
1230 discParams.itvl = 0;
1231 discParams.window = 0;
1232 discParams.filter_policy = 0;
1233 discParams.limited = 0;
1236 int32_t duration = (durationMs == 0) ? BLE_HS_FOREVER
1237 : static_cast<int32_t>(durationMs);
1238 int rc = ble_gap_disc(ownAddrType_, duration, &discParams,
1239 bleGapEventCallback,
nullptr);
1241 LOG_E(
TAG,
"Failed to start scan: %d", rc);
1243 if (wasAdvertising && !keepAdvertising) {
1250 scanWasAdvertising_ = keepAdvertising ? false : wasAdvertising;
1252 LOG_I(
TAG,
"Scan started (%lu ms%s)", (
unsigned long)durationMs,
1253 keepAdvertising ?
", adv kept" :
"");
1260void BluetoothController::stopScan() {
1261 if (!scanning_)
return;
1263 ble_gap_disc_cancel();
1280static bool parseAdvName(
const uint8_t* data, uint8_t dataLen,
1281 char*
name,
size_t nameMaxLen,
bool* isComplete =
nullptr) {
1283 while (pos < dataLen) {
1284 uint8_t len = data[pos];
1285 if (len == 0 || pos + len > dataLen)
break;
1286 uint8_t type = data[pos + 1];
1288 if (type == 0x09 || type == 0x08) {
1289 uint8_t nameLen = len - 1;
1290 size_t copyLen = (nameLen < nameMaxLen - 1) ? nameLen : nameMaxLen - 1;
1291 memcpy(
name, &data[pos + 2], copyLen);
1292 name[copyLen] =
'\0';
1293 if (isComplete) *isComplete = (type == 0x09);
1309static void fillScanResult(
BleScanResult& result,
const ble_gap_disc_desc* disc,
1310 bool* outNameComplete) {
1311 memcpy(result.
mac, disc->addr.val, 6);
1313 result.
rssi = disc->rssi;
1314 result.
name[0] =
'\0';
1317 ? disc->length_data : sizeof(result.advData);
1320 bool isComplete =
false;
1321 parseAdvName(disc->data, disc->length_data, result.
name,
sizeof(result.
name), &isComplete);
1322 const bool haveName = (result.
name[0] !=
'\0');
1323 if (outNameComplete) *outNameComplete = haveName && isComplete;
1325 snprintf(result.
name,
sizeof(result.
name),
"%02X:%02X:%02X:%02X:%02X:%02X",
1326 result.
mac[5], result.
mac[4], result.
mac[3],
1327 result.
mac[2], result.
mac[1], result.
mac[0]);
1331void BluetoothController::onScanResult(
const ble_gap_disc_desc* disc) {
1335 for (uint8_t i = 0; i < scanResultCount_; i++) {
1336 if (memcmp(scanResults_[i].mac, disc->addr.val, 6) == 0) {
1337 if (disc->rssi > scanResults_[i].rssi) {
1338 scanResults_[i].rssi = disc->rssi;
1344 char parsedName[32];
1345 bool isComplete =
false;
1346 if (parseAdvName(disc->data, disc->length_data, parsedName,
sizeof(parsedName), &isComplete) &&
1347 parsedName[0] !=
'\0' &&
1348 (isComplete || !scanNameComplete_[i]) &&
1349 strcmp(parsedName, scanResults_[i].
name) != 0) {
1350 strncpy(scanResults_[i].
name, parsedName,
sizeof(scanResults_[i].
name) - 1);
1351 scanResults_[i].name[
sizeof(scanResults_[i].name) - 1] =
'\0';
1352 scanNameComplete_[i] = isComplete;
1353 LOG_D(
TAG,
"Name updated: %s (evt=0x%02X)", parsedName, disc->event_type);
1362 if (scanResultCount_ < MAX_SCAN_RESULTS) {
1363 slot = scanResultCount_++;
1365 uint8_t weakest = 0;
1366 for (uint8_t i = 1; i < scanResultCount_; i++) {
1367 if (scanResults_[i].rssi < scanResults_[weakest].rssi) weakest = i;
1369 if (disc->rssi <= scanResults_[weakest].rssi)
return;
1373 bool nameComplete =
false;
1374 fillScanResult(scanResults_[slot], disc, &nameComplete);
1375 scanNameComplete_[slot] = nameComplete;
1376 LOG_D(
TAG,
"Found: %s (RSSI %d) evt=0x%02X dlen=%d",
1377 scanResults_[slot].
name, scanResults_[slot].rssi,
1378 disc->event_type, disc->length_data);
1384void BluetoothController::onScanComplete() {
1386 LOG_I(
TAG,
"Scan complete, found %d devices", scanResultCount_);
1389 if (scanWasAdvertising_) {
1390 scanWasAdvertising_ =
false;
1401uint8_t BluetoothController::getScanResults(
BleScanResult* results, uint8_t maxResults) {
1402 if (!results || maxResults == 0)
return 0;
1404 uint8_t count = (scanResultCount_ < maxResults) ? scanResultCount_ : maxResults;
1405 memcpy(results, scanResults_, count *
sizeof(
BleScanResult));
1418bool BluetoothController::registerGattService(
const GattServiceDef& service,
1419 bool pluginReserved) {
1420 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
1426 ble_uuid_any_t wantUuid;
1427 convertUuid(service.
uuid, wantUuid);
1428 const int firstSlot = pluginReserved ? PLUGIN_SERVICE_SLOT : 0;
1429 const int lastSlot = pluginReserved ? MAX_REGISTERED_SERVICES : PLUGIN_SERVICE_SLOT;
1431 for (
int i = firstSlot; i < lastSlot; i++) {
1432 if (s_services[i].active && ble_uuid_cmp(&s_services[i].svcUuid.u, &wantUuid.u) == 0) {
1437 for (
int i = firstSlot; i < lastSlot; i++) {
1438 if (!s_services[i].active) { slot = i;
break; }
1442 LOG_E(
TAG,
"No free GATT service slots (max %d)", MAX_REGISTERED_SERVICES);
1446 auto& s = s_services[slot];
1447 memset(&s, 0,
sizeof(InternalService));
1450 convertUuid(service.
uuid, s.svcUuid);
1453 uint8_t numChars = std::min(service.
numCharacteristics, (uint8_t)MAX_CHARS_PER_SERVICE);
1454 s.numChars = numChars;
1456 for (uint8_t i = 0; i < numChars; i++) {
1458 auto& dst = s.nimbleChars[i];
1460 convertUuid(src.uuid, s.charUuids[i]);
1462 dst.uuid = &s.charUuids[i].u;
1463 dst.access_cb = gattServiceAccessCb;
1465 dst.descriptors =
nullptr;
1466 dst.flags = mapProperties(src.properties, src.permissions);
1467 dst.min_key_size = 0;
1468 dst.val_handle = src.valueHandle;
1471 uint8_t numDsc = src.numDescriptors;
1472 if (numDsc > MAX_DESCRIPTORS_PER_CHAR) numDsc = MAX_DESCRIPTORS_PER_CHAR;
1473 for (uint8_t d = 0; d < numDsc; d++) {
1475 uint16_t uuid16 = 0;
1478 default: uuid16 = 0;
break;
1480 if (uuid16 == 0)
continue;
1482 s.dscUuids[i][d].u.
type = BLE_UUID_TYPE_16;
1483 s.dscUuids[i][d].u16.u.type = BLE_UUID_TYPE_16;
1484 s.dscUuids[i][d].u16.value = uuid16;
1487 uint8_t copyLen = (gd.dataLen <= 4) ? gd.dataLen : 4;
1488 s.dscPacked[i][d][0] = copyLen;
1489 memcpy(&s.dscPacked[i][d][1], gd.data, copyLen);
1491 s.dscDefs[i][d].uuid = &s.dscUuids[i][d].u;
1492 s.dscDefs[i][d].att_flags = BLE_ATT_F_READ;
1493 s.dscDefs[i][d].min_key_size = 0;
1494 s.dscDefs[i][d].access_cb = gattStaticDescriptorAccessCb;
1495 s.dscDefs[i][d].arg = s.dscPacked[i][d];
1499 memset(&s.dscDefs[i][numDsc], 0,
sizeof(ble_gatt_dsc_def));
1500 dst.descriptors = s.dscDefs[i];
1503 s.writeCallbacks[i] = src.onWrite;
1504 s.readCallbacks[i] = src.onRead;
1508 memset(&s.nimbleChars[numChars], 0,
sizeof(ble_gatt_chr_def));
1511 s.nimbleSvcs[0].type = BLE_GATT_SVC_TYPE_PRIMARY;
1512 s.nimbleSvcs[0].uuid = &s.svcUuid.u;
1513 s.nimbleSvcs[0].includes =
nullptr;
1514 s.nimbleSvcs[0].characteristics = s.nimbleChars;
1515 memset(&s.nimbleSvcs[1], 0,
sizeof(ble_gatt_svc_def));
1523 LOG_I(
TAG,
"GATT service stored, deferred until BLE enable (slot %d)", slot);
1534 LOG_E(
TAG,
"GATT service register: BLE restart failed (slot %d)", slot);
1537 LOG_I(
TAG,
"GATT service registered via BLE restart (slot %d, %d chars)", slot, numChars);
1541bool BluetoothController::unregisterGattService(
const BleUuid& serviceUuid) {
1542 cdc::core::RecursiveMutexGuard guard(lifecycleMutex_);
1543 ble_uuid_any_t wantUuid;
1544 convertUuid(serviceUuid, wantUuid);
1546 for (
int i = 0; i < MAX_REGISTERED_SERVICES; i++) {
1547 if (s_services[i].active && ble_uuid_cmp(&s_services[i].svcUuid.u, &wantUuid.u) == 0) {
1551 if (slot < 0)
return false;
1553 memset(&s_services[slot], 0,
sizeof(InternalService));
1558 LOG_I(
TAG,
"GATT service unregistered (slot %d, deferred)", slot);
1567 LOG_E(
TAG,
"GATT service unregister: BLE restart failed (slot %d)", slot);
1570 LOG_I(
TAG,
"GATT service unregistered via BLE restart (slot %d)", slot);
1582bool BluetoothController::sendNotification(uint16_t connHandle, uint16_t attrHandle,
1583 const uint8_t* data, uint16_t len) {
1584 if (!enabled_ || !data || len == 0) {
1588 auto notifyOne = [&](uint16_t handle) ->
bool {
1590 bool subscribed =
false;
1591 for (uint8_t i = 0; i < MAX_SUBSCRIBE_ENTRIES; i++) {
1592 if (subscribes_[i].active &&
1593 subscribes_[i].connHandle == handle &&
1594 subscribes_[i].attrHandle == attrHandle &&
1595 subscribes_[i].notify) {
1600 if (!subscribed)
return false;
1602 struct os_mbuf* om = ble_hs_mbuf_from_flat(data, len);
1604 LOG_E(
TAG,
"Failed to allocate mbuf for notification");
1607 int rc = ble_gatts_notify_custom(handle, attrHandle, om);
1611 os_mbuf_free_chain(om);
1617 if (connHandle == 0xFFFF || connHandle == BLE_HS_CONN_HANDLE_NONE) {
1619 for (uint8_t i = 0; i < MAX_CONNECTIONS; i++) {
1620 if (connections_[i].active) {
1621 if (notifyOne(connections_[i].handle)) any =
true;
1626 return notifyOne(connHandle);
1633uint16_t BluetoothController::getMtu()
const {
1634 uint16_t handle = primaryConnHandle();
1635 if (handle == BLE_HS_CONN_HANDLE_NONE) {
1638 uint16_t mtu = ble_att_mtu(handle);
1639 return (mtu > 3) ? (mtu - 3) : 20;
1651bool BluetoothController::addAdvertisingUuid(
const BleUuid& uuid) {
1653 for (uint8_t i = 0; i < advUuidCount_; i++) {
1654 if (advUuids_[i] == uuid)
return true;
1657 if (advUuidCount_ >= MAX_ADV_UUIDS) {
1658 LOG_E(
TAG,
"Max advertising UUIDs reached (%d)", MAX_ADV_UUIDS);
1662 advUuids_[advUuidCount_++] = uuid;
1663 LOG_I(
TAG,
"Advertising UUID registered (%d total)", advUuidCount_);
1666 if (enabled_ && synced_) {
1680void BluetoothController::removeAdvertisingUuid(
const BleUuid& uuid) {
1681 for (uint8_t i = 0; i < advUuidCount_; i++) {
1682 if (advUuids_[i] == uuid) {
1684 for (uint8_t j = i; j < advUuidCount_ - 1; j++) {
1685 advUuids_[j] = advUuids_[j + 1];
1688 LOG_I(
TAG,
"Advertising UUID removed (%d remaining)", advUuidCount_);
1708template <
typename CB,
size_t N>
1710 BluetoothController::ListenerSlot<CB> (&slots)[N], CB cb) {
1712 for (
size_t i = 0; i < N; i++) {
1713 if (!slots[i].active) {
1714 slots[i].active =
true;
1715 slots[i].callback = cb;
1725template <
typename CB,
size_t N>
1726static void removeListener(
1727 BluetoothController::ListenerSlot<CB> (&slots)[N],
1729 if (token >= N)
return;
1730 slots[token].active =
false;
1731 slots[token].callback =
nullptr;
1735BluetoothController::addConnectionCallback(ConnectionCallback cb) {
1736 return addListener(connCallbacks_, cb);
1740BluetoothController::addDisconnectionCallback(DisconnectionCallback cb) {
1741 return addListener(disconnCallbacks_, cb);
1744void BluetoothController::removeConnectionCallback(
ListenerToken token) {
1745 removeListener(connCallbacks_, token);
1748void BluetoothController::removeDisconnectionCallback(
ListenerToken token) {
1749 removeListener(disconnCallbacks_, token);
1760void BluetoothController::setPasskeyCallback(PasskeyCallback cb) {
1768void BluetoothController::setAuthCompleteCallback(AuthCompleteCallback cb) {
1769 authCompleteCb_ = cb;
1773BluetoothController::addNumericComparisonCallback(NumericComparisonCallback cb) {
1774 return addListener(numCmpCallbacks_, cb);
1777void BluetoothController::removeNumericComparisonCallback(
ListenerToken token) {
1778 removeListener(numCmpCallbacks_, token);
1781void BluetoothController::setNumericComparisonCallback(NumericComparisonCallback cb) {
1782 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) numCmpCallbacks_[i].active =
false;
1783 if (cb) addListener(numCmpCallbacks_, cb);
1787BluetoothController::addEncryptionChangeCallback(EncChangeCallback cb) {
1788 return addListener(encChangeCallbacks_, cb);
1791void BluetoothController::removeEncryptionChangeCallback(
ListenerToken token) {
1792 removeListener(encChangeCallbacks_, token);
1800bool BluetoothController::initiateSecurity(uint16_t connHandle) {
1801 int rc = ble_gap_security_initiate(connHandle);
1803 if (rc != 0 && rc != BLE_HS_EALREADY) {
1804 LOG_W(
TAG,
"ble_gap_security_initiate failed: %d", rc);
1817bool BluetoothController::getPeerIdAddr(uint16_t connHandle, uint8_t addr[6],
1818 uint8_t* addrType)
const {
1819 if (!addr || !addrType)
return false;
1820 struct ble_gap_conn_desc desc = {};
1821 if (ble_gap_conn_find(connHandle, &desc) != 0)
return false;
1822 std::memcpy(addr, desc.peer_id_addr.val, 6);
1823 *addrType = desc.peer_id_addr.type;
1832void BluetoothController::forgetBond(
const uint8_t addr[6], uint8_t addrType) {
1834 ble_addr_t peer = {};
1835 peer.type = addrType;
1836 std::memcpy(peer.val, addr, 6);
1837 int rc = ble_gap_unpair(&peer);
1839 LOG_W(
TAG,
"ble_gap_unpair failed: %d", rc);
1851uint8_t BluetoothController::getBondedDevices(
BleBondInfo* out, uint8_t maxCount)
const {
1852 if (!out || maxCount == 0)
return 0;
1854 ble_addr_t peers[MAX_BONDS] = {};
1856 if (ble_store_util_bonded_peers(peers, &numPeers, MAX_BONDS) != 0) {
1861 for (
int i = 0; i < numPeers && count < maxCount; i++) {
1862 std::memcpy(out[count].addr, peers[i].val, 6);
1863 out[count].addrType = peers[i].type;
1864 out[count].connected =
false;
1865 for (uint8_t c = 0; c < MAX_CONNECTIONS; c++) {
1866 if (!connections_[c].active)
continue;
1867 struct ble_gap_conn_desc desc = {};
1868 if (ble_gap_conn_find(connections_[c].handle, &desc) != 0)
continue;
1869 if (desc.peer_id_addr.type == peers[i].type &&
1870 std::memcmp(desc.peer_id_addr.val, peers[i].val, 6) == 0) {
1871 out[count].connected =
true;
1885void BluetoothController::respondToNumericComparison(uint16_t connHandle,
bool accept) {
1886 struct ble_sm_io pkey = {};
1887 pkey.action = BLE_SM_IOACT_NUMCMP;
1888 pkey.numcmp_accept = accept ? 1 : 0;
1889 int rc = ble_sm_inject_io(connHandle, &pkey);
1891 LOG_E(
TAG,
"ble_sm_inject_io failed: %d", rc);
1893 LOG_I(
TAG,
"Pairing %s", accept ?
"accepted" :
"rejected");
1901void BluetoothController::onPasskeyAction(uint16_t connHandle,
1902 const ble_gap_passkey_params* params) {
1903 if (!params)
return;
1905 switch (params->action) {
1906 case BLE_SM_IOACT_NUMCMP: {
1907 LOG_I(
TAG,
"Numeric comparison: %06lu", (
unsigned long)params->numcmp);
1908 LOG_I(
TAG,
"%s stack free: %lu words", pcTaskGetName(
nullptr),
1909 (
unsigned long)uxTaskGetStackHighWaterMark(
nullptr));
1910 bool dispatched =
false;
1911 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
1912 if (numCmpCallbacks_[i].active && numCmpCallbacks_[i].callback) {
1913 numCmpCallbacks_[i].callback(connHandle, params->numcmp);
1919 LOG_W(
TAG,
"No numeric comparison callback registered, rejecting");
1920 respondToNumericComparison(connHandle,
false);
1925 case BLE_SM_IOACT_DISP:
1926 LOG_I(
TAG,
"Display passkey: %06lu", (
unsigned long)params->numcmp);
1928 passkeyCb_(params->numcmp);
1933 LOG_W(
TAG,
"Unhandled passkey action: %d", params->action);
1949bool BluetoothController::setAdvertisingManufacturerData(uint16_t companyId,
1950 const uint8_t* data, uint16_t len) {
1951 if (!data || len >
sizeof(mfgData_))
return false;
1953 memcpy(mfgData_, data, len);
1955 mfgCompanyId_ = companyId;
1968void BluetoothController::clearAdvertisingManufacturerData() {
1969 mfgDataSet_ =
false;
1990int gattcChrDiscCb(uint16_t connHandle,
const struct ble_gatt_error* error,
1991 const struct ble_gatt_chr* chr,
void* arg) {
1993 auto* ctrl = BluetoothController::instance_;
1994 if (!ctrl)
return 0;
1996 if (error->status == 0 && chr) {
1997 auto& svc = ctrl->discoveredSvc_;
1999 auto& dc = svc.characteristics[svc.numCharacteristics];
2003 if (chr->uuid.u.type == BLE_UUID_TYPE_16) {
2005 }
else if (chr->uuid.u.type == BLE_UUID_TYPE_32) {
2007 uint8_t u128[16] = { 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00,
2008 0x00, 0x80, 0x00, 0x10, 0x00, 0x00,
2010 uint32_t v = chr->uuid.u32.value;
2011 u128[12] = v & 0xFF;
2012 u128[13] = (v >> 8) & 0xFF;
2013 u128[14] = (v >> 16) & 0xFF;
2014 u128[15] = (v >> 24) & 0xFF;
2019 dc.valueHandle = chr->val_handle;
2020 dc.properties = chr->properties;
2021 svc.numCharacteristics++;
2023 }
else if (error->status == BLE_HS_EDONE) {
2024 LOG_I(
TAG,
"Char discovery done (%d chars)", ctrl->discoveredSvc_.numCharacteristics);
2025 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2026 if (ctrl->svcDiscoveryCallbacks_[i].active &&
2027 ctrl->svcDiscoveryCallbacks_[i].callback) {
2028 ctrl->svcDiscoveryCallbacks_[i].callback(
2029 connHandle, &ctrl->discoveredSvc_,
true);
2033 LOG_E(
TAG,
"Char discovery error: %d", error->status);
2034 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2035 if (ctrl->svcDiscoveryCallbacks_[i].active &&
2036 ctrl->svcDiscoveryCallbacks_[i].callback) {
2037 ctrl->svcDiscoveryCallbacks_[i].callback(connHandle,
nullptr,
true);
2053int gattcSvcDiscCb(uint16_t connHandle,
const struct ble_gatt_error* error,
2054 const struct ble_gatt_svc* service,
void* arg) {
2056 auto* ctrl = BluetoothController::instance_;
2057 if (!ctrl)
return 0;
2059 auto dispatchFailure = [&]() {
2060 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2061 if (ctrl->svcDiscoveryCallbacks_[i].active &&
2062 ctrl->svcDiscoveryCallbacks_[i].callback) {
2063 ctrl->svcDiscoveryCallbacks_[i].callback(connHandle,
nullptr,
true);
2068 if (error->status == 0 && service) {
2069 ctrl->discoverSvcStart_ = service->start_handle;
2070 ctrl->discoverSvcEnd_ = service->end_handle;
2071 LOG_I(
TAG,
"Service found: handles %d-%d",
2072 service->start_handle, service->end_handle);
2073 }
else if (error->status == BLE_HS_EDONE) {
2074 if (ctrl->discoverSvcStart_ != 0) {
2075 int rc = ble_gattc_disc_all_chrs(connHandle,
2076 ctrl->discoverSvcStart_,
2077 ctrl->discoverSvcEnd_,
2078 gattcChrDiscCb,
nullptr);
2080 LOG_E(
TAG,
"ble_gattc_disc_all_chrs failed: %d", rc);
2084 LOG_W(
TAG,
"Service not found on remote device");
2088 LOG_E(
TAG,
"Service discovery error: %d", error->status);
2103int gattcReadCb(uint16_t connHandle,
const struct ble_gatt_error* error,
2104 struct ble_gatt_attr* attr,
void* arg) {
2106 auto* ctrl = BluetoothController::instance_;
2107 if (!ctrl)
return 0;
2109 if (error->status == 0 && attr && attr->om) {
2110 uint16_t len = OS_MBUF_PKTLEN(attr->om);
2111 if (len >
sizeof(s_gattAccessBuf)) len =
sizeof(s_gattAccessBuf);
2112 ble_hs_mbuf_to_flat(attr->om, s_gattAccessBuf, len,
nullptr);
2113 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2114 if (ctrl->charReadCallbacks_[i].active &&
2115 ctrl->charReadCallbacks_[i].callback) {
2116 ctrl->charReadCallbacks_[i].callback(
2117 connHandle, attr->handle, s_gattAccessBuf, len);
2121 LOG_E(
TAG,
"GATT read failed: %d", error->status);
2122 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2123 if (ctrl->charReadCallbacks_[i].active &&
2124 ctrl->charReadCallbacks_[i].callback) {
2125 ctrl->charReadCallbacks_[i].callback(connHandle, 0,
nullptr, 0);
2141int gattcWriteCb(uint16_t connHandle,
const struct ble_gatt_error* error,
2142 struct ble_gatt_attr* attr,
void* arg) {
2144 auto* ctrl = BluetoothController::instance_;
2145 if (!ctrl)
return 0;
2147 uint16_t handle = attr ? attr->handle : 0;
2148 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) {
2149 if (ctrl->writeCompleteCallbacks_[i].active &&
2150 ctrl->writeCompleteCallbacks_[i].callback) {
2151 ctrl->writeCompleteCallbacks_[i].callback(connHandle, handle, error->status);
2168bool BluetoothController::connect(
const uint8_t* addr, uint8_t addrType) {
2169 if (!enabled_ || !synced_ || !addr)
return false;
2174 advertising_ =
false;
2178 bleAddr.type = addrType;
2179 memcpy(bleAddr.val, addr, 6);
2181 int rc = ble_gap_connect(ownAddrType_, &bleAddr, 10000,
nullptr,
2182 bleGapEventCallback,
nullptr);
2184 LOG_E(
TAG,
"ble_gap_connect failed: %d", rc);
2189 LOG_I(
TAG,
"Connecting to %02X:%02X:%02X:%02X:%02X:%02X",
2190 addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
2197void BluetoothController::cancelConnect() {
2198 ble_gap_conn_cancel();
2207bool BluetoothController::discoverServiceByUuid(uint16_t connHandle,
const BleUuid& uuid) {
2208 if (!enabled_)
return false;
2211 memset(&discoveredSvc_, 0,
sizeof(discoveredSvc_));
2212 discoveredSvc_.uuid = uuid;
2213 discoverTargetUuid_ = uuid;
2214 discoverSvcStart_ = 0;
2215 discoverSvcEnd_ = 0;
2217 ble_uuid_any_t nimbleUuid;
2218 convertUuid(uuid, nimbleUuid);
2220 int rc = ble_gattc_disc_svc_by_uuid(connHandle, &nimbleUuid.u,
2221 gattcSvcDiscCb,
nullptr);
2223 LOG_E(
TAG,
"ble_gattc_disc_svc_by_uuid failed: %d", rc);
2227 LOG_I(
TAG,
"Service discovery started (connHandle=%d)", connHandle);
2240bool BluetoothController::writeCharacteristic(uint16_t connHandle, uint16_t attrHandle,
2241 const uint8_t* data, uint16_t len,
2242 bool withResponse) {
2243 if (!enabled_ || !data || len == 0)
return false;
2247 rc = ble_gattc_write_flat(connHandle, attrHandle, data, len,
2248 gattcWriteCb,
nullptr);
2253 rc = ble_gattc_write_no_rsp_flat(connHandle, attrHandle, data, len);
2257 LOG_E(
TAG,
"GATT write failed: %d", rc);
2270bool BluetoothController::readCharacteristic(uint16_t connHandle, uint16_t attrHandle) {
2271 if (!enabled_)
return false;
2273 int rc = ble_gattc_read(connHandle, attrHandle, gattcReadCb,
nullptr);
2275 LOG_E(
TAG,
"ble_gattc_read failed: %d", rc);
2288bool BluetoothController::enableNotifications(uint16_t connHandle, uint16_t cccdHandle) {
2289 if (!enabled_)
return false;
2291 uint8_t val[2] = { 0x01, 0x00 };
2292 int rc = ble_gattc_write_flat(connHandle, cccdHandle, val,
sizeof(val),
2293 gattcWriteCb,
nullptr);
2295 LOG_E(
TAG,
"Enable notifications failed: %d", rc);
2299 LOG_I(
TAG,
"Notifications enabled (cccd=%d)", cccdHandle);
2307void BluetoothController::disconnectHandle(uint16_t connHandle) {
2308 ble_gap_terminate(connHandle, BLE_ERR_REM_USER_CONN_TERM);
2312BluetoothController::addServiceDiscoveryCallback(ServiceDiscoveryCallback cb) {
2313 return addListener(svcDiscoveryCallbacks_, cb);
2316BluetoothController::addCharacteristicReadCallback(CharacteristicReadCallback cb) {
2317 return addListener(charReadCallbacks_, cb);
2320BluetoothController::addNotificationCallback(NotificationCallback cb) {
2321 return addListener(notifyCallbacks_, cb);
2324BluetoothController::addWriteCompleteCallback(WriteCompleteCallback cb) {
2325 return addListener(writeCompleteCallbacks_, cb);
2328void BluetoothController::removeServiceDiscoveryCallback(
ListenerToken t) {
2329 removeListener(svcDiscoveryCallbacks_, t);
2331void BluetoothController::removeCharacteristicReadCallback(
ListenerToken t) {
2332 removeListener(charReadCallbacks_, t);
2334void BluetoothController::removeNotificationCallback(
ListenerToken t) {
2335 removeListener(notifyCallbacks_, t);
2337void BluetoothController::removeWriteCompleteCallback(
ListenerToken t) {
2338 removeListener(writeCompleteCallbacks_, t);
2341void BluetoothController::setServiceDiscoveryCallback(ServiceDiscoveryCallback cb) {
2342 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) svcDiscoveryCallbacks_[i].active =
false;
2343 if (cb) addListener(svcDiscoveryCallbacks_, cb);
2345void BluetoothController::setCharacteristicReadCallback(CharacteristicReadCallback cb) {
2346 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) charReadCallbacks_[i].active =
false;
2347 if (cb) addListener(charReadCallbacks_, cb);
2349void BluetoothController::setNotificationCallback(NotificationCallback cb) {
2350 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) notifyCallbacks_[i].active =
false;
2351 if (cb) addListener(notifyCallbacks_, cb);
2353void BluetoothController::setWriteCompleteCallback(WriteCompleteCallback cb) {
2354 for (uint8_t i = 0; i < MAX_CONN_CALLBACKS; i++) writeCompleteCallbacks_[i].active =
false;
2355 if (cb) addListener(writeCompleteCallbacks_, cb);
2386 LOG_W(
TAG,
"Bluetooth disabled (NimBLE not configured)");
2400 const char*
getName()
const override {
return "bluetooth"; }
2403 LOG_W(
TAG,
"Cannot enable - NimBLE not configured in sdkconfig");
2414 if (mac) esp_read_mac(mac, ESP_MAC_BT);
2415 return mac !=
nullptr;
2423 strncpy(deviceName_,
name,
sizeof(deviceName_) - 1);
2433 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