1

I've set up a Nano 33 IOT as a central device and I'm attempting to read the data from a Cycling speed and cadence BLE device.

The spec for the CSC Gatt service I want is: https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.cycling_speed_and_cadence.xml

And the characteristic: https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.csc_measurement.xml

I've tried a few different approaches, this one seems like it should get me the closest. I'm afraid I'm feeling around in the dark and hope someone can give me a definitive answer of how to read these values because I'm not even sure what they SHOULD look like even if I do somehow manage to read them correctly!

I initially tried using this code to read the values, which seemed to get me reasonably close, but seeing as there are different data types and I could just as easily read 32 values, I don't believe it to be correct.

uint16_t cscValues[16];
cscCharacteristic.readValue(cscValues, 16);

for(int i = 0; i < 16; i++){
    Serial.print(i);
    Serial.print(":");
    Serial.print(cscValues[i]);
    Serial.print(" ");
}
Serial.println("");

this gives the output:

//not moving:
10:50:49.401 -> 0:21249 1:0 2:11008 3:8314 4:556 5:8192 6:25637 7:0 8:0 9:0 10:704 11:8192 12:568 13:8192 14:22485 15:0 
10:50:50.409 -> 0:21249 1:0 2:11008 3:8314 4:556 5:8192 6:25637 7:0 8:0 9:0 10:704 11:8192 12:568 13:8192 14:22485 15:0 

//moving:
10:51:07.518 -> 0:24577 1:0 2:8448 3:8338 4:556 5:8192 6:25637 7:0 8:0 9:0 10:704 11:8192 12:568 13:8192 14:22485 15:0 
10:51:08.513 -> 0:25345 1:0 2:36864 3:8342 4:556 5:8192 6:25637 7:0 8:0 9:0 10:704 11:8192 12:568 13:8192 14:22485 15:0 
10:51:09.490 -> 0:26113 1:0 2:6144 3:8347 4:556 5:8192 6:25637 7:0 8:0 9:0 10:704 11:8192 12:568 13:8192 14:22485 15:0 

The method I'm currently using is based off of a Arduino forum post I found: https://forum.arduino.cc/index.php?topic=711228.0

My full sketch:

#include <ArduinoBLE.h>

#define CSC_DATA_SIZE 16

typedef struct __attribute__( ( packed ) )
{
  int32_t cum_wheel_rev;
  int16_t last_wheel_event;
  int16_t cum_cranks;
  int16_t last_crank_event;  
} csc_data_t;

typedef union
{
  csc_data_t values;
  uint8_t bytes[ CSC_DATA_SIZE ];
} csc_data_ut;

csc_data_ut cscData;

void setup() {

  Serial.begin(9600);

  //wait until the serial port is connected for debugging
  while (!Serial);

  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");

    while (1);
  }

  Serial.println("BLE Central scan");

  // start scanning for peripheral
  BLE.scanForUuid("1816"); // Cycle speed and cadence UUID  
}

void loop() {
  //Read BLE devices

  // check if a peripheral has been discovered
  BLEDevice peripheral = BLE.available();

  if (peripheral) {
    // discovered a peripheral
    Serial.println("Discovered a peripheral");
    Serial.println("-----------------------");

    // print address
    Serial.print("Address: ");
    Serial.println(peripheral.address());

    // print the local name, if present
    if (peripheral.hasLocalName()) {
      Serial.print("Local Name: ");
      Serial.println(peripheral.localName());
    }

    // print the advertised service UUIDs, if present
    if (peripheral.hasAdvertisedServiceUuid()) {
      Serial.print("Service UUIDs: ");
      for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) {
        Serial.print(peripheral.advertisedServiceUuid(i));
        Serial.print(" ");
      }
      Serial.println();
    }

    // print the RSSI
    Serial.print("RSSI: ");
    Serial.println(peripheral.rssi());

    Serial.println();

    // stop scanning
    BLE.stopScan();  

    getSpeed(peripheral);

    // peripheral disconnected, start scanning again
    BLE.scanForUuid("1816"); // Cycle speed and cadence UUID
  }
}

void getSpeed(BLEDevice peripheral) {
  // connect to the peripheral
  Serial.println("Connecting ...");

  if (peripheral.connect()) {
    Serial.println("Connected");
  } else {
    Serial.println("Failed to connect!");
    return;
  }

  // discover peripheral attributes
  Serial.println("Discovering attributes ...");
  if (peripheral.discoverAttributes()) {
    Serial.println("Attributes discovered");
  } else {
    Serial.println("Attribute discovery failed!");
    peripheral.disconnect();
    return;
  }

  // retrieve the LED characteristic
  BLECharacteristic cscCharacteristic = peripheral.characteristic("2A5B");

  if (!cscCharacteristic) {
    Serial.println("Peripheral does not have CSC characteristic!");
    peripheral.disconnect();
    return;
  } 

  if (!cscCharacteristic.canSubscribe()) {
     Serial.println("canSubscribe False");
     
  } else {
  if(!cscCharacteristic.subscribe()) {
    
    Serial.println("subscription Failed");
    }else {
      Serial.println("subscribed!");
    }
  }

  int totalWheelRevolutions = 0;
  int wheelDiameter = 750; // in mm

  while (peripheral.connected()) {
    //happens every second.
    if (cscCharacteristic.valueUpdated()) {
      if (cscCharacteristic.valueLength() > 0) {
          cscCharacteristic.readValue(m5600Data.bytes, M5600_DATA_SIZE);

          Serial.print(" cum_wheel_rev: ");
          Serial.print(cscData.values.cum_wheel_rev);
          Serial.print(" last_wheel_event: ");
          Serial.print(cscData.values.last_wheel_event);
          Serial.print(" cum_cranks: ");
          Serial.print(cscData.values.cum_cranks);
          Serial.print(" last_crank_event: ");
          Serial.print(cscData.values.last_crank_event);
          Serial.println("");
      }
    }
  }

  Serial.println("Peripheral disconnected");
}

