WouterJD / FortiusANT

FortiusANT enables a pre-smart Tacx trainer (usb- or ANT-connected) to communicate with TrainerRoad, Rouvy or Zwift through ANT or Bluetooth LE.
GNU General Public License v3.0
146 stars 78 forks source link

Grade2Resistance() configurable? #51

Closed mattipee closed 4 years ago

mattipee commented 4 years ago

Before I found your project, I was about to implement the standard equations in antifier to account for rolling resistance, drag and slope.

You quote , I had also found

I understand you have put some effort into making resistance work for you. Personally, I didn't like the default 150/100. I understand also that Zwift for example just works on power - speed, gear, cadence is meaningless. But having just got GoldenCheetah to let me replay .gpx files, realism is my aim for now. I implemented an "option 4" which was just a pure real-world-power to resistance calculation, no scaling or top/bottom end adjustments. It felt ok - even better with flywheel weight. Only one ride, and I guess there's nothing perfect, but that's half the fun - having a play with code while pedalling.

So whatever algorithm I find feels best, is the one I will use. And I can modify code to suit me, all good.

But allowing the algorithms and parameters to be configurable and then providing some defaults seems good.

The Grade2Resistance() routine and the tuning of it is a big deal for ride simulation. The use of virtual flywheel weight I posted in the previous issue is also a feel-factor.

More command line parameters might be easiest but perhaps not ideal. I guess there might be considerable GUI work to allow full customisability.

WouterJD commented 4 years ago

I started with antifier myself and -like others- I did not get the powercurve working.I removed some pieces, struggled along and found a working system. You have found the excercises and -indeed- you are the first digging into it - at least according the responses I receive.

As you see -option else- I have implemented a "real life" power formula but was not happy with it. Major issue is that a negative target is nice in real life, but not on a virtual trainer. And so I tried along.

Some weeks ago I discussed the thing with a friend and decided to implement the digital gearbox, providing a range from 0.3 ... 3 and that works in practice as well.

Most users take FortiusANT as-is and do not experiment with the formula's and therefore a solid standard would be good.

Therefore: experiment with formula 4 and I would be obliged to receive it, I will certainly test it myself. Afterwards, parameterization can be decided. That's for later.

mattipee commented 4 years ago

I still need to purge the 2000W anomalies from my stats last year using antifier. It didn't handle max-effort sprints well, though mid-range it seemed to be acceptable.

My main hope, other than ERG training, is to re-ride GPX rides, and so if I have to drop to 1st gear and grind up a 10% hill, so be it. It's what I have to do outside. I understand there are limits. I probably couldn't cycle up a 20% hill anyway. And I accept that low-speed high-resistance may not suit the TACX

However, my understanding in that the negative target will accelerate the wheel, as would gravity, but only until the velocity increases to such a point where the other forces (drag/rolling) balance out and the speed will stabilise - at that point, you change into top gear and keep pedalling, as you would outside.

I will keep playing. I may work in some knobs or dials, graphs or similar as I play. I'll let you know where I end up.

mattipee commented 4 years ago

OK, so low speed with high resistance... even 5%, but testing 10% and up, in first gear, I can get calculated resistance values up >10,000, 250W say, but reported current values from the trainer always end up lower when at slow speed (eg >10000 target, 7000/8000, actual). And it feels really jittery, as if the controller is stuttering.

If I speed up, drop the grade, and really push it, I can get back up to desired resistance values of 10K+ for a hard sprint, and the current resistance values I get back from the trainer track that.

But at slow speed, it's not able to provide the resistance. This is basically what you found? And why you up the wheel speed?

WouterJD commented 4 years ago

My experience is that at low wheel speed, you cannot achieve high powers because of slipping. Therefore I chose to keep the wheel spinning at the maximum speed possible = I cycle in the highest gear available.

This is no issue, because the speed of the wheel is irrelevant and who doubts that: a direct drive trainer has no wheel at all.

There are two parameters: cadence and resistance.

In powermode (ergmode) lowering the cadence causes the resistance to go up, because the resulting power is kept constant.

In resistance mode (grademode) lowering the cadence results in a lower delivered power because the resistance is constant.

