fix: response gating between BLE writes (matches Android) #15

Merged
schwifty merged 1 commit from schwifty/response-gating-fix into main 2026-03-22 03:12:49 +00:00
Collaborator

Root Cause

iOS was firing the next GATT command immediately after the BLE write ACK, without waiting for the beacon's FFE1 notification response. Android explicitly waits up to 1s for each response via responseChannel.receive(1000ms) before sending the next command.

This meant iOS was hammering the beacon's MCU faster than it could process commands, causing supervision timeout disconnects and the "DX Smart command characteristic" error.

What Changed

  • Response gating: After each FFE2 write, now waits for FFE1 notification (up to 1s) before advancing — exactly matching Android's pattern
  • Rejection detection: Checks for 4E 4F 00 rejection pattern from beacon (matches Android)
  • Fallback timeout: If beacon doesn't respond within 1s, advances anyway (some commands don't produce responses)
  • Cleanup: Gate timer properly cancelled on disconnect, cleanup, and state resets

Android Reference

// Android (DXSmartProvisioner.kt) — working pattern:
val writeOk = writeCharacteristic(ffe2, packet)
val resp = withTimeoutOrNull(1000L) { responseChannel.receive() }  // ← THIS
delay(200)

Test Plan

  • Pull branch on Mac with Xcode, build clean
  • Provision a DX-Smart beacon — should complete without disconnects
  • Verify no more "DX Smart command characteristic" errors
  • Check logs show FFE1 responses arriving between writes

🤖 Generated with Claude Code

## Root Cause iOS was firing the next GATT command immediately after the BLE write ACK, without waiting for the beacon's FFE1 notification response. Android explicitly waits up to 1s for each response via `responseChannel.receive(1000ms)` before sending the next command. This meant iOS was hammering the beacon's MCU faster than it could process commands, causing supervision timeout disconnects and the "DX Smart command characteristic" error. ## What Changed - **Response gating**: After each FFE2 write, now waits for FFE1 notification (up to 1s) before advancing — exactly matching Android's pattern - **Rejection detection**: Checks for `4E 4F 00` rejection pattern from beacon (matches Android) - **Fallback timeout**: If beacon doesn't respond within 1s, advances anyway (some commands don't produce responses) - **Cleanup**: Gate timer properly cancelled on disconnect, cleanup, and state resets ## Android Reference ```kotlin // Android (DXSmartProvisioner.kt) — working pattern: val writeOk = writeCharacteristic(ffe2, packet) val resp = withTimeoutOrNull(1000L) { responseChannel.receive() } // ← THIS delay(200) ``` ## Test Plan - [ ] Pull branch on Mac with Xcode, build clean - [ ] Provision a DX-Smart beacon — should complete without disconnects - [ ] Verify no more "DX Smart command characteristic" errors - [ ] Check logs show FFE1 responses arriving between writes 🤖 Generated with [Claude Code](https://claude.com/claude-code)
schwifty added 2 commits 2026-03-22 03:09:12 +00:00
After 2+ disconnects during provisioning, the beacon's BLE stack can
return incomplete characteristic discovery results. Instead of hard-
failing with "DX-Smart command characteristic (FFE2) not found", we
now re-trigger characteristic discovery (up to 2 attempts) and resume
writing from the saved position once FFE2 is found.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: iOS was firing the next GATT command as soon as the BLE write
ACK arrived, without waiting for the beacon's FFE1 notification response.
Android explicitly waits up to 1s for the beacon to respond (via
responseChannel.receive) before sending the next command. This gives the
beacon MCU time to process each command before the next one arrives.

Without this gate, the beacon gets overwhelmed and drops the BLE connection
(supervision timeout), causing the "DX Smart command characteristic" error
John reported after repeated disconnects.

Changes:
- Add awaitingCommandResponse flag + 1s response gate timer
- After each FFE2 write success, wait for FFE1 notification before advancing
- If no response within 1s, advance anyway (some commands don't respond)
- Check for 4E 4F 00 rejection pattern (matches Android)
- Clean up gate timer on disconnect, cleanup, and state resets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
schwifty merged commit 813198599a into main 2026-03-22 03:12:49 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: payfrit/payfrit-beacon-ios#15
No description provided.