Closed Tschmidt9490 closed 1 week ago
I'm assuming you found the issue? Was it something in the library?
I made a mistake with some connection for my BLE HID remote. It seems to be fixed and had nothing to do with the App implementation side of the code. The heap is now under control.
I do have a reconnection issue though. I am almost giving up on getting this remote to reconnect after a reboot of the esp32...
Thanks. What is happening with reconnecting? Some changes were made lately to help reconnecting with bonded devices.
It does not seem to do it at all actually. Once i restart i need to have a button pressed on the remote. If not no device is shown which seems okay. I guess the remote is going in to sleep mode if no button is pressed for X time.
I use a slightly modified version of the "NimBLE_client.ino" example to accomodate the HID spec.
static uint32_t scanTime = 0; // Scan time in seconds
int bleServers = 0; bool doConnect = false; NimBLEAdvertisedDevice *advertisedDevice = nullptr; bool commandProcessed = false; char hexStr[5] = {0}; extern unsigned long lastUserActivityTime;
static NimBLEUUID batteryServiceUUID("180F"); static NimBLEUUID batteryLevelCharUUID("2A19");
extern bool remoteDebug;
void setHexStr(const char* value) { strncpy(hexStr, value, sizeof(hexStr) - 1); hexStr[sizeof(hexStr) - 1] = '\0'; // Ensure null termination commandProcessed = false; // Reset the processed flag when a new value is set }
const char* getHexStr() { return hexStr; }
/ None of these are required as they will be handled by the library with defaults. Remove as you see fit for your needs / void ClientCallbacks::onConnect(NimBLEClient pClient) { Serial.println("Connected"); / After connection we should change the parameters if we don't need fast response times.
Min interval: 120 1.25ms = 150, Max interval: 120 1.25ms = 150, 0 latency, 60 10ms = 600ms timeout / pClient->updateConnParams(120,120,0,60); } void ClientCallbacks::onDisconnect(NimBLEClient* pClient) { Serial.print(pClient->getPeerAddress().toString().c_str()); Serial.println(" Disconnected - Starting scan"); NimBLEDevice::getScan()->start(scanTime, scanEndedCB); }
/** Called when the peripheral requests a change to the connection parameters.
/ Pairing process complete, we can check the results in ble_gap_conn_desc / void onAuthenticationComplete(ble_gap_conn_desc desc){ if(!desc->sec_state.encrypted) { Serial.println("Encrypt connection failed - disconnecting"); / Find the client with the connection handle provided in desc */ NimBLEDevice::getClientByID(desc->conn_handle)->disconnect(); return; } }
void AdvertisedDeviceCallbacks::onResult(NimBLEAdvertisedDevice *ad) { if (ad->isAdvertisingService(NimBLEUUID(HID_SERVICE))) { NimBLEDevice::getScan()->stop(); } advertisedDevice = ad; doConnect = true; }
void handleBatteryLevelNotification(uint8_t* data, size_t length) { if (length > 0) { int batteryLevel = data[0]; // Assuming the battery level is the first byte int barValue;
// Convert battery percentage to a value between 1 and 4 based on intervals
if (batteryLevel <= 25) {
barValue = 1;
} else if (batteryLevel <= 50) {
barValue = 2;
} else if (batteryLevel <= 75) {
barValue = 3;
} else {
barValue = 4;
}
if (remoteDebug) {
Serial.print("Battery Level: ");
Serial.print(batteryLevel);
Serial.println("%");
Serial.print("Bar Value: ");
Serial.println(barValue);
}
lv_bar_set_value(ui_Bar1, barValue, LV_ANIM_ON);
}
}
void handleHIDNotification(uint8_t* data, size_t length) { if ((length == 8) || (length == 3)) { // Determine the target index based on the data length size_t targetIndex = (length == 8) ? 2 : 0;
if (remoteDebug) {
Serial.println("Data Received:");
Serial.printf("Length: %zu, Targeting byte at index: %zu\n", length, targetIndex);
char hexStr[3]; // Buffer for 2 hex digits + null terminator
snprintf(hexStr, sizeof(hexStr), "%02X", data[targetIndex]); // Convert the targeted byte to hex string
Serial.printf("Byte at Index %zu in Hex: %s\n", targetIndex, hexStr);
setHexStr(hexStr);
}
} else {
if (remoteDebug) {
Serial.println("Received data length does not meet the required criteria.");
}
}
}
void buttonPressCallback(NimBLERemoteCharacteristic characteristic, uint8_t data, size_t length, bool is_notify) { if (!is_notify) return; // If the callback was not triggered by a notification, exit lastUserActivityTime = millis(); // update last user activity // Get the UUID of the characteristic that triggered the notification NimBLEUUID charUUID = characteristic->getUUID();
// Print the characteristic UUID
if (remoteDebug) {
Serial.print("Notification received from characteristic: ");
Serial.println(charUUID.toString().c_str());
}
// Check if this is a notification from the Battery Level characteristic
if (charUUID.equals(NimBLEUUID((uint16_t)0x2A19))) {
handleBatteryLevelNotification(data, length);
} else {
handleHIDNotification(data, length);
}
}
void scanEndedCB(NimBLEScanResults results) { Serial.println("Scan Ended"); }
static ClientCallbacks clientCB;
bool connectToServer() { lastUserActivityTime = millis(); lv_label_set_text(ui_Label10, "Connecting remote"); lv_obj_set_style_text_font(ui_Label10, &lv_font_montserrat_28, LV_PART_MAIN | LV_STATE_DEFAULT); int startTime = millis(); NimBLEClient *pClient = nullptr;
if (NimBLEDevice::getClientListSize()) {
pClient = NimBLEDevice::getClientByPeerAddress(advertisedDevice->getAddress());
if (pClient) {
if (!pClient->connect(advertisedDevice, false)) {
if (remoteDebug) {Serial.println("reconnect failed");}
return false;
}
if (remoteDebug) {Serial.println("reconnected client");}
} else { pClient = NimBLEDevice::getDisconnectedClient(); }
}
/** No client to reuse? Create a new one. */
if(!pClient) {
if(NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) {
Serial.println("Max clients reached - no more connections available");
return false;
}
pClient = NimBLEDevice::createClient();
Serial.println("New client created");
pClient->setClientCallbacks(&clientCB, false);
/** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout.
* These settings are safe for 3 clients to connect reliably, can go faster if you have less
* connections. Timeout should be a multiple of the interval, minimum is 100ms.
* Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout
*/
pClient->setConnectionParams(12,12,0,51);
/** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */
pClient->setConnectTimeout(20);
if (!pClient->connect(advertisedDevice)) {
/** Created a client but failed to connect, don't need to keep it as it has no data */
NimBLEDevice::deleteClient(pClient);
Serial.println("Failed to connect, deleted client");
return false;
}
}
if (!pClient->isConnected()) {
if (!pClient->connect(advertisedDevice)) {
if (remoteDebug) {
Serial.println("failed to connect");
}
return false;
}
}
// Use the discovered services and characteristics
auto services = pClient->getServices(true);
static NimBLEUUID hidServiceUUID(HID_SERVICE);
for (auto service : *services) {
auto serviceUUID = service->getUUID();
if (serviceUUID.equals(hidServiceUUID)) {
if (remoteDebug) {
Serial.println("-> Found HID service");
}
auto characteristics = service->getCharacteristics(true);
for (auto characteristic : *characteristics) {
if (remoteDebug) {
Serial.print("---> Found characteristic: ");
Serial.println(characteristic->getUUID().toString().c_str());
}
if (characteristic->canRead()) {
if (remoteDebug) {
Serial.println("-----> Can read...");
}
std::vector<uint8_t> value = characteristic->readValue();
if (remoteDebug) {
Serial.print("Value: ");
for (auto byte : value) {
Serial.print(byte, HEX);
Serial.print(" ");
}
Serial.println();
}
}
if (characteristic->canNotify() || characteristic->canIndicate()) {
if (remoteDebug) {
Serial.println("-----> Can notify/indicate. Subscribing...");
}
if (!characteristic->subscribe(true, buttonPressCallback, true)) {
if (remoteDebug) {
Serial.println("subscribe notification failed");
}
pClient->disconnect();
return false;
}
if (remoteDebug) {
Serial.println("Subscribed successfully");
}
}
}
}
// Check for Battery Service
else if (serviceUUID.equals(batteryServiceUUID)) {
if (remoteDebug) {
Serial.println("-> Found Battery Service");
}
auto characteristics = service->getCharacteristics(true);
for (auto characteristic : *characteristics) {
if (characteristic->getUUID().equals(batteryLevelCharUUID)) {
if (remoteDebug) {
Serial.println("---> Found Battery Level Characteristic");
}
// Read battery level if characteristic is readable
if (characteristic->canRead()) {
if (remoteDebug) {
Serial.println("-----> Reading Battery Level...");
}
std::vector<uint8_t> value = characteristic->readValue();
if (remoteDebug) {
Serial.print("Battery Level: ");
Serial.println((int)value[0]); // Assuming battery level is the first byte
}
}
// Subscribe to battery level notifications
if (characteristic->canNotify()) {
if (remoteDebug) {
Serial.println("-----> Subscribing to Battery Level Notifications...");
}
characteristic->subscribe(true, buttonPressCallback, true);
}
}
}
}
}
char timeString[4];
float seconds = (millis() - startTime) / 1000.0;
snprintf(timeString, sizeof(timeString), "%3.2f", seconds);
if (remoteDebug) {
Serial.print("completed connection after ");
Serial.println(timeString);
}
return true;
}
void initRemoteClient(bool initDevice) { if (initDevice) { NimBLEDevice::init(""); NimBLEDevice::setPower(CLIENT_POWER); NimBLEDevice::setSecurityAuth(true, true, true); }
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); //use numeric comparison
/** create new scan */
NimBLEScan* pScan = NimBLEDevice::getScan();
/** create a callback that gets called when advertisers are found */
pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
pScan->setInterval(45);
pScan->setWindow(15);
pScan->setActiveScan(true);
pScan->start(scanTime, scanEndedCB);
}
void remoteTick() { if (doConnect) { doConnect = false; if (!connectToServer()) { if (remoteDebug) { Serial.println("failed to connect, starting scan"); } //NimBLEDevice::getScan()->start(scanTime,scanEndedCB); } } }
Now i have got it to reconnect. I had a variable declared at bool and not static bool which somehow made the difference. Yay.
Now i am keen to find a faster reconnect. It seems to need to rediscover all characteristics and resubscribe after a reboot. Is this needed? Should it save the device information for the next reconnect?
Great!
Yes, when reconnecting you should set the deleteAttributes parameter to false so you won't need to discover them again.
the one you are referring to is this one right?
if (!pClient->connect(advertisedDevice,false)) {
/** Created a client but failed to connect, don't need to keep it as it has no data */
NimBLEDevice::deleteClient(pClient);
Serial.println("Failed to connect, deleted client");
resumeOtherTasks();
return false;
}
here is the whole function just for reference
bool connectToServer() { suspendOtherTasks(); lastUserActivityTime = millis(); lv_label_set_text(ui_Label10, "Connecting remote"); lv_obj_set_style_text_font(ui_Label10, &lv_font_montserrat_28, LV_PART_MAIN | LV_STATE_DEFAULT); int startTime = millis(); NimBLEClient *pClient = nullptr;
if (NimBLEDevice::getClientListSize()) {
pClient = NimBLEDevice::getClientByPeerAddress(advertisedDevice->getAddress());
if (pClient) {
if (!pClient->connect(advertisedDevice, false)) {
if (remoteDebug) {Serial.println("reconnect failed");}
resumeOtherTasks();
return false;
}
if (remoteDebug) {Serial.println("reconnected client");}
} else { pClient = NimBLEDevice::getDisconnectedClient(); }
}
/** No client to reuse? Create a new one. */
if(!pClient) {
if(NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) {
Serial.println("Max clients reached - no more connections available");
resumeOtherTasks();
return false;
}
pClient = NimBLEDevice::createClient();
Serial.println("New client created");
pClient->setClientCallbacks(&clientCB, false);
/** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout.
* These settings are safe for 3 clients to connect reliably, can go faster if you have less
* connections. Timeout should be a multiple of the interval, minimum is 100ms.
* Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout
*/
pClient->setConnectionParams(6,12,0,100);
/** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */
pClient->setConnectTimeout(10);
if (!pClient->connect(advertisedDevice,false)) {
/** Created a client but failed to connect, don't need to keep it as it has no data */
NimBLEDevice::deleteClient(pClient);
Serial.println("Failed to connect, deleted client");
resumeOtherTasks();
return false;
}
}
if (!pClient->isConnected()) {
if (!pClient->connect(advertisedDevice)) {
if (remoteDebug) {
Serial.println("failed to connect");
}
resumeOtherTasks();
return false;
}
}
// Use the discovered services and characteristics
auto services = pClient->getServices(true);
static NimBLEUUID hidServiceUUID(HID_SERVICE);
for (auto service : *services) {
suspendOtherTasks();
auto serviceUUID = service->getUUID();
if (serviceUUID.equals(hidServiceUUID)) {
if (remoteDebug) {
Serial.println("-> Found HID service");
}
auto characteristics = service->getCharacteristics(true);
for (auto characteristic : *characteristics) {
suspendOtherTasks();
if (remoteDebug) {
Serial.print("---> Found characteristic: ");
Serial.println(characteristic->getUUID().toString().c_str());
}
if (characteristic->canRead()) {
if (remoteDebug) {
Serial.println("-----> Can read...");
}
std::vector<uint8_t> value = characteristic->readValue();
if (remoteDebug) {
Serial.print("Value: ");
for (auto byte : value) {
Serial.print(byte, HEX);
Serial.print(" ");
}
Serial.println();
}
}
if (characteristic->canNotify() || characteristic->canIndicate()) {
if (remoteDebug) {
Serial.println("-----> Can notify/indicate. Subscribing...");
}
if (!characteristic->subscribe(true, buttonPressCallback, true)) {
if (remoteDebug) {
Serial.println("subscribe notification failed");
}
pClient->disconnect();
resumeOtherTasks();
return false;
}
if (remoteDebug) {
Serial.println("Subscribed successfully");
}
}
resumeOtherTasks();
}
}
// Check for Battery Service
else if (serviceUUID.equals(batteryServiceUUID)) {
if (remoteDebug) {
Serial.println("-> Found Battery Service");
}
auto characteristics = service->getCharacteristics(true);
for (auto characteristic : *characteristics) {
suspendOtherTasks();
if (characteristic->getUUID().equals(batteryLevelCharUUID)) {
if (remoteDebug) {
Serial.println("---> Found Battery Level Characteristic");
}
// Read battery level if characteristic is readable
if (characteristic->canRead()) {
if (remoteDebug) {
Serial.println("-----> Reading Battery Level...");
}
std::vector<uint8_t> value = characteristic->readValue();
if (remoteDebug) {
Serial.print("Battery Level: ");
Serial.println((int)value[0]); // Assuming battery level is the first byte
}
}
// Subscribe to battery level notifications
if (characteristic->canNotify()) {
if (remoteDebug) {
Serial.println("-----> Subscribing to Battery Level Notifications...");
}
characteristic->subscribe(true, buttonPressCallback, true);
}
}
resumeOtherTasks();
}
}
resumeOtherTasks();
}
char timeString[4];
float seconds = (millis() - startTime) / 1000.0;
snprintf(timeString, sizeof(timeString), "%3.2f", seconds);
if (remoteDebug) {
Serial.print("completed connection after ");
Serial.println(timeString);
}
resumeOtherTasks();
return true;
}
i can also see that this
NimBLEDevice::getClientListSize()
Returns a 0 so it does not seem to be stored
i can see that after a successful connection is made with the connect to server that a bond if formed and the ClientListSize is increased by 1. The data is not persistent between reboots though
By pulling the battery of the remote and inserting again and not rebooting i can reconnect. But the code performs a full discovery again
The attributes are not stored in flash, only bond info is if enabled, so you would need to do discovery after reboot.
My heap is slowly getting eaten up over time. This is ultimately causing a reboot of the device over time. I get the following issues.
Backtrace: 0x40083845:0x3fff1d70 0x400940b1:0x3fff1d90 0x40099cd1:0x3fff1db0 0x4017a3fb:0x3fff1e30 0x4017a442:0x3fff1e50 0x4017b1b1:0x3fff1e70 0x4017a568:0x3fff1e90 0x4018abe1:0x3fff1eb0 0x4018f41e:0x3fff1ef0 0x4018f83c:0x3fff1f60 0x40194f26:0x3fff1f80 0x40194d0a:0x3fff1fc0 0x40195144:0x3fff1fe0 0x4019376d:0x3fff2000 0x40090a46:0x3fff2020 0x40188f2a:0x3fff2040
0 0x40083845:0x3fff1d70 in panic_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/panic.c:408
1 0x400940b1:0x3fff1d90 in esp_system_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/esp_system.c:137
2 0x40099cd1:0x3fff1db0 in abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/newlib/abort.c:46
3 0x4017a3fb:0x3fff1e30 in pbus_rx_dco_cal_1step at /home/cff/gittree/chip7.1_phy/chip_7.1/board_code/app_test/pp/phy/phy_chip_v7_cal.c:729
4 0x4017a442:0x3fff1e50 in pbus_rx_dco_cal_1step at /home/cff/gittree/chip7.1_phy/chip_7.1/board_code/app_test/pp/phy/phy_chip_v7_cal.c:738
5 0x4017b1b1:0x3fff1e70 in ram_rfcal_pwrctrl at /home/cff/gittree/chip7.1_phy/chip_7.1/board_code/app_test/pp/phy/phy_chip_v7_cal.c:1685
6 0x4017a568:0x3fff1e90 in pbus_rx_dco_cal_1step at /home/cff/gittree/chip7.1_phy/chip_7.1/board_code/app_test/pp/phy/phy_chip_v7_cal.c:769 (discriminator 1)
7 0x4018abe1:0x3fff1eb0 in NimBLEDevice::setSecurityAuth(unsigned char) at .pio/libdeps/adafruit_feather_esp32_v2/NimBLE-Arduino/src/NimBLEDevice.cpp:1021
8 0x4018f41e:0x3fff1ef0 in ble_att_svr_fill_type_value at .pio/libdeps/adafruit_feather_esp32_v2/NimBLE-Arduino/src/nimble/nimble/host/src/ble_att_svr.c:1159
9 0x4018f83c:0x3fff1f60 in ble_att_svr_prev_handle at .pio/libdeps/adafruit_feather_esp32_v2/NimBLE-Arduino/src/nimble/nimble/host/src/ble_att_svr.c:143
10 0x40194f26:0x3fff1f80 in ble_gatts_count_cfg at .pio/libdeps/adafruit_feather_esp32_v2/NimBLE-Arduino/src/nimble/nimble/host/src/ble_gatts.c:2161
11 0x40194d0a:0x3fff1fc0 in ble_gatts_chr_updated at .pio/libdeps/adafruit_feather_esp32_v2/NimBLE-Arduino/src/nimble/nimble/host/src/ble_gatts.c:1612
12 0x40195144:0x3fff1fe0 in ble_hs_timer_sched at .pio/libdeps/adafruit_feather_esp32_v2/NimBLE-Arduino/src/nimble/nimble/host/src/ble_hs.c:482
13 0x4019376d:0x3fff2000 in ble_gattc_disc_chr_uuid_rx_adata at .pio/libdeps/adafruit_feather_esp32_v2/NimBLE-Arduino/src/nimble/nimble/host/src/ble_gattc.c:2487
14 0x40090a46:0x3fff2020 in ble_npl_event_run at .pio/libdeps/adafruit_feather_esp32_v2/NimBLE-Arduino/src/nimble/porting/npl/freertos/include/nimble/nimble_npl_os.h:526
15 0x40188f2a:0x3fff2040 in std::enable_if<std::and_<std::_not<std::is_tuple_like<unsigned char> >, std::is_move_constructible<unsigned char>, std::is_move_assignable<unsigned char> >::value, void>::type std::swap<unsigned char>(unsigned char&, unsigned char&) at c:\users\scoth.platformio\packages\toolchain-xtensa-esp32\xtensa-esp32-elf\include\c++\8.4.0\bits/move.h:194
Is there a way to track this down?
I believe it might be related to this function in my code
void BLEAppInterface_startAdvertising() { if (!pAdvertising) { pAdvertising = NimBLEDevice::getAdvertising(); } else { pAdvertising->stop(); // Ensure previous advertising is stopped }
}