The power is sent to (and used by) the requesting trainer software; they all display it and Zwift/Rouvy calculate it to a virtual speed. The speed is sent to the trainer software but simply ignored.

In resistance mode the resistance is set by formula's but not many people even investigate the command line so do not aree with the results (the -f is very important, the others disputable). Therefore you can adjust with UP/DOWN buttons and increase/decrease the resistance.

That's how I build it.

mattipee commented 4 years ago

I don't think I'm "slipping" on the motor wheel, I think the motor controller is just unable to torque back at me at low speed. At least that's what I feel.

So, if I'm riding to a .gpx in GoldenCheetah, the power matters (for logging and performance analysis), but so does kmh speed, as this determines progress through the route. I'm totally happy with the "power only" input to Zwift et al, and it's easy to see the difference between trainer speed and Zwift calculated speed with "trainer difficulty" in Zwift at 50%. I'm using the pure physics calculations at the moment for Grade2Resistance().

I'm considering splitting that function to Grade2Power()... the physics is probably undeniable that for the given input, the power required is calculable - it's the conversion to wheel speed and trainer resistance that matters to stay within the operating bounds of the Fortius.

For that reason then, I'm thinking along the lines of computing a "virtual speed" based off CurrentPower and TargetGrade, if I can... so that trainer wheel speed can be whatever it needs to be, but ANT+ reports power and speed both as accurately as possible.

mattipee commented 4 years ago

If the issue is with high resistance at low speed, then for higher power, the low gears are out. Nothing can be done for grinding up hills in low gear. Obviously, low effort in low gears at low speed is fine, just not high effort.

So, consider a compromise where low gears are forgotten. One could redefine what it means for the motor to spin at a given rate - ie call 20kmh, 10kmh instead. Just a simpaly change to the speed factor. There are two compromises here - one you lose bottom gears, two you lose top speed, as what was 60kmh is now 30kmh.

We've already accepted the loss of bottom gears - you say you ride in top gear only anyway. But the loss of top speed is not ideal.

What if, then, the relationship between motor spindle speed and speed in kmh were non-linear. To achieve high power at low road speed, we require a higher motor spindle speed, say a factor of 2 for 10kmh. And define some speed where the factor drops to 1 - perhaps such that a high cadence sprint in top gear is again realistic.

The compromise here is then only a condensed gear train, and a new relationship between cadence and speed - each gear is now non-linear - but power should remain equivalent to real life.

I'm not sure how different that is from your solution, or whether it's too late to be making sense, but I think my thought is simply to redefine wheel2speed as a non-linear function. There's no calculation accounting for cadence of ftp - just spindle speed.

Do you follow?

mattipee commented 4 years ago

Yes!

image

Picking my inflection points at 10kmh (0.5x beneath that), and 30kmh (x1 above that), and interpolating between at x2, I appear to get the behaviour I thought I would.

I can grind up a 15% hill at 6km/h and ~300W without slipping. Starts feeling lumpy if I increase grade/power still at slow speed, but the inflection points and VirtualSpeed calc could be tuneable to minimise lumpiness for whatever climb I was doing. At 30km/h or above, I get the gearing and tyre-speed-tones I expect.

mattipee commented 4 years ago

Perhaps the virtual flywheel is affected as it has a different impression of speed than I calculate, and perhaps other impacts, so further study required, but I am encouraged by the realisation of a non-linear speed relationship with respect to high power at low speed.

mattipee commented 4 years ago

See #56 and #55 - relevant to parameterisation of Grade2Resistance(), or at least the physical calculations of Grade2Power().

mattipee commented 4 years ago

Making the speed relationship non-linear could, as well as increasing motor spindle speed for low virtual speed, increase virtual speed for high spindle speeds, simulating a higher top gear.

Obviously, all this makes the range of gears ratios available rather inadequate across the full range, and it may be hit-or-miss whether you get the power:cadence ratio right for a given gear on a given grade - as in, on a real bike you might search for the next in-between-gear but not find the one you want - compare a 6 speed cassette to a 10 speed, for example.

But the thing is, it could all be tweak-able in realtime. It is indeed a bit like a digital gearbox, but you're only playing with the shape of a single speed conversion function.

I'm very keen on this idea. For me, the ideal combination is looking like the true physical Grade2Power() calculations, an accurate virtual flywheel weight, combined with a tuneable speed function - I think will give me all that I want. (Yet to work out whether user weight needs to be varied to correct trainer's inertial calcs given the actual-vs-virtual speed difference.

Depending on what the shape of the function looks like, I think you'll be able to tune out skipping at the low end, prevent spinning out at the top end, and otherwise get as close to natural speed/cadence as you like. Or some combination of all three.

mattipee commented 4 years ago

I have created a branch that ontains:

These three changes combined are worth a test ride.

https://github.com/mattipee/FortiusANT/tree/non-linear-speed

brianrow commented 4 years ago

@mattipee , Hello, I was wondering if you could give me the GC device configuration you used for the Fortius. I have tried various combinations and am struggling to get the feedback to the trainer from GC.

brianrow commented 4 years ago

@mattipee , Hello, I was wondering if you could give me the GC device configuration you used for the Fortius. I have tried various combinations and am struggling to get the feedback to the trainer from GC.

mattipee commented 4 years ago

Therefore: experiment with formula 4 and I would be obliged to receive it, I will certainly test it myself. Afterwards, parameterization can be decided. That's for later.

@WouterJD I think I am ready to propose my principles for formula 4, which is definitely my personal preference to ride.

  1. a faithful virtual flywheel weight
  2. Grade2Resistance() option 4 is simply Grade2Power() (independent of any trainer characteristics - just the standard physics-based formulas), plus Power2Resistance().
  3. use non-linear virtual speed relationship at the bottom end to allow high efforts at low speed - effectively allowing a higher wheel speed

The intentions are to:

The non-linear speed curve is subject to some experimentation, and probably nice to be able to adjust it, but I can propose a default.

Leaving the other tweaks on my mind aside (data pages, refactoring, etc...), I think other users may be interested to have a play with the ride-feel, which may be most easily done if we can develop and agree a PR for this to master?

Have you had an opportunity to test my branch linked above? I will resurrect it later this evening and perhaps make a couple of tweaks.

WouterJD commented 4 years ago

Did not check branch yet. Now working on pedal stroke which required splitting user interface off into a separate process which is done now. (under test) Please do suggest formula's and I will implement options

mattipee commented 4 years ago

Would your rather implement yourself than deal with PRs? I'm about to clear down my fork and start again. Happy to collaborate, equally happy to let you have full control.

mattipee commented 4 years ago

In a nutshell, the following two changes:

  1. NOW - Weight for the virtual flywheel so that grade changes take effect gradually.
  2. NOW - Power calculation according to the laws of physics
  3. LATER - workaround for low-speed-high-load limitation, eg non-linear virtual speed relationship

All it would then need is a command line option to choose between "option 3, no flywheel" and "option 4, with flywheel".

See the initial diff here:

diff --git a/pythoncode/usbTrainer.py b/pythoncode/usbTrainer.py
index d1b0927..88fb80c 100644
--- a/pythoncode/usbTrainer.py
+++ b/pythoncode/usbTrainer.py
@@ -353,6 +353,10 @@ def Grade2Resistance(TargetGrade, UserAndBikeWeight, SpeedKmh, Cadence):
         if debug.on(debug.Application):
             logfile.Write ("3: Grade=%4.1f, R_max=%4.0f, R_min=%4.0f, resistance=%4.0f" % (TargetGrade, R_max, R_min, rtn) )

+    elif option == 4:
+        P = PfietsNL(TargetGrade, UserAndBikeWeight, SpeedKmh)
+        return Power2Resistance(P, SpeedKmh, Cadence)
+
     else:
         #-----------------------------------------------------------------------
         # Let's start with the basic formula.
