cagnulein / qdomyos-zwift

Zwift bridge for smart treadmills and bike/cyclette
https://www.qzfitness.com/
GNU General Public License v3.0
369 stars 109 forks source link

Add support for the Trixter X-Dream V1 bike #855

Open drmason789 opened 2 years ago

drmason789 commented 2 years ago

Full Details

The data comes in from the serial port at baud rate 115200 as 32-character text strings of characters 0-9 and a-f (lower case), i.e. hexadecimal text for 16 bytes. The bike this was measured on delivers packets at a rate of about 1 every 13ms.

                                     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
          (00) Header (0x6a) --------+  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
          (01) Steering ----------------+  |  |  |  |  |  |  |  |  |  |  |  |  |  |
          (02) Unknown (0x80)--------------+  |  |  |  |  |  |  |  |  |  |  |  |  |
          (03) Crank position ----------------+  |  |  |  |  |  |  |  |  |  |  |  |
          (04) Right brake ----------------------+  |  |  |  |  |  |  |  |  |  |  |
          (05) Left brake --------------------------+  |  |  |  |  |  |  |  |  |  |
          (06) Unknown (0xff)--------------------------+  |  |  |  |  |  |  |  |  |
          (07) Unknown (0xff)-----------------------------+  |  |  |  |  |  |  |  |
          (08) Button flags ---------------------------------+  |  |  |  |  |  |  |
          (09) Button flags ------------------------------------+  |  |  |  |  |  |
          (0A) Crank revolution time (high byte) ------------------+  |  |  |  |  |
          (0B) Crank revolution time (low byte) ----------------------+  |  |  |  |
          (0C) Flywheel Revolution Time (high byte) ---------------------+  |  |  |
          (0D) Flywheel Revolution Time (low byte) -------------------------+  |  |
          (0E) Heart rate (BPM) -----------------------------------------------+  |
          (0F) XOR of 00 to 0E----------------------------------------------------+

Header is always 0x6a Steering [0, 255] Crank Position [1,60] Left Brake [135, 250] (250 when released) Right Brake [135, 250] (250 when released) Button Flags are a bitmapped field. Flywheel revolution time F: RPM = 576000.0/F approximately. Crank revolution time C: RPM = 1/(C * 0.000006) approximately. Heart rate is in beats per minute. Unknowns 02, 06, 07 are always 128, 255 and 255. 0F is the XOR of the first 15 bytes.

Button flags combined into 1 16-bit unsigned integer and subtracted from 65535 form a bitmapped field:

None = 0
Seated = 8

BackGearUp = 32
BackGearDown = 64
FrontGearUp = 32768
FrontGearDown = 128

LeftArrow = 4096
RightArrow = 16384
UpArrow = 256
DownArrow = 1024

Blue = 8192
Red = 512
Green = 2048

For Zwift, preferences would be steering, heart rate, cumulative crank and flywheel rotations calculated from time between samples and the speed and cadence themselves.

The bike supports 251 resistance levels, and these are maintained by pushing an array of 6 bytes to the bike every 10ms or so. A resistance level of 0 is maintained by sending nothing.

Each resistance packet is as follows:

                    6a 00 00 00 00 00
00 Header (0x6a) ----+  |  |  |  |  |
01 Level (0..0xFA)------+  |  |  |  |
02 (Level+0x3C)%0xFF ------+  |  |  |
03 (Level+0x5A)%0xFF ---------+  |  |
04 (Level+0x78)%0xFF ------------+  |
05 XOR of 00..04 -------------------+

Unlike the incoming data, which is hexadecimal text, this outgoing data is sent as the actual bytes specified above.

drmason789 commented 2 years ago

The steering values are from 0 to 255,

When mapped to Elite Sterzo, what "angle" range should these be mapped to?

I could try a linear mapping to begin with, but the steering on these bikes a is a point of undesirable calibration and I think it will be prudent to add the ability to adjust the mapping through the UI/settings, and possibly even map the incoming values to a curve, as I often feel it's more sensitive turning in one direction than the other.

cagnulein commented 2 years ago

When mapped to Elite Sterzo, what "angle" range should these be mapped to?

-45 to +45 if I remember correctly

I could try a linear mapping to begin with, but the steering on these bikes a is a point of undesirable calibration and I think it will be prudent to add the ability to adjust the mapping through the UI/settings, and possibly even map the incoming values to a curve, as I often feel it's more sensitive turning in one direction than the other.

Correct, we did similar thing to SS2K calibration

drmason789 commented 2 years ago

I've committed some serial port code reduced from serialdirconn.

Things to do:

drmason789 commented 2 years ago

@cagnulein Note that if you're able to run QZ on Windows (I have given up trying to get QtCreator to work with this project on Windows - issues with none of the "kits" being usable) you should be able to test this code using my TestController project in my trixter-xdream-bike repo. I've written an emulator for the bike and can even play the "game" that comes with the bike using the emulator.

cagnulein commented 2 years ago
  • is the "bluetooth event time" available somewhere? I.e. the number of 1/1024s from an origin in time?

what do you mean? do you need access to the RTC?

  • study the code to work out if I can pass the CSCS data into bike::* members, or do I have to do other things?

do you mean the cadence information? Just populate the Cadence properties inside the class, check, for example, the echelonsport.cpp class to have an example

  • does it have to call bluetooth functions to send the data, or just configure bike:: members and call something?

I will add the virtual bridge in this module when you're ready, it's easier to do than to explain :)

@cagnulein Note that if you're able to run QZ on Windows (I have given up trying to get QtCreator to work with this project on Windows - issues with none of the "kits" being usable) you should be able to test this code using my TestController project in my trixter-xdream-bike repo. I've written an emulator for the bike and can even play the "game" that comes with the bike using the emulator.

there is a CI that build automatically the windows version, you have just to start a PR here

drmason789 commented 2 years ago

"Bluetooth Event Time" (for want of a more standard term)

In the Bluetooth spec, the CSCS (Cycling Speed and Cadence Service) data packet has CumulativeWheelRevolutions, LastWheelEventTime, CumulativeCrankRevolutions, LastCrankEventTime, where the Last*EventTime fields are unsigned 16 bit integers, where the unit of time is 1/1024s. So I would look around the code for a time source that delivers the current "Bluetooth time" which would be 1/1024th of a second since a starting point the system defines somewhere. Or perhaps I should just calculate it in this bike's class from the system clock.

CSCS information

Yes, I mean cadence information, but specifically the data from the CSCS spec (CumulativeWheelRevolutions, LastWheelEventTime, CumulativeCrankRevolutions, LastCrankEventTime).

Although I've seen there are "metric" fields:

bike::Cadence
 bike::CrankRevs
 bike::Distance
 bike::Heart
 bike::LastCrankEventTime
 bike::Resistance
 bike::currentSteeringAngle()
bike::Speed

I'm having to look at other code to work out what the units are.

I'm wondering what to do with the flywheel speed indicator. The bike sends back a number related to the time taken to complete the last revolution, and I can convert this to other units, and combine using samples and time between them to calculate a number of wheel revolutions, however I am yet to determine what "speed" and "distance" mean here as I've not seen a wheel radius anywhere. Angular velocity of flywheel I have, but how is distance defined?

I'm looking at echelonconnectsport.cpp.

cagnulein commented 2 years ago

In the Bluetooth spec, the CSCS (Cycling Speed and Cadence Service) data packet has CumulativeWheelRevolutions, LastWheelEventTime, CumulativeCrankRevolutions, LastCrankEventTime, where the Last*EventTime fields are unsigned 16 bit integers, where the unit of time is 1/1024s. So I would look around the code for a time source that delivers the current "Bluetooth time" which would be 1/1024th of a second since a starting point the system defines somewhere. Or perhaps I should just calculate it in this bike's class from the system clock.

yes add this branch https://github.com/cagnulein/qdomyos-zwift/blob/941f86ee566c833d49aca489581807e452f9a24a/src/echelonconnectsport.cpp#L246 and QZ will do the rest automatically

cagnulein commented 2 years ago

I'm wondering what to do with the flywheel speed indicator. The bike sends back a number related to the time taken to complete the last revolution, and I can convert this to other units, and combine using samples and time between them to calculate a number of wheel revolutions, however I am yet to determine what "speed" and "distance" mean here as I've not seen a wheel radius anywhere. Angular velocity of flywheel I have, but how is distance defined?

what typically I do when I have this kind of scenario is using the cadence value and use the cadence_sensor_ratio setting to obtain a speed value and of course a distance value

drmason789 commented 2 years ago

Next I will be looking at how QZ detects the device and knows which class to instantiate.

In my diagnostic app for this bike, I have it listen for a short period on all the serial ports it can open for valid data packets then auto-selects the first matching port. Hopefully I will be able to do something similar here.

cagnulein commented 2 years ago

@drmason789 yes autodiscovery it will be possible. Let me know if you need a hand on this

drmason789 commented 2 years ago

I've written some code to search the serial ports for one that delivers valid data for the bike. I've not tested this yet, as I've not got anything running in the QtCreator environment.

I am yet to determine where this would need to go for QZ to call it to detect the device. Also, if the device has many serial ports it could take a few seconds to determine if the bike is there.

Perhaps there should be a setting to specify if QZ actually looks for this type of device, and a setting to store the name of the last serial port the device was found on, so that one can be tried first.

drmason789 commented 2 years ago

@cagnulein Could you please recommend some next steps? e.g.

cagnulein commented 2 years ago

@drmason789 in my mind you should put the static function that try to understand if a serial port is good in the bluetooth.cpp, here https://github.com/cagnulein/qdomyos-zwift/blob/fe4b82740d85ef72089cdb85e36b97e96bbe4e15/src/bluetooth.cpp#L387 so if your function will return true, you can create a new trixterxdreamv1bike object and go on with your module.

Yes you can align to master with any issue I guess. Do you know how to do this, isn't it?

