ptx2 / gymnasticon

Make obsolete and/or proprietary exercise bikes work with popular cycling training apps like Zwift, TrainerRoad, Rouvy and more.

Home Page:https://ptx2.net/posts/unbricking-a-bike-with-a-raspberry-pi

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Feature request: Extend autodect bike mode to new bike types

chriselsen opened this issue · comments

Currently the default "autodect" bike type can only recognize the peloton and flywheel bike types, but not the newer bike types ic4 and keiser. It would be great if gymnasticon could automatically detect all currently supported bike types, making a manual specification via CLI only necessary in the unlikely event that more than one bike type could be present.

Currently the autodection of bikes is implemented within the autodetectBikeClient function, which discovers a Peleton bike based on the serial interface path and falls back to flywheel otherwise.
Instead this function could - after failing to discover the Peleton - fall back to performing a BLE scan until one of the supported bike types are discovered. It should be possible to discover the supported bike types based on the advertisement.localName:

  • Flywheel: "Flywheel 1"?
  • IC4: "IC Bike"
  • Keiser: "M3"

Afterwards the function can return with a corresponding createXXXBikeClient() call.

@ptx2 I'm a bit stuck on this effort, as I can't find an elegant way to implement this.
The actual auto-detection piece is quite simple:

  1. If it's not a Peloton bike, start scanning for BLE:
noble.on('stateChange', function(state) {
  if (state === 'poweredOn') {
    noble.startScanning(null, false);
    console.log('Scanning...');
  } else {
    noble.stopScanning();
  }
});
  1. Next wait for advertisements and check if any of the LocalName matches a supported bike:
noble.on('discover', function(peripheral) {
  switch(peripheral.advertisement.localName) {
  case 'M3':
    // Keiser M3 detected
    break;
  case 'IC Bike':
    // Schwinn IC4 detected
    break;
  case 'Flywheel 1':
    // Flywheel detected
    break;
  default:
    // Keep looking
}
  1. Modify autoDetectBikeClient to create a bike accrodingly.

But the last item is where I'm stuck: I would need to pause the control flow within autoDetectBikeClient until a supported bike is discovered. But that's a no-no in Node and I can't come up with an elegant workaround.
Any idea?

commented

Hey @chriselsen, sorry for the delay in reply it's been an unusually busy week.

I think we can solve this by making createBikeClient async and call it like bike = await createBikeClient() from app.run().

The autodetect function could be something like:

async function createAutodetectBikeClient(options, noble) {
  // same peloton check as before, but use async calls instead of fs.existsSync

  const filters = {
    name: (n) => ['Flywheel 1', 'IC Bike', 'M3'].includes(n);
  }
  let peripheral = await scan(noble, null, filters); // scan() from util/ble-scan.js
  switch (peripheral.advertisement.localName) {
  case 'M3': return createKeiserBikeClient(options, noble);
  // etc.
  }
}

Some thoughts:

  • There's no timeout, but in theory I think that's probably ok. But we could always change scan() to support a timeout.
  • We return the first device we see. It might be better to scan for X seconds and return the device with the strongest signal. I think again probably don't need to worry about this much in practice.
commented

Hey @chriselsen, do you have time / interest for this one still? If not, I have time today to take a look.

Hey @chriselsen, do you have time / interest for this one still? If not, I have time today to take a look.

I don't have the time right now to look into this. Please go ahead and take a look.

commented

Added in #81.