Skip to content

Commit

Permalink
Added quick blue back end
Browse files Browse the repository at this point in the history
The quick blue back end cannot be used because of dependency hell. But I did put all the work in creating it so here it stays in this branch until usable and needed.
  • Loading branch information
jeroen1602 committed Jan 26, 2023
1 parent 190de09 commit 8b02d60
Show file tree
Hide file tree
Showing 15 changed files with 715 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:
dart pub get
cd ../flutter_reactive_ble_back_end
flutter pub get
cd ../quick_blue_back_end
flutter pub get
cd ../flutter_web_bluetooth_back_end
dart pub get
cd ../../lighthouse_logger
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.

version:
revision: 135454af32477f815a7525073027a3ff9eff1bfd
channel: stable

project_type: plugin

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# quick_blue_back_end

Use the `quick_blue` library as a back end.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'src/quick_blue_back_end_unsupported.dart'
if (dart.library.io) 'src/quick_blue_back_end_io.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
part of quick_blue_back_end;

class QuickBlueCharacteristic extends LHBluetoothCharacteristic {
QuickBlueCharacteristic(
final String deviceId,
final String serviceId,
final String characteristicId,
final ValueHandler valueHandler,
) : _deviceId = deviceId,
_serviceId = serviceId,
_characteristicId = characteristicId,
_valueHandler = valueHandler,
uuid = LighthouseGuid.fromString(characteristicId);

final String _deviceId;
final String _serviceId;
final String _characteristicId;
final ValueHandler _valueHandler;

@override
final LighthouseGuid uuid;

@override
Future<List<int>> read() {
return _valueHandler.readValue(_deviceId, _serviceId, _characteristicId);
}

@override
Future<void> write(final List<int> data,
{final bool withoutResponse = false}) {
return _valueHandler.writeValue(_deviceId, _serviceId, _characteristicId,
Uint8List.fromList(data), withoutResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
part of quick_blue_back_end;

/// An abstraction for the [FlutterReactiveBle] bluetooth device.
class FlutterReactiveBleBluetoothDevice extends LHBluetoothDevice {
FlutterReactiveBleBluetoothDevice(
this.device,
final ConnectionHandler connectionHandler,
final ServiceHandler serviceHandler,
final ValueHandler valueHandler)
: id = LHDeviceIdentifier(device.deviceId),
_connectionHandler = connectionHandler,
_serviceHandler = serviceHandler,
_valueHandler = valueHandler;

final BlueScanResult device;
final ConnectionHandler _connectionHandler;
final ServiceHandler _serviceHandler;
final ValueHandler _valueHandler;

@override
final LHDeviceIdentifier id;

@override
Future<void> connect({final Duration? timeout}) async {
QuickBlue.connect(device.deviceId);
if (timeout != null) {
try {
await _connectionHandler
.waitForState(device.deviceId, BlueConnectionState.connected)
.timeout(timeout);
} on TimeoutException {
await disconnect();
rethrow;
}
} else {
await _connectionHandler.waitForState(
device.deviceId, BlueConnectionState.connected);
}
}

@override
Future<void> disconnect() async {
QuickBlue.disconnect(device.deviceId);
try {
await _connectionHandler
.waitForState(device.deviceId, BlueConnectionState.connected)
.timeout(const Duration(seconds: 5));
} on TimeoutException {
lighthouseLogger.severe("Ignored error while tyring to disconnect $id");
// ignore exception.
} finally {
_connectionHandler.removeSubject(device.deviceId);
_serviceHandler.removeSubject(device.deviceId);
}
}

@override
Future<List<LHBluetoothService>> discoverServices() async {
QuickBlue.discoverServices(device.deviceId);

return await _serviceHandler.waitForServices(device.deviceId).then((value) {
return value.map((serviceAndCharacteristics) {
return QuickBlueService(
serviceAndCharacteristics.serviceId,
serviceAndCharacteristics.characteristicIds
.map((final characteristic) {
return QuickBlueCharacteristic(
device.deviceId,
serviceAndCharacteristics.serviceId,
characteristic,
_valueHandler);
}).toList());
}).toList();
});
}

@override
Stream<LHBluetoothDeviceState> get state =>
_connectionHandler.getOrCreateStream(device.deviceId).map((final state) {
switch (state) {
case BlueConnectionState.connected:
return LHBluetoothDeviceState.connected;
case BlueConnectionState.disconnected:
return LHBluetoothDeviceState.disconnected;
default:
assert(false, "This shouldn't happen");
lighthouseLogger.severe("Unhandled device state in the converter");
return LHBluetoothDeviceState.unknown;
}
});

@override
String get name => device.name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
part of quick_blue_back_end;

class QuickBlueService extends LHBluetoothService {
QuickBlueService(final String serviceId, this.characteristics)
: uuid = LighthouseGuid.fromString(serviceId);

@override
final LighthouseGuid uuid;
@override
final List<LHBluetoothCharacteristic> characteristics;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
part of quick_blue_back_end;

class ConnectionHandler {
ConnectionHandler._() {
QuickBlue.setConnectionHandler(_connectionHandler);
}

void _connectionHandler(
final String deviceId, final BlueConnectionState state) {
final subject = _subjects[deviceId];
if (subject != null) {
subject.add(state);
} else {
lighthouseLogger.info("Could not handle connection state for $deviceId");
}
}

final Map<String, BehaviorSubject<BlueConnectionState>> _subjects = {};

Stream<BlueConnectionState> getOrCreateStream(final String deviceId) {
return _getOrCreateSubject(deviceId).stream;
}

BehaviorSubject<BlueConnectionState> _getOrCreateSubject(
final String deviceId) {
return _subjects[deviceId] ??=
BehaviorSubject.seeded(BlueConnectionState.disconnected);
}

Future<void> removeSubject(final String deviceId) async {
final subject = _subjects[deviceId];
if (subject != null) {
await subject.close();
_subjects.remove(deviceId);
} else {
lighthouseLogger.info("Could not close subject for $deviceId");
}
}

Future<void> removeAllSubjects() async {
for (final subject in _subjects.values) {
await subject.close();
}
_subjects.clear();
}

Future<void> waitForState(
final String deviceId, final BlueConnectionState state) async {
final subject = _getOrCreateSubject(deviceId);
if (subject.valueOrNull == state) {
return;
}
subject.stream.firstWhere((element) => element == state);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
part of quick_blue_back_end;

@immutable
class ServiceData {
const ServiceData(this.serviceId, this.characteristicIds);

final String serviceId;
final List<String> characteristicIds;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ServiceData &&
runtimeType == other.runtimeType &&
serviceId == other.serviceId;

@override
int get hashCode => serviceId.hashCode;
}

class ServiceHandler {
ServiceHandler._() {
QuickBlue.setServiceHandler(_serviceHandler);
}

void _serviceHandler(final String deviceId, final String serviceId,
List<String> characteristicIds) {
final data = ServiceData(serviceId, characteristicIds);

final set = (_services[deviceId] ??= {})..add(data);

final subject = _subjects[deviceId];
if (subject != null) {
subject.add(set);
} else {
lighthouseLogger.info("Could not handle connection state for $deviceId");
}
}

final Map<String, BehaviorSubject<Set<ServiceData>>> _subjects = {};
final Map<String, Set<ServiceData>> _services = {};

Stream<Set<ServiceData>> getOrCreateStream(final String deviceId) {
return _getOrCreateSubject(deviceId).stream;
}

BehaviorSubject<Set<ServiceData>> _getOrCreateSubject(final String deviceId) {
return _subjects[deviceId] ??= BehaviorSubject.seeded({});
}

Future<Set<ServiceData>> waitForServices(final String deviceId) async {
return _getOrCreateSubject(deviceId)
.switchMap(
(final i) => TimerStream(i, const Duration(milliseconds: 150)))
.first;
}

Future<void> removeSubject(final String deviceId) async {
final subject = _subjects[deviceId];
if (subject != null) {
await subject.close();
_subjects.remove(deviceId);
} else {
lighthouseLogger.info("Could not close subject for $deviceId");
}
}

Future<void> removeAllSubjects() async {
for (final subject in _subjects.values) {
await subject.close();
}
_subjects.clear();
}
}
Loading

0 comments on commit 8b02d60

Please sign in to comment.