From 42787eb3cbc67cf9b8e233da7d78badbc5d7ab2f Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Thu, 26 Dec 2024 12:30:31 -0600 Subject: [PATCH 01/23] added CSC to the sensordatafactory --- lib/SS2K/include/sensors/CscSensorData.h | 35 +++++++++ lib/SS2K/src/sensors/CscSensorData.cpp | 90 ++++++++++++++++++++++ lib/SS2K/src/sensors/SensorDataFactory.cpp | 3 + 3 files changed, 128 insertions(+) create mode 100644 lib/SS2K/include/sensors/CscSensorData.h create mode 100644 lib/SS2K/src/sensors/CscSensorData.cpp diff --git a/lib/SS2K/include/sensors/CscSensorData.h b/lib/SS2K/include/sensors/CscSensorData.h new file mode 100644 index 00000000..86fe3245 --- /dev/null +++ b/lib/SS2K/include/sensors/CscSensorData.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 Anthony Doud & Joel Baranick + * All rights reserved + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#pragma once + +#include "SensorData.h" + +class CscSensorData : public SensorData { + public: + CscSensorData() : SensorData("CSC") {} + + bool hasHeartRate(); + bool hasCadence(); + bool hasPower(); + bool hasSpeed(); + bool hasResistance(); + int getHeartRate(); + float getCadence(); + int getPower(); + float getSpeed(); + int getResistance(); + void decode(uint8_t *data, size_t length); + + private: + float cadence = nanf(""); + float speed = nanf(""); + uint32_t lastWheelEventTime = 0; + uint32_t lastCrankEventTime = 0; + uint32_t lastWheelRevolutions = 0; + uint32_t lastCrankRevolutions = 0; +}; \ No newline at end of file diff --git a/lib/SS2K/src/sensors/CscSensorData.cpp b/lib/SS2K/src/sensors/CscSensorData.cpp new file mode 100644 index 00000000..1623c071 --- /dev/null +++ b/lib/SS2K/src/sensors/CscSensorData.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2020 Anthony Doud & Joel Baranick + * All rights reserved + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include "Data.h" +#include "endian.h" +#include "sensors/CscSensorData.h" + +bool CscSensorData::hasHeartRate() { return false; } + +bool CscSensorData::hasCadence() { return !std::isnan(this->cadence); } + +bool CscSensorData::hasPower() { return false; } + +bool CscSensorData::hasSpeed() { return !std::isnan(this->speed); } + +bool CscSensorData::hasResistance() { return false; } + +int CscSensorData::getHeartRate() { return INT_MIN; } + +float CscSensorData::getCadence() { return this->cadence; } + +int CscSensorData::getPower() { return INT_MIN; } + +float CscSensorData::getSpeed() { return this->speed; } + +int CscSensorData::getResistance() { return INT_MIN; } + +void CscSensorData::decode(uint8_t *data, size_t length) { + uint8_t flags = data[0]; + int pos = 1; // Start after flags byte + + // Check wheel revolution data present flag (bit 0) + if (flags & 0x01) { + uint32_t wheelRevolutions = get_le32(&data[pos]); + pos += 4; + uint16_t wheelEventTime = get_le16(&data[pos]); + pos += 2; + + // Calculate speed if we have previous measurements + if (lastWheelEventTime > 0) { + // Handle timer wraparound (16-bit timer) + uint16_t timeDiff = (wheelEventTime >= lastWheelEventTime) ? + (wheelEventTime - lastWheelEventTime) : + (65535 - lastWheelEventTime + wheelEventTime); + + if (timeDiff > 0) { + // Convert to meters/second then km/h + // Time is in 1/1024th of a second + float wheelCircumference = 2.095f; // Default 700c wheel circumference in meters + float revolutions = wheelRevolutions - lastWheelRevolutions; + float timeSeconds = timeDiff / 1024.0f; + float speedMS = (revolutions * wheelCircumference) / timeSeconds; + this->speed = speedMS * 3.6f; // Convert m/s to km/h + } + } + + lastWheelRevolutions = wheelRevolutions; + lastWheelEventTime = wheelEventTime; + } + + // Check crank revolution data present flag (bit 1) + if (flags & 0x02) { + uint16_t crankRevolutions = get_le16(&data[pos]); + pos += 2; + uint16_t crankEventTime = get_le16(&data[pos]); + pos += 2; + + // Calculate cadence if we have previous measurements + if (lastCrankEventTime > 0) { + // Handle timer wraparound (16-bit timer) + uint16_t timeDiff = (crankEventTime >= lastCrankEventTime) ? + (crankEventTime - lastCrankEventTime) : + (65535 - lastCrankEventTime + crankEventTime); + + if (timeDiff > 0) { + // Time is in 1/1024th of a second + float revolutions = crankRevolutions - lastCrankRevolutions; + float timeMinutes = (timeDiff / 1024.0f) / 60.0f; + this->cadence = revolutions / timeMinutes; + } + } + + lastCrankRevolutions = crankRevolutions; + lastCrankEventTime = crankEventTime; + } +} \ No newline at end of file diff --git a/lib/SS2K/src/sensors/SensorDataFactory.cpp b/lib/SS2K/src/sensors/SensorDataFactory.cpp index a2eb32c2..cf6be01b 100644 --- a/lib/SS2K/src/sensors/SensorDataFactory.cpp +++ b/lib/SS2K/src/sensors/SensorDataFactory.cpp @@ -14,6 +14,7 @@ #include "sensors/HeartRateData.h" #include "sensors/EchelonData.h" #include "sensors/PelotonData.h" +#include "sensors/CscSensorData.h" std::shared_ptr SensorDataFactory::getSensorData(const NimBLEUUID characteristicUUID, const uint64_t peerAddress, uint8_t *data, size_t length) { for (auto &it : SensorDataFactory::knownDevices) { @@ -35,6 +36,8 @@ std::shared_ptr SensorDataFactory::getSensorData(const NimBLEUUID ch sensorData = std::shared_ptr(new EchelonData()); } else if (characteristicUUID == PELOTON_DATA_UUID) { sensorData = std::shared_ptr(new PelotonData()); + } else if (characteristicUUID == CSCMEASUREMENT_UUID) { + sensorData = std::shared_ptr(new CscSensorData()); } else { return NULL_SENSOR_DATA; } From 63718a37726df465db3ebd617f436e33e4c7ffac Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Wed, 1 Jan 2025 18:18:16 -0600 Subject: [PATCH 02/23] Supports Multipile Characteristics in notify Queue --- include/BLE_Common.h | 7 +++-- lib/SS2K/include/Constants.h | 4 +-- platformio.ini | 2 +- src/BLE_Client.cpp | 58 +++++++++++++++++------------------- src/BLE_Server.cpp | 2 +- src/Main.cpp | 2 +- 6 files changed, 37 insertions(+), 38 deletions(-) diff --git a/include/BLE_Common.h b/include/BLE_Common.h index 0fe36269..3c895a2a 100644 --- a/include/BLE_Common.h +++ b/include/BLE_Common.h @@ -98,6 +98,7 @@ void bleClientTask(void *pvParameters); // FLYWHEEL_UART_TX_UUID}; typedef struct NotifyData { + NimBLEUUID charUUID; uint8_t data[25]; size_t length; } NotifyData; @@ -136,7 +137,7 @@ class SpinBLEAdvertisedDevice { void set(BLEAdvertisedDevice *device, int id = BLE_HS_CONN_HANDLE_NONE, BLEUUID inServiceUUID = (uint16_t)0x0000, BLEUUID inCharUUID = (uint16_t)0x0000); void reset(); void print(); - bool enqueueData(uint8_t data[25], size_t length); + bool enqueueData(uint8_t data[25], size_t length, NimBLEUUID charUUID); NotifyData dequeueData(); }; @@ -183,10 +184,10 @@ class SpinBLEClient { // Disconnects all devices. They will then be reconnected if scanned and preferred again. void reconnectAllDevices(); - String adevName2UniqueName(NimBLEAdvertisedDevice *inDev); + String adevName2UniqueName(const NimBLEAdvertisedDevice *inDev); }; -class MyAdvertisedDeviceCallback : public NimBLEAdvertisedDeviceCallbacks { +class ScanCallbacks : public NimBLEScanCallbacks { public: void onResult(NimBLEAdvertisedDevice *); }; diff --git a/lib/SS2K/include/Constants.h b/lib/SS2K/include/Constants.h index c30fdee6..f5a66edb 100644 --- a/lib/SS2K/include/Constants.h +++ b/lib/SS2K/include/Constants.h @@ -77,8 +77,8 @@ #define ECHELON_DATA_UUID NimBLEUUID("0bf669f4-45f2-11e7-9598-0800200c9a66") // Dummy UUID for Peloton Serial Data Interface -#define PELOTON_DATA_UUID NimBLEUUID("00000000-0000-0000-0000-0000000000321") -#define PELOTON_ADDRESS NimBLEAddress("00:00:00:00:00:00:00") +#define PELOTON_DATA_UUID NimBLEUUID("00000000-0000-0000-0000-000000000321") +#define PELOTON_ADDRESS NimBLEAddress("00:00:00:00:00:00", 0) // peloton Serial #define PELOTON_RQ_SIZE 4 #define PELOTON_HEADER 0xF1 diff --git a/platformio.ini b/platformio.ini index 4837d707..07f1ac90 100644 --- a/platformio.ini +++ b/platformio.ini @@ -29,7 +29,7 @@ build_flags = -D CORE_DEBUG_LEVEL=1 -D ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE=3500 lib_deps = - https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/1.4.0.zip + https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.1.2.zip https://github.com/teemuatlut/TMCStepper/archive/refs/tags/v0.7.3.zip https://github.com/bblanchon/ArduinoJson/archive/refs/tags/v6.20.0.zip https://github.com/gin66/FastAccelStepper/archive/refs/tags/0.31.2.zip diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp index 67ef9ded..5fce7786 100644 --- a/src/BLE_Client.cpp +++ b/src/BLE_Client.cpp @@ -26,7 +26,7 @@ TaskHandle_t BLEClientTask; SpinBLEClient spinBLEClient; static MyClientCallback myClientCallback; -static MyAdvertisedDeviceCallback myAdvertisedDeviceCallbacks; +static ScanCallbacks myScanCallbacks; void SpinBLEClient::start() { // Create the task for the BLE Client loop @@ -53,8 +53,8 @@ static void onNotify(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t // enqueue sensor data for (size_t i = 0; i < NUM_BLE_DEVICES; i++) { - if (pBLERemoteCharacteristic->getUUID() == spinBLEClient.myBLEDevices[i].charUUID) { - spinBLEClient.myBLEDevices[i].enqueueData(pData, length); + if (pBLERemoteCharacteristic->getClient()->getPeerAddress() == spinBLEClient.myBLEDevices[i].peerAddress) { + spinBLEClient.myBLEDevices[i].enqueueData(pData, length, pBLERemoteCharacteristic->getUUID()); } } } @@ -107,11 +107,11 @@ void bleClientTask(void *pvParameters) { } } // Spin Down process for the Server. It's here because it needs to be non-blocking for the maintenance loop. - // Checking for cadence also so that we don't home when nobody is around. + // Checking for cadence also so that we don't home when nobody is around. if (spinBLEServer.spinDownFlag && rtConfig->cad.getValue()) { if (spinBLEServer.spinDownFlag >= 2) { // Home Both Directions ss2k->goHome(true); - } else { // Startup Homing + } else { // Startup Homing ss2k->goHome(false); } spinBLEServer.spinDownFlag = 0; @@ -199,7 +199,7 @@ bool SpinBLEClient::connectToServer() { NimBLEClient *pClient = nullptr; /** Check if we have a client we should reuse first **/ - if (NimBLEDevice::getClientListSize()) { + if (NimBLEDevice::getCreatedClientCount()) { /** Special case when we already know this device, we send false as the * second argument in connect() to prevent refreshing the service database. * This saves considerable time and power. @@ -233,7 +233,7 @@ bool SpinBLEClient::connectToServer() { /** No client to reuse? Create a new one. */ if (!pClient) { - if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) { + if (NimBLEDevice::getCreatedClientCount() >= NIMBLE_MAX_CONNECTIONS) { Serial.println("Max clients reached - no more connections available"); return false; } @@ -278,7 +278,7 @@ bool SpinBLEClient::connectToServer() { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Successful remote subscription."); spinBLEClient.myBLEDevices[device_number].doConnect = false; this->reconnectTries = MAX_RECONNECT_TRIES; - spinBLEClient.myBLEDevices[device_number].set(myDevice, pClient->getConnId(), serviceUUID, charUUID); + spinBLEClient.myBLEDevices[device_number].set(myDevice, pClient->getConnHandle(), serviceUUID, charUUID); spinBLEClient.myBLEDevices[device_number].peerAddress = pClient->getPeerAddress(); removeDuplicates(pClient); return true; @@ -330,7 +330,7 @@ bool SpinBLEClient::connectToServer() { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Successful %s subscription.", pChr->getUUID().toString().c_str()); spinBLEClient.myBLEDevices[device_number].doConnect = false; this->reconnectTries = MAX_RECONNECT_TRIES; - spinBLEClient.myBLEDevices[device_number].set(myDevice, pClient->getConnId(), serviceUUID, charUUID); + spinBLEClient.myBLEDevices[device_number].set(myDevice, pClient->getConnHandle(), serviceUUID, charUUID); spinBLEClient.myBLEDevices[device_number].peerAddress = pClient->getPeerAddress(); removeDuplicates(pClient); } @@ -401,7 +401,7 @@ void MyClientCallback::onAuthenticationComplete(ble_gap_conn_desc desc) { SS2K_L * Scan for BLE servers and find the first one that advertises the service we are looking for. */ -void MyAdvertisedDeviceCallback::onResult(BLEAdvertisedDevice *advertisedDevice) { +void ScanCallbacks::onResult(BLEAdvertisedDevice *advertisedDevice) { // Define granular constants for maximal reuse during logging const char *const MATCHED = "Matched "; const char *const DIDNT_MATCH_THE_SAVED = " didn't match the saved: "; @@ -483,18 +483,16 @@ void SpinBLEClient::scanProcess(int duration) { BLEScan *pBLEScan = BLEDevice::getScan(); pBLEScan->stop(); vTaskDelay(50 / portTICK_PERIOD_MS); - pBLEScan->clearDuplicateCache(); pBLEScan->clearResults(); - pBLEScan->setAdvertisedDeviceCallbacks(&myAdvertisedDeviceCallbacks); + pBLEScan->setScanCallbacks(&myScanCallbacks); pBLEScan->setInterval(49); // 97 pBLEScan->setWindow(33); // 67 pBLEScan->setDuplicateFilter(true); pBLEScan->setActiveScan(true); // might cause memory leak if true - undetermined. We don't get device names without it. - BLEScanResults foundDevices = pBLEScan->start(duration, false); - this->dontBlockScan = false; - - int count = foundDevices.getCount(); + pBLEScan->start(duration, false); + this->dontBlockScan = false; + int count = pBLEScan->getResults().getCount(); StaticJsonDocument<1000> devices; // Check if 'devices' JSON document already exists and has content; if so, deserialize it. @@ -504,32 +502,32 @@ void SpinBLEClient::scanProcess(int duration) { } for (int i = 0; i < count; i++) { - BLEAdvertisedDevice d = foundDevices.getDevice(i); + const NimBLEAdvertisedDevice* d = pBLEScan->getResults().getDevice(i); // Check for duplicates by name or address before adding bool isDuplicate = false; for (JsonPair kv : devices.as()) { JsonObject obj = kv.value().as(); - if (obj.containsKey("name") && obj["name"] == this->adevName2UniqueName(&d)) { + if (obj.containsKey("name") && obj["name"] == this->adevName2UniqueName(d)) { isDuplicate = true; break; } } // is this device advertising something we're interested in? - if (!isDuplicate && (d.isAdvertisingService(CYCLINGPOWERSERVICE_UUID) || d.isAdvertisingService(HEARTSERVICE_UUID) || d.isAdvertisingService(FLYWHEEL_UART_SERVICE_UUID) || - d.isAdvertisingService(FITNESSMACHINESERVICE_UUID) || d.isAdvertisingService(ECHELON_DEVICE_UUID) || d.isAdvertisingService(HID_SERVICE_UUID))) { + if (!isDuplicate && (d->isAdvertisingService(CYCLINGPOWERSERVICE_UUID) || d->isAdvertisingService(HEARTSERVICE_UUID) || d->isAdvertisingService(FLYWHEEL_UART_SERVICE_UUID) || + d->isAdvertisingService(FITNESSMACHINESERVICE_UUID) || d->isAdvertisingService(ECHELON_DEVICE_UUID) || d->isAdvertisingService(HID_SERVICE_UUID))) { String device = "device " + String(devices.size()); // Use the current size to index the new device - devices[device]["name"] = this->adevName2UniqueName(&d); + devices[device]["name"] = this->adevName2UniqueName(d); // Workaround for IC4 not advertising FTMS as the first service. // Potentially others may need to be added in the future. // The symptom was the bike name not showing up in the HTML. - if (d.haveServiceUUID() && d.isAdvertisingService(FITNESSMACHINESERVICE_UUID)) { + if (d->haveServiceUUID() && d->isAdvertisingService(FITNESSMACHINESERVICE_UUID)) { devices[device]["UUID"] = FITNESSMACHINESERVICE_UUID.toString(); } else { - devices[device]["UUID"] = d.getServiceUUID().toString(); + devices[device]["UUID"] = d->getServiceUUID().toString(); } } } @@ -661,7 +659,7 @@ void SpinBLEClient::postConnect() { } writeCharacteristic->writeValue(FitnessMachineControlPointProcedure::StartOrResume, 1); SS2K_LOG(BLE_CLIENT_LOG_TAG, "Updating Connection Params for: %s", _BLEd.peerAddress.toString().c_str()); - BLEDevice::getServer()->updateConnParams(pClient->getConnId(), 120, 120, 2, 1000); + BLEDevice::getServer()->updateConnParams(pClient->getConnHandle(), 120, 120, 2, 1000); spinBLEClient.handleBattInfo(pClient, true); } } @@ -669,7 +667,7 @@ void SpinBLEClient::postConnect() { } } -bool SpinBLEAdvertisedDevice::enqueueData(uint8_t *data, size_t length) { +bool SpinBLEAdvertisedDevice::enqueueData(uint8_t *data, size_t length, NimBLEUUID charUUID) { NotifyData notifyData; if (!uxQueueSpacesAvailable(this->dataBufferQueue)) { @@ -677,7 +675,8 @@ bool SpinBLEAdvertisedDevice::enqueueData(uint8_t *data, size_t length) { return pdFALSE; } - notifyData.length = length; + notifyData.length = length; + notifyData.charUUID = charUUID; for (size_t i = 0; i < length; i++) { notifyData.data[i] = data[i]; // Serial.printf("%02x ", notifyData.data[i]); @@ -756,9 +755,8 @@ void SpinBLEClient::connectBLE_HID(NimBLEClient *pClient) { // One real device reports 2 with the same UUID but // different handles. Using getCharacteristic() results // in subscribing to only one. - std::vector *charVector; - charVector = pSvc->getCharacteristics(true); - for (auto &it : *charVector) { + std::vector charVector = pSvc->getCharacteristics(true); + for (auto &it : charVector) { if (it->getUUID() == NimBLEUUID(HID_REPORT_DATA_UUID)) { Serial.println(it->toString().c_str()); if (it->canNotify()) { @@ -853,7 +851,7 @@ void SpinBLEClient::handleBattInfo(NimBLEClient *pClient, bool updateNow = false } } // Returns a device name with the las two of the peer address attached. This lets us distinguish between multiple devices with the same device name. -String SpinBLEClient::adevName2UniqueName(NimBLEAdvertisedDevice *inDev) { +String SpinBLEClient::adevName2UniqueName(const NimBLEAdvertisedDevice *inDev) { if (inDev->haveName()) { String _outDevName = String(inDev->getName().c_str()); // add the last two of the string diff --git a/src/BLE_Server.cpp b/src/BLE_Server.cpp index a2610c35..c5b5b002 100644 --- a/src/BLE_Server.cpp +++ b/src/BLE_Server.cpp @@ -62,7 +62,7 @@ void startBLEServer() { pAdvertising->addServiceUUID(WATTBIKE_SERVICE_UUID); pAdvertising->setMaxInterval(250); pAdvertising->setMinInterval(160); - pAdvertising->setScanResponse(true); + pAdvertising->enableScanResponse(true); BLEFirmwareSetup(); BLEDevice::startAdvertising(); diff --git a/src/Main.cpp b/src/Main.cpp index b495a40b..0224fa48 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -60,7 +60,7 @@ void SS2K::stopTasks() { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Shutting Down all BLE services"); spinBLEClient.reconnectTries = 0; spinBLEClient.intentionalDisconnect = NUM_BLE_DEVICES; - if (NimBLEDevice::getInitialized()) { + if (NimBLEDevice::isInitialized()) { NimBLEDevice::deinit(); ss2k->stopTasks(); } From 31901111b28cc72101b858c5747385c5d769ab6d Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Thu, 2 Jan 2025 20:16:13 -0600 Subject: [PATCH 03/23] WIP -Added new Queue -Migrating to New NimBLE --- include/BLE_Common.h | 38 ++++++----- include/BLE_Cycling_Power_Service.h | 2 +- include/BLE_Cycling_Speed_Cadence.h | 2 +- include/BLE_Fitness_Machine_Service.h | 2 +- include/BLE_Heart_Service.h | 2 +- include/settings.h | 12 ++-- platformio.ini | 4 +- src/BLE_Client.cpp | 91 +++++++++++++++------------ src/BLE_Cycling_Power_Service.cpp | 2 +- src/BLE_Cycling_Speed_Cadence.cpp | 2 +- src/BLE_Fitness_Machine_Service.cpp | 2 +- src/BLE_Heart_Service.cpp | 2 +- src/BLE_Server.cpp | 57 +++++++++++++---- src/BLE_Setup.cpp | 4 +- src/HTTP_Server_Basic.cpp | 18 +++--- src/Main.cpp | 2 +- 16 files changed, 149 insertions(+), 93 deletions(-) diff --git a/include/BLE_Common.h b/include/BLE_Common.h index 3c895a2a..2fa559c2 100644 --- a/include/BLE_Common.h +++ b/include/BLE_Common.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -38,9 +39,12 @@ void BLECommunications(); // *****************************Server**************************** class MyServerCallbacks : public NimBLEServerCallbacks { public: - void onConnect(BLEServer *, ble_gap_conn_desc *desc); - void onDisconnect(BLEServer *); - bool onConnParamsUpdateRequest(NimBLEClient *pClient, const ble_gap_upd_params *params); + void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo); + void onDisconnect(NimBLEServer* pServer); + void onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo); + uint32_t onPassKeyDisplay(); + void onAuthenticationComplete(NimBLEConnInfo& connInfo); + bool onConnParamsUpdateRequest(uint16_t handle, const ble_gap_upd_params* params); }; // TODO add the rest of the server to this class @@ -66,10 +70,12 @@ class SpinBLEServer { SpinBLEServer() { memset(&clientSubscribed, 0, sizeof(clientSubscribed)); } }; -class MyCallbacks : public NimBLECharacteristicCallbacks { +class MyCharacteristicCallbacks : public NimBLECharacteristicCallbacks { public: - void onWrite(BLECharacteristic *); - void onSubscribe(NimBLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc, uint16_t subValue); + void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override; + void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override; + void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override; + void onStatus(NimBLECharacteristic* pCharacteristic, int code) override; }; extern SpinBLEServer spinBLEServer; @@ -120,7 +126,7 @@ class SpinBLEAdvertisedDevice { // } // } - NimBLEAdvertisedDevice *advertisedDevice = nullptr; + const NimBLEAdvertisedDevice *advertisedDevice = nullptr; NimBLEAddress peerAddress; int connectedClientID = BLE_HS_CONN_HANDLE_NONE; @@ -134,7 +140,7 @@ class SpinBLEAdvertisedDevice { bool doConnect = false; void setPostConnected(bool pc) { isPostConnected = pc; } bool getPostConnected() { return isPostConnected; } - void set(BLEAdvertisedDevice *device, int id = BLE_HS_CONN_HANDLE_NONE, BLEUUID inServiceUUID = (uint16_t)0x0000, BLEUUID inCharUUID = (uint16_t)0x0000); + void set(const NimBLEAdvertisedDevice *device, int id = BLE_HS_CONN_HANDLE_NONE, BLEUUID inServiceUUID = (uint16_t)0x0000, BLEUUID inCharUUID = (uint16_t)0x0000); void reset(); void print(); bool enqueueData(uint8_t data[25], size_t length, NimBLEUUID charUUID); @@ -188,17 +194,19 @@ class SpinBLEClient { }; class ScanCallbacks : public NimBLEScanCallbacks { - public: - void onResult(NimBLEAdvertisedDevice *); +public: + void onResult(const NimBLEAdvertisedDevice* advertisedDevice) override; + void onScanEnd(const NimBLEScanResults& results, int reason) override; +private: }; class MyClientCallback : public NimBLEClientCallbacks { public: - void onConnect(BLEClient *); - void onDisconnect(BLEClient *); - uint32_t onPassKeyRequest(); - bool onConfirmPIN(uint32_t); - void onAuthenticationComplete(ble_gap_conn_desc); + void onConnect(NimBLEClient* pClient) override; + void onDisconnect(NimBLEClient* pClient, int reason) override; + void onPassKeyEntry(NimBLEConnInfo& connInfo) override; + void onConfirmPasskey(NimBLEConnInfo& connInfo, uint32_t pass_key) override; + void onAuthenticationComplete(NimBLEConnInfo& connInfo) override; }; extern SpinBLEClient spinBLEClient; diff --git a/include/BLE_Cycling_Power_Service.h b/include/BLE_Cycling_Power_Service.h index 234bc86f..0eef8ac3 100644 --- a/include/BLE_Cycling_Power_Service.h +++ b/include/BLE_Cycling_Power_Service.h @@ -13,7 +13,7 @@ class BLE_Cycling_Power_Service { public: BLE_Cycling_Power_Service(); - void setupService(NimBLEServer *pServer, MyCallbacks *chrCallbacks); + void setupService(NimBLEServer *pServer, MyCharacteristicCallbacks *chrCallbacks); void update(); private: diff --git a/include/BLE_Cycling_Speed_Cadence.h b/include/BLE_Cycling_Speed_Cadence.h index 7e48b79e..bee802ab 100644 --- a/include/BLE_Cycling_Speed_Cadence.h +++ b/include/BLE_Cycling_Speed_Cadence.h @@ -13,7 +13,7 @@ class BLE_Cycling_Speed_Cadence { public: BLE_Cycling_Speed_Cadence(); - void setupService(NimBLEServer *pServer, MyCallbacks *chrCallbacks); + void setupService(NimBLEServer *pServer, MyCharacteristicCallbacks *chrCallbacks); void update(); private: diff --git a/include/BLE_Fitness_Machine_Service.h b/include/BLE_Fitness_Machine_Service.h index 2ecdef85..386ff7e9 100644 --- a/include/BLE_Fitness_Machine_Service.h +++ b/include/BLE_Fitness_Machine_Service.h @@ -13,7 +13,7 @@ class BLE_Fitness_Machine_Service { public: BLE_Fitness_Machine_Service(); - void setupService(NimBLEServer *pServer, MyCallbacks *chrCallbacks); + void setupService(NimBLEServer *pServer, MyCharacteristicCallbacks *chrCallbacks); void update(); bool spinDown(uint8_t response); diff --git a/include/BLE_Heart_Service.h b/include/BLE_Heart_Service.h index c3498c4b..49755e7e 100644 --- a/include/BLE_Heart_Service.h +++ b/include/BLE_Heart_Service.h @@ -13,7 +13,7 @@ class BLE_Heart_Service { public: BLE_Heart_Service(); - void setupService(NimBLEServer *pServer, MyCallbacks *chrCallbacks); + void setupService(NimBLEServer *pServer, MyCharacteristicCallbacks *chrCallbacks); void update(); private: diff --git a/include/settings.h b/include/settings.h index 51314d62..e2acf7f5 100644 --- a/include/settings.h +++ b/include/settings.h @@ -224,11 +224,11 @@ const char* const DEFAULT_PASSWORD = "password"; // Name of default Power Meter. any connects to anything, none connects to // nothing. -#define CONNECTED_POWER_METER "any" +#define CONNECTED_POWER_METER "none" // Name of default heart monitor. any connects to anything, none connects to // nothing. -#define CONNECTED_HEART_MONITOR "any" +#define CONNECTED_HEART_MONITOR "none" // Name of default remote. any connects to anything, none connects to // nothing. @@ -305,14 +305,14 @@ const char* const DEFAULT_PASSWORD = "password"; // Interval for polling ble battery updates #define BATTERY_UPDATE_INTERVAL_MILLIS 300000 -// Initial and web scan duration. -#define DEFAULT_SCAN_DURATION 5 +// Initial and web scan duration in milliseconds +#define DEFAULT_SCAN_DURATION 5000 // Default homing sensitivity value #define DEFAULT_HOMING_SENSITIVITY 50 -// BLE automatic reconnect duration. Set this low to avoid interruption. -#define BLE_RECONNECT_SCAN_DURATION 5 +// BLE automatic reconnect duration in milliseconds. Set this low to avoid interruption. +#define BLE_RECONNECT_SCAN_DURATION 5000 // Task Stack Sizes #define MAIN_STACK 6500 diff --git a/platformio.ini b/platformio.ini index 07f1ac90..764fa1f0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -13,7 +13,7 @@ default_envs = release [esp32doit] lib_ldf_mode = chain lib_compat_mode = strict -platform = espressif32 @ 6.0.1 +platform = espressif32 @ 6.9.0 board = esp32doit-devkit-v1 framework = arduino board_build.partitions = min_spiffs.csv @@ -26,7 +26,7 @@ build_flags = !python build_date_macro.py -D CONFIG_BT_NIMBLE_MAX_CONNECTIONS=7 -D CONFIG_MDNS_STRICT_MODE=1 - -D CORE_DEBUG_LEVEL=1 + -D CORE_DEBUG_LEVEL=5 -D ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE=3500 lib_deps = https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.1.2.zip diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp index 5fce7786..0a369037 100644 --- a/src/BLE_Client.cpp +++ b/src/BLE_Client.cpp @@ -37,6 +37,13 @@ void SpinBLEClient::start() { 1, /* priority of the task */ &BLEClientTask, /* Task handle to keep track of created task */ 1); /* pin task to core */ + + NimBLEScan *pBLEScan = NimBLEDevice::getScan(); + pBLEScan->setScanCallbacks(&myScanCallbacks, false); + pBLEScan->setInterval(49); // 97 + pBLEScan->setWindow(33); // 67 + pBLEScan->setDuplicateFilter(true); + pBLEScan->setActiveScan(true); } static void onNotify(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { @@ -63,6 +70,7 @@ static void onNotify(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t // Manages device connections and scanning. void bleClientTask(void *pvParameters) { long int scanDelay = millis(); + spinBLEClient.checkBLEReconnect(); for (;;) { vTaskDelay(BLE_CLIENT_DELAY / portTICK_PERIOD_MS); // Delay between loops. @@ -83,7 +91,7 @@ void bleClientTask(void *pvParameters) { } // Scan for BLE devices that we should connect to this client - if ((millis() - scanDelay) > ((BLE_RECONNECT_SCAN_DURATION * 1000) * 2)) { + if ((millis() - scanDelay) > ((BLE_RECONNECT_SCAN_DURATION) * 2)) { spinBLEClient.checkBLEReconnect(); scanDelay = millis(); #ifdef DEBUG_STACK @@ -92,7 +100,7 @@ void bleClientTask(void *pvParameters) { } if (spinBLEClient.doScan && (!ss2k->isUpdating)) { - spinBLEClient.scanProcess(); + spinBLEClient.scanProcess(DEFAULT_SCAN_DURATION); } // Connect BLE Servers to this client @@ -124,9 +132,9 @@ bool SpinBLEClient::connectToServer() { NimBLEUUID serviceUUID; NimBLEUUID charUUID; - int successful = 0; - BLEAdvertisedDevice *myDevice = nullptr; - int device_number = -1; + int successful = 0; + const NimBLEAdvertisedDevice *myDevice; + int device_number = -1; for (int i = 0; i < NUM_BLE_DEVICES; i++) { if (spinBLEClient.myBLEDevices[i].doConnect == true) { // Client wants to be connected @@ -250,9 +258,9 @@ bool SpinBLEClient::connectToServer() { */ pClient->setConnectionParams(6, 6, 0, 200); /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ - pClient->setConnectTimeout(5); // 5 + pClient->setConnectTimeout(5 * 1000); // 5 seconds - if (!pClient->connect(myDevice->getAddress())) { + if (!pClient->connect(myDevice, false)) { SS2K_LOG(BLE_CLIENT_LOG_TAG, " - Failed to connect client"); /** Created a client but failed to connect, don't need to keep it as it has no data */ spinBLEClient.myBLEDevices[device_number].reset(); @@ -346,14 +354,12 @@ bool SpinBLEClient::connectToServer() { /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ -void MyClientCallback::onConnect(NimBLEClient *pClient) { - // additional characteristic subscriptions. -} +void MyClientCallback::onConnect(NimBLEClient *pClient) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Connected"); } -void MyClientCallback::onDisconnect(NimBLEClient *pClient) { +void MyClientCallback::onDisconnect(NimBLEClient *pClient, int reason) { if (!pClient->isConnected()) { NimBLEAddress addr = pClient->getPeerAddress(); - SS2K_LOG(BLE_CLIENT_LOG_TAG, "This disconnected client Address %s", addr.toString().c_str()); + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Client %s Disconnected, reason = %d", addr.toString().c_str(), reason); for (size_t i = 0; i < NUM_BLE_DEVICES; i++) { if (addr == spinBLEClient.myBLEDevices[i].peerAddress) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Detected %s Disconnect", spinBLEClient.myBLEDevices[i].serviceUUID.toString().c_str()); @@ -383,25 +389,34 @@ void MyClientCallback::onDisconnect(NimBLEClient *pClient) { } } -/***************** New - Security handled here ******************** -****** Note: these are the same return values as defaults ********/ -uint32_t MyClientCallback::onPassKeyRequest() { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Client PassKeyRequest"); - return 123456; +void MyClientCallback::onPassKeyEntry(NimBLEConnInfo &connInfo) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Client Passkey Entry"); + /** This should prompt the user to enter the passkey displayed on the peer device */ + NimBLEDevice::injectPassKey(connInfo, 123456); } -bool MyClientCallback::onConfirmPIN(uint32_t pass_key) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "The passkey YES/NO number: %ud", pass_key); - return true; + +void MyClientCallback::onConfirmPasskey(NimBLEConnInfo &connInfo, uint32_t pass_key) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "The passkey YES/NO number: %" PRIu32, pass_key); + /** Inject false if passkeys don't match. */ + NimBLEDevice::injectConfirmPasskey(connInfo, true); } -void MyClientCallback::onAuthenticationComplete(ble_gap_conn_desc desc) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Starting BLE work!"); } -/*******************************************************************/ +void MyClientCallback::onAuthenticationComplete(NimBLEConnInfo &connInfo) { + if (!connInfo.isEncrypted()) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Encrypt connection failed - disconnecting"); + /** Find the client with the connection handle provided in connInfo */ + NimBLEDevice::getClientByHandle(connInfo.getConnHandle())->disconnect(); + return; + } + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Starting BLE work!"); +} /** * Scan for BLE servers and find the first one that advertises the service we are looking for. */ -void ScanCallbacks::onResult(BLEAdvertisedDevice *advertisedDevice) { +void ScanCallbacks::onResult(const NimBLEAdvertisedDevice *advertisedDevice) { + Serial.printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str()); // Define granular constants for maximal reuse during logging const char *const MATCHED = "Matched "; const char *const DIDNT_MATCH_THE_SAVED = " didn't match the saved: "; @@ -480,19 +495,17 @@ void SpinBLEClient::scanProcess(int duration) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Scanning for BLE servers and putting them into a list..."); - BLEScan *pBLEScan = BLEDevice::getScan(); - pBLEScan->stop(); - vTaskDelay(50 / portTICK_PERIOD_MS); + NimBLEScan *pBLEScan = NimBLEDevice::getScan(); + if (pBLEScan->isScanning()) { + return; + } pBLEScan->clearResults(); - pBLEScan->setScanCallbacks(&myScanCallbacks); - pBLEScan->setInterval(49); // 97 - pBLEScan->setWindow(33); // 67 - pBLEScan->setDuplicateFilter(true); - pBLEScan->setActiveScan(true); // might cause memory leak if true - undetermined. We don't get device names without it. - pBLEScan->start(duration, false); + pBLEScan->start(duration, false, true); this->dontBlockScan = false; +} - int count = pBLEScan->getResults().getCount(); +void ScanCallbacks::onScanEnd(const NimBLEScanResults &results, int reason) { + int count = results.getCount(); StaticJsonDocument<1000> devices; // Check if 'devices' JSON document already exists and has content; if so, deserialize it. @@ -502,13 +515,13 @@ void SpinBLEClient::scanProcess(int duration) { } for (int i = 0; i < count; i++) { - const NimBLEAdvertisedDevice* d = pBLEScan->getResults().getDevice(i); + const NimBLEAdvertisedDevice *d = results.getDevice(i); // Check for duplicates by name or address before adding bool isDuplicate = false; for (JsonPair kv : devices.as()) { JsonObject obj = kv.value().as(); - if (obj.containsKey("name") && obj["name"] == this->adevName2UniqueName(d)) { + if (obj.containsKey("name") && obj["name"] == spinBLEClient.adevName2UniqueName(d)) { isDuplicate = true; break; } @@ -519,7 +532,7 @@ void SpinBLEClient::scanProcess(int duration) { d->isAdvertisingService(FITNESSMACHINESERVICE_UUID) || d->isAdvertisingService(ECHELON_DEVICE_UUID) || d->isAdvertisingService(HID_SERVICE_UUID))) { String device = "device " + String(devices.size()); // Use the current size to index the new device - devices[device]["name"] = this->adevName2UniqueName(d); + devices[device]["name"] = spinBLEClient.adevName2UniqueName(d); // Workaround for IC4 not advertising FTMS as the first service. // Potentially others may need to be added in the future. @@ -534,7 +547,7 @@ void SpinBLEClient::scanProcess(int duration) { String output; serializeJson(devices, output); - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Bluetooth Client Found Devices: %s", output.c_str()); + // SS2K_LOG(BLE_CLIENT_LOG_TAG, "Bluetooth Client Found Devices: %s", output.c_str()); #ifdef USE_TELEGRAM SEND_TO_TELEGRAM("Bluetooth Client Found Devices: " + output); #endif @@ -863,9 +876,9 @@ String SpinBLEClient::adevName2UniqueName(const NimBLEAdvertisedDevice *inDev) { } } -void SpinBLEAdvertisedDevice::set(BLEAdvertisedDevice *device, int id, BLEUUID inServiceUUID, BLEUUID inCharUUID) { +void SpinBLEAdvertisedDevice::set(const NimBLEAdvertisedDevice *device, int id, BLEUUID inServiceUUID, BLEUUID inCharUUID) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Setting Device %s", device->getAddress().toString().c_str()); - this->advertisedDevice = device; + this->advertisedDevice = const_cast(device); this->peerAddress = device->getAddress(); this->connectedClientID = id; this->serviceUUID = BLEUUID(inServiceUUID); diff --git a/src/BLE_Cycling_Power_Service.cpp b/src/BLE_Cycling_Power_Service.cpp index 63058113..9d58257e 100644 --- a/src/BLE_Cycling_Power_Service.cpp +++ b/src/BLE_Cycling_Power_Service.cpp @@ -9,7 +9,7 @@ #include BLE_Cycling_Power_Service::BLE_Cycling_Power_Service() : pPowerMonitor(nullptr), cyclingPowerFeatureCharacteristic(nullptr), sensorLocationCharacteristic(nullptr){} -void BLE_Cycling_Power_Service::setupService(NimBLEServer *pServer, MyCallbacks *chrCallbacks) { +void BLE_Cycling_Power_Service::setupService(NimBLEServer *pServer, MyCharacteristicCallbacks *chrCallbacks) { // Power Meter service setup pPowerMonitor = spinBLEServer.pServer->createService(CYCLINGPOWERSERVICE_UUID); cyclingPowerMeasurementCharacteristic = pPowerMonitor->createCharacteristic(CYCLINGPOWERMEASUREMENT_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); diff --git a/src/BLE_Cycling_Speed_Cadence.cpp b/src/BLE_Cycling_Speed_Cadence.cpp index da333ede..384ef8d3 100644 --- a/src/BLE_Cycling_Speed_Cadence.cpp +++ b/src/BLE_Cycling_Speed_Cadence.cpp @@ -10,7 +10,7 @@ BLE_Cycling_Speed_Cadence::BLE_Cycling_Speed_Cadence() : pCyclingSpeedCadenceService(nullptr), cscMeasurement(nullptr), cscFeature(nullptr) {} -void BLE_Cycling_Speed_Cadence::setupService(NimBLEServer *pServer, MyCallbacks *chrCallbacks) { +void BLE_Cycling_Speed_Cadence::setupService(NimBLEServer *pServer, MyCharacteristicCallbacks *chrCallbacks) { pCyclingSpeedCadenceService = pServer->createService(CSCSERVICE_UUID); cscMeasurement = pCyclingSpeedCadenceService->createCharacteristic(CSCMEASUREMENT_UUID, NIMBLE_PROPERTY::NOTIFY); cscFeature = pCyclingSpeedCadenceService->createCharacteristic(CSCFEATURE_UUID, NIMBLE_PROPERTY::READ); diff --git a/src/BLE_Fitness_Machine_Service.cpp b/src/BLE_Fitness_Machine_Service.cpp index e3c9bac1..6c1b5434 100644 --- a/src/BLE_Fitness_Machine_Service.cpp +++ b/src/BLE_Fitness_Machine_Service.cpp @@ -21,7 +21,7 @@ BLE_Fitness_Machine_Service::BLE_Fitness_Machine_Service() uint8_t ftmsTrainingStatus[2] = {0x08, 0x00}; -void BLE_Fitness_Machine_Service::setupService(NimBLEServer *pServer, MyCallbacks *chrCallbacks) { +void BLE_Fitness_Machine_Service::setupService(NimBLEServer *pServer, MyCharacteristicCallbacks *chrCallbacks) { // Resistance, IPower, HeartRate uint8_t ftmsResistanceLevelRange[6] = {0x01, 0x00, 0x64, 0x00, 0x01, 0x00}; // 1:100 increment 1 uint8_t ftmsPowerRange[6] = {0x01, 0x00, 0xA0, 0x0F, 0x01, 0x00}; // 1:4000 watts increment 1 diff --git a/src/BLE_Heart_Service.cpp b/src/BLE_Heart_Service.cpp index 9a7b7372..09534574 100644 --- a/src/BLE_Heart_Service.cpp +++ b/src/BLE_Heart_Service.cpp @@ -10,7 +10,7 @@ BLE_Heart_Service::BLE_Heart_Service() : pHeartService(nullptr), heartRateMeasurementCharacteristic(nullptr) {} -void BLE_Heart_Service::setupService(NimBLEServer *pServer, MyCallbacks *chrCallbacks) { +void BLE_Heart_Service::setupService(NimBLEServer *pServer, MyCharacteristicCallbacks *chrCallbacks) { // HEART RATE MONITOR SERVICE SETUP pHeartService = spinBLEServer.pServer->createService(HEARTSERVICE_UUID); heartRateMeasurementCharacteristic = pHeartService->createCharacteristic(HEARTCHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); diff --git a/src/BLE_Server.cpp b/src/BLE_Server.cpp index c5b5b002..8097454a 100644 --- a/src/BLE_Server.cpp +++ b/src/BLE_Server.cpp @@ -25,7 +25,7 @@ // BLE Server Settings SpinBLEServer spinBLEServer; -static MyCallbacks chrCallbacks; +static MyCharacteristicCallbacks chrCallbacks; BLE_Cycling_Speed_Cadence cyclingSpeedCadenceService; BLE_Cycling_Power_Service cyclingPowerService; @@ -53,7 +53,7 @@ void startBLEServer() { BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); // const std::string fitnessData = {0b00000001, 0b00100000, 0b00000000}; // pAdvertising->setServiceData(FITNESSMACHINESERVICE_UUID, fitnessData); - + pAdvertising->setName(userConfig->getDeviceName()); pAdvertising->addServiceUUID(FITNESSMACHINESERVICE_UUID); pAdvertising->addServiceUUID(CYCLINGPOWERSERVICE_UUID); pAdvertising->addServiceUUID(CSCSERVICE_UUID); @@ -127,8 +127,9 @@ void SpinBLEServer::updateWheelAndCrankRev() { } // Creating Server Connection Callbacks -void MyServerCallbacks::onConnect(BLEServer *pServer, ble_gap_conn_desc *desc) { - SS2K_LOG(BLE_SERVER_LOG_TAG, "Bluetooth Remote Client Connected: %s Connected Clients: %d", NimBLEAddress(desc->peer_ota_addr).toString().c_str(), pServer->getConnectedCount()); +void MyServerCallbacks::onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) { + SS2K_LOG(BLE_SERVER_LOG_TAG, "Bluetooth Remote Client Connected: %s Connected Clients: %d", + connInfo.getAddress().toString().c_str(), pServer->getConnectedCount()); if (pServer->getConnectedCount() < CONFIG_BT_NIMBLE_MAX_CONNECTIONS - NUM_BLE_DEVICES) { BLEDevice::startAdvertising(); @@ -138,7 +139,7 @@ void MyServerCallbacks::onConnect(BLEServer *pServer, ble_gap_conn_desc *desc) { } } -void MyServerCallbacks::onDisconnect(BLEServer *pServer) { +void MyServerCallbacks::onDisconnect(NimBLEServer* pServer) { SS2K_LOG(BLE_SERVER_LOG_TAG, "Bluetooth Remote Client Disconnected. Remaining Clients: %d", pServer->getConnectedCount()); BLEDevice::startAdvertising(); // client disconnected while trying to write fw - reboot to clear the faulty upload. @@ -148,27 +149,57 @@ void MyServerCallbacks::onDisconnect(BLEServer *pServer) { } } -bool MyServerCallbacks::onConnParamsUpdateRequest(NimBLEClient *pClient, const ble_gap_upd_params *params) { - SS2K_LOG(BLE_SERVER_LOG_TAG, "Updated Server Connection Parameters for %s", pClient->getPeerAddress().toString().c_str()); +void MyServerCallbacks::onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo) { + SS2K_LOG(BLE_SERVER_LOG_TAG, "MTU updated: %u for connection ID: %u", MTU, connInfo.getConnHandle()); +} + +uint32_t MyServerCallbacks::onPassKeyDisplay() { + uint32_t passkey = 123456; // Static passkey for demonstration + SS2K_LOG(BLE_SERVER_LOG_TAG, "Server Passkey Display: %u", passkey); + return passkey; +} + +void MyServerCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo) { + if (!connInfo.isEncrypted()) { + SS2K_LOG(BLE_SERVER_LOG_TAG, "Encrypt connection failed - disconnecting client"); + NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle()); + return; + } + SS2K_LOG(BLE_SERVER_LOG_TAG, "Secured connection to: %s", connInfo.getAddress().toString().c_str()); +} + +bool MyServerCallbacks::onConnParamsUpdateRequest(uint16_t handle, const ble_gap_upd_params *params) { + SS2K_LOG(BLE_SERVER_LOG_TAG, "Updated Server Connection Parameters for handle: %d", handle); return true; -}; +} // END SERVER CALLBACKS -void MyCallbacks::onWrite(BLECharacteristic *pCharacteristic) { +void MyCharacteristicCallbacks::onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) { + SS2K_LOG(BLE_SERVER_LOG_TAG, "Read from %s by client: %s", + pCharacteristic->getUUID().toString().c_str(), + connInfo.getAddress().toString().c_str()); +} + +void MyCharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) { if (pCharacteristic->getUUID() == FITNESSMACHINECONTROLPOINT_UUID) { spinBLEServer.writeCache.push(pCharacteristic->getValue()); } else { - SS2K_LOG(BLE_SERVER_LOG_TAG, "Write to %s is not supported", pCharacteristic->getUUID().toString()); + SS2K_LOG(BLE_SERVER_LOG_TAG, "Write to %s is not supported", pCharacteristic->getUUID().toString().c_str()); } } -void MyCallbacks::onSubscribe(NimBLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc, uint16_t subValue) { +void MyCharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, int code) { + SS2K_LOG(BLE_SERVER_LOG_TAG, "Notification/Indication status code: %d for characteristic: %s", + code, pCharacteristic->getUUID().toString().c_str()); +} + +void MyCharacteristicCallbacks::onSubscribe(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) { String str = "Client ID: "; NimBLEUUID pUUID = pCharacteristic->getUUID(); - str += desc->conn_handle; + str += connInfo.getConnHandle(); str += " Address: "; - str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str(); + str += connInfo.getAddress().toString().c_str(); if (subValue == 0) { str += " Unsubscribed to "; spinBLEServer.setClientSubscribed(pUUID, false); diff --git a/src/BLE_Setup.cpp b/src/BLE_Setup.cpp index 7d427c44..7bf90ab9 100644 --- a/src/BLE_Setup.cpp +++ b/src/BLE_Setup.cpp @@ -13,8 +13,8 @@ void setupBLE() { // Common BLE setup for both client and server SS2K_LOG(BLE_SETUP_LOG_TAG, "Starting Arduino BLE Client application..."); - BLEDevice::init(userConfig->getDeviceName()); - BLEDevice::setMTU(515); //-- enabling this is very important for BLE firmware updates. + NimBLEDevice::init(userConfig->getDeviceName()); + NimBLEDevice::setMTU(515); //-- enabling this is very important for BLE firmware updates. spinBLEClient.start(); startBLEServer(); SS2K_LOG(BLE_SETUP_LOG_TAG, "%s %s %s", userConfig->getConnectedPowerMeter(), userConfig->getConnectedHeartMonitor(), userConfig->getConnectedRemote()); diff --git a/src/HTTP_Server_Basic.cpp b/src/HTTP_Server_Basic.cpp index 180d7862..c09a5c15 100644 --- a/src/HTTP_Server_Basic.cpp +++ b/src/HTTP_Server_Basic.cpp @@ -51,11 +51,11 @@ void _staSetup() { } void _APSetup() { - // WiFi.eraseAP(); //Needed if we switch back to espressif32 @6.5.0 + //WiFi.eraseAP(); //Needed if we switch back to espressif32 @6.5.0 WiFi.mode(WIFI_AP); - WiFi.setHostname("reset"); // Fixes a bug when switching Arduino Core Versions - WiFi.softAPsetHostname("reset"); - WiFi.setHostname(userConfig->getDeviceName()); + //WiFi.setHostname("reset"); // Fixes a bug when switching Arduino Core Versions + //WiFi.softAPsetHostname("reset"); + //WiFi.setHostname(userConfig->getDeviceName()); WiFi.softAPsetHostname(userConfig->getDeviceName()); WiFi.enableAP(true); vTaskDelay(500); // Micro controller requires some time to reset the mode @@ -93,12 +93,15 @@ void startWifi() { // Couldn't connect to existing network, Create SoftAP if (WiFi.status() != WL_CONNECTED) { + SS2K_LOG(HTTP_SERVER_LOG_TAG, "Starting AP Mode"); _APSetup(); if (strcmp(userConfig->getSsid(), DEVICE_NAME) == 0) { // If default SSID is still in use, let the user select a new password. // Else fall back to the default password. WiFi.softAP(userConfig->getDeviceName(), userConfig->getPassword()); + SS2K_LOG(HTTP_SERVER_LOG_TAG, "Using Stored Password"); } else { + SS2K_LOG(HTTP_SERVER_LOG_TAG, "Using Default Password"); WiFi.softAP(userConfig->getDeviceName(), DEFAULT_PASSWORD); } vTaskDelay(50); @@ -405,6 +408,7 @@ void HTTP_Server::start() { 1); /* pin task to core 1 */ #endif // USE_TELEGRAM server.begin(); + server.enableDelay(false); SS2K_LOG(HTTP_SERVER_LOG_TAG, "HTTP server started"); } @@ -414,9 +418,9 @@ void HTTP_Server::webClientUpdate() { _webClientTimer = millis(); static unsigned long mDnsTimer = millis(); // NOLINT: There is no overload in String for uint64_t server.handleClient(); - if (WiFi.getMode() != WIFI_MODE_STA) { - dnsServer.processNextRequest(); - } + //if (WiFi.getMode() != WIFI_MODE_STA) { + // dnsServer.processNextRequest(); + //} // Keep MDNS alive if ((millis() - mDnsTimer) > 30000) { MDNS.addServiceTxt("http", "_tcp", "lf", String(mDnsTimer)); diff --git a/src/Main.cpp b/src/Main.cpp index 0224fa48..2baa4bf9 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -165,7 +165,7 @@ void setup() { NULL, /* parameter of the task */ 20, /* priority of the task */ &maintenanceLoopTask, /* Task handle to keep track of created task */ - 1); /* pin task to core */ + 0); /* pin task to core */ } void loop() { // Delete this task so we can make one that's more memory efficient. From e94538a68515267f2be4000c390e1594820115a6 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Sat, 4 Jan 2025 17:13:49 -0500 Subject: [PATCH 04/23] Fixed Advertising --- src/BLE_Custom_Characteristic.cpp | 1 + src/BLE_Cycling_Power_Service.cpp | 2 ++ src/BLE_Cycling_Speed_Cadence.cpp | 1 + src/BLE_Fitness_Machine_Service.cpp | 1 + src/BLE_Heart_Service.cpp | 1 + src/BLE_Server.cpp | 14 ++++---------- src/BLE_Wattbike_Service.cpp | 1 + 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/BLE_Custom_Characteristic.cpp b/src/BLE_Custom_Characteristic.cpp index 69f2a89f..6681e7fe 100644 --- a/src/BLE_Custom_Characteristic.cpp +++ b/src/BLE_Custom_Characteristic.cpp @@ -95,6 +95,7 @@ void BLE_ss2kCustomCharacteristic::setupService(NimBLEServer *pServer) { smartSpin2kCharacteristic->setValue(ss2kCustomCharacteristicValue, sizeof(ss2kCustomCharacteristicValue)); smartSpin2kCharacteristic->setCallbacks(new ss2kCustomCharacteristicCallbacks()); pSmartSpin2kService->start(); + spinBLEServer.pServer->getAdvertising()->addServiceUUID(pSmartSpin2kService->getUUID()); } void BLE_ss2kCustomCharacteristic::update() {} diff --git a/src/BLE_Cycling_Power_Service.cpp b/src/BLE_Cycling_Power_Service.cpp index 9d58257e..bac42fe2 100644 --- a/src/BLE_Cycling_Power_Service.cpp +++ b/src/BLE_Cycling_Power_Service.cpp @@ -21,6 +21,8 @@ void BLE_Cycling_Power_Service::setupService(NimBLEServer *pServer, MyCharacteri sensorLocationCharacteristic->setValue(cpsLocation, sizeof(cpsLocation)); cyclingPowerMeasurementCharacteristic->setCallbacks(chrCallbacks); pPowerMonitor->start(); + //spinBLEServer.pServer->getAdvertising()->addServiceUUID(pPowerMonitor->getUUID()); + } void BLE_Cycling_Power_Service::update() { diff --git a/src/BLE_Cycling_Speed_Cadence.cpp b/src/BLE_Cycling_Speed_Cadence.cpp index 384ef8d3..37b06951 100644 --- a/src/BLE_Cycling_Speed_Cadence.cpp +++ b/src/BLE_Cycling_Speed_Cadence.cpp @@ -18,6 +18,7 @@ void BLE_Cycling_Speed_Cadence::setupService(NimBLEServer *pServer, MyCharacteri cscFeature->setValue(cscFeatureFlags, sizeof(cscFeatureFlags)); cscMeasurement->setCallbacks(chrCallbacks); pCyclingSpeedCadenceService->start(); + //spinBLEServer.pServer->getAdvertising()->addServiceUUID(pCyclingSpeedCadenceService->getUUID()); } void BLE_Cycling_Speed_Cadence::update() { diff --git a/src/BLE_Fitness_Machine_Service.cpp b/src/BLE_Fitness_Machine_Service.cpp index 6c1b5434..0e7e3d02 100644 --- a/src/BLE_Fitness_Machine_Service.cpp +++ b/src/BLE_Fitness_Machine_Service.cpp @@ -60,6 +60,7 @@ void BLE_Fitness_Machine_Service::setupService(NimBLEServer *pServer, MyCharacte fitnessMachineIndoorBikeData->setCallbacks(chrCallbacks); fitnessMachineControlPoint->setCallbacks(chrCallbacks); pFitnessMachineService->start(); + spinBLEServer.pServer->getAdvertising()->addServiceUUID(pFitnessMachineService->getUUID()); } void BLE_Fitness_Machine_Service::update() { diff --git a/src/BLE_Heart_Service.cpp b/src/BLE_Heart_Service.cpp index 09534574..87f64b7e 100644 --- a/src/BLE_Heart_Service.cpp +++ b/src/BLE_Heart_Service.cpp @@ -18,6 +18,7 @@ void BLE_Heart_Service::setupService(NimBLEServer *pServer, MyCharacteristicCall heartRateMeasurementCharacteristic->setValue(heartRateMeasurement, 2); heartRateMeasurementCharacteristic->setCallbacks(chrCallbacks); pHeartService->start(); + spinBLEServer.pServer->getAdvertising()->addServiceUUID(pHeartService->getUUID()); } void BLE_Heart_Service::update() { diff --git a/src/BLE_Server.cpp b/src/BLE_Server.cpp index 8097454a..60bd3313 100644 --- a/src/BLE_Server.cpp +++ b/src/BLE_Server.cpp @@ -50,24 +50,18 @@ void startBLEServer() { deviceInformationService.setupService(spinBLEServer.pServer); wattbikeService.setupService(spinBLEServer.pServer); // No callback needed - BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + BLEAdvertising* pAdvertising = BLEDevice::getAdvertising(); // const std::string fitnessData = {0b00000001, 0b00100000, 0b00000000}; // pAdvertising->setServiceData(FITNESSMACHINESERVICE_UUID, fitnessData); - pAdvertising->setName(userConfig->getDeviceName()); - pAdvertising->addServiceUUID(FITNESSMACHINESERVICE_UUID); - pAdvertising->addServiceUUID(CYCLINGPOWERSERVICE_UUID); - pAdvertising->addServiceUUID(CSCSERVICE_UUID); - pAdvertising->addServiceUUID(HEARTSERVICE_UUID); - pAdvertising->addServiceUUID(SMARTSPIN2K_SERVICE_UUID); - pAdvertising->addServiceUUID(WATTBIKE_SERVICE_UUID); + pAdvertising->setName(static_cast(userConfig->getDeviceName())); pAdvertising->setMaxInterval(250); pAdvertising->setMinInterval(160); pAdvertising->enableScanResponse(true); BLEFirmwareSetup(); - BLEDevice::startAdvertising(); + pAdvertising->start(); - SS2K_LOG(BLE_SERVER_LOG_TAG, "Bluetooth Characteristic defined!"); + SS2K_LOG(BLE_SERVER_LOG_TAG, "Bluetooth Characteristics defined!"); } void SpinBLEServer::update() { diff --git a/src/BLE_Wattbike_Service.cpp b/src/BLE_Wattbike_Service.cpp index b4b479dc..56c39ac0 100644 --- a/src/BLE_Wattbike_Service.cpp +++ b/src/BLE_Wattbike_Service.cpp @@ -23,6 +23,7 @@ void BLE_Wattbike_Service::setupService(NimBLEServer *pServer) { // Start the service pWattbikeService->start(); + spinBLEServer.pServer->getAdvertising()->addServiceUUID(pWattbikeService->getUUID()); } void BLE_Wattbike_Service::parseNemit() { From ece6d5efc5bdb94ad428b8e7228ef4b56e17489c Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Sat, 4 Jan 2025 22:23:12 -0500 Subject: [PATCH 05/23] NimBLE migration 90% complete --- include/BLE_Custom_Characteristic.h | 10 +++++----- platformio.ini | 6 +++--- src/BLE_Custom_Characteristic.cpp | 10 +++++++--- src/BLE_Server.cpp | 12 ++++++------ 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/include/BLE_Custom_Characteristic.h b/include/BLE_Custom_Characteristic.h index 21a5eb3a..2ccc3326 100644 --- a/include/BLE_Custom_Characteristic.h +++ b/include/BLE_Custom_Characteristic.h @@ -74,12 +74,12 @@ class BLE_ss2kCustomCharacteristic { static void parseNemit(); private: - BLEService *pSmartSpin2kService; - BLECharacteristic *smartSpin2kCharacteristic; + NimBLEService *pSmartSpin2kService; + NimBLECharacteristic *smartSpin2kCharacteristic; uint8_t ss2kCustomCharacteristicValue[3] = {0x00, 0x00, 0x00}; }; -class ss2kCustomCharacteristicCallbacks : public BLECharacteristicCallbacks { - void onWrite(BLECharacteristic *); - void onSubscribe(NimBLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc, uint16_t subValue); +class ss2kCustomCharacteristicCallbacks : public NimBLECharacteristicCallbacks { + void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override; + void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override; }; diff --git a/platformio.ini b/platformio.ini index 764fa1f0..9a876eef 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,10 +26,10 @@ build_flags = !python build_date_macro.py -D CONFIG_BT_NIMBLE_MAX_CONNECTIONS=7 -D CONFIG_MDNS_STRICT_MODE=1 - -D CORE_DEBUG_LEVEL=5 + -D CORE_DEBUG_LEVEL=1 -D ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE=3500 lib_deps = - https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.1.2.zip + https://github.com/h2zero/NimBLE-Arduino#bugfix/adv-data-sequencing https://github.com/teemuatlut/TMCStepper/archive/refs/tags/v0.7.3.zip https://github.com/bblanchon/ArduinoJson/archive/refs/tags/v6.20.0.zip https://github.com/gin66/FastAccelStepper/archive/refs/tags/0.31.2.zip @@ -71,4 +71,4 @@ check_flags = --suppress=unmatchedSuppression --suppress=missingIncludeSystem check_severity = medium, high -check_skip_packages = true \ No newline at end of file +check_skip_packages = true diff --git a/src/BLE_Custom_Characteristic.cpp b/src/BLE_Custom_Characteristic.cpp index 6681e7fe..ac8a90d4 100644 --- a/src/BLE_Custom_Characteristic.cpp +++ b/src/BLE_Custom_Characteristic.cpp @@ -100,12 +100,16 @@ void BLE_ss2kCustomCharacteristic::setupService(NimBLEServer *pServer) { void BLE_ss2kCustomCharacteristic::update() {} -void ss2kCustomCharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic) { +void ss2kCustomCharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) { std::string rxValue = pCharacteristic->getValue(); + SS2K_LOG(CUSTOM_CHAR_LOG_TAG, "Write from %s", connInfo.getAddress().toString().c_str()); BLE_ss2kCustomCharacteristic::process(rxValue); } -void ss2kCustomCharacteristicCallbacks::onSubscribe(NimBLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc, uint16_t subValue) { NimBLEDevice::setMTU(515); } +void ss2kCustomCharacteristicCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) { + SS2K_LOG(CUSTOM_CHAR_LOG_TAG, "Subscribe from %s", connInfo.getAddress().toString().c_str()); + NimBLEDevice::setMTU(515); +} void BLE_ss2kCustomCharacteristic::notify(char _item, int tableRow) { // regular non power table update @@ -926,4 +930,4 @@ void BLE_ss2kCustomCharacteristic::parseNemit() { BLE_ss2kCustomCharacteristic::notify(BLE_homingSensitivity); return; } -} \ No newline at end of file +} diff --git a/src/BLE_Server.cpp b/src/BLE_Server.cpp index 60bd3313..64912eb7 100644 --- a/src/BLE_Server.cpp +++ b/src/BLE_Server.cpp @@ -42,6 +42,8 @@ void startBLEServer() { spinBLEServer.pServer->setCallbacks(new MyServerCallbacks()); // start services + BLEAdvertising* pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->enableScanResponse(true); cyclingSpeedCadenceService.setupService(spinBLEServer.pServer, &chrCallbacks); cyclingPowerService.setupService(spinBLEServer.pServer, &chrCallbacks); heartService.setupService(spinBLEServer.pServer, &chrCallbacks); @@ -49,16 +51,14 @@ void startBLEServer() { ss2kCustomCharacteristic.setupService(spinBLEServer.pServer); deviceInformationService.setupService(spinBLEServer.pServer); wattbikeService.setupService(spinBLEServer.pServer); // No callback needed - - BLEAdvertising* pAdvertising = BLEDevice::getAdvertising(); + BLEFirmwareSetup(); + // const std::string fitnessData = {0b00000001, 0b00100000, 0b00000000}; // pAdvertising->setServiceData(FITNESSMACHINESERVICE_UUID, fitnessData); pAdvertising->setName(static_cast(userConfig->getDeviceName())); pAdvertising->setMaxInterval(250); pAdvertising->setMinInterval(160); - pAdvertising->enableScanResponse(true); - BLEFirmwareSetup(); pAdvertising->start(); SS2K_LOG(BLE_SERVER_LOG_TAG, "Bluetooth Characteristics defined!"); @@ -184,8 +184,8 @@ void MyCharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, N } void MyCharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, int code) { - SS2K_LOG(BLE_SERVER_LOG_TAG, "Notification/Indication status code: %d for characteristic: %s", - code, pCharacteristic->getUUID().toString().c_str()); + //SS2K_LOG(BLE_SERVER_LOG_TAG, "Notification/Indication status code: %d for characteristic: %s", + // code, pCharacteristic->getUUID().toString().c_str()); } void MyCharacteristicCallbacks::onSubscribe(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) { From c1544a9997b7b3e381f7a96465a6f74f0c5cf02b Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Sun, 5 Jan 2025 20:27:12 -0600 Subject: [PATCH 06/23] updated advertising --- src/BLE_Firmware_Update.cpp | 2 +- src/BLE_Heart_Service.cpp | 2 +- src/Main.cpp | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/BLE_Firmware_Update.cpp b/src/BLE_Firmware_Update.cpp index c33db42a..640c932c 100644 --- a/src/BLE_Firmware_Update.cpp +++ b/src/BLE_Firmware_Update.cpp @@ -181,7 +181,7 @@ void BLEFirmwareSetup() { pService->start(); // 6. Start advertising - spinBLEServer.pServer->getAdvertising()->addServiceUUID(pService->getUUID()); + // spinBLEServer.pServer->getAdvertising()->addServiceUUID(pService->getUUID()); downloadFlag = false; } diff --git a/src/BLE_Heart_Service.cpp b/src/BLE_Heart_Service.cpp index 87f64b7e..0cf1afbc 100644 --- a/src/BLE_Heart_Service.cpp +++ b/src/BLE_Heart_Service.cpp @@ -18,7 +18,7 @@ void BLE_Heart_Service::setupService(NimBLEServer *pServer, MyCharacteristicCall heartRateMeasurementCharacteristic->setValue(heartRateMeasurement, 2); heartRateMeasurementCharacteristic->setCallbacks(chrCallbacks); pHeartService->start(); - spinBLEServer.pServer->getAdvertising()->addServiceUUID(pHeartService->getUUID()); + //spinBLEServer.pServer->getAdvertising()->addServiceUUID(pHeartService->getUUID()); } void BLE_Heart_Service::update() { diff --git a/src/Main.cpp b/src/Main.cpp index 2baa4bf9..0eefdce5 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -263,10 +263,8 @@ void SS2K::maintenanceLoop(void *pvParameters) { userPWC->saveToLittleFS(); } - // Things to do every two seconds - if ((millis() - intervalTimer) > 2003) { // add check here for when to restart WiFi - // maybe if in STA mode and 8.8.8.8 no ping return? - // ss2k->restartWifi(); + // Things to do every one seconds + if ((millis() - intervalTimer) > 1003) { logHandler.writeLogs(); webSocketAppender.Loop(); intervalTimer = millis(); From ccb35dfe5b3f5f63c1a9d158f945116628652f67 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Sun, 5 Jan 2025 21:05:03 -0600 Subject: [PATCH 07/23] Added Supported Services vector --- include/BLE_Common.h | 18 ++++++ src/BLE_Client.cpp | 128 ++++++++++++++++++++++++------------------- 2 files changed, 90 insertions(+), 56 deletions(-) diff --git a/include/BLE_Common.h b/include/BLE_Common.h index 2fa559c2..27dcfeaa 100644 --- a/include/BLE_Common.h +++ b/include/BLE_Common.h @@ -13,9 +13,27 @@ #include #include #include +#include #include "Main.h" #include "BLE_Definitions.h" #include "BLE_Wattbike_Service.h" +#include "Constants.h" + +// Vector of supported BLE services and their corresponding characteristic UUIDs +struct BLEServiceInfo { + BLEUUID serviceUUID; + BLEUUID characteristicUUID; + String name; +}; + +inline const std::vector SUPPORTED_SERVICES = { + {CYCLINGPOWERSERVICE_UUID, CYCLINGPOWERMEASUREMENT_UUID, "Cycling Power Service"}, + {HEARTSERVICE_UUID, HEARTCHARACTERISTIC_UUID, "Heart Rate Service"}, + {FITNESSMACHINESERVICE_UUID, FITNESSMACHINEINDOORBIKEDATA_UUID, "Fitness Machine Service"}, + {HID_SERVICE_UUID, HID_REPORT_DATA_UUID, "HID Service"}, + {ECHELON_SERVICE_UUID, ECHELON_DATA_UUID, "Echelon Service"}, + {FLYWHEEL_UART_SERVICE_UUID, FLYWHEEL_UART_TX_UUID, "Flywheel UART Service"} +}; #define BLE_CLIENT_LOG_TAG "BLE_Client" #define BLE_COMMON_LOG_TAG "BLE_Common" diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp index 0a369037..f88ed247 100644 --- a/src/BLE_Client.cpp +++ b/src/BLE_Client.cpp @@ -48,7 +48,7 @@ void SpinBLEClient::start() { static void onNotify(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { // Parse BLE shifter info. - if (pBLERemoteCharacteristic->getRemoteService()->getUUID() == HID_SERVICE_UUID) { + if (pBLERemoteCharacteristic->getRemoteService()->getUUID().equals(HID_SERVICE_UUID)) { Serial.print(pData[0], HEX); if (pData[0] == 0x04) { rtConfig->setShifterPosition(rtConfig->getShifterPosition() + 1); @@ -139,17 +139,8 @@ bool SpinBLEClient::connectToServer() { for (int i = 0; i < NUM_BLE_DEVICES; i++) { if (spinBLEClient.myBLEDevices[i].doConnect == true) { // Client wants to be connected if (spinBLEClient.myBLEDevices[i].advertisedDevice) { // Client is assigned - // If this device is advertising HR service AND not advertising FTMS service AND there is no connected PM AND the next slot is set to connect, connect that one first - // and connect the HRM last. if (spinBLEClient.myBLEDevices[i].advertisedDevice->isAdvertisingService(HEARTSERVICE_UUID) && - // (!spinBLEClient.myBLEDevices[i].advertisedDevice->isAdvertisingService(FITNESSMACHINESERVICE_UUID)) && (!connectedPM) && - // (spinBLEClient.myBLEDevices[i + 1].doConnect == true)) { - // myDevice = spinBLEClient.myBLEDevices[i + 1].advertisedDevice; - // device_number = i + 1; - // SS2K_LOG(BLE_CLIENT_LOG_TAG, "Connecting HRM last."); - // } else { myDevice = spinBLEClient.myBLEDevices[i].advertisedDevice; device_number = i; - // } break; } else { SS2K_LOG(BLE_CLIENT_LOG_TAG, "doConnect and client out of alignment. Resetting device slot."); @@ -164,34 +155,31 @@ bool SpinBLEClient::connectToServer() { SS2K_LOG(BLE_CLIENT_LOG_TAG, "No Device Found to Connect"); return false; } - // FUTURE - Iterate through an array of UUID's we support instead of all the if checks. if (myDevice->getServiceUUIDCount() > 0) { - if (myDevice->isAdvertisingService(FLYWHEEL_UART_SERVICE_UUID) && (myDevice->getName() == FLYWHEEL_BLE_NAME)) { - serviceUUID = FLYWHEEL_UART_SERVICE_UUID; - charUUID = FLYWHEEL_UART_TX_UUID; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "trying to connect to Flywheel Bike"); - } else if (myDevice->isAdvertisingService(FITNESSMACHINESERVICE_UUID)) { - serviceUUID = FITNESSMACHINESERVICE_UUID; - charUUID = FITNESSMACHINEINDOORBIKEDATA_UUID; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "trying to connect to Fitness Machine Service"); - } else if (myDevice->isAdvertisingService(CYCLINGPOWERSERVICE_UUID)) { - serviceUUID = CYCLINGPOWERSERVICE_UUID; - charUUID = CYCLINGPOWERMEASUREMENT_UUID; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "trying to connect to Cycling Power Service"); - } else if (myDevice->isAdvertisingService(ECHELON_DEVICE_UUID)) { - serviceUUID = ECHELON_SERVICE_UUID; - charUUID = ECHELON_DATA_UUID; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Trying to connect to Echelon Bike"); - } else if (myDevice->isAdvertisingService(HEARTSERVICE_UUID)) { - serviceUUID = HEARTSERVICE_UUID; - charUUID = HEARTCHARACTERISTIC_UUID; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Trying to connect to HRM"); - } else if (myDevice->isAdvertisingService(HID_SERVICE_UUID)) { - serviceUUID = HID_SERVICE_UUID; - charUUID = HID_REPORT_DATA_UUID; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Trying to connect to BLE HID remote"); - } else { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "No advertised UUID found"); + bool found = false; + for (const auto& service : SUPPORTED_SERVICES) { + // Special case for Flywheel which requires name check + if (service.serviceUUID.equals(FLYWHEEL_UART_SERVICE_UUID)) { + if (myDevice->isAdvertisingService(service.serviceUUID) && myDevice->getName() == FLYWHEEL_BLE_NAME) { + serviceUUID = service.serviceUUID; + charUUID = service.characteristicUUID; + SS2K_LOG(BLE_CLIENT_LOG_TAG, "trying to connect to %s", service.name.c_str()); + found = true; + break; + } + } + // For all other services + else if (myDevice->isAdvertisingService(service.serviceUUID)) { + serviceUUID = service.serviceUUID; + charUUID = service.characteristicUUID; + SS2K_LOG(BLE_CLIENT_LOG_TAG, "trying to connect to %s", service.name.c_str()); + found = true; + break; + } + } + + if (!found) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "No supported service UUID found"); spinBLEClient.myBLEDevices[device_number].reset(); return false; } @@ -280,7 +268,7 @@ bool SpinBLEClient::connectToServer() { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Connected to: %s - %s RSSI %d", this->adevName2UniqueName(myDevice).c_str(), pClient->getPeerAddress().toString().c_str(), pClient->getRssi()); - if (serviceUUID == HID_SERVICE_UUID) { + if (serviceUUID.equals(HID_SERVICE_UUID)) { connectBLE_HID(pClient); this->reconnectTries = MAX_RECONNECT_TRIES; SS2K_LOG(BLE_CLIENT_LOG_TAG, "Successful remote subscription."); @@ -430,14 +418,28 @@ void ScanCallbacks::onResult(const NimBLEAdvertisedDevice *advertisedDevice) { String aDevName = spinBLEClient.adevName2UniqueName(advertisedDevice); const char *aDevAddr = advertisedDevice->getAddress().toString().c_str(); - if ((advertisedDevice->haveServiceUUID()) && (advertisedDevice->isAdvertisingService(CYCLINGPOWERSERVICE_UUID) || - (advertisedDevice->isAdvertisingService(FLYWHEEL_UART_SERVICE_UUID) && aDevName == String(FLYWHEEL_BLE_NAME)) || - advertisedDevice->isAdvertisingService(FITNESSMACHINESERVICE_UUID) || advertisedDevice->isAdvertisingService(HEARTSERVICE_UUID) || - advertisedDevice->isAdvertisingService(ECHELON_DEVICE_UUID) || advertisedDevice->isAdvertisingService(HID_SERVICE_UUID))) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Trying to match found device name: %s", aDevName.c_str()); + if (advertisedDevice->haveServiceUUID()) { + bool isSupported = false; + for (const auto& service : SUPPORTED_SERVICES) { + // Special case for Flywheel which requires name check + if (service.serviceUUID.equals(FLYWHEEL_UART_SERVICE_UUID)) { + if (advertisedDevice->isAdvertisingService(service.serviceUUID.toString()) && aDevName == String(FLYWHEEL_BLE_NAME)) { + isSupported = true; + break; + } + } + // For all other services + else if (advertisedDevice->isAdvertisingService(service.serviceUUID.toString())) { + isSupported = true; + break; + } + } + + if (isSupported) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Trying to match found device name: %s", aDevName.c_str()); // Handling for BLE connected remotes - if (advertisedDevice->getServiceUUID() == HID_SERVICE_UUID) { + if (advertisedDevice->getServiceUUID().equals(HID_SERVICE_UUID)) { if (strcmp(userConfig->getConnectedRemote(), "any") == 0) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "%s%s", REMOTE, STRING_MATCHED_ANY); } else { @@ -450,7 +452,7 @@ void ScanCallbacks::onResult(const NimBLEAdvertisedDevice *advertisedDevice) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "%s %s%s", REMOTE, NAME, MATCHED, aDevName); } } - } else if (advertisedDevice->getServiceUUID() == HEARTSERVICE_UUID) { + } else if (advertisedDevice->getServiceUUID().equals(HEARTSERVICE_UUID)) { if (strcmp(userConfig->getConnectedHeartMonitor(), "any") == 0) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "%s%s", HRM, STRING_MATCHED_ANY); } else { @@ -487,6 +489,7 @@ void ScanCallbacks::onResult(const NimBLEAdvertisedDevice *advertisedDevice) { } SS2K_LOG(BLE_CLIENT_LOG_TAG, "Checking Slot %d", i); } + } } } @@ -527,9 +530,22 @@ void ScanCallbacks::onScanEnd(const NimBLEScanResults &results, int reason) { } } - // is this device advertising something we're interested in? - if (!isDuplicate && (d->isAdvertisingService(CYCLINGPOWERSERVICE_UUID) || d->isAdvertisingService(HEARTSERVICE_UUID) || d->isAdvertisingService(FLYWHEEL_UART_SERVICE_UUID) || - d->isAdvertisingService(FITNESSMACHINESERVICE_UUID) || d->isAdvertisingService(ECHELON_DEVICE_UUID) || d->isAdvertisingService(HID_SERVICE_UUID))) { + // Check if device advertises any of our supported services + bool isSupported = false; + for (const auto& service : SUPPORTED_SERVICES) { + if (service.serviceUUID.equals(FLYWHEEL_UART_SERVICE_UUID)) { + if (d->isAdvertisingService(service.serviceUUID) && spinBLEClient.adevName2UniqueName(d) == String(FLYWHEEL_BLE_NAME)) { + isSupported = true; + break; + } + } + else if (d->isAdvertisingService(service.serviceUUID.toString())) { + isSupported = true; + break; + } + } + + if (!isDuplicate && isSupported) { String device = "device " + String(devices.size()); // Use the current size to index the new device devices[device]["name"] = spinBLEClient.adevName2UniqueName(d); @@ -569,7 +585,7 @@ void SpinBLEClient::removeDuplicates(NimBLEClient *pClient) { for (size_t i = 0; i < NUM_BLE_DEVICES; i++) { // Disconnect oldest PM to avoid two connected. oldBLEd = this->myBLEDevices[i]; if (oldBLEd.advertisedDevice) { - if ((tBLEd.serviceUUID == oldBLEd.serviceUUID) && (tBLEd.peerAddress != oldBLEd.peerAddress)) { + if ((tBLEd.serviceUUID.equals(oldBLEd.serviceUUID)) && (tBLEd.peerAddress != oldBLEd.peerAddress)) { if (BLEDevice::getClientByPeerAddress(oldBLEd.peerAddress)) { if (BLEDevice::getClientByPeerAddress(oldBLEd.peerAddress)->isConnected()) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "%s Matched another service. Disconnecting: %s", tBLEd.peerAddress.toString().c_str(), oldBLEd.peerAddress.toString().c_str()); @@ -602,7 +618,7 @@ void SpinBLEClient::FTMSControlPointWrite(const uint8_t *pData, int length) { modData[i] = pData[i]; } for (int i = 0; i < NUM_BLE_DEVICES; i++) { - if (myBLEDevices[i].getPostConnected() && (myBLEDevices[i].serviceUUID == FITNESSMACHINESERVICE_UUID)) { + if (myBLEDevices[i].getPostConnected() && (myBLEDevices[i].serviceUUID.equals(FITNESSMACHINESERVICE_UUID))) { if (NimBLEDevice::getClientByPeerAddress(myBLEDevices[i].peerAddress)->getService(FITNESSMACHINESERVICE_UUID)) { pClient = NimBLEDevice::getClientByPeerAddress(myBLEDevices[i].peerAddress); break; @@ -770,7 +786,7 @@ void SpinBLEClient::connectBLE_HID(NimBLEClient *pClient) { // in subscribing to only one. std::vector charVector = pSvc->getCharacteristics(true); for (auto &it : charVector) { - if (it->getUUID() == NimBLEUUID(HID_REPORT_DATA_UUID)) { + if (it->getUUID().equals(NimBLEUUID(HID_REPORT_DATA_UUID))) { Serial.println(it->toString().c_str()); if (it->canNotify()) { if (!it->subscribe(true, onNotify)) { @@ -884,20 +900,20 @@ void SpinBLEAdvertisedDevice::set(const NimBLEAdvertisedDevice *device, int id, this->serviceUUID = BLEUUID(inServiceUUID); this->charUUID = BLEUUID(inCharUUID); this->dataBufferQueue = xQueueCreate(4, sizeof(NotifyData)); - if (inServiceUUID == HEARTSERVICE_UUID) { + if (inServiceUUID.equals(HEARTSERVICE_UUID)) { this->isHRM = true; spinBLEClient.connectedHRM = true; SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered HRM on Connect"); - } else if (inServiceUUID == CSCSERVICE_UUID) { + } else if (inServiceUUID.equals(CSCSERVICE_UUID)) { this->isCSC = true; spinBLEClient.connectedCD = true; SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered CSC on Connect"); - } else if (inServiceUUID == CYCLINGPOWERSERVICE_UUID || inServiceUUID == FITNESSMACHINESERVICE_UUID || inServiceUUID == FLYWHEEL_UART_SERVICE_UUID || - inServiceUUID == ECHELON_SERVICE_UUID || inServiceUUID == PELOTON_DATA_UUID) { + } else if (inServiceUUID.equals(CYCLINGPOWERSERVICE_UUID) || inServiceUUID.equals(FITNESSMACHINESERVICE_UUID) || inServiceUUID.equals(FLYWHEEL_UART_SERVICE_UUID) || + inServiceUUID.equals(ECHELON_SERVICE_UUID) || inServiceUUID.equals(PELOTON_DATA_UUID)) { this->isPM = true; spinBLEClient.connectedPM = true; SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered PM on Connect"); - } else if (inServiceUUID == HID_SERVICE_UUID) { + } else if (inServiceUUID.equals(HID_SERVICE_UUID)) { this->isRemote = true; spinBLEClient.connectedRemote = true; SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered Remote on Connect"); From 841ec90628097a776caab982c1d9e148dd6bf606 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Mon, 6 Jan 2025 11:05:51 -0600 Subject: [PATCH 08/23] WIP --- include/BLE_Common.h | 27 +++++--- platformio.ini | 5 +- src/BLE_Client.cpp | 160 ++++++++++++++++++------------------------- src/BLE_Common.cpp | 36 ++++++++-- 4 files changed, 120 insertions(+), 108 deletions(-) diff --git a/include/BLE_Common.h b/include/BLE_Common.h index 27dcfeaa..c0fbb3d5 100644 --- a/include/BLE_Common.h +++ b/include/BLE_Common.h @@ -26,14 +26,19 @@ struct BLEServiceInfo { String name; }; -inline const std::vector SUPPORTED_SERVICES = { - {CYCLINGPOWERSERVICE_UUID, CYCLINGPOWERMEASUREMENT_UUID, "Cycling Power Service"}, - {HEARTSERVICE_UUID, HEARTCHARACTERISTIC_UUID, "Heart Rate Service"}, - {FITNESSMACHINESERVICE_UUID, FITNESSMACHINEINDOORBIKEDATA_UUID, "Fitness Machine Service"}, - {HID_SERVICE_UUID, HID_REPORT_DATA_UUID, "HID Service"}, - {ECHELON_SERVICE_UUID, ECHELON_DATA_UUID, "Echelon Service"}, - {FLYWHEEL_UART_SERVICE_UUID, FLYWHEEL_UART_TX_UUID, "Flywheel UART Service"} -}; +namespace BLEServices { + const std::vector SUPPORTED_SERVICES = { + {CYCLINGPOWERSERVICE_UUID, CYCLINGPOWERMEASUREMENT_UUID, "Cycling Power Service"}, + {CSCSERVICE_UUID, CSCMEASUREMENT_UUID, "Cycling Speed And Cadence Service"}, + {HEARTSERVICE_UUID, HEARTCHARACTERISTIC_UUID, "Heart Rate Service"}, + {FITNESSMACHINESERVICE_UUID, FITNESSMACHINEINDOORBIKEDATA_UUID, "Fitness Machine Service"}, + {HID_SERVICE_UUID, HID_REPORT_DATA_UUID, "HID Service"}, + {ECHELON_SERVICE_UUID, ECHELON_DATA_UUID, "Echelon Service"}, + {FLYWHEEL_UART_SERVICE_UUID, FLYWHEEL_UART_TX_UUID, "Flywheel UART Service"} + }; +} + +using BLEServices::SUPPORTED_SERVICES; #define BLE_CLIENT_LOG_TAG "BLE_Client" #define BLE_COMMON_LOG_TAG "BLE_Common" @@ -54,6 +59,12 @@ void setupBLE(); extern TaskHandle_t BLEClientTask; // ***********************Common********************************** void BLECommunications(); + +// Check if a BLE device supports any of our supported services +bool isDeviceSupported(const NimBLEAdvertisedDevice* advertisedDevice, const String& deviceName = ""); + +// Get service info for a supported device +const BLEServiceInfo* getDeviceServiceInfo(const NimBLEAdvertisedDevice* advertisedDevice, const String& deviceName = ""); // *****************************Server**************************** class MyServerCallbacks : public NimBLEServerCallbacks { public: diff --git a/platformio.ini b/platformio.ini index 9a876eef..4ebdea8b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,10 +26,11 @@ build_flags = !python build_date_macro.py -D CONFIG_BT_NIMBLE_MAX_CONNECTIONS=7 -D CONFIG_MDNS_STRICT_MODE=1 - -D CORE_DEBUG_LEVEL=1 + -D CORE_DEBUG_LEVEL=5 -D ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE=3500 + -std=gnu++17 lib_deps = - https://github.com/h2zero/NimBLE-Arduino#bugfix/adv-data-sequencing + https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.1.3.zip https://github.com/teemuatlut/TMCStepper/archive/refs/tags/v0.7.3.zip https://github.com/bblanchon/ArduinoJson/archive/refs/tags/v6.20.0.zip https://github.com/gin66/FastAccelStepper/archive/refs/tags/0.31.2.zip diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp index f88ed247..eac8a9bf 100644 --- a/src/BLE_Client.cpp +++ b/src/BLE_Client.cpp @@ -46,9 +46,9 @@ void SpinBLEClient::start() { pBLEScan->setActiveScan(true); } -static void onNotify(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { +static void notifyCB(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { // Parse BLE shifter info. - if (pBLERemoteCharacteristic->getRemoteService()->getUUID().equals(HID_SERVICE_UUID)) { + if (pBLERemoteCharacteristic->getRemoteService()->getUUID() == HID_SERVICE_UUID) { Serial.print(pData[0], HEX); if (pData[0] == 0x04) { rtConfig->setShifterPosition(rtConfig->getShifterPosition() + 1); @@ -57,7 +57,8 @@ static void onNotify(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t rtConfig->setShifterPosition(rtConfig->getShifterPosition() - 1); } } - + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Notification received from %s - Service UUID: %s", pBLERemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString().c_str(), + pBLERemoteCharacteristic->getRemoteService()->getUUID().toString().c_str()); // enqueue sensor data for (size_t i = 0; i < NUM_BLE_DEVICES; i++) { if (pBLERemoteCharacteristic->getClient()->getPeerAddress() == spinBLEClient.myBLEDevices[i].peerAddress) { @@ -66,6 +67,16 @@ static void onNotify(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t } } +void testnotifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { + std::string str = (isNotify == true) ? "Notification" : "Indication"; + str += " from "; + str += pRemoteCharacteristic->getClient()->getPeerAddress().toString(); + str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString(); + str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString(); + str += ", Value = " + std::string((char*)pData, length); + Serial.printf("%s\n", str.c_str()); +} + // BLE Client loop task. // Manages device connections and scanning. void bleClientTask(void *pvParameters) { @@ -156,33 +167,16 @@ bool SpinBLEClient::connectToServer() { return false; } if (myDevice->getServiceUUIDCount() > 0) { - bool found = false; - for (const auto& service : SUPPORTED_SERVICES) { - // Special case for Flywheel which requires name check - if (service.serviceUUID.equals(FLYWHEEL_UART_SERVICE_UUID)) { - if (myDevice->isAdvertisingService(service.serviceUUID) && myDevice->getName() == FLYWHEEL_BLE_NAME) { - serviceUUID = service.serviceUUID; - charUUID = service.characteristicUUID; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "trying to connect to %s", service.name.c_str()); - found = true; - break; - } - } - // For all other services - else if (myDevice->isAdvertisingService(service.serviceUUID)) { - serviceUUID = service.serviceUUID; - charUUID = service.characteristicUUID; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "trying to connect to %s", service.name.c_str()); - found = true; - break; - } - } - - if (!found) { + const BLEServiceInfo *serviceInfo = getDeviceServiceInfo(myDevice, String(myDevice->getName().c_str())); + if (!serviceInfo) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "No supported service UUID found"); spinBLEClient.myBLEDevices[device_number].reset(); return false; } + + serviceUUID = serviceInfo->serviceUUID; + charUUID = serviceInfo->characteristicUUID; + SS2K_LOG(BLE_CLIENT_LOG_TAG, "trying to connect to %s", serviceInfo->name.c_str()); } else { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Device has no Service UUID"); spinBLEClient.myBLEDevices[device_number].reset(); @@ -268,7 +262,7 @@ bool SpinBLEClient::connectToServer() { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Connected to: %s - %s RSSI %d", this->adevName2UniqueName(myDevice).c_str(), pClient->getPeerAddress().toString().c_str(), pClient->getRssi()); - if (serviceUUID.equals(HID_SERVICE_UUID)) { + if (serviceUUID == HID_SERVICE_UUID) { connectBLE_HID(pClient); this->reconnectTries = MAX_RECONNECT_TRIES; SS2K_LOG(BLE_CLIENT_LOG_TAG, "Successful remote subscription."); @@ -299,7 +293,8 @@ bool SpinBLEClient::connectToServer() { SS2K_LOG(BLE_COMMON_LOG_TAG, "%s", logBuf); } if (pChr->canNotify()) { - if (!pChr->subscribe(true, onNotify)) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Setting up notifications for service %s char %s, handle %d", serviceUUID.toString().c_str(), charUUID.toString().c_str(), pChr->getHandle()); + if (!pChr->subscribe(true, testnotifyCB)) { /** Disconnect if subscribe failed */ SS2K_LOG(BLE_CLIENT_LOG_TAG, "Notifications Failed for %s", pClient->getPeerAddress().toString().c_str()); spinBLEClient.myBLEDevices[device_number].reset(); @@ -308,10 +303,10 @@ bool SpinBLEClient::connectToServer() { NimBLEDevice::deleteClient(pClient); return false; } - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Notifications Subscribed for %s", pClient->getPeerAddress().toString().c_str()); + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Notifications Subscribed for %s, UUID %s", pClient->getPeerAddress().toString().c_str(), pChr->getUUID().toString().c_str()); } else if (pChr->canIndicate()) { /** Send false as first argument to subscribe to indications instead of notifications */ - if (!pChr->subscribe(false, onNotify)) { + if (!pChr->subscribe(false, notifyCB)) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Indications Failed for %s", pClient->getPeerAddress().toString().c_str()); /** Disconnect if subscribe failed */ spinBLEClient.myBLEDevices[device_number].reset(); @@ -342,7 +337,7 @@ bool SpinBLEClient::connectToServer() { /** None of these are required as they will be handled by the library with defaults. ** ** Remove as you see fit for your needs */ -void MyClientCallback::onConnect(NimBLEClient *pClient) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Connected"); } +void MyClientCallback::onConnect(NimBLEClient *pClient) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Connected, %s", pClient->getPeerAddress().toString().c_str()); } void MyClientCallback::onDisconnect(NimBLEClient *pClient, int reason) { if (!pClient->isConnected()) { @@ -359,7 +354,7 @@ void MyClientCallback::onDisconnect(NimBLEClient *pClient, int reason) { } if ((spinBLEClient.myBLEDevices[i].charUUID == CYCLINGPOWERMEASUREMENT_UUID) || (spinBLEClient.myBLEDevices[i].charUUID == FITNESSMACHINEINDOORBIKEDATA_UUID) || (spinBLEClient.myBLEDevices[i].charUUID == FLYWHEEL_UART_RX_UUID) || (spinBLEClient.myBLEDevices[i].charUUID == ECHELON_SERVICE_UUID) || - (spinBLEClient.myBLEDevices[i].charUUID == CYCLINGPOWERSERVICE_UUID)) { + (spinBLEClient.myBLEDevices[i].charUUID == CYCLINGPOWERSERVICE_UUID) || (spinBLEClient.myBLEDevices[i].charUUID == CSCSERVICE_UUID)) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Deregistered PM on Disconnect"); rtConfig->pm_batt.setValue(0); } @@ -418,28 +413,11 @@ void ScanCallbacks::onResult(const NimBLEAdvertisedDevice *advertisedDevice) { String aDevName = spinBLEClient.adevName2UniqueName(advertisedDevice); const char *aDevAddr = advertisedDevice->getAddress().toString().c_str(); - if (advertisedDevice->haveServiceUUID()) { - bool isSupported = false; - for (const auto& service : SUPPORTED_SERVICES) { - // Special case for Flywheel which requires name check - if (service.serviceUUID.equals(FLYWHEEL_UART_SERVICE_UUID)) { - if (advertisedDevice->isAdvertisingService(service.serviceUUID.toString()) && aDevName == String(FLYWHEEL_BLE_NAME)) { - isSupported = true; - break; - } - } - // For all other services - else if (advertisedDevice->isAdvertisingService(service.serviceUUID.toString())) { - isSupported = true; - break; - } - } - - if (isSupported) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Trying to match found device name: %s", aDevName.c_str()); + if (advertisedDevice->haveServiceUUID() && isDeviceSupported(advertisedDevice, aDevName)) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Trying to match found device name: %s", aDevName.c_str()); // Handling for BLE connected remotes - if (advertisedDevice->getServiceUUID().equals(HID_SERVICE_UUID)) { + if (advertisedDevice->getServiceUUID() == HID_SERVICE_UUID) { if (strcmp(userConfig->getConnectedRemote(), "any") == 0) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "%s%s", REMOTE, STRING_MATCHED_ANY); } else { @@ -452,7 +430,7 @@ void ScanCallbacks::onResult(const NimBLEAdvertisedDevice *advertisedDevice) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "%s %s%s", REMOTE, NAME, MATCHED, aDevName); } } - } else if (advertisedDevice->getServiceUUID().equals(HEARTSERVICE_UUID)) { + } else if (advertisedDevice->getServiceUUID() == HEARTSERVICE_UUID) { if (strcmp(userConfig->getConnectedHeartMonitor(), "any") == 0) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "%s%s", HRM, STRING_MATCHED_ANY); } else { @@ -489,7 +467,6 @@ void ScanCallbacks::onResult(const NimBLEAdvertisedDevice *advertisedDevice) { } SS2K_LOG(BLE_CLIENT_LOG_TAG, "Checking Slot %d", i); } - } } } @@ -530,22 +507,7 @@ void ScanCallbacks::onScanEnd(const NimBLEScanResults &results, int reason) { } } - // Check if device advertises any of our supported services - bool isSupported = false; - for (const auto& service : SUPPORTED_SERVICES) { - if (service.serviceUUID.equals(FLYWHEEL_UART_SERVICE_UUID)) { - if (d->isAdvertisingService(service.serviceUUID) && spinBLEClient.adevName2UniqueName(d) == String(FLYWHEEL_BLE_NAME)) { - isSupported = true; - break; - } - } - else if (d->isAdvertisingService(service.serviceUUID.toString())) { - isSupported = true; - break; - } - } - - if (!isDuplicate && isSupported) { + if (!isDuplicate && d->haveServiceUUID() && isDeviceSupported(d, spinBLEClient.adevName2UniqueName(d))) { String device = "device " + String(devices.size()); // Use the current size to index the new device devices[device]["name"] = spinBLEClient.adevName2UniqueName(d); @@ -585,7 +547,7 @@ void SpinBLEClient::removeDuplicates(NimBLEClient *pClient) { for (size_t i = 0; i < NUM_BLE_DEVICES; i++) { // Disconnect oldest PM to avoid two connected. oldBLEd = this->myBLEDevices[i]; if (oldBLEd.advertisedDevice) { - if ((tBLEd.serviceUUID.equals(oldBLEd.serviceUUID)) && (tBLEd.peerAddress != oldBLEd.peerAddress)) { + if ((tBLEd.serviceUUID == oldBLEd.serviceUUID) && (tBLEd.peerAddress != oldBLEd.peerAddress)) { if (BLEDevice::getClientByPeerAddress(oldBLEd.peerAddress)) { if (BLEDevice::getClientByPeerAddress(oldBLEd.peerAddress)->isConnected()) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "%s Matched another service. Disconnecting: %s", tBLEd.peerAddress.toString().c_str(), oldBLEd.peerAddress.toString().c_str()); @@ -618,7 +580,7 @@ void SpinBLEClient::FTMSControlPointWrite(const uint8_t *pData, int length) { modData[i] = pData[i]; } for (int i = 0; i < NUM_BLE_DEVICES; i++) { - if (myBLEDevices[i].getPostConnected() && (myBLEDevices[i].serviceUUID.equals(FITNESSMACHINESERVICE_UUID))) { + if (myBLEDevices[i].getPostConnected() && (myBLEDevices[i].serviceUUID == FITNESSMACHINESERVICE_UUID)) { if (NimBLEDevice::getClientByPeerAddress(myBLEDevices[i].peerAddress)->getService(FITNESSMACHINESERVICE_UUID)) { pClient = NimBLEDevice::getClientByPeerAddress(myBLEDevices[i].peerAddress); break; @@ -786,10 +748,10 @@ void SpinBLEClient::connectBLE_HID(NimBLEClient *pClient) { // in subscribing to only one. std::vector charVector = pSvc->getCharacteristics(true); for (auto &it : charVector) { - if (it->getUUID().equals(NimBLEUUID(HID_REPORT_DATA_UUID))) { + if (it->getUUID() == NimBLEUUID(HID_REPORT_DATA_UUID)) { Serial.println(it->toString().c_str()); if (it->canNotify()) { - if (!it->subscribe(true, onNotify)) { + if (!it->subscribe(true, notifyCB)) { /** Disconnect if subscribe failed */ Serial.println("subscribe notification failed"); NimBLEDevice::deleteClient(pClient); @@ -900,26 +862,38 @@ void SpinBLEAdvertisedDevice::set(const NimBLEAdvertisedDevice *device, int id, this->serviceUUID = BLEUUID(inServiceUUID); this->charUUID = BLEUUID(inCharUUID); this->dataBufferQueue = xQueueCreate(4, sizeof(NotifyData)); - if (inServiceUUID.equals(HEARTSERVICE_UUID)) { - this->isHRM = true; - spinBLEClient.connectedHRM = true; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered HRM on Connect"); - } else if (inServiceUUID.equals(CSCSERVICE_UUID)) { - this->isCSC = true; - spinBLEClient.connectedCD = true; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered CSC on Connect"); - } else if (inServiceUUID.equals(CYCLINGPOWERSERVICE_UUID) || inServiceUUID.equals(FITNESSMACHINESERVICE_UUID) || inServiceUUID.equals(FLYWHEEL_UART_SERVICE_UUID) || - inServiceUUID.equals(ECHELON_SERVICE_UUID) || inServiceUUID.equals(PELOTON_DATA_UUID)) { - this->isPM = true; - spinBLEClient.connectedPM = true; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered PM on Connect"); - } else if (inServiceUUID.equals(HID_SERVICE_UUID)) { - this->isRemote = true; - spinBLEClient.connectedRemote = true; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered Remote on Connect"); - } else { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Failed to set service!"); + + // Only register services when we have a connected client + if (id != BLE_HS_CONN_HANDLE_NONE) { + NimBLEClient *pClient = NimBLEDevice::getClientByPeerAddress(device->getAddress()); + if (pClient) { + // Get all services + const std::vector &services = pClient->getServices(true); + for (auto &pService : services) { + BLEUUID serviceUUID = pService->getUUID(); + + if (serviceUUID == HEARTSERVICE_UUID) { + this->isHRM = true; + spinBLEClient.connectedHRM = true; + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered HRM on Connect"); + } else if (serviceUUID == CSCSERVICE_UUID) { + this->isCSC = true; + spinBLEClient.connectedCD = true; + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered CSC on Connect"); + } else if (serviceUUID == CYCLINGPOWERSERVICE_UUID || serviceUUID == FITNESSMACHINESERVICE_UUID || serviceUUID == FLYWHEEL_UART_SERVICE_UUID || + serviceUUID == ECHELON_SERVICE_UUID || serviceUUID == PELOTON_DATA_UUID) { + this->isPM = true; + spinBLEClient.connectedPM = true; + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered PM on Connect"); + } else if (serviceUUID == HID_SERVICE_UUID) { + this->isRemote = true; + spinBLEClient.connectedRemote = true; + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered Remote on Connect"); + } + } + } } + // During initial discovery, just store the device info without registering services } void SpinBLEAdvertisedDevice::reset() { diff --git a/src/BLE_Common.cpp b/src/BLE_Common.cpp index 1347d460..d24a2106 100644 --- a/src/BLE_Common.cpp +++ b/src/BLE_Common.cpp @@ -17,6 +17,32 @@ bool hr2p = false; +const BLEServiceInfo* getDeviceServiceInfo(const NimBLEAdvertisedDevice* advertisedDevice, const String& deviceName) { + if (!advertisedDevice->haveServiceUUID()) { + return nullptr; + } + + for (const auto& service : SUPPORTED_SERVICES) { + // Special case for Flywheel which requires name check + if (service.serviceUUID == FLYWHEEL_UART_SERVICE_UUID) { + if (advertisedDevice->isAdvertisingService(service.serviceUUID) && + deviceName == String(FLYWHEEL_BLE_NAME)) { + return &service; + } + } + // For all other services + else if (advertisedDevice->isAdvertisingService(service.serviceUUID)) { + return &service; + } + } + + return nullptr; +} + +bool isDeviceSupported(const NimBLEAdvertisedDevice* advertisedDevice, const String& deviceName) { + return getDeviceServiceInfo(advertisedDevice, deviceName) != nullptr; +} + void BLECommunications() { static unsigned long int bleCommTimer = millis(); if (((millis() - bleCommTimer) > BLE_NOTIFY_DELAY) && !ss2k->isUpdating) { @@ -24,10 +50,10 @@ void BLECommunications() { // **********************************Client*************************************** for (auto &_BLEd : spinBLEClient.myBLEDevices) { // loop through discovered devices if (_BLEd.connectedClientID != BLE_HS_CONN_HANDLE_NONE) { - SS2K_LOGW(BLE_COMMON_LOG_TAG, "Address: (%s) Client ID: (%d) SerUUID: (%s) CharUUID: (%s) HRM: (%s) PM: (%s) CSC: (%s) CT: (%s) doConnect: (%s) postConnect: (%s)", - _BLEd.peerAddress.toString().c_str(), _BLEd.connectedClientID, _BLEd.serviceUUID.toString().c_str(), _BLEd.charUUID.toString().c_str(), - _BLEd.isHRM ? "true" : "false", _BLEd.isPM ? "true" : "false", _BLEd.isCSC ? "true" : "false", _BLEd.isCT ? "true" : "false", _BLEd.doConnect ? "true" : "false", - _BLEd.getPostConnected() ? "true" : "false"); + // SS2K_LOGW(BLE_COMMON_LOG_TAG, "Address: (%s) Client ID: (%d) SerUUID: (%s) CharUUID: (%s) HRM: (%s) PM: (%s) CSC: (%s) CT: (%s) doConnect: (%s) postConnect: (%s)", + // _BLEd.peerAddress.toString().c_str(), _BLEd.connectedClientID, _BLEd.serviceUUID.toString().c_str(), _BLEd.charUUID.toString().c_str(), + // _BLEd.isHRM ? "true" : "false", _BLEd.isPM ? "true" : "false", _BLEd.isCSC ? "true" : "false", _BLEd.isCT ? "true" : "false", _BLEd.doConnect ? "true" : "false", + // _BLEd.getPostConnected() ? "true" : "false"); if (_BLEd.advertisedDevice) { // is device registered? if ((_BLEd.connectedClientID != BLE_HS_CONN_HANDLE_NONE) && (_BLEd.doConnect == false)) { // client must not be in connection process if (BLEDevice::getClientByPeerAddress(_BLEd.peerAddress)) { // nullptr check @@ -121,4 +147,4 @@ void BLECommunications() { digitalWrite(LED_PIN, HIGH); } } -} \ No newline at end of file +} From be7eeaa76ee7d2287f6dfb8f21c560ca6bd32dc2 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Tue, 21 Jan 2025 18:50:27 -0600 Subject: [PATCH 09/23] latest --- src/BLE_Client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp index eac8a9bf..6d5f65fa 100644 --- a/src/BLE_Client.cpp +++ b/src/BLE_Client.cpp @@ -46,7 +46,7 @@ void SpinBLEClient::start() { pBLEScan->setActiveScan(true); } -static void notifyCB(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { +void notifyCB(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { // Parse BLE shifter info. if (pBLERemoteCharacteristic->getRemoteService()->getUUID() == HID_SERVICE_UUID) { Serial.print(pData[0], HEX); From d7073faa30c61027cdd60fae291e9a5685d27659 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Tue, 21 Jan 2025 20:42:34 -0600 Subject: [PATCH 10/23] getting closer --- platformio.ini | 4 ++-- src/BLE_Client.cpp | 59 +++++++++++++++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/platformio.ini b/platformio.ini index 4ebdea8b..37bbb775 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,11 +26,11 @@ build_flags = !python build_date_macro.py -D CONFIG_BT_NIMBLE_MAX_CONNECTIONS=7 -D CONFIG_MDNS_STRICT_MODE=1 - -D CORE_DEBUG_LEVEL=5 + -D CORE_DEBUG_LEVEL=1 -D ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE=3500 -std=gnu++17 lib_deps = - https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.1.3.zip + https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.2.0.zip https://github.com/teemuatlut/TMCStepper/archive/refs/tags/v0.7.3.zip https://github.com/bblanchon/ArduinoJson/archive/refs/tags/v6.20.0.zip https://github.com/gin66/FastAccelStepper/archive/refs/tags/0.31.2.zip diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp index 66d84405..a123d1e5 100644 --- a/src/BLE_Client.cpp +++ b/src/BLE_Client.cpp @@ -67,14 +67,32 @@ void notifyCB(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pDa } } -void testnotifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { - std::string str = (isNotify == true) ? "Notification" : "Indication"; - str += " from "; - str += pRemoteCharacteristic->getClient()->getPeerAddress().toString(); - str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString(); - str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString(); - str += ", Value = " + std::string((char*)pData, length); - Serial.printf("%s\n", str.c_str()); +void subscribeToAllNotifications(NimBLEClient* pClient) { + if(!pClient || !pClient->isConnected()) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Client not connected for notifications"); + return; + } + + for(const auto& service : BLEServices::SUPPORTED_SERVICES) { + NimBLERemoteService* pSvc = pClient->getService(service.serviceUUID); + if(pSvc) { + for(const auto& pChr : pSvc->getCharacteristics()) { + if(pChr) { + if(pChr->canNotify() || pChr->canIndicate()) { + if(pChr->subscribe(true, notifyCB)) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Subscribed to %s %s", + service.name.c_str(), + pChr->getUUID().toString().c_str()); + } else { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Failed to subscribe to %s %s", + service.name.c_str(), + pChr->getUUID().toString().c_str()); + } + } + } + } + } + } } // BLE Client loop task. @@ -284,7 +302,7 @@ bool SpinBLEClient::connectToServer() { pChr = pSvc->getCharacteristic(charUUID); if (pChr) { /** make sure it's not null */ - if (pChr->canRead()) { +/* if (pChr->canRead()) { NimBLEAttValue value = pChr->readValue(); const int kLogBufMaxLength = 250; char logBuf[kLogBufMaxLength]; @@ -293,9 +311,9 @@ bool SpinBLEClient::connectToServer() { SS2K_LOG(BLE_COMMON_LOG_TAG, "%s", logBuf); } if (pChr->canNotify()) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Setting up notifications for service %s char %s, handle %d", serviceUUID.toString().c_str(), charUUID.toString().c_str(), pChr->getHandle()); - if (!pChr->subscribe(true, testnotifyCB)) { - /** Disconnect if subscribe failed */ + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Setting up notifications for service %s char %s, handle %d", serviceUUID.toString().c_str(), pChr->getUUID().toString().c_str(), pChr->getHandle()); + if (!pChr->subscribe(true, notifyCB)) { + /** Disconnect if subscribe failed *//* SS2K_LOG(BLE_CLIENT_LOG_TAG, "Notifications Failed for %s", pClient->getPeerAddress().toString().c_str()); spinBLEClient.myBLEDevices[device_number].reset(); pClient->deleteServices(); @@ -305,10 +323,10 @@ bool SpinBLEClient::connectToServer() { } SS2K_LOG(BLE_CLIENT_LOG_TAG, "Notifications Subscribed for %s, UUID %s", pClient->getPeerAddress().toString().c_str(), pChr->getUUID().toString().c_str()); } else if (pChr->canIndicate()) { - /** Send false as first argument to subscribe to indications instead of notifications */ + /** Send false as first argument to subscribe to indications instead of notifications if (!pChr->subscribe(false, notifyCB)) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Indications Failed for %s", pClient->getPeerAddress().toString().c_str()); - /** Disconnect if subscribe failed */ + /** Disconnect if subscribe failed spinBLEClient.myBLEDevices[device_number].reset(); pClient->deleteServices(); NimBLEDevice::getScan()->erase(pClient->getPeerAddress()); @@ -316,7 +334,8 @@ bool SpinBLEClient::connectToServer() { return false; } SS2K_LOG(BLE_CLIENT_LOG_TAG, "Indications Subscribed for %s", pClient->getPeerAddress().toString().c_str()); - } + } */ + subscribeToAllNotifications(pClient); this->reconnectTries = MAX_RECONNECT_TRIES; SS2K_LOG(BLE_CLIENT_LOG_TAG, "Successful %s subscription.", pChr->getUUID().toString().c_str()); spinBLEClient.myBLEDevices[device_number].doConnect = false; @@ -615,7 +634,9 @@ void SpinBLEClient::postConnect() { for (auto &_BLEd : spinBLEClient.myBLEDevices) { // Check that the device has been assigned and it hasn't been post connected. if ((_BLEd.connectedClientID != BLE_HS_CONN_HANDLE_NONE) && !_BLEd.getPostConnected()) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Post connecting: %s , ConnID %d", _BLEd.peerAddress.toString().c_str(), _BLEd.connectedClientID); + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Post connecting: %s , ConnID %d, PrimaryChar %s", _BLEd.peerAddress.toString().c_str(), _BLEd.connectedClientID, _BLEd.charUUID.toString().c_str()); + //auto testCharactreristic = NimBLEDevice::getClientByPeerAddress(_BLEd.peerAddress)->getService(CYCLINGPOWERSERVICE_UUID)->getCharacteristic(CYCLINGPOWERMEASUREMENT_UUID); + //testCharactreristic->subscribe(true, notifyCB); if (NimBLEDevice::getClientByPeerAddress(_BLEd.peerAddress)) { _BLEd.setPostConnected(true); NimBLEClient *pClient = NimBLEDevice::getClientByPeerAddress(_BLEd.peerAddress); @@ -636,7 +657,7 @@ void SpinBLEClient::postConnect() { if ((_BLEd.charUUID == FITNESSMACHINEINDOORBIKEDATA_UUID)) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Updating Connection Params for: %s", _BLEd.peerAddress.toString().c_str()); - BLEDevice::getServer()->updateConnParams(pClient->getConnId(), 100, 100, 2, 1000); + BLEDevice::getServer()->updateConnParams(pClient->getConnHandle(), 100, 100, 2, 1000); spinBLEClient.handleBattInfo(pClient, true); auto featuresCharacteristic = pClient->getService(FITNESSMACHINESERVICE_UUID)->getCharacteristic(FITNESSMACHINEFEATURE_UUID); @@ -775,11 +796,11 @@ void SpinBLEClient::connectBLE_HID(NimBLEClient *pClient) { if (it->canNotify()) { if (!it->subscribe(true, notifyCB)) { /** Disconnect if subscribe failed */ - Serial.println("subscribe notification failed"); + Serial.println("HID subscribe notification failed"); NimBLEDevice::deleteClient(pClient); return; // false; } else { - Serial.println("subscribed"); + Serial.println("subscribed to HID"); } } } From 51facd21e3172de9df2c351acfd3dd31ba6f75ad Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Tue, 21 Jan 2025 21:59:45 -0600 Subject: [PATCH 11/23] Incoming Cadence is working --- include/BLE_Common.h | 64 +++++++++++------------ src/BLE_Client.cpp | 122 ++++++++++++++----------------------------- src/BLE_Common.cpp | 56 ++++++++++---------- 3 files changed, 97 insertions(+), 145 deletions(-) diff --git a/include/BLE_Common.h b/include/BLE_Common.h index c0fbb3d5..80aa62e7 100644 --- a/include/BLE_Common.h +++ b/include/BLE_Common.h @@ -21,21 +21,19 @@ // Vector of supported BLE services and their corresponding characteristic UUIDs struct BLEServiceInfo { - BLEUUID serviceUUID; - BLEUUID characteristicUUID; - String name; + BLEUUID serviceUUID; + BLEUUID characteristicUUID; + String name; }; namespace BLEServices { - const std::vector SUPPORTED_SERVICES = { - {CYCLINGPOWERSERVICE_UUID, CYCLINGPOWERMEASUREMENT_UUID, "Cycling Power Service"}, - {CSCSERVICE_UUID, CSCMEASUREMENT_UUID, "Cycling Speed And Cadence Service"}, - {HEARTSERVICE_UUID, HEARTCHARACTERISTIC_UUID, "Heart Rate Service"}, - {FITNESSMACHINESERVICE_UUID, FITNESSMACHINEINDOORBIKEDATA_UUID, "Fitness Machine Service"}, - {HID_SERVICE_UUID, HID_REPORT_DATA_UUID, "HID Service"}, - {ECHELON_SERVICE_UUID, ECHELON_DATA_UUID, "Echelon Service"}, - {FLYWHEEL_UART_SERVICE_UUID, FLYWHEEL_UART_TX_UUID, "Flywheel UART Service"} - }; +const std::vector SUPPORTED_SERVICES = {{CYCLINGPOWERSERVICE_UUID, CYCLINGPOWERMEASUREMENT_UUID, "Cycling Power Service"}, + {CSCSERVICE_UUID, CSCMEASUREMENT_UUID, "Cycling Speed And Cadence Service"}, + {HEARTSERVICE_UUID, HEARTCHARACTERISTIC_UUID, "Heart Rate Service"}, + {FITNESSMACHINESERVICE_UUID, FITNESSMACHINEINDOORBIKEDATA_UUID, "Fitness Machine Service"}, + {HID_SERVICE_UUID, HID_REPORT_DATA_UUID, "HID Service"}, + {ECHELON_SERVICE_UUID, ECHELON_DATA_UUID, "Echelon Service"}, + {FLYWHEEL_UART_SERVICE_UUID, FLYWHEEL_UART_TX_UUID, "Flywheel UART Service"}}; } using BLEServices::SUPPORTED_SERVICES; @@ -88,8 +86,8 @@ class SpinBLEServer { bool IndoorBikeData : 1; bool CyclingSpeedCadence : 1; } clientSubscribed; - int spinDownFlag = 0; - NimBLEServer *pServer = nullptr; + int spinDownFlag = 0; + NimBLEServer* pServer = nullptr; void setClientSubscribed(NimBLEUUID pUUID, bool subscribe); void notifyShift(); double calculateSpeed(); @@ -111,8 +109,8 @@ extern SpinBLEServer spinBLEServer; extern BLE_Wattbike_Service wattbikeService; void startBLEServer(); -void logCharacteristic(char *buffer, const size_t bufferCapacity, const byte *data, const size_t dataLength, const NimBLEUUID serviceUUID, const NimBLEUUID charUUID, - const char *format, ...); +void logCharacteristic(char* buffer, const size_t bufferCapacity, const byte* data, const size_t dataLength, const NimBLEUUID serviceUUID, const NimBLEUUID charUUID, + const char* format, ...); void calculateInstPwrFromHR(); int connectedClientCount(); @@ -123,7 +121,7 @@ void BLEFirmwareSetup(); // Keeping the task outside the class so we don't need a mask. // We're only going to run one anyway. -void bleClientTask(void *pvParameters); +void bleClientTask(void* pvParameters); // UUID's the client has methods for // BLEUUID serviceUUIDs[4] = {FITNESSMACHINESERVICE_UUID, @@ -133,6 +131,7 @@ void bleClientTask(void *pvParameters); // FLYWHEEL_UART_TX_UUID}; typedef struct NotifyData { + NimBLEUUID serviceUUID; NimBLEUUID charUUID; uint8_t data[25]; size_t length; @@ -155,7 +154,7 @@ class SpinBLEAdvertisedDevice { // } // } - const NimBLEAdvertisedDevice *advertisedDevice = nullptr; + const NimBLEAdvertisedDevice* advertisedDevice = nullptr; NimBLEAddress peerAddress; int connectedClientID = BLE_HS_CONN_HANDLE_NONE; @@ -169,10 +168,10 @@ class SpinBLEAdvertisedDevice { bool doConnect = false; void setPostConnected(bool pc) { isPostConnected = pc; } bool getPostConnected() { return isPostConnected; } - void set(const NimBLEAdvertisedDevice *device, int id = BLE_HS_CONN_HANDLE_NONE, BLEUUID inServiceUUID = (uint16_t)0x0000, BLEUUID inCharUUID = (uint16_t)0x0000); + void set(const NimBLEAdvertisedDevice* device, int id = BLE_HS_CONN_HANDLE_NONE, BLEUUID inServiceUUID = (uint16_t)0x0000, BLEUUID inCharUUID = (uint16_t)0x0000); void reset(); void print(); - bool enqueueData(uint8_t data[25], size_t length, NimBLEUUID charUUID); + bool enqueueData(uint8_t data[25], size_t length, NimBLEUUID serviceUUID, NimBLEUUID charUUID); NotifyData dequeueData(); }; @@ -196,7 +195,7 @@ class SpinBLEClient { double cscLastWheelEvtTime = 0.0; int reconnectTries = MAX_RECONNECT_TRIES; - BLERemoteCharacteristic *pRemoteCharacteristic = nullptr; + BLERemoteCharacteristic* pRemoteCharacteristic = nullptr; // BLEDevices myBLEDevices; SpinBLEAdvertisedDevice myBLEDevices[NUM_BLE_DEVICES]; @@ -206,27 +205,28 @@ class SpinBLEClient { bool connectToServer(); // Check for duplicate services of BLEClient and remove the previously // connected one. - void removeDuplicates(NimBLEClient *pClient); - void resetDevices(NimBLEClient *pClient); + void removeDuplicates(NimBLEClient* pClient); + void resetDevices(NimBLEClient* pClient); void postConnect(); - void FTMSControlPointWrite(const uint8_t *pData, int length); - void connectBLE_HID(NimBLEClient *pClient); - void keepAliveBLE_HID(NimBLEClient *pClient); - void handleBattInfo(NimBLEClient *pClient, bool updateNow); + void FTMSControlPointWrite(const uint8_t* pData, int length); + void connectBLE_HID(NimBLEClient* pClient); + void keepAliveBLE_HID(NimBLEClient* pClient); + void handleBattInfo(NimBLEClient* pClient, bool updateNow); // Instead of using this directly, set the .doScan flag to start a scan. void scanProcess(int duration = DEFAULT_SCAN_DURATION); void checkBLEReconnect(); // Disconnects all devices. They will then be reconnected if scanned and preferred again. void reconnectAllDevices(); - String adevName2UniqueName(const NimBLEAdvertisedDevice *inDev); + String adevName2UniqueName(const NimBLEAdvertisedDevice* inDev); }; class ScanCallbacks : public NimBLEScanCallbacks { -public: - void onResult(const NimBLEAdvertisedDevice* advertisedDevice) override; - void onScanEnd(const NimBLEScanResults& results, int reason) override; -private: + public: + void onResult(const NimBLEAdvertisedDevice* advertisedDevice) override; + void onScanEnd(const NimBLEScanResults& results, int reason) override; + + private: }; class MyClientCallback : public NimBLEClientCallbacks { diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp index a123d1e5..d1659111 100644 --- a/src/BLE_Client.cpp +++ b/src/BLE_Client.cpp @@ -57,42 +57,37 @@ void notifyCB(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pDa rtConfig->setShifterPosition(rtConfig->getShifterPosition() - 1); } } - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Notification received from %s - Service UUID: %s", pBLERemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString().c_str(), - pBLERemoteCharacteristic->getRemoteService()->getUUID().toString().c_str()); // enqueue sensor data for (size_t i = 0; i < NUM_BLE_DEVICES; i++) { if (pBLERemoteCharacteristic->getClient()->getPeerAddress() == spinBLEClient.myBLEDevices[i].peerAddress) { - spinBLEClient.myBLEDevices[i].enqueueData(pData, length, pBLERemoteCharacteristic->getUUID()); + spinBLEClient.myBLEDevices[i].enqueueData(pData, length, pBLERemoteCharacteristic->getRemoteService()->getUUID(), pBLERemoteCharacteristic->getUUID()); } } } -void subscribeToAllNotifications(NimBLEClient* pClient) { - if(!pClient || !pClient->isConnected()) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Client not connected for notifications"); - return; - } - - for(const auto& service : BLEServices::SUPPORTED_SERVICES) { - NimBLERemoteService* pSvc = pClient->getService(service.serviceUUID); - if(pSvc) { - for(const auto& pChr : pSvc->getCharacteristics()) { - if(pChr) { - if(pChr->canNotify() || pChr->canIndicate()) { - if(pChr->subscribe(true, notifyCB)) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Subscribed to %s %s", - service.name.c_str(), - pChr->getUUID().toString().c_str()); - } else { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Failed to subscribe to %s %s", - service.name.c_str(), - pChr->getUUID().toString().c_str()); - } - } - } +void subscribeToAllNotifications(NimBLEClient *pClient) { + if (!pClient || !pClient->isConnected()) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Client not connected for notifications"); + return; + } + for (const auto &service : BLEServices::SUPPORTED_SERVICES) { + NimBLERemoteService *pSvc = pClient->getService(service.serviceUUID); + if (pSvc) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Found %s", service.name.c_str()); + for (const auto &pChr : pSvc->getCharacteristics(true)) { + if (pChr) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Found %s, %s", service.serviceUUID.toString().c_str(), pChr->getUUID().toString().c_str()); + if (pChr->canNotify() || pChr->canIndicate()) { + if (pChr->subscribe(true, notifyCB)) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Subscribed to %s %s", service.name.c_str(), pChr->getUUID().toString().c_str()); + } else { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Failed to subscribe to %s %s", service.name.c_str(), pChr->getUUID().toString().c_str()); } + } } + } } + } } // BLE Client loop task. @@ -293,62 +288,20 @@ bool SpinBLEClient::connectToServer() { } /** Now we can read/write/subscribe the characteristics of the services we are interested in */ - NimBLERemoteService *pSvc = nullptr; - NimBLERemoteCharacteristic *pChr = nullptr; - NimBLERemoteDescriptor *pDsc = nullptr; + NimBLERemoteService *pSvc = nullptr; pSvc = pClient->getService(serviceUUID); if (pSvc) { /** make sure it's not null */ - pChr = pSvc->getCharacteristic(charUUID); - - if (pChr) { /** make sure it's not null */ -/* if (pChr->canRead()) { - NimBLEAttValue value = pChr->readValue(); - const int kLogBufMaxLength = 250; - char logBuf[kLogBufMaxLength]; - int logBufLength = ss2k_log_hex_to_buffer(value.data(), value.length(), logBuf, 0, kLogBufMaxLength); - logBufLength += snprintf(logBuf + logBufLength, kLogBufMaxLength - logBufLength, " <-initial Value"); - SS2K_LOG(BLE_COMMON_LOG_TAG, "%s", logBuf); - } - if (pChr->canNotify()) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Setting up notifications for service %s char %s, handle %d", serviceUUID.toString().c_str(), pChr->getUUID().toString().c_str(), pChr->getHandle()); - if (!pChr->subscribe(true, notifyCB)) { - /** Disconnect if subscribe failed *//* - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Notifications Failed for %s", pClient->getPeerAddress().toString().c_str()); - spinBLEClient.myBLEDevices[device_number].reset(); - pClient->deleteServices(); - NimBLEDevice::getScan()->erase(pClient->getPeerAddress()); - NimBLEDevice::deleteClient(pClient); - return false; - } - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Notifications Subscribed for %s, UUID %s", pClient->getPeerAddress().toString().c_str(), pChr->getUUID().toString().c_str()); - } else if (pChr->canIndicate()) { - /** Send false as first argument to subscribe to indications instead of notifications - if (!pChr->subscribe(false, notifyCB)) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Indications Failed for %s", pClient->getPeerAddress().toString().c_str()); - /** Disconnect if subscribe failed - spinBLEClient.myBLEDevices[device_number].reset(); - pClient->deleteServices(); - NimBLEDevice::getScan()->erase(pClient->getPeerAddress()); - NimBLEDevice::deleteClient(pClient); - return false; - } - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Indications Subscribed for %s", pClient->getPeerAddress().toString().c_str()); - } */ - subscribeToAllNotifications(pClient); - this->reconnectTries = MAX_RECONNECT_TRIES; - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Successful %s subscription.", pChr->getUUID().toString().c_str()); - spinBLEClient.myBLEDevices[device_number].doConnect = false; - this->reconnectTries = MAX_RECONNECT_TRIES; - spinBLEClient.myBLEDevices[device_number].set(myDevice, pClient->getConnHandle(), serviceUUID, charUUID); - spinBLEClient.myBLEDevices[device_number].peerAddress = pClient->getPeerAddress(); - removeDuplicates(pClient); - } - + this->reconnectTries = MAX_RECONNECT_TRIES; + // SS2K_LOG(BLE_CLIENT_LOG_TAG, "Successful %s subscription.", pChr->getUUID().toString().c_str()); + spinBLEClient.myBLEDevices[device_number].doConnect = false; + this->reconnectTries = MAX_RECONNECT_TRIES; + spinBLEClient.myBLEDevices[device_number].set(myDevice, pClient->getConnHandle(), serviceUUID, charUUID); + spinBLEClient.myBLEDevices[device_number].peerAddress = pClient->getPeerAddress(); + removeDuplicates(pClient); } else { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Failed to find service: %s", serviceUUID.toString().c_str()); } - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Device Connected"); return true; } @@ -634,12 +587,12 @@ void SpinBLEClient::postConnect() { for (auto &_BLEd : spinBLEClient.myBLEDevices) { // Check that the device has been assigned and it hasn't been post connected. if ((_BLEd.connectedClientID != BLE_HS_CONN_HANDLE_NONE) && !_BLEd.getPostConnected()) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Post connecting: %s , ConnID %d, PrimaryChar %s", _BLEd.peerAddress.toString().c_str(), _BLEd.connectedClientID, _BLEd.charUUID.toString().c_str()); - //auto testCharactreristic = NimBLEDevice::getClientByPeerAddress(_BLEd.peerAddress)->getService(CYCLINGPOWERSERVICE_UUID)->getCharacteristic(CYCLINGPOWERMEASUREMENT_UUID); - //testCharactreristic->subscribe(true, notifyCB); + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Post connecting: %s , ConnID %d, PrimaryChar %s", _BLEd.peerAddress.toString().c_str(), _BLEd.connectedClientID, + _BLEd.charUUID.toString().c_str()); if (NimBLEDevice::getClientByPeerAddress(_BLEd.peerAddress)) { _BLEd.setPostConnected(true); NimBLEClient *pClient = NimBLEDevice::getClientByPeerAddress(_BLEd.peerAddress); + subscribeToAllNotifications(pClient); if (_BLEd.charUUID == ECHELON_DATA_UUID) { NimBLERemoteCharacteristic *writeCharacteristic = pClient->getService(ECHELON_SERVICE_UUID)->getCharacteristic(ECHELON_WRITE_UUID); if (writeCharacteristic == nullptr) { @@ -659,7 +612,7 @@ void SpinBLEClient::postConnect() { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Updating Connection Params for: %s", _BLEd.peerAddress.toString().c_str()); BLEDevice::getServer()->updateConnParams(pClient->getConnHandle(), 100, 100, 2, 1000); spinBLEClient.handleBattInfo(pClient, true); - + auto featuresCharacteristic = pClient->getService(FITNESSMACHINESERVICE_UUID)->getCharacteristic(FITNESSMACHINEFEATURE_UUID); if (featuresCharacteristic == nullptr) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Failed to find FTMS features characteristic UUID: %s", FITNESSMACHINEFEATURE_UUID.toString().c_str()); @@ -680,7 +633,7 @@ void SpinBLEClient::postConnect() { return; } } - + NimBLERemoteCharacteristic *writeCharacteristic = pClient->getService(FITNESSMACHINESERVICE_UUID)->getCharacteristic(FITNESSMACHINECONTROLPOINT_UUID); if (writeCharacteristic == nullptr) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Failed to find FTMS control characteristic UUID: %s", FITNESSMACHINECONTROLPOINT_UUID.toString().c_str()); @@ -701,7 +654,7 @@ void SpinBLEClient::postConnect() { } } -bool SpinBLEAdvertisedDevice::enqueueData(uint8_t *data, size_t length, NimBLEUUID charUUID) { +bool SpinBLEAdvertisedDevice::enqueueData(uint8_t *data, size_t length, NimBLEUUID serviceUUID, NimBLEUUID charUUID) { NotifyData notifyData; if (!uxQueueSpacesAvailable(this->dataBufferQueue)) { @@ -709,8 +662,9 @@ bool SpinBLEAdvertisedDevice::enqueueData(uint8_t *data, size_t length, NimBLEUU return pdFALSE; } - notifyData.length = length; - notifyData.charUUID = charUUID; + notifyData.length = length; + notifyData.charUUID = charUUID; + notifyData.serviceUUID = serviceUUID; for (size_t i = 0; i < length; i++) { notifyData.data[i] = data[i]; // Serial.printf("%02x ", notifyData.data[i]); diff --git a/src/BLE_Common.cpp b/src/BLE_Common.cpp index d24a2106..1ac54683 100644 --- a/src/BLE_Common.cpp +++ b/src/BLE_Common.cpp @@ -18,49 +18,46 @@ bool hr2p = false; const BLEServiceInfo* getDeviceServiceInfo(const NimBLEAdvertisedDevice* advertisedDevice, const String& deviceName) { - if (!advertisedDevice->haveServiceUUID()) { - return nullptr; - } + if (!advertisedDevice->haveServiceUUID()) { + return nullptr; + } - for (const auto& service : SUPPORTED_SERVICES) { - // Special case for Flywheel which requires name check - if (service.serviceUUID == FLYWHEEL_UART_SERVICE_UUID) { - if (advertisedDevice->isAdvertisingService(service.serviceUUID) && - deviceName == String(FLYWHEEL_BLE_NAME)) { - return &service; - } - } - // For all other services - else if (advertisedDevice->isAdvertisingService(service.serviceUUID)) { - return &service; - } + for (const auto& service : SUPPORTED_SERVICES) { + // Special case for Flywheel which requires name check + if (service.serviceUUID == FLYWHEEL_UART_SERVICE_UUID) { + if (advertisedDevice->isAdvertisingService(service.serviceUUID) && deviceName == String(FLYWHEEL_BLE_NAME)) { + return &service; + } } - - return nullptr; -} + // For all other services + else if (advertisedDevice->isAdvertisingService(service.serviceUUID)) { + return &service; + } + } -bool isDeviceSupported(const NimBLEAdvertisedDevice* advertisedDevice, const String& deviceName) { - return getDeviceServiceInfo(advertisedDevice, deviceName) != nullptr; + return nullptr; } +bool isDeviceSupported(const NimBLEAdvertisedDevice* advertisedDevice, const String& deviceName) { return getDeviceServiceInfo(advertisedDevice, deviceName) != nullptr; } + void BLECommunications() { static unsigned long int bleCommTimer = millis(); if (((millis() - bleCommTimer) > BLE_NOTIFY_DELAY) && !ss2k->isUpdating) { bleCommTimer = millis(); // **********************************Client*************************************** - for (auto &_BLEd : spinBLEClient.myBLEDevices) { // loop through discovered devices + for (auto& _BLEd : spinBLEClient.myBLEDevices) { // loop through discovered devices if (_BLEd.connectedClientID != BLE_HS_CONN_HANDLE_NONE) { - // SS2K_LOGW(BLE_COMMON_LOG_TAG, "Address: (%s) Client ID: (%d) SerUUID: (%s) CharUUID: (%s) HRM: (%s) PM: (%s) CSC: (%s) CT: (%s) doConnect: (%s) postConnect: (%s)", - // _BLEd.peerAddress.toString().c_str(), _BLEd.connectedClientID, _BLEd.serviceUUID.toString().c_str(), _BLEd.charUUID.toString().c_str(), - // _BLEd.isHRM ? "true" : "false", _BLEd.isPM ? "true" : "false", _BLEd.isCSC ? "true" : "false", _BLEd.isCT ? "true" : "false", _BLEd.doConnect ? "true" : "false", - // _BLEd.getPostConnected() ? "true" : "false"); + // SS2K_LOGW(BLE_COMMON_LOG_TAG, "Address: (%s) Client ID: (%d) SerUUID: (%s) CharUUID: (%s) HRM: (%s) PM: (%s) CSC: (%s) CT: (%s) doConnect: (%s) postConnect: (%s)", + // _BLEd.peerAddress.toString().c_str(), _BLEd.connectedClientID, _BLEd.serviceUUID.toString().c_str(), _BLEd.charUUID.toString().c_str(), + // _BLEd.isHRM ? "true" : "false", _BLEd.isPM ? "true" : "false", _BLEd.isCSC ? "true" : "false", _BLEd.isCT ? "true" : "false", _BLEd.doConnect ? "true" : + // "false", _BLEd.getPostConnected() ? "true" : "false"); if (_BLEd.advertisedDevice) { // is device registered? if ((_BLEd.connectedClientID != BLE_HS_CONN_HANDLE_NONE) && (_BLEd.doConnect == false)) { // client must not be in connection process if (BLEDevice::getClientByPeerAddress(_BLEd.peerAddress)) { // nullptr check - BLEClient *pClient = NimBLEDevice::getClientByPeerAddress(_BLEd.peerAddress); + BLEClient* pClient = NimBLEDevice::getClientByPeerAddress(_BLEd.peerAddress); // Client connected with a valid UUID registered if ((_BLEd.serviceUUID != BLEUUID((uint16_t)0x0000)) && (pClient->isConnected())) { - BLERemoteCharacteristic *pRemoteBLECharacteristic = pClient->getService(_BLEd.serviceUUID)->getCharacteristic(_BLEd.charUUID); + BLERemoteCharacteristic* pRemoteBLECharacteristic = pClient->getService(_BLEd.serviceUUID)->getCharacteristic(_BLEd.charUUID); // Handle BLE HID Remotes if (_BLEd.serviceUUID == HID_SERVICE_UUID) { @@ -80,13 +77,14 @@ void BLECommunications() { for (size_t i = 0; i < length; i++) { pData[i] = incomingNotifyData.data[i]; } - collectAndSet(pRemoteBLECharacteristic->getUUID(), _BLEd.serviceUUID, pRemoteBLECharacteristic->getRemoteService()->getClient()->getPeerAddress(), pData, length); + collectAndSet(incomingNotifyData.charUUID, incomingNotifyData.serviceUUID, pRemoteBLECharacteristic->getRemoteService()->getClient()->getPeerAddress(), pData, + length); } spinBLEClient.handleBattInfo(pClient, false); } else if (!pClient->isConnected()) { // This shouldn't ever be - // called... + // called... SS2K_LOG(BLE_COMMON_LOG_TAG, "Workaround connect"); _BLEd.doConnect = true; //} From defad9a19c597cb407f495de7eea710fd06f9d84 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 22 Jan 2025 04:04:17 +0000 Subject: [PATCH 12/23] Update changelog for version 25.1.10 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5563ae..5abda86f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +### Changed + +### Hardware + + +## [25.1.10] + +### Added + ### Changed - Added checks for IC SE Bike Connection. From cfb76a184b8e0d837c45e89c1efd6a88cb483914 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Tue, 21 Jan 2025 22:28:59 -0600 Subject: [PATCH 13/23] Cadence zeros now --- lib/SS2K/include/sensors/CscSensorData.h | 1 + lib/SS2K/src/sensors/CscSensorData.cpp | 23 ++++++++++++++++++++--- src/BLE_Client.cpp | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/SS2K/include/sensors/CscSensorData.h b/lib/SS2K/include/sensors/CscSensorData.h index 86fe3245..20c72af1 100644 --- a/lib/SS2K/include/sensors/CscSensorData.h +++ b/lib/SS2K/include/sensors/CscSensorData.h @@ -32,4 +32,5 @@ class CscSensorData : public SensorData { uint32_t lastCrankEventTime = 0; uint32_t lastWheelRevolutions = 0; uint32_t lastCrankRevolutions = 0; + uint8_t missedReadingCount = 0; }; \ No newline at end of file diff --git a/lib/SS2K/src/sensors/CscSensorData.cpp b/lib/SS2K/src/sensors/CscSensorData.cpp index 1623c071..aaa1363b 100644 --- a/lib/SS2K/src/sensors/CscSensorData.cpp +++ b/lib/SS2K/src/sensors/CscSensorData.cpp @@ -72,15 +72,32 @@ void CscSensorData::decode(uint8_t *data, size_t length) { // Calculate cadence if we have previous measurements if (lastCrankEventTime > 0) { // Handle timer wraparound (16-bit timer) - uint16_t timeDiff = (crankEventTime >= lastCrankEventTime) ? - (crankEventTime - lastCrankEventTime) : + uint16_t timeDiff = (crankEventTime >= lastCrankEventTime) ? + (crankEventTime - lastCrankEventTime) : (65535 - lastCrankEventTime + crankEventTime); if (timeDiff > 0) { // Time is in 1/1024th of a second float revolutions = crankRevolutions - lastCrankRevolutions; float timeMinutes = (timeDiff / 1024.0f) / 60.0f; - this->cadence = revolutions / timeMinutes; + float cadence = revolutions / timeMinutes; + + if (cadence > 1) { + if (cadence > 200) { // Human is unlikely producing 200+ cadence + // Cadence Error: Could happen if cadence measurements were missed + // Leave cadence unchanged + cadence = this->cadence; + } + this->cadence = cadence; + this->missedReadingCount = 0; + } else { + this->missedReadingCount++; + } + } else { // the crank rev probably didn't update + if (this->missedReadingCount > 2) { // Require three consecutive readings before setting 0 cadence + this->cadence = 0; + } + this->missedReadingCount++; } } diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp index d1659111..ae6a3125 100644 --- a/src/BLE_Client.cpp +++ b/src/BLE_Client.cpp @@ -858,7 +858,7 @@ void SpinBLEAdvertisedDevice::set(const NimBLEAdvertisedDevice *device, int id, this->connectedClientID = id; this->serviceUUID = BLEUUID(inServiceUUID); this->charUUID = BLEUUID(inCharUUID); - this->dataBufferQueue = xQueueCreate(4, sizeof(NotifyData)); + this->dataBufferQueue = xQueueCreate(6, sizeof(NotifyData)); // Only register services when we have a connected client if (id != BLE_HS_CONN_HANDLE_NONE) { From 6143072f310a67af4d9bc145c0fed5e20536e857 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Fri, 24 Jan 2025 15:50:13 -0600 Subject: [PATCH 14/23] Ready for testing --- include/BLE_Common.h | 10 ++++++++-- platformio.ini | 2 +- src/BLE_Client.cpp | 18 +++++------------- src/BLE_Common.cpp | 8 +------- src/BLE_Fitness_Machine_Service.cpp | 5 ++--- 5 files changed, 17 insertions(+), 26 deletions(-) diff --git a/include/BLE_Common.h b/include/BLE_Common.h index 80aa62e7..0e781a65 100644 --- a/include/BLE_Common.h +++ b/include/BLE_Common.h @@ -19,6 +19,10 @@ #include "BLE_Wattbike_Service.h" #include "Constants.h" +//Client size allocated to the queue for receiving characteristic data +#define NOTIFY_DATA_QUEUE_SIZE 25 +#define NOTIFY_DATA_QUEUE_LENGTH 10 + // Vector of supported BLE services and their corresponding characteristic UUIDs struct BLEServiceInfo { BLEUUID serviceUUID; @@ -130,10 +134,12 @@ void bleClientTask(void* pvParameters); // CYCLINGPOWERMEASUREMENT_UUID, HEARTCHARACTERISTIC_UUID, // FLYWHEEL_UART_TX_UUID}; + + typedef struct NotifyData { NimBLEUUID serviceUUID; NimBLEUUID charUUID; - uint8_t data[25]; + uint8_t data[NOTIFY_DATA_QUEUE_SIZE]; size_t length; } NotifyData; @@ -171,7 +177,7 @@ class SpinBLEAdvertisedDevice { void set(const NimBLEAdvertisedDevice* device, int id = BLE_HS_CONN_HANDLE_NONE, BLEUUID inServiceUUID = (uint16_t)0x0000, BLEUUID inCharUUID = (uint16_t)0x0000); void reset(); void print(); - bool enqueueData(uint8_t data[25], size_t length, NimBLEUUID serviceUUID, NimBLEUUID charUUID); + bool enqueueData(uint8_t data[NOTIFY_DATA_QUEUE_SIZE], size_t length, NimBLEUUID serviceUUID, NimBLEUUID charUUID); NotifyData dequeueData(); }; diff --git a/platformio.ini b/platformio.ini index 37bbb775..e1033275 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,7 +30,7 @@ build_flags = -D ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE=3500 -std=gnu++17 lib_deps = - https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.2.0.zip + https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.1.3.zip https://github.com/teemuatlut/TMCStepper/archive/refs/tags/v0.7.3.zip https://github.com/bblanchon/ArduinoJson/archive/refs/tags/v6.20.0.zip https://github.com/gin66/FastAccelStepper/archive/refs/tags/0.31.2.zip diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp index ae6a3125..fa74612c 100644 --- a/src/BLE_Client.cpp +++ b/src/BLE_Client.cpp @@ -46,7 +46,7 @@ void SpinBLEClient::start() { pBLEScan->setActiveScan(true); } -void notifyCB(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { +static void notifyCB(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { // Parse BLE shifter info. if (pBLERemoteCharacteristic->getRemoteService()->getUUID() == HID_SERVICE_UUID) { Serial.print(pData[0], HEX); @@ -78,8 +78,8 @@ void subscribeToAllNotifications(NimBLEClient *pClient) { if (pChr) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Found %s, %s", service.serviceUUID.toString().c_str(), pChr->getUUID().toString().c_str()); if (pChr->canNotify() || pChr->canIndicate()) { - if (pChr->subscribe(true, notifyCB)) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Subscribed to %s %s", service.name.c_str(), pChr->getUUID().toString().c_str()); + if (pChr->canNotify() ? pChr->subscribe(true, notifyCB) : pChr->subscribe(false, notifyCB)) { + SS2K_LOG(BLE_CLIENT_LOG_TAG, "Subscribed to %s %s handle: %d", service.name.c_str(), pChr->getUUID().toString().c_str(), pChr->getHandle()); } else { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Failed to subscribe to %s %s", service.name.c_str(), pChr->getUUID().toString().c_str()); } @@ -209,7 +209,7 @@ bool SpinBLEClient::connectToServer() { */ pClient = NimBLEDevice::getClientByPeerAddress(myDevice->getAddress()); if (pClient) { - pClient->setConnectTimeout(2); + pClient->setConnectTimeout(500); SS2K_LOG(BLE_CLIENT_LOG_TAG, "Reusing Client"); if (!pClient->connect(myDevice, false)) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Reconnect failed "); @@ -251,7 +251,7 @@ bool SpinBLEClient::connectToServer() { * 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, 6, 0, 200); + pClient->setConnectionParams(6, 12, 0, 500); /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ pClient->setConnectTimeout(5 * 1000); // 5 seconds @@ -266,13 +266,6 @@ bool SpinBLEClient::connectToServer() { } } - if (!pClient->isConnected()) { - if (!pClient->connect(myDevice)) { - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Failed to connect"); - return false; - } - } - SS2K_LOG(BLE_CLIENT_LOG_TAG, "Connected to: %s - %s RSSI %d", this->adevName2UniqueName(myDevice).c_str(), pClient->getPeerAddress().toString().c_str(), pClient->getRssi()); if (serviceUUID == HID_SERVICE_UUID) { @@ -293,7 +286,6 @@ bool SpinBLEClient::connectToServer() { pSvc = pClient->getService(serviceUUID); if (pSvc) { /** make sure it's not null */ this->reconnectTries = MAX_RECONNECT_TRIES; - // SS2K_LOG(BLE_CLIENT_LOG_TAG, "Successful %s subscription.", pChr->getUUID().toString().c_str()); spinBLEClient.myBLEDevices[device_number].doConnect = false; this->reconnectTries = MAX_RECONNECT_TRIES; spinBLEClient.myBLEDevices[device_number].set(myDevice, pClient->getConnHandle(), serviceUUID, charUUID); diff --git a/src/BLE_Common.cpp b/src/BLE_Common.cpp index 1ac54683..58fbcd54 100644 --- a/src/BLE_Common.cpp +++ b/src/BLE_Common.cpp @@ -47,24 +47,18 @@ void BLECommunications() { // **********************************Client*************************************** for (auto& _BLEd : spinBLEClient.myBLEDevices) { // loop through discovered devices if (_BLEd.connectedClientID != BLE_HS_CONN_HANDLE_NONE) { - // SS2K_LOGW(BLE_COMMON_LOG_TAG, "Address: (%s) Client ID: (%d) SerUUID: (%s) CharUUID: (%s) HRM: (%s) PM: (%s) CSC: (%s) CT: (%s) doConnect: (%s) postConnect: (%s)", - // _BLEd.peerAddress.toString().c_str(), _BLEd.connectedClientID, _BLEd.serviceUUID.toString().c_str(), _BLEd.charUUID.toString().c_str(), - // _BLEd.isHRM ? "true" : "false", _BLEd.isPM ? "true" : "false", _BLEd.isCSC ? "true" : "false", _BLEd.isCT ? "true" : "false", _BLEd.doConnect ? "true" : - // "false", _BLEd.getPostConnected() ? "true" : "false"); if (_BLEd.advertisedDevice) { // is device registered? if ((_BLEd.connectedClientID != BLE_HS_CONN_HANDLE_NONE) && (_BLEd.doConnect == false)) { // client must not be in connection process if (BLEDevice::getClientByPeerAddress(_BLEd.peerAddress)) { // nullptr check BLEClient* pClient = NimBLEDevice::getClientByPeerAddress(_BLEd.peerAddress); // Client connected with a valid UUID registered if ((_BLEd.serviceUUID != BLEUUID((uint16_t)0x0000)) && (pClient->isConnected())) { - BLERemoteCharacteristic* pRemoteBLECharacteristic = pClient->getService(_BLEd.serviceUUID)->getCharacteristic(_BLEd.charUUID); // Handle BLE HID Remotes if (_BLEd.serviceUUID == HID_SERVICE_UUID) { spinBLEClient.keepAliveBLE_HID(pClient); // keep alive doesn't seem to help :( continue; // There is not data that needs to be dequeued for the remote, so got to the next device. } - // Dequeue sensor data we stored during notifications while (pdTRUE) { NotifyData incomingNotifyData = _BLEd.dequeueData(); @@ -77,7 +71,7 @@ void BLECommunications() { for (size_t i = 0; i < length; i++) { pData[i] = incomingNotifyData.data[i]; } - collectAndSet(incomingNotifyData.charUUID, incomingNotifyData.serviceUUID, pRemoteBLECharacteristic->getRemoteService()->getClient()->getPeerAddress(), pData, + collectAndSet(incomingNotifyData.charUUID, incomingNotifyData.serviceUUID, _BLEd.peerAddress, pData, length); } diff --git a/src/BLE_Fitness_Machine_Service.cpp b/src/BLE_Fitness_Machine_Service.cpp index 0e7e3d02..423450aa 100644 --- a/src/BLE_Fitness_Machine_Service.cpp +++ b/src/BLE_Fitness_Machine_Service.cpp @@ -309,12 +309,11 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { ftmsStatus = {FitnessMachineStatus::StartedOrResumedByUser}; ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); - fitnessMachineTrainingStatus->notify(false); } fitnessMachineStatusCharacteristic->setValue(ftmsStatus.data(), ftmsStatus.size()); pCharacteristic->indicate(); - fitnessMachineTrainingStatus->notify(false); - fitnessMachineStatusCharacteristic->notify(false); + fitnessMachineTrainingStatus->notify(); + fitnessMachineStatusCharacteristic->notify(); } } From 1ce8abfc69228aac87395f87907ea077ea3dd26d Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Fri, 24 Jan 2025 19:34:57 -0600 Subject: [PATCH 15/23] added provisions to use powertable and cadence for power --- data/bluetoothscanner.html | 2 +- data/settings.html | 10 +++ include/BLE_Custom_Characteristic.h | 1 + include/ERG_Mode.h | 4 + include/SmartSpin_parameters.h | 4 + src/BLE_Client.cpp | 71 ++++++++++++++++- src/BLE_Common.cpp | 9 +-- src/BLE_Custom_Characteristic.cpp | 22 +++++- src/ERG_Mode.cpp | 114 +++++++++++++++++++++++++++- src/HTTP_Server_Basic.cpp | 5 ++ src/SensorCollector.cpp | 2 +- src/SmartSpin_parameters.cpp | 6 ++ 12 files changed, 236 insertions(+), 14 deletions(-) diff --git a/data/bluetoothscanner.html b/data/bluetoothscanner.html index 575663f3..c0d8a0c3 100644 --- a/data/bluetoothscanner.html +++ b/data/bluetoothscanner.html @@ -138,7 +138,7 @@

