mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
180 lines
6.3 KiB
Dart
180 lines
6.3 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:dartx/dartx.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:swift_control/utils/requirements/android.dart';
|
|
import 'package:universal_ble/universal_ble.dart';
|
|
|
|
import '../bluetooth/ble.dart';
|
|
import 'devices/base_device.dart';
|
|
import 'messages/notification.dart';
|
|
|
|
class Connection {
|
|
final devices = <BaseDevice>[];
|
|
var androidNotificationsSetup = false;
|
|
|
|
final _connectionQueue = <BaseDevice>[];
|
|
var _handlingConnectionQueue = false;
|
|
|
|
final Map<BaseDevice, StreamSubscription<BaseNotification>> _streamSubscriptions = {};
|
|
final StreamController<BaseNotification> _actionStreams = StreamController<BaseNotification>.broadcast();
|
|
Stream<BaseNotification> get actionStream => _actionStreams.stream;
|
|
|
|
final Map<BaseDevice, StreamSubscription<BleConnectionUpdate>> _connectionSubscriptions = {};
|
|
final StreamController<BaseDevice> _connectionStreams = StreamController<BaseDevice>.broadcast();
|
|
Stream<BaseDevice> get connectionStream => _connectionStreams.stream;
|
|
|
|
final _lastScanResult = <BleDevice>[];
|
|
final ValueNotifier<bool> hasDevices = ValueNotifier(false);
|
|
final ValueNotifier<bool> isScanning = ValueNotifier(false);
|
|
|
|
void initialize() {
|
|
UniversalBle.onScanResult = (result) {
|
|
if (_lastScanResult.none((e) => e.deviceId == result.deviceId)) {
|
|
_lastScanResult.add(result);
|
|
final scanResult = BaseDevice.fromScanResult(result);
|
|
_actionStreams.add(
|
|
LogNotification('Found new device: ${result.name ?? scanResult?.runtimeType ?? result.deviceId}'),
|
|
);
|
|
if (scanResult != null) {
|
|
_addDevices([scanResult]);
|
|
}
|
|
}
|
|
};
|
|
|
|
UniversalBle.onValueChange = (deviceId, characteristicUuid, value) {
|
|
final device = devices.firstOrNullWhere((e) => e.device.deviceId == deviceId);
|
|
if (device == null) {
|
|
_actionStreams.add(LogNotification('Device not found: $deviceId'));
|
|
return;
|
|
} else {
|
|
device.processCharacteristic(characteristicUuid, value);
|
|
}
|
|
};
|
|
}
|
|
|
|
Future<void> performScanning() async {
|
|
isScanning.value = true;
|
|
_actionStreams.add(LogNotification('Scanning for devices...'));
|
|
|
|
// does not work on web, may not work on Windows
|
|
if (!kIsWeb && !Platform.isWindows) {
|
|
UniversalBle.getSystemDevices(
|
|
withServices: [BleUuid.ZWIFT_CUSTOM_SERVICE_UUID, BleUuid.ZWIFT_RIDE_CUSTOM_SERVICE_UUID],
|
|
).then((devices) async {
|
|
final baseDevices = devices.mapNotNull(BaseDevice.fromScanResult).toList();
|
|
if (baseDevices.isNotEmpty) {
|
|
_addDevices(baseDevices);
|
|
}
|
|
});
|
|
}
|
|
|
|
await UniversalBle.startScan(
|
|
scanFilter: ScanFilter(withServices: [BleUuid.ZWIFT_CUSTOM_SERVICE_UUID, BleUuid.ZWIFT_RIDE_CUSTOM_SERVICE_UUID]),
|
|
platformConfig: PlatformConfig(web: WebOptions(optionalServices: [BleUuid.ZWIFT_CUSTOM_SERVICE_UUID])),
|
|
);
|
|
Future.delayed(Duration(seconds: 30)).then((_) {
|
|
if (isScanning.value) {
|
|
UniversalBle.stopScan();
|
|
isScanning.value = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
void _addDevices(List<BaseDevice> dev) {
|
|
final newDevices = dev.where((device) => !devices.contains(device)).toList();
|
|
devices.addAll(newDevices);
|
|
|
|
_connectionQueue.addAll(newDevices);
|
|
_handleConnectionQueue();
|
|
|
|
hasDevices.value = devices.isNotEmpty;
|
|
if (devices.isNotEmpty && !androidNotificationsSetup && !kIsWeb && Platform.isAndroid) {
|
|
androidNotificationsSetup = true;
|
|
NotificationRequirement.setup().catchError((e) {
|
|
_actionStreams.add(LogNotification(e.toString()));
|
|
});
|
|
}
|
|
}
|
|
|
|
void _handleConnectionQueue() {
|
|
// windows apparently has issues when connecting to multiple devices at once, so don't
|
|
if (_connectionQueue.isNotEmpty && !_handlingConnectionQueue) {
|
|
_handlingConnectionQueue = true;
|
|
final device = _connectionQueue.removeAt(0);
|
|
_actionStreams.add(LogNotification('Connecting to: ${device.device.name ?? device.runtimeType}'));
|
|
_connect(device)
|
|
.then((_) {
|
|
_handlingConnectionQueue = false;
|
|
_actionStreams.add(LogNotification('Connection finished: ${device.device.name ?? device.runtimeType}'));
|
|
if (_connectionQueue.isNotEmpty) {
|
|
_handleConnectionQueue();
|
|
}
|
|
})
|
|
.catchError((e) {
|
|
_handlingConnectionQueue = false;
|
|
_actionStreams.add(LogNotification('Connection failed: ${device.device.name ?? device.runtimeType} - $e'));
|
|
if (_connectionQueue.isNotEmpty) {
|
|
_handleConnectionQueue();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _connect(BaseDevice bleDevice) async {
|
|
try {
|
|
final actionSubscription = bleDevice.actionStream.listen((data) {
|
|
_actionStreams.add(data);
|
|
});
|
|
final connectionStateSubscription = UniversalBle.connectionStream(bleDevice.device.deviceId).listen((state) {
|
|
bleDevice.isConnected = state.isConnected;
|
|
_connectionStreams.add(bleDevice);
|
|
if (!bleDevice.isConnected) {
|
|
devices.remove(bleDevice);
|
|
_streamSubscriptions[bleDevice]?.cancel();
|
|
_streamSubscriptions.remove(bleDevice);
|
|
_connectionSubscriptions[bleDevice]?.cancel();
|
|
_connectionSubscriptions.remove(bleDevice);
|
|
_lastScanResult.clear();
|
|
// try reconnect
|
|
if (!isScanning.value) {
|
|
performScanning();
|
|
}
|
|
}
|
|
});
|
|
_connectionSubscriptions[bleDevice] = connectionStateSubscription;
|
|
|
|
await bleDevice.connect();
|
|
|
|
_streamSubscriptions[bleDevice] = actionSubscription;
|
|
} catch (e, backtrace) {
|
|
_actionStreams.add(LogNotification("$e\n$backtrace"));
|
|
if (kDebugMode) {
|
|
print(e);
|
|
print("backtrace: $backtrace");
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
void reset() {
|
|
UniversalBle.stopScan();
|
|
isScanning.value = false;
|
|
for (var device in devices) {
|
|
_streamSubscriptions[device]?.cancel();
|
|
_streamSubscriptions.remove(device);
|
|
_connectionSubscriptions[device]?.cancel();
|
|
_connectionSubscriptions.remove(device);
|
|
UniversalBle.disconnect(device.device.deviceId);
|
|
}
|
|
_lastScanResult.clear();
|
|
hasDevices.value = false;
|
|
devices.clear();
|
|
}
|
|
|
|
void signalChange(BaseDevice baseDevice) {
|
|
_connectionStreams.add(baseDevice);
|
|
}
|
|
}
|