cscsensor 0.06: Now read wheel rev as well as cadence sensor

Improve connection code
master
Gordon Williams 2021-11-26 10:04:02 +00:00
parent 095165c5c9
commit a4299586e8
4 changed files with 86 additions and 74 deletions

View File

@ -2948,7 +2948,7 @@
"id": "cscsensor", "id": "cscsensor",
"name": "Cycling speed sensor", "name": "Cycling speed sensor",
"shortName": "CSCSensor", "shortName": "CSCSensor",
"version": "0.05", "version": "0.06",
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch", "description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
"icon": "icons8-cycling-48.png", "icon": "icons8-cycling-48.png",
"tags": "outdoors,exercise,ble,bluetooth", "tags": "outdoors,exercise,ble,bluetooth",

View File

@ -3,3 +3,5 @@
0.03: Save total distance traveled 0.03: Save total distance traveled
0.04: Add sensor battery level indicator 0.04: Add sensor battery level indicator
0.05: Add cadence sensor support 0.05: Add cadence sensor support
0.06: Now read wheel rev as well as cadence sensor
Improve connection code

View File

@ -9,10 +9,16 @@ Currently the app displays the following data:
- maximum speed - maximum speed
- trip distance traveled - trip distance traveled
- total distance traveled - total distance traveled
- an icon with the battery status of the remote sensor - an icon with the battery status of the remote sensor
Button 1 resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app. Button 1 resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app.
If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 will attempt to reconnect to the sensor. If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 will attempt to reconnect to the sensor.
Button 2 switches between the display for cycling speed and cadence. Button 2 switches between the display for cycling speed and cadence.
Values displayed are imperial or metric (depending on locale), cadence is in RPM, the wheel circumference can be adjusted in the global settings app. Values displayed are imperial or metric (depending on locale), cadence is in RPM, the wheel circumference can be adjusted in the global settings app.
# TODO
* Use Layout Library to provide proper Bangle.js 2 support
* Turn CSC sensor support into a library
* Support for `Recorder` app, to allow CSC readings to be logged alongside GPS

View File

