mirror of https://github.com/espruino/BangleApps
Merge branch 'master' of https://github.com/peerdavid/BangleApps
commit
3e4ed5e49b
|
@ -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"},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.1: Added source code
|
||||
0.2: Added a README file
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# FD6FDetect
|
||||
|
||||
An app dedicated to letting you know how many Exposure Notification beacons are near you.
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Updated to work with both Bangle.js 1 and 2.
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue