What happens
A user with a T-Deck companion reports having to tap Connect 4 or more times before a session starts. Their debug logs show every failed connect as Connection attempt 1/3 followed immediately by Connection failed. Attempts 2 and 3 almost never run.
One representative stretch from a single Android session:
- Three consecutive GATT connect timeouts (
fbp-code: 1 | Timed out after 15s), each abandoned after attempt 1 of 3.
- Two connections that reached the MeshCore handshake and then died there (
writeCharacteristic timeout, then fbp-code: 6 | Device is disconnected), each aborting the whole connection.
- Success on the 7th manual tap.
A separate iOS report shows the same shape on auto-reconnect after an app relaunch: one 15s connect timeout on attempt 1 of 3, then give up.
The user's manual re-taps eventually succeed every time, which is exactly the work the internal retry loop was supposed to do.
Why
connect() in lib/services/bluetooth/mobile_bluetooth.dart only treats two error classes as retryable:
- Android
android-code: 133 (GATT_ERROR)
- iOS bond errors (apple-code 14/15), which are deliberately rethrown without retrying
Everything else is rethrown on the first attempt: the 15s GATT connect timeout, transient GATT errors such as UNKNOWN_GATT_ERROR (28) from setNotifyValue, and service discovery failures. So the advertised 3 attempts only ever apply to error 133.
Separately, failures that happen after the transport connects, during the 9-step protocol handshake (writeCharacteristic timeout, device dropping mid-handshake), abort the connection with no automatic re-run at any level.
Suggested direction
- Treat connect timeouts and transient GATT errors as retryable inside the existing
_maxRetries loop (keeping the iOS bond-error early exit as is).
- Consider one automatic re-run of the connection workflow when the handshake dies within a few seconds of a successful transport connect.
What happens
A user with a T-Deck companion reports having to tap Connect 4 or more times before a session starts. Their debug logs show every failed connect as
Connection attempt 1/3followed immediately byConnection failed. Attempts 2 and 3 almost never run.One representative stretch from a single Android session:
fbp-code: 1 | Timed out after 15s), each abandoned after attempt 1 of 3.writeCharacteristictimeout, thenfbp-code: 6 | Device is disconnected), each aborting the whole connection.A separate iOS report shows the same shape on auto-reconnect after an app relaunch: one 15s connect timeout on attempt 1 of 3, then give up.
The user's manual re-taps eventually succeed every time, which is exactly the work the internal retry loop was supposed to do.
Why
connect()inlib/services/bluetooth/mobile_bluetooth.dartonly treats two error classes as retryable:android-code: 133(GATT_ERROR)Everything else is rethrown on the first attempt: the 15s GATT connect timeout, transient GATT errors such as
UNKNOWN_GATT_ERROR (28)fromsetNotifyValue, and service discovery failures. So the advertised 3 attempts only ever apply to error 133.Separately, failures that happen after the transport connects, during the 9-step protocol handshake (
writeCharacteristictimeout, device dropping mid-handshake), abort the connection with no automatic re-run at any level.Suggested direction
_maxRetriesloop (keeping the iOS bond-error early exit as is).