feat: 重构项目 2.0.0 (#6)

* feat: 重构项目

* feat: 添加 bluez_central_manager

* feat: 联合插件

* feat: 拆分项目

* feat: 实现 linux 部分接口

* feat: 重新创建项目

* feat: 定义接口

* feat: 实现接入插件

* feat: 清空接入插件示例代码

* feat: 开发 linux 插件

* feat: 调整接口

* 临时提交

* feat: 实现 Android 接口

* fix: 修复 Android 问题

* fix: 移除多余文件

* feat: 重构项目 (#5)

* fix: 移除多余的状态判断

* fix: 外围设备断开时检查是否存在未完成的操作

* feat: 尝试使用 win32 实现接口

* fix: 修复大小写问题

* feat: 实现 macOS 接口

* feat: 实现 macOS 接口

* fix:支持使用16位短字符串生成UUID

* fix: 修复未清理已完成操作的问题

* fix: 规范命名

* 添加蓝牙使用描述

* fix: 更新 README.md
This commit is contained in:
Mr剑侠客
2023-08-17 17:49:26 +08:00
committed by GitHub
parent 3abe9d5b3d
commit d1726b52fa
371 changed files with 15666 additions and 15993 deletions

30
bluetooth_low_energy_linux/.gitignore vendored Normal file
View File

@ -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/

View File

@ -0,0 +1,30 @@
# 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: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
channel: stable
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
- platform: linux
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
# 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'

View File

@ -0,0 +1,4 @@
## 2.0.0
- Rewrite the whole project with federated plugins.
- Support macOS and Linux.

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 yanshouwang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,15 @@
# bluetooth_low_energy_linux
The Linux implementation of [`bluetooth_low_energy`][1].
## Usage
This package is [endorsed][2], which means you can simply use `bluetooth_low_energy`
normally. This package will be automatically included in your app when you do,
so you do not need to add it to your `pubspec.yaml`.
However, if you `import` this package to use any of its APIs directly, you
should add it to your `pubspec.yaml` as usual.
[1]: https://pub.dev/packages/bluetooth_low_energy
[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin

View File

@ -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

View File

@ -0,0 +1,9 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'src/my_central_controller.dart';
abstract class BluetoothLowEnergyLinux {
static void registerWith() {
CentralController.instance = MyCentralController();
}
}

View File

@ -0,0 +1,81 @@
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluez/bluez.dart';
extension MyBlueZAdapter on BlueZAdapter {
CentralState get state {
return powered ? CentralState.poweredOn : CentralState.poweredOff;
}
}
extension MyBlueZDevice on BlueZDevice {
BlueZUUID get uuid {
final node = address.replaceAll(':', '');
// We don't know the timestamp of the bluetooth device, use nil UUID as prefix.
return BlueZUUID.fromString("00000000-0000-0000-$node");
}
Advertisement get advertisement {
final manufacturerSpecificData = manufacturerData.map((key, value) {
final id = key.id;
final data = Uint8List.fromList(value);
return MapEntry(id, data);
});
final serviceUUIDs = uuids.map((uuid) => uuid.toUUID()).toList();
final serviceData = this.serviceData.map((uuid, data) {
final key = uuid.toUUID();
final value = Uint8List.fromList(data);
return MapEntry(key, value);
});
return Advertisement(
name: name,
manufacturerSpecificData: manufacturerSpecificData,
serviceUUIDs: serviceUUIDs,
serviceData: serviceData,
);
}
}
extension MyBlueZUUID on BlueZUUID {
UUID toUUID() => UUID(value);
}
extension MyBlueZGattCharacteristic on BlueZGattCharacteristic {
List<GattCharacteristicProperty> get properties => flags
.map((e) => e.toProperty())
.whereType<GattCharacteristicProperty>()
.toList();
}
extension MyBlueZGattCharacteristicFlag on BlueZGattCharacteristicFlag {
GattCharacteristicProperty? toProperty() {
switch (this) {
case BlueZGattCharacteristicFlag.read:
return GattCharacteristicProperty.read;
case BlueZGattCharacteristicFlag.write:
return GattCharacteristicProperty.write;
case BlueZGattCharacteristicFlag.writeWithoutResponse:
return GattCharacteristicProperty.writeWithoutResponse;
case BlueZGattCharacteristicFlag.notify:
return GattCharacteristicProperty.notify;
case BlueZGattCharacteristicFlag.indicate:
return GattCharacteristicProperty.indicate;
default:
return null;
}
}
}
extension MyGattCharacteristicWriteType on GattCharacteristicWriteType {
BlueZGattCharacteristicWriteType toBlueZ() {
switch (this) {
case GattCharacteristicWriteType.withResponse:
return BlueZGattCharacteristicWriteType.request;
case GattCharacteristicWriteType.withoutResponse:
return BlueZGattCharacteristicWriteType.command;
default:
throw UnimplementedError();
}
}
}

View File

@ -0,0 +1,426 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
import 'my_gatt_characteristic.dart';
import 'my_gatt_descriptor.dart';
import 'my_gatt_service.dart';
import 'my_peripheral.dart';
class MyCentralController extends CentralController {
MyCentralController()
: _client = BlueZClient(),
_stateChangedController = StreamController.broadcast(),
_discoveredController = StreamController.broadcast(),
_peripheralStateChangedController = StreamController.broadcast(),
_characteristicValueChangedController = StreamController.broadcast(),
_servicesResolvedController = StreamController.broadcast(),
_devicePropertiesChangedSubscriptions = {},
_characteristicPropertiesChangedSubscriptions = {},
_myPeripherals = {},
_myServices = {},
_myCharacteristics = {},
_myDescriptors = {},
_state = CentralState.unknown;
final BlueZClient _client;
final StreamController<CentralStateChangedEventArgs> _stateChangedController;
final StreamController<CentralDiscoveredEventArgs> _discoveredController;
final StreamController<PeripheralStateChangedEventArgs>
_peripheralStateChangedController;
final StreamController<GattCharacteristicValueChangedEventArgs>
_characteristicValueChangedController;
final StreamController<int> _servicesResolvedController;
final Map<int, StreamSubscription<List<String>>>
_devicePropertiesChangedSubscriptions;
final Map<int, StreamSubscription<List<String>>>
_characteristicPropertiesChangedSubscriptions;
final Map<int, MyPeripheral> _myPeripherals;
final Map<int, MyGattService> _myServices;
final Map<int, MyGattCharacteristic> _myCharacteristics;
final Map<int, MyGattDescriptor> _myDescriptors;
BlueZAdapter get _adapter => _client.adapters.first;
CentralState _state;
@override
CentralState get state => _state;
@override
Stream<CentralStateChangedEventArgs> get stateChanged =>
_stateChangedController.stream;
@override
Stream<CentralDiscoveredEventArgs> get discovered =>
_discoveredController.stream;
@override
Stream<PeripheralStateChangedEventArgs> get peripheralStateChanged =>
_peripheralStateChangedController.stream;
@override
Stream<GattCharacteristicValueChangedEventArgs>
get characteristicValueChanged =>
_characteristicValueChangedController.stream;
Stream<int> get _servicesResolved => _servicesResolvedController.stream;
late StreamSubscription<List<String>> _adapterPropertiesChangedSubscription;
late StreamSubscription<BlueZDevice> _deviceAddedSubscription;
late StreamSubscription<BlueZDevice> _deviceRemovedSubscription;
Future<void> _throwWithState(CentralState state) async {
if (this.state == state) {
throw BluetoothLowEnergyError('$state is unexpected.');
}
}
Future<void> _throwWithoutState(CentralState state) async {
if (this.state != state) {
throw BluetoothLowEnergyError(
'$state is expected, but current state is ${this.state}.',
);
}
}
@override
Future<void> setUp() async {
await _throwWithoutState(CentralState.unknown);
await _client.connect();
_state =
_client.adapters.isEmpty ? CentralState.unsupported : _adapter.state;
if (_state == CentralState.unsupported) {
return;
}
for (var device in _client.devices) {
if (device.adapter != _adapter) {
continue;
}
_beginDevicePropertiesChangedListener(device);
}
_adapterPropertiesChangedSubscription = _adapter.propertiesChanged.listen(
_onAdapterPropertiesChanged,
);
_deviceAddedSubscription = _client.deviceAdded.listen(_onDeviceAdded);
_deviceRemovedSubscription = _client.deviceRemoved.listen(_onDeviceRemoved);
}
@override
Future<void> tearDown() async {
await _throwWithState(CentralState.unknown);
if (_state != CentralState.unsupported && _adapter.discovering) {
await _adapter.stopDiscovery();
}
for (var myPeripheral in _myPeripherals.values) {
final device = myPeripheral.device;
if (device.connected) {
await device.disconnect();
}
}
_myPeripherals.clear();
_myServices.clear();
_myCharacteristics.clear();
_myDescriptors.clear();
for (var device in _client.devices) {
if (device.adapter != _adapter) {
continue;
}
_endDevicePropertiesChangedListener(device);
}
_adapterPropertiesChangedSubscription.cancel();
_deviceAddedSubscription.cancel();
_deviceRemovedSubscription.cancel();
await _client.close();
_state = CentralState.unknown;
}
@override
Future<void> startDiscovery() async {
await _throwWithoutState(CentralState.poweredOn);
await _adapter.startDiscovery();
}
@override
Future<void> stopDiscovery() async {
await _throwWithoutState(CentralState.poweredOn);
await _adapter.stopDiscovery();
}
@override
Future<void> connect(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
final device = myPeripheral.device;
await device.connect();
}
@override
Future<void> disconnect(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
final device = myPeripheral.device;
await device.disconnect();
}
@override
Future<void> discoverGATT(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
final device = myPeripheral.device;
if (!device.connected) {
throw BluetoothLowEnergyError('Peripheral is disconnected.');
}
if (device.servicesResolved) {
return;
}
await _servicesResolved.firstWhere(
(hashCode) => hashCode == peripheral.hashCode,
);
}
@override
Future<List<GattService>> getServices(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
final blueZDevice = myPeripheral.device;
return blueZDevice.gattServices
.map(
(service) => _myServices.putIfAbsent(
service.hashCode,
() => MyGattService(service),
),
)
.toList();
}
@override
Future<List<GattCharacteristic>> getCharacteristics(
GattService service,
) async {
await _throwWithoutState(CentralState.poweredOn);
final myService = service as MyGattService;
final blueZService = myService.service;
return blueZService.characteristics
.map(
(characteristic) => _myCharacteristics.putIfAbsent(
characteristic.hashCode,
() => MyGattCharacteristic(characteristic),
),
)
.toList();
}
@override
Future<List<GattDescriptor>> getDescriptors(
GattCharacteristic characteristic,
) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final blueZCharacteristic = myCharacteristic.characteristic;
return blueZCharacteristic.descriptors
.map(
(descriptor) => _myDescriptors.putIfAbsent(
descriptor.hashCode,
() => MyGattDescriptor(descriptor),
),
)
.toList();
}
@override
Future<Uint8List> readCharacteristic(
GattCharacteristic characteristic,
) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final blueZCharacteristic = myCharacteristic.characteristic;
final blueZValue = await blueZCharacteristic.readValue();
return Uint8List.fromList(blueZValue);
}
@override
Future<void> writeCharacteristic(
GattCharacteristic characteristic, {
required Uint8List value,
required GattCharacteristicWriteType type,
}) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final blueZCharacteristic = myCharacteristic.characteristic;
await blueZCharacteristic.writeValue(
value,
type: type.toBlueZ(),
);
}
@override
Future<void> notifyCharacteristic(
GattCharacteristic characteristic, {
required bool state,
}) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final blueZCharacteristic = myCharacteristic.characteristic;
if (state) {
await blueZCharacteristic.startNotify();
} else {
await blueZCharacteristic.stopNotify();
}
}
@override
Future<Uint8List> readDescriptor(GattDescriptor descriptor) async {
await _throwWithoutState(CentralState.poweredOn);
final myDescriptor = descriptor as MyGattDescriptor;
final blueZDescriptor = myDescriptor.descriptor;
final blueZValue = await blueZDescriptor.readValue();
return Uint8List.fromList(blueZValue);
}
@override
Future<void> writeDescriptor(
GattDescriptor descriptor, {
required Uint8List value,
}) async {
await _throwWithoutState(CentralState.poweredOn);
final myDescriptor = descriptor as MyGattDescriptor;
final blueZDescriptor = myDescriptor.descriptor;
await blueZDescriptor.writeValue(value);
}
void _onAdapterPropertiesChanged(List<String> properties) {
for (var property in properties) {
switch (property) {
case 'Powered':
final state = _adapter.state;
if (_state == state) {
return;
}
_state = state;
final eventArgs = CentralStateChangedEventArgs(state);
_stateChangedController.add(eventArgs);
break;
default:
break;
}
}
}
void _onDeviceAdded(BlueZDevice device) {
if (device.adapter != _adapter) {
return;
}
_onDiscovered(device);
_beginDevicePropertiesChangedListener(device);
}
void _onDeviceRemoved(BlueZDevice device) {
if (device.adapter != _adapter) {
return;
}
_endDevicePropertiesChangedListener(device);
}
void _onDiscovered(BlueZDevice device) {
final myPeripheral = _myPeripherals.putIfAbsent(
device.hashCode,
() => MyPeripheral(device),
);
final rssi = device.rssi;
final advertisement = device.advertisement;
final eventArgs = CentralDiscoveredEventArgs(
myPeripheral,
rssi,
advertisement,
);
_discoveredController.add(eventArgs);
}
void _beginDevicePropertiesChangedListener(BlueZDevice device) {
for (var service in device.gattServices) {
for (var characteristic in service.characteristics) {
_beginCharacteristicPropertiesChangedListener(characteristic);
}
}
final subscription = device.propertiesChanged.listen((properties) {
for (var property in properties) {
switch (property) {
case 'RSSI':
_onDiscovered(device);
break;
case 'Connected':
final myPeripheral =
_myPeripherals[device.hashCode] as MyPeripheral;
final state = device.connected;
final eventArgs = PeripheralStateChangedEventArgs(
myPeripheral,
state,
);
_peripheralStateChangedController.add(eventArgs);
break;
case 'UUIDs':
break;
case 'ServicesResolved':
if (device.servicesResolved) {
for (var service in device.gattServices) {
for (var characteristic in service.characteristics) {
_beginCharacteristicPropertiesChangedListener(characteristic);
}
}
_servicesResolvedController.add(device.hashCode);
}
break;
default:
break;
}
}
});
_devicePropertiesChangedSubscriptions[device.hashCode] = subscription;
}
void _endDevicePropertiesChangedListener(BlueZDevice device) {
for (var service in device.gattServices) {
for (var characteristic in service.characteristics) {
_endCharacteristicPropertiesChangedListener(characteristic);
}
}
final subscription = _devicePropertiesChangedSubscriptions.remove(
device.address,
);
subscription?.cancel();
}
void _beginCharacteristicPropertiesChangedListener(
BlueZGattCharacteristic characteristic,
) {
final subscription = characteristic.propertiesChanged.listen((properties) {
for (var property in properties) {
switch (property) {
case 'Value':
final instance = _myCharacteristics[characteristic.hashCode];
final myCharacteristic = instance is MyGattCharacteristic
? instance
: MyGattCharacteristic(characteristic);
final value = Uint8List.fromList(characteristic.value);
final eventArgs = GattCharacteristicValueChangedEventArgs(
myCharacteristic,
value,
);
_characteristicValueChangedController.add(eventArgs);
break;
default:
break;
}
}
});
_characteristicPropertiesChangedSubscriptions[characteristic.hashCode] =
subscription;
}
void _endCharacteristicPropertiesChangedListener(
BlueZGattCharacteristic characteristic,
) {
final subscription = _characteristicPropertiesChangedSubscriptions.remove(
characteristic.hashCode,
);
subscription?.cancel();
}
}

