mirror of https://github.com/espruino/BangleApps
Cycling: Implement settings
parent
14aca8db0f
commit
a44c62630a
|
@ -1,7 +1,7 @@
|
|||
# Cycling
|
||||
> Displays data from a BLE Cycling Speed and Cadence sensor.
|
||||
|
||||
*Fork of the CSCSensor app using the layout library and separate module for CSC functionality*
|
||||
*This is a fork of the CSCSensor app using the layout library and separate module for CSC functionality. It also drops persistence of total distance on the Bangle, as this information is also persisted on the sensor itself. Further, it allows configuration of display units (metric/imperial) independent of chosen locale. Finally, multiple sensors can be used and wheel circumference can be configured for each sensor individually.*
|
||||
|
||||
The following data are displayed:
|
||||
- curent speed
|
||||
|
@ -11,16 +11,24 @@ The following data are displayed:
|
|||
- trip distance
|
||||
- total distance
|
||||
|
||||
Total distance is not stored on the Bangle, but instead is calculated from the CWR (cumulative wheel revolutions) reported by the sensor. This metric is, according to the BLE spec, a absolute value that persists throughout the lifetime of the sensor and never rolls over.
|
||||
Other than in the original version of the app, total distance is not stored on the Bangle, but instead is calculated from the CWR (cumulative wheel revolutions) reported by the sensor. This metric is, according to the BLE spec, an absolute value that persists throughout the lifetime of the sensor and never rolls over.
|
||||
|
||||
**Cadence / Crank features are currently not implemented**
|
||||
|
||||
# TODO
|
||||
* Settings: imperial/metric
|
||||
* Store circumference per device address
|
||||
## Usage
|
||||
Open the app and connect to a CSC sensor.
|
||||
|
||||
Upon first connection, close the app afain and enter the settings app to configure the wheel circumference. The total circumference is (cm + mm) - it is split up into two values for ease of configuration. Check the status screen inside the Cycling app while connected to see the address of the currently connected sensor (if you need to differentiate between multiple sensors).
|
||||
|
||||
Inside the Cycling app, use button / tap screen to:
|
||||
- cycle through screens (if connected)
|
||||
- reconnect (if connection aborted)
|
||||
|
||||
## TODO
|
||||
* Sensor battery status
|
||||
* Implement crank events / show cadence
|
||||
* Bangle.js 1 compatibility
|
||||
* Allow setting CWR on the sensor (this is a feature intended by the BLE CSC spec, in case the sensor is replaced or transferred to a different bike)
|
||||
|
||||
# Development
|
||||
## Development
|
||||
There is a "mock" version of the `blecsc` module, which can be used to test features in the emulator. Check `blecsc-emu.js` for usage.
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
const Layout = require('Layout');
|
||||
const storage = require('Storage');
|
||||
|
||||
const SETTINGS_FILE = 'cycling.json';
|
||||
const storage = require('Storage');
|
||||
const SETTINGS_DEFAULT = {
|
||||
sensors: {},
|
||||
metric: true,
|
||||
};
|
||||
|
||||
const RECONNECT_TIMEOUT = 4000;
|
||||
const MAX_CONN_ATTEMPTS = 2;
|
||||
|
@ -13,8 +17,8 @@ class CSCSensor {
|
|||
this.display = display;
|
||||
|
||||
// Load settings
|
||||
this.settings = storage.readJSON(SETTINGS_FILE, 1) || {};
|
||||
this.wheelCirc = (this.settings.wheelcirc || 2230) / 1000; // unit: m
|
||||
this.settings = storage.readJSON(SETTINGS_FILE, true) || SETTINGS_DEFAULT;
|
||||
this.wheelCirc = undefined;
|
||||
|
||||
// CSC runtime variables
|
||||
this.movingTime = 0; // unit: s
|
||||
|
@ -34,13 +38,16 @@ class CSCSensor {
|
|||
// Layout configuration
|
||||
this.layout = 0;
|
||||
this.display.useMetricUnits(true);
|
||||
// this.display.useMetricUnits(!require("locale").speed(1).toString().endsWith("mph"));
|
||||
this.deviceAddress = undefined;
|
||||
this.display.useMetricUnits((this.settings.metric));
|
||||
}
|
||||
|
||||
onDisconnect(event) {
|
||||
console.log("disconnected ", event);
|
||||
|
||||
this.connected = false;
|
||||
this.wheelCirc = undefined;
|
||||
|
||||
this.setLayout(0);
|
||||
this.display.setDeviceAddress("unknown");
|
||||
|
||||
|
@ -51,7 +58,23 @@ class CSCSensor {
|
|||
this.display.setStatus("Disconnected");
|
||||
setTimeout(this.connect.bind(this), RECONNECT_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
loadCircumference() {
|
||||
if (!this.deviceAddress) return;
|
||||
|
||||
// Add sensor to settings if not present
|
||||
if (!this.settings.sensors[this.deviceAddress]) {
|
||||
this.settings.sensors[this.deviceAddress] = {
|
||||
cm: 223,
|
||||
mm: 0,
|
||||
};
|
||||
storage.writeJSON(SETTINGS_FILE, this.settings);
|
||||
}
|
||||
|
||||
const high = this.settings.sensors[this.deviceAddress].cm || 223;
|
||||
const low = this.settings.sensors[this.deviceAddress].mm || 0;
|
||||
this.wheelCirc = (10*high + low) / 1000;
|
||||
}
|
||||
|
||||
connect() {
|
||||
|
@ -70,12 +93,14 @@ class CSCSensor {
|
|||
this.failedAttempts = 0;
|
||||
this.failed = false;
|
||||
this.connected = true;
|
||||
var addr = this.blecsc.getDeviceAddress();
|
||||
console.log("Connected to " + addr);
|
||||
this.deviceAddress = this.blecsc.getDeviceAddress();
|
||||
console.log("Connected to " + this.deviceAddress);
|
||||
|
||||
this.display.setDeviceAddress(addr);
|
||||
this.display.setDeviceAddress(this.deviceAddress);
|
||||
this.display.setStatus("Connected");
|
||||
|
||||
this.loadCircumference();
|
||||
|
||||
// Switch to speed screen in 2s
|
||||
setTimeout(function() {
|
||||
this.setLayout(1);
|
||||
|
@ -90,9 +115,9 @@ class CSCSensor {
|
|||
|
||||
disconnect() {
|
||||
this.blecsc.disconnect();
|
||||
this.connected = false;
|
||||
this.reset();
|
||||
this.setLayout(0);
|
||||
this.display.setStatus("Disconnected")
|
||||
this.display.setStatus("Disconnected");
|
||||
}
|
||||
|
||||
setLayout(num) {
|
||||
|
@ -110,6 +135,7 @@ class CSCSensor {
|
|||
this.connected = false;
|
||||
this.failed = false;
|
||||
this.failedAttempts = 0;
|
||||
this.wheelCirc = undefined;
|
||||
}
|
||||
|
||||
interact(d) {
|
||||
|
@ -127,7 +153,7 @@ class CSCSensor {
|
|||
|
||||
updateScreen() {
|
||||
var tripDist = this.cwrTrip * this.wheelCirc;
|
||||
var avgSpeed = this.movingTime > 3 ? tripDist / this.movingTime : 0
|
||||
var avgSpeed = this.movingTime > 3 ? tripDist / this.movingTime : 0;
|
||||
|
||||
this.display.setTotalDistance(this.cwr * this.wheelCirc);
|
||||
this.display.setTripDistance(tripDist);
|
||||
|
@ -297,7 +323,7 @@ class CSCDisplay {
|
|||
bgCol: "#fff",
|
||||
height: 32,
|
||||
c: [
|
||||
{ type: "txt", id: "addr_l", label: "MAC", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 36 },
|
||||
{ type: "txt", id: "addr_l", label: "ADDR", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 36 },
|
||||
{ type: "txt", id: "addr", label: "unknown", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 140 },
|
||||
]
|
||||
},
|
||||
|
@ -316,13 +342,13 @@ class CSCDisplay {
|
|||
|
||||
renderIfLayoutActive(layout, node) {
|
||||
if (layout != this.currentLayout) return;
|
||||
this.layouts[layout].render(node)
|
||||
this.layouts[layout].render(node);
|
||||
}
|
||||
|
||||
useMetricUnits(metric) {
|
||||
this.metric = metric;
|
||||
|
||||
console.log("using " + (metric ? "metric" : "imperial") + " units");
|
||||
// console.log("using " + (metric ? "metric" : "imperial") + " units");
|
||||
|
||||
var speedUnit = metric ? "km/h" : "mph";
|
||||
this.layouts.speed.speed_u.label = speedUnit;
|
||||
|
@ -378,12 +404,12 @@ class CSCDisplay {
|
|||
}
|
||||
|
||||
setTripDistance(distance) {
|
||||
this.layouts.distance.tripd.label = this.convertDistance(distance).toFixed(1)
|
||||
this.layouts.distance.tripd.label = this.convertDistance(distance).toFixed(1);
|
||||
this.renderIfLayoutActive("distance", this.layouts.distance.tripd_g);
|
||||
}
|
||||
|
||||
setTotalDistance(distance) {
|
||||
const distance = this.convertDistance(distance);
|
||||
distance = this.convertDistance(distance);
|
||||
if (distance >= 1000) {
|
||||
this.layouts.distance.totald.label = String(Math.round(distance));
|
||||
} else {
|
||||
|
@ -393,12 +419,12 @@ class CSCDisplay {
|
|||
}
|
||||
|
||||
setDeviceAddress(address) {
|
||||
this.layouts.status.addr.label = address
|
||||
this.layouts.status.addr.label = address;
|
||||
this.renderIfLayoutActive("status", this.layouts.status.addr_g);
|
||||
}
|
||||
|
||||
setStatus(status) {
|
||||
this.layouts.status.status.label = status
|
||||
this.layouts.status.status.label = status;
|
||||
this.renderIfLayoutActive("status", this.layouts.status.status_g);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,35 +3,55 @@
|
|||
* @param {function} back Use back() to return to settings menu
|
||||
*/
|
||||
(function(back) {
|
||||
const SETTINGS_FILE = 'cscsensor.json'
|
||||
// initialize with default settings...
|
||||
let s = {
|
||||
'wheelcirc': 2230,
|
||||
}
|
||||
// ...and overwrite them with any saved values
|
||||
// This way saved values are preserved if a new version adds more settings
|
||||
const storage = require('Storage')
|
||||
const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
|
||||
for (const key in saved) {
|
||||
s[key] = saved[key];
|
||||
}
|
||||
// creates a function to safe a specific setting, e.g. save('color')(1)
|
||||
function save(key) {
|
||||
return function (value) {
|
||||
s[key] = value;
|
||||
storage.write(SETTINGS_FILE, s);
|
||||
}
|
||||
}
|
||||
const SETTINGS_FILE = 'cycling.json'
|
||||
|
||||
// Set default values and merge with stored values
|
||||
let settings = Object.assign({
|
||||
metric: true,
|
||||
sensors: {},
|
||||
}, (storage.readJSON(SETTINGS_FILE, true) || {}));
|
||||
|
||||
const menu = {
|
||||
'': { 'title': 'Cycle speed sensor' },
|
||||
'': { 'title': 'Cycling' },
|
||||
'< Back': back,
|
||||
'Wheel circ.(mm)': {
|
||||
value: s.wheelcirc,
|
||||
min: 800,
|
||||
max: 2400,
|
||||
step: 5,
|
||||
onchange: save('wheelcirc'),
|
||||
'Units': {
|
||||
value: settings.metric,
|
||||
format: v => v ? 'metric' : 'imperial',
|
||||
onchange: (metric) => {
|
||||
settings.metric = metric;
|
||||
storage.writeJSON(SETTINGS_FILE, settings);
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const sensorMenus = {};
|
||||
for (var addr of Object.keys(settings.sensors)) {
|
||||
// Define sub menu
|
||||
sensorMenus[addr] = {
|
||||
'': { title: addr },
|
||||
'< Back': () => E.showMenu(menu),
|
||||
'cm': {
|
||||
value: settings.sensors[addr].cm,
|
||||
min: 80, max: 240, step: 1,
|
||||
onchange: (v) => {
|
||||
settings.sensors[addr].cm = v;
|
||||
storage.writeJSON(SETTINGS_FILE, settings);
|
||||
},
|
||||
},
|
||||
'+ mm': {
|
||||
value: settings.sensors[addr].mm,
|
||||
min: 0, max: 9, step: 1,
|
||||
onchange: (v) => {
|
||||
settings.sensors[addr].mm = v;
|
||||
storage.writeJSON(SETTINGS_FILE, settings);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Add entry to main menu
|
||||
menu[addr] = () => E.showMenu(sensorMenus[addr]);
|
||||
}
|
||||
|
||||
E.showMenu(menu);
|
||||
})
|
||||
})(load)
|
||||
|
|
Loading…
Reference in New Issue