Skip to content

Commit 8f9576b

Browse files
committed
fix(ble): restructure advertising and ensure spec compliance for Garmin compatibility
- Restructured BLE advertising to prioritize the Cycling Power Service (CPS) UUID (0x1818) in the primary packet, mimicking commercial power meters to bypass strict Garmin discovery filters. - Added BLE Appearance (0x0484) to explicitly identify the device as a Cycling Power Sensor. - Moved the SmartSpin2k custom service UUID to the scan response to prevent advertising packet overflow and reduce noise for central scanners. - Updated the Cycling Power Measurement characteristic to NOTIFY only to comply with the Bluetooth CPP v1.1 specification. - Forced a public MAC address type to ensure stable pairing with Garmin watches that reject random resolvable addresses.
1 parent 4db14df commit 8f9576b

2 files changed

Lines changed: 15 additions & 16 deletions

File tree

src/BLE_Cycling_Power_Service.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ BLE_Cycling_Power_Service::BLE_Cycling_Power_Service() : pPowerMonitor(nullptr),
1212
void BLE_Cycling_Power_Service::setupService(NimBLEServer *pServer, MyCharacteristicCallbacks *chrCallbacks) {
1313
// Power Meter service setup
1414
pPowerMonitor = spinBLEServer.pServer->createService(CYCLINGPOWERSERVICE_UUID);
15-
cyclingPowerMeasurementCharacteristic = pPowerMonitor->createCharacteristic(CYCLINGPOWERMEASUREMENT_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
15+
cyclingPowerMeasurementCharacteristic = pPowerMonitor->createCharacteristic(CYCLINGPOWERMEASUREMENT_UUID, NIMBLE_PROPERTY::NOTIFY);
1616
cyclingPowerFeatureCharacteristic = pPowerMonitor->createCharacteristic(CYCLINGPOWERFEATURE_UUID, NIMBLE_PROPERTY::READ);
1717
sensorLocationCharacteristic = pPowerMonitor->createCharacteristic(SENSORLOCATION_UUID, NIMBLE_PROPERTY::READ);
1818
byte cpsLocation[1] = {0b0101}; // sensor location 5 == left crank

src/BLE_Server.cpp

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,32 +45,31 @@ void startBLEServer() {
4545
pAdvertising->enableScanResponse(true);
4646
NimBLEAdvertisementData oScanResponseData;
4747
NimBLEAdvertisementData oAdvertisementData;
48-
std::vector<NimBLEUUID> oServiceUUIDs;
49-
oScanResponseData.setFlags(0x06); // General Discoverable, BR/EDR Not Supported
50-
oScanResponseData.setCompleteServices(SMARTSPIN2K_SERVICE_UUID);
5148
cyclingSpeedCadenceService.setupService(spinBLEServer.pServer, &chrCallbacks);
5249
cyclingPowerService.setupService(spinBLEServer.pServer, &chrCallbacks);
5350
heartService.setupService(spinBLEServer.pServer, &chrCallbacks);
5451
fitnessMachineService.setupService(spinBLEServer.pServer, &chrCallbacks);
5552
ss2kCustomCharacteristic.setupService(spinBLEServer.pServer);
5653
deviceInformationService.setupService(spinBLEServer.pServer);
57-
// add all service UUIDs to advertisement vector
58-
oServiceUUIDs.push_back(CSCSERVICE_UUID);
59-
oServiceUUIDs.push_back(CYCLINGPOWERSERVICE_UUID);
60-
oServiceUUIDs.push_back(HEARTSERVICE_UUID);
61-
oServiceUUIDs.push_back(FITNESSMACHINESERVICE_UUID);
62-
oAdvertisementData.setFlags(0x06); // General Discoverable, BR/EDR Not Supported
63-
oAdvertisementData.setAppearance(0x0484); // Cycling: Power Sensor - required for Garmin discovery
64-
oAdvertisementData.setCompleteServices16(oServiceUUIDs);
54+
55+
// Primary advertising packet: mimic real power meters (CPS UUID + appearance only)
56+
// Garmin's power meter scanner requires CPS as the sole/primary advertised service.
57+
// Other services (HRM, CSC, FTMS) are still discoverable via GATT after connection.
58+
std::vector<NimBLEUUID> primaryServiceUUIDs;
59+
primaryServiceUUIDs.push_back(CYCLINGPOWERSERVICE_UUID);
60+
oAdvertisementData.setFlags(0x06); // General Discoverable, BR/EDR Not Supported
61+
oAdvertisementData.setAppearance(0x0484); // Cycling: Power Sensor
62+
oAdvertisementData.setCompleteServices16(primaryServiceUUIDs);
63+
oAdvertisementData.setName(userConfig->getDeviceName()); // Include name in primary packet
6564
pAdvertising->setAdvertisementData(oAdvertisementData);
65+
66+
// Scan response: custom service UUID for SS2K app discovery
67+
oScanResponseData.setCompleteServices(SMARTSPIN2K_SERVICE_UUID);
6668
pAdvertising->setScanResponseData(oScanResponseData);
69+
6770
// wattbikeService.setupService(spinBLEServer.pServer); // No callback needed
6871
// sb20Service.begin();
6972
BLEFirmwareSetup(spinBLEServer.pServer);
70-
71-
// const std::string fitnessData = {0b00000001, 0b00100000, 0b00000000};
72-
// pAdvertising->setServiceData(FITNESSMACHINESERVICE_UUID, fitnessData);
73-
pAdvertising->setName(userConfig->getDeviceName());
7473
pAdvertising->setMaxInterval(250);
7574
pAdvertising->setMinInterval(160);
7675
pAdvertising->start();

0 commit comments

Comments
 (0)