Replies: 8 comments 54 replies
-
The big question is whether you'd like it near-real time (as the PM5 does) or just at the end of the stroke. The latter is already possible, the real-time option requires some rethinking of the filter algorithms in RowingStatistics (they trim all curves to exclude noise). Please also note, the number of datapoints in a force curve varies quite a lot: Lars' his waterrower has 5 datapoints in the drive, a concept2 typically has 80. So that is something to keep in mind. One practical point: if you have to sacrifice two tiles, it would be the calories and the watts. Total time is quite a crucial one. In practice, Watts are just another presentation of pace, so not a big deal if you lose it (on a PM5, the are never on the same screen......). |
Beta Was this translation helpful? Give feedback.
-
This was a very conscious design decission I made over a year ago. Really revisitting this would require me to go back to my notes. Key concept here is that for the data in the flank we don't know what phase it belongs to, and we do know for the data in front of the flank. That is correct, but the createCurveAligner in (see https://github.com/JaapvanEkris/openrowingmonitor/blob/v1beta_updates/app/engine/RowingStatistics.js) is there to remove the head and tail values if there is noise in them. As torque is based on potentially volatile metrics and thus can be noisy, it is a necessary step that can't be skipped. That is what I meant with that the current forcecurve implementation requires some serious thinking before turned into a live forcecurve viewer on the GUI (as cleaning up noise live can have weird visual effects). If you use the exposed driveHandleForceCurve from the RowingStatistics with a decent minumumForceBeforeStroke, that shouldn't happen. One can debate if the curvealigner should always prefix the curve with a starting base value (zero for force, but what to do with handle speed), but that is the thing that produces force curves for over a year now in quite a reliable fashion (and yes, I check them all each row). And please, let's not have discussions about extensions in a pull request. This reduces the clarity of the status of this pull request for the package maintainer. |
Beta Was this translation helpful? Give feedback.
-
So I have another question on this topic (sorry about the amount of questions, the reason I am asking them as I am looking at the code and I dont want to reinvent the wheel in terms of design decision) that relates to the force curve. On the initial start i.e. when we are in the WaitingForDrive for the first time after starting the program, logically ORM waits until the deltaTime Series is filled up with data, then a power is detected and then starts counting totalNumberOfImpulses and certain other metrics. But this is not the case for the force/torque data as it is calculated for every impulse. now my question is that would not ORM be slightly out of sync (totalSpinningTime and totalNumberOfImpulses) with one impulse at the start that impacts (not materially I suppose) distance calculation as it is possible like with this data. What I am trying to say is that here it is clear that the whole rowing session starts with a drive, but (flankLength: 7) the impulses for distance calculation as well as for the data for force curve gets collected only after 0.06313 (i.e. the first datapoint for which data is considered is 0.058266). So for your to understand the background of my question: with a "cold" start of ORM I noticed that the force curve for the very first stroke (assuming that it is started with Drive immediately) starts with rather high values. Its clear from the data that the drive started 7 impulses before, and the code? for deltaTimeBeforeFlank is very much correctly gets the data from at the beginning of the flank (_deltaTime.yAtSeriesBegin() -> seriesArray[0]): totalNumberOfImpulses += 1
_deltaTimeBeforeFlank = _deltaTime.yAtSeriesBegin()
totalTimeSpinning += _deltaTimeBeforeFlank
_angularVelocityBeforeFlank = _angularVelocityAtBeginFlank
_angularAccelerationBeforeFlank = _angularAccelerationAtBeginFlank
_torqueBeforeFlank = _torqueAtBeginFlank The totalNumberOfImpulses would be 1 and lag behind exactly with 1 impulse compared to the totalSpinning time: when the totalNumberOfImpulses gets the first increment, the totalSpinning time will be 0.315088 (which is not the beforeFlank - that is 5.3 but rather at the beginning of the flank). But the _torqueBeforeFlank value will equal to the value calucated at 0.06313 which for me would be the beginning of a different flank than the data for totalNumberOfImpulses and totalSpinningTime. I suppose you have considered this (and you may concluded (and I dont want tear up old wounds so to speak :), that it is fine based on a cost/benefit approach, or I am simply missing something :)), but I was wondering whether this creates a slight out of sync state. (please note that the below torque values are for representation purposes only) 5.331447 | _torqueBeforeFlank = 0 | _torqueAtBeginFlank = 0 |
Beta Was this translation helpful? Give feedback.
-
@Abasz, also looking at this discussion as well: #131 I think what we clearly agree that Weblient settings should be kept in the webclient, and that a good first step would be to allow the client to configure itself :). Discussions about the GUI ordering other features to change settings and these features persisting their own settings etc. could come in a later stage (also looking at future plans where the datarecorder for fit and tcx potentially providing data second-based). I think that the GUI managing its own settings is a good first step which will fit that plan regardless, and we could do that anyway. |
Beta Was this translation helpful? Give feedback.
-
While I was fiddling with the force curve to see what is the best solution (I dont think real time is viable actually, I gave it a quick spin by updating the UI on every new impulse and it was not that smooth, the chart.js was not able to handle the updates - which is possible was due to the animation, so its possible that it could be tweaked, but I am not rooting for real time). Anyway, so I was playing with the damper settings on my machine and tried to see how higher DF would impact on force curve and technique. I noticed that with plain torque stroke detection (i.e. where I set the minimumStrokeQuality to 1 and minumumRecoverySlope to 0) when I set a higher damper and then back to lower ORM was not able to recognize any strokes. I realized that this is due to the fact that we are using the dragfactor of the last recovery to detect the next where that DF is no longer valid (as the damper was lowered and the air drag decreases, the torque actually never dips under zero). This is just an observation I wanted to share, not sure if you have ever thought about this. Now the main reason or question I would like to ask is that while I was debugging this (naturally first I thought its a bug of the stroke detection but really this is a bug of the settings) I noticed that when you initialize the Flywheel you call reset() which pushes two zeros to the _deltaTime.push(0, 0)
_angularDistance.push(0, 0) Why is this necessary? What is the purpose of having one initial element that is zero in the deltaTime OLS and the TS Quadratic series? The interesting part is that you dont have the same for the recoveryDeltaTime, i.e. after reset the first element in that is an actual data point. |
Beta Was this translation helpful? Give feedback.
-
So I completed an update of the GUI it adds the following features: https://github.com/Abasz/openrowingmonitor/tree/improve-gui
There are a couple of notes as at now:
Next step: |
Beta Was this translation helpful? Give feedback.
-
I actually created a factory for the metric tiles that simplifies adding more tiles. I will push these commits shortly. Basically adding a new tile should be as easy as adding a property to an object in code that compiles with a set interface. For example: {
distance: {
displayName: 'Distance',
size: 1,
template: (metrics, showIcon) => {
const linearDistance = formatDistance(metrics?.totalLinearDistance)
return simpleMetricFactory(linearDistance.distance, linearDistance.unit, showIcon ? icon_route : '')
}
},
calories: { displayName: 'Calories', size: 1, template: (metrics, showIcon) => simpleMetricFactory(formatNumber(metrics?.totalCalories), 'kcal', showIcon ? icon_fire : '') },
} Any rendering logic (both in the settings tab and in the dashboard) will be driven by this object and constructed automatically. So any metric that we would like to add can be added simply by adding a new property on this object, add Since I removed any dependency on server side formatted data and reworked tile creation logic (that enabled to remove the odd object property filtering that I did not really understand), for these factories all metric is available that is available as payload via the websocket from the server. What I am trying to say here is that if we want a metric to be available it just need adding to one object with the metadata. Also if a new metric is added on the server side to the broadcasted metrics, it should be available to be added to a new metric tile. Any complex calculation logic can be performed in the factory before passing the This should mean that there is no need to add ternary operators and logic paths to the displayed metrics as one can crate a tile for the remaining interval time, the elapsed interval time as well as the total moving time and select to display those that the user want to see. |
Beta Was this translation helpful? Give feedback.
-
wow guys! I've been away from the rower for a couple of months with an injury (and general laziness) and I've come back to a huge amount of work that you two have done with open rower! I'm loving the GUI enhancements! |
Beta Was this translation helpful? Give feedback.
-
Somthing I thought would be usefull:
A few minor things are needed (options for showing force curve vs. other metrics). I will create a separate branch for testing once that is done.
Beta Was this translation helpful? Give feedback.
All reactions