var t_obj = JSON.parse(data.foundDevices) { for (var key in t_obj) { - if (t_obj[key].UUID == '0x1818' || t_obj[key].UUID == '0x1826' || t_obj[key].UUID == '6e400001-b5a3-f393-e0a9-e50e24dcca9e' || t_obj[key].UUID == '0bf669f0-45f2-11e7-9598-0800200c9a66') { + if (t_obj[key].UUID == '0x1816' || t_obj[key].UUID == '0x1818' || t_obj[key].UUID == '0x1826' || t_obj[key].UUID == '6e400001-b5a3-f393-e0a9-e50e24dcca9e' || t_obj[key].UUID == '0bf669f0-45f2-11e7-9598-0800200c9a66') { optionPM = document.createElement('option'); if (t_obj[key].name) { optionPM.text = t_obj[key].name; diff --git a/data/settings.html b/data/settings.html index bfdb371a..ce88ace5 100644 --- a/data/settings.html +++ b/data/settings.html @@ -254,6 +254,15 @@

class="slider"> + + +

PowerTable For PowerUse the PowerTable for Power instead of PM

+ + + + + @@ -324,6 +333,7 @@

document.getElementById("stepperDir").checked = obj.stepperDir; document.getElementById("shifterDir").checked = obj.shifterDir; document.getElementById("udpLogEnabled").checked = !!obj.udpLogEnabled; + document.getElementById("pTab4Pwr").checked = !!obj.pTab4Pwr; updateSlider(document.getElementById("shiftStep").value, document.getElementById("shiftStepValue")); updateSlider(document.getElementById("inclineMultiplier").value, document.getElementById("inclineMultiplierValue")); updateSlider(document.getElementById("ERGSensitivity").value, document.getElementById("ERGSensitivityValue")); diff --git a/include/BLE_Custom_Characteristic.h b/include/BLE_Custom_Characteristic.h index 2ccc3326..76b8899d 100644 --- a/include/BLE_Custom_Characteristic.h +++ b/include/BLE_Custom_Characteristic.h @@ -61,6 +61,7 @@ const uint8_t BLE_simulateTargetWatts = 0x29; // are we sending target watts const uint8_t BLE_hMin = 0x2A; // Minimum homing value const uint8_t BLE_hMax = 0x2B; // Maximum homing value const uint8_t BLE_homingSensitivity = 0x2C; // Homing sensitivity value +const uint8_t BLE_pTab4Pwr = 0x2D; // Use power values for power table class BLE_ss2kCustomCharacteristic { public: diff --git a/include/ERG_Mode.h b/include/ERG_Mode.h index afdba436..292b05ec 100644 --- a/include/ERG_Mode.h +++ b/include/ERG_Mode.h @@ -89,6 +89,7 @@ class TestResults { class PowerTable { public: + bool saveFlag = false; TableRow tableRow[POWERTABLE_CAD_SIZE]; // What used to be in the ERGTaskLoop(). This is the main control function for ERG Mode and the powertable operations. @@ -106,6 +107,9 @@ class PowerTable { // returns incline for wattTarget. Null if not found. int32_t lookup(int watts, int cad); + // returns watts for given cadence and target position. Returns RETURN_ERROR if not found. + int32_t lookupWatts(int cad, int32_t targetPosition); + // automatically load or save the Power Table bool _manageSaveState(); diff --git a/include/SmartSpin_parameters.h b/include/SmartSpin_parameters.h index befe8d77..9cb12d50 100644 --- a/include/SmartSpin_parameters.h +++ b/include/SmartSpin_parameters.h @@ -123,6 +123,7 @@ class userParameters { int stepperSpeed; bool stepperDir; bool shifterDir; + bool pTab4Pwr = false; bool udpLogEnabled = false; int32_t hMin = INT32_MIN; int32_t hMax = INT32_MIN; @@ -199,6 +200,9 @@ class userParameters { void setUdpLogEnabled(bool enabled) { udpLogEnabled = enabled; } bool getUdpLogEnabled() { return udpLogEnabled; } + void setPTab4Pwr(bool pTab) { pTab4Pwr = pTab; } + bool getPTab4Pwr() { return pTab4Pwr; } + void setFoundDevices(String fdv) { foundDevices = fdv; } const char* getFoundDevices() { return foundDevices.c_str(); } diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp index fa74612c..70b81262 100644 --- a/src/BLE_Client.cpp +++ b/src/BLE_Client.cpp @@ -46,6 +46,17 @@ void SpinBLEClient::start() { pBLEScan->setActiveScan(true); } +/** + * @brief Callback function for BLE notifications. + * + * This function is called whenever a notification is received from a BLE characteristic. + * It handles specific notifications for the HID service and enqueues sensor data for further processing. + * + * @param pBLERemoteCharacteristic Pointer to the remote characteristic that generated the notification. + * @param pData Pointer to the data received in the notification. + * @param length Length of the data received. + * @param isNotify Boolean indicating if the notification is a notify or indicate. + */ static void notifyCB(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { // Parse BLE shifter info. if (pBLERemoteCharacteristic->getRemoteService()->getUUID() == HID_SERVICE_UUID) { @@ -57,7 +68,7 @@ static void notifyCB(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8 rtConfig->setShifterPosition(rtConfig->getShifterPosition() - 1); } } - // enqueue sensor data + // Enqueue sensor data for (size_t i = 0; i < NUM_BLE_DEVICES; i++) { if (pBLERemoteCharacteristic->getClient()->getPeerAddress() == spinBLEClient.myBLEDevices[i].peerAddress) { spinBLEClient.myBLEDevices[i].enqueueData(pData, length, pBLERemoteCharacteristic->getRemoteService()->getUUID(), pBLERemoteCharacteristic->getUUID()); @@ -65,6 +76,15 @@ static void notifyCB(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8 } } +/** + * @brief Subscribes to all notifications for supported BLE services on the given client. + * + * This function iterates through all supported BLE services and subscribes to notifications + * for each characteristic that supports notifications or indications. + * + * @param pClient Pointer to the NimBLEClient object representing the BLE client. + * The client must be connected for the function to proceed. + */ void subscribeToAllNotifications(NimBLEClient *pClient) { if (!pClient || !pClient->isConnected()) { SS2K_LOG(BLE_CLIENT_LOG_TAG, "Client not connected for notifications"); @@ -92,6 +112,31 @@ void subscribeToAllNotifications(NimBLEClient *pClient) { // BLE Client loop task. // Manages device connections and scanning. +/** + * @brief Task function to manage BLE client operations. + * + * This function handles the BLE client operations including scanning for BLE devices, + * connecting to BLE servers, and managing BLE connections. It runs in an infinite loop + * with a delay between iterations. + * + * @param pvParameters Pointer to the parameters passed to the task (unused). + * + * The function performs the following operations: + * - Checks and manages BLE reconnections. + * - Disconnects all connected servers if an update is in progress. + * - Scans for BLE devices to connect to the client. + * - Connects BLE servers to the client. + * - Manages the spin down process for the server. + * + * The function uses the following global variables and objects: + * - spinBLEClient: Manages BLE client operations. + * - ss2k: Represents the main application state and configuration. + * - rtConfig: Runtime configuration for the application. + * - spinBLEServer: Manages BLE server operations. + * + * The function also includes debug logging and stack high water mark monitoring + * when the DEBUG_STACK macro is defined. + */ void bleClientTask(void *pvParameters) { long int scanDelay = millis(); spinBLEClient.checkBLEReconnect(); @@ -285,7 +330,7 @@ bool SpinBLEClient::connectToServer() { pSvc = pClient->getService(serviceUUID); if (pSvc) { /** make sure it's not null */ - this->reconnectTries = MAX_RECONNECT_TRIES; + this->reconnectTries = MAX_RECONNECT_TRIES; spinBLEClient.myBLEDevices[device_number].doConnect = false; this->reconnectTries = MAX_RECONNECT_TRIES; spinBLEClient.myBLEDevices[device_number].set(myDevice, pClient->getConnHandle(), serviceUUID, charUUID); @@ -359,9 +404,21 @@ void MyClientCallback::onAuthenticationComplete(NimBLEConnInfo &connInfo) { } /** - * Scan for BLE servers and find the first one that advertises the service we are looking for. + * @brief Callback function that is called when a BLE device is found during scanning. + * + * This function processes the advertised BLE device, checks if it matches the supported devices, + * and attempts to connect to it if it matches the user configuration. + * + * @param advertisedDevice Pointer to the NimBLEAdvertisedDevice object representing the found device. + * + * The function performs the following steps: + * - Logs the found device. + * - Checks if the device has a service UUID and if it is supported. + * - Depending on the service UUID, it checks if the device matches the user configuration for + * connected remote, heart monitor, or power meter. + * - If the device matches the user configuration, it attempts to connect to the device and logs the result. + * - If the device does not match the user configuration, it ignores the device. */ - void ScanCallbacks::onResult(const NimBLEAdvertisedDevice *advertisedDevice) { Serial.printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str()); // Define granular constants for maximal reuse during logging @@ -808,6 +865,12 @@ void SpinBLEClient::handleBattInfo(NimBLEClient *pClient, bool updateNow = false static unsigned long last_battery_update = 0; if ((millis() - last_battery_update >= BATTERY_UPDATE_INTERVAL_MILLIS) || (last_battery_update == 0) || updateNow) { last_battery_update = millis(); + if (pClient->getService(BATTERYSERVICE_UUID) == nullptr) { + return; + } + if (pClient->getService(BATTERYSERVICE_UUID)->getCharacteristic(BATTERYCHARACTERISTIC_UUID) == nullptr) { + return; + } if (pClient->getService(HEARTSERVICE_UUID) && pClient->getService(BATTERYSERVICE_UUID)) { // get battery level at first connect BLERemoteCharacteristic *battCharacteristic = pClient->getService(BATTERYSERVICE_UUID)->getCharacteristic(BATTERYCHARACTERISTIC_UUID); if (battCharacteristic != nullptr) { diff --git a/src/BLE_Common.cpp b/src/BLE_Common.cpp index 58fbcd54..2458c5be 100644 --- a/src/BLE_Common.cpp +++ b/src/BLE_Common.cpp @@ -53,7 +53,6 @@ void BLECommunications() { BLEClient* pClient = NimBLEDevice::getClientByPeerAddress(_BLEd.peerAddress); // Client connected with a valid UUID registered if ((_BLEd.serviceUUID != BLEUUID((uint16_t)0x0000)) && (pClient->isConnected())) { - // Handle BLE HID Remotes if (_BLEd.serviceUUID == HID_SERVICE_UUID) { spinBLEClient.keepAliveBLE_HID(pClient); // keep alive doesn't seem to help :( @@ -71,11 +70,11 @@ void BLECommunications() { for (size_t i = 0; i < length; i++) { pData[i] = incomingNotifyData.data[i]; } - collectAndSet(incomingNotifyData.charUUID, incomingNotifyData.serviceUUID, _BLEd.peerAddress, pData, - length); + collectAndSet(incomingNotifyData.charUUID, incomingNotifyData.serviceUUID, _BLEd.peerAddress, pData, length); + } + if (_BLEd.getPostConnected()) { + spinBLEClient.handleBattInfo(pClient, false); } - - spinBLEClient.handleBattInfo(pClient, false); } else if (!pClient->isConnected()) { // This shouldn't ever be // called... diff --git a/src/BLE_Custom_Characteristic.cpp b/src/BLE_Custom_Characteristic.cpp index ac8a90d4..07c8a389 100644 --- a/src/BLE_Custom_Characteristic.cpp +++ b/src/BLE_Custom_Characteristic.cpp @@ -102,7 +102,7 @@ void BLE_ss2kCustomCharacteristic::update() {} void ss2kCustomCharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) { std::string rxValue = pCharacteristic->getValue(); - SS2K_LOG(CUSTOM_CHAR_LOG_TAG, "Write from %s", connInfo.getAddress().toString().c_str()); + //SS2K_LOG(CUSTOM_CHAR_LOG_TAG, "Write from %s", connInfo.getAddress().toString().c_str()); BLE_ss2kCustomCharacteristic::process(rxValue); } @@ -679,6 +679,7 @@ void BLE_ss2kCustomCharacteristic::process(std::string rxValue) { for (int i = 0; i < POWERTABLE_WATT_SIZE; i++) { powerTable->tableRow[rxValue[2]].tableEntry[i].targetPosition = (int16_t((uint8_t)(rxValue[i*2 + 3]) << 0 | (uint8_t)(rxValue[i*2 + 4]) << 8)); } + powerTable->saveFlag = true; } else { //SS2K_LOG(CUSTOM_CHAR_LOG_TAG, "Table row invalid"); // Logging causes crashes in ISR @@ -761,6 +762,20 @@ void BLE_ss2kCustomCharacteristic::process(std::string rxValue) { } break; + case BLE_pTab4Pwr: // 0x2D + logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "<-pTab4Pwr"); + if (rxValue[0] == cc_read) { + returnValue[0] = cc_success; + returnValue[2] = (uint8_t)(userConfig->getPTab4Pwr()); + returnLength += 1; + } + if (rxValue[0] == cc_write) { + returnValue[0] = cc_success; + userConfig->setPTab4Pwr(rxValue[2]); + logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "(%s)", userConfig->getPTab4Pwr() ? "true" : "false"); + } + break; + default: logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "<-Unknown Characteristic"); returnValue[0] = cc_error; @@ -930,4 +945,9 @@ void BLE_ss2kCustomCharacteristic::parseNemit() { BLE_ss2kCustomCharacteristic::notify(BLE_homingSensitivity); return; } + if (userConfig->getPTab4Pwr() != _oldParams.getPTab4Pwr()) { + _oldParams.setPTab4Pwr(userConfig->getPTab4Pwr()); + BLE_ss2kCustomCharacteristic::notify(BLE_pTab4Pwr); + return; + } } diff --git a/src/ERG_Mode.cpp b/src/ERG_Mode.cpp index 51ea2ce9..a73553b3 100644 --- a/src/ERG_Mode.cpp +++ b/src/ERG_Mode.cpp @@ -29,6 +29,7 @@ void PowerTable::runERG() { static bool hasConnectedPowerMeter = false; static bool simulationRunning = false; static int loopCounter = 0; + static bool pTabUsed4Pwr = false; if ((millis() - ergTimer) > ERG_MODE_DELAY) { // reset the timer. @@ -41,6 +42,42 @@ void PowerTable::runERG() { return; } + static unsigned long int saveFlagCooldown = 0; + // save powertable if saveFlag has been set for 10 seconds using a saveFlagCooldown timer + // this is to provide enough time to transmit a new powerTable using BLE. + if (this->saveFlag) { + if (saveFlagCooldown == 0) { + saveFlagCooldown = millis(); + } + if ((millis() - saveFlagCooldown) > 10000) { + this->_save(); + saveFlagCooldown = 0; + saveFlag = false; + } + } + + // values hard set for testing: + // userConfig->setPTab4Pwr(true); + // rtConfig->setHomed(true); + + if ((!spinBLEClient.connectedPM || userConfig->getPTab4Pwr()) && rtConfig->cad.getValue() && rtConfig->getHomed()) { + // Lookup watts using the Power Table. + pTabUsed4Pwr = true; + if (this->_hasBeenLoadedThisSession) { + rtConfig->watts.setValue(lookupWatts(rtConfig->cad.getValue(), ss2k->getCurrentPosition())); + // rtConfig->watts.setSimulate(true); + } else { + // only run _manageSaveState every 5 seconds + static unsigned long int saveStateTimer = millis(); + if ((millis() - saveStateTimer) > 5000) { + this->_manageSaveState(); + saveStateTimer = millis(); + } + } + } else { + pTabUsed4Pwr = false; + } + if (rtConfig->cad.getValue() > 0 && rtConfig->watts.getValue() > 0) { hasConnectedPowerMeter = spinBLEClient.connectedPM; simulationRunning = rtConfig->watts.getTarget(); @@ -48,8 +85,10 @@ void PowerTable::runERG() { simulationRunning = rtConfig->watts.getSimulate(); } - // add values to torque table - powerTable->processPowerValue(powerBuffer, rtConfig->cad.getValue(), rtConfig->watts); + if (!pTabUsed4Pwr) { + // add values to Power table + powerTable->processPowerValue(powerBuffer, rtConfig->cad.getValue(), rtConfig->watts); + } // compute ERG if ((rtConfig->getFTMSMode() == FitnessMachineControlPointProcedure::SetTargetPower) && (hasConnectedPowerMeter || simulationRunning)) { @@ -1019,6 +1058,77 @@ bool PowerTable::_save() { return true; // return successful } +int32_t PowerTable::lookupWatts(int cad, int32_t targetPosition) { + // Convert targetPosition from external format (x100) to internal format + int16_t internalPosition = targetPosition / 100; + + // Calculate cadence index + int cadIndex = round(((float)cad - (float)MINIMUM_TABLE_CAD) / (float)POWERTABLE_CAD_INCREMENT); + + // Clamp cadence index to table limits + if (cadIndex < 0) { + cadIndex = 0; + } else if (cadIndex >= POWERTABLE_CAD_SIZE) { + cadIndex = POWERTABLE_CAD_SIZE - 1; + } + + // Find closest positions and corresponding watts in the row + int leftWattIndex = -1; + int rightWattIndex = -1; + + // Search for closest positions + for (int j = 0; j < POWERTABLE_WATT_SIZE; j++) { + if (this->tableRow[cadIndex].tableEntry[j].targetPosition != INT16_MIN) { + if (this->tableRow[cadIndex].tableEntry[j].targetPosition <= internalPosition) { + leftWattIndex = j; + } else { + rightWattIndex = j; + break; + } + } + } + + // If we found valid positions on both sides, interpolate + if (leftWattIndex != -1 && rightWattIndex != -1) { + int leftPos = this->tableRow[cadIndex].tableEntry[leftWattIndex].targetPosition; + int rightPos = this->tableRow[cadIndex].tableEntry[rightWattIndex].targetPosition; + int leftWatts = leftWattIndex * POWERTABLE_WATT_INCREMENT; + int rightWatts = rightWattIndex * POWERTABLE_WATT_INCREMENT; + + // Linear interpolation + int watts = leftWatts + (rightWatts - leftWatts) * (internalPosition - leftPos) / (rightPos - leftPos); + SS2K_LOG(ERG_MODE_LOG_TAG, "LookupWatts interpolated %dw from pos %d, cad %d", watts, targetPosition, cad); + return watts; + } + + // If we only found positions on one side, extrapolate + if (leftWattIndex != -1 && leftWattIndex > 0) { + // Extrapolate using two leftmost points + int pos1 = this->tableRow[cadIndex].tableEntry[leftWattIndex - 1].targetPosition; + int pos2 = this->tableRow[cadIndex].tableEntry[leftWattIndex].targetPosition; + int watts1 = (leftWattIndex - 1) * POWERTABLE_WATT_INCREMENT; + int watts2 = leftWattIndex * POWERTABLE_WATT_INCREMENT; + + int watts = watts2 + (watts2 - watts1) * (internalPosition - pos2) / (pos2 - pos1); + SS2K_LOG(ERG_MODE_LOG_TAG, "LookupWatts extrapolated high %dw from pos %d, cad %d", watts, targetPosition, cad); + return watts; + } + + if (rightWattIndex != -1 && rightWattIndex < POWERTABLE_WATT_SIZE - 1) { + // Extrapolate using two rightmost points + int pos1 = this->tableRow[cadIndex].tableEntry[rightWattIndex].targetPosition; + int pos2 = this->tableRow[cadIndex].tableEntry[rightWattIndex + 1].targetPosition; + int watts1 = rightWattIndex * POWERTABLE_WATT_INCREMENT; + int watts2 = (rightWattIndex + 1) * POWERTABLE_WATT_INCREMENT; + + int watts = watts1 + (watts1 - watts2) * (pos1 - internalPosition) / (pos1 - pos2); + SS2K_LOG(ERG_MODE_LOG_TAG, "LookupWatts extrapolated low %dw from pos %d, cad %d", watts, targetPosition, cad); + return watts; + } + SS2K_LOG(ERG_MODE_LOG_TAG, "LookupWatts failed to find a value for pos %d, cad %d", targetPosition, cad); + return RETURN_ERROR; +} + // Reset the PowerTable to 0; bool PowerTable::reset() { ss2k->resetPowerTableFlag = false; diff --git a/src/HTTP_Server_Basic.cpp b/src/HTTP_Server_Basic.cpp index c09a5c15..2324e500 100644 --- a/src/HTTP_Server_Basic.cpp +++ b/src/HTTP_Server_Basic.cpp @@ -543,6 +543,11 @@ void HTTP_Server::settingsProcessor() { } else if (wasSettingsUpdate) { userConfig->setUdpLogEnabled(false); } + if (!server.arg("pTab4Pwr").isEmpty()) { + userConfig->setPTab4Pwr(true); + } else if (wasSettingsUpdate) { + userConfig->setPTab4Pwr(false); + } if (!server.arg("stealthChop").isEmpty()) { userConfig->setStealthChop(true); ss2k->updateStealthChop(); diff --git a/src/SensorCollector.cpp b/src/SensorCollector.cpp index d587b40d..79c206cb 100644 --- a/src/SensorCollector.cpp +++ b/src/SensorCollector.cpp @@ -43,7 +43,7 @@ void collectAndSet(NimBLEUUID charUUID, NimBLEUUID serviceUUID, NimBLEAddress ad } } - if (sensorData->hasPower() && !rtConfig->watts.getSimulate()) { + if (sensorData->hasPower() && !rtConfig->watts.getSimulate() && !userConfig->getPTab4Pwr()) { if ((charUUID == PELOTON_DATA_UUID) && !((String(userConfig->getConnectedPowerMeter()) == "none") || (String(userConfig->getConnectedPowerMeter()) == "any"))) { // Peloton connected but using BLE Power Meter. So skip power for Peloton UUID. } else { diff --git a/src/SmartSpin_parameters.cpp b/src/SmartSpin_parameters.cpp index d44ed0f5..3638df96 100644 --- a/src/SmartSpin_parameters.cpp +++ b/src/SmartSpin_parameters.cpp @@ -67,6 +67,7 @@ void userParameters::setDefaults() { stepperDir = true; shifterDir = true; udpLogEnabled = false; + pTab4Pwr = false; hMin = INT32_MIN; hMax = INT32_MIN; homingSensitivity = DEFAULT_HOMING_SENSITIVITY; @@ -103,6 +104,7 @@ String userParameters::returnJSON() { doc["shifterDir"] = shifterDir; doc["stepperDir"] = stepperDir; doc["udpLogEnabled"] = udpLogEnabled; + doc["pTab4Pwr"] = pTab4Pwr; doc["hMin"] = hMin; doc["hMax"] = hMax; doc["homingSensitivity"] = homingSensitivity; @@ -154,6 +156,7 @@ void userParameters::saveToLittleFS() { doc["shifterDir"] = shifterDir; doc["stepperDir"] = stepperDir; doc["udpLogEnabled"] = udpLogEnabled; + doc["pTab4Pwr"] = pTab4Pwr; doc["hMin"] = hMin; doc["hMax"] = hMax; doc["homingSensitivity"] = homingSensitivity; @@ -226,6 +229,9 @@ void userParameters::loadFromLittleFS() { if (!doc["udpLogEnabled"].isNull()) { setUdpLogEnabled(doc["udpLogEnabled"]); } + if (!doc["pTab4Pwr"].isNull()) { + setPTab4Pwr(doc["pTab4Pwr"]); + } if (doc["powerCorrectionFactor"]) { setPowerCorrectionFactor(doc["powerCorrectionFactor"]); if ((getPowerCorrectionFactor() < MIN_PCF) || (getPowerCorrectionFactor() > MAX_PCF)) { From 7ccc5494d444c3d5c476f1a489f20a7a5a08e7d9 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Sat, 25 Jan 2025 18:36:09 -0600 Subject: [PATCH 16/23] table should now load with csc only --- include/ERG_Mode.h | 2 +- src/ERG_Mode.cpp | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/ERG_Mode.h b/include/ERG_Mode.h index 292b05ec..80ecd8a2 100644 --- a/include/ERG_Mode.h +++ b/include/ERG_Mode.h @@ -111,7 +111,7 @@ class PowerTable { int32_t lookupWatts(int cad, int32_t targetPosition); // automatically load or save the Power Table - bool _manageSaveState(); + bool _manageSaveState(bool canSkipReliabilityChecks = false); // save powertable from littlefs bool _save(); diff --git a/src/ERG_Mode.cpp b/src/ERG_Mode.cpp index a73553b3..e2858a80 100644 --- a/src/ERG_Mode.cpp +++ b/src/ERG_Mode.cpp @@ -65,12 +65,12 @@ void PowerTable::runERG() { pTabUsed4Pwr = true; if (this->_hasBeenLoadedThisSession) { rtConfig->watts.setValue(lookupWatts(rtConfig->cad.getValue(), ss2k->getCurrentPosition())); - // rtConfig->watts.setSimulate(true); } else { // only run _manageSaveState every 5 seconds static unsigned long int saveStateTimer = millis(); if ((millis() - saveStateTimer) > 5000) { - this->_manageSaveState(); + //load the power table, true to skip checks. + this->_manageSaveState(true); saveStateTimer = millis(); } } @@ -875,7 +875,7 @@ void PowerTable::newEntry(PowerBuffer& powerBuffer) { BLE_ss2kCustomCharacteristic::notify(0x27, k); } -bool PowerTable::_manageSaveState() { +bool PowerTable::_manageSaveState(bool canSkipReliabilityChecks) { // Check if the table has been loaded in this session if (!this->_hasBeenLoadedThisSession) { SS2K_LOG(POWERTABLE_LOG_TAG, "Loading Power Table...."); @@ -895,6 +895,11 @@ bool PowerTable::_manageSaveState() { bool savedHomed; file.read((uint8_t*)&savedHomed, sizeof(savedHomed)); + // If both current and saved tables were created with homing, we can skip position reliability checks + if (!canSkipReliabilityChecks) { + canSkipReliabilityChecks = savedHomed && rtConfig->getHomed(); + } + if (version != TABLE_VERSION) { SS2K_LOG(POWERTABLE_LOG_TAG, "Expected power table version %d, found version %d", TABLE_VERSION, version); file.close(); @@ -912,9 +917,6 @@ bool PowerTable::_manageSaveState() { SS2K_LOG(POWERTABLE_LOG_TAG, "Loading power table version %d, Size %d, Homed %d", version, savedQuality, savedHomed); - // If both current and saved tables were created with homing, we can skip position reliability checks - bool canSkipReliabilityChecks = savedHomed && rtConfig->getHomed(); - if (!canSkipReliabilityChecks) { // Initialize a counter for reliable positions int reliablePositions = 0; From c2076ef6d838c3c17a6bd5f04365446c55deb18c Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Sat, 25 Jan 2025 19:52:01 -0600 Subject: [PATCH 17/23] cad with no pm --- src/BLE_Common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BLE_Common.cpp b/src/BLE_Common.cpp index 2458c5be..e3c25369 100644 --- a/src/BLE_Common.cpp +++ b/src/BLE_Common.cpp @@ -101,7 +101,7 @@ void BLECommunications() { #endif // DEBUG_HR_TO_PWR // Set outputs to zero if we're not simulating or have connected devices. - if (!spinBLEClient.connectedPM && !hr2p && !rtConfig->watts.getSimulate() && !rtConfig->cad.getSimulate()) { + if (!spinBLEClient.connectedPM && !hr2p && !rtConfig->watts.getSimulate() && !rtConfig->cad.getSimulate() && !userConfig->getPTab4Pwr()) { rtConfig->cad.setValue(0); rtConfig->watts.setValue(0); } From da6754a40a3babed52576fb10d3cd5e2f3130a61 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Sat, 25 Jan 2025 20:18:21 -0600 Subject: [PATCH 18/23] indications fixed. Working with Zwift --- src/BLE_Client.cpp | 2 +- src/BLE_Fitness_Machine_Service.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp index 70b81262..dceb78ea 100644 --- a/src/BLE_Client.cpp +++ b/src/BLE_Client.cpp @@ -838,7 +838,7 @@ void SpinBLEClient::checkBLEReconnect() { this->doScan = true; SS2K_LOG(BLE_CLIENT_LOG_TAG, "No HRM Connected"); } - if ((String(userConfig->getConnectedPowerMeter()) != "none") && !(spinBLEClient.connectedPM)) { + if ((String(userConfig->getConnectedPowerMeter()) != "none") && !(spinBLEClient.connectedPM || spinBLEClient.connectedCD)) { this->doScan = true; SS2K_LOG(BLE_CLIENT_LOG_TAG, "No PM Connected"); } diff --git a/src/BLE_Fitness_Machine_Service.cpp b/src/BLE_Fitness_Machine_Service.cpp index 423450aa..6729f8c2 100644 --- a/src/BLE_Fitness_Machine_Service.cpp +++ b/src/BLE_Fitness_Machine_Service.cpp @@ -43,12 +43,12 @@ void BLE_Fitness_Machine_Service::setupService(NimBLEServer *pServer, MyCharacte pFitnessMachineService = spinBLEServer.pServer->createService(FITNESSMACHINESERVICE_UUID); fitnessMachineFeature = pFitnessMachineService->createCharacteristic(FITNESSMACHINEFEATURE_UUID, NIMBLE_PROPERTY::READ); fitnessMachineControlPoint = pFitnessMachineService->createCharacteristic(FITNESSMACHINECONTROLPOINT_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::INDICATE); - fitnessMachineStatusCharacteristic = pFitnessMachineService->createCharacteristic(FITNESSMACHINESTATUS_UUID, NIMBLE_PROPERTY::NOTIFY); + fitnessMachineStatusCharacteristic = pFitnessMachineService->createCharacteristic(FITNESSMACHINESTATUS_UUID, NIMBLE_PROPERTY::INDICATE); fitnessMachineIndoorBikeData = pFitnessMachineService->createCharacteristic(FITNESSMACHINEINDOORBIKEDATA_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); fitnessMachineResistanceLevelRange = pFitnessMachineService->createCharacteristic(FITNESSMACHINERESISTANCELEVELRANGE_UUID, NIMBLE_PROPERTY::READ); fitnessMachinePowerRange = pFitnessMachineService->createCharacteristic(FITNESSMACHINEPOWERRANGE_UUID, NIMBLE_PROPERTY::READ); fitnessMachineInclinationRange = pFitnessMachineService->createCharacteristic(FITNESSMACHINEINCLINATIONRANGE_UUID, NIMBLE_PROPERTY::READ); - fitnessMachineTrainingStatus = pFitnessMachineService->createCharacteristic(FITNESSMACHINETRAININGSTATUS_UUID, NIMBLE_PROPERTY::NOTIFY); + fitnessMachineTrainingStatus = pFitnessMachineService->createCharacteristic(FITNESSMACHINETRAININGSTATUS_UUID, NIMBLE_PROPERTY::INDICATE); fitnessMachineFeature->setValue(ftmsFeature.bytes, sizeof(ftmsFeature)); ftmsIndoorBikeData[0] = static_cast(ftmsIBDFlags & 0xFF); // LSB, mask with 0xFF to get the lower 8 bits @@ -193,7 +193,7 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { case FitnessMachineControlPointProcedure::SetTargetPower: { rtConfig->setFTMSMode((uint8_t)rxValue[0]); - if (spinBLEClient.connectedPM || rtConfig->watts.getSimulate()) { + if (spinBLEClient.connectedPM || rtConfig->watts.getSimulate() || spinBLEClient.connectedCD) { returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; rtConfig->watts.setTarget(bytes_to_u16(rxValue[2], rxValue[1])); @@ -312,8 +312,8 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { } fitnessMachineStatusCharacteristic->setValue(ftmsStatus.data(), ftmsStatus.size()); pCharacteristic->indicate(); - fitnessMachineTrainingStatus->notify(); - fitnessMachineStatusCharacteristic->notify(); + fitnessMachineTrainingStatus->indicate(); + fitnessMachineStatusCharacteristic->indicate(); } } From 3feb3dc9d54a6a41bdeeb53f019faf659b61b4ce Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Sun, 26 Jan 2025 15:46:47 -0600 Subject: [PATCH 19/23] testing to fix notifications in Zwift --- src/BLE_Fitness_Machine_Service.cpp | 30 ++++++++++++++++++++--------- src/BLE_Server.cpp | 9 +++++++-- src/Main.cpp | 2 +- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/BLE_Fitness_Machine_Service.cpp b/src/BLE_Fitness_Machine_Service.cpp index 6729f8c2..03a264d6 100644 --- a/src/BLE_Fitness_Machine_Service.cpp +++ b/src/BLE_Fitness_Machine_Service.cpp @@ -43,12 +43,12 @@ void BLE_Fitness_Machine_Service::setupService(NimBLEServer *pServer, MyCharacte pFitnessMachineService = spinBLEServer.pServer->createService(FITNESSMACHINESERVICE_UUID); fitnessMachineFeature = pFitnessMachineService->createCharacteristic(FITNESSMACHINEFEATURE_UUID, NIMBLE_PROPERTY::READ); fitnessMachineControlPoint = pFitnessMachineService->createCharacteristic(FITNESSMACHINECONTROLPOINT_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::INDICATE); - fitnessMachineStatusCharacteristic = pFitnessMachineService->createCharacteristic(FITNESSMACHINESTATUS_UUID, NIMBLE_PROPERTY::INDICATE); - fitnessMachineIndoorBikeData = pFitnessMachineService->createCharacteristic(FITNESSMACHINEINDOORBIKEDATA_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); + fitnessMachineStatusCharacteristic = pFitnessMachineService->createCharacteristic(FITNESSMACHINESTATUS_UUID, NIMBLE_PROPERTY::NOTIFY); + fitnessMachineIndoorBikeData = pFitnessMachineService->createCharacteristic(FITNESSMACHINEINDOORBIKEDATA_UUID, NIMBLE_PROPERTY::NOTIFY); fitnessMachineResistanceLevelRange = pFitnessMachineService->createCharacteristic(FITNESSMACHINERESISTANCELEVELRANGE_UUID, NIMBLE_PROPERTY::READ); fitnessMachinePowerRange = pFitnessMachineService->createCharacteristic(FITNESSMACHINEPOWERRANGE_UUID, NIMBLE_PROPERTY::READ); fitnessMachineInclinationRange = pFitnessMachineService->createCharacteristic(FITNESSMACHINEINCLINATIONRANGE_UUID, NIMBLE_PROPERTY::READ); - fitnessMachineTrainingStatus = pFitnessMachineService->createCharacteristic(FITNESSMACHINETRAININGSTATUS_UUID, NIMBLE_PROPERTY::INDICATE); + fitnessMachineTrainingStatus = pFitnessMachineService->createCharacteristic(FITNESSMACHINETRAININGSTATUS_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); fitnessMachineFeature->setValue(ftmsFeature.bytes, sizeof(ftmsFeature)); ftmsIndoorBikeData[0] = static_cast(ftmsIBDFlags & 0xFF); // LSB, mask with 0xFF to get the lower 8 bits @@ -130,21 +130,23 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { switch ((uint8_t)rxValue[0]) { case FitnessMachineControlPointProcedure::RequestControl: - returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; + static bool firstRequest = true; + returnValue[2] = firstRequest ? FitnessMachineControlPointResultCode::Success : FitnessMachineControlPointResultCode::OpCodeNotSupported; + firstRequest = !firstRequest; rtConfig->watts.setTarget(0); rtConfig->setSimTargetWatts(false); logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Control Request"); - ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Idle; // 0x01; + ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x01; fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); ftmsStatus = {FitnessMachineStatus::StartedOrResumedByUser}; pCharacteristic->setValue(returnValue, 3); break; case FitnessMachineControlPointProcedure::Reset: { - returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; + returnValue[2] = FitnessMachineControlPointResultCode::InvalidParameter; // 0x01; logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Reset"); - ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Idle; // 0x01; + ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x01; ftmsStatus = {FitnessMachineStatus::Reset}; fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); pCharacteristic->setValue(returnValue, 3); @@ -309,11 +311,21 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { ftmsStatus = {FitnessMachineStatus::StartedOrResumedByUser}; ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); + fitnessMachineTrainingStatus->notify(); } fitnessMachineStatusCharacteristic->setValue(ftmsStatus.data(), ftmsStatus.size()); + SS2K_LOG(FMTS_SERVER_LOG_TAG, "FTMS Status: %02x %02x", ftmsStatus[0], ftmsStatus[1]); pCharacteristic->indicate(); - fitnessMachineTrainingStatus->indicate(); - fitnessMachineStatusCharacteristic->indicate(); + std::string characteristicValue = pCharacteristic->getValue(); + std::string logValue; + for (size_t i = 0; i < characteristicValue.length(); ++i) { + char buf[4]; + snprintf(buf, sizeof(buf), "%02x ", (unsigned char)characteristicValue[i]); + logValue += buf; + } + SS2K_LOG(FMTS_SERVER_LOG_TAG, "FTMS CP Value: %s", logValue.c_str()); + fitnessMachineTrainingStatus->notify(); + fitnessMachineStatusCharacteristic->notify(); } } diff --git a/src/BLE_Server.cpp b/src/BLE_Server.cpp index 64912eb7..bf7a72da 100644 --- a/src/BLE_Server.cpp +++ b/src/BLE_Server.cpp @@ -184,8 +184,13 @@ void MyCharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, N } void MyCharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, int code) { - //SS2K_LOG(BLE_SERVER_LOG_TAG, "Notification/Indication status code: %d for characteristic: %s", - // code, pCharacteristic->getUUID().toString().c_str()); + //loop through and print out the characteristic data + for(int i = 0; i < pCharacteristic->getValue().size(); i++) { + SS2K_LOG(BLE_SERVER_LOG_TAG, "Characteristic Data: %d", pCharacteristic->getValue()[i]); + } + + SS2K_LOG(BLE_SERVER_LOG_TAG, "Notification/Indication status code: %d for characteristic: %s", + code, pCharacteristic->getUUID().toString().c_str()); } void MyCharacteristicCallbacks::onSubscribe(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) { diff --git a/src/Main.cpp b/src/Main.cpp index 0eefdce5..dfb7f825 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -224,7 +224,7 @@ void SS2K::maintenanceLoop(void *pvParameters) { speed = userConfig->getStepperSpeed(); } } - + ss2k->updateStepperSpeed(speed); } From ffe57adee2120871479ddb849dbb88bd4ddac1c7 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Sun, 26 Jan 2025 19:11:17 -0600 Subject: [PATCH 20/23] more testing --- platformio.ini | 5 +- src/BLE_Fitness_Machine_Service.cpp | 93 ++++++++--------------------- src/BLE_Server.cpp | 16 +++-- 3 files changed, 39 insertions(+), 75 deletions(-) diff --git a/platformio.ini b/platformio.ini index e1033275..3860dee8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,7 +30,10 @@ build_flags = -D ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE=3500 -std=gnu++17 lib_deps = - https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.1.3.zip + #use a PR version of the library to fix a bug. Remove this line when the PR is merged + #NimBLE-Arduino PR #878 + https://github.com/h2zero/NimBLE-Arduino.git#refactor + #https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.1.3.zip https://github.com/teemuatlut/TMCStepper/archive/refs/tags/v0.7.3.zip https://github.com/bblanchon/ArduinoJson/archive/refs/tags/v6.20.0.zip https://github.com/gin66/FastAccelStepper/archive/refs/tags/0.31.2.zip diff --git a/src/BLE_Fitness_Machine_Service.cpp b/src/BLE_Fitness_Machine_Service.cpp index 03a264d6..4a570236 100644 --- a/src/BLE_Fitness_Machine_Service.cpp +++ b/src/BLE_Fitness_Machine_Service.cpp @@ -94,7 +94,7 @@ void BLE_Fitness_Machine_Service::update() { ftmsIndoorBikeData[10] = (uint8_t)hr; - fitnessMachineIndoorBikeData->setValue(ftmsIndoorBikeData, 11); + fitnessMachineIndoorBikeData->setValue(ftmsIndoorBikeData, sizeof(ftmsIndoorBikeData)); fitnessMachineIndoorBikeData->notify(); const int kLogBufCapacity = 200; // Data(30), Sep(data/2), Arrow(3), CharId(37), Sep(3), CharId(37), Sep(3), Name(10), Prefix(2), HR(7), SEP(1), CD(10), SEP(1), PW(8), @@ -113,71 +113,54 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { if (rxValue == "") { return; } + uint8_t returnValue[3] = {FitnessMachineControlPointProcedure::ResponseCode, (uint8_t)rxValue[0], FitnessMachineControlPointResultCode::OpCodeNotSupported}; BLECharacteristic *pCharacteristic = NimBLEDevice::getServer()->getServiceByUUID(FITNESSMACHINESERVICE_UUID)->getCharacteristic(FITNESSMACHINECONTROLPOINT_UUID); + std::vector ftmsStatus = {FitnessMachineStatus::ReservedForFutureUse}; - std::vector ftmsStatus; if (rxValue.length() >= 1) { - uint8_t *pData = reinterpret_cast(&rxValue[0]); - int length = rxValue.length(); - + uint8_t *pData = reinterpret_cast(&rxValue[0]); + int length = rxValue.length(); const int kLogBufCapacity = (rxValue.length() * 2) + 60; // largest comment is 48 VV char logBuf[kLogBufCapacity]; - int logBufLength = ss2k_log_hex_to_buffer(pData, length, logBuf, 0, kLogBufCapacity); - int port = 0; - uint8_t returnValue[3] = {FitnessMachineControlPointProcedure::ResponseCode, (uint8_t)rxValue[0], FitnessMachineControlPointResultCode::OpCodeNotSupported}; - - ftmsStatus = {FitnessMachineStatus::ReservedForFutureUse}; + int logBufLength = ss2k_log_hex_to_buffer(pData, length, logBuf, 0, kLogBufCapacity); + int port = 0; switch ((uint8_t)rxValue[0]) { case FitnessMachineControlPointProcedure::RequestControl: - static bool firstRequest = true; - returnValue[2] = firstRequest ? FitnessMachineControlPointResultCode::Success : FitnessMachineControlPointResultCode::OpCodeNotSupported; - firstRequest = !firstRequest; + returnValue[2] = FitnessMachineControlPointResultCode::Success; rtConfig->watts.setTarget(0); rtConfig->setSimTargetWatts(false); logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Control Request"); ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x01; - fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); - ftmsStatus = {FitnessMachineStatus::StartedOrResumedByUser}; - pCharacteristic->setValue(returnValue, 3); + ftmsStatus = {FitnessMachineStatus::StartedOrResumedByUser}; break; case FitnessMachineControlPointProcedure::Reset: { - returnValue[2] = FitnessMachineControlPointResultCode::InvalidParameter; // 0x01; - + returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Reset"); ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x01; ftmsStatus = {FitnessMachineStatus::Reset}; - fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); - pCharacteristic->setValue(returnValue, 3); + } break; case FitnessMachineControlPointProcedure::SetTargetInclination: { rtConfig->setFTMSMode((uint8_t)rxValue[0]); returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; - - port = (rxValue[2] << 8) + rxValue[1]; + port = (rxValue[2] << 8) + rxValue[1]; port *= 10; - rtConfig->setTargetIncline(port); - logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Incline Mode: %2f", rtConfig->getTargetIncline() / 100); - ftmsStatus = {FitnessMachineStatus::TargetInclineChanged, (uint8_t)rxValue[1], (uint8_t)rxValue[2]}; ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; - fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); - pCharacteristic->setValue(returnValue, 3); } break; case FitnessMachineControlPointProcedure::SetTargetResistanceLevel: { rtConfig->setFTMSMode((uint8_t)rxValue[0]); returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; - fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); if ((int)rxValue[1] >= rtConfig->getMinResistance() && (int)rxValue[1] <= rtConfig->getMaxResistance()) { rtConfig->resistance.setTarget((int)rxValue[1]); returnValue[2] = FitnessMachineControlPointResultCode::Success; - logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Resistance Mode: %d", rtConfig->resistance.getTarget()); } else if ((int)rxValue[1] > rtConfig->getMinResistance()) { rtConfig->resistance.setTarget(rtConfig->getMaxResistance()); @@ -190,21 +173,17 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { } ftmsStatus = {FitnessMachineStatus::TargetResistanceLevelChanged, (uint8_t)(rtConfig->resistance.getTarget() % 256)}; rtConfig->resistance.setTarget(rtConfig->resistance.getTarget()); - pCharacteristic->setValue(returnValue, 3); } break; case FitnessMachineControlPointProcedure::SetTargetPower: { rtConfig->setFTMSMode((uint8_t)rxValue[0]); if (spinBLEClient.connectedPM || rtConfig->watts.getSimulate() || spinBLEClient.connectedCD) { returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; - rtConfig->watts.setTarget(bytes_to_u16(rxValue[2], rxValue[1])); logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> ERG Mode Target: %d Current: %d Incline: %2f", rtConfig->watts.getTarget(), rtConfig->watts.getValue(), rtConfig->getTargetIncline() / 100); - ftmsStatus = {FitnessMachineStatus::TargetPowerChanged, (uint8_t)rxValue[1], (uint8_t)rxValue[2]}; ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::WattControl; // 0x0C; - fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); // Adjust set point for powerCorrectionFactor and send to FTMS server (if connected) int adjustedTarget = rtConfig->watts.getTarget() / userConfig->getPowerCorrectionFactor(); const uint8_t translated[] = {FitnessMachineControlPointProcedure::SetTargetPower, (uint8_t)(adjustedTarget % 256), (uint8_t)(adjustedTarget / 256)}; @@ -213,36 +192,28 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { returnValue[2] = FitnessMachineControlPointResultCode::OpCodeNotSupported; // 0x02; no power meter connected, so no ERG logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> ERG Mode: No Power Meter Connected"); } - pCharacteristic->setValue(returnValue, 3); } break; case FitnessMachineControlPointProcedure::StartOrResume: { returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; - pCharacteristic->setValue(returnValue, 3); - logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Start Training"); ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; - fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); - ftmsStatus = {FitnessMachineStatus::StartedOrResumedByUser}; + ftmsStatus = {FitnessMachineStatus::StartedOrResumedByUser}; } break; case FitnessMachineControlPointProcedure::StopOrPause: { returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; - pCharacteristic->setValue(returnValue, 3); // rxValue[1] == 1 -> Stop, 2 -> Pause // TODO: Move stepper to Min Position logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Stop Training"); - ftmsStatus = {FitnessMachineStatus::StoppedOrPausedByUser}; ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; - fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); } break; case FitnessMachineControlPointProcedure::SetIndoorBikeSimulationParameters: { // sim mode rtConfig->setFTMSMode((uint8_t)rxValue[0]); returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; - pCharacteristic->setValue(returnValue, 3); signed char buf[2]; // int16_t windSpeed = (rxValue[2] << 8) + rxValue[1]; buf[0] = rxValue[3]; // (Least significant byte) @@ -252,7 +223,6 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { port = bytes_to_u16(buf[1], buf[0]); rtConfig->setTargetIncline(port); logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Sim Mode Incline %2f", rtConfig->getTargetIncline() / 100); - ftmsStatus = {FitnessMachineStatus::IndoorBikeSimulationParametersChanged, (uint8_t)rxValue[1], (uint8_t)rxValue[2], @@ -262,7 +232,6 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { (uint8_t)rxValue[6]}; ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; - fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); spinBLEClient.FTMSControlPointWrite(pData, length); } break; @@ -272,50 +241,35 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { returnValue[2] = FitnessMachineControlPointResultCode::Success; pCharacteristic->setValue(controlPoint, 6); logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Spin Down Requested"); - - ftmsStatus = {FitnessMachineStatus::SpinDownStatus, 0x01}; // send low and high speed targets - - ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; - pCharacteristic->setValue(returnValue, 3); - pCharacteristic->indicate(); + ftmsStatus = {FitnessMachineStatus::SpinDownStatus, 0x01}; // send low and high speed targets + ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; spinBLEServer.spinDownFlag = 2; } break; case FitnessMachineControlPointProcedure::SetTargetedCadence: { rtConfig->setFTMSMode((uint8_t)rxValue[0]); - returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; - pCharacteristic->setValue(returnValue, 3); - + returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; int targetCadence = bytes_to_u16(rxValue[2], rxValue[1]); // rtConfig->setTargetCadence(targetCadence); logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Target Cadence: %d ", targetCadence); - - ftmsStatus = {FitnessMachineStatus::TargetedCadenceChanged, (uint8_t)rxValue[1], (uint8_t)rxValue[2]}; - + ftmsStatus = {FitnessMachineStatus::TargetedCadenceChanged, (uint8_t)rxValue[1], (uint8_t)rxValue[2]}; ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; - fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); } break; default: { logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> Unsupported FTMS Request"); - pCharacteristic->setValue(returnValue, 3); } } SS2K_LOG(FMTS_SERVER_LOG_TAG, "%s", logBuf); } else { SS2K_LOG(FMTS_SERVER_LOG_TAG, "App wrote nothing "); SS2K_LOG(FMTS_SERVER_LOG_TAG, "assuming it's a Control request"); - - uint8_t controlPoint[3] = {FitnessMachineControlPointProcedure::ResponseCode, 0x00, FitnessMachineControlPointResultCode::Success}; - pCharacteristic->setValue(controlPoint, 3); + returnValue[2] = FitnessMachineControlPointResultCode::Success; ftmsStatus = {FitnessMachineStatus::StartedOrResumedByUser}; ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; - fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); - fitnessMachineTrainingStatus->notify(); } fitnessMachineStatusCharacteristic->setValue(ftmsStatus.data(), ftmsStatus.size()); - SS2K_LOG(FMTS_SERVER_LOG_TAG, "FTMS Status: %02x %02x", ftmsStatus[0], ftmsStatus[1]); - pCharacteristic->indicate(); + SS2K_LOG(FMTS_SERVER_LOG_TAG, "Calling notify %s -> %02x %02x", fitnessMachineStatusCharacteristic->getUUID().toString().c_str(), ftmsStatus[0], ftmsStatus[1]); std::string characteristicValue = pCharacteristic->getValue(); std::string logValue; for (size_t i = 0; i < characteristicValue.length(); ++i) { @@ -323,9 +277,12 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { snprintf(buf, sizeof(buf), "%02x ", (unsigned char)characteristicValue[i]); logValue += buf; } - SS2K_LOG(FMTS_SERVER_LOG_TAG, "FTMS CP Value: %s", logValue.c_str()); - fitnessMachineTrainingStatus->notify(); - fitnessMachineStatusCharacteristic->notify(); + SS2K_LOG(FMTS_SERVER_LOG_TAG, "Caling notify %s -> %s", pCharacteristic->getUUID().toString().c_str(), logValue.c_str()); + fitnessMachineControlPoint->setValue(returnValue, 3); + fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); + fitnessMachineControlPoint->notify(); + //fitnessMachineTrainingStatus->notify(); + //fitnessMachineStatusCharacteristic->notify(); } } diff --git a/src/BLE_Server.cpp b/src/BLE_Server.cpp index bf7a72da..6b19c68c 100644 --- a/src/BLE_Server.cpp +++ b/src/BLE_Server.cpp @@ -184,13 +184,17 @@ void MyCharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, N } void MyCharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, int code) { - //loop through and print out the characteristic data - for(int i = 0; i < pCharacteristic->getValue().size(); i++) { - SS2K_LOG(BLE_SERVER_LOG_TAG, "Characteristic Data: %d", pCharacteristic->getValue()[i]); - } + //loop through and accumulate the data into a C++ string + std::string characteristicValue = pCharacteristic->getValue(); + std::string logValue; + for (size_t i = 0; i < characteristicValue.length(); ++i) { + char buf[4]; + snprintf(buf, sizeof(buf), "%02x ", (unsigned char)characteristicValue[i]); + logValue += buf; + } - SS2K_LOG(BLE_SERVER_LOG_TAG, "Notification/Indication status code: %d for characteristic: %s", - code, pCharacteristic->getUUID().toString().c_str()); + SS2K_LOG(BLE_SERVER_LOG_TAG, "Notification/Indication status code: %d for char: %s Data: %s", + code, pCharacteristic->getUUID().toString().c_str(), logValue.c_str()); } void MyCharacteristicCallbacks::onSubscribe(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) { From 486cde2625dfe56fe8dfe84184f8a6d7fb595a62 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Wed, 12 Feb 2025 10:07:56 -0800 Subject: [PATCH 21/23] working --- platformio.ini | 4 ++-- src/BLE_Fitness_Machine_Service.cpp | 19 ++++++++++++------- src/BLE_Server.cpp | 2 +- src/HTTP_Server_Basic.cpp | 4 ++-- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/platformio.ini b/platformio.ini index 3860dee8..3f700599 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,8 +32,8 @@ build_flags = lib_deps = #use a PR version of the library to fix a bug. Remove this line when the PR is merged #NimBLE-Arduino PR #878 - https://github.com/h2zero/NimBLE-Arduino.git#refactor - #https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.1.3.zip + #https://github.com/h2zero/NimBLE-Arduino.git#refactor + https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/2.2.1.zip https://github.com/teemuatlut/TMCStepper/archive/refs/tags/v0.7.3.zip https://github.com/bblanchon/ArduinoJson/archive/refs/tags/v6.20.0.zip https://github.com/gin66/FastAccelStepper/archive/refs/tags/0.31.2.zip diff --git a/src/BLE_Fitness_Machine_Service.cpp b/src/BLE_Fitness_Machine_Service.cpp index 4a570236..2131caca 100644 --- a/src/BLE_Fitness_Machine_Service.cpp +++ b/src/BLE_Fitness_Machine_Service.cpp @@ -19,7 +19,7 @@ BLE_Fitness_Machine_Service::BLE_Fitness_Machine_Service() fitnessMachineInclinationRange(nullptr), fitnessMachineTrainingStatus(nullptr) {} -uint8_t ftmsTrainingStatus[2] = {0x08, 0x00}; +uint8_t ftmsTrainingStatus[2] = {0x00, 0x00}; // Initialize to Other state void BLE_Fitness_Machine_Service::setupService(NimBLEServer *pServer, MyCharacteristicCallbacks *chrCallbacks) { // Resistance, IPower, HeartRate @@ -269,7 +269,7 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::Other; // 0x00; } fitnessMachineStatusCharacteristic->setValue(ftmsStatus.data(), ftmsStatus.size()); - SS2K_LOG(FMTS_SERVER_LOG_TAG, "Calling notify %s -> %02x %02x", fitnessMachineStatusCharacteristic->getUUID().toString().c_str(), ftmsStatus[0], ftmsStatus[1]); + //SS2K_LOG(FMTS_SERVER_LOG_TAG, "Calling notify %s -> %02x %02x", fitnessMachineStatusCharacteristic->getUUID().toString().c_str(), ftmsStatus[0], ftmsStatus[1]); std::string characteristicValue = pCharacteristic->getValue(); std::string logValue; for (size_t i = 0; i < characteristicValue.length(); ++i) { @@ -277,12 +277,17 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { snprintf(buf, sizeof(buf), "%02x ", (unsigned char)characteristicValue[i]); logValue += buf; } - SS2K_LOG(FMTS_SERVER_LOG_TAG, "Caling notify %s -> %s", pCharacteristic->getUUID().toString().c_str(), logValue.c_str()); + SS2K_LOG(FMTS_SERVER_LOG_TAG, "Calling notify %s -> %s", pCharacteristic->getUUID().toString().c_str(), logValue.c_str()); fitnessMachineControlPoint->setValue(returnValue, 3); fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); - fitnessMachineControlPoint->notify(); - //fitnessMachineTrainingStatus->notify(); - //fitnessMachineStatusCharacteristic->notify(); + fitnessMachineControlPoint->indicate(); + // only notify if the value has changed + static uint8_t _lastTrainingStatus[2] = {0x00, 0x00}; + if (memcmp(_lastTrainingStatus, ftmsTrainingStatus, 2) != 0) { + memcpy(_lastTrainingStatus, ftmsTrainingStatus, 2); + fitnessMachineTrainingStatus->notify(); + } + fitnessMachineStatusCharacteristic->notify(); } } @@ -328,4 +333,4 @@ bool BLE_Fitness_Machine_Service::spinDown(uint8_t response) { }*/ return true; -} \ No newline at end of file +} diff --git a/src/BLE_Server.cpp b/src/BLE_Server.cpp index 6b19c68c..98b6dd2d 100644 --- a/src/BLE_Server.cpp +++ b/src/BLE_Server.cpp @@ -193,7 +193,7 @@ void MyCharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, logValue += buf; } - SS2K_LOG(BLE_SERVER_LOG_TAG, "Notification/Indication status code: %d for char: %s Data: %s", + SS2K_LOGW(BLE_SERVER_LOG_TAG, "Notification/Indication status code: %d for char: %s Data: %s", code, pCharacteristic->getUUID().toString().c_str(), logValue.c_str()); } diff --git a/src/HTTP_Server_Basic.cpp b/src/HTTP_Server_Basic.cpp index 2324e500..e17ef0e7 100644 --- a/src/HTTP_Server_Basic.cpp +++ b/src/HTTP_Server_Basic.cpp @@ -58,7 +58,7 @@ void _APSetup() { //WiFi.setHostname(userConfig->getDeviceName()); WiFi.softAPsetHostname(userConfig->getDeviceName()); WiFi.enableAP(true); - vTaskDelay(500); // Micro controller requires some time to reset the mode + vTaskDelay(500/portTICK_RATE_MS); // Micro controller requires some time to reset the mode } // ********************************WIFI Setup************************* @@ -104,7 +104,7 @@ void startWifi() { SS2K_LOG(HTTP_SERVER_LOG_TAG, "Using Default Password"); WiFi.softAP(userConfig->getDeviceName(), DEFAULT_PASSWORD); } - vTaskDelay(50); + vTaskDelay(50/portTICK_RATE_MS); myIP = WiFi.softAPIP(); /* Setup the DNS server redirecting all the domains to the apIP */ dnsServer.setErrorReplyCode(DNSReplyCode::NoError); From 1a1f0e80db131f8fcac858ec6f0b3286071b2628 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Wed, 12 Feb 2025 15:47:48 -0800 Subject: [PATCH 22/23] added incomplete SB20 service --- include/BLE_SB20_Service.h | 39 +++++++++++++++++++++++++++++++++ src/BLE_SB20_Service.cpp | 44 ++++++++++++++++++++++++++++++++++++++ src/BLE_Server.cpp | 4 ++++ 3 files changed, 87 insertions(+) create mode 100644 include/BLE_SB20_Service.h create mode 100644 src/BLE_SB20_Service.cpp diff --git a/include/BLE_SB20_Service.h b/include/BLE_SB20_Service.h new file mode 100644 index 00000000..e29f5bde --- /dev/null +++ b/include/BLE_SB20_Service.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Anthony Doud & Joel Baranick + * All rights reserved + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#pragma once + +#include +#include "BLE_Common.h" + +// See https://github.com/JanDeVisser/stages-monitor/tree/master/sb20display +#define SB20_SERVICE_UUID "a026ee0b-0a7d-4ab3-97fa-f1500f9feb8b" +#define SB20_CHARACTERISTIC_UUID "a026e037-0a7d-4ab3-97fa-f1500f9feb8b" + +struct SB20Data { + uint8_t gear; // Calculated Gear (1-22) + uint16_t cadence; // Cadence in RPM + uint16_t power; // Power in Watts + uint16_t heartrate; // Heart Rate in BPM + uint8_t battery; // always 4 +}; + +class BLE_SB20_Service { +public: + BLE_SB20_Service(); + + void begin(); + void setData(SB20Data data); + void notify(); + +private: + BLEService *pService; + BLECharacteristic *pCharacteristic; + SB20Data currentData; + bool deviceConnected; + +}; diff --git a/src/BLE_SB20_Service.cpp b/src/BLE_SB20_Service.cpp new file mode 100644 index 00000000..65710d9b --- /dev/null +++ b/src/BLE_SB20_Service.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 Anthony Doud & Joel Baranick + * All rights reserved + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include "BLE_SB20_Service.h" + +BLE_SB20_Service::BLE_SB20_Service() : pService(nullptr), pCharacteristic(nullptr) { + currentData.gear = 1; + currentData.cadence = 0; + currentData.power = 0; + currentData.heartrate = 0; + currentData.battery = 4; // Always full +} + +void BLE_SB20_Service::begin() { + pService = BLEDevice::createServer()->createService(SB20_SERVICE_UUID); + pCharacteristic = pService->createCharacteristic(SB20_CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); + pService->start(); + SS2K_LOG(SS2K_LOG_TAG,"SB20 Service started\n"); +} + +void BLE_SB20_Service::setData(SB20Data data) { currentData = data; } + +void BLE_SB20_Service::notify() { + if (pCharacteristic == nullptr) { + return; + } + + // Get current values and populate data struct + int shifterPosition = rtConfig->getShifterPosition(); + shifterPosition = constrain(shifterPosition, 1, 22); // Clamp to 1-22 + + currentData.gear = shifterPosition; + currentData.cadence = rtConfig->cad.getValue(); // direct value, no scaling + currentData.power = rtConfig->watts.getValue(); + currentData.heartrate = rtConfig->hr.getValue(); + + pCharacteristic->setValue((uint8_t *)¤tData, sizeof(currentData)); + pCharacteristic->notify(); + SS2K_LOG(SS2K_LOG_TAG,"SB20 data sent: Gear=%d, Cadence=%d, Power=%d, HR=%d\n", currentData.gear, currentData.cadence, currentData.power, currentData.heartrate); +} \ No newline at end of file diff --git a/src/BLE_Server.cpp b/src/BLE_Server.cpp index 98b6dd2d..a352cf3e 100644 --- a/src/BLE_Server.cpp +++ b/src/BLE_Server.cpp @@ -15,6 +15,7 @@ #include "BLE_Custom_Characteristic.h" #include "BLE_Device_Information_Service.h" #include "BLE_Wattbike_Service.h" +#include "BLE_SB20_Service.h" #include #include @@ -34,6 +35,7 @@ BLE_Fitness_Machine_Service fitnessMachineService; BLE_ss2kCustomCharacteristic ss2kCustomCharacteristic; BLE_Device_Information_Service deviceInformationService; BLE_Wattbike_Service wattbikeService; +//BLE_SB20_Service sb20Service; void startBLEServer() { // Server Setup @@ -51,6 +53,7 @@ void startBLEServer() { ss2kCustomCharacteristic.setupService(spinBLEServer.pServer); deviceInformationService.setupService(spinBLEServer.pServer); wattbikeService.setupService(spinBLEServer.pServer); // No callback needed + //sb20Service.begin(); BLEFirmwareSetup(); // const std::string fitnessData = {0b00000001, 0b00100000, 0b00000000}; @@ -73,6 +76,7 @@ void SpinBLEServer::update() { cyclingSpeedCadenceService.update(); fitnessMachineService.update(); wattbikeService.parseNemit(); // Changed from update() to parseNemit() + ///sb20Service.notify(); } double SpinBLEServer::calculateSpeed() { From ccfb22a484548d04acbc89e211a1efdb7bbd73d2 Mon Sep 17 00:00:00 2001 From: Anthony Doud Date: Fri, 14 Feb 2025 10:01:35 -0600 Subject: [PATCH 23/23] working --- src/BLE_Fitness_Machine_Service.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BLE_Fitness_Machine_Service.cpp b/src/BLE_Fitness_Machine_Service.cpp index 2131caca..a062f4e5 100644 --- a/src/BLE_Fitness_Machine_Service.cpp +++ b/src/BLE_Fitness_Machine_Service.cpp @@ -281,12 +281,12 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { fitnessMachineControlPoint->setValue(returnValue, 3); fitnessMachineTrainingStatus->setValue(ftmsTrainingStatus, 2); fitnessMachineControlPoint->indicate(); - // only notify if the value has changed + /* only notify if the value has changed static uint8_t _lastTrainingStatus[2] = {0x00, 0x00}; if (memcmp(_lastTrainingStatus, ftmsTrainingStatus, 2) != 0) { memcpy(_lastTrainingStatus, ftmsTrainingStatus, 2); fitnessMachineTrainingStatus->notify(); - } + } */ fitnessMachineStatusCharacteristic->notify(); } }