Support for rowing data / migrating from rowsandall.com

So how can I get the stroke data analysis to intervals or do I need ErgData app?

What about the pm5 local log?

But isn’t it the case for all other sports? Even in cycling you don’t have a continuous pressure on the pedal. You may see a constant power value, but this is also an average over one or two rotations, and during this rotation you won’t have a constant value, you have highs and lows, but you never see that

1 Like

But isn’t it the case for all other sports? Even in cycling you don’t have a continuous pressure on the pedal. You may see a constant power value, but this is also an average over one or two rotations, and during this rotation you won’t have a constant value, you have highs and lows, but you never see that

In cycling the unpowered part is measured in miliseconds. In rowing a drive (the powered section) is typically 0.6 to 0.9 seconds long, followed by a 1.3 to 2.5 seconds recovery (an unpowered section). In general we maintain a 1:2 ratio, that is for each second of work, we recover for two seconds. Not many sports do that. And that makes the sport really different: where in cycling speed (also due to inertia) does not actually vary that much, you get huge speed variations within a stroke, especially on high drag setups. So we need to calculate all metrics across the stroke.

But one might wonder the reverse: if in cycling people really would make a point about decent metrics, you’d want a left and right force curve per rotation, as well as timings etc.. And why would a time driven approach suffice, as it implies you throw away data at high cadance?

This definitely is the data I’m missing - along with stroke length.
I’m currently using exr although I can get the Pm5 to record the data locally as well. Data goes via bt to exr but once this link is established you can play with the pm5 and program intervals or splits there without affecting what exr sends.

You have to access the pm5 log via c2 utility and cable

What is the easiest way to be able to see stroke data from pm5 please?

I’m on EXR too :slight_smile: Saw you on the Discord server.

The thing is EXR records per second, thus destroying the relationship between strokes and time/distance. And to make things worse, they don’t record the stroke number. The PM5 itself will record stroke data, but only via ErgData. Perhaps we should nudge EXR to fix this, and expand on the recorded data, as distance per stroke is actually a field that is reported by a PM5 as a field and can be recorded in a fit-file.

Most people work around this by using EXR on a windows laptop and then connect EXR via the USB-cable, and use ErgZone or ErgData to record the session.

You need to record the workout with the ErgData app. It will automatically be uploaded to your C2 Logbook. When you “view” the workout in your logbook, you will see a button to “Download as FIT”. Find the file you just downloaded, go to Intervals.icu and, at the top of the Activities page, select “upload”.

If you mean the Memory page on the PM5, workouts in that list do not have per-stroke data and would not be useful for analysis in Intervals.

As a first setup, I tried to map the fields we currently have in OpenRowingMonitor rowsAndAll csv-export (as most metrics are there because Sander and his team thought it was a good idea, and the overlap with users wanting to see this data in our GUI is 100%, I consider that a good start), and relate that to the data a PM5 produces, what a fit-file can store and what intervals.icu can now show:

RowsAndAll field PM5 field FIT-file field Known intervals.icu field
TimeStamp (sec) - timestamp -
ElapsedTime (sec) PM5 reports this in 0.01 sec in message 0031 (UInt8) - time
DistanceMeters PM5 reports this in 0.1 meters in message 0031 (UInt8) distance distance
Stroke Number PM5 reports this as strokecount in message 0035 (UInt16) total_cycles -
HRCur (bpm) PM5 reports this in bpm in message 0032 (UInt8) heartrate heartrate
Speed PM5 reports this in 0.001 m/s in message 0032 (UInt16) Speed pace
Power (watts) PM5 reports this in Watts in message 0036 (UInt16) power power
Cadence (stokes/min) PM5 reports this in SPM in message 0032 (UInt8) cadence cadance
StrokeDistance (meters) PM5 reports this in 0.01 meters in message 0035 (UInt16) cycle_length16 MISSING
DragFactor PM5 reports this as dragfactor in message 0031 (UInt8) Resistance (UInt8) MISSING
- PM5 reports work per stroke in 0.1 Juoles in message 0035 (UInt16) accumulated_power contains the accumulated work, which allows work per stroke to be calculated when using ‘Smart recording’ MISSING

Some fields can not be transported by a vanilla FIT-file, but require “developer fields”:

RowsAndAll field PM5 field Proposed FIT-file field Known intervals.icu field
DriveTime (ms) PM5 reports this in 0.01 sec in message 0035 (UInt8) In miliseconds (UInt16) MISSING
StrokeRecoveryTime (ms) PM5 reports this in 0.01 sec in message 0035 (UInt16) In miliseconds (UInt16) MISSING
DriveLength (meters) PM5 reports this in 0.01 meters in message 0035 (UInt8) In 0.01 meters (UInt8) MISSING
Calories (kCal) PM5 reports this in KCal in message 0033 (UInt16) Is available at the session, split and lap level, not at the stroke level MISSING?
PeakDriveForce (N) PM5 reports this in 0.1 LBS in message 0035 (UInt16) In 0.1 Newton (UInt16) MISSING
AverageDriveForce (N) PM5 reports this in 0.1 LBS in message 0035 (UInt16) In 0.1 Newton (UInt16) MISSING
Handle_Force_(N) PM5 reports this in 0.1 LBS in several 003D messages (UInt16) In 0.1 Newton (Array of UInt16) MISSING

The handle force curve is in fact quite an important feature, where @Sander_Roosendaal has done some really smart work by aggregating across all strokes, and allow to zoom in on a part of the session.

3 Likes

ErgZone captures all that data. Once Intervals supports them then we’ll definitely make adjustments to the fit file generation we export to Intervals.

Those exact data points are also available for the SkiErg, so the same format would work for XC skiing. The BikeErg is different and does not expose the same data.

2 Likes

Intervals.icu now supports rowing data. CrewNerd exports directly, and PM5 FIT files via ErgData give per-stroke metrics like stroke rate, distance, work, force, and cadence. Some advanced fields need developer support. EXR loses stroke-level detail, so ErgData is preferred.

I’m the lead developer of OpenRowingMonitor so I’m quite aware what FIT-files can transport, and what intervals.icu supports for rowing as we provide the richest dataset of them all. And as far as I know, vanilla FIT-files can’t transport force-values in its records.

Essentially currently we are stuck with the metrics that make sense for a bike as well. To really analyse rowing you need more data, as drive length, stroke shape (i.e. the force curve), ratio and stroke-to-stroke-consistency are much more dominant than in cycling. Platforms like RP3 go way beyond RowsAndAll’s very decent but basic analysis, and do extremely valuable analysis on this data. So to become really usefull to rowing analysis, and come even close to RowsAndAll’s current capabilities, a lot has to be added…

Hi - I’m the developer of the CrewNerd app. I moved quickly to support intervals.icu when Sander told me that rowsandall would be going away.

The thing I’m most concerned about now are Courses/Challenges. Having a public repository for these is so important. They can be tricky to create for most people, so being able to share them is incredibly useful. I hope that we see that feature added to intervals.icu.

5 Likes

Hi there, I am working on FIT export from Rowsandall that captures some of the fields as developer fields. Here’s what I am planning so far. Your feedback is appreciated:

rowingdata column Developer field name Base type Scale Units
DriveLength (meters) DriveLength UINT16 1000 m
DriveTime (ms) DriveTime UINT16 1 ms
DragFactor DragFactor UINT16 1
StrokeRecoveryTime (ms) StrokeRecoveryTime UINT16 1 ms
AverageDriveForce (lbs) AverageDriveForceLbs UINT16 10 lbs
PeakDriveForce (lbs) PeakDriveForceLbs UINT16 10 lbs
AverageDriveForce (N) AverageDriveForceN UINT16 10 N
PeakDriveForce (N) PeakDriveForceN UINT16 10 N
AverageBoatSpeed (m/s) AverageBoatSpeed UINT16 1000 m/s
WorkoutState WorkoutState UINT8 1

Regular fields:

rowingdata column FIT field Type Notes
TimeStamp (sec) timestamp Native Seconds since 1970-01-01 UTC; relative timestamps are combined with row_date
cum_dist or Horizontal (meters) distance Native Cumulative distance in meters (FIT scale 100)
Cadence (stokes/min) cadence Native Stroke rate in strokes/min; omitted if zero
HRCur (bpm) heart_rate Native Clamped to 0–255 (FIT uint8)
Power (watts) power Native Clamped to 0–65535 (FIT uint16)
Stroke500mPace (sec/500m) enhanced_speed Native Converted to m/s via 500/pace; FIT scale 1000
latitude position_lat Native Degrees; omitted if missing or invalid
longitude position_long Native Degrees; omitted if missing or invalid

And session data:

Source FIT message Fields
Session totals SessionMessage total_distance, total_calories, avg_heart_rate, max_heart_rate, avg_cadence, avg_power
Lap boundaries LapMessage Same as session; currently one lap for the whole workout
Position LapMessage start_position_lat/long, end_position_lat/long (degrees) when valid
1 Like

Some comments from my perspective:

  • StrokeRecoveryTime is a good name, but perhaps DriveTime might be called StrokeDriveTIme to maintain symmetry in naming
  • Distance per stroke is missing. It is actually stored by more recent Garmin watches in “cycle length16” (it is part of the ANT+ spec), and they even display it quite prominently in Garmin Connect. In RowsAndAll it is called “StrokeDistance (meters)”
  • Stroke number is missing. It can be stored in “total cycles(cycles)”. It is a native Garmin field and rowing machines report it via ANT+ to Garmin watches (but it doesn’t seem to be recorded in the resulting FIT file)

I do have a kind request: could you please add the force curve in there as well? I realize this deviates from a simple metric like pace, making it much more complex to implement, but for me seeing the force curve afterwards and see where there are dents is valueable feedback. I find it a valueable tool, and it was one of the main reasons to subscribe to RowsAndAll. There are array elements in the FIT-structure, so my impression is that it should work.

Regarding the session data, my interpretation of the workout structure:

  • The session level
  • The split level (what most people would call an interval), we see this being filled on a Garmin when a session gets multiple intervals
  • The Lap level (what most would call splits), we see this filled when a Garmin watch is set to “Autolap”.

On RowsAndAll you have splits/intervals, and I think they would best fit onto FIT splits.

2 Likes

I’ll add StrokeDistance as a developer field. The cycle distance has a max of 2.55 meters if I am not mistaken. That wouldn’t work as we are between 6 and 12 meters typically in rowing.

Force curve is a difficult one with Fit. Let me think about it.

Intervals.icu has excellent interval discovery functionality. And there is WorkoutState and LapIdx. It was my intention to keep the FIT export a function of the rowingdata python library (GitHub - sanderroosendaal/rowingdata: Python tools to process rowing (the sport) data ¡ GitHub). On Rowsandall I have some extra fields but intervals are basically calculated from Workoutstate.

FYI, if you want to track details of what I am doing, best to check this document in the develop branch of the rowingdata repo: rowingdata/docs/FIT_EXPORT.md at develop · sanderroosendaal/rowingdata · GitHub I’ll try to keep the discussion here high level and digestable for non-coders.

1 Like

On in-stroke data (oarlock data, force curves, seat speed, boat speed and acceleration), different vendors have different approaches. Rowsandall does its best to support all approaches, but I am doubtful there can be a unified FIT file approach. Studying this.

OK, I have vibe coded something for your review: rowingdata/docs/FIT_EXPORT.md at develop ¡ sanderroosendaal/rowingdata ¡ GitHub

Also note that I have added support for Catch, Finish, Slip, Wash, PeakForceAngle.

Developer fields exported

