NimBLE HID + custom service
Hi there,
so I tried to create a service that act as I/O via customService from my phone to my esp32c3 the BLE also act as HID devices if connected (it could be connect silmutaneously)
this works just fine when using bluedroid, but when ported into NimBLE, we got security issue to the HID not writing
- here's my NimBLE version of the script
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("Starting ESP32 NimBLE Test...");
NimBLEDevice::init(DEVICE_NAME);
Serial.println("NimBLE Device initialized.");
NimBLEDevice::setSecurityAuth(true, true, true);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
NimBLEDevice::setSecurityPasskey(PASSKEY);
NimBLEDevice::setMTU(500);
pServer = NimBLEDevice::createServer();
pServer->setCallbacks(&serverCallbacks);
pHid = new NimBLEHIDDevice(pServer);
pInputKeyboard = pHid->getInputReport(KEYBOARD_ID);
pOutputKeyboard = pHid->getOutputReport(KEYBOARD_ID);
pInputMediaKeys = pHid->getInputReport(MEDIA_KEYS_ID);
pHid->setReportMap((uint8_t*)_hidReportDescriptor, sizeof(_hidReportDescriptor));
pHid->setManufacturer("Manufacture"); // Set manufacturer
pHid->setPnp(0x02, 0x1234, 0x5678, 0x0110); // Optional: PnP ID
pHid->setHidInfo(0x00, 0x01); // Set HID info with just version and country code
pHid->startServices(); // This creates the HID service and characteristics
// --- Setup Custom Service ---
pCustomService = pServer->createService(CUSTOM_SERVICE_UUID);
pCustomCharacteristic = pCustomService->createCharacteristic(
CUSTOM_DATA_CHAR_UUID,
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE |
NIMBLE_PROPERTY::WRITE_ENC |
NIMBLE_PROPERTY::NOTIFY
);
pCustomCharacteristic->setCallbacks(&charCallbacks); // Set callbacks for custom char
pCustomCharacteristic->setValue("InitialNimValue");
NimBLEDescriptor* pCCCDesc = pCustomCharacteristic->createDescriptor(
"2902", // Standard CCCD UUID
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE_ENC,
2 // Max length
);
pCustomCharacteristic->addDescriptor(pCCCDesc);
pCustomService->start();
Serial.println("Custom Service started.");
// --- Advertising ---
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID(CUSTOM_SERVICE_UUID); // Add custom service UUID
pAdvertising->addServiceUUID(pHid->getHidService()->getUUID()); // Add HID service UUID
pAdvertising->setAppearance(0x03C1); // HID Keyboard appearance
// pAdvertising->enableScanResponse(true);
NimBLEDevice::startAdvertising();
Serial.println("Advertising started (HID + Custom).");
Serial.println("Setup Complete!");
}
-- and here's my Bluedroid version --
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("Starting ESP32 BLE Test (Adjusted Conn Params)...");
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
BLEDevice::init(DEVICE_NAME);
Serial.println("BLE Device initialized.");
pServer = BLEDevice::createServer();
pServer->setCallbacks(&serverCallbacks);
pHid = new BLEHIDDevice(pServer);
inputKeyboard = pHid->inputReport(KEYBOARD_ID);
outputKeyboard = pHid->outputReport(KEYBOARD_ID);
inputMediaKeys = pHid->inputReport(MEDIA_KEYS_ID);
pHid->reportMap((uint8_t*)_hidReportDescriptor, sizeof(_hidReportDescriptor));
pHid->manufacturer()->setValue("Manufacturer");
// BLE Security configuration using BLESecurity class (as in previous step)
BLESecurity *pSecurity = new BLESecurity();
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND);
pSecurity->setCapability(ESP_IO_CAP_IO);
pSecurity->setKeySize(16);
pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
BLEDevice::setSecurityCallbacks(&securityCallbacks);
pHid->startServices();
Serial.println("BLE Server created.");
pCustomService = pServer->createService(CUSTOM_SERVICE_UUID);
pCustomCharacteristic = pCustomService->createCharacteristic(
CUSTOM_DATA_CHAR_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY
);
BLE2902* p2902Descriptor = new BLE2902();
p2902Descriptor->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE_ENCRYPTED);
pCustomCharacteristic->addDescriptor(p2902Descriptor);
pCustomCharacteristic->setCallbacks(&charCallbacks);
pCustomCharacteristic->setValue("InitialValue");
pCustomService->start();
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
if (pAdvertising) {
pAdvertising->addServiceUUID(CUSTOM_SERVICE_UUID);
pAdvertising->addServiceUUID(pHid->hidService()->getUUID());
pAdvertising->setAppearance(0x03C1);
Serial.println("Advertising configured (Custom + HID, adjusted conn params).");
pAdvertising->setScanResponse(true);
Serial.println("Advertising started.");
} else {
Serial.println("Error: Failed to get Advertising object!");
}
BLEDevice::startAdvertising();
Serial.println("Setup Complete!");
}
things to note that, the setAuthenticationMode when advertising multiple service is tricky,
I assume the bluedroid one could complete bonding with ESP_IO_CAP_IO because of onConfirmPIN security callback did auto confirm,
but the NimBLE behaviour with this kinda difference, when I tried to connect, it immediately got disconnected before even pressing the pair prompt (since there's no security callback to handle this (?))
here's the log of immediate connection fail if using BLE_HS_IO_DISPLAY_ONLY
--- NimBLE Server: Client Connected ---
Conn ID: 1, Address: 64:a6:f2:26:92:51
--- NimBLE Security: onAuthenticationComplete ---
Authentication Failed!
Encrypted: No, Conn Interval: 24
i tried different setSecurityIOCap some could be connected both custom service + HID, but the HID was not triggering anything event after bonding with (BLE_HS_IO_DISPLAY_YESNO)
anyone have experienced this ?
thank you!
I would start by deleting this:
NimBLEDescriptor* pCCCDesc = pCustomCharacteristic->createDescriptor(
"2902", // Standard CCCD UUID
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE_ENC,
2 // Max length
);
pCustomCharacteristic->addDescriptor(pCCCDesc);
It has no functionality in this library, the BLE stack will create it internally.
As for the issue itself I would need to see the logs at debug level. One thing to try is disabling SC:
NimBLEDevice::setSecurityAuth(true, true, false);
Sometimes that messes with things.
Thank you for fast reply!
here's the log debug level
Starting ESP32 NimBLE Test...
Button pins configured.
I NimBLEDevice: BLE Host Task Started
I NimBLEDevice: NimBle host synced.
NimBLE Device initialized.
D NimBLEDevice: Setting bonding: 1, mitm: 1, sc: 0
D NimBLEService: Adding a duplicate characteristic with UUID: 0x2a4d
D NimBLEService: Adding a duplicate characteristic with UUID: 0x2a4d
D NimBLEService: >> start(): Starting service: UUID: 0x180a, handle: 0x0000
D NimBLEService: Adding 2 characteristics for service UUID: 0x180a, handle: 0x0000
D NimBLEService: << start()
D NimBLEService: >> start(): Starting service: UUID: 0x1812, handle: 0x0000
D NimBLEService: Adding 7 characteristics for service UUID: 0x1812, handle: 0x0000
D NimBLEService: << start()
D NimBLEService: >> start(): Starting service: UUID: 0x180f, handle: 0x0000
D NimBLEService: Adding 1 characteristics for service UUID: 0x180f, handle: 0x0000
D NimBLEService: << start()
D NimBLEService: >> start(): Starting service: UUID: 4fafc201-1fb5-459e-8fcc-c5c9c331914b, handle: 0x0000
D NimBLEService: Adding 1 characteristics for service UUID: 4fafc201-1fb5-459e-8fcc-c5c9c331914b, handle: 0x0000
D NimBLEService: << start()
Custom Service started.
Advertising configured (Custom + HID, adjusted conn params).
Advertising started.
D NimBLEAdvertising: >> Advertising start: duration=0, dirAddr=NULL
primary service
uuid 0x1800
handle 1
end_handle 5
characteristic
uuid 0x2a00
def_handle 2
val_handle 3
min_key_size 0
flags [READ]
characteristic
uuid 0x2a01
def_handle 4
val_handle 5
min_key_size 0
flags [READ]
primary service
uuid 0x1801
handle 6
end_handle 13
characteristic
uuid 0x2a05
def_handle 7
val_handle 8
min_key_size 0
flags [INDICATE]
ccc descriptor
uuid 0x2902
handle 9
min_key_size 0
flags [READ|WRITE]
characteristic
uuid 0x2b3a
def_handle 10
val_handle 11
min_key_size 0
flags [READ]
characteristic
uuid 0x2b29
def_handle 12
val_handle 13
min_key_size 0
flags [READ|WRITE]
primary service
uuid 0x180a
handle 14
end_handle 18
characteristic
uuid 0x2a50
def_handle 15
val_handle 16
min_key_size 0
flags [READ]
characteristic
uuid 0x2a29
def_handle 17
val_handle 18
min_key_size 0
flags [READ]
primary service
uuid 0x1812
handle 19
end_handle 38
characteristic
uuid 0x2a4a
def_handle 20
val_handle 21
min_key_size 0
flags [READ]
characteristic
uuid 0x2a4b
def_handle 22
val_handle 23
min_key_size 0
flags [READ]
characteristic
uuid 0x2a4c
def_handle 24
val_handle 25
min_key_size 0
flags [WRITE_NO_RSP]
characteristic
uuid 0x2a4e
def_handle 26
val_handle 27
min_key_size 0
flags [READ|WRITE_NO_RSP]
characteristic
uuid 0x2a4d
def_handle 28
val_handle 29
min_key_size 0
flags [READ|NOTIFY|READ_ENC]
ccc descriptor
uuid 0x2902
handle 30
min_key_size 0
flags [READ|WRITE]
descriptor
uuid 0x2908
handle 31
min_key_size 0
flags [READ|READ_ENC]
characteristic
uuid 0x2a4d
def_handle 32
val_handle 33
min_key_size 0
flags [READ|WRITE_NO_RSP|WRITE|READ_ENC|WRITE_ENC]
descriptor
uuid 0x2908
handle 34
min_key_size 0
flags [READ|WRITE|READ_ENC|WRITE_ENC]
characteristic
uuid 0x2a4d
def_handle 35
val_handle 36
min_key_size 0
flags [READ|NOTIFY|READ_ENC]
ccc descriptor
uuid 0x2902
handle 37
min_key_size 0
flags [READ|WRITE]
descriptor
uuid 0x2908
handle 38
min_key_size 0
flags [READ|READ_ENC]
primary service
uuid 0x180f
handle 39
end_handle 43
characteristic
uuid 0x2a19
def_handle 40
val_handle 41
min_key_size 0
flags [READ|NOTIFY]
ccc descriptor
uuid 0x2902
handle 42
min_key_size 0
flags [READ|WRITE]
descriptor
uuid 0x2904
handle 43
min_key_size 0
flags [READ]
primary service
uuid 4fafc201-1fb5-459e-8fcc-c5c9c331914b
handle 44
end_handle 47
characteristic
uuid 4fafc202-1fb5-459e-8fcc-c5c9c331914b
def_handle 45
val_handle 46
min_key_size 0
flags [WRITE|NOTIFY|READ_ENC]
ccc descriptor
uuid 0x2902
handle 47
min_key_size 0
flags [READ|WRITE]
D NimBLEAdvertising: setAdvertisementData: 02 01 06 03 03 12 18 11 07 4b 91 31 c3 c9 c5 cc 8f 9e 45 b5 1f 01 c2 af 4f 03 19 c1 03
D NimBLEAdvertising: << Advertising start
Advertising started (HID + Custom).
Setup Complete!
D NimBLEServer: >> handleGapEvent:
--- NimBLE Server: Client Connected ---
Conn ID: 1, Address: 78:22:1a:1c:fa:ad
D NimBLEServer: << handleGapEvent
...
D NimBLEServer: >> handleGapEvent:
D NimBLEServerCallbacks: onConnParamsUpdate: default
D NimBLEServer: << handleGapEvent
...
D NimBLEServer: >> handleGapEvent:
D NimBLEServerCallbacks: onConnParamsUpdate: default
D NimBLEServer: << handleGapEvent
...
D NimBLEServer: >> handleGapEvent:
I NimBLEServer: subscribe event; attr_handle=46, subscribed: true
--- NimBLE Char 4fafc202-1fb5-459e-8fcc-c5c9c331914b: Client 78:22:1a:1c:fa:ad subscribed to notifications/indications
D NimBLEServer: << handleGapEvent
...
D NimBLEServer: >> handleGapEvent:
D NimBLEServerCallbacks: onPassKeyDisplay: default: 123456
D NimBLEServer: BLE_SM_IOACT_DISP; ble_sm_inject_io result: 0
D NimBLEServer: << handleGapEvent
...
D NimBLEServer: >> handleGapEvent:
--- NimBLE Security: onAuthenticationComplete ---
Authentication Success!
Bonding Successful!
Encrypted: Yes, Conn Interval: 24
D NimBLEServer: << handleGapEvent
D NimBLEDeviceCallbacks: onStoreStatus: default
D NimBLEDeviceCallbacks: onStoreStatus: default
D NimBLEServer: >> handleGapEvent:
D NimBLEServerCallbacks: onIdentity: default
D NimBLEServer: << handleGapEvent
...
D NimBLEServer: >> handleGapEvent:
I NimBLEServer: subscribe event; attr_handle=8, subscribed: true
D NimBLEServer: << handleGapEvent
D NimBLEDeviceCallbacks: onStoreStatus: default
D NimBLEServer: >> handleGapEvent:
...
D NimBLEServer: << handleGapEvent
D NimBLEServer: Gatt Write event
D NimBLEServer: >> handleGapEvent:
--- NimBLE Custom Char: Write received from 78:22:1a:1c:fa:ad: ANIMATE_SLEEPY (len 14)
D NimBLEServer: << handleGapEvent
...
D NimBLEServer: Gatt Read event
D NimBLECharacteristicCallbacks: onRead: default
D NimBLEServer: >> handleGapEvent:
D NimBLEServer: << handleGapEvent
D NimBLEServer: Gatt Read event
D NimBLECharacteristicCallbacks: onRead: default
D NimBLEServer: Gatt Read event
D NimBLEServer: >> handleGapEvent:
D NimBLEServer: << handleGapEvent
D NimBLEServer: Gatt Read event
D NimBLEServer: Gatt Read event
D NimBLEServer: >> handleGapEvent:
D NimBLEServer: << handleGapEvent
D NimBLEServer: Gatt Read event
...
D NimBLEDescriptorCallbacks: onRead: default
D NimBLEServer: Gatt Read event
D NimBLEDescriptorCallbacks: onRead: default
D NimBLEServer: Gatt Read event
D NimBLEDescriptorCallbacks: onRead: default
D NimBLEServer: Gatt Read event
D NimBLECharacteristicCallbacks: onRead: default
D NimBLEServer: >> handleGapEvent:
D NimBLEServer: << handleGapEvent
D NimBLEServer: >> handleGapEvent:
I NimBLEServer: subscribe event; attr_handle=29, subscribed: true
D NimBLECharacteristicCallbacks: onSubscribe: default
D NimBLEServer: << handleGapEvent
D NimBLEDeviceCallbacks: onStoreStatus: default
D NimBLEServer: Gatt Read event
D NimBLECharacteristicCallbacks: onRead: default
D NimBLEServer: >> handleGapEvent:
I NimBLEServer: subscribe event; attr_handle=36, subscribed: true
D NimBLECharacteristicCallbacks: onSubscribe: default
D NimBLEServer: << handleGapEvent
D NimBLEDeviceCallbacks: onStoreStatus: default
D NimBLEServer: >> handleGapEvent:
D NimBLEServer: << handleGapEvent
D NimBLEServer: >> handleGapEvent:
I NimBLEServer: subscribe event; attr_handle=41, subscribed: true
D NimBLECharacteristicCallbacks: onSubscribe: default
D NimBLEServer: << handleGapEvent
D NimBLEDeviceCallbacks: onStoreStatus: default
D NimBLEServer: >> handleGapEvent:
D NimBLEServer: << handleGapEvent
D NimBLEServer: >> handleGapEvent:
D NimBLEServer: << handleGapEvent
[ 28102][D][Tone.cpp:124] tone(): _pin=7, frequency=1800 Hz, duration=100 ms
[ 28109][V][Tone.cpp:62] tone_init(): Creating tone queue
[ 28114][V][Tone.cpp:68] tone_init(): Tone queue created
[ 28119][V][Tone.cpp:72] tone_init(): Creating tone task
[ 28124][V][Tone.cpp:85] tone_init(): Tone task created
Sending Volume Up via HID...
D NimBLEServer: Gatt Read event
D NimBLEServer: >> handleGapEvent:
D NimBLECharacteristicCallbacks: onStatus: default
D NimBLEServer: << handleGapEvent
[ 28132][D][Tone.cpp:31] tone_task(): Task received from queue TONE_START: _pin=7, frequency=1800 Hz, duration=100 ms
[ 28153][D][Tone.cpp:33] tone_task(): Setup LED controll on channel 0
E (29151) ledc: ledc_get_duty(745): LEDC is not initialized
D NimBLEServer: Gatt Read event
D NimBLEServer: >> handleGapEvent:
D NimBLECharacteristicCallbacks: onStatus: default
D NimBLEServer: << handleGapEvent
D NimBLEServer: >> handleGapEvent:
...
D NimBLEServer: << handleGapEvent
[ 31050][D][Tone.cpp:124] tone(): _pin=7, frequency=1800 Hz, duration=100 ms
Sending Volume Up via HID...
D NimBLEServer: Gatt Read event
D NimBLEServer: >> handleGapEvent:
D NimBLECharacteristicCallbacks: onStatus: default
D NimBLEServer: << handleGapEvent
[ 31060][D][Tone.cpp:31] tone_task(): Task received from queue TONE_START: _pin=7, frequency=1800 Hz, duration=100 ms
[ 31081][D][Tone.cpp:33] tone_task(): Setup LED controll on channel 0
D NimBLEServer: Gatt Read event
D NimBLEServer: >> handleGapEvent:
D NimBLECharacteristicCallbacks: onStatus: default
D NimBLEServer: << handleGapEvent
Yes, I guess the secure part did cause some problem when bond,
I think for the multi advertise connection is working now, im trying figuring out the HID part, im not quite sure if it's related to this or not, but it seems that the HID part still got issue ?
I gonna try with simple sketch
Okay, so, I modified this library https://github.com/T-vK/ESP32-BLE-Keyboard to use the latest version of the NimBLE (https://github.com/h2zero/NimBLE-Arduino/releases/tag/2.2.3)
and test for simple sketch, it doesn't work to my android phone but it does work on my windows pc 😅
okay, so I have tried other HID lib (https://github.com/Mystfit/ESP32-BLE-CompositeHID) with minimal sketch + latest NimBLE result: Keypress works, but Media Keys doesn't work in android (tested from NimBLE version 2.1.0 to latest)
I also tried the https://github.com/T-vK/ESP32-BLE-Keyboard with older version of Nimble (1.4.3) everything works in android
is there any workaround for the android media keys to works in latest NimBLE ?
alright, it's confirmed if i downgrade to version 1.4.3 it works fine for both android and my windows pc (media keys) also works (multi advertise - bond - etc), it might be because of the part characteristic reading from HID (?)
but, since this might be different context from what i report at the first place,i will close this issues,
Thank you @h2zero for the Amazing Lib!
Hmm, downgrading versions isn't really a fix lol. I'm going to repoen this and ask that you test with the master branch.
ah sure, at least the older version was working for me (better than bluedroid in terms of flash and ram 😅), so I planned to use it temporarily;
but yes, is the version in the master branch have potential fix already ? i will try the lattest commit on main and posted the result here later ya
okay, i tested the code on the master, still not fixed
here's the log
the Sending Volume Up via HID... is the event where i sent the HID key to my paired android device,
let me know if you need to to do more testing ya
flags [READ|READ_ENC]
primary service
uuid 0x180f
handle 37
end_handle 41
characteristic
uuid 0x2a19
def_handle 38
val_handle 39
min_key_size 0
flags [READ|NOTIFY]
ccc descriptor
uuid 0x2902
handle 40
min_key_size 0
flags [READ|WRITE]
descriptor
uuid 0x2904
handle 41
min_key_size 0
flags [READ]
D NimBLEAdvertising: setAdvertisementData: 02 01 06 03 19 c1 03 03 03 4a 2a
D NimBLEAdvertising: << Advertising start
[ 507][D][BleKeyboard.cpp:146] begin(): [] Advertising started!
D NimBLEServer: >> handleGapEvent:
...
D NimBLEServerCallbacks: onPhyUpdate: default, txPhy: 2, rxPhy: 2
D NimBLEServer: >> handleGapEvent:
...
D NimBLEServerCallbacks: onIdentity: default
...
D NimBLEServerCallbacks: onAuthenticationComplete: default
D NimBLEServer: << handleGapEvent
...
D NimBLEServerCallbacks: onConnParamsUpdate: default
...
I NimBLEServer: subscribe event; attr_handle=8, subscribed: true
...
D NimBLEServerCallbacks: onConnParamsUpdate: default
D NimBLEServer: << handleGapEvent
D NimBLEServer: Gatt Read event
...
I NimBLEServer: subscribe event; attr_handle=27, subscribed: true
D NimBLECharacteristicCallbacks: onSubscribe: default
...
I NimBLEServer: subscribe event; attr_handle=34, subscribed: true
D NimBLECharacteristicCallbacks: onSubscribe: default
D NimBLEServer: << handleGapEvent
D NimBLEDeviceCallbacks: onStoreStatus: default
D NimBLEServer: >> handleGapEvent:
I NimBLEServer: subscribe event; attr_handle=39, subscribed: true
D NimBLECharacteristicCallbacks: onSubscribe: default
D NimBLEServer: << handleGapEvent
D NimBLEDeviceCallbacks: onStoreStatus: default
1
[ 14650][D][Tone.cpp:124] tone(): _pin=7, frequency=1800 Hz, duration=100 ms
[ 14657][V][Tone.cpp:62] tone_init(): Creating tone queue
[ 14662][V][Tone.cpp:68] tone_init(): Tone queue created
[ 14667][V][Tone.cpp:72] tone_init(): Creating tone task
[ 14673][V][Tone.cpp:85] tone_init(): Tone task created
Sending Volume Up via HID...
D NimBLEServer: Gatt Read event
D NimBLEServer: >> handleGapEvent:
D NimBLECharacteristicCallbacks: onStatus: default
D NimBLEServer: << handleGapEvent
[ 14680][D][Tone.cpp:31] tone_task(): Task received from queue TONE_START: _pin=7, frequency=1800 HzD NimBLEServer: Gatt Read event
D NimBLEServer: >> handleGapEvent:
D NimBLECharacteristicCallbacks: onStatus: default
D NimBLEServer: << handleGapEvent
, duration=100 ms
[ 14716][D][Tone.cpp:33] tone_task(): Setup LED controll on channel 0
E (15717) ledc: ledc_get_duty(745): LEDC is not initialized
[ 20676][D][Tone.cpp:124] tone(): _pin=7, frequency=1800 Hz, duration=100 ms
Sending Volume Up via HID...
D NimBLEServer: Gatt Read event
D NimBLEServer: >> handleGapEvent:
D NimBLECharacteristicCallbacks: onStatus: default
D NimBLEServer: << handleGapEvent
[ 20686][D][Tone.cpp:31] tone_task(): Task received from queue TONE_START: _pin=7, frequency=18D NimBLEServer: Gatt Read event
D NimBLEServer: >> handleGapEvent:
D NimBLECharacteristicCallbacks: onStatus: default
D NimBLEServer: << handleGapEvent
00 Hz, duration=100 ms
...