From 8b589711d4fd360f6d585cafc189df773b46d597 Mon Sep 17 00:00:00 2001 From: ryanbr Date: Mon, 27 Jul 2026 21:58:12 -0700 Subject: [PATCH] Stop a working 4.0 battery read logging as a failure (#900) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WhoopBleClient logs every non-SUCCESS command response, which is how the MG haptics rejection (#48) would surface in-app. But every WHOOP 4.0 GET_BATTERY_LEVEL reply on record carries a zeroed [seq][result] prefix, so a battery read that returned a perfectly good percentage logs as: Command response: GET_BATTERY_LEVEL(26) → FAILURE(0) next to a battery gauge showing 42%. A log line asserting a failure that did not happen is exactly the artefact #900 exists to warn about — that issue began when a FAILURE(0) was read as evidence about firmware, and this line manufactures the same misreading on every battery read a 4.0 owner makes. The line still prints. Suppressing it would hide the anomaly, and the anomaly is the thing #900 wants a capture of. It is annotated instead, when the same frame also decoded a value: Command response: GET_BATTERY_LEVEL(26) → FAILURE(0) (the reply still carried a value: battery 42.5% — see #900, the 4.0 result byte is not established) A genuine failure that carried no value — the extended-battery case from #791, the UNSUPPORTED rejections — is untouched, because the annotation is gated on a decoded value being present. This does not decide what the 4.0 result byte means; #900 still wants one real capture of known provenance. It stops the log asserting an answer in the meantime, the same discipline as #913, #914 and #918. Android-only: the Swift side has no command-response result consumer, so there is no parity twin to change. No test — the string is inside a class that needs the Android framework, and the annotation is a log line rather than behaviour; the rendered output was checked against both real 4.0 battery fixtures instead. --- .../main/java/com/noop/ble/WhoopBleClient.kt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/com/noop/ble/WhoopBleClient.kt b/android/app/src/main/java/com/noop/ble/WhoopBleClient.kt index cca5742ce4..3823ccb39e 100644 --- a/android/app/src/main/java/com/noop/ble/WhoopBleClient.kt +++ b/android/app/src/main/java/com/noop/ble/WhoopBleClient.kt @@ -5073,8 +5073,23 @@ class WhoopBleClient( } // Surface non-success command results in the strap log — a result=UNSUPPORTED line // here is how the MG haptics rejection (#48) would have shown itself in-app. + // + // A reply that DELIVERED ITS VALUE is annotated rather than reported as a bare failure. + // The 4.0 GET_BATTERY_LEVEL replies on record carry a zeroed [seq][result] prefix, so a + // battery read that returned a perfectly good percentage logs as + // "FAILURE(0)" — and a log line saying a battery read failed, next to a battery gauge + // showing 42%, is the kind of artefact that gets quoted in an issue as evidence of a + // fault that is not there. That is how #900 started. The line still prints, because + // hiding it would hide the anomaly itself; it just no longer reads as a failure. if (result != null && !result.startsWith("SUCCESS")) { - log("Command response: ${respCmd ?: "?"} → $result") + val decodedValue = doubleValue(parsed.parsed["battery_pct"]) + val note = if (decodedValue != null) { + " (the reply still carried a value: battery ${"%.1f".format(decodedValue)}%" + + " — the result byte on this reply is not established, see #900)" + } else { + "" + } + log("Command response: ${respCmd ?: "?"} → $result$note") } // Arm-readback diagnostic (#401 close-out): armStrapAlarm follows every WHOOP 4.0 arm // with GET_ALARM_TIME (67) so the log proves what the STRAP believes is armed, not just