Skip to content

Commit

Permalink
Extract dropout filter. (#59)
Browse files Browse the repository at this point in the history
There are now two bikes that have spurious zero issues. Flywheel has the
issue with power. Keiser has the issue with both power and cadence. The
same filter can be used for both bikes and remove some duplication in
the code. Also adds some tests. Fixes #56.
  • Loading branch information
ptx2 committed Feb 23, 2021
1 parent 1ced808 commit 9c2f84f
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 58 deletions.
31 changes: 2 additions & 29 deletions src/bikes/flywheel.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {execFile} from 'child_process';
const execFileAsync = util.promisify(execFile);
import {scan} from '../util/ble-scan';
import {macAddress} from '../util/mac-address';
import {createDropoutFilter} from '../util/dropout-filter';

// GATT service/characteristic UUIDs
const UART_SERVICE_UUID = '6e400001b5a3f393e0a9e50e24dcca9e';
Expand Down Expand Up @@ -52,7 +53,7 @@ export class FlywheelBikeClient extends EventEmitter {
throw new Error('Already connected');
}

this.fixPowerDropout = createPowerDropoutFilter();
this.fixPowerDropout = createDropoutFilter();

// scan
this.peripheral = await scan(this.noble, [UART_SERVICE_UUID], this.filters);
Expand Down Expand Up @@ -213,31 +214,3 @@ async function updateConnectionParameters(peripheral, minInterval, maxInterval,
await execFileAsync(cmd, args);
}
}

/**
* Workaround for an issue in the Flywheel Bike where it occasionally
* incorrectly reports zero power (watts).
*
* @private
*/
function createPowerDropoutFilter() {
let prev = null;

/**
* Returns stats payload with spurious zero removed.
* @param {object} curr - current stats payload
* @param {number} curr.power - power (watts)
* @param {number} curr.cadence - cadence (rpm)
* @returns {object} fixed - fixed stats payload
* @returns {object} fixed.power - fixed power (watts)
* @returns {object} fixed.cadence - cadence
*/
return function (curr) {
let fixed = {...curr};
if (prev !== null && curr.power === 0 && curr.cadence > 0 && prev.power > 0) {
fixed.power = prev.power;
}
prev = curr;
return fixed;
}
}
30 changes: 1 addition & 29 deletions src/bikes/keiser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {EventEmitter} from 'events';
import {Timer} from '../util/timer';
import {scan} from '../util/ble-scan';
import {macAddress} from '../util/mac-address';
import {createDropoutFilter} from '../util/dropout-filter';

const KEISER_LOCALNAME = "M3";
const KEISER_VALUE_MAGIC = Buffer.from([0x02, 0x01]); // identifies Keiser data message
Expand Down Expand Up @@ -181,32 +182,3 @@ export function parse(data) {
throw new Error('unable to parse message');
}

/**
* Workaround for an issue in the Keiser Bike where it occasionally
* incorrectly reports zero cadence (rpm) or zero power (watts)
* @private
*/
function createDropoutFilter() {
let prev = null;

/**
* Returns stats payload with spurious zero removed.
* @param {object} curr - current stats payload
* @param {number} curr.power - power (watts)
* @param {number} curr.cadence - cadence (rpm)
* @returns {object} fixed - fixed stats payload
* @returns {object} fixed.power - fixed power (watts)
* @returns {object} fixed.cadence - cadence
*/
return function (curr) {
let fixed = {...curr};
if (prev !== null && curr.power === 0 && curr.cadence > 0 && prev.power > 0) {
fixed.power = prev.power;
}
if (prev !== null && curr.cadence === 0 && curr.power > 0 && prev.cadence > 0) {
fixed.cadence = prev.cadence;
}
prev = curr;
return fixed;
}
}
40 changes: 40 additions & 0 deletions src/test/util/dropout-filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import test from 'tape';
import {createDropoutFilter} from '../../util/dropout-filter';

test('fill spurious zero power measurements with previous value', t => {
const filter = createDropoutFilter();
let r;

r = filter({ power: 100, cadence: 101 });
t.equal(r.power, 100, 'power (watts)');
t.equal(r.cadence, 101, 'cadence (rpm)');

r = filter({ power: 0, cadence: 101 });
t.equal(r.power, 100, 'power (watts)');
t.equal(r.cadence, 101, 'cadence (rpm)');

r = filter({ power: 0, cadence: 101 });
t.equal(r.power, 0, 'power (watts)');
t.equal(r.cadence, 101, 'cadence (rpm)');

t.end();
});

test('fill spurious zero cadence measurements with previous value', t => {
const filter = createDropoutFilter();
let r;

r = filter({ power: 100, cadence: 101 });
t.equal(r.power, 100, 'power (watts)');
t.equal(r.cadence, 101, 'cadence (rpm)');

r = filter({ power: 100, cadence: 0 });
t.equal(r.power, 100, 'power (watts)');
t.equal(r.cadence, 101, 'cadence (rpm)');

r = filter({ power: 100, cadence: 0 });
t.equal(r.power, 100, 'power (watts)');
t.equal(r.cadence, 0, 'cadence (rpm)');

t.end();
});
33 changes: 33 additions & 0 deletions src/util/dropout-filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Workaround for an issue in the bikes that occasionally
* incorrectly report zero cadence (rpm) or zero power (watts)
*
* Power dropouts have been observed on the Flywheel bike
* Power and cadence dropouts have been observed on the Keiser bike
*
* This filter can be used for both bikes.
*/
export function createDropoutFilter() {
let prev = null;

/**
* Returns stats payload with spurious zero removed.
* @param {object} curr - current stats payload
* @param {number} curr.power - power (watts)
* @param {number} curr.cadence - cadence (rpm)
* @returns {object} fixed - fixed stats payload
* @returns {object} fixed.power - fixed power (watts)
* @returns {object} fixed.cadence - cadence
*/
return function (curr) {
let fixed = {...curr};
if (prev !== null && curr.power === 0 && curr.cadence > 0 && prev.power > 0) {
fixed.power = prev.power;
}
if (prev !== null && curr.cadence === 0 && curr.power > 0 && prev.cadence > 0) {
fixed.cadence = prev.cadence;
}
prev = curr;
return fixed;
}
}

0 comments on commit 9c2f84f

Please sign in to comment.