0

Trying to use battery service on Garmin Speed&Cadence Sensor
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Trying to use battery service on Garmin Speed&Cadence Sensor

by johanneshk on Mon Sep 09, 2019 5:47 am

Hello,
I want to build a simple 'bike distance display'.
I'm currently using an Adafruit Feather nRF52 Bluefruit LE - nRF52832 with a Adafruit 2.13" Monochrome eInk / ePaper Display FeatherWing - 250x122 Monochrome attached.

I can successfully use UUID16_SVC_CYCLING_SPEED_AND_CADENCE and subscribe to UUID16_CHR_CSC_MEASUREMENT but now I want to use UUID16_SVC_BATTERY and read UUID16_CHR_BATTERY_LEVEL, but I cannot seem to get it to work.

Here is my current (messy) code:

Code: Select all | TOGGLE FULL SIZE
#include <bluefruit.h>
#include "Adafruit_EPD.h"
#include "Ticker.h"

#define WHEEL_CIRCUMFERENCE_MM               2100

#define SD_CS       27
#define SRAM_CS     30
#define EPD_CS      31
#define EPD_DC      11   


#define VBATPIN A7

#define EPD_RESET   -1 // can set to -1 and share with microcontroller Reset!
#define EPD_BUSY    -1 // can set to -1 to not use a pin (will wait a fixed delay)                 


BLEClientService        cscs(UUID16_SVC_CYCLING_SPEED_AND_CADENCE);
BLEClientCharacteristic cscc(UUID16_CHR_CSC_MEASUREMENT);

BLEClientService        bs(UUID16_SVC_BATTERY);
BLEClientCharacteristic bsbc(UUID16_CHR_BATTERY_LEVEL);




Adafruit_SSD1675 epd(250, 122, EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY);

float measuredvbat = 5;
void printState();
void serialPrintState();
Ticker stateTicker(printState, 3*60*1000);
Ticker serialStateTicker(serialPrintState, 5000);

uint32_t cumulative_wheel_revs = 0;
uint16_t last_time;


void setup()
{
  Serial.begin(115200);
  Bluefruit.begin(0, 1);
  Bluefruit.setName("----");
 
  cscs.begin();

  // set up callback for receiving measurement
  cscc.setNotifyCallback(csc_notify_callback);
  cscc.begin();

  bs.begin();
 // bsbc.setNotifyCallback(bsbc_notify_callback);
  bsbc.begin();

 
  // Increase Blink rate to different from PrPh advertising mode
  Bluefruit.setConnLedInterval(250);
 
  // Callbacks for Central
  Bluefruit.Central.setDisconnectCallback(disconnect_callback);
  Bluefruit.Central.setConnectCallback(connect_callback);
 
  /* Start Central Scanning
   * - Enable auto scan if disconnected
   * - Interval = 100 ms, window = 80 ms
   * - Don't use active scan
   * - Filter only accept CSC service
   * - Start(timeout) with timeout = 0 will scan forever (until connected)
   */
  Bluefruit.Scanner.setRxCallback(scan_callback);
  Bluefruit.Scanner.restartOnDisconnect(true);
  Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms
  Bluefruit.Scanner.filterUuid(cscs.uuid);
  Bluefruit.Scanner.useActiveScan(false);
  Bluefruit.Scanner.start(0);                   // // 0 = Don't stop scanning after n seconds

 
  epd.begin();
  epd.setTextWrap(true);
  epd.setTextSize(1);
 
  stateTicker.start();
  serialStateTicker.start();


 
}
 
void loop()
{
  stateTicker.update();
  serialStateTicker.update();
}

void printState(){
    Serial.println("print state");
  epd.clearBuffer();
  epd.setCursor(10, 10);
  epd.setTextColor(EPD_BLACK); 
 
  epd.print("Distance: " + String(((float)cumulative_wheel_revs)*WHEEL_CIRCUMFERENCE_MM/10e6) + "km");
  epd.setCursor(10, 20);
  epd.print("Revolutions: " + String(cumulative_wheel_revs));
  epd.setCursor(10, 30);
  epd.print("Sensor Battery Level: " + String(bsbc.read8()));
 
 
  epd.display();
}

