Closed BigJinge closed 5 years ago
Realised that extending the array for cscmeasurement to add extra bytes would update when the notify was posted and it did.
Have emulated the same array figures as a stock retail cadence sensor and checking on the LightBlue BLE scanner, the ESP32 BLE is outputting the same as the stock retail one. However, the stock one is visible in Zwift and Wahoo Fitness apps and the ESP32 sensor is not, but that's a different problem.
Has this every been resolved? I am running into the same issue.
I assume you mean that the array you extended was: byte cscmeasurement[3] = {0b00000010, 0, 0};
From what I can gather the cscmeasurement is of variable length depending on whether you are including wheel rotation and/or crank rotation data (wheel rotation data uint32, crank data uint16). Did you end up always including both (so that array size constant) or did you dynamically alter the size of the cscmeasurement data depending on whether you were sending one or the other or both?
by the way, would it be possible for you to share your final code?
thanks, Alan
This version seems to work OK for me:
/*
Multi BLE Sensor - Richard Hedderly 2019
Based on heart sensor code by Andreas Spiess which was based on a Neil
Kolban example.
Based on Neil Kolban example for IDF:
https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp
Ported to Arduino ESP32 by Evandro Copercini
updates by chegewara
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
uint16_t crankrev; // Cadence RPM
uint16_t lastcrank; // Last crank time
uint32_t wheelrev; // Wheel revolutions
uint16_t lastwheel; // Last crank time
uint16_t cadence;
byte speedkph; // Speed in KPH
byte speedmph; // Speed in MPH
byte cscmeasurement[11] = { 0b00000011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
byte cscfeature[1] = { 0b0000000000000010 };
byte sensorlocation[1] = { 6 };
bool _BLEClientConnected = false;
//0x2901 is a custom user description
// Define Speed and Cadence Properties
#define speedService BLEUUID ((uint16_t) 0x1816)
BLECharacteristic cscMeasurementCharacteristics( BLEUUID ((uint16_t) 0x2A5B), BLECharacteristic::PROPERTY_NOTIFY); //CSC Measurement Characteristic
BLECharacteristic cscFeatureCharacteristics( BLEUUID ((uint16_t) 0x2A5C), BLECharacteristic::PROPERTY_READ); //CSC Feature Characteristic
BLECharacteristic sensorLocationCharacteristics( BLEUUID ((uint16_t) 0x2A5D), BLECharacteristic::PROPERTY_READ); //Sensor Location Characteristic
BLECharacteristic scControlPointCharacteristics( BLEUUID ((uint16_t) 0x2A55), BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_INDICATE); // SC Control Point Characteristic
//0x2901 is a custom user description
BLEDescriptor cscMeasurementDescriptor( BLEUUID ((uint16_t) 0x2901));
BLEDescriptor cscFeatureDescriptor( BLEUUID ((uint16_t) 0x2901));
BLEDescriptor sensorLocationDescriptor( BLEUUID ((uint16_t) 0x2901));
BLEDescriptor scControlPointDescriptor( BLEUUID ((uint16_t) 0x2901));
// 0x2A5B - Notify - Client Charactaristic Config is 1 - DONE : CSC
// Measurement
// 0x2A5C - Read - 0x200 - DONE : CSC Feature
// 0x2A5D - Read - None or 0x06 when active - Done : Sensor Location - 6
// is right crank
// 0x2A55 - Write Indicate - Client Characteristic Config is 2 : SC
// Control Point
// Define Battery
//Servicec UUID 0x180F - Battery Level UUID 0x2A19
// Define Firmware
//UUID 0x2A26
class MyServerCallbacks:public BLEServerCallbacks
{
void onConnect (BLEServer * pServer)
{
_BLEClientConnected = true;
};
void onDisconnect (BLEServer * pServer)
{
_BLEClientConnected = false;
}
};
void InitBLE ()
{
// Create BLE Device
BLEDevice::init ("Multi Fitness BLE Sensor");
// Create BLE Server
BLEServer *pServer = BLEDevice::createServer ();
pServer->setCallbacks( new MyServerCallbacks ());
// Create Speed and Cadence Configuration
BLEService *pSpeed = pServer->createService(speedService);
// Create Speed and Cadence Service
pSpeed->addCharacteristic( &cscMeasurementCharacteristics);
pSpeed->addCharacteristic( &cscFeatureCharacteristics);
pSpeed->addCharacteristic( &sensorLocationCharacteristics);
pSpeed->addCharacteristic( &scControlPointCharacteristics);
cscMeasurementDescriptor.setValue( "Exercise Bike CSC Measurement");
cscMeasurementCharacteristics.addDescriptor( &cscMeasurementDescriptor);
cscMeasurementCharacteristics.addDescriptor( new BLE2902());
cscFeatureDescriptor.setValue ("Exercise Bike CSC Feature");
cscFeatureCharacteristics.addDescriptor (&cscFeatureDescriptor);
sensorLocationDescriptor.setValue( "Exercise Bike CSC Sensor Location");
sensorLocationCharacteristics.addDescriptor( &sensorLocationDescriptor);
scControlPointDescriptor.setValue( "Exercise Bike CSC SC Control Point");
scControlPointCharacteristics.addDescriptor( &scControlPointDescriptor);
// Add UUIDs for Services to BLE Service Advertising
pServer->getAdvertising()->addServiceUUID( speedService);
// Start p Instances
pSpeed->start();
// Start Advertising
pServer->getAdvertising()->start();
}
void setup ()
{
Serial.begin(115200);
Serial.println("Start");
InitBLE();
crankrev = 0;
lastcrank = 0;
wheelrev = 0;
lastwheel = 0;
}
void loop ()
{
// _______________________
// SPEED + CADENCE SECTION
// -----------------------
// Read Cadence RPM Pins
cadence = 50; // Some arbitrary test value
if (cadence == 0) {
cadence = 1;
}
crankrev = crankrev + 1;
lastcrank = lastcrank + 1024*60/cadence;
wheelrev = wheelrev + 1;
lastwheel = lastwheel + 1024*60/cadence;
// That, lastcran in the array position 2 will only show FF or 256 even
// if lastcrank is set to a uint16_t
// Calculate Speed from cadence
//
// Set and Send Speed and Cadence Notify
cscmeasurement[1] = wheelrev & 0xFF;
cscmeasurement[2] = (wheelrev >> 8) & 0xFF;
cscmeasurement[3] = (wheelrev >> 16) & 0xFF;
cscmeasurement[4] = (wheelrev >> 24) & 0xFF;
cscmeasurement[5] = lastwheel & 0xFF;
cscmeasurement[6] = (lastwheel >> 8) & 0xFF;
cscmeasurement[7] = crankrev & 0xFF;
cscmeasurement[8] = (crankrev >> 8) & 0xFF;
cscmeasurement[9] = lastcrank & 0xFF;
cscmeasurement[10] = (lastcrank >> 8) & 0xFF;
cscMeasurementCharacteristics.setValue(cscmeasurement, 11);
cscMeasurementCharacteristics.notify();
cscFeatureCharacteristics.setValue(cscfeature, 1);
sensorLocationCharacteristics.setValue(sensorlocation, 1);
Serial.print("crankrev = ");
Serial.println(crankrev);
Serial.print("lastcrank = ");
Serial.println(lastcrank);
Serial.print(cscmeasurement[0]); //Should be 2 to reflect binary - Yes
Serial.print(" ");
Serial.print(cscmeasurement[1]); // Should increment by 2 - Yes
Serial.print(" ");
Serial.print(cscmeasurement[2]);
Serial.print(" ");
Serial.print(cscmeasurement[3]);
Serial.print(" ");
Serial.print(cscmeasurement[4]);
Serial.print(" ");
Serial.print(cscmeasurement[5]);
Serial.print(" ");
Serial.print(cscmeasurement[6]);
Serial.print(" ");
Serial.print(cscmeasurement[7]);
Serial.print(" ");
Serial.print(cscmeasurement[8]);
Serial.print(" ");
Serial.print(cscmeasurement[9]);
Serial.print(" ");
Serial.print(cscmeasurement[10]);
Serial.print(" ");
Serial.print(cscmeasurement[11]);
Serial.println(" ");
delay(2000);
}
Thanks. I was sort of able to get mine to work but the speed bike computer app displays does not make sense (way too fast). You have interpreted the lastwheel differently than I have. I am passing a value that that varies between 250 and 1000 (gradually increase then decrease to simulate changes in speed). You are actually adding 1024 each time. It seems you would quickly go over the max uint16 value (about 65,535). Are you able to get a bike app (wahoo or Zwift) to show an appropriate speed?
Thanks, AGS
Sent from my iPhone
On Apr 19, 2020, at 2:17 PM, verjus notifications@github.com wrote:
This version seems to work OK for me:
/* Multi BLE Sensor - Richard Hedderly 2019
Based on heart sensor code by Andreas Spiess which was based on a Neil Kolban example.
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp Ported to Arduino ESP32 by Evandro Copercini updates by chegewara
*/
include
include
include
include
uint16_t crankrev; // Cadence RPM uint16_t lastcrank; // Last crank time
uint32_t wheelrev; // Wheel revolutions uint16_t lastwheel; // Last crank time
byte speedkph; // Speed in KPH byte speedmph; // Speed in MPH byte cscmeasurement[11] = { 0b00000011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; byte cscfeature[1] = { 0b0000000000000010 }; byte sensorlocation[1] = { 6 };
bool _BLEClientConnected = false;
//0x2901 is a custom user description // Define Speed and Cadence Properties
define speedService BLEUUID ((uint16_t) 0x1816)
BLECharacteristic cscMeasurementCharacteristics( BLEUUID ((uint16_t) 0x2A5B), BLECharacteristic::PROPERTY_NOTIFY); //CSC Measurement Characteristic BLECharacteristic cscFeatureCharacteristics( BLEUUID ((uint16_t) 0x2A5C), BLECharacteristic::PROPERTY_READ); //CSC Feature Characteristic BLECharacteristic sensorLocationCharacteristics( BLEUUID ((uint16_t) 0x2A5D), BLECharacteristic::PROPERTY_READ); //Sensor Location Characteristic BLECharacteristic scControlPointCharacteristics( BLEUUID ((uint16_t) 0x2A55), BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_INDICATE); // SC Control Point Characteristic
//0x2901 is a custom user description BLEDescriptor cscMeasurementDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor cscFeatureDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor sensorLocationDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor scControlPointDescriptor( BLEUUID ((uint16_t) 0x2901));
// 0x2A5B - Notify - Client Charactaristic Config is 1 - DONE : CSC // Measurement // 0x2A5C - Read - 0x200 - DONE : CSC Feature // 0x2A5D - Read - None or 0x06 when active - Done : Sensor Location - 6 // is right crank // 0x2A55 - Write Indicate - Client Characteristic Config is 2 : SC // Control Point
// Define Battery //Servicec UUID 0x180F - Battery Level UUID 0x2A19
// Define Firmware //UUID 0x2A26
class MyServerCallbacks:public BLEServerCallbacks { void onConnect (BLEServer * pServer) { _BLEClientConnected = true; };
void onDisconnect (BLEServer * pServer) { _BLEClientConnected = false; }
};
void InitBLE () { // Create BLE Device BLEDevice::init ("Multi Fitness BLE Sensor");
// Create BLE Server BLEServer *pServer = BLEDevice::createServer (); pServer->setCallbacks( new MyServerCallbacks ());
// Create Speed and Cadence Configuration BLEService *pSpeed = pServer->createService(speedService);
// Create Speed and Cadence Service pSpeed->addCharacteristic( &cscMeasurementCharacteristics); pSpeed->addCharacteristic( &cscFeatureCharacteristics); pSpeed->addCharacteristic( &sensorLocationCharacteristics); pSpeed->addCharacteristic( &scControlPointCharacteristics);
cscMeasurementDescriptor.setValue( "Exercise Bike CSC Measurement"); cscMeasurementCharacteristics.addDescriptor( &cscMeasurementDescriptor); cscMeasurementCharacteristics.addDescriptor( new BLE2902());
cscFeatureDescriptor.setValue ("Exercise Bike CSC Feature"); cscFeatureCharacteristics.addDescriptor (&cscFeatureDescriptor);
sensorLocationDescriptor.setValue( "Exercise Bike CSC Sensor Location"); sensorLocationCharacteristics.addDescriptor( &sensorLocationDescriptor);
scControlPointDescriptor.setValue( "Exercise Bike CSC SC Control Point"); scControlPointCharacteristics.addDescriptor( &scControlPointDescriptor);
// Add UUIDs for Services to BLE Service Advertising pServer->getAdvertising()->addServiceUUID( speedService);
// Start p Instances pSpeed->start();
// Start Advertising pServer->getAdvertising()->start(); }
void setup () { Serial.begin(115200); Serial.println("Start"); InitBLE(); crankrev = 0; lastcrank = 0; wheelrev = 0; lastwheel = 0;
}
void loop () { // ___ // SPEED + CADENCE SECTION // ----------------------- // Read Cadence RPM Pins
crankrev = crankrev + 1; lastcrank = lastcrank + 1024;
wheelrev = crankrev + 1; lastwheel = lastwheel + 1024;
// That, lastcran in the array position 2 will only show FF or 256 even // if lastcrank is set to a uint16_t
// Calculate Speed from cadence //
// Set and Send Speed and Cadence Notify cscmeasurement[1] = wheelrev & 0xFF; cscmeasurement[2] = (wheelrev >> 8) & 0xFF;
cscmeasurement[3] = (wheelrev >> 16) & 0xFF; cscmeasurement[4] = (wheelrev >> 24) & 0xFF;
cscmeasurement[5] = lastwheel & 0xFF; cscmeasurement[6] = (lastwheel >> 8) & 0xFF;
cscmeasurement[7] = crankrev & 0xFF; cscmeasurement[8] = (crankrev >> 8) & 0xFF;
cscmeasurement[9] = lastcrank & 0xFF; cscmeasurement[10] = (lastcrank >> 8) & 0xFF;
cscMeasurementCharacteristics.setValue(cscmeasurement, 11); cscMeasurementCharacteristics.notify();
cscFeatureCharacteristics.setValue(cscfeature, 1); sensorLocationCharacteristics.setValue(sensorlocation, 1);
Serial.print("crankrev = "); Serial.println(crankrev); Serial.print("lastcrank = "); Serial.println(lastcrank);
Serial.print(cscmeasurement[0]); //Should be 2 to reflect binary - Yes Serial.print(" "); Serial.print(cscmeasurement[1]); // Should increment by 2 - Yes Serial.print(" "); Serial.print(cscmeasurement[2]); Serial.print(" "); Serial.print(cscmeasurement[3]); Serial.print(" "); Serial.print(cscmeasurement[4]); Serial.print(" "); Serial.print(cscmeasurement[5]); Serial.print(" "); Serial.print(cscmeasurement[6]); Serial.print(" "); Serial.print(cscmeasurement[7]); Serial.print(" "); Serial.print(cscmeasurement[8]); Serial.print(" "); Serial.print(cscmeasurement[9]); Serial.print(" "); Serial.print(cscmeasurement[10]); Serial.print(" "); Serial.print(cscmeasurement[11]); Serial.println(" "); delay(2000); }
— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.
I also have a somewhat working version and can confirm that it plays okay with Zwift (at least on Android). My Implementation sends both power as well as Speed/Cadence.
I will try to clean up my code so I can post it here as well.
Sorry, this was a test version that focused on the Endian representation and passed a fixed implied cadence. If you want to pass a real cadence, you have to use the Bluetooth definition of CSC. The first byte can take values 1,2 or 3 because it is Flags field and the first two bits indicate whether the value contains wheel revolution data, crank revolution data or both. Wheel revolution data, if present, consists of a 32-bit cumulative count of revolutions of the wheel plus a 16-bit value which represents the time the last wheel event was measured in units of 1/1024 of a second.
Crank data, if present consists of a 16-bit cumulative count of revolutions of the crank plus a similar, 16-bit last event time field.
So the formula for cadence is: Cadence(i+1)= ( Cumulative crank(i+1) - Cumulative crank(i) )/(Event Time(i+1) - Event Time(i)) 1024 60
So you need to invert that formula. For my application, I chose an arbitrary and fixed delta cumulative crank set to 1. So the formula becomes: Cadence = 102460/(Event Time(i+1) - Event Time(i)). So in order to send the cadence, I invert the formula: Delta T = 102460/cadence.
I've updated the code with the correct computation. As for you question about overflow of the uint16_t, I had the same concern but it turns out that since the server only uses the current and last cumulative crank revolutions, when an overflow occurs the uint16_t just wraps around and the computation still seems to work. The 60 comes from the conversion to minutes, btw.
@Avriox, I would love to see your implementation using the power characteristics as I discovered that it is the one I should have used instead of the CSC, since the power one contains both instantaneous power and revolution data (cadence).
Hope this helps, I have not fully checked the code above. I just used it to test the BLE stack on ESP32 (compared to btstack).
So I just want to be sure I understand correctly the data value it is expecting for the lastcrank or lastwheel events. As noted above your code ADDS the time since lastcrank (or lastwheel) in a way that looks like a cumulative or running total of time rather than as simply passing the time interval between the last two revolutions. Assume for simplicity sake I am going to pass it cadence data and the crank is spinning 60 RPM (once per second). The BLE specifications are expecting a value in 1024's of a second so each revolution the time since last revolution has been 1 second (1024/1024's of a second). Data structure (specifies least significant bit first)byte 1: flag: 0x02 (only sending cadence data)byte 2: low byte value for cumulative revolutionsbyte 3: high byte value for cumulative revolutionsbyte 4: low byte value for lastCrankEventbyte 5: high byte value for lastCrankEvent so for the first 5 data sends it would be incrementing the cumulative revolutions by 1 and the lastcrank by 1024:0x02 0x01 0x00 0x00 0x040x02 0x02 0x00 0x00 0x080x02 0x03 0x00 0x00 0x0c0x02 0x04 0x00 0x00 0x100x02 0x05 0x00 0x00 0x14 I am assuming there is no calculation of cadence or velocity done prior to passing value, it is all done at the receiving (client's) end so the formula you stated makes sense if I'm writing a client app but if I'm passing to an already built biking app it will do the calculation at its end. I will try this tonight (or sometime this week). My assumption had been that I am always only passing the time elapsed since the lastcrank event so my data would look like this (note only the cumulative wheel revolutions are changing) and that the client was calculating cadence (or velocity) based simply on most recent period of revolution (which in this case is constant at 1024/1024's of a second).0x02 0x01 0x00 0x00 0x04 0x02 0x02 0x00 0x00 0x04 0x02 0x03 0x00 0x00 0x04 0x02 0x04 0x00 0x00 0x04 0x02 0x05 0x00 0x00 0x04
AGS
On Monday, April 20, 2020, 06:22:46 AM HST, verjus <notifications@github.com> wrote:
Sorry, this was a test version that focused on the Endian representation and passed a fixed implied cadence. If you want to pass a real cadence, you have to use the Bluetooth definition of CSC. The first byte can take values 1,2 or 3 because it is Flags field and the first two bits indicate whether the value contains wheel revolution data, crank revolution data or both. Wheel revolution data, if present, consists of a 32-bit cumulative count of revolutions of the wheel plus a 16-bit value which represents the time the last wheel event was measured in units of 1/1024 of a second.
Crank data, if present consists of a 16-bit cumulative count of revolutions of the crank plus a similar, 16-bit last event time field.
So the formula for cadence is: Cadence(i+1)= ( Cumulative crank(i+1) - Cumulative crank(i) )/(Event Time(i+1) - Event Time(i)) 1024 60
So you need to invert that formula. For my application, I chose an arbitrary and fixed delta cumulative crank set to 1. So the formula becomes: Cadence = 102460/(Event Time(i+1) - Event Time(i)). So in order to send the cadence, I invert the formula: Delta T = 102460/cadence.
I've updated the code with the correct computation. As for you question about overflow of the uint16_t, I had the same concern but it turns out that since the server only uses the current and last cumulative crank revolutions, when an overflow occurs the uint16_t just wraps around and the computation still seems to work. The 60 comes from the conversion to minutes, btw.
@Avriox, I would love to see your implementation using the power characteristics as I discovered that it is the one I should have used instead of the CSC, since the power one contains both instantaneous power and revolution data (cadence).
Hope this helps, I have not fully checked the code above. I just used it to test the BLE stack on ESP32 (compared to btstack).
— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.
Correct, all expected values are cumulative for crank and elapsed for time. The client is expected to calculate the cadence based on last event time and last cumulative cranks and previous ones, which I assume it has to store in memory.
I have a similar use case to yours where I get the cadence from another BLE format and I relay it along in the CSC standard. It works with SufferFest in IOS and Zwift in Android. It is annoying that the cadence can't be passed directly but that's how the ble CSC service was written. Note that the same goes for speed, using the wheel revolutions. Wheel revolution with change with respect to crank revolution depending on selected gear. However, the power can be passed directly in the cycling power service.
Thanks so much for your insight. Turns out I was doing everything right EXCEPT how to report out the lastWheel or lastCrank events. I have been scouring the interwebs for weeks looking for insight on the data reporting for cscmeasurements and nowhere did I see anything to suggest that I would cumulate the lastWheel intervals (rather than just report out the interval since last event). After all, the data is labeled
Here is my code in case anyone else is interested. I haven't tried expanding yet to to reporting full speed and cadence but I would imagine it should be pretty straightforward.
/*
Very basic bluetooth speed sensor, primary purpose is to work out data structures and notify and ble commands
basic structure copied from https://github.com/nkolban/esp32-snippets/issues/844
Multi BLE Sensor - Richard Hedderly 2019
Based on heart sensor code by Andreas Spiess which was based on a Neil Kolban example.
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp
Ported to Arduino ESP32 by Evandro Copercini
updates by chegewara
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
//declare a union of type csc_data which points to a struct with data appropriate types
//Note that the ESP32 appears to pad the first element of the array so it is easier to store data in separate unions and then transcribe byte array elements
union csc_data{
byte cscValues[7];
};
union csc_cumWheelRev{
struct{
uint32_t cumWheelRev;
};
byte cumWheelBytes[4];
};
union csc_lastWheelEvent{
struct{
uint16_t lastWheelEvent;
};
byte lastWheelBytes[2];
};
//instantiate unions
union csc_data myCSCdata;
union csc_cumWheelRev myWheelRev;
union csc_lastWheelEvent myWheelEvent;
//global variables for simulation
int simulationIncrement = 1024;
int simulationPlusMinus = 1;
int cycleCounter = 0;
int lastValue = 0;
//set cscfeature flag to support both crank and wheel data (don't need to report both but device should support both
byte cscfeature[1] = {0b0000000000000011};
bool _BLEClientConnected = false;
// Define Speed and Cadence Properties
#define speedService BLEUUID((uint16_t)0x1816)
BLECharacteristic cscMeasurementCharacteristics(BLEUUID((uint16_t)0x2A5B), BLECharacteristic::PROPERTY_NOTIFY); //CSC Measurement Characteristic
BLECharacteristic cscFeatureCharacteristics(BLEUUID((uint16_t)0x2A5C), BLECharacteristic::PROPERTY_READ); //CSC Feature Characteristic
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
_BLEClientConnected = true;
};
void onDisconnect(BLEServer* pServer) {
_BLEClientConnected = false;
}
};
void InitBLE() {
// Create BLE Device
BLEDevice::init("Bike speed test");
// Create BLE Server
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create Speed and Cadence Configuration
BLEService *pSpeed = pServer->createService(speedService); // Create Speed and Cadence Service
pSpeed->addCharacteristic(&cscMeasurementCharacteristics);
pSpeed->addCharacteristic(&cscFeatureCharacteristics);
//cscMeasurementCharacteristics.addDescriptor(new BLE2902());
// Add UUIDs for Services to BLE Service Advertising
pServer->getAdvertising()->addServiceUUID(speedService);
// Start p Instances
pSpeed->start();
// Start Advertising
pServer->getAdvertising()->start();
}
void setup() {
Serial.begin(115200);
Serial.println("Start");
InitBLE();
myCSCdata.cscValues[0] = 1; //initialize flags to indicate will wheel data
myWheelRev.cumWheelRev = 0;
myWheelEvent.lastWheelEvent = 1024;
}
void loop() {
//to simulate....
//increment data (simuulated data)
myWheelRev.cumWheelRev++;
cycleCounter += simulationPlusMinus;
if (simulationIncrement > 1023){
simulationPlusMinus = -1;
}
if (simulationIncrement < 257){
simulationPlusMinus = 1;
}
simulationIncrement = simulationIncrement + (50*simulationPlusMinus);
myWheelEvent.lastWheelEvent = myWheelEvent.lastWheelEvent + simulationIncrement;
//transcribe the byte values from the wheelrev and wheelevent to the cscValues byte array
myCSCdata.cscValues[1] = myWheelRev.cumWheelBytes[0];
myCSCdata.cscValues[2] = myWheelRev.cumWheelBytes[1];
myCSCdata.cscValues[3] = myWheelRev.cumWheelBytes[2];
myCSCdata.cscValues[4] = myWheelRev.cumWheelBytes[3];
myCSCdata.cscValues[5] = myWheelEvent.lastWheelBytes[0];
myCSCdata.cscValues[6] = myWheelEvent.lastWheelBytes[1];
// Set and Send Speed and Cadence Notify
cscMeasurementCharacteristics.setValue(myCSCdata.cscValues, 7);
cscMeasurementCharacteristics.notify();
// cscFeatureCharacteristics.setValue(cscfeature, 1);
//debug print statements to view data being reported
Serial.print(myCSCdata.cscValues[0]);
Serial.print(" ");
Serial.print(myCSCdata.cscValues[1]);
Serial.print(" ");
Serial.print(myCSCdata.cscValues[2]);
Serial.print(" ");
Serial.print(myCSCdata.cscValues[3]);
Serial.print(" ");
Serial.print(myCSCdata.cscValues[4]);
Serial.print(" ");
Serial.print(myCSCdata.cscValues[5]);
Serial.print(" ");
Serial.print(myCSCdata.cscValues[6]);
Serial.print(" ");
Serial.print(myWheelEvent.lastWheelEvent);
Serial.print(" (");
Serial.print(myWheelEvent.lastWheelEvent - lastValue);
Serial.println(" more than last value)");
lastValue = myWheelEvent.lastWheelEvent;
delay(1000);
}
hello friend, I have simple trainer, esp32 and proximity sensor. I want to connect to zwift. I tried this code and it working. But there is little problem about speed. I can read my cadance but speed is wrong.
Bu sürüm benim için iyi çalışıyor gibi görünüyor:
/* Multi BLE Sensor - Richard Hedderly 2019 Based on heart sensor code by Andreas Spiess which was based on a Neil Kolban example. Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp Ported to Arduino ESP32 by Evandro Copercini updates by chegewara */ #include <BLEDevice.h> #include <BLEUtils.h> #include <BLEServer.h> #include <BLE2902.h> uint16_t crankrev; // Cadence RPM uint16_t lastcrank; // Last crank time uint32_t wheelrev; // Wheel revolutions uint16_t lastwheel; // Last crank time uint16_t cadence; byte speedkph; // Speed in KPH byte speedmph; // Speed in MPH byte cscmeasurement[11] = { 0b00000011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; byte cscfeature[1] = { 0b0000000000000010 }; byte sensorlocation[1] = { 6 }; bool _BLEClientConnected = false; //0x2901 is a custom user description // Define Speed and Cadence Properties #define speedService BLEUUID ((uint16_t) 0x1816) BLECharacteristic cscMeasurementCharacteristics( BLEUUID ((uint16_t) 0x2A5B), BLECharacteristic::PROPERTY_NOTIFY); //CSC Measurement Characteristic BLECharacteristic cscFeatureCharacteristics( BLEUUID ((uint16_t) 0x2A5C), BLECharacteristic::PROPERTY_READ); //CSC Feature Characteristic BLECharacteristic sensorLocationCharacteristics( BLEUUID ((uint16_t) 0x2A5D), BLECharacteristic::PROPERTY_READ); //Sensor Location Characteristic BLECharacteristic scControlPointCharacteristics( BLEUUID ((uint16_t) 0x2A55), BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_INDICATE); // SC Control Point Characteristic //0x2901 is a custom user description BLEDescriptor cscMeasurementDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor cscFeatureDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor sensorLocationDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor scControlPointDescriptor( BLEUUID ((uint16_t) 0x2901)); // 0x2A5B - Notify - Client Charactaristic Config is 1 - DONE : CSC // Measurement // 0x2A5C - Read - 0x200 - DONE : CSC Feature // 0x2A5D - Read - None or 0x06 when active - Done : Sensor Location - 6 // is right crank // 0x2A55 - Write Indicate - Client Characteristic Config is 2 : SC // Control Point // Define Battery //Servicec UUID 0x180F - Battery Level UUID 0x2A19 // Define Firmware //UUID 0x2A26 class MyServerCallbacks:public BLEServerCallbacks { void onConnect (BLEServer * pServer) { _BLEClientConnected = true; }; void onDisconnect (BLEServer * pServer) { _BLEClientConnected = false; } }; void InitBLE () { // Create BLE Device BLEDevice::init ("Multi Fitness BLE Sensor"); // Create BLE Server BLEServer *pServer = BLEDevice::createServer (); pServer->setCallbacks( new MyServerCallbacks ()); // Create Speed and Cadence Configuration BLEService *pSpeed = pServer->createService(speedService); // Create Speed and Cadence Service pSpeed->addCharacteristic( &cscMeasurementCharacteristics); pSpeed->addCharacteristic( &cscFeatureCharacteristics); pSpeed->addCharacteristic( &sensorLocationCharacteristics); pSpeed->addCharacteristic( &scControlPointCharacteristics); cscMeasurementDescriptor.setValue( "Exercise Bike CSC Measurement"); cscMeasurementCharacteristics.addDescriptor( &cscMeasurementDescriptor); cscMeasurementCharacteristics.addDescriptor( new BLE2902()); cscFeatureDescriptor.setValue ("Exercise Bike CSC Feature"); cscFeatureCharacteristics.addDescriptor (&cscFeatureDescriptor); sensorLocationDescriptor.setValue( "Exercise Bike CSC Sensor Location"); sensorLocationCharacteristics.addDescriptor( &sensorLocationDescriptor); scControlPointDescriptor.setValue( "Exercise Bike CSC SC Control Point"); scControlPointCharacteristics.addDescriptor( &scControlPointDescriptor); // Add UUIDs for Services to BLE Service Advertising pServer->getAdvertising()->addServiceUUID( speedService); // Start p Instances pSpeed->start(); // Start Advertising pServer->getAdvertising()->start(); } void setup () { Serial.begin(115200); Serial.println("Start"); InitBLE(); crankrev = 0; lastcrank = 0; wheelrev = 0; lastwheel = 0; } void loop () { // _______________________ // SPEED + CADENCE SECTION // ----------------------- // Read Cadence RPM Pins cadence = 50; // Some arbitrary test value if (cadence == 0) { cadence = 1; } crankrev = crankrev + 1; lastcrank = lastcrank + 1024*60/cadence; wheelrev = wheelrev + 1; lastwheel = lastwheel + 1024*60/cadence; // That, lastcran in the array position 2 will only show FF or 256 even // if lastcrank is set to a uint16_t // Calculate Speed from cadence // // Set and Send Speed and Cadence Notify cscmeasurement[1] = wheelrev & 0xFF; cscmeasurement[2] = (wheelrev >> 8) & 0xFF; cscmeasurement[3] = (wheelrev >> 16) & 0xFF; cscmeasurement[4] = (wheelrev >> 24) & 0xFF; cscmeasurement[5] = lastwheel & 0xFF; cscmeasurement[6] = (lastwheel >> 8) & 0xFF; cscmeasurement[7] = crankrev & 0xFF; cscmeasurement[8] = (crankrev >> 8) & 0xFF; cscmeasurement[9] = lastcrank & 0xFF; cscmeasurement[10] = (lastcrank >> 8) & 0xFF; cscMeasurementCharacteristics.setValue(cscmeasurement, 11); cscMeasurementCharacteristics.notify(); cscFeatureCharacteristics.setValue(cscfeature, 1); sensorLocationCharacteristics.setValue(sensorlocation, 1); Serial.print("crankrev = "); Serial.println(crankrev); Serial.print("lastcrank = "); Serial.println(lastcrank); Serial.print(cscmeasurement[0]); //Should be 2 to reflect binary - Yes Serial.print(" "); Serial.print(cscmeasurement[1]); // Should increment by 2 - Yes Serial.print(" "); Serial.print(cscmeasurement[2]); Serial.print(" "); Serial.print(cscmeasurement[3]); Serial.print(" "); Serial.print(cscmeasurement[4]); Serial.print(" "); Serial.print(cscmeasurement[5]); Serial.print(" "); Serial.print(cscmeasurement[6]); Serial.print(" "); Serial.print(cscmeasurement[7]); Serial.print(" "); Serial.print(cscmeasurement[8]); Serial.print(" "); Serial.print(cscmeasurement[9]); Serial.print(" "); Serial.print(cscmeasurement[10]); Serial.print(" "); Serial.print(cscmeasurement[11]); Serial.println(" "); delay(2000); }
Hi,
Try to change this:
byte speedkph; // Speed in KPH
byte speedmph; // Speed in MPH
byte cscmeasurement[11] = { 0b00000011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
byte cscfeature[1] = { 0b0000000000000010 };
byte sensorlocation[1] = { 6 };
to this:
byte speedkph; // Speed in KPH
byte speedmph; // Speed in MPH
byte cscmeasurement[11] = { 0b00000011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
byte cscfeature[1] = { 0b0000000000000011 };
byte sensorlocation[1] = { 6 };
The first bit of cscfeature needs to be set to 1 to indicate that wheel data is available, as explained here:
See the bluetooth guide for more details.
Note that, it needs to be consistent with the Flags fields 0b00000011 defined in cscmeasurement[0].
`The Wheel Revolution Data Present bit (bit 0 of the Flags field) indicates whether or not the Cumulative Wheel Revolutions and Last Wheel Event Time fields are present.
The Crank Revolution Data Present bit (bit 1 of the Flags field) indicates whether or not the Cumulative Crank Revolutions and the Last Crank Event Time fields are present. `
Also, I would try to move:
cscFeatureCharacteristics.setValue(cscfeature, 1);
sensorLocationCharacteristics.setValue(sensorlocation, 1);
outside the main loop and into InitBLE as it doesn't need to be set every measurement but just once.
Finally,
The following should be changed from
wheelrev = wheelrev + 1;
lastwheel = lastwheel + 1024*60/cadence;
to
wheelrev = wheelrev + 1;
lastwheel = lastwheel + 1024*60/speed;
If your sensor measures speed, than inject it. If not, you can infer the speed based on the cadence and your gear ratio/resistance.
Let me know if that helps!
What kind of speed values are you getting? Are they consistent or do they bounce around? With a cadence of 50 and a typical bike wheel circumference of around 2100 mm I would expect a speed of about 6 km/h. If the speed is consistent but the wrong speed the problem may be in the zwift setting for wheel circumference. Are you getting a good cadence value (50?) AGS
On Saturday, May 23, 2020, 04:24:52 PM HST, fatihmehmetyy <notifications@github.com> wrote:
hello friend, I have simple trainer, esp32 and proximity sensor. I want to connect to zwift. I tried this code and it working. But there is little problem about speed. I can read my cadance but speed is wrong.
Bu sürüm benim için iyi çalışıyor gibi görünüyor: /* Multi BLE Sensor - Richard Hedderly 2019
Based on heart sensor code by Andreas Spiess which was based on a Neil Kolban example.
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp Ported to Arduino ESP32 by Evandro Copercini updates by chegewara
*/
uint16_t crankrev; // Cadence RPM uint16_t lastcrank; // Last crank time
uint32_t wheelrev; // Wheel revolutions uint16_t lastwheel; // Last crank time
uint16_t cadence;
byte speedkph; // Speed in KPH byte speedmph; // Speed in MPH byte cscmeasurement[11] = { 0b00000011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; byte cscfeature[1] = { 0b0000000000000010 }; byte sensorlocation[1] = { 6 };
bool _BLEClientConnected = false;
//0x2901 is a custom user description // Define Speed and Cadence Properties
BLECharacteristic cscMeasurementCharacteristics( BLEUUID ((uint16_t) 0x2A5B), BLECharacteristic::PROPERTY_NOTIFY); //CSC Measurement Characteristic BLECharacteristic cscFeatureCharacteristics( BLEUUID ((uint16_t) 0x2A5C), BLECharacteristic::PROPERTY_READ); //CSC Feature Characteristic BLECharacteristic sensorLocationCharacteristics( BLEUUID ((uint16_t) 0x2A5D), BLECharacteristic::PROPERTY_READ); //Sensor Location Characteristic BLECharacteristic scControlPointCharacteristics( BLEUUID ((uint16_t) 0x2A55), BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_INDICATE); // SC Control Point Characteristic
//0x2901 is a custom user description BLEDescriptor cscMeasurementDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor cscFeatureDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor sensorLocationDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor scControlPointDescriptor( BLEUUID ((uint16_t) 0x2901));
// 0x2A5B - Notify - Client Charactaristic Config is 1 - DONE : CSC // Measurement // 0x2A5C - Read - 0x200 - DONE : CSC Feature // 0x2A5D - Read - None or 0x06 when active - Done : Sensor Location - 6 // is right crank // 0x2A55 - Write Indicate - Client Characteristic Config is 2 : SC // Control Point
// Define Battery //Servicec UUID 0x180F - Battery Level UUID 0x2A19
// Define Firmware //UUID 0x2A26
class MyServerCallbacks:public BLEServerCallbacks { void onConnect (BLEServer * pServer) { _BLEClientConnected = true; };
void onDisconnect (BLEServer * pServer) { _BLEClientConnected = false; }
};
void InitBLE () { // Create BLE Device BLEDevice::init ("Multi Fitness BLE Sensor");
// Create BLE Server BLEServer *pServer = BLEDevice::createServer (); pServer->setCallbacks( new MyServerCallbacks ());
// Create Speed and Cadence Configuration BLEService *pSpeed = pServer->createService(speedService);
// Create Speed and Cadence Service pSpeed->addCharacteristic( &cscMeasurementCharacteristics); pSpeed->addCharacteristic( &cscFeatureCharacteristics); pSpeed->addCharacteristic( &sensorLocationCharacteristics); pSpeed->addCharacteristic( &scControlPointCharacteristics);
cscMeasurementDescriptor.setValue( "Exercise Bike CSC Measurement"); cscMeasurementCharacteristics.addDescriptor( &cscMeasurementDescriptor); cscMeasurementCharacteristics.addDescriptor( new BLE2902());
cscFeatureDescriptor.setValue ("Exercise Bike CSC Feature"); cscFeatureCharacteristics.addDescriptor (&cscFeatureDescriptor);
sensorLocationDescriptor.setValue( "Exercise Bike CSC Sensor Location"); sensorLocationCharacteristics.addDescriptor( &sensorLocationDescriptor);
scControlPointDescriptor.setValue( "Exercise Bike CSC SC Control Point"); scControlPointCharacteristics.addDescriptor( &scControlPointDescriptor);
// Add UUIDs for Services to BLE Service Advertising pServer->getAdvertising()->addServiceUUID( speedService);
// Start p Instances pSpeed->start();
// Start Advertising pServer->getAdvertising()->start(); }
void setup () { Serial.begin(115200); Serial.println("Start"); InitBLE(); crankrev = 0; lastcrank = 0; wheelrev = 0; lastwheel = 0;
}
void loop () { // ___ // SPEED + CADENCE SECTION // ----------------------- // Read Cadence RPM Pins
cadence = 50; // Some arbitrary test value
if (cadence == 0) { cadence = 1; } crankrev = crankrev + 1; lastcrank = lastcrank + 1024*60/cadence;
wheelrev = wheelrev + 1; lastwheel = lastwheel + 1024*60/cadence;
// That, lastcran in the array position 2 will only show FF or 256 even // if lastcrank is set to a uint16_t
// Calculate Speed from cadence //
// Set and Send Speed and Cadence Notify cscmeasurement[1] = wheelrev & 0xFF; cscmeasurement[2] = (wheelrev >> 8) & 0xFF;
cscmeasurement[3] = (wheelrev >> 16) & 0xFF; cscmeasurement[4] = (wheelrev >> 24) & 0xFF;
cscmeasurement[5] = lastwheel & 0xFF; cscmeasurement[6] = (lastwheel >> 8) & 0xFF;
cscmeasurement[7] = crankrev & 0xFF; cscmeasurement[8] = (crankrev >> 8) & 0xFF;
cscmeasurement[9] = lastcrank & 0xFF; cscmeasurement[10] = (lastcrank >> 8) & 0xFF;
cscMeasurementCharacteristics.setValue(cscmeasurement, 11); cscMeasurementCharacteristics.notify();
cscFeatureCharacteristics.setValue(cscfeature, 1); sensorLocationCharacteristics.setValue(sensorlocation, 1);
Serial.print("crankrev = "); Serial.println(crankrev); Serial.print("lastcrank = "); Serial.println(lastcrank);
Serial.print(cscmeasurement[0]); //Should be 2 to reflect binary - Yes Serial.print(" "); Serial.print(cscmeasurement[1]); // Should increment by 2 - Yes Serial.print(" "); Serial.print(cscmeasurement[2]); Serial.print(" "); Serial.print(cscmeasurement[3]); Serial.print(" "); Serial.print(cscmeasurement[4]); Serial.print(" "); Serial.print(cscmeasurement[5]); Serial.print(" "); Serial.print(cscmeasurement[6]); Serial.print(" "); Serial.print(cscmeasurement[7]); Serial.print(" "); Serial.print(cscmeasurement[8]); Serial.print(" "); Serial.print(cscmeasurement[9]); Serial.print(" "); Serial.print(cscmeasurement[10]); Serial.print(" "); Serial.print(cscmeasurement[11]); Serial.println(" "); delay(2000); }
— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.
I notice that some of the samples here use a cscmeasurement array of length 11 and yet assign a value to cscmeasurement[11].
I don't see any assignments to cscmeasurement[11], but you are correct:
Serial.print(cscmeasurement[11]); Serial.println(" ");
is out of bounds and should be removed.
I also have a somewhat working version and can confirm that it plays okay with Zwift (at least on Android). My Implementation sends both power as well as Speed/Cadence.
I will try to clean up my code so I can post it here as well.
Any chance you could post that code? Thanks!
Here is modified code that does both power and crank speed: https://gist.github.com/arpruss/d8a26cb8121be7dcdff33c3b07cc79bf
@arpruss , with your example in Zwift, I am getting "No signal" message when TEST mode is enabled.
I don't have Zwift, but I fixed a bunch of bugs and the following version works in RGT Cycling: https://github.com/arpruss/BLEBike
I just used it with an exercise bike, not just in test mode.
@arpruss , I think I figured it out. Zwift kind of expecing to send a signal every x seconds, even if there is no new data available to kind of "ping" the service. Otherwise it is showing "no signal" error.
I am not sure if this is correct with specification, but that fixed it for me.
I thought my test code would always send a signal every second.
This version seems to work OK for me:
/* Multi BLE Sensor - Richard Hedderly 2019 Based on heart sensor code by Andreas Spiess which was based on a Neil Kolban example. Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp Ported to Arduino ESP32 by Evandro Copercini updates by chegewara */ #include <BLEDevice.h> #include <BLEUtils.h> #include <BLEServer.h> #include <BLE2902.h> uint16_t crankrev; // Cadence RPM uint16_t lastcrank; // Last crank time uint32_t wheelrev; // Wheel revolutions uint16_t lastwheel; // Last crank time uint16_t cadence; byte speedkph; // Speed in KPH byte speedmph; // Speed in MPH byte cscmeasurement[11] = { 0b00000011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; byte cscfeature[1] = { 0b0000000000000010 }; byte sensorlocation[1] = { 6 }; bool _BLEClientConnected = false; //0x2901 is a custom user description // Define Speed and Cadence Properties #define speedService BLEUUID ((uint16_t) 0x1816) BLECharacteristic cscMeasurementCharacteristics( BLEUUID ((uint16_t) 0x2A5B), BLECharacteristic::PROPERTY_NOTIFY); //CSC Measurement Characteristic BLECharacteristic cscFeatureCharacteristics( BLEUUID ((uint16_t) 0x2A5C), BLECharacteristic::PROPERTY_READ); //CSC Feature Characteristic BLECharacteristic sensorLocationCharacteristics( BLEUUID ((uint16_t) 0x2A5D), BLECharacteristic::PROPERTY_READ); //Sensor Location Characteristic BLECharacteristic scControlPointCharacteristics( BLEUUID ((uint16_t) 0x2A55), BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_INDICATE); // SC Control Point Characteristic //0x2901 is a custom user description BLEDescriptor cscMeasurementDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor cscFeatureDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor sensorLocationDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor scControlPointDescriptor( BLEUUID ((uint16_t) 0x2901)); // 0x2A5B - Notify - Client Charactaristic Config is 1 - DONE : CSC // Measurement // 0x2A5C - Read - 0x200 - DONE : CSC Feature // 0x2A5D - Read - None or 0x06 when active - Done : Sensor Location - 6 // is right crank // 0x2A55 - Write Indicate - Client Characteristic Config is 2 : SC // Control Point // Define Battery //Servicec UUID 0x180F - Battery Level UUID 0x2A19 // Define Firmware //UUID 0x2A26 class MyServerCallbacks:public BLEServerCallbacks { void onConnect (BLEServer * pServer) { _BLEClientConnected = true; }; void onDisconnect (BLEServer * pServer) { _BLEClientConnected = false; } }; void InitBLE () { // Create BLE Device BLEDevice::init ("Multi Fitness BLE Sensor"); // Create BLE Server BLEServer *pServer = BLEDevice::createServer (); pServer->setCallbacks( new MyServerCallbacks ()); // Create Speed and Cadence Configuration BLEService *pSpeed = pServer->createService(speedService); // Create Speed and Cadence Service pSpeed->addCharacteristic( &cscMeasurementCharacteristics); pSpeed->addCharacteristic( &cscFeatureCharacteristics); pSpeed->addCharacteristic( &sensorLocationCharacteristics); pSpeed->addCharacteristic( &scControlPointCharacteristics); cscMeasurementDescriptor.setValue( "Exercise Bike CSC Measurement"); cscMeasurementCharacteristics.addDescriptor( &cscMeasurementDescriptor); cscMeasurementCharacteristics.addDescriptor( new BLE2902()); cscFeatureDescriptor.setValue ("Exercise Bike CSC Feature"); cscFeatureCharacteristics.addDescriptor (&cscFeatureDescriptor); sensorLocationDescriptor.setValue( "Exercise Bike CSC Sensor Location"); sensorLocationCharacteristics.addDescriptor( &sensorLocationDescriptor); scControlPointDescriptor.setValue( "Exercise Bike CSC SC Control Point"); scControlPointCharacteristics.addDescriptor( &scControlPointDescriptor); // Add UUIDs for Services to BLE Service Advertising pServer->getAdvertising()->addServiceUUID( speedService); // Start p Instances pSpeed->start(); // Start Advertising pServer->getAdvertising()->start(); } void setup () { Serial.begin(115200); Serial.println("Start"); InitBLE(); crankrev = 0; lastcrank = 0; wheelrev = 0; lastwheel = 0; } void loop () { // _______________________ // SPEED + CADENCE SECTION // ----------------------- // Read Cadence RPM Pins cadence = 50; // Some arbitrary test value if (cadence == 0) { cadence = 1; } crankrev = crankrev + 1; lastcrank = lastcrank + 1024*60/cadence; wheelrev = wheelrev + 1; lastwheel = lastwheel + 1024*60/cadence; // That, lastcran in the array position 2 will only show FF or 256 even // if lastcrank is set to a uint16_t // Calculate Speed from cadence // // Set and Send Speed and Cadence Notify cscmeasurement[1] = wheelrev & 0xFF; cscmeasurement[2] = (wheelrev >> 8) & 0xFF; cscmeasurement[3] = (wheelrev >> 16) & 0xFF; cscmeasurement[4] = (wheelrev >> 24) & 0xFF; cscmeasurement[5] = lastwheel & 0xFF; cscmeasurement[6] = (lastwheel >> 8) & 0xFF; cscmeasurement[7] = crankrev & 0xFF; cscmeasurement[8] = (crankrev >> 8) & 0xFF; cscmeasurement[9] = lastcrank & 0xFF; cscmeasurement[10] = (lastcrank >> 8) & 0xFF; cscMeasurementCharacteristics.setValue(cscmeasurement, 11); cscMeasurementCharacteristics.notify(); cscFeatureCharacteristics.setValue(cscfeature, 1); sensorLocationCharacteristics.setValue(sensorlocation, 1); Serial.print("crankrev = "); Serial.println(crankrev); Serial.print("lastcrank = "); Serial.println(lastcrank); Serial.print(cscmeasurement[0]); //Should be 2 to reflect binary - Yes Serial.print(" "); Serial.print(cscmeasurement[1]); // Should increment by 2 - Yes Serial.print(" "); Serial.print(cscmeasurement[2]); Serial.print(" "); Serial.print(cscmeasurement[3]); Serial.print(" "); Serial.print(cscmeasurement[4]); Serial.print(" "); Serial.print(cscmeasurement[5]); Serial.print(" "); Serial.print(cscmeasurement[6]); Serial.print(" "); Serial.print(cscmeasurement[7]); Serial.print(" "); Serial.print(cscmeasurement[8]); Serial.print(" "); Serial.print(cscmeasurement[9]); Serial.print(" "); Serial.print(cscmeasurement[10]); Serial.print(" "); Serial.print(cscmeasurement[11]); Serial.println(" "); delay(2000); }
I've tried this one, and it works with Zwift itself on Android. However it shows "No Signal" when connected to Zwift Companion and PC game. Not sure why tho, anyone tested this snippet with Companion app?
Could it be that Zwift Companion needs a power sensor, and not just cadence?
Nope, it's actually only working as Bluetooth-internet gateway to PC version of Zwift, so same sensor options.
I'm trying to use virtual power to ride on my "dumb" trainer, aka cadence+wheel speed+known trainer model and setting. So far I've tested above example with android Zwift version - it's OK, I'm seeing actual cadence and speed. But when pairing via companion - Zwift PC detects my ESP32, pairs with it and shows No Signal. Not sure what's the actual difference here.
Have you tried my code? (You'll want to turn off the display options.) https://github.com/arpruss/BLEBike
I don't have Zwift, but it does work with RGT Cycling.
Have you tried my code? (You'll want to turn off the display options.) https://github.com/arpruss/BLEBike
I don't have Zwift, but it does work with RGT Cycling.
It's actually working. Obviously I read zero RPM after quick test, but that's better than No Signal. Many thanks, I'll look into your code,
You can #define TEST and you should read 59-60 RPM.
My Wahoo KICKR seems to have an opto for cadence and a PWM'd FET it looks like for spinning resistance, does anyone know what I need to implement to exchange their PCB with my own as theirs stopped working.
If you can tap the signal of these two sensors, you could use a Nordic nrf52840 or nrf52832 to broadcast power and cadence. Since power consumption may not be a big issue, you could use an eps32. Someone posted a code for esp32 a few posts above. Essentially, you will derive the cadence from the cumulative number of cranks and the crank timings. The power should be available from the sensor. So if you can compute cadence and power from the sensors on the ESP32, you can adjust the code above to broadcast them and profit.
So what is the FET doing? I thought it was braking...
On Wed, Jan 27, 2021, 22:28 verjus, notifications@github.com wrote:
If you can tap the signal of these two sensors, you could use a Nordic nrf52840 or nrf52832 to broadcast power and cadence. Since power consumption may not be a big issue, you could use an eps32. Someone posted a code for esp32 a few posts above. Essentially, you will derive the cadence from the cumulative number of cranks and the crank timings. The power should be available from the sensor. So if you can compute cadence and power from the sensors on the ESP32, you can adjust the code above to broadcast them and profit.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/nkolban/esp32-snippets/issues/844#issuecomment-768772753, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACBV22I5YRROH2ZRBQW3Q2LS4DKUHANCNFSM4HBNG5VA .
I don't know, I don't have a KICKR. I also don't know what FET stands for. However, to derive power, cadence only is not sufficient. Power is often estimated using resistance combined with cadence. So it could be braking, if by braking you mean resistance. You would have to multiply cadence and a resistance/breaking to get a value proportional to power. Probably would need a constant C, for calibration: Power = C Cadence Resistance
I had some problems with the code from above, but doing some changes it looks like working perfectly: As the thread headline already says:
"byte cscfeature[1] = { 0b0000000000000010 };"
is nonsense. You cannot add a 16 bit value to a byte variable. Using:
"byte cscfeature[2] = { 0b00000011 , 0};"
should be ok with the right byte ordering and so my clients enterprete the values correctly as flags for speed and cadence.
I also added a DeviceInformationService as most commercial devices have it (just looking nice) and added some lines for reconnecting BLE.
Here is the revised code.
/*
Multi BLE Sensor - Richard Hedderly 2019
Based on heart sensor code by Andreas Spiess which was based on a Neil
Kolban example.
Based on Neil Kolban example for IDF:
https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp
Ported to Arduino ESP32 by Evandro Copercini
updates by chegewara
updates by andreasz70
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint16_t crankrev; // Cadence RPM
uint16_t lastcrank; // Last crank time
uint32_t wheelrev; // Wheel revolutions
uint16_t lastwheel; // Last crank time
double cadence;
byte cscmeasurement[11] = { 0b00000011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
byte cscfeature[2] = { 0b00000011 , 0};
byte sensorlocation[1] = { 6 };
bool _BLEClientConnected = false;
//0x2901 is a custom user description
// Define Speed and Cadence Properties
#define cyclingSpeedAndCadenceService BLEUUID ((uint16_t) 0x1816)
BLECharacteristic cscMeasurementCharacteristics( BLEUUID ((uint16_t) 0x2A5B), BLECharacteristic::PROPERTY_NOTIFY); //CSC Measurement Characteristic
BLECharacteristic cscFeatureCharacteristics( BLEUUID ((uint16_t) 0x2A5C), BLECharacteristic::PROPERTY_READ); //CSC Feature Characteristic
BLECharacteristic sensorLocationCharacteristics( BLEUUID ((uint16_t) 0x2A5D), BLECharacteristic::PROPERTY_READ); //Sensor Location Characteristic
BLECharacteristic scControlPointCharacteristics( BLEUUID ((uint16_t) 0x2A55), BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_INDICATE); // SC Control Point Characteristic
#define DeviceInformationService BLEUUID ((uint16_t) 0x180A)
BLECharacteristic ManufacturerNameCharacteristics( BLEUUID ((uint16_t) 0x2A29), BLECharacteristic::PROPERTY_READ);
BLECharacteristic ModelNumberCharacteristics( BLEUUID ((uint16_t) 0x2A24), BLECharacteristic::PROPERTY_READ);
BLECharacteristic HardwareRevisionCharacteristics( BLEUUID ((uint16_t) 0x2A27), BLECharacteristic::PROPERTY_READ);
BLECharacteristic SoftwareRevisionCharacteristics( BLEUUID ((uint16_t) 0x2A26), BLECharacteristic::PROPERTY_READ);
//0x2901 is a custom user description
BLEDescriptor cscMeasurementDescriptor( BLEUUID ((uint16_t) 0x2901));
BLEDescriptor cscFeatureDescriptor( BLEUUID ((uint16_t) 0x2901));
BLEDescriptor sensorLocationDescriptor( BLEUUID ((uint16_t) 0x2901));
BLEDescriptor scControlPointDescriptor( BLEUUID ((uint16_t) 0x2901));
// 0x2A5B - Notify - Client Charactaristic Config is 1 - DONE : CSC
// Measurement
// 0x2A5C - Read - 0x200 - DONE : CSC Feature
// 0x2A5D - Read - None or 0x06 when active - Done : Sensor Location - 6
// is right crank
// 0x2A55 - Write Indicate - Client Characteristic Config is 2 : SC
// Control Point
// Define Battery
//Servicec UUID 0x180F - Battery Level UUID 0x2A19
// Define Firmware
//UUID 0x2A26
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
BLEDevice::startAdvertising();
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
void InitBLE ()
{
// Create BLE Device
BLEDevice::init ("Multi Fitness BLE Sensor");
// Create BLE Server
BLEServer *pServer = BLEDevice::createServer ();
pServer->setCallbacks( new MyServerCallbacks ());
// Create Speed and Cadence Configuration
BLEService *pSpeed = pServer->createService(cyclingSpeedAndCadenceService);
// Create Speed and Cadence Service
pSpeed->addCharacteristic( &cscMeasurementCharacteristics);
pSpeed->addCharacteristic( &cscFeatureCharacteristics);
pSpeed->addCharacteristic( &sensorLocationCharacteristics);
pSpeed->addCharacteristic( &scControlPointCharacteristics);
cscMeasurementCharacteristics.addDescriptor( new BLE2902());
cscFeatureCharacteristics.setValue(cscfeature, 2);
sensorLocationCharacteristics.setValue(sensorlocation, 1);
scControlPointCharacteristics.addDescriptor( new BLE2902());
BLEService *pInfo = pServer->createService(DeviceInformationService);
pInfo->addCharacteristic( &ManufacturerNameCharacteristics);
ManufacturerNameCharacteristics.setValue((unsigned char *) "my DIY production", 17);
pInfo->addCharacteristic( &ModelNumberCharacteristics);
ModelNumberCharacteristics.setValue({ 0x45, 0x53, 0x50, 0x33, 0x32 });
pInfo->addCharacteristic( &HardwareRevisionCharacteristics);
HardwareRevisionCharacteristics.setValue({ "2", ".", "0" });
pInfo->addCharacteristic( &SoftwareRevisionCharacteristics);
SoftwareRevisionCharacteristics.setValue({ 0x32, 0x2e, 0x33 });
// Add UUIDs for Services to BLE Service Advertising
pServer->getAdvertising()->addServiceUUID(cyclingSpeedAndCadenceService);
// Start p Instances
pSpeed->start();
pInfo->start();
// Start Advertising
pServer->getAdvertising()->start();
}
void setup ()
{
Serial.begin(115200);
Serial.println("Start");
InitBLE();
crankrev = 0;
lastcrank = 0;
wheelrev = 0;
lastwheel = 0;
cadence=50;
}
void loop ()
{
// notify changed value
if (deviceConnected) {
// _______________________
// SPEED + CADENCE SECTION
// -----------------------
// Read Cadence RPM Pins
cadence += random(1)-0.5 ; // Some arbitrary test value
if (cadence == 0) {
cadence = 1;
}
//crankrev = crankrev + 1;
//lastcrank = lastcrank + 1024*60/cadence;
wheelrev = wheelrev + 1;
lastwheel = lastwheel + 1024*60/cadence;
// That, lastcran in the array position 2 will only show FF or 256 even
// if lastcrank is set to a uint16_t
// Calculate Speed from cadence
//
// Set and Send Speed and Cadence Notify
cscmeasurement[1] = wheelrev & 0xFF;
cscmeasurement[2] = (wheelrev >> 8) & 0xFF;
cscmeasurement[3] = (wheelrev >> 16) & 0xFF;
cscmeasurement[4] = (wheelrev >> 24) & 0xFF;
cscmeasurement[5] = lastwheel & 0xFF;
cscmeasurement[6] = (lastwheel >> 8) & 0xFF;
cscmeasurement[7] = crankrev & 0xFF;
cscmeasurement[8] = (crankrev >> 8) & 0xFF;
cscmeasurement[9] = lastcrank & 0xFF;
cscmeasurement[10] = (lastcrank >> 8) & 0xFF;
cscMeasurementCharacteristics.setValue(cscmeasurement, 11);
cscMeasurementCharacteristics.notify();
Serial.print("crankrev = ");
Serial.println(crankrev);
Serial.print("lastcrank = ");
Serial.println(lastcrank);
Serial.print(cscmeasurement[0]); //Should be 2 to reflect binary - Yes
Serial.print(" ");
Serial.print(cscmeasurement[1]); // Should increment by 2 - Yes
Serial.print(" ");
Serial.print(cscmeasurement[2]);
Serial.print(" ");
Serial.print(cscmeasurement[3]);
Serial.print(" ");
Serial.print(cscmeasurement[4]);
Serial.print(" ");
Serial.print(cscmeasurement[5]);
Serial.print(" ");
Serial.print(cscmeasurement[6]);
Serial.print(" ");
Serial.print(cscmeasurement[7]);
Serial.print(" ");
Serial.print(cscmeasurement[8]);
Serial.print(" ");
Serial.print(cscmeasurement[9]);
Serial.print(" ");
Serial.print(cscmeasurement[10]);
Serial.print(" ");
Serial.print(cscmeasurement[11]);
Serial.println(" ");
delay(2000);
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("start advertising");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
}
@andreasz70 I tested your code on an ESP32. It is working for the nrf connect app and the Toolkit but it is not working for the Garmin Edge 1030. I can connect to the sensor but I can't see any value. I was testing my own code (based on Neil Kolban's BLE library) befor and I got the same result. Don't know what is missing to make it running. Short info to my project - I want to read the data from a Daum 8008 TRS3 (allready working) over RS232 and feed it via BLE to my Bike Computer (heart rate is working but I stucked in cadence and speed) to record the session.
Can you test your "sensor" on a Garmin device?
I dont have any bluetooth devices to test. Tested only with the nrf connect app and other android and windows apps.
@andreasz70 I tested your code on an ESP32. It is working for the nrf connect app and the Toolkit but it is not working for the Garmin Edge 1030. I can connect to the sensor but I can't see any value. I was testing my own code (based on Neil Kolban's BLE library) befor and I got the same result. Don't know what is missing to make it running. Short info to my project - I want to read the data from a Daum 8008 TRS3 (allready working) over RS232 and feed it via BLE to my Bike Computer (heart rate is working but I stucked in cadence and speed) to record the session.
Can you test your "sensor" on a Garmin device?
Did you find out anything new?
I do have exactly the same problem with my own code. nRF connect and Toolkit are working my Garmin 530 connects without any problem but won't show cadence data. The HRM Example from Andreas Spiess did work flawlessly. I've read a lot about this exact problem in the last days but no solution. I will try this code in the next days and share the result.
@andreasz70 I tested your code on an ESP32. It is working for the nrf connect app and the Toolkit but it is not working for the Garmin Edge 1030. I can connect to the sensor but I can't see any value. I was testing my own code (based on Neil Kolban's BLE library) befor and I got the same result. Don't know what is missing to make it running. Short info to my project - I want to read the data from a Daum 8008 TRS3 (allready working) over RS232 and feed it via BLE to my Bike Computer (heart rate is working but I stucked in cadence and speed) to record the session. Can you test your "sensor" on a Garmin device?
Did you find out anything new?
I do have exactly the same problem with my own code. nRF connect and Toolkit are working my Garmin 530 connects without any problem but won't show cadence data. The HRM Example from Andreas Spiess did work flawlessly. I've read a lot about this exact problem in the last days but no solution. I will try this code in the next days and share the result.
HRM is no problem on the egde device. I've read and tested a lot of possibilities in the last day - without any solution. It seems that the Garmin devices are looking for a "special" thing on the sensor. I'm actually trying to get only the cadence working because I've a Wahoo BT cadence sensor at home and I want to do reverse engineering for that device. If the cadence is running I'll go foreward for the speed sensor (That's the plan ;-))
What are the differents between the Wahoo sensor and the ESP in nRF connect app:
One strange thing is on the Garmin side - When I connect to the Wahoo it recognise it as a cadence sensor. When I connect to the ESP it says that it is a speed and cadence sensor (the CSC feature is set to 0x02 and the measurement flags are also set to 0x02 --> only cadence). The nrF connect only displays the cadence related values as it should. I don't know why.
So if you have any ideas or results, please let me know.
@JimmyGold So I finally tried the example above, without any surprise it's not working. I've tried some "cadence monitor" apps, these work flawlessly, it's only the Garmin. I also did notice that the Garmin seams to ignore the Flags. It shows "speed and cadence" always like you sad. I think this has to do with the Appearance. It seems like there is no way to change the Appearance under 0x1800 - 0x2A01 within the Arduino Library.
Let me know when you make progress with your reversing project.
@Paul-Lukas
Let me know when you make progress with your reversing project.
I've managed it with an additional modul (Bluefruit LE SPI friend). Now I can send the CSC to the Garmin device. With the built in BLE device of the ESP32 it was not possible for me to get it working.
Here is the code: https://github.com/JimmyGold/Daum-BLE-bridge
Looking for answers I found this thread. I'm also trying to get the ESP32 to work with a Garmin (Edge830) and have the same result:
I have the Garmin BLE Cadence sensor that came with the Edge and tried to mimic that, but that didn't do the trick.
@JimmyGold I've tried some "cadence monitor" apps, these work flawlessly, it's only the Garmin. I also did notice that the Garmin seams to ignore the Flags. It shows "speed and cadence" always like you sad. I think this has to do with the Appearance. It seems like there is no way to change the Appearance under 0x1800 - 0x2A01 within the Arduino Library.
That was my guess as well and I found out that setting the Appearance characteristic can be set in the Advertising data and in the Generic Access service (0x1800) with the following code:
auto pAdvertising = BLEDevice::getAdvertising();
pAdvertising->setAppearance(ESP_BLE_APPEARANCE_CYCLING_CADENCE);
::esp_ble_gap_config_local_icon(ESP_BLE_APPEARANCE_CYCLING_CADENCE);
Then the correct Appearance comes up in both before and after connecting to the device. However, it does not solve the problem that Garmin detects a speed/cadence sensor when only the cadence feature flag is set.
So I am out of ideas as well.
I know this is almost a year old thread. But I've been trying to solve exactly the same issue. I have Garmin Fenix 5 and also trying to get the ESP32 to work with my watch. The nRF connect can see the BLE. I can also pair with Zwift- But for some reason never on Garmin.
auto pAdvertising = BLEDevice::getAdvertising(); pAdvertising->setAppearance(ESP_BLE_APPEARANCE_CYCLING_CADENCE); ::esp_ble_gap_config_local_icon(ESP_BLE_APPEARANCE_CYCLING_CADENCE);
@ArdKuijpers Do you have the whole code? I would like to try that..
@marpelto This has been a sleeping private project but I've decided to make it public at: https://github.com/ArdKuijpers/skateometer.
10 months ago, I tried to make it work with both a Fenix5 and an Edge830, but could not them to work. It seems it is some sort of strange (buggy) behavior between ESP32 and Garmin. Eventually I gave up because I have enough other projects ;)
But if you find a solution, I hope you will share it. Good luck!
I do agree that some strange behavior with ESP32. But let's see...
I despaired on the same problem regarding Garmin, until I just switched the Bluetooth stack to https://github.com/h2zero/NimBLE-Arduino Now it finally works 🤩 https://cdn.discordapp.com/attachments/840291519712854067/995716127818793060/IMG_20220710_173914_edit_603179031364209.jpg (nRF stuff only as known-good-config)
Can anyone please confirm which parameter is for cadence RPM in cycling speed and cadence? I want to interface my hardware with the Zwift mobile app, where I want to control RPM via potentiometer. When I connect my BLE device with zwift, it successfully connetced, and I can see power and heart rate updating, but RPM shows continously 0, and I only have a cadence sensor available. Speed is not required in my case. Here is my code function where I am controlling cadence via a potentiometer. You can say that I am just controlling the parameter for now, and later on, I will connect the sensor. I'm a bit confused because there are a lot of things coming in cycling speed and cadence: 1. SC control point, 2. Sensor location, 3. CSC feature, 4. CSC measurement. For cadence RPM, I think it can be obtained from CSC measurement, but I'm not sure which parameter it's for and which parameter I need to update to change the RPM in the app because it's constantly showing 0. Please refer to the two attached pictures for reference
Here is the code
uint8_t FlagCSC =0b00000011; uint8_t cscmeasurement[11] = { 0b00000011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
void Cadancemeasur() { dummy_data_t.Cadancedata = map(analogRead(Potentio1), 0, 4095, 0, 200);
if (dummy_data_t.Cadancedata == 0) { dummy_data_t.Cadancedata= 1; } crankrev = crankrev + 1; lastcrank = lastcrank + 1024*60/dummy_data_t.Cadancedata;
wheelrev = wheelrev + 1; lastwheel = lastwheel + 1024*60/dummy_data_t.Cadancedata;
// Calculate Speed from cadence
// Set and Send Speed and Cadence Notify
cscmeasurement[0] = FlagCSC;
cscmeasurement[1] = wheelrev & 0xFF;
cscmeasurement[2] = (wheelrev >> 8) & 0xFF;
cscmeasurement[3] = (wheelrev >> 16) & 0xFF;
cscmeasurement[4] = (wheelrev >> 24) & 0xFF;
cscmeasurement[5] = lastwheel & 0xFF;
cscmeasurement[6] = (lastwheel >> 8) & 0xFF;
cscmeasurement[7] = crankrev & 0xFF;
cscmeasurement[8] = (crankrev >> 8) & 0xFF;
cscmeasurement[9] = dummy_data_t.Cadancedata& 0xFF;
cscmeasurement[10] = (dummy_data_t.Cadancedata>> 8) & 0xFF;
CadanceRPMCharacteristics.setValue(cscmeasurement, sizeof(cscmeasurement));
CadanceRPMCharacteristics.notify();
Serial.print("Cadence measurement: ");
Serial.print(dummy_data_t.Cadancedata);
Serial.println(" RPM");
}
Thank you for sharing this, but could you please provide insights into my code and identify where I may be making mistakes?
Here is the actual code which I follow "#include
uint16_t crankrev; // Cadence RPM uint16_t lastcrank; // Last crank time
uint32_t wheelrev; // Wheel revolutions uint16_t lastwheel; // Last crank time
uint16_t cadence;
byte speedkph; // Speed in KPH byte speedmph; // Speed in MPH byte cscmeasurement[11] = { 0b00000011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; byte cscfeature[1] = { 0b0000000000000010 }; byte sensorlocation[1] = { 6 };
bool _BLEClientConnected = false;
//0x2901 is a custom user description // Define Speed and Cadence Properties
BLECharacteristic cscMeasurementCharacteristics( BLEUUID ((uint16_t) 0x2A5B), BLECharacteristic::PROPERTY_NOTIFY); //CSC Measurement Characteristic BLECharacteristic cscFeatureCharacteristics( BLEUUID ((uint16_t) 0x2A5C), BLECharacteristic::PROPERTY_READ); //CSC Feature Characteristic BLECharacteristic sensorLocationCharacteristics( BLEUUID ((uint16_t) 0x2A5D), BLECharacteristic::PROPERTY_READ); //Sensor Location Characteristic BLECharacteristic scControlPointCharacteristics( BLEUUID ((uint16_t) 0x2A55), BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_INDICATE); // SC Control Point Characteristic
//0x2901 is a custom user description BLEDescriptor cscMeasurementDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor cscFeatureDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor sensorLocationDescriptor( BLEUUID ((uint16_t) 0x2901)); BLEDescriptor scControlPointDescriptor( BLEUUID ((uint16_t) 0x2901));
// 0x2A5B - Notify - Client Charactaristic Config is 1 - DONE : CSC // Measurement // 0x2A5C - Read - 0x200 - DONE : CSC Feature // 0x2A5D - Read - None or 0x06 when active - Done : Sensor Location - 6 // is right crank // 0x2A55 - Write Indicate - Client Characteristic Config is 2 : SC // Control Point
// Define Battery //Servicec UUID 0x180F - Battery Level UUID 0x2A19
// Define Firmware //UUID 0x2A26
class MyServerCallbacks:public BLEServerCallbacks { void onConnect (BLEServer * pServer) { _BLEClientConnected = true; };
void onDisconnect (BLEServer * pServer) { _BLEClientConnected = false; }
};
void InitBLE () { // Create BLE Device BLEDevice::init ("Multi Fitness BLE Sensor");
// Create BLE Server BLEServer *pServer = BLEDevice::createServer (); pServer->setCallbacks( new MyServerCallbacks ());
// Create Speed and Cadence Configuration BLEService *pSpeed = pServer->createService(speedService);
// Create Speed and Cadence Service pSpeed->addCharacteristic( &cscMeasurementCharacteristics); pSpeed->addCharacteristic( &cscFeatureCharacteristics); pSpeed->addCharacteristic( &sensorLocationCharacteristics); pSpeed->addCharacteristic( &scControlPointCharacteristics);
cscMeasurementDescriptor.setValue( "Exercise Bike CSC Measurement"); cscMeasurementCharacteristics.addDescriptor( &cscMeasurementDescriptor); cscMeasurementCharacteristics.addDescriptor( new BLE2902());
cscFeatureDescriptor.setValue ("Exercise Bike CSC Feature"); cscFeatureCharacteristics.addDescriptor (&cscFeatureDescriptor);
sensorLocationDescriptor.setValue( "Exercise Bike CSC Sensor Location"); sensorLocationCharacteristics.addDescriptor( &sensorLocationDescriptor);
scControlPointDescriptor.setValue( "Exercise Bike CSC SC Control Point"); scControlPointCharacteristics.addDescriptor( &scControlPointDescriptor);
// Add UUIDs for Services to BLE Service Advertising pServer->getAdvertising()->addServiceUUID( speedService);
// Start p Instances pSpeed->start();
// Start Advertising pServer->getAdvertising()->start(); }
void setup () { Serial.begin(115200); Serial.println("Start"); InitBLE(); crankrev = 0; lastcrank = 0; wheelrev = 0; lastwheel = 0;
}
void loop () { // ___ // SPEED + CADENCE SECTION // ----------------------- // Read Cadence RPM Pins uint16_t dummy_data_tCadancedata = map(analogRead(Potentio1), 0, 4095, 0, 200); cadence = 50; // Some arbitrary test value
if (cadence == 0) { cadence = 1; } crankrev = crankrev + 1; lastcrank = lastcrank + 1024*60/cadence;
wheelrev = wheelrev + 1; lastwheel = lastwheel + 1024*60/cadence;
// That, lastcran in the array position 2 will only show FF or 256 even // if lastcrank is set to a uint16_t
// Calculate Speed from cadence //
// Set and Send Speed and Cadence Notify cscmeasurement[1] = wheelrev & 0xFF; cscmeasurement[2] = (wheelrev >> 8) & 0xFF;
cscmeasurement[3] = (wheelrev >> 16) & 0xFF; cscmeasurement[4] = (wheelrev >> 24) & 0xFF;
cscmeasurement[5] = lastwheel & 0xFF; cscmeasurement[6] = (lastwheel >> 8) & 0xFF;
cscmeasurement[7] = dummy_data_tCadancedata & 0xFF; cscmeasurement[8] = (dummy_data_tCadancedata >> 8) & 0xFF;
cscmeasurement[9] = lastcrank & 0xFF; cscmeasurement[10] = (lastcrank >> 8) & 0xFF;
cscMeasurementCharacteristics.setValue(cscmeasurement, 11); cscMeasurementCharacteristics.notify();
cscFeatureCharacteristics.setValue(cscfeature, 1); sensorLocationCharacteristics.setValue(sensorlocation, 1);
Serial.print("crankrev = "); Serial.println(crankrev); Serial.print("lastcrank = "); Serial.println(lastcrank);
Serial.print(cscmeasurement[0]); //Should be 2 to reflect binary - Yes Serial.print(" "); Serial.print(cscmeasurement[1]); // Should increment by 2 - Yes Serial.print(" "); Serial.print(cscmeasurement[2]); Serial.print(" "); Serial.print(cscmeasurement[3]); Serial.print(" "); Serial.print(cscmeasurement[4]); Serial.print(" "); Serial.print(cscmeasurement[5]); Serial.print(" "); Serial.print(cscmeasurement[6]); Serial.print(" "); Serial.print(cscmeasurement[7]); Serial.print(" "); Serial.print(cscmeasurement[8]); Serial.print(" "); Serial.print(cscmeasurement[9]); Serial.print(" "); Serial.print(cscmeasurement[10]); Serial.print(" "); Serial.print(cscmeasurement[11]); Serial.println(" "); delay(2000); }"
If you read the 2nd link I posted, you'd realize that there isn't a simple RPM field.
Yes, you are absolutely right; this is not a straightforward RPM field, and calculations are required to obtain RPM. However, for now, I am not using any sensor and instead using a potentiometer as a substitute for the sensor. In my code, I have mapped the data from 0 to 200. Additionally, as you can see in the previously attached information, I have implemented code to change the 'last crank' value in nRF Connect. I believe my mistake lies in the data format, and I would like to clarify what I should be asking for. Since I don't require precise calculations via the potentiometer, I just need to transfer simple, unprocessed data ranging from 0 to 200
I also read the second link, but to be honest, I didn't fully understand it. Do you happen to have any examples that could help me grasp the data pattern better?
Hi Neil,
I'm working on taking a feed from a reed switch on an exercise bike. This will give a cadence pulse per revolution. Before I physically attach the ESP32 to the bike I sent some test data from the Arduino code via the BLE connection.
I've taken your BLE server example code (via Andreas Spiess) and have added the BLE cadence service into it.
The LightBlue BLE Explorer shows the heart and cadence services advertising fine.
However...
The cscmeasurement characteristic requires the lastcrank time (1/1024 resolution) in an uint16. I want to pass over 2048ms but it's only letting me pass 255 max which points to the BLE characteristic being a byte. I can't make the characteristic into a uint16 as the BLECharacteristic function requests a byte.
I feel I'm missing something obvious but how can I .setvalue 2048 into the byte cscMeasurementCharacteristics array?
Many thanks
Richard
`/* Multi BLE Sensor - Richard Hedderly 2019
*/
include
include
include
include
byte flags = 0b00111110; byte bpm; // Beats Per Minute byte crankrev; // Cadence RPM uint16_t lastcrank; // Last crank time byte speedkph; // Speed in KPH byte speedmph; // Speed in MPH byte heart[8] = {0b00001110, 60, 0, 0, 0 , 0, 0, 0}; byte cscmeasurement[3] = {0b00000010, 0, 0}; byte cscfeature[1] = {0b0000000000000010}; byte sensorlocation[1] = {6};
bool _BLEClientConnected = false;
// Define Heart Properties
define heartRateService BLEUUID((uint16_t)0x180D) // Define BLE Heart Rate Service
BLECharacteristic heartRateMeasurementCharacteristics(BLEUUID((uint16_t)0x2A37), BLECharacteristic::PROPERTY_NOTIFY); // Heart Rate Characteristics BLEDescriptor heartRateDescriptor(BLEUUID((uint16_t)0x2901)); //0x2901 is a custom user description
// Define Speed and Cadence Properties
define speedService BLEUUID((uint16_t)0x1816)
BLECharacteristic cscMeasurementCharacteristics(BLEUUID((uint16_t)0x2A5B), BLECharacteristic::PROPERTY_NOTIFY); //CSC Measurement Characteristic BLECharacteristic cscFeatureCharacteristics(BLEUUID((uint16_t)0x2A5C), BLECharacteristic::PROPERTY_READ); //CSC Feature Characteristic BLECharacteristic sensorLocationCharacteristics(BLEUUID((uint16_t)0x2A5D), BLECharacteristic::PROPERTY_READ); //Sensor Location Characteristic BLECharacteristic scControlPointCharacteristics(BLEUUID((uint16_t)0x2A55), BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_INDICATE); // SC Control Point Characteristic BLEDescriptor cscMeasurementDescriptor(BLEUUID((uint16_t)0x2901)); //0x2901 is a custom user description BLEDescriptor cscFeatureDescriptor(BLEUUID((uint16_t)0x2901)); //0x2901 is a custom user description BLEDescriptor sensorLocationDescriptor(BLEUUID((uint16_t)0x2901)); //0x2901 is a custom user description BLEDescriptor scControlPointDescriptor(BLEUUID((uint16_t)0x2901)); //0x2901 is a custom user description
// 0x2A5B - Notify - Client Charactaristic Config is 1 - DONE : CSC Measurement // 0x2A5C - Read - 0x200 - DONE : CSC Feature // 0x2A5D - Read - None or 0x06 when active - Done : Sensor Location - 6 is right crank // 0x2A55 - Write Indicate - Client Characteristic Config is 2 : SC Control Point
// Define Battery //Servicec UUID 0x180F - Battery Level UUID 0x2A19
// Define Firmware //UUID 0x2A26
class MyServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer* pServer) { _BLEClientConnected = true; };
};
void InitBLE() { // Create BLE Device BLEDevice::init("Multi Fitness BLE Sensor");
// Create BLE Server BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks());
// Create BLE Heart Configuration BLEService *pHeart = pServer->createService(heartRateService); // Create Heart Service pHeart->addCharacteristic(&heartRateMeasurementCharacteristics); heartRateDescriptor.setValue("Exercise Bike Pulse Grips Rate"); heartRateMeasurementCharacteristics.addDescriptor(&heartRateDescriptor); heartRateMeasurementCharacteristics.addDescriptor(new BLE2902());
// Create Speed and Cadence Configuration BLEService *pSpeed = pServer->createService(speedService); // Create Speed and Cadence Service pSpeed->addCharacteristic(&cscMeasurementCharacteristics); pSpeed->addCharacteristic(&cscFeatureCharacteristics); pSpeed->addCharacteristic(&sensorLocationCharacteristics); pSpeed->addCharacteristic(&scControlPointCharacteristics); cscMeasurementDescriptor.setValue("Exercise Bike CSC Measurement"); cscMeasurementCharacteristics.addDescriptor(&cscMeasurementDescriptor); cscFeatureDescriptor.setValue("Exercise Bike CSC Feature"); cscFeatureCharacteristics.addDescriptor(&cscFeatureDescriptor); sensorLocationDescriptor.setValue("Exercise Bike CSC Sensor Location"); sensorLocationCharacteristics.addDescriptor(&sensorLocationDescriptor); scControlPointDescriptor.setValue("Exercise Bike CSC SC Control Point"); scControlPointCharacteristics.addDescriptor(&scControlPointDescriptor);
// Add UUIDs for Services to BLE Service Advertising pServer->getAdvertising()->addServiceUUID(heartRateService); pServer->getAdvertising()->addServiceUUID(speedService);
// Start p Instances pSpeed->start(); pHeart->start();
// Start Advertising pServer->getAdvertising()->start(); }
void setup() { Serial.begin(115200); Serial.println("Start"); InitBLE(); bpm = 1; }
void loop() {
// _____ // HEART SECTION // ------------- // Read Heart Pins // *****
heart[1] = (byte)bpm; int energyUsed = 3000; heart[3] = energyUsed / 256; heart[2] = energyUsed - (heart[2] * 256); Serial.print("BPM = "); Serial.println(bpm);
// Send Heart Rate Notify heartRateMeasurementCharacteristics.setValue(heart, 8); heartRateMeasurementCharacteristics.notify();
bpm++;
// ___ // SPEED + CADENCE SECTION // ----------------------- // Read Cadence RPM Pins
crankrev = crankrev + 2; lastcrank = 0x7FE;
// That, lastcran in the array position 2 will only show FF or 256 even if lastcrank is set to a uint16_t
// Calculate Speed from cadence //
// Set and Send Speed and Cadence Notify cscmeasurement[1] = crankrev; cscmeasurement[2] = lastcrank; cscMeasurementCharacteristics.setValue(cscmeasurement, 3); cscMeasurementCharacteristics.notify();
cscFeatureCharacteristics.setValue(cscfeature, 1); sensorLocationCharacteristics.setValue(sensorlocation, 1);
//Serial.print("crankrev = "); //Serial.println(crankrev); //Serial.print("lastcrank = "); //Serial.println(lastcrank);
Serial.print(cscmeasurement[0]); //Should be 2 to reflect binary - Yes Serial.print(" "); Serial.print(cscmeasurement[1]); // Should increment by 2 - Yes Serial.print(" "); Serial.println(cscmeasurement[2]); Serial.println(" "); delay(2000); }
`