@@ -606,9 +610,7 @@ def SendToTrainer(devTrainer, Mode, TargetMode, TargetPower, TargetGrade,
         elif TargetMode == gui.mode_Grade:
             Target = Grade2Resistance(TargetGrade, UserAndBikeWeight, SpeedKmh, Cadence)
             Target *= PowercurveFactor                      # manual adjustment of requested resistance
-            Weight = 0x0a                                   # weight=0x0a is a good fly-wheel value
-                                                            # UserAndBikeWeight is not used!
-                                                            #       an 100kg flywheels gives undesired behaviour :-)
+            Weight = UserAndBikeWeight                      # virtual fly-wheel value
         else:
             error = "SendToTrainer; Unsupported TargetMode %s" % TargetMode

My personal issue with option 3 is that if I close my eyes, I can't detect grade changes. As an example, if I'm sitting in gear X at 70rpm doing 200W on level ground and encounter a small rise in the road ahead, I want it to feel like it would in real life - to be able to squeeze on, maintain the 70rpm but have the resistance up so I'm doing 300W, 400W, whatever it takes not to slow down in order to get over the rise, and then feel the squeeze backing off as the grade changes again. It's that element of the real-feel that I think is lacking with option 3 - it just feels quite flat to me on the type of riding I've tried.

WouterJD commented 4 years ago

Please proceed! Good work. The powercurve was the difficult part on antifier and was the reason to start FortiusANT in the first place. The setup allows to do excatly what you do: adjust the formula and experiment. Now busy with vortex and pedal stroke analysis; after that we will merge this and indeed a -Fn option to select the formula.

mattipee commented 4 years ago

I guess I don't want to do too much on my own branch, because the further I get, the harder it is to a) keep in sync with your changes, b) for you to merge things back. I don't want to come across as pushy or proud or anything like that, but I'm keen for the work I do to benefit everyone and make it into your master branch for the long term. I'm happy you're on board with my ideas, though. And so active and eager yourself in supporting your user community. Really, well done!

That's why I stripped it all back and started with just 1) flywheel and 2) physics. For example, 3) non-linear-speed-relationship to fix low-speed-high-load requires both an actual speed and a virtual speed to be passed around, and further changes to the tuple returned from ReceiveFromTrainer() have a large footprint and are harder to write, review and maintain.

It becomes a lot easier/cleaner to implement 3) after something along the lines of the cleanup/refactoring I did on one of my branches (now deleted, because I felt it was too-much-too-soon for the project). Or perhaps it becomes less of a conceptual jump to implement 3) once 1) and 2) are merged. This smaller, initial change should be more digestable, I hope.

Do you use git branches locally for feature development? I see you only have master on github - it could aid concurrent feature development to isolate work in branches and use pull requests and rebase to integrate and keep in sync. For example, the changes 1) and 2) in this thread, probably don't interfere with anything your doing. It would be easy for vortex users to aid you in testing a vortex branch, until such times as that work can be merged to master, for example, allowing other work in parallel.

I guess I'm just getting too excited, please excuse me. :)

WouterJD commented 4 years ago

haha no worries.

For me everything is new: python, github, ANT+ and Tacx interface so it's a steep learning curve. Using the full functionality of github is not yet my specialty and yes-there are many changes and a master branch only which is not the ideal way of working. Time is limitted though.

Please continue investigating in the powercurve, we will merge lateron.

AN yes you are excited and that is stimulating; the pedal stroke analysis is still under development, it required quite some reordering to get the multi-processing working and in the meantime it's i-vortex work asking attention. And yesterday I finally did a ride again, which proved that the MP-changes went well.

Perhaps in short future there is some time to properly think of branches and feature requests etc.

Thanks!

mattipee commented 4 years ago

I think you would be well served by partitioning your own work into feature branches. A branch per issue, and close the issue once PR to master has been reviewed and merged.

WouterJD commented 4 years ago

Agree that I should - if this were a product with formal requests. In the working-environment I would implement decent release management.

mattipee commented 4 years ago

Currently thinking three things:

mattipee commented 4 years ago

https://support.garmin.com/en-US/?faq=OJDGFr90Zi3iO7tjrXJUFA

"All Tacx bike trainers are spec'd with a maximum incline value. This value is calculated with a body weight of 143 lbs (65 kg) in reference to the "strength" of the resistance unit when cycling 6 mph (10 km/h). It is important to understand that when your body weight is higher, the maximum incline the trainer is able to simulate will be lower."

I wonder if the Fortius stated grade is true to this formula.

WouterJD commented 4 years ago

Formula according gribble implemented in https://github.com/WouterJD/FortiusANT/tree/54-usbTrainer

WouterJD commented 4 years ago

Implemented in 3.0