void serialPrintState(){
  double last_time_dbl;
  last_time_dbl = ((double)last_time)/1024.;
  Serial.println("CSC Measurement: ");
  Serial.println(cumulative_wheel_revs);
  Serial.println(last_time);
  Serial.println(last_time_dbl);
  Serial.println("Distance");
  Serial.println(((float)cumulative_wheel_revs)*WHEEL_CIRCUMFERENCE_MM/10e6);
  Serial.println(bsbc.read8());
  Serial.println("");

}


 
/**
 * Callback invoked when scanner pick up an advertising data
 * @param report Structural advertising data
 */
void scan_callback(ble_gap_evt_adv_report_t* report)
{
  // Connect to device with CSC service in advertising
  Bluefruit.Central.connect(report);
}
 
/**
 * Callback invoked when an connection is established
 * @param conn_handle
 */
void connect_callback(uint16_t conn_handle)
{
  Serial.println("Connected");
  Serial.println("Discovering CSC Service ... ");
  Serial.println(conn_handle);
 
  // If CSC is not found, disconnect and return
  if ( !bs.discover(conn_handle) )
  {
    Serial.println("BS not found");
  }
  if ( !cscs.discover(conn_handle) )
  {
    Serial.println("CSC not found");
 
    // disconect since we couldn't find HRM service
    //Bluefruit.Central.disconnect(conn_handle);
 
    return;
  }


  // Once CSC service is found, we continue to discover its characteristic
  Serial.println("Found it");
 
 
  Serial.print("Discovering Measurement characteristic ... ");
  if ( !cscc.discover() )
  {
    // Measurement chr is mandatory, if it is not found (valid), then disconnect
    Serial.println("cscc not found !!!"); 
    Serial.println("Measurement characteristic is mandatory but not found");
    //Bluefruit.Central.disconnect(conn_handle);
    return;
  }
 
     Serial.print("Discovering Measurement characteristic ... ");
  if ( !bsbc.discover() )
  {
    // Measurement chr is mandatory, if it is not found (valid), then disconnect
    Serial.println("bsbc not found !!!"); 
  }
  Serial.println("Found it");

 
  // Reaching here means we are ready to go, let's enable notification on measurement chr
  if ( cscc.enableNotify() )
  {
    Serial.println("Ready to receive CSC Measurement value");
  }else
  {
    Serial.println("Couldn't enable notify for CSC Measurement. Increase DEBUG LEVEL for troubleshooting");
  }

}
 
/**
 * Callback invoked when a connection is dropped
 * @param conn_handle
 * @param reason
 */
void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
  (void) conn_handle;
  (void) reason;
 
  Serial.println("Disconnected");
}
 
 
void csc_notify_callback(BLEClientCharacteristic* chr, uint8_t* data, uint16_t len)
{
 
  memcpy(&cumulative_wheel_revs, data+1, 4);
  memcpy(&last_time, data+5, 2);
}
 



When doing "Serial.println(bsbc.read8());" it just prints '0' although when connecting to the sensor with nrf connect on my phone it tells me the battery level is at 85%. I assume I initialize or use the battery service incorrectly, but cannot figure out why...

Also: As I said I'm using https://www.adafruit.com/product/4195 which also seems to use the VBAT pin, so I cannot read VBAT and use the display at the same time? Can I workaround that by disabling the display while reading battery voltage?

Thanks,
Johannes

johanneshk
 
Posts: 5
Joined: Mon Sep 09, 2019 5:38 am

Re: Trying to use battery service on Garmin Speed&Cadence Se

by p2w on Mon Sep 09, 2019 11:24 am

I do something pretty similar (though for a powermeter instead of a speed & cadence sensor). The only real difference... I do all the work in the MEAS callback, not on a timertick. Could it be you're running into timing- or interruptproblems?

p2w
 
Posts: 95
Joined: Fri Dec 15, 2017 5:29 am

Re: Trying to use battery service on Garmin Speed&Cadence Se

by johanneshk on Tue Sep 10, 2019 4:12 am

p2w wrote:I do something pretty similar (though for a powermeter instead of a speed & cadence sensor). The only real difference... I do all the work in the MEAS callback, not on a timertick. Could it be you're running into timing- or interruptproblems?

Hello,
by callback, do you mean the function 'csc_notify_callback'? This callback sets the wheel revolutions, but has nothing to do with battery level? Again, I don't really understand how to add the battery service: I haven't found any example where more than 1 service is used. Only one service and multiple characteristics. I want to use 'read' not 'notify' on the battery level, which I should be able to call from anywhere!?

