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:
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
364
bluetooth_low_energy_linux/lib/src/my_central_manager.dart
Normal file
364
bluetooth_low_energy_linux/lib/src/my_central_manager.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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:
|
||||
|
Reference in New Issue
Block a user