@ -5,6 +5,8 @@ var characteristic;
const SETTINGS_FILE = 'cscsensor.json'; const SETTINGS_FILE = 'cscsensor.json';
const storage = require('Storage'); const storage = require('Storage');
const W = g.getWidth();
const H = g.getHeight();
class CSCSensor { class CSCSensor {
constructor() { constructor() {
@ -75,7 +77,7 @@ class CSCSensor {
var dist = this.distFactor*(this.lastRevs-this.lastRevsStart)*this.wheelCirc/63360.0; var dist = this.distFactor*(this.lastRevs-this.lastRevsStart)*this.wheelCirc/63360.0;
var ddist = Math.round(100*dist)/100; var ddist = Math.round(100*dist)/100;
var tdist = Math.round(this.distFactor*this.totaldist*10)/10; var tdist = Math.round(this.distFactor*this.totaldist*10)/10;
var dspeed = Math.round(10*this.distFactor*this.speed)/10; var dspeed = Math.round(10*this.distFactor*this.speed)/10;
var dmins = Math.floor(this.movingTime/60).toString(); var dmins = Math.floor(this.movingTime/60).toString();
if (dmins.length<2) dmins = "0"+dmins; if (dmins.length<2) dmins = "0"+dmins;
var dsecs = (Math.floor(this.movingTime) % 60).toString(); var dsecs = (Math.floor(this.movingTime) % 60).toString();
@ -152,7 +154,7 @@ class CSCSensor {
var qChanged = false; var qChanged = false;
if (event.target.uuid == "0x2a5b") { if (event.target.uuid == "0x2a5b") {
if (event.target.value.getUint8(0, true) & 0x2) { if (event.target.value.getUint8(0, true) & 0x2) {
// crank revolution // crank revolution - if enabled
const crankRevs = event.target.value.getUint16(1, true); const crankRevs = event.target.value.getUint16(1, true);
const crankTime = event.target.value.getUint16(3, true); const crankTime = event.target.value.getUint16(3, true);
if (crankTime > this.lastCrankTime) { if (crankTime > this.lastCrankTime) {
@ -161,44 +163,43 @@ class CSCSensor {
} }
this.lastCrankRevs = crankRevs; this.lastCrankRevs = crankRevs;
this.lastCrankTime = crankTime; this.lastCrankTime = crankTime;
} else {
// wheel revolution
var wheelRevs = event.target.value.getUint32(1, true);
var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0);
if (dRevs>0) {
qChanged = true;
this.totaldist += dRevs*this.wheelCirc/63360.0;
if ((this.totaldist-this.settings.totaldist)>0.1) {
this.settings.totaldist = this.totaldist;
storage.writeJSON(SETTINGS_FILE, this.settings);
}
}
this.lastRevs = wheelRevs;
if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs;
var wheelTime = event.target.value.getUint16(5, true);
var dT = (wheelTime-this.lastTime)/1024;
var dBT = (Date.now()-this.lastBangleTime)/1000;
this.lastBangleTime = Date.now();
if (dT<0) dT+=64;
if (Math.abs(dT-dBT)>3) dT = dBT;
this.lastTime = wheelTime;
this.speed = this.lastSpeed;
if (dRevs>0 && dT>0) {
this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT;
this.speedFailed = 0;
this.movingTime += dT;
}
else {
this.speedFailed++;
qChanged = false;
if (this.speedFailed>3) {
this.speed = 0;
qChanged = (this.lastSpeed>0);
}
}
this.lastSpeed = this.speed;
if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed;
} }
// wheel revolution
var wheelRevs = event.target.value.getUint32(1, true);
var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0);
if (dRevs>0) {
qChanged = true;
this.totaldist += dRevs*this.wheelCirc/63360.0;
if ((this.totaldist-this.settings.totaldist)>0.1) {
this.settings.totaldist = this.totaldist;
storage.writeJSON(SETTINGS_FILE, this.settings);
}
}
this.lastRevs = wheelRevs;
if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs;
var wheelTime = event.target.value.getUint16(5, true);
var dT = (wheelTime-this.lastTime)/1024;
var dBT = (Date.now()-this.lastBangleTime)/1000;
this.lastBangleTime = Date.now();
if (dT<0) dT+=64;
if (Math.abs(dT-dBT)>3) dT = dBT;
this.lastTime = wheelTime;
this.speed = this.lastSpeed;
if (dRevs>0 && dT>0) {
this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT;
this.speedFailed = 0;
this.movingTime += dT;
}
else {
this.speedFailed++;
qChanged = false;
if (this.speedFailed>3) {
this.speed = 0;
qChanged = (this.lastSpeed>0);
}
}
this.lastSpeed = this.speed;
if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed;
} }
if (qChanged && this.qUpdateScreen) this.updateScreen(); if (qChanged && this.qUpdateScreen) this.updateScreen();
} }
@ -215,44 +216,47 @@ function getSensorBatteryLevel(gatt) {
}); });
} }
function parseDevice(d) {
device = d;
g.clearRect(0, 60, 239, 239).setFontAlign(0, 0, 0).setColor(0, 1, 0).drawString("Found device", 120, 120).flip();
device.gatt.connect().then(function(ga) {
gatt = ga;
g.clearRect(0, 60, 239, 239).setFontAlign(0, 0, 0).setColor(0, 1, 0).drawString("Connected", 120, 120).flip();
return gatt.getPrimaryService("1816");
}).then(function(s) {
service = s;
return service.getCharacteristic("2a5b");
}).then(function(c) {
characteristic = c;
characteristic.on('characteristicvaluechanged', (event)=>mySensor.updateSensor(event));
return characteristic.startNotifications();
}).then(function() {
console.log("Done!");
g.clearRect(0, 60, 239, 239).setColor(1, 1, 1).flip();
getSensorBatteryLevel(gatt);
mySensor.updateScreen();
}).catch(function(e) {
g.clearRect(0, 60, 239, 239).setColor(1, 0, 0).setFontAlign(0, 0, 0).drawString("ERROR"+e, 120, 120).flip();
console.log(e);
})}
function connection_setup() { function connection_setup() {
NRF.setScan();
mySensor.screenInit = true; mySensor.screenInit = true;
NRF.setScan(parseDevice, { filters: [{services:["1816"]}], timeout: 2000}); E.showMessage("Scanning for CSC sensor...");
g.clearRect(0, 48, 239, 239).setFontVector(18).setFontAlign(0, 0, 0).setColor(0, 1, 0); NRF.requestDevice({ filters: [{services:["1816"]}]}).then(function(d) {
g.drawString("Scanning for CSC sensor...", 120, 120); device = d;
E.showMessage("Found device");
return device.gatt.connect();
}).then(function(ga) {
gatt = ga;
E.showMessage("Connected");
return gatt.getPrimaryService("1816");
}).then(function(s) {
service = s;
return service.getCharacteristic("2a5b");
}).then(function(c) {
characteristic = c;
characteristic.on('characteristicvaluechanged', (event)=>mySensor.updateSensor(event));
return characteristic.startNotifications();
}).then(function() {
console.log("Done!");
g.reset().clearRect(Bangle.appRect).flip();
getSensorBatteryLevel(gatt);
mySensor.updateScreen();
}).catch(function(e) {
E.showMessage(e.toString(), "ERROR");
console.log(e);
});
} }
connection_setup(); connection_setup();
setWatch(function() { mySensor.reset(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN1, {repeat:true, debounce:20}); E.on('kill',()=>{
E.on('kill',()=>{ if (gatt!=undefined) gatt.disconnect(); mySensor.settings.totaldist = mySensor.totaldist; storage.writeJSON(SETTINGS_FILE, mySensor.settings); }); if (gatt!=undefined) gatt.disconnect();
setWatch(function() { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }, BTN3, {repeat:true, debounce:20}); mySensor.settings.totaldist = mySensor.totaldist;
setWatch(function() { mySensor.toggleDisplayCadence(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN2, {repeat:true, debounce:20}); storage.writeJSON(SETTINGS_FILE, mySensor.settings);
NRF.on('disconnect', connection_setup); });
NRF.on('disconnect', connection_setup); // restart if disconnected
Bangle.setUI("updown", d=>{
if (d<0) { mySensor.reset(); g.clearRect(0, 48, W, H); mySensor.updateScreen(); }
if (d==0) { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }
if (d>0) { mySensor.toggleDisplayCadence(); g.clearRect(0, 48, W, H); mySensor.updateScreen(); }
});
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();