Skip to content

Auto-reconnect on unexpected BLE disconnect#81

Open
dgkanatsios wants to merge 1 commit into
soundshed:mainfrom
dgkanatsios:spark2-auto-reconnect
Open

Auto-reconnect on unexpected BLE disconnect#81
dgkanatsios wants to merge 1 commit into
soundshed:mainfrom
dgkanatsios:spark2-auto-reconnect

Conversation

@dgkanatsios

Copy link
Copy Markdown

Thanks a ton for this app! Just tried it on my Spark 2 and the connection was dropped after a while, so I crafted this PR (with AI's help) to mitigate the issue. Tried it on Windows during my guitar class now (1 hour) and there were 0 disconnects. I also tried the "turn off/on Spark 2" scenario and it reconnected successfully.

The BLE provider never listened for the Web Bluetooth
`gattserverdisconnected` event, so when the Spark dropped its BLE
link the app would keep `isConnected = true`, the UI would still
report "connected", and every subsequent command silently failed.

This change wires up a disconnect-detection + single-attempt
auto-reconnect path while keeping the existing manual connect/
disconnect flow untouched.

Behavior:
  * BleProvider attaches a `gattserverdisconnected` listener after
    services are wired up. On unexpected drop it tears down local
    state, detaches the notification listener, clears the receive/
    send queues, fails any pending ack waiters, and invokes
    `onDisconnected`.
  * A new `intentionalDisconnect` flag plus listener removal in
    `disconnect()` keeps user-initiated disconnects from triggering
    the auto-reconnect path.
  * `BleProvider.reconnect()` re-uses the cached `BluetoothDevice`
    handle and re-runs service discovery, so the user is not
    re-prompted with the device picker.
  * `SparkDeviceManager` exposes `onConnectionLost` and `reconnect()`
    and now tracks its receive-loop `setInterval` id so reconnect
    cycles don't stack additional 50ms intervals.
  * `DeviceContext` reacts to connection loss by emitting a
    'disconnected' event to the UI and kicking off a single
    `attemptReconnect()` after a 1.5s settle delay. A monotonic
    `reconnectGeneration` token cancels stale attempts when the
    user takes a competing action (manual scan/connect) so the
    UI state is not clobbered by a superseded attempt.
  * `DeviceViewModel` handles the new 'disconnected' /
    'reconnecting' events alongside the existing 'connected' /
    'failed' ones; the status dot now reflects reality.

Correctness fixes uncovered during review:
  * `connect()` previously left `isConnected = true` if service
    discovery failed after the GATT link was up. A later reconnect
    would short-circuit and report success against an unusable
    connection. Now rolled back fully on failure.
  * `beginQueuedReceive()` registered an anonymous
    `characteristicvaluechanged` listener with no removal path,
    leaking across reconnects. The bound listener and its target
    characteristic are now tracked and removed on every disconnect.
  * `write()` set `isSendQueueProcessing = true` without a
    `try/finally`, so a write that threw (e.g. mid-drop) would
    leave the queue permanently "processing" and stall all future
    commands. Now wrapped in try/finally.
  * `SparkDeviceManager.startReceiver()` called `setInterval` with
    no reference, so every reconnect would add another loop on top
    of the existing one. The interval handle is now stored and
    cleared on disconnect/reconnect.

Out of scope (intentional follow-ups):
  * Multi-attempt backoff (currently exactly one auto-attempt).
  * Aborting an in-flight Spark 2 chunked preset upload when the
    underlying BLE link drops mid-upload.
  * Heartbeat-based liveness for stalls that don't fire
    `gattserverdisconnected` (some Windows BLE stacks).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dgkanatsios

Copy link
Copy Markdown
Author

Also tested it with my Spark Go (connect, disconnect, reconnect) and it works OK.

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