The output is:

10:42:15.427 -> BLE Central scan
10:42:16.193 -> Discovered a peripheral
10:42:16.193 -> -----------------------
10:42:16.193 -> Address: df:f4:55:1e:16:27
10:42:16.193 -> Local Name: XOSS S-021477
10:42:16.193 -> Service UUIDs: 1816 
10:42:16.193 -> RSSI: -72
10:42:16.193 -> 
10:42:16.193 -> Connecting ...
10:42:16.703 -> Connected
10:42:16.703 -> Discovering attributes ...
10:42:17.473 -> Attributes discovered
10:42:17.508 -> subscribed!
//moving:
10:42:18.524 ->  cum_wheel_rev: 11009 last_wheel_event: -32256 cum_cranks: 251 last_crank_event: 0
10:42:19.529 ->  cum_wheel_rev: 11265 last_wheel_event: -19968 cum_cranks: 7 last_crank_event: 0
10:42:20.511 ->  cum_wheel_rev: 11777 last_wheel_event: -10752 cum_cranks: 21 last_crank_event: 0
10:42:21.520 ->  cum_wheel_rev: 12033 last_wheel_event: -512 cum_cranks: 26 last_crank_event: 0
10:42:22.636 ->  cum_wheel_rev: 12545 last_wheel_event: -6912 cum_cranks: 31 last_crank_event: 0
10:42:23.764 ->  cum_wheel_rev: 13057 last_wheel_event: 4608 cum_cranks: 37 last_crank_event: 0
10:42:24.747 ->  cum_wheel_rev: 13313 last_wheel_event: -25600 cum_cranks: 39 last_crank_event: 0
10:42:25.615 ->  cum_wheel_rev: 13825 last_wheel_event: 2816 cum_cranks: 44 last_crank_event: 0
10:42:26.631 ->  cum_wheel_rev: 14337 last_wheel_event: -11264 cum_cranks: 48 last_crank_event: 0
10:42:27.610 ->  cum_wheel_rev: 14849 last_wheel_event: -17664 cum_cranks: 53 last_crank_event: 0
10:42:28.635 ->  cum_wheel_rev: 15361 last_wheel_event: -12288 cum_cranks: 57 last_crank_event: 0
10:42:29.616 ->  cum_wheel_rev: 15873 last_wheel_event: -18432 cum_cranks: 61 last_crank_event: 0
10:42:30.642 ->  cum_wheel_rev: 16385 last_wheel_event: -29696 cum_cranks: 65 last_crank_event: 0
10:42:31.619 ->  cum_wheel_rev: 16641 last_wheel_event: -21248 cum_cranks: 67 last_crank_event: 0
10:42:32.612 ->  cum_wheel_rev: 16897 last_wheel_event: 23040 cum_cranks: 70 last_crank_event: 0
10:42:33.642 ->  cum_wheel_rev: 17665 last_wheel_event: -7168 cum_cranks: 77 last_crank_event: 0
10:42:34.619 ->  cum_wheel_rev: 18177 last_wheel_event: 18944 cum_cranks: 81 last_crank_event: 0
10:42:35.637 ->  cum_wheel_rev: 18945 last_wheel_event: 11264 cum_cranks: 86 last_crank_event: 0
10:42:36.622 ->  cum_wheel_rev: 19457 last_wheel_event: -29440 cum_cranks: 89 last_crank_event: 0
10:42:37.616 ->  cum_wheel_rev: 19969 last_wheel_event: -5888 cum_cranks: 92 last_crank_event: 0

//not moving:
10:42:38.623 ->  cum_wheel_rev: 20481 last_wheel_event: 20224 cum_cranks: 96 last_crank_event: 0
10:42:39.608 ->  cum_wheel_rev: 20481 last_wheel_event: 20224 cum_cranks: 96 last_crank_event: 0
10:42:40.624 ->  cum_wheel_rev: 20481 last_wheel_event: 20224 cum_cranks: 96 last_crank_event: 0
10:42:41.640 ->  cum_wheel_rev: 20481 last_wheel_event: 20224 cum_cranks: 96 last_crank_event: 0
10:42:42.620 ->  cum_wheel_rev: 20481 last_wheel_event: 20224 cum_cranks: 96 last_crank_event: 0
10:42:43.642 ->  cum_wheel_rev: 20481 last_wheel_event: 20224 cum_cranks: 96 last_crank_event: 0
10:42:44.622 ->  cum_wheel_rev: 20481 last_wheel_event: 20224 cum_cranks: 96 last_crank_event: 0
10:42:45.638 ->  cum_wheel_rev: 20481 last_wheel_event: 20224 cum_cranks: 96 last_crank_event: 0

I'm well versed in programming, but new to Arduino. Any help would be extremely valuable!

Thank you!

1
  • So I've managed to realise I was missing the flags in my data. So by adding 'int8_t flags' to the attributes I got it to work. Kind of... I still need to adjust for when fields should be read or not. and I've got some idea's but I'm not sure of the best way to do that. ``` typedef struct __attribute__( ( packed ) ) { int8_t flags; int32_t cum_wheel_rev; int16_t last_wheel_event; int16_t cum_cranks; int16_t last_crank_event; } csc_data_t; ``` – Brian Dec 14 '20 at 20:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.