A draft Python implementation of the Rowing Data Standard.
This package implements Draft v0.1, which is not ratified. Field IDs, scales and units may still change. Do not treat this library as a stable contract for shipping products.
Implements Draft v0.1 §4 native FIT fields, §5.1 core metrics (IDs 0–9
and 19), and Session RecordingStrategy (ID 10), under
application UUID 89e86158-6d47-5c98-9d46-7d29437f27b9. Producers write
protocol version 1 in DeveloperDataId.application_version. A file that omits
it is still read.
Stroke rate is native cadence256 (scale 256) plus cadence, rounded to the
nearest integer. This library does not write developer field StrokeRate (93)
or native fractional_cadence.
RecordingStrategy includes TimeSampled (3). StrokeState (ID 96) is written
only for that strategy: 0 Unknown, 1 Waiting, 2 Drive, 3 Dwell, 4 Recovery.
Force is AverageDriveForce (6) and PeakDriveForce (7), in newtons. Fields 4
and 5 are not part of this sample.
SlipThreshold (94) and WashThreshold (95) are optional session fields, in
newtons, written when set.
Not implemented (later work, or blocked on draft defects):
- Oarlock summary fields 11–18
- Dual-oarlock 200–211
- In-stroke axis metadata 90–92, curve arrays, curve summaries,
.instroke.json
WorkoutState (ID 9) uses the FIT intensity values: 0 Active, 1 Rest, 2 Warmup,
3 Cooldown, 4 Recovery, 5 Interval, 6 Other. An unknown value is read as Other.
When a lap's intensity disagrees with a record, the lap wins. Every lap
written here sets native intensity. A session with no laps is written as one
active lap. RowingSession.moving_time_s() sums timer time over every lap except
Rest, and validate warns about rowing data inside a rest lap (light rowing is
Recovery).
Heart rate is the native heart_rate field. When no sensor is connected, omit
it rather than write 0. cycle_length16 is the distance the boat travels in
one stroke cycle, not cumulative distance and not handle travel (DriveLength).
Pre-v1.2 files: DriveLength used scale 100 / metres. This reader decodes
with v0.1 units (mm) and records a field_scale warning. It does not
silently convert old files into millimetres.
Developer fields are interpreted only when they use the standard application UUID.
From the repository root:
pip install -e "./python[dev]"
Use Python 3.11 or later.
from pathlib import Path
from rowing_data import STANDARD_VERSION, read_fit, validate, write_fit
assert STANDARD_VERSION == "0.1"
session = read_fit("workout.fit")
write_fit(session, Path("copy.fit"))
issues = validate(session) # includes warnings captured during read_fitcd python
pytest
ruff check src tests
CI runs exactly these two commands on Python 3.11, 3.12 and 3.13
(.github/workflows/python.yml), and only
when something under python/ changes. The interop test against the
rowingdata golden FIT file skips on CI, since that file is not in this
repository.