johanneshk
 
Posts: 5
Joined: Mon Sep 09, 2019 5:38 am

Re: Trying to use battery service on Garmin Speed&Cadence Se

by p2w on Tue Sep 10, 2019 8:18 am

johanneshk wrote:
p2w wrote:I do something pretty similar (though for a powermeter instead of a speed & cadence sensor). The only real difference... I do all the work in the MEAS callback, not on a timertick. Could it be you're running into timing- or interruptproblems?

Hello,
by callback, do you mean the function 'csc_notify_callback'? This callback sets the wheel revolutions, but has nothing to do with battery level? Again, I don't really understand how to add the battery service: I haven't found any example where more than 1 service is used. Only one service and multiple characteristics. I want to use 'read' not 'notify' on the battery level, which I should be able to call from anywhere!?

Yes (though I have no CSCS but CPS). I know it has nothing to do with battery level, but you are trying to do everything from a timer. A timer usually is an interrupt driven thing. Reading the battery level involves sending a read request to the target and waiting for a response. That is never a good idea from a time-restrained part of the code. Disclaimer: I am not familiar with the Ticker object you use, but from its use I assume it basically consists of a managed timer that fires installed handlers.

You could try it in loop() - request the BAS there, than sleep for x seconds. You shouldn't need to read battery power often anyway - it won't change very fast. Once per 10 seconds or so is more than fast enough.

Anyway, on your remark about the number of services: I use four services in my app: device information, battery, cycling power services, Nordic UART. BAS on both, read and notification mechanisms. Works like a charm.

p2w
 
Posts: 95
Joined: Fri Dec 15, 2017 5:29 am

Re: Trying to use battery service on Garmin Speed&Cadence Se

by johanneshk on Tue Sep 10, 2019 10:04 am

p2w wrote:
johanneshk wrote:
p2w wrote:I do something pretty similar (though for a powermeter instead of a speed & cadence sensor). The only real difference... I do all the work in the MEAS callback, not on a timertick. Could it be you're running into timing- or interruptproblems?

Hello,
by callback, do you mean the function 'csc_notify_callback'? This callback sets the wheel revolutions, but has nothing to do with battery level? Again, I don't really understand how to add the battery service: I haven't found any example where more than 1 service is used. Only one service and multiple characteristics. I want to use 'read' not 'notify' on the battery level, which I should be able to call from anywhere!?

Yes (though I have no CSCS but CPS). I know it has nothing to do with battery level, but you are trying to do everything from a timer. A timer usually is an interrupt driven thing. Reading the battery level involves sending a read request to the target and waiting for a response. That is never a good idea from a time-restrained part of the code. Disclaimer: I am not familiar with the Ticker object you use, but from its use I assume it basically consists of a managed timer that fires installed handlers.

You could try it in loop() - request the BAS there, than sleep for x seconds. You shouldn't need to read battery power often anyway - it won't change very fast. Once per 10 seconds or so is more than fast enough.

Anyway, on your remark about the number of services: I use four services in my app: device information, battery, cycling power services, Nordic UART. BAS on both, read and notification mechanisms. Works like a charm.


Good point, I'll try that and if that fails create a minimal (not) working example.
Thanks.

johanneshk
 
Posts: 5
Joined: Mon Sep 09, 2019 5:38 am

Re: Trying to use battery service on Garmin Speed&Cadence Se

by johanneshk on Tue Sep 10, 2019 11:19 am

So I reduced my code to just the bare minimum: The CSC and battery level measurement:

Code: Select all | TOGGLE FULL SIZE
#include <bluefruit.h>

#define WHEEL_CIRCUMFERENCE_MM               2100

BLEClientService        cscs(UUID16_SVC_CYCLING_SPEED_AND_CADENCE);
BLEClientCharacteristic cscc(UUID16_CHR_CSC_MEASUREMENT);

BLEClientService        bs(UUID16_SVC_BATTERY);
BLEClientCharacteristic bsbc(UUID16_CHR_BATTERY_LEVEL);





uint32_t cumulative_wheel_revs = 0;
uint16_t last_time;


