Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

### Changed
- Only check battery level on initial connection. This is to fix Tempo power meter drops.
- Moved battery information to the SpinBLEAdvertisedDevice class.

### Hardware

Expand Down
36 changes: 14 additions & 22 deletions include/BLE_Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ namespace BLEServices {
const std::vector<BLEServiceInfo> 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"},
{ECHELON_DEVICE_UUID, ECHELON_SERVICE_UUID, "Echelon Device"}, // Two lines for Echelon
{ECHELON_SERVICE_UUID, ECHELON_DATA_UUID, "Echelon Service"}, // Because one is for search, the other for data
{ECHELON_DEVICE_UUID, ECHELON_SERVICE_UUID, "Echelon Device"}, // Two lines for Echelon
{ECHELON_SERVICE_UUID, ECHELON_DATA_UUID, "Echelon Service"}, // Because one is for search, the other for data
{FITNESSMACHINESERVICE_UUID, FITNESSMACHINEINDOORBIKEDATA_UUID, "Fitness Machine Service"},
{HID_SERVICE_UUID, HID_REPORT_DATA_UUID, "HID Service"},
{FLYWHEEL_UART_SERVICE_UUID, FLYWHEEL_UART_TX_UUID, "Flywheel UART Service"}};
Expand Down Expand Up @@ -145,33 +145,25 @@ class SpinBLEAdvertisedDevice {
private:
QueueHandle_t dataBufferQueue = nullptr;

bool isPostConnected = false;
void clearState(bool resetAdvertisedDevice); // NEW

public: // eventually these should be made private
// // TODO: Do we dispose of this object? Is so, we need to de-allocate the queue.
// // This destructor was called too early and the queue was deleted out from
// // under us.
// ~SpinBLEAdvertisedDevice() {
// if (dataBuffer != nullptr) {
// Serial.println("Deleting queue");
// vQueueDelete(dataBuffer);
// }
// }
public:
SpinBLEAdvertisedDevice() { clearState(true); } // NEW

const NimBLEAdvertisedDevice* advertisedDevice = nullptr;
NimBLEAddress peerAddress;

int connectedClientID = BLE_HS_CONN_HANDLE_NONE;
BLEUUID serviceUUID = (uint16_t)0x0000;
BLEUUID charUUID = (uint16_t)0x0000;
bool isHRM = false;
bool isPM = false;
bool isCSC = false;
bool isCT = false;
bool isRemote = false;
bool doConnect = false;
void setPostConnected(bool pc) { isPostConnected = pc; }
bool getPostConnected() { return isPostConnected; }
Measurement batt;
bool isHRM = false;
bool isPM = false;
bool isCSC = false;
bool isCT = false;
bool isRemote = false;
bool doConnect = false;
bool isPostConnected = false;

void set(const NimBLEAdvertisedDevice* device, int id = BLE_HS_CONN_HANDLE_NONE, BLEUUID inServiceUUID = (uint16_t)0x0000, BLEUUID inCharUUID = (uint16_t)0x0000);
void reset(bool resetAdvertisedDevice = true);
bool enqueueData(uint8_t* data, size_t length, NimBLEUUID serviceUUID, NimBLEUUID charUUID);
Expand Down
3 changes: 1 addition & 2 deletions include/SmartSpin_parameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,9 @@ class RuntimeParameters {

public:
Measurement watts;
Measurement pm_batt;
Measurement hr;
Measurement hr_batt;
Measurement cad;
Measurement batt;
Measurement resistance;

void setTargetIncline(float inc) { targetIncline = inc; }
Expand Down
102 changes: 47 additions & 55 deletions src/BLE_Client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ bool subscribeToAllNotifications(NimBLEClient *pClient) {
if (!pClient || !pClient->isConnected()) {
SS2K_LOG(BLE_CLIENT_LOG_TAG, "Client not connected for notifications");
return false;
} //The Issue with Echelon is that there are multiple services
} // The Issue with Echelon is that there are multiple services
for (const auto &service : BLEServices::SUPPORTED_SERVICES) {
NimBLERemoteService *pSvc = pClient->getService(service.serviceUUID);
if (pSvc) {
Expand Down Expand Up @@ -386,11 +386,9 @@ void MyClientCallback::onDisconnect(NimBLEClient *pClient, int reason) {
(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 == CSCSERVICE_UUID)) {
SS2K_LOG(BLE_CLIENT_LOG_TAG, "Deregistered PM on Disconnect");
rtConfig->pm_batt.setValue(0);
}
if ((spinBLEClient.myBLEDevices[i].charUUID == HEARTCHARACTERISTIC_UUID)) {
SS2K_LOG(BLE_CLIENT_LOG_TAG, "Deregistered HR on Disconnect");
rtConfig->hr_batt.setValue(0);
}
if ((spinBLEClient.myBLEDevices[i].charUUID == HID_REPORT_DATA_UUID)) {
SS2K_LOG(BLE_CLIENT_LOG_TAG, "Deregistered Remote on Disconnect");
Expand Down Expand Up @@ -640,7 +638,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].isPostConnected && (myBLEDevices[i].serviceUUID == FITNESSMACHINESERVICE_UUID)) {
if (NimBLEDevice::getClientByPeerAddress(myBLEDevices[i].peerAddress)->getService(FITNESSMACHINESERVICE_UUID)) {
pClient = NimBLEDevice::getClientByPeerAddress(myBLEDevices[i].peerAddress);
break;
Expand Down Expand Up @@ -674,17 +672,18 @@ void SpinBLEClient::FTMSControlPointWrite(const uint8_t *pData, int length) {
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()) {
if ((_BLEd.connectedClientID != BLE_HS_CONN_HANDLE_NONE) && !_BLEd.isPostConnected) {
String adevName = this->adevName2UniqueName(_BLEd.advertisedDevice);
SS2K_LOG(BLE_CLIENT_LOG_TAG, "Post connecting: %s , ConnID %d, PrimaryChar %s", adevName.c_str(), _BLEd.connectedClientID, _BLEd.charUUID.toString().c_str());
NimBLEClient *pClient = NimBLEDevice::getClientByPeerAddress(_BLEd.peerAddress);
if (pClient) {
BLEDevice::getServer()->updateConnParams(pClient->getConnHandle(), connectionParams[0], connectionParams[1], connectionParams[2], connectionParams[3]);
_BLEd.setPostConnected(subscribeToAllNotifications(pClient));
if (!_BLEd.getPostConnected()) {
_BLEd.isPostConnected = subscribeToAllNotifications(pClient);
if (!_BLEd.isPostConnected) {
SS2K_LOG(BLE_CLIENT_LOG_TAG, "Failed to subscribe to notifications for %s", adevName.c_str());
return;
}
spinBLEClient.handleBattInfo(pClient, false);
if (_BLEd.charUUID == ECHELON_SERVICE_UUID) {
NimBLERemoteCharacteristic *writeCharacteristic = pClient->getService(ECHELON_SERVICE_UUID)->getCharacteristic(ECHELON_WRITE_UUID);
if (writeCharacteristic == nullptr) {
Expand Down Expand Up @@ -907,36 +906,20 @@ void SpinBLEClient::reconnectAllDevices() {
// Poll BLE devices for battCharacteristic if available and read value.
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) {
std::string value = battCharacteristic->readValue();
rtConfig->hr_batt.setValue((uint8_t)value[0]);
SS2K_LOG(BLE_CLIENT_LOG_TAG, "HRM battery updated %d", (int)value[0]);
} else {
rtConfig->hr_batt.setValue(0);
}
} else if ((pClient->getService(CYCLINGPOWERMEASUREMENT_UUID) || pClient->getService(CYCLINGPOWERSERVICE_UUID)) &&
pClient->getService(BATTERYSERVICE_UUID)) { // get batterylevel at first connect
BLERemoteCharacteristic *battCharacteristic = pClient->getService(BATTERYSERVICE_UUID)->getCharacteristic(BATTERYCHARACTERISTIC_UUID);
if (battCharacteristic != nullptr) {
std::string value = battCharacteristic->readValue();
rtConfig->pm_batt.setValue((uint8_t)value[0]);
SS2K_LOG(BLE_CLIENT_LOG_TAG, "PM battery updated %d", (int)value[0]);
} else {
rtConfig->pm_batt.setValue(0);
}
}
if (pClient->getService(BATTERYSERVICE_UUID) == nullptr) {
return;
}
if (pClient->getService(BATTERYSERVICE_UUID)->getCharacteristic(BATTERYCHARACTERISTIC_UUID) == nullptr) {
return;
}
BLERemoteCharacteristic *battCharacteristic = pClient->getService(BATTERYSERVICE_UUID)->getCharacteristic(BATTERYCHARACTERISTIC_UUID);
if (battCharacteristic != nullptr) {
std::string value = battCharacteristic->readValue();
rtConfig->batt.setValue((uint8_t)value[0]);
SS2K_LOG(BLE_CLIENT_LOG_TAG, "%s Battery updated %d", pClient->getPeerAddress().toString().c_str(), (int)value[0]);
}
}

// Helper function to detect if a BLE address is randomized (typically Android devices)
// Updated: only treat as randomized if it's a private random address (dynamic), not static random.
bool SpinBLEClient::isRandomizedAddress(const NimBLEAdvertisedDevice *inDev) {
Expand All @@ -950,8 +933,8 @@ bool SpinBLEClient::isRandomizedAddress(const NimBLEAdvertisedDevice *inDev) {
return false;
}

char firstByteStr[3] = { addrStr[0], addrStr[1], '\0' };
int msb = strtol(firstByteStr, nullptr, 16);
char firstByteStr[3] = {addrStr[0], addrStr[1], '\0'};
int msb = strtol(firstByteStr, nullptr, 16);
if (msb < 0) {
return false;
}
Expand Down Expand Up @@ -1078,28 +1061,37 @@ void SpinBLEAdvertisedDevice::set(const NimBLEAdvertisedDevice *device, int id,
* @param resetAdvertisedDevice If true, the advertised device reference will
* be set to nullptr.
*/
void SpinBLEAdvertisedDevice::clearState(bool resetAdvertisedDevice) {
if (resetAdvertisedDevice) {
advertisedDevice = nullptr;
peerAddress = NimBLEAddress(); // zero / cleared
}
connectedClientID = BLE_HS_CONN_HANDLE_NONE;
serviceUUID = (uint16_t)0x0000;
charUUID = (uint16_t)0x0000;
isHRM = false;
isPM = false;
isCSC = false;
isCT = false;
isRemote = false;
doConnect = false;
isPostConnected = false;
batt = Measurement();
if (dataBufferQueue) {
xQueueReset(dataBufferQueue); // safe to centralize
}
}

void SpinBLEAdvertisedDevice::reset(bool resetAdvertisedDevice) {
SS2K_LOG(BLE_CLIENT_LOG_TAG, "Resetting Device: %d", this->connectedClientID);
if (this->isHRM) spinBLEClient.connectedHRM = false;
if (this->isPM || this->isCSC) {

// Adjust global flags BEFORE clearing local flags.
if (isHRM) spinBLEClient.connectedHRM = false;
if (isPM || isCSC) {
spinBLEClient.connectedPM = false;
spinBLEClient.connectedCD = false;
spinBLEClient.connectedSpeed = false;
}
if (resetAdvertisedDevice) advertisedDevice = nullptr;
// NimBLEAddress peerAddress;
this->connectedClientID = BLE_HS_CONN_HANDLE_NONE;
this->serviceUUID = (uint16_t)0x0000;
this->charUUID = (uint16_t)0x0000;
this->isHRM = false; // Heart Rate Monitor
this->isPM = false; // Power Meter
this->isCSC = false; // Cycling Speed/Cadence
this->isCT = false; // Controllable Trainer
this->isRemote = false; // BLE Remote
this->doConnect = false; // Initiate connection flag
this->isPostConnected = false; // Has Post Connect Been Run?
if (this->dataBufferQueue != nullptr) {
// Serial.println("Resetting queue");
xQueueReset(this->dataBufferQueue);
}

clearState(resetAdvertisedDevice);
}
Loading
Loading