Skip to content

fix(ble): carry the buzz repeat count into the 5/MG haptic body - #6

Open
c0dingnoobi wants to merge 1 commit into
tanarchytan:rc/10.0.0from
c0dingnoobi:fix/mg-buzz-repeat-count
Open

c0dingnoobi wants to merge 1 commit into
tanarchytan:rc/10.0.0from
c0dingnoobi:fix/mg-buzz-repeat-count

Conversation

@c0dingnoobi

Copy link
Copy Markdown

What this PR does

On a WHOOP 5/MG every BuzzPattern produced a single identical pulse — Single, Double, Triple and Long all felt the same, and the Breathe and live-session cues with them.

WhoopBleClient.send() routed 5/MG haptics through RustCodec.buzzFrame(seq), which takes only a sequence number. The caller's repeat count had nowhere to go, so the maverick body's overallLoop byte always went out as 0 — "no repeats" — which is exactly the one-buzz behaviour. The count was being discarded at the Kotlin/FFI boundary, not by the strap.

This builds the haptic body in WhoopBleClient and sends it through the generic commandFrame with opcode 0x13, so loops reaches the wire. overallLoop counts the repeats that follow the first pulse, so it is written as loops - 1.

No .so rebuild and no uniffi signature change: the generic builder already accepts an arbitrary opcode and payload, and pads the inner body to a 4-byte boundary, which this 12-byte body already satisfies. RustCodec.buzzFrame is left in place as the single-pulse byte reference.

loops = 1 reproduces the previously shipped constant byte for byte, so this only moves byte 11, and only when the caller asked for more than one pulse.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • CI / tooling

How it was tested

On a real WHOOP 5/MG: the notification patterns now differ from one another — Double gives two pulses, Triple three, Long a longer train. Before this change all four were a single identical buzz.

Unit tests: MaverickHapticBodyTest (new, 5 cases, pure Kotlin — no FFI, so it runs without the host libwhoop_ffi) locks the body bytes: loops = 1 reproduces the shipped constant 012f98000000000000000000 exactly, 2 → 1 repeat, 3 → 2, 5 → 4, clamping at 7, and a short payload falling back to a single pulse.

WHOOP 4.0 is untouched — it keeps RUN_HAPTICS_PATTERN (79) with [patternId, loops, 0, 0, 0].

Checklist

  • Swift package tests pass for any package I touched (swift test in Packages/<name>) — n/a, no Swift on this branch
  • Android unit tests pass if I touched android/ (./gradlew testFullDebugUnitTest) — see note below
  • No new build warnings introduced
  • UI changes use only StrandDesign tokens — no hardcoded colors, fonts, or spacing — n/a, no UI change
  • No hardcoded hex frame bytes; protocol facts live in the schema / decoders
  • Follows the conventions in docs/CONTRIBUTING.md
  • I did not commit generated output (Strand.xcodeproj/) or any secrets/keystores

On the unit-test box: I cannot run the full suite here. buildRustHostDll needs a sibling whoop-rs checkout with cargo, which this machine does not have, so every FFI-backed test fails at class init with NoClassDefFoundError: uniffi.whoop_ffi. That is environmental and unrelated to this change — the tests added here are deliberately FFI-free and pass.

Separately: app/build.gradle.kts says the parity test "self-skips" when the host library is absent. It does not — SendFrameParityTest hard-fails all 15 cases. Worth a follow-up, not fixed here.

Related issues

RustCodec.buzzFrame takes only a seq, so overallLoop went out as 0 and every
BuzzPattern gave a single identical pulse on a 5/MG. Build the body here and
send it through the generic command builder instead.

Verified on a WHOOP 5/MG: double, triple and long now differ.
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