Once you created the bike object you should be able to see the metrics on the tiles and the virtual bridge should appear on zwift (i didn't check your code yet, but i will do as soon as you will be ready to test).

Of course, if you want you can create 2 different settings to enable your bike and/or to force to a specific serial port. Both ways are fine to me.

Are you able to compile and test now on your own on Windows?

drmason789 commented 2 years ago

Are you able to compile and test now on your own on Windows?

No, I've been developing on my RP4. On Windows, QtCreator takes 10+ minutes to start up, and when I open the qz project I get this:

image

image

I have explored the various options but there are long unresponsive pauses. I've got VS 2022 CE installed, and I've also installed the MSVC 2019 build tools in case those are required for one of those similarly named kits. No effect.

image

cagnulein commented 2 years ago

you need to install a kit. install the mingw 64 bit for qt 5.15.2. you can do this in the maintenance tool of q under c:\qt let me know

Il giorno sab 16 lug 2022 alle 18:30 David Mason @.***> ha scritto:

Are you able to compile and test now on your own on Windows?

No, I've been developing on my RP4. On Windows, QtCreator takes 10+ minutes to start up, and when I open the qz project I get this:

[image: image] https://user-images.githubusercontent.com/29954900/179363253-73b58ea4-f617-4dc3-8c20-208f64085978.png

I have explored the various options but there are long unresponsive pauses. I've got VS 2022 CE installed, and I've also installed the MSVC 2019 build tools in case those are required for one of those similarly named kits. No effect.

[image: image] https://user-images.githubusercontent.com/29954900/179363627-25fb4bf6-03c2-4d43-bf6d-2460e826351b.png

— Reply to this email directly, view it on GitHub https://github.com/cagnulein/qdomyos-zwift/issues/855#issuecomment-1186232997, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAALYWGFP5TGIS4W5P2NAN3VULPSLANCNFSM53B4UFPQ . You are receiving this because you were mentioned.Message ID: @.***>

-- Roberto Viola Software engineer and open source enthusiast http://robertoviola.cloud

drmason789 commented 2 years ago

I think I updated my comment to include a screenshot of the maintenance tool while you were typing your response. I already have all the available kits for 5.15.2 installed. However, they are on my F drive. I wonder if there's a C:\ in a config somewhere that's interfering. I'll look around.

drmason789 commented 2 years ago

Not encouraging...

image

cagnulein commented 2 years ago

yes it seems an issue of path. try to move it to the c:\ drive (only the mingw 64bit) But i don't think it's about the drive. it could be something else, it never happened to me unfortunately:(

drmason789 commented 2 years ago

Installing on the C drive has made a difference, but it seems to have failed to set up the mingw 64bit kit somehow and is complaining about that.

"The Qt version is invalid: qmake does not exist or is not executable"

Same issue as before basically, but this time QtCreator let me select a kit. Still looking.

drmason789 commented 2 years ago

I will be leaving this for a while. I've not been able to debug yet, either on the RP4 or Windows.

The latest work is in my fork of qz, in the trixter-xdream-v1-bike branch.

The state is as follows:

trixterxdreamv1serial - this is a class based on your serialdircon class but it's not for a specific device, but it is hardcoded to 115200 bps which the Trixter bike needs. I think with a few more options (like baud rate) exposed, this could be a generic class for use by anything that delivers serial port data.

trixterxdreamv1client - This is a "business logic" class which is deliberately Qt-free - I've been able to develop it in Visual Studio with Google Test. It takes in characters (the bike delivers its data as hexidecimal text) and interprets this into a state that can be read from a function. It also provides a method to send a resistance level request, but you need to supply the instance with a delegate to write the bytes it wants to send - as I said, no Qt in this class.

trixterxdreamv1bike - This is a subclass of bike. It creates instances of the above and hooks them up. I've not been able to test this yet. It's written to detect when a new state has been detected by the trixterxdreamv1client object, and update the bike metrics. It also contains a timer to pump resistance requests to the trixterxdreamv1client object. There is a static function in this class to check a port to see if there's a bike on it, and another to find all ports and look for a bike on them. This is the code I expect you want in the bluetooth.cpp file.

If someone picks this up in the meantime, my test controller in the trixer-xdream-bike repo should be adequate for manual testing. That page has a link to a youtube video demonstrating an early version of it driving the software that comes with the bike. I should be able to find a human tester or two with real bikes to help too.

cagnulein commented 2 years ago

thanks! Remember that if you want to push a PR, the CI of github will build the code for you without the qt installation.

drmason789 commented 2 years ago

I've got the debugger working with Qt Creator 8.0.0 on Windows 10 now. MingW 8.1.0 is missing g++, gcc and gdb so I installed some extra items and rerouted a few things. Now it works, but slowly.

I've managed to have the detector code (I've put this in bluetooth.cpp) look through the serial ports and find the bike (my test controller actually), which it does by counting valid packets.

However, I'm finding it doesn't last long before I'm getting seg faults in one of the vectors in my "business logic" class. I thought maybe it was a multithreading problem, but the debugger shows only 1 thread in the code and syncing access with a mutex object simply makes the seg fault happen in the mutex object. There seems to be a few people online finding this sort of thing with QSerialPort so I've got leads.

drmason789 commented 2 years ago

I've changed the trixterxdreamv1serial class to use a signal to send the received data, and a slot on the trixterxdreamv1bike class. But even though the connect function for this pair returns true, the slot never gets called.

drmason789 commented 2 years ago

My trixterxdreamv1serial class is based on your serialdircon class, which I think looks very similar to the Qt blocking receiver example. It appears that the QObject for the receiving slot must be "moved to" the QThread that emits the signal in order for the connection to work.

drmason789 commented 2 years ago

@cagnulein I now have it detecting the bike (test controller actually) in bluetooth.cpp, and I have confirmed by debugging through it that the bike object is passed into the 2 template manager objects.

// Try to connect to a Trixter X-Dream V1 bike if the setting is enabled.
    this->trixterxdreamv1bike = this->findTrixterXDreamV1Bike(settings);
    if(this->trixterxdreamv1bike) {
        this->userTemplateManager->start(trixterxdreamv1bike);
        this->innerTemplateManager->start(trixterxdreamv1bike);
        this->connectedAndDiscovered();
        return;
    }

But no information shows up in the UI, and no tiles. Also, the app reports finding my bluetooth mouse rather than the bike (I'm not sure where the application would get the bike's name from). Are you able to tell me what I need to do next? Are we ready for a PR?

cagnulein commented 2 years ago

@drmason789 did you pair the bike in the windows bluetooth settings?

drmason789 commented 2 years ago

@cagnulein The bike is not a bluetooth device.

cagnulein commented 2 years ago

@drmason789 hah sorry you're right, i'm following too many threads :D actually the qz discovery engine works with the bluetooth event. in the bluetooth event there is also the handler for serial and wi-fi devices so you should put there the creation of your device

i didn't actually check your code and i'm not able to review it before monday

i will check it and i will write you some notes

drmason789 commented 2 years ago

It seems I missed some things. Now I've had some success!

image

Now I need to work out why I can't increase the resistance on the tile, and find out how to display steering information.

cagnulein commented 2 years ago

yay! Great work!

Il giorno lun 1 ago 2022 alle 02:41 David Mason @.***> ha scritto:

It seems I missed some things Now I've had some success!

[image: image] https://user-images.githubusercontent.com/29954900/182052659-0d8d41e0-5193-493d-bf63-16a7d251d438.png

Now I need to work out why I can't increase the resistance on the tile, and find out how to display steering information.

— Reply to this email directly, view it on GitHub https://github.com/cagnulein/qdomyos-zwift/issues/855#issuecomment-1200550012, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAALYWC62YGXGADBGTKJ37LVW4MMPANCNFSM53B4UFPQ . You are receiving this because you were mentioned.Message ID: @.***>

-- Roberto Viola Software engineer and open source enthusiast http://robertoviola.cloud

drmason789 commented 2 years ago

@cagnulein I found how to display the name of the detected device on the home screen, instead of that of my bluetooth mouse.

  1. How does a click of a + or - button on the resistance tile get to the bike object?
  2. Once the device has been detected, and the bluetooth::deviceDiscovered has exited, I find I have to go into the settings and back out again in order for the tiles to appear. What do I need to do for the tiles to appear immediately after the device is detected?
cagnulein commented 2 years ago

@drmason789

  1. from here https://github.com/cagnulein/qdomyos-zwift/blob/ebc21c90b42f0a7dbb7defb2e5d6ad4eb4826baa/src/homeform.cpp#L1738 the resistance will write into the bike resistance object. So using the update tick in your class object and checking the requestResistance as I did here https://github.com/cagnulein/qdomyos-zwift/blob/ebc21c90b42f0a7dbb7defb2e5d6ad4eb4826baa/src/echelonconnectsport.cpp#L122 you can do the business logic to the bike about the resistance.

  2. it was a linux/windows bug that I already fixed with this https://github.com/cagnulein/qdomyos-zwift/pull/803/files Check that these changes are in your branch

Let me know

drmason789 commented 2 years ago

I've got those changes:

image

image

Even if I change it to

#if defined(Q_OS_WINDOWS) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
    QBluetoothDeviceInfo b;
    deviceConnected(b);
#endif

it still doesn't show the tiles immediately.

drmason789 commented 2 years ago

Emitting deviceConnected on a fake QBluetoothDeviceInfo object in bluetooth.cpp where the device is detected did the trick:

image

This causes the tiles to show up, the Plus and Minus slots to be connected to the corresponding signals, and thus the resistance change requests getting to my bike object.

I'm good for now.

drmason789 commented 2 years ago

@cagnulein I seem to have the basics working now, cadence, speed, steering, heart rate and resistance. I've had a lot of trouble with timers not working, seg faults, and slots not being called, nothing working when an event loop added. I've got things working by avoiding the use of signals and slots in favour of simpler delegates and mutexes.

https://user-images.githubusercontent.com/29954900/183228601-4dbbec33-949d-4399-9a62-e3e44fc22a55.mp4

The trixterxdreamv1serial class is now a generic serial port monitor, not specifically for the Trixter X-Dream V1 bike, so it could be renamed to something less specific if you want the code to be reusable.

Do you consider it time for a PR?

drmason789 commented 2 years ago

If you want to use this with my emulator, you need to enable it in Settings / Bike Options.

cagnulein commented 2 years ago

@drmason789 yes please, create a PR. Thanks!

drmason789 commented 2 years ago

@cagnulein I'm currently dealing with 2 problems.

1) When using the real bike, QSerialPortInfo reports the port as busy (the isBusy() function which is apparently deprecated in this version of Qt), and QSerialPort won't connect to it, citing "PermissionError". My .NET application connects to the port fine, even between QZ's scans. 2) In Linux, on my RP4, the code that looks for the bike, positioned in bluetooth.cpp where I believe you specified, doesn't get called. This may be because that code is in deviceDiscovered, and is actually depending on an actual blueooth device being discovered. I'm thinking the code to detect non-bluetooth devices needs to go somewhere else.

