Cycling: Implement settings

pull/1596/head
Joseph Paul 2022-03-20 10:51:00 +01:00
parent 14aca8db0f
commit a44c62630a
3 changed files with 103 additions and 49 deletions

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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)