rowingdata column FIT field name Base type Scale Units
DriveLength (meters) DriveLength UINT16 100 m
DriveTime (ms) StrokeDriveTime UINT16 1 ms
DragFactor DragFactor UINT16 1
StrokeRecoveryTime (ms) StrokeRecoveryTime UINT16 1 ms
AverageDriveForce (lbs) AverageDriveForceLbs UINT16 10 lbs
PeakDriveForce (lbs) PeakDriveForceLbs UINT16 10 lbs
AverageDriveForce (N) AverageDriveForceN UINT16 10 N
PeakDriveForce (N) PeakDriveForceN UINT16 10 N
AverageBoatSpeed (m/s) AverageBoatSpeed UINT16 100 m/s
WorkoutState WorkoutState UINT8 1
StrokeDistance (meters) StrokeDistance UINT16 100 m
catch, catchAngle Catch SINT16 10 deg
finish, finishAngle Finish SINT16 10 deg
slip Slip SINT16 10 deg
wash Wash SINT16 10 deg
peakforceangle PeakForceAngle SINT16 10 deg
effectiveLength EffectiveLength UINT16 100 m

Record message mapping (native fields)

rowingdata column FIT field Notes
TimeStamp (sec) timestamp UTC; relative timestamps combined with row_date
cum_dist or Horizontal (meters) distance Cumulative meters (FIT scale 100)
Cadence (stokes/min) cadence Strokes/min; omitted if zero
HRCur (bpm) heart_rate Clamped 0–255
Power (watts) power Clamped 0–65535
Stroke500mPace (sec/500m) enhanced_speed Converted to m/s via 500/pace
latitude position_lat Degrees; omitted if invalid
longitude position_long Degrees; omitted if invalid
Stroke Number or row index total_cycles Stroke number (data is per-stroke)

In-stroke curve export

When instroke_export is not 'off', comma-separated curve columns (RP3 curve_data, Quiske boat accelerator curve, oar angle velocity curve, seat curve) can be exported:

instroke_export Behavior
'off' (default) No curve export; backward compatible.
'summary' Per-stroke metrics (q1, q2, q3, q4, diff, maxpos, minpos) as developer fields, e.g. HandleForceCurve_q1.
'downsampled' Fixed-length curve (default 16 points) per stroke as SINT16 array developer field.
'companion' Sidecar .instroke.json file with full curve data per stroke.

Column mapping: curve_data → HandleForceCurve, boat accelerator curve → BoatAcceleratorCurve, oar angle velocity curve → OarAngleVelocityCurve, seat curve → SeatCurve. Override via instroke_column_map. Columns are auto-detected when instroke_columns is None. Use instroke_downsample_points to set the number of points for downsampled export (default 16).

1 Like

Garmin uses it for indoor rowing, and thus OpenRowingMonitor does as well. We use Cycle_Distance16 which is an unsigned 16 bit integer, allowing a maximum of 655.35 meters. Have seen a lot of weird and badly configured machines, but none hit that limit.

The fit-spec has UInt16 arrays as base type. On OpenRowingMonitor we use them as the HR and HRV data has to be reported like that. Also their Zone reporting depends heavily on it. Not sure if there is a practical length limit.

I know the machine with the most datapoints per drive we’ve seen is the C2 RowErg, with about 100 to 120 datapoints per drive. Perhaps @greis has some insights here as they also interface with the RP3.

Our experience is vastly different for indoor rowing. With ORM we report on a session, spilt (= ORM interval) and lap (= ORM split) level, and there is a bug on parsing that data, as intervals messes with that data. See Question on FIT file parsing: Misaligned Splits / "Summary First" vs "Summary Last" handling

I see the same happens with Garmin data: First intervals too short? Rest of intervals shifted

Good idea!

If you want a (bad) example of how we report data: openrowingmonitor/app/recorders/fitRecorder.js at 0.9.7-(under-construction) ¡ JaapvanEkris/openrowingmonitor ¡ GitHub

Unfortunately, this isn’t too non-developer friendly. We do try to document much of the parameters and timestamp stuff etc as well. AFAIK, this is the maximum of the documented native datafields that can be used.

We are working with an upstream developer to include undocumented Garmin fields as well. But that will be in the next release for us. This includes some summary fields which are interesting.

I havo no idea about other datasources, but isn’t that a little too few? On a RowErg we see around 100 to 120 datapoints. I think RP3 has a similar resolution?