cagnulein commented 2 years ago
  • When using the real bike, QSerialPortInfo reports the port as busy (the isBusy() function which is apparently deprecated in this version of Qt), and QSerialPort won't connect to it, citing "PermissionError". My .NET application connects to the port fine, even between QZ's scans.

of course the port is not used by your .NET application, isn't it?

2. In Linux, on my RP4, the code that looks for the bike, positioned in bluetooth.cpp where I believe you specified, doesn't get called. This may be because that code is in deviceDiscovered, and is actually depending on an actual blueooth device being discovered. I'm thinking the code to detect non-bluetooth devices needs to go somewhere else.

yes we should start a parallel thread for this in the bluetooth.cpp constructor. What do you think?

drmason789 commented 2 years ago

When using the real bike, QSerialPortInfo reports the port as busy (the isBusy() function which is apparently deprecated in this version of Qt), and QSerialPort won't connect to it, citing "PermissionError". My .NET application connects to the port fine, even between QZ's scans.

of course the port is not used by your .NET application, isn't it?

No, I restarted the computer and ran QZ in QtCreator. I've for the QZ log putting the QSerialPortInfo information for all the found ports into the debug log. For the specific port the real bike is on, it says isBusy() is true, and that's false for the other ports. Yet if I run my .NET app, and connect between QZ's scans, it opens the port OK.

  1. In Linux, on my RP4, the code that looks for the bike, positioned in bluetooth.cpp where I believe you specified, doesn't get called. This may be because that code is in deviceDiscovered, and is actually depending on an actual blueooth device being discovered. I'm thinking the code to detect non-bluetooth devices needs to go somewhere else.

yes we should start a parallel thread for this in the bluetooth.cpp constructor. What do you think?

Agreed.

cagnulein commented 2 years ago

No, I restarted the computer and ran QZ in QtCreator. I've for the QZ log putting the QSerialPortInfo information for all the found ports into the debug log. For the specific port the real bike is on, it says isBusy() is true, and that's false for the other ports. Yet if I run my .NET app, and connect between QZ's scans, it opens the port OK.

mmm so strange, i used QSerialPort on other project on windows without any issues :( i don't know

drmason789 commented 2 years ago

For the Linux version on the RP4, I copied the code to detect the bike into the bluetooth class constructor, just before the test code for setting up the schwinnIC4Bike. I ran QZ, and the real Trixter X-Dream V1 bike was detected, and I was able to see inputs in the tiles! QZ still says it's "connecting..." though, so it looks like something else is required. I've not committed this yet.

Having it work on the RP is the main priority. No progress on the Windows QtSerialPort / PL2303 problem.

I can see I'm going to have to do something to provide steering calibration. I think the option to disable, and an offset and deadzone to begin with.

I'm finding QZ is very slow on both Windows and particularly the RP4. Hopefully a release build will be quicker.

cagnulein commented 2 years ago

on windows the slowness is about the debug log. on rpi4 be sure that you enabled the video acceleration by hardware

drmason789 commented 2 years ago

I've added steering calibration with new settings:

Still no ideas about the QSerialPort / PL2303 problem. The Windows PL2303HXA driver is something of a problem for these bikes. See my help page for this. I'm wondering if I use some other serial port library for C++ if it will work. QSerialPort reads my virtual COM port for the test controller without problem.

It's working on the RP4, that's the main thing.

drmason789 commented 2 years ago

yes we should start a parallel thread for this in the bluetooth.cpp constructor. What do you think?

My thoughts are in #889.

drmason789 commented 2 years ago

The problem with QSerialPort / PL2303 is solved by my PR #891 for issue #890.

drmason789 commented 2 years ago

@cagnulein do I need to implement something for "Peloton Resistance" ?

drmason789 commented 2 years ago

I can see there are pelotonToBikeResistance and bikeResistanceToPeloton functions with different implementations.

On the basis of what I see on the internet, I need to map Peloton's 0 to 100 to this bike's 0 to 250. Not sure what the real curve is (this would depend on the position and performance of the bike's solenoid) so I'll just do a linear mapping to begin with.

drmason789 commented 2 years ago

@cagnulein I've done this by overriding pelotonToBikeResistance using a linear scale factor, and set the m_pelotonResistance metric alongside the Resistance metric using the inverse. This seems to work with the UI.

cagnulein commented 2 years ago

yes a linear scale could be ok since we don't know a real curve of the resistance of this bike. Thanks