feat: 5.0.0 (#35)

* draft: 临时提交

* feat: 实现扫描功能

* fix: 优化广播逻辑

* feat: 添加协程方法

* fix: 修改宏定义

* draft: 临时提交

* feat: 调整接口

* fix: 修改版本号

* feat: 4.1.1

* draft: 临时提交

* feat: 5.0.0-dev.2

* fix: 修复版本号错误

* draft: 临时提交

* fix: 修复连接断开异常

* fix: 修复问题

* fix: 优化代码

* fix:  优化 short UUID 格式化逻辑

* fix: 尝试实现 read_rssi 接口,当前此接口不可用,会报异常

* feat: 删除 getMaximumWriteLength 方法

* fix: 更新 CHANGELOG.md

* feat: 5.0.0-dev.1

* fix: 更新依赖项

* feat: linux-5.0.0-dev.1

* fix: 更新 CHANGELOG.md

* fix: 开始搜索设备时清空设备列表

* fix: 开始扫描时清空设备列表

* feat: 5.0.0-dev.2

* fix: 优化 MyGattService 和 MyGattCharacteristic

* feat: 更新 interface 版本 -> 5.0.0-dev.4

* feat: 更新 interface 版本 -> 5.0.0-dev.4

* feat: 实现 flutter 部分 5.0.0

* fix: 移除 maximumWriteLength

* fix: 移除 rssi

* feat: 5.0.0-dev.1

* feat: 5.0.0-dev.2

* fix: 更新依赖项

* fix: 5.0.0-dev.4

* fix: 更新依赖项

* draft: 临时提交

* feat: 5.0.0-dev.5

* draft: 删除 MyCentralManager 和 MyPeripheralManager

* fix: 更新依赖项

* fix: 更新依赖项

* feat: 适配新接口

* feat: 5.0.0-dev.6

* draft: 临时提交

* feat: 5.0.0-dev.7

* fix: 修改版本号

* feat: 5.0.0-dev.8

* feat: 5.0.0-dev.9

* fix: 修复 trimGATT 错误

* feat: 5.0.0-dev.6

* feat: 5.0.0-dev.3

* feat: 5.0.0-dev.4

* fix: 更新 pubspec.lock

* feat: 5.0.0-dev.7

* feat: 5.0.0-dev.3

* fix: balabala

* fix: balabala

* draft: 5.0.0-dev.1

* fix: trim GATT when call the `writeCharacteristic` method.

* fix: make difference of `trim` and `fragment`.

* feat: 5.0.0-dev.1

* feat: 5.0.0-dev.1

* feat: 优化示例程序

* fix: 更新 README.md

* fix: 修复插件引用

* draft: XXXX

* feat: 增加调试信息

* fix: 更新 pubspec.lock

* feat: 5.0.0-dev.4

* feat: 5.0.0-dev.3

* feat: 5.0.0

* feat: 5.0.0

* feat: 5.0.0

* feat: 5.0.0

* feat: 5.0.0

* feat: 5.0.0
This commit is contained in:
iAMD
2023-12-31 00:53:48 +08:00
committed by GitHub
parent cfe0eda4a3
commit 87fe3e2447
137 changed files with 14108 additions and 8393 deletions

View File

@ -1,3 +1,33 @@
## 5.0.0
* Now `CentralManager#writeCharacteristic` will fragment the value automatically, the maximum write length is 512 bytes.
* Add `UUID#fromAddress` constructor.
* Remove `CentralManager#getMaximumWriteLength` method.
* Move `CentralManager#state` to `CentralManager#getState()`.
* Move `PeripheralStateChangedEventArgs` to `ConnectionStateChangedEventArgs`.
* Move `CentralManager#peripheralStateChanged` to `CentralManager#connectionStateChanged`.
* Move `GattCharacteristicValueChangedEventArgs` to `GattCharacteristicNotifiedEventArgs`.
* Move `CentralManager#characteristicValueChanged` to `CentralManager#characteristicNotified`.
* Move `CentralManager#notifyCharacteristic` to `CentralManager#setCharacteristicNotifyState`.
## 5.0.0-dev.4
* Add event logs.
## 5.0.0-dev.3
* Implements new Api.
## 5.0.0-dev.2
* Update interface to 5.0.0-dev.4.
## 5.0.0-dev.1
* Implement the `5.0.0` api.
* [Fix the issue that the same device was discovered multi times.](https://github.com/yanshouwang/bluetooth_low_energy/issues/32)
* [Fix the issue that some devices can't be discovered.](https://github.com/yanshouwang/bluetooth_low_energy/issues/32)
## 4.0.0
* Remove `BluetoothLowEnergy` class.

View File

@ -8,9 +8,6 @@ import 'package:convert/convert.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
CentralManager get centralManager => CentralManager.instance;
PeripheralManager get peripheralManager => PeripheralManager.instance;
void main() {
runZonedGuarded(onStartUp, onCrashed);
}
@ -18,8 +15,8 @@ void main() {
void onStartUp() async {
Logger.root.onRecord.listen(onLogRecord);
WidgetsFlutterBinding.ensureInitialized();
await centralManager.setUp();
await peripheralManager.setUp();
await CentralManager.instance.setUp();
// await peripheralManager.setUp();
runApp(const MyApp());
}
@ -58,6 +55,8 @@ class _MyAppState extends State<MyApp> {
return MaterialApp(
theme: ThemeData.light(
useMaterial3: true,
).copyWith(
materialTapTargetSize: MaterialTapTargetSize.padded,
),
home: const HomeView(),
routes: {
@ -73,81 +72,18 @@ class _MyAppState extends State<MyApp> {
}
}
class HomeView extends StatefulWidget {
class HomeView extends StatelessWidget {
const HomeView({super.key});
@override
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
late final PageController controller;
@override
void initState() {
super.initState();
controller = PageController();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: buildBody(context),
bottomNavigationBar: buildBottomNavigationBar(context),
);
}
Widget buildBody(BuildContext context) {
return PageView.builder(
controller: controller,
itemBuilder: (context, i) {
switch (i) {
case 0:
return const ScannerView();
case 1:
return const AdvertiserView();
default:
throw ArgumentError.value(i);
}
},
itemCount: 2,
);
}
Widget buildBottomNavigationBar(BuildContext context) {
return ListenableBuilder(
listenable: controller,
builder: (context, child) {
return BottomNavigationBar(
onTap: (i) {
const duration = Duration(milliseconds: 300);
const curve = Curves.ease;
controller.animateToPage(
i,
duration: duration,
curve: curve,
);
},
currentIndex: controller.page?.toInt() ?? 0,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.radar),
label: 'scanner',
),
BottomNavigationBarItem(
icon: Icon(Icons.sensors),
label: 'advertiser',
),
],
);
},
);
}
@override
void dispose() {
super.dispose();
controller.dispose();
return const ScannerView();
}
}
@ -168,16 +104,20 @@ class _ScannerViewState extends State<ScannerView> {
@override
void initState() {
super.initState();
state = ValueNotifier(centralManager.state);
state = ValueNotifier(BluetoothLowEnergyState.unknown);
discovering = ValueNotifier(false);
discoveredEventArgs = ValueNotifier([]);
stateChangedSubscription = centralManager.stateChanged.listen(
stateChangedSubscription = CentralManager.instance.stateChanged.listen(
(eventArgs) {
state.value = eventArgs.state;
},
);
discoveredSubscription = centralManager.discovered.listen(
discoveredSubscription = CentralManager.instance.discovered.listen(
(eventArgs) {
final name = eventArgs.advertisement.name;
if (name == null || name.isEmpty) {
return;
}
final items = discoveredEventArgs.value;
final i = items.indexWhere(
(item) => item.peripheral == eventArgs.peripheral,
@ -190,6 +130,11 @@ class _ScannerViewState extends State<ScannerView> {
}
},
);
setUp();
}
void setUp() async {
state.value = await CentralManager.instance.getState();
}
@override
@ -233,12 +178,13 @@ class _ScannerViewState extends State<ScannerView> {
}
Future<void> startDiscovery() async {
await centralManager.startDiscovery();
discoveredEventArgs.value = [];
await CentralManager.instance.startDiscovery();
discovering.value = true;
}
Future<void> stopDiscovery() async {
await centralManager.stopDiscovery();
await CentralManager.instance.stopDiscovery();
discovering.value = false;
}
@ -340,7 +286,13 @@ class _ScannerViewState extends State<ScannerView> {
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: RssiWidget(rssi),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
RssiWidget(rssi),
Text('$rssi'),
],
),
);
},
separatorBuilder: (context, i) {
@ -378,44 +330,39 @@ class PeripheralView extends StatefulWidget {
}
class _PeripheralViewState extends State<PeripheralView> {
late final ValueNotifier<bool> state;
late final ValueNotifier<bool> connectionState;
late final DiscoveredEventArgs eventArgs;
late final ValueNotifier<List<GattService>> services;
late final ValueNotifier<List<GattCharacteristic>> characteristics;
late final ValueNotifier<GattService?> service;
late final ValueNotifier<GattCharacteristic?> characteristic;
late final ValueNotifier<GattCharacteristicWriteType> writeType;
late final ValueNotifier<int> maximumWriteLength;
late final ValueNotifier<int> rssi;
late final ValueNotifier<List<Log>> logs;
late final TextEditingController writeController;
late final StreamSubscription stateChangedSubscription;
late final StreamSubscription valueChangedSubscription;
late final StreamSubscription rssiChangedSubscription;
late final Timer rssiTimer;
late final StreamSubscription connectionStateChangedSubscription;
late final StreamSubscription characteristicNotifiedSubscription;
@override
void initState() {
super.initState();
eventArgs = widget.eventArgs;
state = ValueNotifier(false);
connectionState = ValueNotifier(false);
services = ValueNotifier([]);
characteristics = ValueNotifier([]);
service = ValueNotifier(null);
characteristic = ValueNotifier(null);
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
maximumWriteLength = ValueNotifier(0);
rssi = ValueNotifier(-100);
logs = ValueNotifier([]);
writeController = TextEditingController();
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
connectionStateChangedSubscription =
CentralManager.instance.connectionStateChanged.listen(
(eventArgs) {
if (eventArgs.peripheral != this.eventArgs.peripheral) {
return;
}
final state = eventArgs.state;
this.state.value = state;
if (!state) {
final connectionState = eventArgs.connectionState;
this.connectionState.value = connectionState;
if (!connectionState) {
services.value = [];
characteristics.value = [];
service.value = null;
@ -424,12 +371,13 @@ class _PeripheralViewState extends State<PeripheralView> {
}
},
);
valueChangedSubscription = centralManager.characteristicValueChanged.listen(
characteristicNotifiedSubscription =
CentralManager.instance.characteristicNotified.listen(
(eventArgs) {
final characteristic = this.characteristic.value;
if (eventArgs.characteristic != characteristic) {
return;
}
// final characteristic = this.characteristic.value;
// if (eventArgs.characteristic != characteristic) {
// return;
// }
const type = LogType.notify;
final log = Log(type, eventArgs.value);
logs.value = [
@ -438,28 +386,16 @@ class _PeripheralViewState extends State<PeripheralView> {
];
},
);
rssiTimer = Timer.periodic(
const Duration(seconds: 5),
(timer) async {
final state = this.state.value;
if (state) {
rssi.value = await centralManager.readRSSI(eventArgs.peripheral);
} else {
rssi.value = -100;
}
},
);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (state.value) {
return PopScope(
onPopInvoked: (didPop) async {
if (connectionState.value) {
final peripheral = eventArgs.peripheral;
await centralManager.disconnect(peripheral);
await CentralManager.instance.disconnect(peripheral);
}
return true;
},
child: Scaffold(
appBar: buildAppBar(context),
@ -474,25 +410,17 @@ class _PeripheralViewState extends State<PeripheralView> {
title: Text(title),
actions: [
ValueListenableBuilder(
valueListenable: state,
valueListenable: connectionState,
builder: (context, state, child) {
return TextButton(
onPressed: () async {
final peripheral = eventArgs.peripheral;
if (state) {
await centralManager.disconnect(peripheral);
maximumWriteLength.value = 0;
rssi.value = 0;
await CentralManager.instance.disconnect(peripheral);
} else {
await centralManager.connect(peripheral);
await CentralManager.instance.connect(peripheral);
services.value =
await centralManager.discoverGATT(peripheral);
maximumWriteLength.value =
await centralManager.getMaximumWriteLength(
peripheral,
type: writeType.value,
);
rssi.value = await centralManager.readRSSI(peripheral);
await CentralManager.instance.discoverGATT(peripheral);
}
},
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
@ -566,7 +494,32 @@ class _PeripheralViewState extends State<PeripheralView> {
hint: const Text('CHOOSE A CHARACTERISTIC'),
value: characteristic,
onChanged: (characteristic) {
if (characteristic == null) {
return;
}
this.characteristic.value = characteristic;
final writeType = this.writeType.value;
final canWrite = characteristic.properties.contains(
GattCharacteristicProperty.write,
);
final canWriteWithoutResponse =
characteristic.properties.contains(
GattCharacteristicProperty.writeWithoutResponse,
);
if (writeType ==
GattCharacteristicWriteType.withResponse &&
!canWrite &&
canWriteWithoutResponse) {
this.writeType.value =
GattCharacteristicWriteType.withoutResponse;
}
if (writeType ==
GattCharacteristicWriteType.withoutResponse &&
!canWriteWithoutResponse &&
canWrite) {
this.writeType.value =
GattCharacteristicWriteType.withResponse;
}
},
);
},
@ -624,129 +577,45 @@ class _PeripheralViewState extends State<PeripheralView> {
},
),
),
Row(
children: [
ValueListenableBuilder(
valueListenable: writeType,
builder: (context, writeType, child) {
return ToggleButtons(
onPressed: (i) async {
final type = GattCharacteristicWriteType.values[i];
this.writeType.value = type;
maximumWriteLength.value =
await centralManager.getMaximumWriteLength(
eventArgs.peripheral,
type: type,
);
},
constraints: const BoxConstraints(
minWidth: 0.0,
minHeight: 0.0,
),
borderRadius: BorderRadius.circular(4.0),
isSelected: GattCharacteristicWriteType.values
.map((type) => type == writeType)
.toList(),
children: GattCharacteristicWriteType.values.map((type) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 4.0,
),
child: Text(type.name),
);
}).toList(),
);
// final segments =
// GattCharacteristicWriteType.values.map((type) {
// return ButtonSegment(
// value: type,
// label: Text(type.name),
// );
// }).toList();
// return SegmentedButton(
// segments: segments,
// selected: {writeType},
// showSelectedIcon: false,
// style: OutlinedButton.styleFrom(
// tapTargetSize: MaterialTapTargetSize.shrinkWrap,
// padding: EdgeInsets.zero,
// visualDensity: VisualDensity.compact,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8.0),
// ),
// ),
// );
},
),
const SizedBox(width: 8.0),
ValueListenableBuilder(
valueListenable: state,
builder: (context, state, child) {
return ValueListenableBuilder(
valueListenable: maximumWriteLength,
builder: (context, maximumWriteLength, child) {
return Text('$maximumWriteLength');
},
);
},
),
const Spacer(),
ValueListenableBuilder(
valueListenable: rssi,
builder: (context, rssi, child) {
return RssiWidget(rssi);
},
),
],
),
Container(
margin: const EdgeInsets.only(bottom: 16.0),
height: 160.0,
child: ValueListenableBuilder(
valueListenable: characteristic,
builder: (context, characteristic, child) {
final bool canNotify, canRead, canWrite;
if (characteristic == null) {
canNotify = canRead = canWrite = false;
} else {
final properties = characteristic.properties;
canNotify = properties.contains(
GattCharacteristicProperty.notify,
);
canRead = properties.contains(
GattCharacteristicProperty.read,
);
canWrite = properties.contains(
GattCharacteristicProperty.write,
);
}
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: TextField(
controller: writeController,
enabled: canWrite,
expands: true,
maxLines: null,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8.0,
),
),
),
),
Column(
mainAxisSize: MainAxisSize.min,
ValueListenableBuilder(
valueListenable: characteristic,
builder: (context, characteristic, chld) {
final bool canNotify, canRead, canWrite, canWriteWithoutResponse;
if (characteristic == null) {
canNotify =
canRead = canWrite = canWriteWithoutResponse = false;
} else {
final properties = characteristic.properties;
canNotify = properties.contains(
GattCharacteristicProperty.notify,
) ||
properties.contains(
GattCharacteristicProperty.indicate,
);
canRead = properties.contains(
GattCharacteristicProperty.read,
);
canWrite = properties.contains(
GattCharacteristicProperty.write,
);
canWriteWithoutResponse = properties.contains(
GattCharacteristicProperty.writeWithoutResponse,
);
}
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
margin: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
ElevatedButton(
onPressed: characteristic != null && canNotify
? () async {
await centralManager.notifyCharacteristic(
await CentralManager.instance
.setCharacteristicNotifyState(
characteristic,
state: true,
);
@ -754,10 +623,11 @@ class _PeripheralViewState extends State<PeripheralView> {
: null,
child: const Text('NOTIFY'),
),
TextButton(
const SizedBox(width: 8.0),
ElevatedButton(
onPressed: characteristic != null && canRead
? () async {
final value = await centralManager
final value = await CentralManager.instance
.readCharacteristic(characteristic);
const type = LogType.read;
final log = Log(type, value);
@ -765,31 +635,124 @@ class _PeripheralViewState extends State<PeripheralView> {
}
: null,
child: const Text('READ'),
),
TextButton(
onPressed: characteristic != null && canWrite
? () async {
final text = writeController.text;
final elements = utf8.encode(text);
final value = Uint8List.fromList(elements);
final type = writeType.value;
await centralManager.writeCharacteristic(
characteristic,
value: value,
type: type,
);
final log = Log(LogType.write, value);
logs.value = [...logs.value, log];
}
: null,
child: const Text('WRITE'),
),
)
],
),
],
);
},
),
),
SizedBox(
height: 160.0,
child: TextField(
controller: writeController,
enabled: canWrite || canWriteWithoutResponse,
expands: true,
maxLines: null,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8.0,
),
),
),
),
Row(
children: [
ValueListenableBuilder(
valueListenable: writeType,
builder: (context, writeType, child) {
return ToggleButtons(
onPressed: canWrite || canWriteWithoutResponse
? (i) {
if (!canWrite || !canWriteWithoutResponse) {
return;
}
final type =
GattCharacteristicWriteType.values[i];
this.writeType.value = type;
}
: null,
constraints: const BoxConstraints(
minWidth: 0.0,
minHeight: 0.0,
),
borderRadius: BorderRadius.circular(4.0),
isSelected: GattCharacteristicWriteType.values
.map((type) => type == writeType)
.toList(),
children: GattCharacteristicWriteType.values.map(
(type) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 4.0,
),
child: Text(type.name),
);
},
).toList(),
);
// final segments =
// GattCharacteristicWriteType.values.map((type) {
// return ButtonSegment(
// value: type,
// label: Text(type.name),
// );
// }).toList();
// return SegmentedButton(
// segments: segments,
// selected: {writeType},
// showSelectedIcon: false,
// style: OutlinedButton.styleFrom(
// tapTargetSize: MaterialTapTargetSize.shrinkWrap,
// padding: EdgeInsets.zero,
// visualDensity: VisualDensity.compact,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8.0),
// ),
// ),
// );
},
),
const Spacer(),
ElevatedButton(
onPressed: characteristic != null && canWrite
? () async {
final text = writeController.text;
final elements = utf8.encode(text);
final value = Uint8List.fromList(elements);
final type = writeType.value;
// Fragments the value by 512 bytes.
const fragmentSize = 512;
var start = 0;
while (start < value.length) {
final end = start + fragmentSize;
final fragmentedValue = end < value.length
? value.sublist(start, end)
: value.sublist(start);
await CentralManager.instance
.writeCharacteristic(
characteristic,
value: fragmentedValue,
type: type,
);
final log = Log(
LogType.write,
fragmentedValue,
);
logs.value = [...logs.value, log];
start = end;
}
}
: null,
child: const Text('WRITE'),
),
],
),
const SizedBox(height: 16.0),
],
);
},
),
],
),
@ -799,290 +762,19 @@ class _PeripheralViewState extends State<PeripheralView> {
@override
void dispose() {
super.dispose();
rssiTimer.cancel();
stateChangedSubscription.cancel();
valueChangedSubscription.cancel();
state.dispose();
connectionStateChangedSubscription.cancel();
characteristicNotifiedSubscription.cancel();
connectionState.dispose();
services.dispose();
characteristics.dispose();
service.dispose();
characteristic.dispose();
writeType.dispose();
maximumWriteLength.dispose();
rssi.dispose();
logs.dispose();
writeController.dispose();
}
}
class AdvertiserView extends StatefulWidget {
const AdvertiserView({super.key});
@override
State<AdvertiserView> createState() => _AdvertiserViewState();
}
class _AdvertiserViewState extends State<AdvertiserView>
with SingleTickerProviderStateMixin {
late final ValueNotifier<BluetoothLowEnergyState> state;
late final ValueNotifier<bool> advertising;
late final ValueNotifier<List<Log>> logs;
late final StreamSubscription stateChangedSubscription;
late final StreamSubscription readCharacteristicCommandReceivedSubscription;
late final StreamSubscription writeCharacteristicCommandReceivedSubscription;
late final StreamSubscription notifyCharacteristicCommandReceivedSubscription;
@override
void initState() {
super.initState();
state = ValueNotifier(peripheralManager.state);
advertising = ValueNotifier(false);
logs = ValueNotifier([]);
stateChangedSubscription = peripheralManager.stateChanged.listen(
(eventArgs) {
state.value = eventArgs.state;
},
);
readCharacteristicCommandReceivedSubscription =
peripheralManager.readCharacteristicCommandReceived.listen(
(eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final id = eventArgs.id;
final offset = eventArgs.offset;
final log = Log(
LogType.read,
Uint8List.fromList([]),
'central: ${central.uuid}; characteristic: ${characteristic.uuid}; id: $id; offset: $offset',
);
logs.value = [
...logs.value,
log,
];
// final maximumWriteLength = peripheralManager.getMaximumWriteLength(
// central,
// );
const status = true;
final value = Uint8List.fromList([0x01, 0x02, 0x03]);
await peripheralManager.sendReadCharacteristicReply(
central,
characteristic: characteristic,
id: id,
offset: offset,
status: status,
value: value,
);
},
);
writeCharacteristicCommandReceivedSubscription =
peripheralManager.writeCharacteristicCommandReceived.listen(
(eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final id = eventArgs.id;
final offset = eventArgs.offset;
final value = eventArgs.value;
final log = Log(
LogType.write,
value,
'central: ${central.uuid}; characteristic: ${characteristic.uuid}; id: $id; offset: $offset',
);
logs.value = [
...logs.value,
log,
];
const status = true;
await peripheralManager.sendWriteCharacteristicReply(
central,
characteristic: characteristic,
id: id,
offset: offset,
status: status,
);
},
);
notifyCharacteristicCommandReceivedSubscription =
peripheralManager.notifyCharacteristicCommandReceived.listen(
(eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final state = eventArgs.state;
final log = Log(
LogType.write,
Uint8List.fromList([]),
'central: ${central.uuid}; characteristic: ${characteristic.uuid}; state: $state',
);
logs.value = [
...logs.value,
log,
];
// Write someting to the central when notify started.
if (state) {
final value = Uint8List.fromList([0x03, 0x02, 0x01]);
await peripheralManager.notifyCharacteristicValueChanged(
central,
characteristic: characteristic,
value: value,
);
}
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: buildAppBar(context),
body: buildBody(context),
);
}
PreferredSizeWidget buildAppBar(BuildContext context) {
return AppBar(
title: const Text('Advertiser'),
actions: [
ValueListenableBuilder(
valueListenable: state,
builder: (context, state, child) {
return ValueListenableBuilder(
valueListenable: advertising,
builder: (context, advertising, child) {
return TextButton(
onPressed: state == BluetoothLowEnergyState.poweredOn
? () async {
if (advertising) {
await stopAdvertising();
} else {
await startAdvertising();
}
}
: null,
child: Text(
advertising ? 'END' : 'BEGIN',
),
);
},
);
},
),
],
);
}
Future<void> startAdvertising() async {
await peripheralManager.clearServices();
final service = GattService(
uuid: UUID.short(100),
characteristics: [
GattCharacteristic(
uuid: UUID.short(200),
properties: [
GattCharacteristicProperty.read,
],
descriptors: [],
),
GattCharacteristic(
uuid: UUID.short(201),
properties: [
GattCharacteristicProperty.read,
GattCharacteristicProperty.write,
GattCharacteristicProperty.writeWithoutResponse,
],
descriptors: [],
),
GattCharacteristic(
uuid: UUID.short(202),
properties: [
GattCharacteristicProperty.notify,
GattCharacteristicProperty.indicate,
],
descriptors: [],
),
],
);
await peripheralManager.addService(service);
final advertisement = Advertisement(
name: 'flutter',
manufacturerSpecificData: ManufacturerSpecificData(
id: 0x2e19,
data: Uint8List.fromList([0x01, 0x02, 0x03]),
),
);
await peripheralManager.startAdvertising(advertisement);
advertising.value = true;
}
Future<void> stopAdvertising() async {
await peripheralManager.stopAdvertising();
advertising.value = false;
}
Widget buildBody(BuildContext context) {
final theme = Theme.of(context);
return ValueListenableBuilder(
valueListenable: logs,
builder: (context, logs, child) {
return ListView.builder(
itemBuilder: (context, i) {
final log = logs[i];
final type = log.type.name.toUpperCase().characters.first;
final Color typeColor;
switch (log.type) {
case LogType.read:
typeColor = Colors.blue;
break;
case LogType.write:
typeColor = Colors.amber;
break;
case LogType.notify:
typeColor = Colors.red;
break;
default:
typeColor = Colors.black;
}
final time = DateFormat.Hms().format(log.time);
final value = log.value;
final message = '${log.detail}; ${hex.encode(value)}';
return Text.rich(
TextSpan(
text: '[$type:${value.length}]',
children: [
TextSpan(
text: ' $time: ',
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.green,
),
),
TextSpan(
text: message,
style: theme.textTheme.bodyMedium,
),
],
style: theme.textTheme.bodyMedium?.copyWith(
color: typeColor,
),
),
);
},
itemCount: logs.length,
);
},
);
}
@override
void dispose() {
super.dispose();
stateChangedSubscription.cancel();
readCharacteristicCommandReceivedSubscription.cancel();
writeCharacteristicCommandReceivedSubscription.cancel();
notifyCharacteristicCommandReceivedSubscription.cancel();
state.dispose();
advertising.dispose();
logs.dispose();
}
}
class Log {
final DateTime time;
final LogType type;

View File

@ -23,15 +23,15 @@ packages:
path: ".."
relative: true
source: path
version: "4.0.0"
version: "5.0.0"
bluetooth_low_energy_platform_interface:
dependency: "direct main"
description:
name: bluetooth_low_energy_platform_interface
sha256: a01819f4ef89d033edaa979465ec8c3af13b2618dc718d90fe681be91b6c4356
sha256: "54f92ab2d7746fb6f2b4a40a48cd7eb8e3bf772f3ee89e1979d4d7b741fb2a05"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "5.0.0"
bluez:
dependency: transitive
description:
@ -68,10 +68,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.2"
version: "1.18.0"
convert:
dependency: "direct main"
description:
@ -92,10 +92,10 @@ packages:
dependency: transitive
description:
name: dbus
sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263"
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
url: "https://pub.dev"
source: hosted
version: "0.7.8"
version: "0.7.10"
fake_async:
dependency: transitive
description:
@ -134,10 +134,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: ad76540d21c066228ee3f9d1dad64a9f7e46530e8bb7c85011a88bc1fd874bc5
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.1"
flutter_test:
dependency: "direct dev"
description: flutter
@ -157,10 +157,10 @@ packages:
dependency: "direct main"
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.18.1"
version: "0.19.0"
lints:
dependency: transitive
description:
@ -205,10 +205,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
path:
dependency: transitive
description:
@ -221,26 +221,26 @@ packages:
dependency: transitive
description:
name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "5.4.0"
version: "6.0.2"
platform:
dependency: transitive
description:
name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.2"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8
url: "https://pub.dev"
source: hosted
version: "2.1.6"
version: "2.1.7"
process:
dependency: transitive
description:
@ -266,18 +266,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
@ -306,10 +306,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
version: "0.6.1"
typed_data:
dependency: transitive
description:
@ -330,18 +330,18 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: c620a6f783fa22436da68e42db7ebbf18b8c44b9a46ab911f666ff09ffd9153f
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
url: "https://pub.dev"
source: hosted
version: "11.7.1"
version: "11.10.0"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
version: "0.3.0"
webdriver:
dependency: transitive
description:
@ -354,10 +354,10 @@ packages:
dependency: transitive
description:
name: xml
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.5.0"
sdks:
dart: ">=3.1.0-185.0.dev <4.0.0"
dart: ">=3.2.0 <4.0.0"
flutter: ">=3.3.0"

View File

@ -17,7 +17,7 @@ dependencies:
flutter:
sdk: flutter
bluetooth_low_energy_platform_interface: ^4.0.0
bluetooth_low_energy_platform_interface: ^5.0.0
bluetooth_low_energy_linux:
# When depending on this package from a real application you should use:
# bluetooth_low_energy: ^x.y.z
@ -30,7 +30,7 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
convert: ^3.1.1
intl: ^0.18.1
intl: ^0.19.0
dev_dependencies:
integration_test:

View File

@ -1,9 +1,9 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'src/my_central_manager2.dart';
import 'src/my_central_manager.dart';
abstract class BluetoothLowEnergyLinux {
static void registerWith() {
MyCentralManager.instance = MyCentralManager2();
CentralManager.instance = MyCentralManager();
}
}

View File

@ -7,81 +7,6 @@ import 'my_gatt_characteristic2.dart';
import 'my_gatt_descriptor2.dart';
import 'my_gatt_service2.dart';
extension BlueZAdapterX on BlueZAdapter {
BluetoothLowEnergyState get myState {
return powered
? BluetoothLowEnergyState.poweredOn
: BluetoothLowEnergyState.poweredOff;
}
}
extension BlueZDeviceX 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-0000-$node");
}
UUID get myUUID => uuid.toMyUUID();
List<MyGattService2> get myServices =>
gattServices.map((service) => MyGattService2(service)).toList();
Advertisement get myAdvertisement {
final myName = name.isNotEmpty ? name : null;
final myServiceUUIDs = uuids.map((uuid) => uuid.toMyUUID()).toList();
final myServiceData = serviceData.map((uuid, data) {
final myUUID = uuid.toMyUUID();
final myData = Uint8List.fromList(data);
return MapEntry(myUUID, myData);
});
return Advertisement(
name: myName,
serviceUUIDs: myServiceUUIDs,
serviceData: myServiceData,
manufacturerSpecificData: myManufacturerSpecificData,
);
}
ManufacturerSpecificData get myManufacturerSpecificData {
final entry = manufacturerData.entries.last;
final myId = entry.key.id;
final myData = Uint8List.fromList(entry.value);
return ManufacturerSpecificData(
id: myId,
data: myData,
);
}
}
extension BlueZGattServiceX on BlueZGattService {
UUID get myUUID => uuid.toMyUUID();
List<MyGattCharacteristic2> get myCharacteristics => characteristics
.map((characteristic) => MyGattCharacteristic2(characteristic))
.toList();
}
extension MyBlueZGattCharacteristic on BlueZGattCharacteristic {
UUID get myUUID => uuid.toMyUUID();
List<GattCharacteristicProperty> get myProperties => flags
.map((e) => e.toMyProperty())
.whereType<GattCharacteristicProperty>()
.toList();
List<MyGattDescriptor2> get myDescriptors =>
descriptors.map((descriptor) => MyGattDescriptor2(descriptor)).toList();
}
extension BlueZGattDescriptorX on BlueZGattDescriptor {
UUID get myUUID => uuid.toMyUUID();
}
extension BlueZUUIDX on BlueZUUID {
UUID toMyUUID() => UUID(value);
}
extension BlueZGattCharacteristicFlagX on BlueZGattCharacteristicFlag {
GattCharacteristicProperty? toMyProperty() {
switch (this) {
@ -113,3 +38,75 @@ extension GattCharacteristicWriteTypeX on GattCharacteristicWriteType {
}
}
}
extension BlueZAdapterX on BlueZAdapter {
BluetoothLowEnergyState get myState {
return powered
? BluetoothLowEnergyState.poweredOn
: BluetoothLowEnergyState.poweredOff;
}
}
extension BlueZDeviceX on BlueZDevice {
UUID get myUUID => UUID.fromAddress(address);
List<MyGattService2> get myServices =>
gattServices.map((service) => MyGattService2(service)).toList();
Advertisement get myAdvertisement {
final myName = name.isNotEmpty ? name : null;
final myServiceUUIDs = uuids.map((uuid) => uuid.toMyUUID()).toList();
final myServiceData = serviceData.map((uuid, data) {
final myUUID = uuid.toMyUUID();
final myData = Uint8List.fromList(data);
return MapEntry(myUUID, myData);
});
return Advertisement(
name: myName,
serviceUUIDs: myServiceUUIDs,
serviceData: myServiceData,
manufacturerSpecificData: myManufacturerSpecificData,
);
}
ManufacturerSpecificData? get myManufacturerSpecificData {
final entry = manufacturerData.entries.lastOrNull;
if (entry == null) {
return null;
}
final myId = entry.key.id;
final myData = Uint8List.fromList(entry.value);
return ManufacturerSpecificData(
id: myId,
data: myData,
);
}
}
extension BlueZGattDescriptorX on BlueZGattDescriptor {
UUID get myUUID => uuid.toMyUUID();
}
extension MyBlueZGattCharacteristic on BlueZGattCharacteristic {
UUID get myUUID => uuid.toMyUUID();
List<GattCharacteristicProperty> get myProperties => flags
.map((e) => e.toMyProperty())
.whereType<GattCharacteristicProperty>()
.toList();
List<MyGattDescriptor2> get myDescriptors =>
descriptors.map((descriptor) => MyGattDescriptor2(descriptor)).toList();
}
extension BlueZGattServiceX on BlueZGattService {
UUID get myUUID => uuid.toMyUUID();
List<MyGattCharacteristic2> get myCharacteristics => characteristics
.map((characteristic) => MyGattCharacteristic2(characteristic))
.toList();
}
extension BlueZUUIDX on BlueZUUID {
UUID toMyUUID() => UUID(value);
}

View File

@ -0,0 +1,364 @@
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_event_args.dart';
import 'my_gatt_characteristic2.dart';
import 'my_gatt_descriptor2.dart';
import 'my_gatt_service2.dart';
import 'my_peripheral2.dart';
class MyCentralManager extends CentralManager {
final BlueZClient _blueZClient;
final StreamController<BluetoothLowEnergyStateChangedEventArgs>
_stateChangedController;
final StreamController<DiscoveredEventArgs> _discoveredController;
final StreamController<ConnectionStateChangedEventArgs>
_connectionStateChangedController;
final StreamController<GattCharacteristicNotifiedEventArgs>
_characteristicNotifiedController;
final StreamController<BlueZDeviceServicesResolvedEventArgs>
_blueZServicesResolvedController;
final Map<int, StreamSubscription>
_blueZCharacteristicPropertiesChangedSubscriptions;
final Map<String, List<MyGattService2>> _services;
BluetoothLowEnergyState _state;
MyCentralManager()
: _blueZClient = BlueZClient(),
_stateChangedController = StreamController.broadcast(),
_discoveredController = StreamController.broadcast(),
_connectionStateChangedController = StreamController.broadcast(),
_characteristicNotifiedController = StreamController.broadcast(),
_blueZServicesResolvedController = StreamController.broadcast(),
_blueZCharacteristicPropertiesChangedSubscriptions = {},
_services = {},
_state = BluetoothLowEnergyState.unknown;
BlueZAdapter get _blueZAdapter => _blueZClient.adapters.first;
@override
Stream<BluetoothLowEnergyStateChangedEventArgs> get stateChanged =>
_stateChangedController.stream;
@override
Stream<DiscoveredEventArgs> get discovered => _discoveredController.stream;
@override
Stream<ConnectionStateChangedEventArgs> get connectionStateChanged =>
_connectionStateChangedController.stream;
@override
Stream<GattCharacteristicNotifiedEventArgs> get characteristicNotified =>
_characteristicNotifiedController.stream;
Stream<BlueZDeviceServicesResolvedEventArgs> get _blueZServicesResolved =>
_blueZServicesResolvedController.stream;
@override
Future<void> setUp() async {
logger.info('setUp');
await _blueZClient.connect();
if (_blueZClient.adapters.isEmpty) {
_state = BluetoothLowEnergyState.unsupported;
return;
}
_state = _blueZAdapter.myState;
_blueZAdapter.propertiesChanged.listen(_onBlueZAdapterPropertiesChanged);
for (var blueZDevice in _blueZClient.devices) {
if (blueZDevice.adapter.address != _blueZAdapter.address) {
continue;
}
_beginBlueZDevicePropertiesChangedListener(blueZDevice);
}
_blueZClient.deviceAdded.listen(_onBlueZClientDeviceAdded);
}
@override
Future<BluetoothLowEnergyState> getState() {
logger.info('getState');
return Future.value(_state);
}
@override
Future<void> startDiscovery() async {
logger.info('startDiscovery');
await _blueZAdapter.startDiscovery();
}
@override
Future<void> stopDiscovery() async {
logger.info('stopDiscovery');
await _blueZAdapter.stopDiscovery();
}
@override
Future<void> connect(Peripheral peripheral) async {
if (peripheral is! MyPeripheral2) {
throw TypeError();
}
final blueZDevice = peripheral.blueZDevice;
final blueZAddress = blueZDevice.address;
logger.info('connect: $blueZAddress');
await blueZDevice.connect();
}
@override
Future<void> disconnect(Peripheral peripheral) async {
if (peripheral is! MyPeripheral2) {
throw TypeError();
}
final blueZDevice = peripheral.blueZDevice;
final blueZAddress = blueZDevice.address;
logger.info('disconnect: $blueZAddress');
await blueZDevice.disconnect();
}
@override
Future<int> readRSSI(Peripheral peripheral) async {
if (peripheral is! MyPeripheral2) {
throw TypeError();
}
final blueZDevice = peripheral.blueZDevice;
final blueZAddress = blueZDevice.address;
logger.info('readRSSI: $blueZAddress');
return blueZDevice.rssi;
}
@override
Future<List<GattService>> discoverGATT(Peripheral peripheral) async {
if (peripheral is! MyPeripheral2) {
throw TypeError();
}
final blueZDevice = peripheral.blueZDevice;
final blueZAddress = blueZDevice.address;
logger.info('discoverGATT: $blueZAddress');
if (!blueZDevice.connected) {
throw StateError('Peripheral is disconnected.');
}
if (!blueZDevice.servicesResolved) {
await _blueZServicesResolved.firstWhere(
(eventArgs) => eventArgs.device == blueZDevice,
);
}
final services = _services[blueZAddress] ?? [];
return services;
}
@override
Future<Uint8List> readCharacteristic(
GattCharacteristic characteristic,
) async {
if (characteristic is! MyGattCharacteristic2) {
throw TypeError();
}
final blueZCharacteristic = characteristic.blueZCharacteristic;
final blueZUUID = blueZCharacteristic.uuid;
logger.info('readCharacteristic: $blueZUUID');
final blueZValue = await blueZCharacteristic.readValue();
return Uint8List.fromList(blueZValue);
}
@override
Future<void> writeCharacteristic(
GattCharacteristic characteristic, {
required Uint8List value,
required GattCharacteristicWriteType type,
}) async {
if (characteristic is! MyGattCharacteristic2) {
throw TypeError();
}
final blueZCharacteristic = characteristic.blueZCharacteristic;
final blueZUUID = blueZCharacteristic.uuid;
final trimmedValue = value.trimGATT();
final blueZType = type.toBlueZWriteType();
logger.info('writeCharacteristic: $blueZUUID - $trimmedValue, $blueZType');
await blueZCharacteristic.writeValue(
trimmedValue,
type: blueZType,
);
}
@override
Future<void> setCharacteristicNotifyState(
GattCharacteristic characteristic, {
required bool state,
}) async {
if (characteristic is! MyGattCharacteristic2) {
throw TypeError();
}
final blueZCharacteristic = characteristic.blueZCharacteristic;
final blueZUUID = blueZCharacteristic.uuid;
if (state) {
logger.info('startNotify: $blueZUUID');
await blueZCharacteristic.startNotify();
} else {
logger.info('stopNotify: $blueZUUID');
await blueZCharacteristic.stopNotify();
}
}
@override
Future<Uint8List> readDescriptor(GattDescriptor descriptor) async {
if (descriptor is! MyGattDescriptor2) {
throw TypeError();
}
final blueZDescriptor = descriptor.blueZDescriptor;
final blueZUUID = blueZDescriptor.uuid;
logger.info('readDescriptor: $blueZUUID');
final blueZValue = await blueZDescriptor.readValue();
return Uint8List.fromList(blueZValue);
}
@override
Future<void> writeDescriptor(
GattDescriptor descriptor, {
required Uint8List value,
}) async {
if (descriptor is! MyGattDescriptor2) {
throw TypeError();
}
final blueZDescriptor = descriptor.blueZDescriptor;
final blueZUUID = blueZDescriptor.uuid;
final trimmedValue = value.trimGATT();
logger.info('writeDescriptor: $blueZUUID - $trimmedValue');
await blueZDescriptor.writeValue(trimmedValue);
}
void _onBlueZAdapterPropertiesChanged(List<String> blueZAdapterProperties) {
logger.info('onBlueZAdapterPropertiesChanged: $blueZAdapterProperties');
for (var blueZAdapterProperty in blueZAdapterProperties) {
switch (blueZAdapterProperty) {
case 'Powered':
final state = _blueZAdapter.myState;
if (_state == state) {
return;
}
_state = state;
final eventArgs = BluetoothLowEnergyStateChangedEventArgs(state);
_stateChangedController.add(eventArgs);
break;
default:
break;
}
}
}
void _onBlueZClientDeviceAdded(BlueZDevice blueZDevice) {
logger.info('onBlueZClientDeviceAdded: ${blueZDevice.address}');
if (blueZDevice.adapter.address != _blueZAdapter.address) {
return;
}
_onBlueZDiscovered(blueZDevice);
_beginBlueZDevicePropertiesChangedListener(blueZDevice);
}
void _onBlueZDiscovered(BlueZDevice blueZDevice) {
final peripheral = MyPeripheral2(blueZDevice);
final rssi = blueZDevice.rssi;
final advertisement = blueZDevice.myAdvertisement;
final eventArgs = DiscoveredEventArgs(
peripheral,
rssi,
advertisement,
);
_discoveredController.add(eventArgs);
}
void _beginBlueZDevicePropertiesChangedListener(BlueZDevice blueZDevice) {
blueZDevice.propertiesChanged.listen((blueZDeviceProperties) {
logger.info(
'onBlueZDevicePropertiesChanged: ${blueZDevice.address}, $blueZDeviceProperties');
for (var blueZDeviceProperty in blueZDeviceProperties) {
switch (blueZDeviceProperty) {
case 'RSSI':
_onBlueZDiscovered(blueZDevice);
break;
case 'Connected':
final peripheral = MyPeripheral2(blueZDevice);
final state = blueZDevice.connected;
final eventArgs = ConnectionStateChangedEventArgs(
peripheral,
state,
);
_connectionStateChangedController.add(eventArgs);
if (!state) {
_endBlueZCharacteristicPropertiesChangedListener(blueZDevice);
}
break;
case 'UUIDs':
break;
case 'ServicesResolved':
if (blueZDevice.servicesResolved) {
_endBlueZCharacteristicPropertiesChangedListener(blueZDevice);
_services[blueZDevice.address] = blueZDevice.myServices;
_beginBlueZCharacteristicPropertiesChangedListener(blueZDevice);
final eventArgs =
BlueZDeviceServicesResolvedEventArgs(blueZDevice);
_blueZServicesResolvedController.add(eventArgs);
}
break;
default:
break;
}
}
});
}
void _beginBlueZCharacteristicPropertiesChangedListener(
BlueZDevice blueZDevice,
) {
final services = _services[blueZDevice.address];
if (services == null) {
return;
}
for (var service in services) {
final characteristics = service.characteristics;
for (var characteristic in characteristics) {
final blueZCharacteristic = characteristic.blueZCharacteristic;
final subscription = blueZCharacteristic.propertiesChanged.listen(
(blueZCharacteristicProperties) {
logger.info(
'onBlueZCharacteristicPropertiesChanged: ${blueZDevice.address}.${blueZCharacteristic.uuid}, $blueZCharacteristicProperties');
for (var blueZCharacteristicPropety
in blueZCharacteristicProperties) {
switch (blueZCharacteristicPropety) {
case 'Value':
final value = Uint8List.fromList(blueZCharacteristic.value);
final eventArgs = GattCharacteristicNotifiedEventArgs(
characteristic,
value,
);
_characteristicNotifiedController.add(eventArgs);
break;
default:
break;
}
}
},
);
_blueZCharacteristicPropertiesChangedSubscriptions[
blueZCharacteristic.hashCode] = subscription;
}
}
}
void _endBlueZCharacteristicPropertiesChangedListener(
BlueZDevice blueZDevice,
) {
final services = _services.remove(blueZDevice.address);
if (services == null) {
return;
}
for (var service in services) {
final characteristics = service.characteristics;
for (var characteristic in characteristics) {
final blueZCharacteristic = characteristic.blueZCharacteristic;
final subscription = _blueZCharacteristicPropertiesChangedSubscriptions
.remove(blueZCharacteristic.hashCode);
subscription?.cancel();
}
}
}
}

View File

@ -1,341 +0,0 @@
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_event_args.dart';
import 'my_gatt_characteristic2.dart';
import 'my_gatt_descriptor2.dart';
import 'my_gatt_service2.dart';
import 'my_peripheral2.dart';
class MyCentralManager2 extends MyCentralManager {
MyCentralManager2()
: _client = BlueZClient(),
_stateChangedController = StreamController.broadcast(),
_discoveredController = StreamController.broadcast(),
_peripheralStateChangedController = StreamController.broadcast(),
_characteristicValueChangedController = StreamController.broadcast(),
_deviceServicesResolvedController = StreamController.broadcast(),
_myServicesOfMyPeripherals = {},
_characteristicPropertiesChangedSubscriptions = {},
_state = BluetoothLowEnergyState.unknown;
final BlueZClient _client;
final StreamController<BluetoothLowEnergyStateChangedEventArgs>
_stateChangedController;
final StreamController<DiscoveredEventArgs> _discoveredController;
final StreamController<PeripheralStateChangedEventArgs>
_peripheralStateChangedController;
final StreamController<GattCharacteristicValueChangedEventArgs>
_characteristicValueChangedController;
final StreamController<BlueZDeviceServicesResolvedEventArgs>
_deviceServicesResolvedController;
final Map<int, List<MyGattService2>> _myServicesOfMyPeripherals;
final Map<int, StreamSubscription>
_characteristicPropertiesChangedSubscriptions;
BlueZAdapter get _adapter => _client.adapters.first;
BluetoothLowEnergyState _state;
@override
BluetoothLowEnergyState get state => _state;
@override
Stream<BluetoothLowEnergyStateChangedEventArgs> get stateChanged =>
_stateChangedController.stream;
@override
Stream<DiscoveredEventArgs> get discovered => _discoveredController.stream;
@override
Stream<PeripheralStateChangedEventArgs> get peripheralStateChanged =>
_peripheralStateChangedController.stream;
@override
Stream<GattCharacteristicValueChangedEventArgs>
get characteristicValueChanged =>
_characteristicValueChangedController.stream;
Stream<BlueZDeviceServicesResolvedEventArgs> get _servicesResolved =>
_deviceServicesResolvedController.stream;
Future<void> _throwWithoutState(BluetoothLowEnergyState state) async {
if (this.state != state) {
throw StateError(
'$state is expected, but current state is ${this.state}.');
}
}
@override
Future<void> setUp() async {
// TODO: hot restart is not handled.
await _client.connect();
_state = _client.adapters.isEmpty
? BluetoothLowEnergyState.unsupported
: _adapter.myState;
if (_state == BluetoothLowEnergyState.unsupported) {
return;
}
for (var device in _client.devices) {
if (device.adapter.address != _adapter.address) {
continue;
}
_beginDevicePropertiesChangedListener(device);
}
_adapter.propertiesChanged.listen(_onAdapterPropertiesChanged);
_client.deviceAdded.listen(_onDeviceAdded);
}
@override
Future<void> startDiscovery() async {
await _throwWithoutState(BluetoothLowEnergyState.poweredOn);
await _adapter.startDiscovery();
}
@override
Future<void> stopDiscovery() async {
await _throwWithoutState(BluetoothLowEnergyState.poweredOn);
await _adapter.stopDiscovery();
}
@override
Future<void> connect(Peripheral peripheral) async {
await _throwWithoutState(BluetoothLowEnergyState.poweredOn);
final myPeripheral = peripheral as MyPeripheral2;
final device = myPeripheral.device;
await device.connect();
}
@override
Future<void> disconnect(Peripheral peripheral) async {
await _throwWithoutState(BluetoothLowEnergyState.poweredOn);
final myPeripheral = peripheral as MyPeripheral2;
final device = myPeripheral.device;
await device.disconnect();
}
@override
Future<int> getMaximumWriteLength(
Peripheral peripheral, {
required GattCharacteristicWriteType type,
}) async {
// TODO: 当前版本 `bluez` 插件不支持获取 MTU返回最小值 20.
return 20;
}
@override
Future<int> readRSSI(Peripheral peripheral) async {
await _throwWithoutState(BluetoothLowEnergyState.poweredOn);
final myPeripheral = peripheral as MyPeripheral2;
final device = myPeripheral.device;
return device.rssi;
}
@override
Future<List<GattService>> discoverGATT(Peripheral peripheral) async {
await _throwWithoutState(BluetoothLowEnergyState.poweredOn);
final myPeripheral = peripheral as MyPeripheral2;
final device = myPeripheral.device;
if (!device.connected) {
throw StateError('Peripheral is disconnected.');
}
if (!device.servicesResolved) {
await _servicesResolved.firstWhere(
(eventArgs) => eventArgs.device == device,
);
}
final myServices = _myServicesOfMyPeripherals[myPeripheral.hashCode];
if (myServices == null) {
throw ArgumentError.notNull();
}
return myServices;
}
@override
Future<Uint8List> readCharacteristic(
GattCharacteristic characteristic,
) async {
await _throwWithoutState(BluetoothLowEnergyState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic2;
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(BluetoothLowEnergyState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic2;
final blueZCharacteristic = myCharacteristic.characteristic;
await blueZCharacteristic.writeValue(
value,
type: type.toBlueZWriteType(),
);
}
@override
Future<void> notifyCharacteristic(
GattCharacteristic characteristic, {
required bool state,
}) async {
await _throwWithoutState(BluetoothLowEnergyState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic2;
final blueZCharacteristic = myCharacteristic.characteristic;
if (state) {
await blueZCharacteristic.startNotify();
} else {
await blueZCharacteristic.stopNotify();
}
}
@override
Future<Uint8List> readDescriptor(GattDescriptor descriptor) async {
await _throwWithoutState(BluetoothLowEnergyState.poweredOn);
final myDescriptor = descriptor as MyGattDescriptor2;
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(BluetoothLowEnergyState.poweredOn);
final myDescriptor = descriptor as MyGattDescriptor2;
final blueZDescriptor = myDescriptor.descriptor;
await blueZDescriptor.writeValue(value);
}
void _onAdapterPropertiesChanged(List<String> properties) {
for (var property in properties) {
switch (property) {
case 'Powered':
final myState = _adapter.myState;
if (_state == myState) {
return;
}
_state = myState;
final eventArgs = BluetoothLowEnergyStateChangedEventArgs(myState);
_stateChangedController.add(eventArgs);
break;
default:
break;
}
}
}
void _onDeviceAdded(BlueZDevice device) {
if (device.adapter.address != _adapter.address) {
return;
}
_onDiscovered(device);
_beginDevicePropertiesChangedListener(device);
}
void _onDiscovered(BlueZDevice device) {
final myPeripheral = MyPeripheral2(device);
final myRSSI = device.rssi;
final myAdvertiseData = device.myAdvertisement;
final eventArgs = DiscoveredEventArgs(
myPeripheral,
myRSSI,
myAdvertiseData,
);
_discoveredController.add(eventArgs);
}
void _beginDevicePropertiesChangedListener(BlueZDevice device) {
device.propertiesChanged.listen((properties) {
for (var property in properties) {
switch (property) {
case 'RSSI':
_onDiscovered(device);
break;
case 'Connected':
final myPeripheral = MyPeripheral2(device);
final state = device.connected;
final eventArgs = PeripheralStateChangedEventArgs(
myPeripheral,
state,
);
_peripheralStateChangedController.add(eventArgs);
if (!state) {
_endCharacteristicPropertiesChangedListener(myPeripheral);
}
break;
case 'UUIDs':
break;
case 'ServicesResolved':
if (device.servicesResolved) {
final myPeripheral = MyPeripheral2(device);
_endCharacteristicPropertiesChangedListener(myPeripheral);
final myServices = device.myServices;
_myServicesOfMyPeripherals[myPeripheral.hashCode] = myServices;
_beginCharacteristicPropertiesChangedListener(myPeripheral);
final eventArgs = BlueZDeviceServicesResolvedEventArgs(device);
_deviceServicesResolvedController.add(eventArgs);
}
break;
default:
break;
}
}
});
}
void _beginCharacteristicPropertiesChangedListener(
MyPeripheral myPeripheral,
) {
final myServices = _myServicesOfMyPeripherals[myPeripheral.hashCode];
if (myServices == null) {
throw ArgumentError.notNull();
}
for (var myService in myServices) {
final myCharacteristics =
myService.characteristics.cast<MyGattCharacteristic2>();
for (var myCharacteristic in myCharacteristics) {
final characteristic = myCharacteristic.characteristic;
final subscription = characteristic.propertiesChanged.listen(
(properties) {
for (var property in properties) {
switch (property) {
case 'Value':
final myValue = Uint8List.fromList(characteristic.value);
final eventArgs = GattCharacteristicValueChangedEventArgs(
myCharacteristic,
myValue,
);
_characteristicValueChangedController.add(eventArgs);
break;
default:
break;
}
}
},
);
_characteristicPropertiesChangedSubscriptions[
myCharacteristic.hashCode] = subscription;
}
}
}
void _endCharacteristicPropertiesChangedListener(MyPeripheral myPeripheral) {
final myServices = _myServicesOfMyPeripherals.remove(myPeripheral.hashCode);
if (myServices == null) {
return;
}
for (var myService in myServices) {
final myCharacteristics = myService.characteristics;
for (var myCharacteristic in myCharacteristics) {
final subscription = _characteristicPropertiesChangedSubscriptions
.remove(myCharacteristic.hashCode);
subscription?.cancel();
}
}
}
}

View File

@ -2,15 +2,28 @@ import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_pla
import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
import 'my_gatt_descriptor2.dart';
class MyGattCharacteristic2 extends MyGattCharacteristic {
final BlueZGattCharacteristic characteristic;
final BlueZGattCharacteristic blueZCharacteristic;
MyGattCharacteristic2(this.characteristic)
MyGattCharacteristic2(this.blueZCharacteristic)
: super(
hashCode: characteristic.hashCode,
uuid: characteristic.myUUID,
properties: characteristic.myProperties,
descriptors: characteristic.myDescriptors,
uuid: blueZCharacteristic.myUUID,
properties: blueZCharacteristic.myProperties,
descriptors: blueZCharacteristic.myDescriptors,
);
@override
List<MyGattDescriptor2> get descriptors =>
super.descriptors.cast<MyGattDescriptor2>();
@override
int get hashCode => blueZCharacteristic.hashCode;
@override
bool operator ==(Object other) {
return other is MyGattCharacteristic2 &&
other.blueZCharacteristic == blueZCharacteristic;
}
}

View File

@ -4,11 +4,19 @@ import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
class MyGattDescriptor2 extends MyGattDescriptor {
final BlueZGattDescriptor descriptor;
final BlueZGattDescriptor blueZDescriptor;
MyGattDescriptor2(this.descriptor)
MyGattDescriptor2(this.blueZDescriptor)
: super(
hashCode: descriptor.hashCode,
uuid: descriptor.myUUID,
uuid: blueZDescriptor.myUUID,
);
@override
int get hashCode => blueZDescriptor.hashCode;
@override
bool operator ==(Object other) {
return other is MyGattDescriptor2 &&
other.blueZDescriptor == blueZDescriptor;
}
}

View File

@ -2,14 +2,26 @@ import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_pla
import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
import 'my_gatt_characteristic2.dart';
class MyGattService2 extends MyGattService {
final BlueZGattService service;
final BlueZGattService blueZService;
MyGattService2(this.service)
MyGattService2(this.blueZService)
: super(
hashCode: service.hashCode,
uuid: service.myUUID,
characteristics: service.myCharacteristics,
uuid: blueZService.myUUID,
characteristics: blueZService.myCharacteristics,
);
@override
List<MyGattCharacteristic2> get characteristics =>
super.characteristics.cast<MyGattCharacteristic2>();
@override
int get hashCode => blueZService.hashCode;
@override
bool operator ==(Object other) {
return other is MyGattService2 && other.blueZService == blueZService;
}
}

View File

@ -4,11 +4,10 @@ import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
class MyPeripheral2 extends MyPeripheral {
final BlueZDevice device;
final BlueZDevice blueZDevice;
MyPeripheral2(this.device)
MyPeripheral2(this.blueZDevice)
: super(
hashCode: device.hashCode,
uuid: device.myUUID,
uuid: blueZDevice.myUUID,
);
}

View File

@ -1,6 +1,6 @@
name: bluetooth_low_energy_linux
description: Linux implementation of the bluetooth_low_energy plugin.
version: 4.0.0
version: 5.0.0
homepage: https://github.com/yanshouwang/bluetooth_low_energy
environment:
@ -10,7 +10,7 @@ environment:
dependencies:
flutter:
sdk: flutter
bluetooth_low_energy_platform_interface: ^4.0.0
bluetooth_low_energy_platform_interface: ^5.0.0
bluez: ^0.8.1
dev_dependencies: