Add NordicTrack Elliptical Spacesaver S700 profile - #4796
Merged
Conversation
The S700 works over the SE7i wire protocol for incline/resistance, but its live telemetry speed packet is shifted 2 bytes from the SE7i frame: the 0x5a marker sits at byte[4] (byte[3]==0x00) instead of the SE7i's byte[4]==0x46, so the existing SE7i speed check never matched. Since elliptical watts()/KCal only depend on currentSpeed(), this meant no pace, no watts and no calories for the whole session, even though incline and resistance (parsed from a separate packet type) worked. Gate the fix behind a new nordictrack_elliptical_s700 setting so other NordicTrack elliptical profiles (default, C7.5, SE7i) are unaffected: enabling it reuses the SE7i init/incline/resistance protocol and adds correct S700 speed-packet detection/decoding. Extracted the packet detection/decoding into small static helpers (isS700SpeedPacket, s700SpeedFromPacket, isSe7iResistanceInclinationPacket, se7iInclinationFromPacket, se7iResistanceFromPacket) so the exact production parsing logic is directly unit-testable, and added a regression test that feeds real captured BLE frames from the issue's debug logs through this code. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Byte[2] of the S700 speed packet carries the stride cadence in RPM, identical to the SE7i format. The previous commit incorrectly described it as a "free-running counter" and omitted cadence parsing, leaving Cadence stuck at 0 for the whole session. Evidence from real captured packets in the debug logs: - SE7i log (09:51): byte[2] values increase from 9→45 as user ramps up - S700 post-patch log (Jul 13): byte[2] values decrease from 34→23 as user winds down — both correlate directly with speed, confirming it is cadence, not a counter. Add s700CadenceFromPacket() static helper (byte[2]) and call it in the S700 speed-packet handler alongside the existing speed parse, matching the SE7i cadence-parse pattern exactly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… estimate (#4794) The 00 12 01 04 02 30/31 resistance packet carries machine-measured wattage at bytes 14-15 (little-endian uint16). Confirmed from Jul 14 debug log where user reported the console showing 0->23W: byte[14]=0x17=23 appears in the first packet after exercise starts, matching exactly. Previously elliptical::watts() returned ~200W for typical elliptical speeds via a running VO2 formula (210/pace * weight / 1000 * 75), which is inaccurate for an elliptical and ignores resistance level. Add se7iWattsFromPacket() static helper (bytes 14-15 LE) and use it for SE7i/S700 modes in update_metrics() and the KCal accumulator. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… tile (#4794) update_metrics(bool watt_calc, double watts) only writes m_watt (the value shown in the watt tile) when watt_calc=true. The previous commit always passed false, so m_watt was never updated to the machine-reported value even though m_watts was correctly parsed from the packet. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hine watts (#4794) elliptical::watts() always calls m_watt.setValue() as a side effect, so every incoming BLE packet was resetting m_watt to the VO2-calculated value, undoing what update_metrics(true, m_watts) had just written. Two-part fix: - Set m_watt directly from m_watts in the resistance packet handler, immediately after parsing, so it stays current between update() ticks. - In the characteristicChanged debug line, skip the watts() call (and its side effect) for SE7i/S700 mode; read m_watt.value() instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After the SE7i init sequence completes, the S700 switches to "remote control mode" and its live telemetry packets change from byte[4]=0x5a to byte[4]=0x46. isS700SpeedPacket was only checking for 0x5a, so speed/cadence went to 0 once init completed, which also blocked watt tile updates (update_metrics requires speed > 0). Extend the check to accept both markers. The speed/cadence layout (cadence at byte[2], speed LE at bytes[12-13] / 100) is identical between the two variants. Regression test added from the real log. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #4794.
Analysis of the two debug logs attached to the issue showed:
00 12 01 04 02 30/31 ...packet type), but pace/calories/watts never update. Root cause: the live telemetry speed packet on this unit is the SE7i type0x01frame shifted by 2 bytes — its0x5amarker sits atbyte[4](withbyte[3]==0x00,byte[2]a free-running counter) instead of the SE7i'sbyte[4]==0x46— so the existing SE7i speed-detection branch never matched,Speedstayed 0 forever, and sinceelliptical::watts()/KCalonly depend oncurrentSpeed(), calories/watts/pace never appeared.Changes
nordictrack_elliptical_s700setting (off by default), added as the last setting insettings.qml,qzsettings.h/qzsettings.cpp, andsettings-catalog.json, so this fix is fully opt-in and doesn't touch the default/C7.5/SE7i profiles for other users.isS700SpeedPacket,s700SpeedFromPacket,isSe7iResistanceInclinationPacket,se7iInclinationFromPacket,se7iResistanceFromPacket) onnordictrackelliptical, so the exact production parsing logic is directly unit-testable (matching the existing pattern used byapexbike's static parser helpers).tst/Devices/TestNordictrackEllipticalS700Parser.h/.cpp, a regression test that feeds real captured BLE frames from the issue's debug logs into these helpers and checks the decoded values (and that they don't collide with the legacy SE7i or default-mode packet shapes).Test plan
NordictrackEllipticalS700ParserTest.*tests pass (5/5), using real packets captured from the attached debug logs.nordictrack_elliptical_s700enabled on their Spacesaver S700 unit.🤖 Generated with Claude Code