Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 65 additions & 6 deletions app/lib/services/devices/transports/ble_transport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class BleTransport extends DeviceTransport {
final StreamController<DeviceTransportState> _connectionStateController;
final Map<String, StreamController<List<int>>> _streamControllers = {};
final Map<String, StreamSubscription> _characteristicSubscriptions = {};
Future<void> _writeQueue = Future.value();

List<BluetoothService> _services = [];
DeviceTransportState _state = DeviceTransportState.disconnected;
Expand Down Expand Up @@ -187,12 +188,48 @@ class BleTransport extends DeviceTransport {
throw Exception('Characteristic not found: $serviceUuid:$characteristicUuid');
}

try {
await characteristic.write(data);
} catch (e) {
debugPrint('BLE Transport: Failed to write characteristic: $e');
rethrow;
}
final task = _writeQueue.then((_) async {
int retries = 0;
while (true) {
if (_state != DeviceTransportState.connected || !_bleDevice.isConnected) {
debugPrint('Skipping write: Device is disconnected');
return;
}

try {
await characteristic
.write(
data,
withoutResponse: characteristic.properties.writeWithoutResponse,
allowLongWrite: true,
)
.timeout(const Duration(seconds: 2));
return;
} catch (e) {
if (_isDisconnectionError(e)) {
debugPrint('Device Disconnected');
return;
}

if (!_bleDevice.isConnected) {
return;
}

final retryable = _isRetryable(e);
if (!retryable || retries >= 3) {
debugPrint('BLE write failed (stop retry) $serviceUuid:$characteristicUuid → $e');
rethrow;
}

final delay = 80 * (1 << retries);
await Future.delayed(Duration(milliseconds: delay));
retries++;
}
}
});

_writeQueue = task.catchError((_) {});
await task;
}

Future<BluetoothCharacteristic?> _getCharacteristic(String serviceUuid, String characteristicUuid) async {
Expand All @@ -209,6 +246,28 @@ class BleTransport extends DeviceTransport {
);
}

bool _isDisconnectionError(Object e) {
final msg = e.toString().toLowerCase();
final is133 = RegExp(r'\b133\b').hasMatch(msg);

return is133 ||
msg.contains("gatt_error") ||
msg.contains("unknown_ble_error") ||
msg.contains("device is disconnected") ||
msg.contains("the device is disconnected");
}

bool _isRetryable(Object e) {
if (e is FlutterBluePlusException) {
final desc = (e.description ?? "").toLowerCase();
if (e.code == 201 || desc.contains("busy")) return true;
return false;
}

final msg = e.toString().toLowerCase();
return msg.contains("busy") || RegExp(r'\b201\b').hasMatch(msg);
}

@override
Future<void> dispose() async {
await _bleConnectionSubscription?.cancel();
Expand Down