void setup()
{
  Serial.begin(115200);
  Bluefruit.begin(0, 1);
  Bluefruit.setName("----");
 
  cscs.begin();

  // set up callback for receiving measurement
  cscc.setNotifyCallback(csc_notify_callback);
  cscc.begin();

  bs.begin();
 // bsbc.setNotifyCallback(bsbc_notify_callback);
  bsbc.begin();

 
  // Increase Blink rate to different from PrPh advertising mode
  Bluefruit.setConnLedInterval(250);
 
  // Callbacks for Central
  Bluefruit.Central.setDisconnectCallback(disconnect_callback);
  Bluefruit.Central.setConnectCallback(connect_callback);
 
  /* Start Central Scanning
   * - Enable auto scan if disconnected
   * - Interval = 100 ms, window = 80 ms
   * - Don't use active scan
   * - Filter only accept CSC service
   * - Start(timeout) with timeout = 0 will scan forever (until connected)
   */
  Bluefruit.Scanner.setRxCallback(scan_callback);
  Bluefruit.Scanner.restartOnDisconnect(true);
  Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms
  Bluefruit.Scanner.filterUuid(cscs.uuid);
  Bluefruit.Scanner.useActiveScan(false);
  Bluefruit.Scanner.start(0);                   // // 0 = Don't stop scanning after n seconds

   
}
 
void loop()
{
  serialPrintState();
  delay(5000);
}



void serialPrintState(){
  double last_time_dbl;
  last_time_dbl = ((double)last_time)/1024.;
  Serial.println("CSC Measurement: ");
  Serial.println(cumulative_wheel_revs);
  Serial.println(last_time);
  Serial.println(last_time_dbl);
  Serial.println("Distance");
  Serial.println(((float)cumulative_wheel_revs)*WHEEL_CIRCUMFERENCE_MM/10e6);
  Serial.print("Battery: ");
  Serial.println(bsbc.read8());
  Serial.println("");

}


 
/**
 * Callback invoked when scanner pick up an advertising data
 * @param report Structural advertising data
 */
void scan_callback(ble_gap_evt_adv_report_t* report)
{
  // Connect to device with CSC service in advertising
  Bluefruit.Central.connect(report);
}
 
/**
 * Callback invoked when an connection is established
 * @param conn_handle
 */
void connect_callback(uint16_t conn_handle)
{

  if ( !bs.discover(conn_handle) )
  {
    Serial.println("BS not found");
  }
  if ( !cscs.discover(conn_handle) )
  {
    Serial.println("CSC not found");
     return;
  }

  if ( !cscc.discover() )
  {
    // Measurement chr is mandatory, if it is not found (valid), then disconnect
    Serial.println("cscc not found !!!"); 
    Serial.println("Measurement characteristic is mandatory but not found");
    //Bluefruit.Central.disconnect(conn_handle);
    return;
  }
 
     Serial.print("Discovering Measurement characteristic ... ");
  if ( !bsbc.discover() )
  {
    // Measurement chr is mandatory, if it is not found (valid), then disconnect
    Serial.println("bsbc not found !!!"); 
  }
  Serial.println("Found it");

 
  // Reaching here means we are ready to go, let's enable notification on measurement chr
  if ( cscc.enableNotify() )
  {
    Serial.println("Ready to receive CSC Measurement value");
  }else
  {
    Serial.println("Couldn't enable notify for CSC Measurement. Increase DEBUG LEVEL for troubleshooting");
  }

  Serial.println("Connected");
}
 
/**
 * Callback invoked when a connection is dropped
 * @param conn_handle
 * @param reason
 */
void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
  (void) conn_handle;
  (void) reason;
 
  Serial.println("Disconnected");
}
 
 
void csc_notify_callback(BLEClientCharacteristic* chr, uint8_t* data, uint16_t len)
{
 
  memcpy(&cumulative_wheel_revs, data+1, 4);
  memcpy(&last_time, data+5, 2);
}
 


Still, 'bsbc.read8()' just returns '0', example print:
Code: Select all | TOGGLE FULL SIZE
CSC Measurement:
55730
57358
56.01
Distance
11.70
Battery: 0


I removed the ticker code. I still think that I somehow setup 'bsbc' incorrectly?
Thanks.

johanneshk
 
Posts: 5
Joined: Mon Sep 09, 2019 5:38 am

Please be positive and constructive with your questions and comments.