View File

@ -0,0 +1,17 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
import 'my_object.dart';
class MyGattCharacteristic extends MyObject implements GattCharacteristic {
final BlueZGattCharacteristic characteristic;
MyGattCharacteristic(this.characteristic) : super(characteristic);
@override
UUID get uuid => characteristic.uuid.toUUID();
@override
List<GattCharacteristicProperty> get properties => characteristic.properties;
}

View File

@ -0,0 +1,14 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
import 'my_object.dart';
class MyGattDescriptor extends MyObject implements GattDescriptor {
final BlueZGattDescriptor descriptor;
MyGattDescriptor(this.descriptor) : super(descriptor);
@override
UUID get uuid => descriptor.uuid.toUUID();
}

View File

@ -0,0 +1,14 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
import 'my_object.dart';
class MyGattService extends MyObject implements GattService {
final BlueZGattService service;
MyGattService(this.service) : super(service);
@override
UUID get uuid => service.uuid.toUUID();
}

View File

@ -0,0 +1,11 @@
abstract class MyObject {
@override
final int hashCode;
MyObject(Object instance) : hashCode = instance.hashCode;
@override
bool operator ==(Object other) {
return other is MyObject && other.hashCode == hashCode;
}
}

View File

@ -0,0 +1,14 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
import 'my_object.dart';
class MyPeripheral extends MyObject implements Peripheral {
final BlueZDevice device;
MyPeripheral(this.device) : super(device);
@override
UUID get uuid => device.uuid.toUUID();
}

View File

@ -0,0 +1,27 @@
name: bluetooth_low_energy_linux
description: Linux implementation of the bluetooth_low_energy plugin.
version: 2.0.0
homepage: https://github.com/yanshouwang/bluetooth_low_energy
environment:
sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.3.0"
dependencies:
flutter:
sdk: flutter
bluetooth_low_energy_platform_interface:
path: ../bluetooth_low_energy_platform_interface
bluez: ^0.8.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
plugin:
implements: bluetooth_low_energy
platforms:
linux:
dartPluginClass: BluetoothLowEnergyLinux