Skip to content

Devices: show the #592 battery-probe result in a copyable dialog - #601

Merged
ryanbr merged 1 commit into
mainfrom
feat/battery-probe-result-dialog-592
Jul 19, 2026
Merged

ryanbr merged 1 commit into
mainfrom
feat/battery-probe-result-dialog-592

Conversation

@ryanbr

@ryanbr ryanbr commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Follow-up to #599. The probe currently writes its result only to the strap log, so reading or sharing a capture means exporting the whole log. This surfaces it in place — a result dialog you can read and copy straight into the issue.

What it adds

  • WhoopBleClient — publishes the probe result (raw hex + payload triage + the 5/MG result code) to a new extendedBatteryProbe: StateFlow<String?> alongside the existing log lines. A " waiting" sentinel is set when the probe is sent so the dialog can show an in-flight state until the reply lands; clearExtendedBatteryProbe() resets it.
  • AppViewModel — exposes the flow + clear.
  • DevicesScreen — when a result arrives, a read-only dialog shows the text (monospace, selectable, scrollable to 360dp) with a Copy button (LocalClipboardManager) and Close. So a capture pastes straight into GET_EXTENDED_BATTERY_INFO / ENTER-EXIT_HIGH_FREQ_SYNC: opcode mismatch between two independent RE sources (87/85/86 vs 98/96/97) #592 with no full-log export.
  • strings.xml (+de/es/fr) — dialog title, the waiting line, Copy, Close.

Notes

  • Log lines unchanged — the strap-log bundle still carries the capture, so nothing regresses for the existing report flow; this is purely additive UX.
  • Read-only, no new opcode — same safety envelope as Add a read-only extended-battery opcode probe to settle #592 (Android) #599; this only displays what the probe already produced.
  • Motivation: in testing, "Send probe" gave no visible result (by design — log-only), which made the capture hard to retrieve. This closes that gap.

Verification

  • Full Android module compiles; testFullDebugUnitTest green (StateFlow + UI only, no analytics logic touched); Tools/i18n_audit.py --ci main exit 0 (4 new keys × de/es/fr, XML validated).
  • Delivery/UI aren't CI-testable; the dialog was built against the existing probe/reboot-probe dialog patterns.
  • Swift twin to follow together with the probe twin.

@ryanbr
ryanbr force-pushed the feat/battery-probe-result-dialog-592 branch from fdd7744 to 3a34fcd Compare July 19, 2026 03:20
Follow-up to #599. The probe wrote its result only to the strap log, so reading
or sharing a capture meant a full log export. Surface it directly instead.

- WhoopBleClient: publish the probe result (raw hex + payload triage + 5/MG
  result code) to a new `extendedBatteryProbe` StateFlow as well as the log; a
  "__waiting__" sentinel is set on send. If NO response arrives within
  EXTENDED_BATTERY_PROBE_TIMEOUT_MS (8s), the silence is surfaced + logged as a
  result in its own right — no-reply is evidence against opcode 98, and a stuck
  "waiting" dialog is avoided. clearExtendedBatteryProbe() resets it.
- AppViewModel: expose the flow + clear.
- DevicesScreen: when a result (or the waiting/no-reply state) arrives, a
  read-only dialog shows the text (monospace, selectable, scrollable) with a
  Copy button (LocalClipboardManager) so the capture pastes straight into #592
  without exporting the whole log.
- strings.xml (+de/es/fr): dialog title, waiting line, Copy, Close.

The log lines are unchanged, so the strap-log bundle still carries the capture.
Verified: full Android module compiles; testFullDebugUnitTest green; i18n audit
--ci exit 0. Swift twin to follow with the probe twin.
@ryanbr
ryanbr force-pushed the feat/battery-probe-result-dialog-592 branch from 3a34fcd to f7d2a94 Compare July 19, 2026 03:22
@ryanbr
ryanbr merged commit 89bb417 into main Jul 19, 2026
1 check passed
@ryanbr
ryanbr deleted the feat/battery-probe-result-dialog-592 branch July 19, 2026 03:23
ryanbr added a commit that referenced this pull request Jul 19, 2026
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.
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
ryanbr#601)

Follow-up to ryanbr#599. The probe wrote its result only to the strap log, so reading
or sharing a capture meant a full log export. Surface it directly instead.

- WhoopBleClient: publish the probe result (raw hex + payload triage + 5/MG
  result code) to a new `extendedBatteryProbe` StateFlow as well as the log; a
  "__waiting__" sentinel is set on send. If NO response arrives within
  EXTENDED_BATTERY_PROBE_TIMEOUT_MS (8s), the silence is surfaced + logged as a
  result in its own right — no-reply is evidence against opcode 98, and a stuck
  "waiting" dialog is avoided. clearExtendedBatteryProbe() resets it.
- AppViewModel: expose the flow + clear.
- DevicesScreen: when a result (or the waiting/no-reply state) arrives, a
  read-only dialog shows the text (monospace, selectable, scrollable) with a
  Copy button (LocalClipboardManager) so the capture pastes straight into ryanbr#592
  without exporting the whole log.
- strings.xml (+de/es/fr): dialog title, waiting line, Copy, Close.

The log lines are unchanged, so the strap-log bundle still carries the capture.
Verified: full Android module compiles; testFullDebugUnitTest green; i18n audit
--ci exit 0. Swift twin to follow with the probe twin.
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
ryanbr#601)

Follow-up to ryanbr#599. The probe wrote its result only to the strap log, so reading
or sharing a capture meant a full log export. Surface it directly instead.

- WhoopBleClient: publish the probe result (raw hex + payload triage + 5/MG
  result code) to a new `extendedBatteryProbe` StateFlow as well as the log; a
  "__waiting__" sentinel is set on send. If NO response arrives within
  EXTENDED_BATTERY_PROBE_TIMEOUT_MS (8s), the silence is surfaced + logged as a
  result in its own right — no-reply is evidence against opcode 98, and a stuck
  "waiting" dialog is avoided. clearExtendedBatteryProbe() resets it.
- AppViewModel: expose the flow + clear.
- DevicesScreen: when a result (or the waiting/no-reply state) arrives, a
  read-only dialog shows the text (monospace, selectable, scrollable) with a
  Copy button (LocalClipboardManager) so the capture pastes straight into ryanbr#592
  without exporting the whole log.
- strings.xml (+de/es/fr): dialog title, waiting line, Copy, Close.

The log lines are unchanged, so the strap-log bundle still carries the capture.
Verified: full Android module compiles; testFullDebugUnitTest green; i18n audit
--ci exit 0. Swift twin to follow with the probe twin.
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.
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.

1 participant