Merge pull request #1562 from nxdefiant/master

cscsensor: Make BangleJS2 compatible
master
Gordon Williams 2022-03-14 09:55:05 +00:00 committed by GitHub
commit 1c93a46f82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 88 additions and 80 deletions

View File

@ -5,3 +5,4 @@
0.05: Add cadence sensor support 0.05: Add cadence sensor support
0.06: Now read wheel rev as well as cadence sensor 0.06: Now read wheel rev as well as cadence sensor
Improve connection code Improve connection code
0.07: Make Bangle.js 2 compatible

View File

@ -11,9 +11,9 @@ Currently the app displays the following data:
- 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 (swipe up on Bangle.js 2) 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 (swipe down on Bangle.js 2) will attempt to reconnect to the sensor.
Button 2 switches between the display for cycling speed and cadence. Button 2 (tap on Bangle.js 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.

View File

@ -7,6 +7,11 @@ const SETTINGS_FILE = 'cscsensor.json';
const storage = require('Storage'); const storage = require('Storage');
const W = g.getWidth(); const W = g.getWidth();
const H = g.getHeight(); const H = g.getHeight();
const yStart = 48;
const rowHeight = (H-yStart)/6;
const yCol1 = W/2.7586;
const fontSizeLabel = W/12.632;
const fontSizeValue = W/9.2308;
class CSCSensor { class CSCSensor {
constructor() { constructor() {
@ -22,7 +27,6 @@ class CSCSensor {
this.speed = 0; this.speed = 0;
this.maxSpeed = 0; this.maxSpeed = 0;
this.lastSpeed = 0; this.lastSpeed = 0;
this.qUpdateScreen = true;
this.lastRevsStart = -1; this.lastRevsStart = -1;
this.qMetric = !require("locale").speed(1).toString().endsWith("mph"); this.qMetric = !require("locale").speed(1).toString().endsWith("mph");
this.speedUnit = this.qMetric ? "km/h" : "mph"; this.speedUnit = this.qMetric ? "km/h" : "mph";
@ -49,6 +53,7 @@ class CSCSensor {
toggleDisplayCadence() { toggleDisplayCadence() {
this.showCadence = !this.showCadence; this.showCadence = !this.showCadence;
this.screenInit = true; this.screenInit = true;
g.setBgColor(0, 0, 0);
} }
setBatteryLevel(level) { setBatteryLevel(level) {
@ -63,14 +68,16 @@ class CSCSensor {
} }
drawBatteryIcon() { drawBatteryIcon() {
g.setColor(1, 1, 1).drawRect(10, 55, 20, 75).fillRect(14, 53, 16, 55).setColor(0).fillRect(11, 56, 19, 74); g.setColor(1, 1, 1).drawRect(10*W/240, yStart+0.029167*H, 20*W/240, yStart+0.1125*H)
.fillRect(14*W/240, yStart+0.020833*H, 16*W/240, yStart+0.029167*H)
.setColor(0).fillRect(11*W/240, yStart+0.033333*H, 19*W/240, yStart+0.10833*H);
if (this.batteryLevel!=-1) { if (this.batteryLevel!=-1) {
if (this.batteryLevel<25) g.setColor(1, 0, 0); if (this.batteryLevel<25) g.setColor(1, 0, 0);
else if (this.batteryLevel<50) g.setColor(1, 0.5, 0); else if (this.batteryLevel<50) g.setColor(1, 0.5, 0);
else g.setColor(0, 1, 0); else g.setColor(0, 1, 0);
g.fillRect(11, 74-18*this.batteryLevel/100, 19, 74); g.fillRect(11*W/240, (yStart+0.10833*H)-18*this.batteryLevel/100, 19*W/240, yStart+0.10833*H);
} }
else g.setFontVector(14).setFontAlign(0, 0, 0).setColor(0xffff).drawString("?", 16, 66); else g.setFontVector(W/17.143).setFontAlign(0, 0, 0).setColor(0xffff).drawString("?", 16*W/240, yStart+0.075*H);
} }
updateScreenRevs() { updateScreenRevs() {
@ -88,36 +95,36 @@ class CSCSensor {
for (var i=0; i<6; ++i) { for (var i=0; i<6; ++i) {
if ((i&1)==0) g.setColor(0, 0, 0); if ((i&1)==0) g.setColor(0, 0, 0);
else g.setColor(0x30cd); else g.setColor(0x30cd);
g.fillRect(0, 48+i*32, 86, 48+(i+1)*32); g.fillRect(0, yStart+i*rowHeight, yCol1-1, yStart+(i+1)*rowHeight);
if ((i&1)==1) g.setColor(0); if ((i&1)==1) g.setColor(0);
else g.setColor(0x30cd); else g.setColor(0x30cd);
g.fillRect(87, 48+i*32, 239, 48+(i+1)*32); g.fillRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight);
g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239);//.drawRect(0, 48, 87, 239); g.setColor(0.5, 0.5, 0.5).drawRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight).drawLine(0, H-1, W-1, H-1);
g.moveTo(0, 80).lineTo(30, 80).lineTo(30, 48).lineTo(87, 48).lineTo(87, 239).lineTo(0, 239).lineTo(0, 80); g.moveTo(0, yStart+0.13333*H).lineTo(30*W/240, yStart+0.13333*H).lineTo(30*W/240, yStart).lineTo(yCol1, yStart).lineTo(yCol1, H-1).lineTo(0, H-1).lineTo(0, yStart+0.13333*H);
} }
g.setFontAlign(1, 0, 0).setFontVector(19).setColor(1, 1, 0); g.setFontAlign(1, 0, 0).setFontVector(fontSizeLabel).setColor(1, 1, 0);
g.drawString("Time:", 87, 66); g.drawString("Time:", yCol1, yStart+rowHeight/2+0*rowHeight);
g.drawString("Speed:", 87, 98); g.drawString("Speed:", yCol1, yStart+rowHeight/2+1*rowHeight);
g.drawString("Ave spd:", 87, 130); g.drawString("Avg spd:", yCol1, yStart+rowHeight/2+2*rowHeight);
g.drawString("Max spd:", 87, 162); g.drawString("Max spd:", yCol1, yStart+rowHeight/2+3*rowHeight);
g.drawString("Trip:", 87, 194); g.drawString("Trip:", yCol1, yStart+rowHeight/2+4*rowHeight);
g.drawString("Total:", 87, 226); g.drawString("Total:", yCol1, yStart+rowHeight/2+5*rowHeight);
this.drawBatteryIcon(); this.drawBatteryIcon();
this.screenInit = false; this.screenInit = false;
} }
g.setFontAlign(-1, 0, 0).setFontVector(26); g.setFontAlign(-1, 0, 0).setFontVector(fontSizeValue);
g.setColor(0x30cd).fillRect(88, 49, 238, 79); g.setColor(0x30cd).fillRect(yCol1+1, 49+rowHeight*0, 238, 47+1*rowHeight);
g.setColor(0xffff).drawString(dmins+":"+dsecs, 92, 66); g.setColor(0xffff).drawString(dmins+":"+dsecs, yCol1+5, 50+rowHeight/2+0*rowHeight);
g.setColor(0).fillRect(88, 81, 238, 111); g.setColor(0).fillRect(yCol1+1, 49+rowHeight*1, 238, 47+2*rowHeight);
g.setColor(0xffff).drawString(dspeed+" "+this.speedUnit, 92, 98); g.setColor(0xffff).drawString(dspeed+" "+this.speedUnit, yCol1+5, 50+rowHeight/2+1*rowHeight);
g.setColor(0x30cd).fillRect(88, 113, 238, 143); g.setColor(0x30cd).fillRect(yCol1+1, 49+rowHeight*2, 238, 47+3*rowHeight);
g.setColor(0xffff).drawString(avespeed + " " + this.speedUnit, 92, 130); g.setColor(0xffff).drawString(avespeed + " " + this.speedUnit, yCol1+5, 50+rowHeight/2+2*rowHeight);
g.setColor(0).fillRect(88, 145, 238, 175); g.setColor(0).fillRect(yCol1+1, 49+rowHeight*3, 238, 47+4*rowHeight);
g.setColor(0xffff).drawString(maxspeed + " " + this.speedUnit, 92, 162); g.setColor(0xffff).drawString(maxspeed + " " + this.speedUnit, yCol1+5, 50+rowHeight/2+3*rowHeight);
g.setColor(0x30cd).fillRect(88, 177, 238, 207); g.setColor(0x30cd).fillRect(yCol1+1, 49+rowHeight*4, 238, 47+5*rowHeight);
g.setColor(0xffff).drawString(ddist + " " + this.distUnit, 92, 194); g.setColor(0xffff).drawString(ddist + " " + this.distUnit, yCol1+5, 50+rowHeight/2+4*rowHeight);
g.setColor(0).fillRect(88, 209, 238, 238); g.setColor(0).fillRect(yCol1+1, 49+rowHeight*5, 238, 47+6*rowHeight);
g.setColor(0xffff).drawString(tdist + " " + this.distUnit, 92, 226); g.setColor(0xffff).drawString(tdist + " " + this.distUnit, yCol1+5, 50+rowHeight/2+5*rowHeight);
} }
updateScreenCadence() { updateScreenCadence() {
@ -125,21 +132,21 @@ class CSCSensor {
for (var i=0; i<2; ++i) { for (var i=0; i<2; ++i) {
if ((i&1)==0) g.setColor(0, 0, 0); if ((i&1)==0) g.setColor(0, 0, 0);
else g.setColor(0x30cd); else g.setColor(0x30cd);
g.fillRect(0, 48+i*32, 86, 48+(i+1)*32); g.fillRect(0, yStart+i*rowHeight, yCol1-1, yStart+(i+1)*rowHeight);
if ((i&1)==1) g.setColor(0); if ((i&1)==1) g.setColor(0);
else g.setColor(0x30cd); else g.setColor(0x30cd);
g.fillRect(87, 48+i*32, 239, 48+(i+1)*32); g.fillRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight);
g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239);//.drawRect(0, 48, 87, 239); g.setColor(0.5, 0.5, 0.5).drawRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight).drawLine(0, H-1, W-1, H-1);
g.moveTo(0, 80).lineTo(30, 80).lineTo(30, 48).lineTo(87, 48).lineTo(87, 239).lineTo(0, 239).lineTo(0, 80); g.moveTo(0, yStart+0.13333*H).lineTo(30*W/240, yStart+0.13333*H).lineTo(30*W/240, yStart).lineTo(yCol1, yStart).lineTo(yCol1, H-1).lineTo(0, H-1).lineTo(0, yStart+0.13333*H);
} }
g.setFontAlign(1, 0, 0).setFontVector(19).setColor(1, 1, 0); g.setFontAlign(1, 0, 0).setFontVector(fontSizeLabel).setColor(1, 1, 0);
g.drawString("Cadence:", 87, 98); g.drawString("Cadence:", yCol1, yStart+rowHeight/2+1*rowHeight);
this.drawBatteryIcon(); this.drawBatteryIcon();
this.screenInit = false; this.screenInit = false;
} }
g.setFontAlign(-1, 0, 0).setFontVector(26); g.setFontAlign(-1, 0, 0).setFontVector(fontSizeValue);
g.setColor(0).fillRect(88, 81, 238, 111); g.setColor(0).fillRect(yCol1+1, 49+rowHeight*1, 238, 47+2*rowHeight);
g.setColor(0xffff).drawString(Math.round(this.cadence), 92, 98); g.setColor(0xffff).drawString(Math.round(this.cadence), yCol1+5, 50+rowHeight/2+1*rowHeight);
} }
updateScreen() { updateScreen() {
@ -163,45 +170,45 @@ class CSCSensor {
} }
this.lastCrankRevs = crankRevs; this.lastCrankRevs = crankRevs;
this.lastCrankTime = crankTime; this.lastCrankTime = crankTime;
} } else {
// wheel revolution // wheel revolution
var wheelRevs = event.target.value.getUint32(1, true); var wheelRevs = event.target.value.getUint32(1, true);
var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0); var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0);
if (dRevs>0) { if (dRevs>0) {
qChanged = true; qChanged = true;
this.totaldist += dRevs*this.wheelCirc/63360.0; this.totaldist += dRevs*this.wheelCirc/63360.0;
if ((this.totaldist-this.settings.totaldist)>0.1) { if ((this.totaldist-this.settings.totaldist)>0.1) {
this.settings.totaldist = this.totaldist; this.settings.totaldist = this.totaldist;
storage.writeJSON(SETTINGS_FILE, this.settings); storage.writeJSON(SETTINGS_FILE, this.settings);
}
} }
} this.lastRevs = wheelRevs;
this.lastRevs = wheelRevs; if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs;
if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs; var wheelTime = event.target.value.getUint16(5, true);
var wheelTime = event.target.value.getUint16(5, true); var dT = (wheelTime-this.lastTime)/1024;
var dT = (wheelTime-this.lastTime)/1024; var dBT = (Date.now()-this.lastBangleTime)/1000;
var dBT = (Date.now()-this.lastBangleTime)/1000; this.lastBangleTime = Date.now();
this.lastBangleTime = Date.now(); if (dT<0) dT+=64;
if (dT<0) dT+=64; if (Math.abs(dT-dBT)>3) dT = dBT;
if (Math.abs(dT-dBT)>3) dT = dBT; this.lastTime = wheelTime;
this.lastTime = wheelTime; this.speed = this.lastSpeed;
this.speed = this.lastSpeed; if (dRevs>0 && dT>0) {
if (dRevs>0 && dT>0) { this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT;
this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT; this.speedFailed = 0;
this.speedFailed = 0; this.movingTime += dT;
this.movingTime += dT; } else if (!this.showCadence) {
} this.speedFailed++;
else { qChanged = false;
this.speedFailed++; if (this.speedFailed>3) {
qChanged = false; this.speed = 0;
if (this.speedFailed>3) { qChanged = (this.lastSpeed>0);
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;
} }
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.updateScreen();
} }
} }
@ -253,9 +260,9 @@ E.on('kill',()=>{
}); });
NRF.on('disconnect', connection_setup); // restart if disconnected NRF.on('disconnect', connection_setup); // restart if disconnected
Bangle.setUI("updown", d=>{ Bangle.setUI("updown", d=>{
if (d<0) { mySensor.reset(); g.clearRect(0, 48, W, H); mySensor.updateScreen(); } if (d<0) { mySensor.reset(); g.clearRect(0, yStart, W, H); mySensor.updateScreen(); }
if (d==0) { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); } else if (d>0) { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }
if (d>0) { mySensor.toggleDisplayCadence(); g.clearRect(0, 48, W, H); mySensor.updateScreen(); } else { mySensor.toggleDisplayCadence(); g.clearRect(0, yStart, W, H); mySensor.updateScreen(); }
}); });
Bangle.loadWidgets(); Bangle.loadWidgets();

View File

@ -2,11 +2,11 @@
"id": "cscsensor", "id": "cscsensor",
"name": "Cycling speed sensor", "name": "Cycling speed sensor",
"shortName": "CSCSensor", "shortName": "CSCSensor",
"version": "0.06", "version": "0.07",
"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",
"supports": ["BANGLEJS"], "supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"cscsensor.app.js","url":"cscsensor.app.js"}, {"name":"cscsensor.app.js","url":"cscsensor.app.js"},