Skip to content

Ride summary: descent and total kJ in the ride footer (OBRF v5) - #2148

Merged
timohueser merged 3 commits into
developfrom
claude/effort-kj-summary-2138
Sep 23, 2026
Merged

timohueser merged 3 commits into
developfrom
claude/effort-kj-summary-2138

Conversation

@timohueser

@timohueser timohueser commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Closes #2138. This is part 2 of 2: the ride summary data (section 5). #2139 added the live effort display and the recorder's energy total. The device ride detail is redesigned in a follow-up PR (three manual pages). That PR reads these fields. This PR adds the data and the iOS row only.

What changed

  • Ride footer v5. The footer grows from 144 to 150 bytes. v4 is gone, with no migration.

    Offset Size Field
    20 2 average speed, cm/s
    22 2 climb, m
    24 2 descent, m (new)
    26 4 point count
    30 / 31 / 32 1 each average HR, maximum HR, average cadence
    33 1 reserved, zero
    34 / 36 2 each average power, maximum power
    38 4 energy, kJ; 0xFFFF_FFFF = no power data (new)
    42 48 name
    90 12 trip key, day index, day count, bike type, trip-name length
    102 48 trip name
  • Descent. The recorder already keeps a dead band for the climb. That dead band also counts the descent, and the ride-resume data already carries it. The footer takes climb.descent(), with the same smoothing and hysteresis as the climb.

  • Energy. The footer takes RecorderMachine::kj(). The energy crosses a reset. A ride without power data writes the absent value, so 0 kJ is a real value.

  • Spec and vectors. specs/obc-ble-interface-spec.md §7.2 has the new table and notes for climb and descent and for energy. ride-v4.bin is now ride-v5.bin (95 m descent, 756 kJ). The manifest and the vector README are updated.

  • Decoders. obc-formats, obc-route (RideStats, RideInfo), host and link tests, obc-web-convert, the web builder objects.ts, and iOS RideObjectCodec all use v5.

  • iOS. RideSummary has descentMeters and energyKJ. Both are kept in summary.json and filled in on ride sync. A ride edit counts the descent again with the 3 m dead band. The energy after an edit is an estimate from the stored points: watts × min(dt, 2 s). The ride detail shows an "Energy · 756 kJ" row after max power, only with power data. iOS shows no descent row, because the tracked ride detail shows its climb only in the one stats line.

  • Device. The ride detail is unchanged. RideSummary carries energy_kj for the follow-up. The descent is not in the resident ride list. The follow-up can read it from the opened ride, as the route detail reads a route's descent.

  • Web builder test. A corruption test flipped the last byte of a stream record. With a 250-byte payload, that byte is alignment padding, so the test now flips a byte in the middle of the payload.

RAM and the trip key

  • On the board, a RideEntry is 112 B (RideSummary 104 B + 8 B id). A new const assert pins this. Without the change below, energy_kj would make it 120 B (+256 B for the 32 resident entries).
  • TripRef now holds its key as NonZeroU64, so Option<TripRef> has no tag. A const assert pins this too. TripRef::new is the only constructor. It returned None for key 0 before this change too.
  • Every producer and reader already keeps key 0 as "no trip":
    • device trip reader: rejects key 0 (test rejects_zero_key)
    • footer and ride-resume decoders: read key 0 as no trip, and reject day bytes without a key
    • iOS Trip.newKey(): returns 1...UInt64.max
    • iOS TripObjectCodec: rejects key 0
    • web builder manage.ts: falls back to || 1n
    • new test: TripRef::new(0, 0, 1) == None
  • Board resource guard on this head: 315,032 B resident. The residual main stack is 44,392 B, 13,052 B above the deep-ride high-water (floor 8,704 B). The descent adds 8 B of resident RAM in the recorder's ride state. resource_baseline.json did not change.

Checks

  • obc test -p passed: obc-formats (55), obc-route (252), obc-vectors (12), obc-link (99), obc-host-core (130), obc-web-convert (14), obc-app (1154), obc-sim (74).
  • cargo clippy -D warnings on the touched crates and on the board crate, and cargo fmt --all --check, are clean.
  • obc shot --check: all 370 frames match develop. No digest changed.
  • OBCKit swift test passed (450 tests, including a new withStats test).
  • These pass on the head before the last rebase, which changed firmware files only: obc check frontend (1003 vitest tests), firmware/tools/tests (101), obc suites check, obc prose --check and python3 docs/build_docs.py --check-links.
  • Not run here: web.builder-browser (Playwright is not installed on this machine), OBCRouting swift test (it needs obc companion-core) and ios.xcuitest. CI covers them.

Out of scope: the device ride-detail display (follow-up PR), GPX and FIT export of energy, calories, and live in-ride fields.

Docs

The public docs changed. docs/content/software/formats.md now says the footer is 150 bytes. That change is its own docs: commit.

Requirements: none

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: cefa84fc-ba1b-4068-b0f5-6f0ee652751e


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@timohueser
timohueser force-pushed the claude/effort-kj-summary-2138 branch from 0912009 to 5aa4977 Compare September 23, 2026 18:44
@timohueser timohueser changed the title Ride summary: total kJ in the ride footer (OBRF v5) Ride summary: descent and total kJ in the ride footer (OBRF v5) Sep 23, 2026
timohueser and others added 3 commits September 23, 2026 21:26
The ride footer grows from 144 to 150 bytes and goes to version 5. v4 is gone.

- Bytes 24..26 hold the ride's descent in metres, next to the climb. The
  recorder takes it from the same dead band as the climb.
- Bytes 38..42 hold the ride's energy from power in whole kJ, next to the
  power summary. 0xFFFF_FFFF means the ride has no power data, so 0 kJ stays
  a real value.

The device ride detail does not change: its redesign reads these fields in a
follow-up. RideSummary carries the energy for that redesign. The descent stays
out of the resident ride list; the detail can read it from the opened ride, as
the route detail reads a route's descent. TripRef keeps its key as NonZeroU64,
so Option<TripRef> loses its tag and a board RideEntry stays at 112 B, now
pinned. TripRef::new already refused key 0, so no behaviour changes.

The phone decodes both fields and keeps them in summary.json. A ride edit
recomputes the descent and estimates the energy from the stored points, with
each interval capped at 2 s as on the device. The ride detail shows an Energy
row. The web builder decodes v5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@timohueser
timohueser force-pushed the claude/effort-kj-summary-2138 branch from 2a28849 to 829db74 Compare September 23, 2026 19:27
@timohueser
timohueser merged commit 39eefea into develop Sep 23, 2026
31 checks passed
@timohueser
timohueser deleted the claude/effort-kj-summary-2138 branch September 23, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Effort display: zone gauge on the map, tinted HR/power tiles, kJ and 5-minute graphs

1 participant