pull/1127/head
David Peer 2021-11-26 19:01:14 +01:00
commit 3e4ed5e49b
8 changed files with 104 additions and 84 deletions

View File

@ -734,11 +734,11 @@
{
"id": "slevel",
"name": "Spirit Level",
"version": "0.01",
"version": "0.02",
"description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat",
"icon": "spiritlevel.png",
"tags": "tool",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"slevel.app.js","url":"spiritlevel.js"},
{"name":"slevel.img","url":"spiritlevel-icon.js","evaluate":true}
@ -2948,7 +2948,7 @@
"id": "cscsensor",
"name": "Cycling speed sensor",
"shortName": "CSCSensor",
"version": "0.05",
"version": "0.06",
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
"icon": "icons8-cycling-48.png",
"tags": "outdoors,exercise,ble,bluetooth",
@ -4039,10 +4039,11 @@
"id": "fd6fdetect",
"name": "fd6fdetect",
"shortName": "fd6fdetect",
"version": "0.1",
"version": "0.2",
"description": "Allows you to see 0xFD6F beacons near you.",
"icon": "app.png",
"tags": "tool",
"readme": "README.md",
"supports": ["BANGLEJS"],
"storage": [
{"name":"fd6fdetect.app.js","url":"app.js"},

View File

@ -3,3 +3,5 @@
0.03: Save total distance traveled
0.04: Add sensor battery level indicator
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
- trip 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.
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.
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 storage = require('Storage');
const W = g.getWidth();
const H = g.getHeight();
class CSCSensor {
constructor() {
@ -75,7 +77,7 @@ class CSCSensor {
var dist = this.distFactor*(this.lastRevs-this.lastRevsStart)*this.wheelCirc/63360.0;
var ddist = Math.round(100*dist)/100;
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();
if (dmins.length<2) dmins = "0"+dmins;
var dsecs = (Math.floor(this.movingTime) % 60).toString();
@ -152,7 +154,7 @@ class CSCSensor {
var qChanged = false;
if (event.target.uuid == "0x2a5b") {
if (event.target.value.getUint8(0, true) & 0x2) {
// crank revolution
// crank revolution - if enabled
const crankRevs = event.target.value.getUint16(1, true);
const crankTime = event.target.value.getUint16(3, true);
if (crankTime > this.lastCrankTime) {
@ -161,44 +163,43 @@ class CSCSensor {
}
this.lastCrankRevs = crankRevs;
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();
}
@ -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() {
NRF.setScan();
mySensor.screenInit = true;
NRF.setScan(parseDevice, { filters: [{services:["1816"]}], timeout: 2000});
g.clearRect(0, 48, 239, 239).setFontVector(18).setFontAlign(0, 0, 0).setColor(0, 1, 0);
g.drawString("Scanning for CSC sensor...", 120, 120);
E.showMessage("Scanning for CSC sensor...");
NRF.requestDevice({ filters: [{services:["1816"]}]}).then(function(d) {
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();
setWatch(function() { mySensor.reset(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN1, {repeat:true, debounce:20});
E.on('kill',()=>{ if (gatt!=undefined) gatt.disconnect(); mySensor.settings.totaldist = mySensor.totaldist; storage.writeJSON(SETTINGS_FILE, mySensor.settings); });
setWatch(function() { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }, BTN3, {repeat:true, debounce:20});
setWatch(function() { mySensor.toggleDisplayCadence(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN2, {repeat:true, debounce:20});
NRF.on('disconnect', connection_setup);
E.on('kill',()=>{
if (gatt!=undefined) gatt.disconnect();
mySensor.settings.totaldist = mySensor.totaldist;
storage.writeJSON(SETTINGS_FILE, mySensor.settings);
});
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.drawWidgets();

View File

@ -1 +1,2 @@
0.1: Added source code
0.2: Added a README file

View File

@ -0,0 +1,3 @@
# FD6FDetect
An app dedicated to letting you know how many Exposure Notification beacons are near you.

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Updated to work with both Bangle.js 1 and 2.

View File

@ -1,5 +1,7 @@
g.clear();
var old = {x:0,y:0};
var W = g.getWidth();
var H = g.getHeight();
Bangle.on('accel',function(v) {
var max = Math.max(Math.abs(v.x),Math.abs(v.y),Math.abs(v.z));
if (Math.abs(v.y)==max) {
@ -14,17 +16,17 @@ Bangle.on('accel',function(v) {
g.setColor(1,1,1);
g.setFont("6x8",2);
g.setFontAlign(0,-1);
g.clearRect(60,0,180,16);
g.drawString(ang.toFixed(1),120,0);
g.clearRect(W*(1/4),0,W*(3/4),H*(1/16));
g.drawString(ang.toFixed(1),W/2,0);
var n = {
x:E.clip(120+v.x*256,4,236),
y:E.clip(120+v.y*256,4,236)};
x:E.clip(W/2+v.x*256,4,W-4),
y:E.clip(H/2+v.y*256,4,H-4)};
g.clearRect(old.x-3,old.y-3,old.x+6,old.y+6);
g.setColor(1,1,1);
g.fillRect(n.x-3,n.y-3,n.x+6,n.y+6);
g.setColor(1,0,0);
g.drawCircle(120,120,20);
g.drawCircle(120,120,60);
g.drawCircle(120,120,100);
g.drawCircle(W/2,H/2,W*(1/12));
g.drawCircle(W/2,H/2,W*(1/4));
g.drawCircle(W/2,H/2,W*(5/12));
old = n;
});