Repository navigation
Extended-battery probe: clean, copyable output + capture diff for field mapping (#592) - #604
Merged
Merged
Conversation
ryanbr
force-pushed
the
feat/battery-probe-capture-diff-592
branch
from
July 19, 2026 03:52
f2baa39 to
43bce10
Compare
…ld mapping (#592) The 4.0 confirmed opcode 98 with a rich 29-byte payload, but the result was a dense run-on and one frame can't map its fields responsibly. This reworks the probe output: - A pure, unit-tested `formatExtendedBatteryProbe` renders a clean report: a verdict header, the full raw hex on one copyable line, an offset-labelled payload hex grid (8 bytes/row), the decoded pack voltage, and a per-byte diff vs the previous capture. - Field-mapping scaffold: persists each payload (SharedPreferences) and diffs the next one against it. A byte that MOVES between captures at different battery %/temperature is that field — SoC/capacity tracks charge, temperature drifts with wear, cycle count only ever climbs. - ExtendedBatteryProbeFormatTest pins it against the REAL 4.0 capture (98 accepted, mV=3970 → 3.97 V), the diff, a bare stub, and a 5/MG UNSUPPORTED verdict. Read-only, additive; no new opcode, no automatic sends. Full module compiles; testFullDebugUnitTest green; i18n audit --ci exit 0. Swift twin to follow.
ryanbr
force-pushed
the
feat/battery-probe-capture-diff-592
branch
from
July 19, 2026 03:55
43bce10 to
9627555
Compare
ryanbr
added a commit
that referenced
this pull request
Jul 19, 2026
* iOS/macOS twin of the #592 extended-battery probe + voltage (#592) Swift port of the Android probe tooling (#599/#601/#603/#604): the read-only GET_EXTENDED_BATTERY_INFO(98) probe, its copyable result dialog, the strap voltage on the Devices card, and the field-mapping capture diff. - WhoopProtocol: ExtendedBatteryProbe.format(...) — the pure formatter (verdict, full raw hex, offset-labelled payload grid, decoded voltage, per-byte capture diff). Placed in the package (not app-side like Kotlin) so swift test covers it on Linux/CI; behaviour is byte-identical to the Kotlin formatter. 5 tests pin it against the real 4.0 capture, the diff, a bare stub, and a 5/MG UNSUPPORTED verdict. - BLEManager: probeExtendedBatteryInfo() (+ 8s no-reply timeout, guard-then-set is race-free since BLE callbacks + the timer both run on main), clearExtended..., the frame[6] (4.0) and frame[10] (5/MG) COMMAND_RESPONSE dumps, the 5/MG allowlist entry for 98, prev-payload persisted in UserDefaults (noop.592.prevPayload). - LiveState: batteryMv + extendedBatteryProbe (+ disconnect resets). - FrameRouter: consume battery_mV → state.batteryMv (event + command response). - AppModel: probeExtendedBatteryInfo()/clearExtendedBatteryProbe() forwards. - DevicesView: the probe menu item, a read-only confirm dialog, a copyable result sheet (selectable text + PlatformPasteboard Copy), and the voltage readout beside the battery percent. Design tokens only. Verified on Linux: WhoopProtocol builds + all 294 tests pass (incl. the 5 new); swiftc -parse clean on every app-target file. App-target compile (BLEManager / DevicesView / etc.) needs an app-build run — flagged. * iOS #592 probe: localize the Devices probe/voltage strings (de/es/fr) Parity fix for the Android side (#603/#601 extracted these to strings.xml). The 8 new DevicesView UI strings — the probe menu item, confirm dialog title + message, Send/waiting/result-title, and the voltage readout + a11y label — were plain literals (English-only, and a i18n-gate failure). Extract them to Localizable.xcstrings with de/es/fr; the voltage uses SwiftUI specifier interpolation so the number localizes too. i18n audit --ci exit 0.
DX23876
pushed a commit
to DX23876/noop
that referenced
this pull request
Jul 22, 2026
…ld mapping (ryanbr#592) (ryanbr#604) The 4.0 confirmed opcode 98 with a rich 29-byte payload, but the result was a dense run-on and one frame can't map its fields responsibly. This reworks the probe output: - A pure, unit-tested `formatExtendedBatteryProbe` renders a clean report: a verdict header, the full raw hex on one copyable line, an offset-labelled payload hex grid (8 bytes/row), the decoded pack voltage, and a per-byte diff vs the previous capture. - Field-mapping scaffold: persists each payload (SharedPreferences) and diffs the next one against it. A byte that MOVES between captures at different battery %/temperature is that field — SoC/capacity tracks charge, temperature drifts with wear, cycle count only ever climbs. - ExtendedBatteryProbeFormatTest pins it against the REAL 4.0 capture (98 accepted, mV=3970 → 3.97 V), the diff, a bare stub, and a 5/MG UNSUPPORTED verdict. Read-only, additive; no new opcode, no automatic sends. Full module compiles; testFullDebugUnitTest green; i18n audit --ci exit 0. Swift twin to follow.
DX23876
pushed a commit
to DX23876/noop
that referenced
this pull request
Jul 22, 2026
…anbr#605) * iOS/macOS twin of the ryanbr#592 extended-battery probe + voltage (ryanbr#592) Swift port of the Android probe tooling (ryanbr#599/ryanbr#601/ryanbr#603/ryanbr#604): the read-only GET_EXTENDED_BATTERY_INFO(98) probe, its copyable result dialog, the strap voltage on the Devices card, and the field-mapping capture diff. - WhoopProtocol: ExtendedBatteryProbe.format(...) — the pure formatter (verdict, full raw hex, offset-labelled payload grid, decoded voltage, per-byte capture diff). Placed in the package (not app-side like Kotlin) so swift test covers it on Linux/CI; behaviour is byte-identical to the Kotlin formatter. 5 tests pin it against the real 4.0 capture, the diff, a bare stub, and a 5/MG UNSUPPORTED verdict. - BLEManager: probeExtendedBatteryInfo() (+ 8s no-reply timeout, guard-then-set is race-free since BLE callbacks + the timer both run on main), clearExtended..., the frame[6] (4.0) and frame[10] (5/MG) COMMAND_RESPONSE dumps, the 5/MG allowlist entry for 98, prev-payload persisted in UserDefaults (noop.592.prevPayload). - LiveState: batteryMv + extendedBatteryProbe (+ disconnect resets). - FrameRouter: consume battery_mV → state.batteryMv (event + command response). - AppModel: probeExtendedBatteryInfo()/clearExtendedBatteryProbe() forwards. - DevicesView: the probe menu item, a read-only confirm dialog, a copyable result sheet (selectable text + PlatformPasteboard Copy), and the voltage readout beside the battery percent. Design tokens only. Verified on Linux: WhoopProtocol builds + all 294 tests pass (incl. the 5 new); swiftc -parse clean on every app-target file. App-target compile (BLEManager / DevicesView / etc.) needs an app-build run — flagged. * iOS ryanbr#592 probe: localize the Devices probe/voltage strings (de/es/fr) Parity fix for the Android side (ryanbr#603/ryanbr#601 extracted these to strings.xml). The 8 new DevicesView UI strings — the probe menu item, confirm dialog title + message, Send/waiting/result-title, and the voltage readout + a11y label — were plain literals (English-only, and a i18n-gate failure). Extract them to Localizable.xcstrings with de/es/fr; the voltage uses SwiftUI specifier interpolation so the number localizes too. i18n audit --ci exit 0.
simoncad7
pushed a commit
to simoncad7/noop
that referenced
this pull request
Aug 17, 2026
…ld mapping (ryanbr#592) (ryanbr#604) The 4.0 confirmed opcode 98 with a rich 29-byte payload, but the result was a dense run-on and one frame can't map its fields responsibly. This reworks the probe output: - A pure, unit-tested `formatExtendedBatteryProbe` renders a clean report: a verdict header, the full raw hex on one copyable line, an offset-labelled payload hex grid (8 bytes/row), the decoded pack voltage, and a per-byte diff vs the previous capture. - Field-mapping scaffold: persists each payload (SharedPreferences) and diffs the next one against it. A byte that MOVES between captures at different battery %/temperature is that field — SoC/capacity tracks charge, temperature drifts with wear, cycle count only ever climbs. - ExtendedBatteryProbeFormatTest pins it against the REAL 4.0 capture (98 accepted, mV=3970 → 3.97 V), the diff, a bare stub, and a 5/MG UNSUPPORTED verdict. Read-only, additive; no new opcode, no automatic sends. Full module compiles; testFullDebugUnitTest green; i18n audit --ci exit 0. Swift twin to follow.
simoncad7
pushed a commit
to simoncad7/noop
that referenced
this pull request
Aug 17, 2026
…anbr#605) * iOS/macOS twin of the ryanbr#592 extended-battery probe + voltage (ryanbr#592) Swift port of the Android probe tooling (ryanbr#599/ryanbr#601/ryanbr#603/ryanbr#604): the read-only GET_EXTENDED_BATTERY_INFO(98) probe, its copyable result dialog, the strap voltage on the Devices card, and the field-mapping capture diff. - WhoopProtocol: ExtendedBatteryProbe.format(...) — the pure formatter (verdict, full raw hex, offset-labelled payload grid, decoded voltage, per-byte capture diff). Placed in the package (not app-side like Kotlin) so swift test covers it on Linux/CI; behaviour is byte-identical to the Kotlin formatter. 5 tests pin it against the real 4.0 capture, the diff, a bare stub, and a 5/MG UNSUPPORTED verdict. - BLEManager: probeExtendedBatteryInfo() (+ 8s no-reply timeout, guard-then-set is race-free since BLE callbacks + the timer both run on main), clearExtended..., the frame[6] (4.0) and frame[10] (5/MG) COMMAND_RESPONSE dumps, the 5/MG allowlist entry for 98, prev-payload persisted in UserDefaults (noop.592.prevPayload). - LiveState: batteryMv + extendedBatteryProbe (+ disconnect resets). - FrameRouter: consume battery_mV → state.batteryMv (event + command response). - AppModel: probeExtendedBatteryInfo()/clearExtendedBatteryProbe() forwards. - DevicesView: the probe menu item, a read-only confirm dialog, a copyable result sheet (selectable text + PlatformPasteboard Copy), and the voltage readout beside the battery percent. Design tokens only. Verified on Linux: WhoopProtocol builds + all 294 tests pass (incl. the 5 new); swiftc -parse clean on every app-target file. App-target compile (BLEManager / DevicesView / etc.) needs an app-build run — flagged. * iOS ryanbr#592 probe: localize the Devices probe/voltage strings (de/es/fr) Parity fix for the Android side (ryanbr#603/ryanbr#601 extracted these to strings.xml). The 8 new DevicesView UI strings — the probe menu item, confirm dialog title + message, Send/waiting/result-title, and the voltage readout + a11y label — were plain literals (English-only, and a i18n-gate failure). Extract them to Localizable.xcstrings with de/es/fr; the voltage uses SwiftUI specifier interpolation so the number localizes too. i18n audit --ci exit 0.
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.
Reworks the #592 probe output after the capture confirmed opcode 98 returns a rich 29-byte payload on the 4.0. Two things: make the result readable/copyable, and make multiple captures do the field-mapping (one frame can't, responsibly).
Cleaner output
A pure, unit-tested
formatExtendedBatteryProberenders the result as a clean report instead of a dense run-on. Your capture now reads:Full raw hex stays on one line (copies cleanly); the payload is an offset-labelled hex grid; the voltage is decoded inline.
Field-mapping scaffold
Persists each payload (SharedPreferences, so captures hours/days apart still diff) and, on the next probe, shows a per-byte delta. The layout can't be read off a single frame — the fields are identified by what they track: capture at 60% and 40% → the byte(s) that moved are SoC/capacity; cold vs worn → the byte that drifted is temperature; a byte that only ever climbs is cycle count.
Tests
ExtendedBatteryProbeFormatTestpins it against the real 4.0 capture (98 accepted,mV=3970 → 3.97 V, hex grid, first-capture), the diff (a moved byte is flagged with its offset), a bare stub, and a 5/MGUNSUPPORTED(3)verdict — 4 cases, all JVM-pure.Verification
testFullDebugUnitTestgreen;i18n_audit.py --ci mainexit 0; byte-scan confirms no stray control chars.Companion to #603 (surfaces the confirmed voltage on the Devices card now).