-
-
Notifications
You must be signed in to change notification settings - Fork 78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Smoothing of WheelSpeed and CurrentResistance #140
Comments
Does it "feel" smoother? I want to think so. You can still see high frequency, per-pedal-stroke variation in CurrentResistance and CurrentPower, so perhaps increasing smoothing window to 2 seconds might help. But you can see the smoothing effect quite clearly, indeed the effect can be seen in TargetResistance as predicted. So perhaps, the proposal is a command line argument to specify number of seconds of smoothing... 0-3. |
The fundamental issues I think I'm trying to address for myself are:
|
Nice analysis. I also observed the same behaviour. When pedalling at a stable pace with stable power (well, at least it feels stable) the power reported by FortiusANT jumps around. I was actually thinking of decoupling the sampling frequency from the broadcasting frequency. I read somewhere that the USB interface can be polled at +/- 70ms intervals, while as you mentioned it is currently sampled at 250ms. This should give about 3,5 times more datapoints to work with, which i think will improve the final result. Thoughts? |
Cheers @marcoveeneman . I don't think increasing frequency will help here, but maybe. I originally played with increased frequency to see what I could get out in terms of a polar graph for pedal stroke analysis. Wouter's implemented this as a GUI option now. I was never really convinced that the resolution and response of CurrentResistance was quite good enough for meaningful PSA. The underlying serial protocol from head unit to motor controller ultimately determines the resolution. Totalreverse stated somewhere what the maximum frequency was in this case. Running USB higher than that, just duplicates values, and can lead to instability. A higher frequency than 4Hz may make button presses more reliable, but as I'm trying to smooth the values obtained anyway, a higher sampling rate would probably just mean more values to average. Or are you thinking that if 4Hz is subsampling the available data, then obtaining instead say, all "10" unique points per second and averaging them all would yield a more accurate average? |
If you do NOT activate PedalStrokeAnalysis, the sampling frequency = 250ms |
Yes. I run on the Raspberry Pi, without GUI. That then defaults to 4Hz. 👍 |
@mattipee Yes, i think taking an average of 10 unique samples within 1 second will yield a better result than taking an average of 4 unique samples within 1 second. With only 4 samples a second you are missing a lot of potentially useful data points. My proposal is to always sample at the maximum frequency the USB head unit allows and only broadcast over ANT at 4Hz. |
@marcoveeneman it's possible within the current implementation (see QuarterSecond variable). However, generally, I think the event loop could be rearchitected, introducing multiple threads and shared state (ANT+, USB, UI being the main three). Indeed, I think current threading is the cause of the GUI not functioning correctly on Linux. That said, leaving architecture alone and simply smoothing over "number of seconds, 0-3s), whether at PSA "max rate" or even at the default 4Hz, I think is an improvement worth considering. I do enjoy improving accuracy for accuracy's sake, but also minimising change footprint for Wouter's sanity. 👍 |
Hmmm... When TTS (Tacx Training Software) connects to the trainer through USB it receives the data like we do. TTS can decide what to do with it. FortiusANT is a bridge between USB and ANT and passes received data to the other side. Do you say that Zwift canot handle the data like TTS could?
In a 250ms cadence it would mean averaging over 4 samples. |
Unfortunately, no TTS source available, but GoldenCheetah's Fortius implementation has comments suggesting it averages power over 1 second (10Hz). |
I'll look into it |
Interesting that TTS uses a higher frequency during run, and GoldenCheetah also. The biggest gain I think for >4Hz is responsiveness of the buttons - I sometimes get missed-clicks, presumably because I press and release within a 0.25s period and it reports as 0. Reliability of button presses should increase with an increased frequency. I think whatever the frequency, the averaging should be done in terms of "seconds" rather than "samples". And indeed the split between the value used for Calculation and the value used for display on PSA requires care. I have a branch in my fork started... https://github.com/mattipee/FortiusANT/tree/smoothing I'll absolutely have a play next time I'm riding. I've an ERG mode FTP-building training session to repeat, and can compare the Strava trace with the last one. I intend to increase frequency of no-GUI USB to 10Hz and see if I get as far as parameterising number of seconds of averaging via the command line. I'll probably run it at either 1 or 2s averaging. In terms of terminology, I'm calling it "smoothing using a running average" but it's a "low pass filter" we're talking about, is that accurate? |
@marcoveeneman Your 200W, 85rpm beats my 150W, 75rpm. Going to have to up my game... Buttons are improved at 10Hz. That alone is enough reasoning for me to increase. Any running average is going to lag, you're right. The Fortius doesn't respond that quickly to changes in TargetResistance... I tried to implement road-feel, but it just doesn't respond that fast. But CurrentResistance and WheelSpeed do appear to reflect reasonably well the variation around the pedal stroke, which other than for PSA, we do want to "ignore", I think. I think I'm settling towards 1s (10Hz) averaging of WheelSpeed and TargetResistance, and possibly additionally 1s (4Hz) reported Power over ANT+. Just to clean it up a little. |
I see your graphs and felt the need to be able to create this in more places. |
@mattipee Keep up the good work and you'll do 200W as well 👍 @WouterJD Nice, now my next question would be: can we also export as CSV for easy import in excel/numbers? :-) |
Would have been easier to implement. You can interpret the JSON using the related excel sheet |
Simple solutions to that issue are tab-separated, semicolon-separated or quoted values. But in any case, I look forward to playing with JSON/tcx or whatever format of data output - useful stuff. Good work! 👍 I'm not going to mention graphing. |
Graph is in the excel 😊
From: mattipee <[email protected]>
Sent: zondag 15 november 2020 23:15
To: WouterJD/FortiusANT <[email protected]>
Cc: WouterJD <[email protected]>; Mention <[email protected]>
Subject: Re: [WouterJD/FortiusANT] Smoothing of WheelSpeed and CurrentResistance (#140)
Simple solutions to that issue are tab-separated, semicolon-separated or quoted values.
But in any case, I look forward to playing with JSON/tcx or whatever format of data output - useful stuff. Good work! 👍 I'm not going to mention graphing.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#140 (comment)> , or unsubscribe <https://github.com/notifications/unsubscribe-auth/ALKJPXNKGIXBFNMOJYMOZHLSQBHGVANCNFSM4TSQAACQ> . <https://github.com/notifications/beacon/ALKJPXOHLKZKGPCRS3XLGR3SQBHGVA5CNFSM4TSQAAC2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOFNPP7DA.gif>
|
Dear all, 722 #---------------------------------------------------------------------------
723 # Our main loop!
724 # The loop has the following phases
725 # -- Get data from trainer
726 # -- Local adjustments (heartrate monitor, cadence sensor)
727 # -- Display actual values
728 # -- Pedal stroke analysis
729 # -- Modify data, due to Buttons or ANT
730 #---------------------------------------------------------------------------
731 if debug.on(debug.Function): logfile.Write('Tacx2Dongle; start main loop')
732 try:
733 while self.RunningSwitch == True and not AntDongle.DongleReconnected:
734 StartTime = time.time()
735 #-------------------------------------------------------------------
736 # USB process is done once every 100ms
737 #-------------------------------------------------------------------
738 if (time.time() - LastUSBtime) > 0.1:
739 LastUSBtime = time.time()
740 QuarterSecond = True
741 else:
742 QuarterSecond = False than I added that same part of code just before the ANT+ section, but there it still does 250 with the old ANT time variable. 826 #-------------------------------------------------------------------
827 # Do ANT work every 1/4 second
828 #-------------------------------------------------------------------
829 if (time.time() - LastANTtime) > 0.1:
830 LastANTtime = time.time()
831 QuarterSecond = True
832 else:
833 QuarterSecond = False
834
835
836
837 messages = [] # messages to be sent to ANT
838 data = [] # responses received from ANT
839 if QuarterSecond:
840 LastANTtime = time.time() They both read and write from the same self. something datastruct and they are consecutive, so no problems with threading anyway. now the trick for smoothing: it's very simplistic and up for improvement, but it works. (use it a lot in machinery for ugly signals) I use the following values: this runs super smooth. but might be a bit too smooth. I think I will try 90% old 10% new. will run my next training with it to see how it performs, but for the test ride I really like the feel. not sure how to share the code @WouterJD because it's such a nasty hack. I was not sure what Axis was doing, and of course it's of no use filtering the button bits. 80 #-----------------------------------------------------------------------
1881 # Parse buffer
1882 #-----------------------------------------------------------------------
1883 filtConst = 0.9567865
1884 tuple = struct.unpack (format, data)
1885 self.Axis = tuple[nAxis1]
1886 self.Buttons = tuple[nButtons]
1887 self.Cadence = self.Cadence * filtConst + (1-filtConst) * (tuple[nCadence])
1888 self.CurrentResistance = self.CurrentResistance * filtConst + (1-filtConst) * (tuple[nCurrentResistance])
1889 self.HeartRate = tuple[nHeartRate]
1890 self.PedalEcho = tuple[nEvents]
1891 self.TargetResistanceFT = tuple[nTargetResistance]
1892 self.WheelSpeed = self.WheelSpeed * filtConst + (1-filtConst) * (tuple[nSpeed]) |
Hi @omedirk axis is the rotation of the steering unit, no need to smooth Question: where does 0.9567865 come from? |
II have been checking into this This delay is quite bad; it is done ALWAYS instead only when retrying. Just to inform you |
changed that section to: 7 while retry:
1778 data = self.USB_Read()
1779 if not len(data) < 40:
1780 break
1781 time.sleep(0.1) # 2020-09-29 short delay @RogerPleijers
1782 retry -= 1 now checking the cpu load, because if there's no delay anywhe it will go up high.. edit: just checked, only 2% on the rpi. so there's enough delays |
I am not sure if you even need to smoothen the cadence, that's actually quite ok. I use a RPi, so I am not entirely sure what the pedal stroke analysis does.. The factor is calculated from the formula's on the site. it's basically the time constant of the filter and the sampling frequency. update: i think .95 is too much really, tomorrow is training day. hopefully can give a good value then. update 2: @WouterJD now that I just posted my code I see that I send the ant messages every 100ms.. oops. but it works fine.. |
I came to similar implementation; note that it is not a regular wait-loop - the retry should be occasional. A likely cause for the error may be that Refresh() calls Receive() before Send(), which may cause that the trainer cannot respond. |
You have redefined CycleTimeANT as 0.100, I do not know whether this will cause undesired effects. |
Now that the JSON export is built-in, these measurements can be nicely conducted. I have added PedalEcho (=1 when the pedal passes the magnet) and PedalCycle, which is alternating 0 and 250 for each cycle. I have done the following test, in manual mode.
The first test, the first cycles maximum emphasis on the right leg and the next cycles on the left leg. Then the part with cadence = 40rpm (three pedal rotations) And the part with cadence = 100rpm (three pedal rotations) The CurrentResistance line has markers for each data-point; the others have no markers but the same number of datapoints. I am curious what you think of this. |
I have done a similar test without Pedal Stroke Analysis, so with a 250ms cycle: And zooming into some cycles at 80rpm: The CurrentResistance is quite stable (and I tried to cycle as stable as possible, even though it's a short measurement). So for this configuration in my occasion, I consider this quite acceptable. |
@omedirk @mattipee Command-line flag -S factor/cps
Suggested implementation [Exact syntax to be defined]
instead of SmoothFactor I could have used SmoothFactorResistance and omit the if/else; but I think this is more understandable although that is a matter of taste. Suggestions welcome. |
@WouterJD I was looking at the graphs where PedalStrokeAnalysis was enabled and was wondering what the sample frequency is, looking at the code this should be around 50hz (20ms cycle). I was under the impression that the head unit could only be read every 70ms, am i wrong in that? Your graphs seem to indicate it is possible to go faster. I'm a bit confused now. |
Correct; TotalReverse thought it would not be possible but it works. |
Just to let you guys know, I settled with an update rate of 100ms for the tacx, 250 for the ant+ stick. |
Would you suggest to change the 250 timer into 100ms? When using PedalStrokeAnalysis, the timer is faster already |
Without the gui 100ms runs fine.
I run the code on a raspberry, so no pedal stroke analysis..
But as I was not sure why the ant was running at 250ms I created two timers.
The whole thing was also a bit of an exercise in understanding the code.
Not sure if after all there is a big benefit 😊 but I like 100ms instead of 250ms. At least it sounds better, 10Hz instead of 4.
From: Wouter Dubbeldam <[email protected]>
Sent: Thursday, 26 November 2020 19:07
To: WouterJD/FortiusANT <[email protected]>
Cc: omedirk <[email protected]>; Mention <[email protected]>
Subject: Re: [WouterJD/FortiusANT] Smoothing of WheelSpeed and CurrentResistance (#140)
Would you suggest to change the 250 timer into 100ms?
Might be too fast when the GUI is on
When using PedalStrokeAnalysis, the timer is faster already
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#140 (comment)> , or unsubscribe <https://github.com/notifications/unsubscribe-auth/AID2MWLWOMFWCQADICWHI5DSR2KMFANCNFSM4TSQAACQ> .
|
ANT definition is 4Hz; Why would a heartrate belt or cadence meter send more data? |
Since there is no communication here, I assume can be closed. |
As smoothing (averaging) is now used to good effect in calibration (#132), my thoughts have now come back to something I've meant to look at for a while.
I've noticed that, for example, in ERG mode at 100W, the reported power moves around, oscillating above/below. At higher wattage, the oscillation seems greater.
There are two factors... WheelSpeed and CurrentResistance
Zwift is capable of 3-second averaging of received power, which I think I have enabled, but I see the oscillation on-screen as well as in Strava uploads.
My thoughts are that by smoothing WheelSpeed a little, TargetResistance becomes smoother. And by smoothing CurrentResistance a little, coupled with the smoothing of WheelSpeed, CurrentPower becomes smoother.
Only "a little"... the issue I think is that because we sample 4Hz, the figures are going to vary through the pedal stroke. At a higher sampling frequency for "pedal stroke analysis", one might want to keep the raw CurrentResistance values, but for the purposes of the mathematical calculations to and from power, a slight smoothing is advantageous.
If the WheelSpeed and CurrentResistance are changing/oscillating through the pedal stroke, then CurrentPower will oscillate. But if WheelSpeed oscillates... for any given sample in time, both TargetPower2Resistance and TargetGrade2Resistance are going to oscillate, too.
Too much smoothing will result in sluggish response, a loss of peak power values, etc... as I found when trying to use the AverageResistance value available in Filler36_37. I couldn't work out how long an average that was, but too long.
So just 1 seconds worth of "running average" smoothing on WheelSpeed and CurrentResistance... simply to eliminate the effects of an uneven pedal stroke from the gross power/resistance calculations.
The text was updated successfully, but these errors were encountered: