feat: 支持外围设备接口,优化中心设备接口 (#18)

* 临时提交

* 临时提交

* 临时提交

* fix: 调整接口

* fix: 修复问题

* fix: 调整 iOS 实现

* fix: 添加注释

* fix: 修改预览版本号

* fix: 修复已知问题

* fix: 优化接口

* fix: 解决 32 位 UUID 报错问题

* fix: 修复问题

* fix: 修复依赖项

* fix: 移除多余代码

* fix: 修复已知问题

* fix: 修复问题

* fix: 修改版本号

* fix: 修复问题

* fix: 发布正式版本
This commit is contained in:
Mr剑侠客
2023-10-10 05:01:25 -05:00
committed by GitHub
parent 073c2b9a2e
commit 79c50d638d
113 changed files with 9125 additions and 4560 deletions

View File

@ -1,3 +1,42 @@
## 3.0.0
* Add `PeripheralManager` api.
* Add `CentralManager#readRSSI` method.
* Add `CentralManager.instance` api.
* Add `PeripheralManager.instance` api.
* Move `CentralController` to `CentralManager`.
* Move `CentralState` to `BluetoothLowEnergyState`.
* Move `CentralDiscoveredEventArgs` to `DiscoveredEventArgs`.
* Move `Advertisement` class to `AdvertiseData` class.
* Move `setUp` method from `BluetoothLowEnergy` class to `BluetoothLowEnergyManger` class.
* Change the type of `manufacturerSpecificData` from `Map<int, Uint8List>` to `ManufacturerSpecificData`.
* [Fix the issue that `UUID.fromString()` throw FormatException with 32 bits UUID string.](https://github.com/yanshouwang/bluetooth_low_energy/issues/13)
* Fix known issues.
## 3.0.0-dev.4
* Move `Advertisement` class to `AdvertiseData` class.
* Fix known issues.
## 3.0.0-dev.3
* [Fix the issue that `UUID.fromString()` throw FormatException with 32 bits UUID string.](https://github.com/yanshouwang/bluetooth_low_energy/issues/13)
* Change the type of `manufacturerSpecificData` from `Map<int, Uint8List>` to `ManufacturerSpecificData`.
## 3.0.0-dev.2
* Move `setUp` method from `BluetoothLowEnergy` class to `BluetoothLowEnergyManger` class.
* Add `CentralManager.instance` api.
* Add `PeripheralManager.instance` api.
## 3.0.0-dev.1
* Add `PeripheralManager` api.
* Add `CentralManager#readRSSI` method.
* Move `CentralController` to `CentralManager`.
* Move `CentralState` to `BluetoothLowEnergyState`.
* Move `CentralDiscoveredEventArgs` to `DiscoveredEventArgs`.
## 2.2.1
* `Android` Fix the issue that `CentralController#getMaximumWriteLength` may throw.

View File

@ -4,12 +4,14 @@ A Flutter plugin for controlling the bluetooth low energy.
## Features
### CentralController
### CentralManager
- [x] SetUp/TearDown central controller.
- [x] Get/Listen central state.
- [x] Set up the central manager.
- [x] Get/Listen the state of the central manager.
- [x] Start/Stop discovery.
- [x] Connect/Disconnect peripherals.
- [x] Get maximum write length of peripherals.
- [x] Read RSSI of peripherals.
- [x] Discover GATT.
- [x] Get GATT services.
- [x] Get GATT characteristics.
@ -17,6 +19,17 @@ A Flutter plugin for controlling the bluetooth low energy.
- [x] Read/Write/Notify GATT characteristics.
- [x] Read/Write GATT descriptors.
### PeripheralManager
- [x] Set up the peripheral manager.
- [x] Get/Listen the state of the peripheral manager.
- [x] Add/Remove/Clear service(s).
- [x] Start/Stop advertising.
- [x] Get maximum write length of centrals.
- [x] Listen read/write/notify characteristic requests from centrals.
- [x] Send read/write characteristic replies to centrals.
- [x] Notify characteristic value changed to centrals.
## Getting Started
Add `bluetooth_low_energy` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/).
@ -28,20 +41,26 @@ dependencies:
Remember to call `await CentralController.setUp()` before use any apis of this plugin.
*Note*: Bluetooth Low Energy doesn't work on emulators, so use physical devices which has bluetooth features for development.
*Note:* Bluetooth Low Energy doesn't work on emulators, so use physical devices which has bluetooth features for development.
### Android
Make sure you have a `miniSdkVersion` with 21 or higher in your `android/app/build.gradle` file.
*Note:* Don't call `getMaximumWriteLength` immediately when connected to a peripheral after Android 13, the `onMtuChanged` callback maybe called with connection events after Android 13, and `getMaximumWriteLength` will call `requestMtu` will also triggered `onMtuChanged`, if you called this before the connection `onMtuChanged`, you will get a fake completion and cause all methods you called before the real `onMtuChanged` triggered will never complete!
### iOS and macOS
According to Apple's [documents](https://developer.apple.com/documentation/corebluetooth/), you must include the [`NSBluetoothAlwaysUsageDescription`](https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothalwaysusagedescription) on or after iOS 13, and include the [`NSBluetoothPeripheralUsageDescription`](https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothperipheralusagedescription) key before iOS 13.
The `PeripheralManager#startAdvertising` only support `name` and `serviceUUIDs`, see [the startAdvertising document](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/1393252-startadvertising)
### Linux
Not tested enough, if you occured any problems, file an issue to let me know about it, i will fix it as soon as possible.
PeripheralManager api is not supported because the `bluez` plugin doesn't support this yet, see [How to use bluez to act as bluetooth peripheral](https://github.com/canonical/bluez.dart/issues/85)
### Windows
Not implemented yet but maybe someday or someone can use the `win32` api to implement this plugin_interface or someday the flutter team support C# on windows platform or someday I am familiar with C++ language...

View File

@ -14,6 +14,9 @@ void main() {
}
void onStartUp() async {
WidgetsFlutterBinding.ensureInitialized();
await CentralManager.instance.setUp();
await PeripheralManager.instance.setUp();
runApp(const MyApp());
}
@ -48,8 +51,7 @@ class _MyAppState extends State<MyApp> {
routes: {
'peripheral': (context) {
final route = ModalRoute.of(context);
final eventArgs =
route!.settings.arguments as CentralDiscoveredEventArgs;
final eventArgs = route!.settings.arguments as DiscoveredEventArgs;
return PeripheralView(
eventArgs: eventArgs,
);
@ -67,26 +69,104 @@ class HomeView extends StatefulWidget {
}
class _HomeViewState extends State<HomeView> {
CentralController get centralController => CentralController.instance;
late final ValueNotifier<CentralState> state;
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();
}
}
CentralManager get centralManager => CentralManager.instance;
class ScannerView extends StatefulWidget {
const ScannerView({super.key});
@override
State<ScannerView> createState() => _ScannerViewState();
}
class _ScannerViewState extends State<ScannerView> {
late final ValueNotifier<BluetoothLowEnergyState> state;
late final ValueNotifier<bool> discovering;
late final ValueNotifier<List<CentralDiscoveredEventArgs>>
discoveredEventArgs;
late final ValueNotifier<List<DiscoveredEventArgs>> discoveredEventArgs;
late final StreamSubscription stateChangedSubscription;
late final StreamSubscription discoveredSubscription;
@override
void initState() {
super.initState();
state = ValueNotifier(centralController.state);
state = ValueNotifier(centralManager.state);
discovering = ValueNotifier(false);
discoveredEventArgs = ValueNotifier([]);
stateChangedSubscription = centralController.stateChanged.listen(
stateChangedSubscription = centralManager.stateChanged.listen(
(eventArgs) {
state.value = eventArgs.state;
},
);
discoveredSubscription = centralController.discovered.listen(
discoveredSubscription = centralManager.discovered.listen(
(eventArgs) {
final items = discoveredEventArgs.value;
final i = items.indexWhere(
@ -100,22 +180,6 @@ class _HomeViewState extends State<HomeView> {
}
},
);
setUp();
}
void setUp() async {
await centralController.setUp();
state.value = centralController.state;
}
Future<void> startDiscovery() async {
await centralController.startDiscovery();
discovering.value = true;
}
Future<void> stopDiscovery() async {
await centralController.stopDiscovery();
discovering.value = false;
}
@override
@ -128,7 +192,7 @@ class _HomeViewState extends State<HomeView> {
PreferredSizeWidget buildAppBar(BuildContext context) {
return AppBar(
title: const Text('Bluetooth LowEnergy'),
title: const Text('Scanner'),
actions: [
ValueListenableBuilder(
valueListenable: state,
@ -137,7 +201,7 @@ class _HomeViewState extends State<HomeView> {
valueListenable: discovering,
builder: (context, discovering, child) {
return TextButton(
onPressed: state == CentralState.poweredOn
onPressed: state == BluetoothLowEnergyState.poweredOn
? () async {
if (discovering) {
await stopDiscovery();
@ -158,13 +222,23 @@ class _HomeViewState extends State<HomeView> {
);
}
Future<void> startDiscovery() async {
await centralManager.startDiscovery();
discovering.value = true;
}
Future<void> stopDiscovery() async {
await centralManager.stopDiscovery();
discovering.value = false;
}
Widget buildBody(BuildContext context) {
return ValueListenableBuilder(
valueListenable: discoveredEventArgs,
builder: (context, discoveredEventArgs, child) {
// final items = discoveredEventArgs;
final items = discoveredEventArgs
.where((eventArgs) => eventArgs.advertisement.name != null)
.where((eventArgs) => eventArgs.advertiseData.name != null)
.toList();
return ListView.separated(
itemBuilder: (context, i) {
@ -172,8 +246,8 @@ class _HomeViewState extends State<HomeView> {
final item = items[i];
final uuid = item.peripheral.uuid;
final rssi = item.rssi;
final advertisement = item.advertisement;
final name = advertisement.name;
final advertiseData = item.advertiseData;
final name = advertiseData.name;
return ListTile(
onTap: () async {
final discovering = this.discovering.value;
@ -200,7 +274,7 @@ class _HomeViewState extends State<HomeView> {
clipBehavior: Clip.antiAlias,
builder: (context) {
final manufacturerSpecificData =
advertisement.manufacturerSpecificData;
advertiseData.manufacturerSpecificData;
return ListView.separated(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
@ -221,11 +295,10 @@ class _HomeViewState extends State<HomeView> {
],
);
} else {
final entry = manufacturerSpecificData.entries
.elementAt(i - 1);
final id =
'0x${entry.key.toRadixString(16).padLeft(4, '0')}';
final value = hex.encode(entry.value);
'0x${manufacturerSpecificData!.id.toRadixString(16).padLeft(4, '0')}';
final value =
hex.encode(manufacturerSpecificData.data);
return Row(
children: [
SizedBox(
@ -242,7 +315,7 @@ class _HomeViewState extends State<HomeView> {
separatorBuilder: (context, i) {
return const Divider();
},
itemCount: manufacturerSpecificData.length + 1,
itemCount: manufacturerSpecificData == null ? 1 : 2,
);
},
);
@ -274,7 +347,6 @@ class _HomeViewState extends State<HomeView> {
@override
void dispose() {
super.dispose();
centralController.tearDown().ignore();
stateChangedSubscription.cancel();
discoveredSubscription.cancel();
state.dispose();
@ -284,7 +356,7 @@ class _HomeViewState extends State<HomeView> {
}
class PeripheralView extends StatefulWidget {
final CentralDiscoveredEventArgs eventArgs;
final DiscoveredEventArgs eventArgs;
const PeripheralView({
super.key,
@ -296,9 +368,8 @@ class PeripheralView extends StatefulWidget {
}
class _PeripheralViewState extends State<PeripheralView> {
CentralController get centralController => CentralController.instance;
late final ValueNotifier<bool> state;
late final CentralDiscoveredEventArgs eventArgs;
late final DiscoveredEventArgs eventArgs;
late final ValueNotifier<List<GattService>> services;
late final ValueNotifier<List<GattCharacteristic>> characteristics;
late final ValueNotifier<GattService?> service;
@ -323,7 +394,7 @@ class _PeripheralViewState extends State<PeripheralView> {
maximumWriteLength = ValueNotifier(20);
logs = ValueNotifier([]);
writeController = TextEditingController();
stateChangedSubscription = centralController.peripheralStateChanged.listen(
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
(eventArgs) {
if (eventArgs.peripheral != this.eventArgs.peripheral) {
return;
@ -339,8 +410,7 @@ class _PeripheralViewState extends State<PeripheralView> {
}
},
);
valueChangedSubscription =
centralController.characteristicValueChanged.listen(
valueChangedSubscription = centralManager.characteristicValueChanged.listen(
(eventArgs) {
final characteristic = this.characteristic.value;
if (eventArgs.characteristic != characteristic) {
@ -362,7 +432,7 @@ class _PeripheralViewState extends State<PeripheralView> {
onWillPop: () async {
if (state.value) {
final peripheral = eventArgs.peripheral;
await centralController.disconnect(peripheral);
await centralManager.disconnect(peripheral);
}
return true;
},
@ -374,7 +444,7 @@ class _PeripheralViewState extends State<PeripheralView> {
}
PreferredSizeWidget buildAppBar(BuildContext context) {
final title = eventArgs.advertisement.name ?? '<EMPTY NAME>';
final title = eventArgs.advertiseData.name ?? '<EMPTY NAME>';
return AppBar(
title: Text(title),
actions: [
@ -385,12 +455,11 @@ class _PeripheralViewState extends State<PeripheralView> {
onPressed: () async {
final peripheral = eventArgs.peripheral;
if (state) {
await centralController.disconnect(peripheral);
await centralManager.disconnect(peripheral);
} else {
await centralController.connect(peripheral);
await centralController.discoverGATT(peripheral);
await centralManager.connect(peripheral);
services.value =
await centralController.getServices(peripheral);
await centralManager.discoverGATT(peripheral);
}
},
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
@ -436,8 +505,7 @@ class _PeripheralViewState extends State<PeripheralView> {
if (service == null) {
return;
}
characteristics.value =
await centralController.getCharacteristics(service);
characteristics.value = service.characteristics;
},
);
},
@ -563,7 +631,7 @@ class _PeripheralViewState extends State<PeripheralView> {
onPressed: state
? () async {
maximumWriteLength.value =
await centralController.getMaximumWriteLength(
await centralManager.getMaximumWriteLength(
eventArgs.peripheral,
type: writeType.value,
);
@ -579,6 +647,14 @@ class _PeripheralViewState extends State<PeripheralView> {
},
),
),
IconButton(
onPressed: () async {
final rssi =
await centralManager.readRSSI(eventArgs.peripheral);
print('RSSI: $rssi');
},
icon: const Icon(Icons.signal_wifi_4_bar),
),
],
),
Container(
@ -627,7 +703,7 @@ class _PeripheralViewState extends State<PeripheralView> {
TextButton(
onPressed: characteristic != null && canNotify
? () async {
await centralController.notifyCharacteristic(
await centralManager.notifyCharacteristic(
characteristic,
state: true,
);
@ -638,7 +714,7 @@ class _PeripheralViewState extends State<PeripheralView> {
TextButton(
onPressed: characteristic != null && canRead
? () async {
final value = await centralController
final value = await centralManager
.readCharacteristic(characteristic);
const type = LogType.read;
final log = Log(type, value);
@ -653,7 +729,7 @@ class _PeripheralViewState extends State<PeripheralView> {
final text = writeController.text;
final elements = utf8.encode(text);
final value = Uint8List.fromList(elements);
await centralController.writeCharacteristic(
await centralManager.writeCharacteristic(
characteristic,
value: value,
type: GattCharacteristicWriteType
@ -694,12 +770,286 @@ class _PeripheralViewState extends State<PeripheralView> {
}
}
PeripheralManager get peripheralManager => PeripheralManager.instance;
class AdvertiserView extends StatefulWidget {
const AdvertiserView({super.key});
@override
State<AdvertiserView> createState() => _AdvertiserViewState();
}
class _AdvertiserViewState extends State<AdvertiserView> {
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,
id,
offset,
status,
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,
id,
offset,
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,
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 advertiseData = AdvertiseData(
name: 'flutter',
manufacturerSpecificData: ManufacturerSpecificData(
id: 0x2e19,
data: Uint8List.fromList([0x01, 0x02, 0x03]),
),
);
await peripheralManager.startAdvertising(advertiseData);
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;
final Uint8List value;
final String? detail;
Log(this.type, this.value) : time = DateTime.now();
Log(
this.type,
this.value, [
this.detail,
]) : time = DateTime.now();
@override
String toString() {
@ -707,7 +1057,11 @@ class Log {
final formatter = DateFormat.Hms();
final time = formatter.format(this.time);
final message = hex.encode(value);
return '[$type]$time: $message';
if (detail == null) {
return '[$type]$time: $message';
} else {
return '[$type]$time: $message /* $detail */';
}
}
}

View File

@ -23,47 +23,39 @@ packages:
path: ".."
relative: true
source: path
version: "2.2.1"
version: "3.0.0"
bluetooth_low_energy_android:
dependency: transitive
description:
name: bluetooth_low_energy_android
sha256: "2098167f30a05cff40313fd49c459d326590b54dc6ea953cbe7ff145ce940e04"
sha256: "227468652811e769d7ff8836aa5c3f7bd0455246785c3eaf0fab2b3c31daed16"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "3.0.0"
bluetooth_low_energy_darwin:
dependency: transitive
description:
name: bluetooth_low_energy_darwin
sha256: "876f2d4a288739091296bc22ea574e64451f592154e4201b5999f174120a8b55"
sha256: f19903e1ad14da6a16de57996ca808064cb690add8b0aecc6ffecdf8da6f7eab
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "3.0.0"
bluetooth_low_energy_linux:
dependency: transitive
description:
name: bluetooth_low_energy_linux
sha256: dc3062991e0a408941829326f0a3b2e1244a8138dca7f6c4c6beadbc26deecb2
sha256: "31a23704a4b34e7f6cea61d94c24b3e1c6698928645aaeaf1af947e4870996d7"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "3.0.0"
bluetooth_low_energy_platform_interface:
dependency: transitive
description:
name: bluetooth_low_energy_platform_interface
sha256: eecaa2c37ebd536339c292d5566cf4360c9ea3861188342791eb3a0cf65cc64c
sha256: "200e686247808591b6d3e355642ba296f0f651466c72efdd701be34116971473"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
bluetooth_low_energy_windows:
dependency: transitive
description:
name: bluetooth_low_energy_windows
sha256: a1b5d5d5ad935f15618e6a86334c1ec682c40ffbe6a78173c3f19b67ff0edcfb
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "3.0.0"
bluez:
dependency: transitive
description:
@ -366,14 +358,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.2"
win32:
dependency: transitive
description:
name: win32
sha256: "9e82a402b7f3d518fb9c02d0e9ae45952df31b9bf34d77baf19da2de03fc2aaa"
url: "https://pub.dev"
source: hosted
version: "5.0.7"
xml:
dependency: transitive
description:

View File

@ -1 +1,8 @@
export 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
export 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart'
hide
MyObject,
MyCentral,
MyPeripheral,
MyGattService,
MyGattCharacteristic,
MyGattDescriptor;

View File

@ -1,20 +1,19 @@
name: bluetooth_low_energy
description: A Flutter plugin for controlling the bluetooth low energy.
version: 2.2.1
version: 3.0.0
homepage: https://github.com/yanshouwang/bluetooth_low_energy
environment:
sdk: '>=3.0.0 <4.0.0'
sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.3.0"
dependencies:
flutter:
sdk: flutter
bluetooth_low_energy_platform_interface: ^2.2.0
bluetooth_low_energy_android: ^2.2.1
bluetooth_low_energy_darwin: ^2.2.0
bluetooth_low_energy_linux: ^2.2.0
bluetooth_low_energy_windows: ^2.2.0
bluetooth_low_energy_platform_interface: ^3.0.0
bluetooth_low_energy_android: ^3.0.0
bluetooth_low_energy_darwin: ^3.0.0
bluetooth_low_energy_linux: ^3.0.0
dev_dependencies:
flutter_test:
@ -32,5 +31,3 @@ flutter:
default_package: bluetooth_low_energy_darwin
linux:
default_package: bluetooth_low_energy_linux
windows:
default_package: bluetooth_low_energy_windows

View File

@ -1,3 +1,48 @@
## 3.0.0
* Add `PeripheralManager` api.
* Add `CentralManager#readRSSI` method.
* Add `CentralManager.instance` api.
* Add `PeripheralManager.instance` api.
* Move `CentralController` to `CentralManager`.
* Move `CentralState` to `BluetoothLowEnergyState`.
* Move `CentralDiscoveredEventArgs` to `DiscoveredEventArgs`.
* Move `Advertisement` class to `AdvertiseData` class.
* Move `setUp` method from `BluetoothLowEnergy` class to `BluetoothLowEnergyManger` class.
* Change the type of `manufacturerSpecificData` from `Map<int, Uint8List>` to `ManufacturerSpecificData`.
* [Fix the issue that `UUID.fromString()` throw FormatException with 32 bits UUID string.](https://github.com/yanshouwang/bluetooth_low_energy/issues/13)
* Fix known issues.
## 3.0.0-dev.6
* Add default `CCCD` to GATT characteristic for notify and indicate.
* Fix the issue that callbacks must run on ui thread.
* Change requested MTU from 512 to 517 when get the maximum write length of characteristic.
## 3.0.0-dev.5
* Fix the issue that the `BLUETOOTH_ADVERTISE` permission is not requested.
## 3.0.0-dev.4
* Move `Advertisement` class to `AdvertiseData` class.
* Fix known issues.
## 3.0.0-dev.3
* [Fix the issue that `UUID.fromString()` throw FormatException with 32 bits UUID string.](https://github.com/yanshouwang/bluetooth_low_energy/issues/13)
* Change the type of `manufacturerSpecificData` from `Map<int, Uint8List>` to `ManufacturerSpecificData`.
## 3.0.0-dev.2
* Move `setUp` method from `BluetoothLowEnergy` class to `BluetoothLowEnergyManger` class.
* Add `CentralManager.instance` api.
* Add `PeripheralManager.instance` api.
## 3.0.0-dev.1
* Implement new api.
## 2.2.1
* Fix the issue that `CentralController#getMaximumWriteLength` may throw.

View File

@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest>

View File

@ -6,33 +6,39 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
/** BluetoothLowEnergyAndroid */
class BluetoothLowEnergyAndroid : FlutterPlugin, ActivityAware {
private lateinit var centralController: MyCentralController
private lateinit var centralManager: MyCentralManager
private lateinit var peripheralManager: MyPeripheralManager
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
val context = binding.applicationContext
val binaryMessenger = binding.binaryMessenger
centralController = MyCentralController(context, binaryMessenger)
MyCentralControllerHostApi.setUp(binaryMessenger, centralController)
centralManager = MyCentralManager(context, binaryMessenger)
peripheralManager = MyPeripheralManager(context, binaryMessenger)
MyCentralManagerHostApi.setUp(binaryMessenger, centralManager)
MyPeripheralManagerHostApi.setUp(binaryMessenger, peripheralManager)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
val binaryMessenger = binding.binaryMessenger
MyCentralControllerHostApi.setUp(binaryMessenger, null)
MyCentralManagerHostApi.setUp(binaryMessenger, null)
MyPeripheralManagerHostApi.setUp(binaryMessenger, null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
centralController.onAttachedToActivity(binding)
}
override fun onDetachedFromActivityForConfigChanges() {
centralController.onDetachedFromActivity()
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
centralController.onAttachedToActivity(binding)
centralManager.onAttachedToActivity(binding)
peripheralManager.onAttachedToActivity(binding)
}
override fun onDetachedFromActivity() {
centralController.onDetachedFromActivity()
centralManager.onDetachedFromActivity()
peripheralManager.onDetachedFromActivity()
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
onAttachedToActivity(binding)
}
override fun onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity()
}
}

View File

@ -0,0 +1,16 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseSettings
class MyAdvertiseCallback(private val peripheralManager: MyPeripheralManager) : AdvertiseCallback() {
override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
super.onStartSuccess(settingsInEffect)
peripheralManager.onStartSuccess(settingsInEffect)
}
override fun onStartFailure(errorCode: Int) {
super.onStartFailure(errorCode)
peripheralManager.onStartFailure(errorCode)
}
}

View File

@ -0,0 +1,233 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.ScanRecord
import android.bluetooth.le.ScanResult
import android.os.ParcelUuid
import android.util.SparseArray
import java.util.UUID
val Any.TAG get() = this::class.java.simpleName as String
val BluetoothAdapter.stateArgs: MyBluetoothLowEnergyStateArgs
get() = state.toBluetoothLowEnergyStateArgs()
fun Int.toBluetoothLowEnergyStateArgs(): MyBluetoothLowEnergyStateArgs {
return when (this) {
BluetoothAdapter.STATE_ON -> MyBluetoothLowEnergyStateArgs.POWEREDON
else -> MyBluetoothLowEnergyStateArgs.POWEREDOFF
}
}
fun BluetoothDevice.toPeripheralArgs(): MyPeripheralArgs {
val hashCodeArgs = hashCode().toLong()
val uuid = this.uuid.toString()
return MyPeripheralArgs(hashCodeArgs, uuid)
}
fun BluetoothDevice.toCentralArgs(): MyCentralArgs {
val hashCodeArgs = hashCode().toLong()
val uuid = this.uuid.toString()
return MyCentralArgs(hashCodeArgs, uuid)
}
val BluetoothDevice.uuid: UUID
get() {
val node = address.filter { char -> char != ':' }
// We don't know the timestamp of the bluetooth device, use nil UUID as prefix.
return UUID.fromString("00000000-0000-0000-0000-$node")
}
val ScanResult.advertiseDataArgs: MyAdvertiseDataArgs
get() {
val record = scanRecord
return if (record == null) {
val nameArgs = null
val serviceUUIDsArgs = emptyList<String?>()
val serviceDataArgs = emptyMap<String?, ByteArray>()
val manufacturerSpecificDataArgs = null
MyAdvertiseDataArgs(nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs)
} else {
val nameArgs = record.deviceName
val serviceUUIDsArgs = record.serviceUuids?.map { uuid -> uuid.toString() }
?: emptyList()
val pairs = record.serviceData.map { (uuid, value) ->
val key = uuid.toString()
return@map Pair(key, value)
}.toTypedArray()
val serviceDataArgs = mapOf<String?, ByteArray?>(*pairs)
val manufacturerSpecificDataArgs = record.manufacturerSpecificData.toManufacturerSpecificDataArgs()
MyAdvertiseDataArgs(nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs)
}
}
fun SparseArray<ByteArray>.toManufacturerSpecificDataArgs(): MyManufacturerSpecificDataArgs? {
var index = 0
val size = size()
val itemsArgs = mutableListOf<MyManufacturerSpecificDataArgs>()
while (index < size) {
val idArgs = keyAt(index).toLong()
val dataArgs = valueAt(index)
val itemArgs = MyManufacturerSpecificDataArgs(idArgs, dataArgs)
itemsArgs.add(itemArgs)
index++
}
return itemsArgs.lastOrNull()
}
val ScanRecord.rawValues: Map<Byte, ByteArray>
get() {
val rawValues = mutableMapOf<Byte, ByteArray>()
var index = 0
val size = bytes.size
while (index < size) {
val length = bytes[index++].toInt() and 0xff
if (length == 0) {
break
}
val end = index + length
val type = bytes[index++]
val value = bytes.slice(index until end).toByteArray()
rawValues[type] = value
index = end
}
return rawValues.toMap()
}
fun MyAdvertiseDataArgs.toAdvertiseData(adapter: BluetoothAdapter): AdvertiseData {
val advertiseDataBuilder = AdvertiseData.Builder()
if (nameArgs == null) {
advertiseDataBuilder.setIncludeDeviceName(false)
} else {
adapter.name = nameArgs
advertiseDataBuilder.setIncludeDeviceName(true)
}
for (serviceUuidArgs in serviceUUIDsArgs) {
val serviceUUID = ParcelUuid.fromString(serviceUuidArgs)
advertiseDataBuilder.addServiceUuid(serviceUUID)
}
for (entry in serviceDataArgs) {
val serviceDataUUID = ParcelUuid.fromString(entry.key as String)
val serviceData = entry.value as ByteArray
advertiseDataBuilder.addServiceData(serviceDataUUID, serviceData)
}
if (manufacturerSpecificDataArgs != null) {
val manufacturerId = manufacturerSpecificDataArgs.idArgs.toInt()
val manufacturerSpecificData = manufacturerSpecificDataArgs.dataArgs
advertiseDataBuilder.addManufacturerData(manufacturerId, manufacturerSpecificData)
}
return advertiseDataBuilder.build()
}
fun BluetoothGattService.toManufacturerSpecificDataArgs(characteristicsArgs: List<MyGattCharacteristicArgs>): MyGattServiceArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
return MyGattServiceArgs(hashCodeArgs, uuidArgs, characteristicsArgs)
}
fun BluetoothGattCharacteristic.toManufacturerSpecificDataArgs(descriptorsArgs: List<MyGattDescriptorArgs>): MyGattCharacteristicArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
return MyGattCharacteristicArgs(hashCodeArgs, uuidArgs, propertyNumbersArgs, descriptorsArgs)
}
val BluetoothGattCharacteristic.propertyNumbersArgs: List<Long>
get() {
val numbersArgs = mutableListOf<Long>()
if (properties and BluetoothGattCharacteristic.PROPERTY_READ != 0) {
val number = MyGattCharacteristicPropertyArgs.READ.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) {
val number = MyGattCharacteristicPropertyArgs.WRITE.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) {
val number = MyGattCharacteristicPropertyArgs.WRITEWITHOUTRESPONSE.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {
val number = MyGattCharacteristicPropertyArgs.NOTIFY.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) {
val number = MyGattCharacteristicPropertyArgs.INDICATE.raw.toLong()
numbersArgs.add(number)
}
return numbersArgs
}
fun BluetoothGattDescriptor.toManufacturerSpecificDataArgs(): MyGattDescriptorArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
return MyGattDescriptorArgs(hashCodeArgs, uuidArgs, null)
}
fun MyGattServiceArgs.toService(): BluetoothGattService {
val uuid = UUID.fromString(uuidArgs)
val serviceType = BluetoothGattService.SERVICE_TYPE_PRIMARY
return BluetoothGattService(uuid, serviceType)
}
fun MyGattCharacteristicArgs.toCharacteristic(): BluetoothGattCharacteristic {
val uuid = UUID.fromString(uuidArgs)
return BluetoothGattCharacteristic(uuid, properties, permissions)
}
val MyGattCharacteristicArgs.properties: Int
get() {
val propertiesArgs = propertyNumbersArgs.filterNotNull().map { args ->
val raw = args.toInt()
MyGattCharacteristicPropertyArgs.ofRaw(raw)
}
val read = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.READ)
val write = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITE)
val writeWithoutResponse = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITEWITHOUTRESPONSE)
val notify = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.NOTIFY)
val indicate = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.INDICATE)
var properties = 0
if (read) properties = properties or BluetoothGattCharacteristic.PROPERTY_READ
if (write) properties = properties or BluetoothGattCharacteristic.PROPERTY_WRITE
if (writeWithoutResponse) properties = properties or BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE
if (notify) properties = properties or BluetoothGattCharacteristic.PROPERTY_NOTIFY
if (indicate) properties = properties or BluetoothGattCharacteristic.PROPERTY_INDICATE
return properties
}
val MyGattCharacteristicArgs.permissions: Int
get() {
val propertiesArgs = propertyNumbersArgs.filterNotNull().map { args ->
val raw = args.toInt()
MyGattCharacteristicPropertyArgs.ofRaw(raw)
}
val read = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.READ)
val write = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITE)
val writeWithoutResponse = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITEWITHOUTRESPONSE)
var permissions = 0
if (read) permissions = permissions or BluetoothGattCharacteristic.PERMISSION_READ
if (write || writeWithoutResponse) permissions = permissions or BluetoothGattCharacteristic.PERMISSION_WRITE
return permissions
}
fun MyGattDescriptorArgs.toDescriptor(): BluetoothGattDescriptor {
val uuid = UUID.fromString(uuidArgs)
val permissions = BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE
return BluetoothGattDescriptor(uuid, permissions)
}
fun Long.toWriteTypeArgs(): MyGattCharacteristicWriteTypeArgs {
val raw = toInt()
return MyGattCharacteristicWriteTypeArgs.ofRaw(raw) ?: throw IllegalArgumentException()
}
fun MyGattCharacteristicWriteTypeArgs.toType(): Int {
return when (this) {
MyGattCharacteristicWriteTypeArgs.WITHRESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
MyGattCharacteristicWriteTypeArgs.WITHOUTRESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
}
}

View File

@ -7,32 +7,39 @@ import android.bluetooth.BluetoothGattDescriptor
import android.os.Build
import java.util.concurrent.Executor
class MyBluetoothGattCallback(private val myCentralController: MyCentralController, private val executor: Executor) : BluetoothGattCallback() {
class MyBluetoothGattCallback(private val centralManager: MyCentralManager, private val executor: Executor) : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
super.onConnectionStateChange(gatt, status, newState)
executor.execute {
myCentralController.onConnectionStateChange(gatt, status, newState)
centralManager.onConnectionStateChange(gatt, status, newState)
}
}
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
super.onMtuChanged(gatt, mtu, status)
executor.execute {
myCentralController.onMtuChanged(gatt, mtu, status)
centralManager.onMtuChanged(gatt, mtu, status)
}
}
override fun onReadRemoteRssi(gatt: BluetoothGatt, rssi: Int, status: Int) {
super.onReadRemoteRssi(gatt, rssi, status)
executor.execute {
centralManager.onReadRemoteRssi(gatt, rssi, status)
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
super.onServicesDiscovered(gatt, status)
executor.execute {
myCentralController.onServicesDiscovered(gatt, status)
centralManager.onServicesDiscovered(gatt, status)
}
}
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
super.onCharacteristicRead(gatt, characteristic, value, status)
executor.execute {
myCentralController.onCharacteristicRead(characteristic, status, value)
centralManager.onCharacteristicRead(characteristic, status, value)
}
}
@ -44,21 +51,21 @@ class MyBluetoothGattCallback(private val myCentralController: MyCentralControll
}
val value = characteristic.value
executor.execute {
myCentralController.onCharacteristicRead(characteristic, status, value)
centralManager.onCharacteristicRead(characteristic, status, value)
}
}
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
super.onCharacteristicWrite(gatt, characteristic, status)
executor.execute {
myCentralController.onCharacteristicWrite(characteristic, status)
centralManager.onCharacteristicWrite(characteristic, status)
}
}
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
super.onCharacteristicChanged(gatt, characteristic, value)
executor.execute {
myCentralController.onCharacteristicChanged(characteristic, value)
centralManager.onCharacteristicChanged(characteristic, value)
}
}
@ -70,14 +77,14 @@ class MyBluetoothGattCallback(private val myCentralController: MyCentralControll
}
val value = characteristic.value
executor.execute {
myCentralController.onCharacteristicChanged(characteristic, value)
centralManager.onCharacteristicChanged(characteristic, value)
}
}
override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
super.onDescriptorRead(gatt, descriptor, status, value)
executor.execute {
myCentralController.onDescriptorRead(descriptor, status, value)
centralManager.onDescriptorRead(descriptor, status, value)
}
}
@ -89,14 +96,14 @@ class MyBluetoothGattCallback(private val myCentralController: MyCentralControll
}
val value = descriptor.value
executor.execute {
myCentralController.onDescriptorRead(descriptor, status, value)
centralManager.onDescriptorRead(descriptor, status, value)
}
}
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
super.onDescriptorWrite(gatt, descriptor, status)
executor.execute {
myCentralController.onDescriptorWrite(descriptor, status)
centralManager.onDescriptorWrite(descriptor, status)
}
}
}

View File

@ -0,0 +1,59 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService
import java.util.concurrent.Executor
class MyBluetoothGattServerCallback(private val peripheralManager: MyPeripheralManager, private val executor: Executor) : BluetoothGattServerCallback() {
override fun onServiceAdded(status: Int, service: BluetoothGattService) {
super.onServiceAdded(status, service)
executor.execute {
peripheralManager.onServiceAdded(status, service)
}
}
override fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
super.onMtuChanged(device, mtu)
executor.execute {
peripheralManager.onMtuChanged(device, mtu)
}
}
override fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic)
executor.execute {
peripheralManager.onCharacteristicReadRequest(device, requestId, offset, characteristic)
}
}
override fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value)
executor.execute {
peripheralManager.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value)
}
}
override fun onNotificationSent(device: BluetoothDevice, status: Int) {
super.onNotificationSent(device, status)
executor.execute {
peripheralManager.onNotificationSent(device, status)
}
}
override fun onDescriptorReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor)
executor.execute {
peripheralManager.onDescriptorReadRequest(device, requestId, offset, descriptor)
}
}
override fun onDescriptorWriteRequest(device: BluetoothDevice, requestId: Int, descriptor: BluetoothGattDescriptor, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value)
executor.execute {
peripheralManager.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value)
}
}
}

View File

@ -0,0 +1,63 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import androidx.annotation.CallSuper
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import java.util.UUID
import java.util.concurrent.Executor
abstract class MyBluetoothLowEnergyManager(private val context: Context) {
companion object {
const val DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xff.toByte()
const val REQUEST_CODE = 443
val HEART_RATE_MEASUREMENT_UUID: UUID = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")
val CLIENT_CHARACTERISTIC_CONFIG_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
}
private lateinit var binding: ActivityPluginBinding
protected val unsupported = !context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
protected val executor = ContextCompat.getMainExecutor(context) as Executor
protected val manager get() = ContextCompat.getSystemService(context, BluetoothManager::class.java) as BluetoothManager
protected val adapter get() = manager.adapter as BluetoothAdapter
private val listener by lazy { MyRequestPermissionResultListener(this) }
private val receiver by lazy { MyBroadcastReceiver(this) }
fun onAttachedToActivity(binding: ActivityPluginBinding) {
binding.addRequestPermissionsResultListener(listener)
this.binding = binding
}
fun onDetachedFromActivity() {
binding.removeRequestPermissionsResultListener(listener)
}
protected fun authorize(permissions: Array<String>) {
val activity = binding.activity
ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE)
}
@CallSuper
protected open fun register() {
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
context.registerReceiver(receiver, filter)
}
@CallSuper
protected open fun unregister() {
context.unregisterReceiver(receiver)
}
abstract fun onRequestPermissionsResult(requestCode: Int, results: IntArray): Boolean
abstract fun onReceive(intent: Intent)
}

View File

@ -4,8 +4,8 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
class MyBroadcastReceiver(private val myCentralController: MyCentralController) : BroadcastReceiver() {
class MyBroadcastReceiver(private val bluetoothLowEnergyManager: MyBluetoothLowEnergyManager) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
myCentralController.onReceive(intent)
bluetoothLowEnergyManager.onReceive(intent)
}
}

View File

@ -1,745 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.util.SparseArray
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.BinaryMessenger
import java.util.UUID
class MyCentralController(private val context: Context, binaryMessenger: BinaryMessenger) : MyCentralControllerHostApi {
companion object {
// const val DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xff.toByte()
private const val REQUEST_CODE = 443
// private val UUID_HEART_RATE_MEASUREMENT = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")
private val UUID_CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
}
private lateinit var binding: ActivityPluginBinding
private val manager = ContextCompat.getSystemService(context, BluetoothManager::class.java) as BluetoothManager
private val adapter = manager.adapter
private val scanner = adapter.bluetoothLeScanner
private val executor = ContextCompat.getMainExecutor(context)
private val myApi = MyCentralControllerFlutterApi(binaryMessenger)
private val myRequestPermissionResultListener = MyRequestPermissionResultListener(this)
private val myBroadcastReceiver = MyBroadcastReceiver(this)
private val myScanCallback = MyScanCallback(this)
private val myBluetoothGattCallback = MyBluetoothGattCallback(this, executor)
private val cachedDevices = mutableMapOf<Int, BluetoothDevice>()
private val cachedGATTs = mutableMapOf<Int, BluetoothGatt>()
private val cachedServices = mutableMapOf<Int, Map<Int, BluetoothGattService>>()
private val cachedCharacteristics = mutableMapOf<Int, Map<Int, BluetoothGattCharacteristic>>()
private val cachedDescriptors = mutableMapOf<Int, Map<Int, BluetoothGattDescriptor>>()
private var registered = false
private var discovering = false
private var setUpCallback: ((Result<MyCentralControllerArgs>) -> Unit)? = null
private var startDiscoveryCallback: ((Result<Unit>) -> Unit)? = null
private val connectCallbacks = mutableMapOf<Int, (Result<Unit>) -> Unit>()
private val disconnectCallbacks = mutableMapOf<Int, (Result<Unit>) -> Unit>()
private val getMaximumWriteLengthCallbacks = mutableMapOf<Int, (Result<Long>) -> Unit>()
private val discoverGattCallbacks = mutableMapOf<Int, (Result<Unit>) -> Unit>()
private val readCharacteristicCallbacks = mutableMapOf<Int, (Result<ByteArray>) -> Unit>()
private val writeCharacteristicCallbacks = mutableMapOf<Int, (Result<Unit>) -> Unit>()
private val readDescriptorCallbacks = mutableMapOf<Int, (Result<ByteArray>) -> Unit>()
private val writeDescriptorCallbacks = mutableMapOf<Int, (Result<Unit>) -> Unit>()
override fun setUp(callback: (Result<MyCentralControllerArgs>) -> Unit) {
try {
val available = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
if (!available) {
val stateNumber = MyCentralStateArgs.UNSUPPORTED.raw.toLong()
val args = MyCentralControllerArgs(stateNumber)
callback(Result.success(args))
}
val unfinishedCallback = setUpCallback
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_CONNECT)
} else {
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
}
val activity = binding.activity
ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE)
setUpCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun tearDown() {
if (registered) {
unregister()
}
if (discovering) {
stopDiscovery()
}
for (gatt in cachedGATTs.values) {
gatt.disconnect()
}
cachedDevices.clear()
cachedGATTs.clear()
cachedServices.clear()
cachedCharacteristics.clear()
cachedDescriptors.clear()
}
private fun register() {
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
context.registerReceiver(myBroadcastReceiver, filter)
registered = true
}
private fun unregister() {
context.unregisterReceiver(myBroadcastReceiver)
registered = false
}
override fun startDiscovery(callback: (Result<Unit>) -> Unit) {
try {
val unfinishedCallback = startDiscoveryCallback
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val filters = emptyList<ScanFilter>()
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
scanner.startScan(filters, settings, myScanCallback)
executor.execute {
val finishedCallback = startDiscoveryCallback ?: return@execute
startDiscoveryCallback = null
finishedCallback(Result.success(Unit))
}
startDiscoveryCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun stopDiscovery() {
scanner.stopScan(myScanCallback)
discovering = false
}
override fun connect(myPeripheralKey: Long, callback: (Result<Unit>) -> Unit) {
try {
val deviceKey = myPeripheralKey.toInt()
val unfinishedCallback = connectCallbacks[deviceKey]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val device = cachedDevices[deviceKey] as BluetoothDevice
val autoConnect = false
cachedGATTs[deviceKey] = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val transport = BluetoothDevice.TRANSPORT_LE
device.connectGatt(context, autoConnect, myBluetoothGattCallback, transport)
} else {
device.connectGatt(context, autoConnect, myBluetoothGattCallback)
}
connectCallbacks[deviceKey] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun disconnect(myPeripheralKey: Long, callback: (Result<Unit>) -> Unit) {
try {
val deviceKey = myPeripheralKey.toInt()
val unfinishedCallback = disconnectCallbacks[deviceKey]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val gatt = cachedGATTs[deviceKey] as BluetoothGatt
gatt.disconnect()
disconnectCallbacks[deviceKey] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun getMaximumWriteLength(myPeripheralKey: Long, callback: (Result<Long>) -> Unit) {
try {
val deviceKey = myPeripheralKey.toInt()
val unfinishedCallback = getMaximumWriteLengthCallbacks[deviceKey]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val gatt = cachedGATTs[deviceKey] as BluetoothGatt
val requesting = gatt.requestMtu(512)
if (!requesting) {
throw IllegalStateException()
}
getMaximumWriteLengthCallbacks[deviceKey] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun discoverGATT(myPeripheralKey: Long, callback: (Result<Unit>) -> Unit) {
try {
val deviceKey = myPeripheralKey.toInt()
val unfinishedCallback = discoverGattCallbacks[deviceKey]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val gatt = cachedGATTs[deviceKey] as BluetoothGatt
val discovering = gatt.discoverServices()
if (!discovering) {
throw IllegalStateException()
}
discoverGattCallbacks[deviceKey] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun getServices(myPeripheralKey: Long): List<MyGattServiceArgs> {
val deviceKey = myPeripheralKey.toInt()
val services = cachedServices[deviceKey] ?: throw IllegalStateException()
return services.values.map { service -> service.toMyArgs() }
}
override fun getCharacteristics(myServiceKey: Long): List<MyGattCharacteristicArgs> {
val serviceKey = myServiceKey.toInt()
val characteristics = cachedCharacteristics[serviceKey] ?: throw IllegalStateException()
return characteristics.values.map { characteristic -> characteristic.toMyArgs() }
}
override fun getDescriptors(myCharacteristicKey: Long): List<MyGattDescriptorArgs> {
val characteristicKey = myCharacteristicKey.toInt()
val descriptors = cachedDescriptors[characteristicKey] ?: throw IllegalStateException()
return descriptors.values.map { descriptor -> descriptor.toMyArgs() }
}
override fun readCharacteristic(myPeripheralKey: Long, myServiceKey: Long, myCharacteristicKey: Long, callback: (Result<ByteArray>) -> Unit) {
try {
val deviceKey = myPeripheralKey.toInt()
val gatt = cachedGATTs[deviceKey] as BluetoothGatt
val serviceKey = myServiceKey.toInt()
val characteristics = cachedCharacteristics[serviceKey]
?: throw IllegalArgumentException()
val characteristicKey = myCharacteristicKey.toInt()
val characteristic = characteristics[characteristicKey] as BluetoothGattCharacteristic
val unfinishedCallback = readCharacteristicCallbacks[characteristicKey]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val reading = gatt.readCharacteristic(characteristic)
if (!reading) {
throw IllegalStateException()
}
readCharacteristicCallbacks[characteristicKey] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun writeCharacteristic(myPeripheralKey: Long, myServiceKey: Long, myCharacteristicKey: Long, value: ByteArray, myTypeNumber: Long, callback: (Result<Unit>) -> Unit) {
try {
val deviceKey = myPeripheralKey.toInt()
val gatt = cachedGATTs[deviceKey] as BluetoothGatt
val serviceKey = myServiceKey.toInt()
val characteristics = cachedCharacteristics[serviceKey]
?: throw IllegalArgumentException()
val characteristicKey = myCharacteristicKey.toInt()
val characteristic = characteristics[characteristicKey] as BluetoothGattCharacteristic
val unfinishedCallback = writeCharacteristicCallbacks[characteristicKey]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val myTypeArgs = myTypeNumber.toMyGattCharacteristicTypeArgs()
val writeType = myTypeArgs.toType()
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val writeCode = gatt.writeCharacteristic(characteristic, value, writeType)
writeCode == BluetoothStatusCodes.SUCCESS
} else {
// TODO: remove this when minSdkVersion >= 33
characteristic.value = value
characteristic.writeType = writeType
gatt.writeCharacteristic(characteristic)
}
if (!writing) {
throw IllegalStateException()
}
writeCharacteristicCallbacks[characteristicKey] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun notifyCharacteristic(myPeripheralKey: Long, myServiceKey: Long, myCharacteristicKey: Long, state: Boolean, callback: (Result<Unit>) -> Unit) {
try {
val deviceKey = myPeripheralKey.toInt()
val gatt = cachedGATTs[deviceKey] as BluetoothGatt
val serviceKey = myServiceKey.toInt()
val characteristics = cachedCharacteristics[serviceKey]
?: throw IllegalArgumentException()
val characteristicKey = myCharacteristicKey.toInt()
val characteristic = characteristics[characteristicKey] as BluetoothGattCharacteristic
val notifying = gatt.setCharacteristicNotification(characteristic, state)
if (!notifying) {
throw IllegalStateException()
}
// TODO: Seems the docs is not correct, this operation is necessary for all characteristics.
// https://developer.android.com/guide/topics/connectivity/bluetooth/transfer-ble-data#notification
// This is specific to Heart Rate Measurement.
// if (characteristic.uuid == UUID_HEART_RATE_MEASUREMENT) {
val descriptor = characteristic.getDescriptor(UUID_CLIENT_CHARACTERISTIC_CONFIG)
val descriptorKey = descriptor.hashCode()
val unfinishedCallback = writeDescriptorCallbacks[descriptorKey]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val value = if (state) BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
else BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val writeCode = gatt.writeDescriptor(descriptor, value)
writeCode == BluetoothStatusCodes.SUCCESS
} else {
// TODO: remove this when minSdkVersion >= 33
descriptor.value = value
gatt.writeDescriptor(descriptor)
}
if (!writing) {
throw IllegalStateException()
}
writeDescriptorCallbacks[descriptorKey] = callback
// } else {
// callback(Result.success(Unit))
// }
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun readDescriptor(myPeripheralKey: Long, myCharacteristicKey: Long, myDescriptorKey: Long, callback: (Result<ByteArray>) -> Unit) {
try {
val deviceKey = myPeripheralKey.toInt()
val gatt = cachedGATTs[deviceKey] as BluetoothGatt
val characteristicKey = myCharacteristicKey.toInt()
val descriptors = cachedDescriptors[characteristicKey]
?: throw IllegalArgumentException()
val descriptorKey = myDescriptorKey.toInt()
val descriptor = descriptors[descriptorKey] as BluetoothGattDescriptor
val unfinishedCallback = readDescriptorCallbacks[descriptorKey]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val reading = gatt.readDescriptor(descriptor)
if (!reading) {
throw IllegalStateException()
}
readDescriptorCallbacks[descriptorKey] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun writeDescriptor(myPeripheralKey: Long, myCharacteristicKey: Long, myDescriptorKey: Long, value: ByteArray, callback: (Result<Unit>) -> Unit) {
try {
val deviceKey = myPeripheralKey.toInt()
val gatt = cachedGATTs[deviceKey] as BluetoothGatt
val characteristicKey = myCharacteristicKey.toInt()
val descriptors = cachedDescriptors[characteristicKey]
?: throw IllegalArgumentException()
val descriptorKey = myDescriptorKey.toInt()
val descriptor = descriptors[descriptorKey] as BluetoothGattDescriptor
val unfinishedCallback = writeDescriptorCallbacks[descriptorKey]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val writeCode = gatt.writeDescriptor(descriptor, value)
writeCode == BluetoothStatusCodes.SUCCESS
} else {
// TODO: remove this when minSdkVersion >= 33
descriptor.value = value
gatt.writeDescriptor(descriptor)
}
if (!writing) {
throw IllegalStateException()
}
writeDescriptorCallbacks[descriptorKey] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
fun onAttachedToActivity(binding: ActivityPluginBinding) {
binding.addRequestPermissionsResultListener(myRequestPermissionResultListener)
this.binding = binding
}
fun onDetachedFromActivity() {
binding.removeRequestPermissionsResultListener(myRequestPermissionResultListener)
}
fun onRequestPermissionsResult(requestCode: Int, results: IntArray): Boolean {
if (requestCode != REQUEST_CODE) {
return false
}
val callback = setUpCallback ?: return false
val isGranted = results.all { r -> r == PackageManager.PERMISSION_GRANTED }
if (isGranted) {
register()
}
val myStateArgs = if (isGranted) adapter.myStateArgs
else MyCentralStateArgs.UNAUTHORIZED
val myStateNumber = myStateArgs.raw.toLong()
val myArgs = MyCentralControllerArgs(myStateNumber)
callback(Result.success(myArgs))
return true
}
fun onReceive(intent: Intent) {
val action = intent.action
if (action != BluetoothAdapter.ACTION_STATE_CHANGED) {
return
}
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
val myStateArgs = state.toMyCentralStateArgs()
val myStateNumber = myStateArgs.raw.toLong()
myApi.onStateChanged(myStateNumber) {}
}
fun onScanFailed(errorCode: Int) {
val callback = startDiscoveryCallback ?: return
startDiscoveryCallback = null
val error = IllegalStateException("Start discovery failed with error code: $errorCode")
callback(Result.failure(error))
}
fun onScanResult(result: ScanResult) {
val device = result.device
val deviceKey = device.hashCode()
cachedDevices[deviceKey] = device
val myPeripheralArgs = device.toMyArgs()
val rssi = result.rssi.toLong()
val myAdvertisementArgs = result.myAdvertisementArgs
myApi.onDiscovered(myPeripheralArgs, rssi, myAdvertisementArgs) {}
}
fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val device = gatt.device
val deviceKey = device.hashCode()
val myPeripheralKey = deviceKey.toLong()
if (newState != BluetoothProfile.STATE_CONNECTED) {
gatt.close()
cachedGATTs.remove(deviceKey)
val error = IllegalStateException("GATT is disconnected with status: $status")
val getMaximumWriteLengthCallback = getMaximumWriteLengthCallbacks.remove(deviceKey)
if (getMaximumWriteLengthCallback != null) {
getMaximumWriteLengthCallback(Result.failure(error))
}
val discoverGattCallback = discoverGattCallbacks.remove(deviceKey)
if (discoverGattCallback != null) {
discoverGattCallback(Result.failure(error))
}
val services = cachedServices[deviceKey] ?: emptyMap()
for (service in services) {
val characteristics = cachedCharacteristics[service.key] ?: emptyMap()
for (characteristic in characteristics) {
val readCharacteristicCallback = readCharacteristicCallbacks.remove(characteristic.key)
val writeCharacteristicCallback = writeCharacteristicCallbacks.remove(characteristic.key)
if (readCharacteristicCallback != null) {
readCharacteristicCallback(Result.failure(error))
}
if (writeCharacteristicCallback != null) {
writeCharacteristicCallback(Result.failure(error))
}
val descriptors = cachedDescriptors[characteristic.key] ?: emptyMap()
for (descriptor in descriptors) {
val readDescriptorCallback = readDescriptorCallbacks.remove(descriptor.key)
val writeDescriptorCallback = writeDescriptorCallbacks.remove(descriptor.key)
if (readDescriptorCallback != null) {
readDescriptorCallback(Result.failure(error))
}
if (writeDescriptorCallback != null) {
writeDescriptorCallback(Result.failure(error))
}
}
}
}
}
val connectCallback = connectCallbacks.remove(deviceKey)
val disconnectCallback = disconnectCallbacks.remove(deviceKey)
if (connectCallback == null && disconnectCallback == null) {
// State changed.
val state = newState == BluetoothProfile.STATE_CONNECTED
myApi.onPeripheralStateChanged(myPeripheralKey, state) {}
} else {
if (connectCallback != null) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// Connect succeed.
connectCallback(Result.success(Unit))
myApi.onPeripheralStateChanged(myPeripheralKey, true) {}
} else {
// Connect failed.
val error = IllegalStateException("Connect failed with status: $status")
connectCallback(Result.failure(error))
}
}
if (disconnectCallback != null) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// Disconnect succeed.
disconnectCallback(Result.success(Unit))
myApi.onPeripheralStateChanged(myPeripheralKey, false) {}
} else {
// Disconnect failed.
val error = IllegalStateException("Connect failed with status: $status")
disconnectCallback(Result.failure(error))
}
}
}
}
fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
val device = gatt.device
val deviceKey = device.hashCode()
val callback = getMaximumWriteLengthCallbacks.remove(deviceKey) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
val maximumWriteLength = (mtu - 3).toLong()
callback(Result.success(maximumWriteLength))
} else {
val error = IllegalStateException("Get maximum write length failed with status: $status")
callback(Result.failure(error))
}
}
fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
val device = gatt.device
val deviceKey = device.hashCode()
val callback = discoverGattCallbacks.remove(deviceKey) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
val cachedServices = mutableMapOf<Int, BluetoothGattService>()
for (service in gatt.services) {
val serviceKey = service.hashCode()
cachedServices[serviceKey] = service
val cachedCharacteristics = mutableMapOf<Int, BluetoothGattCharacteristic>()
for (characteristic in service.characteristics) {
val characteristicKey = characteristic.hashCode()
cachedCharacteristics[characteristicKey] = characteristic
val cachedDescriptors = mutableMapOf<Int, BluetoothGattDescriptor>()
for (descriptor in characteristic.descriptors) {
val descriptorKey = descriptor.hashCode()
cachedDescriptors[descriptorKey] = descriptor
}
this.cachedDescriptors[characteristicKey] = cachedDescriptors
}
this.cachedCharacteristics[serviceKey] = cachedCharacteristics
}
this.cachedServices[deviceKey] = cachedServices
callback(Result.success(Unit))
} else {
val error = IllegalStateException("Discover GATT failed with status: $status")
callback(Result.failure(error))
}
}
fun onCharacteristicRead(characteristic: BluetoothGattCharacteristic, status: Int, value: ByteArray) {
val characteristicKey = characteristic.hashCode()
val callback = readCharacteristicCallbacks.remove(characteristicKey) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(value))
} else {
val error = IllegalStateException("Read characteristic failed with status: $status.")
callback(Result.failure(error))
}
}
fun onCharacteristicWrite(characteristic: BluetoothGattCharacteristic, status: Int) {
val characteristicKey = characteristic.hashCode()
val callback = writeCharacteristicCallbacks.remove(characteristicKey) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val error = IllegalStateException("Write characteristic failed with status: $status.")
callback(Result.failure(error))
}
}
fun onCharacteristicChanged(characteristic: BluetoothGattCharacteristic, value: ByteArray) {
val characteristicKey = characteristic.hashCode()
val myCharacteristicKey = characteristicKey.toLong()
myApi.onCharacteristicValueChanged(myCharacteristicKey, value) {}
}
fun onDescriptorRead(descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
val descriptorKey = descriptor.hashCode()
val callback = readDescriptorCallbacks.remove(descriptorKey) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(value))
} else {
val error = IllegalStateException("Read descriptor failed with status: $status.")
callback(Result.failure(error))
}
}
fun onDescriptorWrite(descriptor: BluetoothGattDescriptor, status: Int) {
val descriptorKey = descriptor.hashCode()
val callback = writeDescriptorCallbacks.remove(descriptorKey) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val error = IllegalStateException("Write descriptor failed with status: $status.")
callback(Result.failure(error))
}
}
}
private val BluetoothAdapter.myStateArgs: MyCentralStateArgs
get() = state.toMyCentralStateArgs()
private fun Int.toMyCentralStateArgs(): MyCentralStateArgs {
return when (this) {
BluetoothAdapter.STATE_ON -> MyCentralStateArgs.POWEREDON
else -> MyCentralStateArgs.POWEREDOFF
}
}
private fun BluetoothDevice.toMyArgs(): MyPeripheralArgs {
val key = hashCode().toLong()
val uuid = this.uuid.toString()
return MyPeripheralArgs(key, uuid)
}
private val BluetoothDevice.uuid: UUID
get() {
val node = address.filter { char -> char != ':' }
// We don't know the timestamp of the bluetooth device, use nil UUID as prefix.
return UUID.fromString("00000000-0000-0000-0000-$node")
}
private val ScanResult.myAdvertisementArgs: MyAdvertisementArgs
get() {
val record = scanRecord
return if (record == null) {
val name = null
val manufacturerSpecificData = emptyMap<Long?, ByteArray?>()
val serviceUUIDs = emptyList<String?>()
val serviceData = emptyMap<String?, ByteArray>()
MyAdvertisementArgs(name, manufacturerSpecificData, serviceUUIDs, serviceData)
} else {
val name = record.deviceName
val manufacturerSpecificData = record.manufacturerSpecificData.toMyArgs()
val serviceUUIDs = record.serviceUuids?.map { uuid -> uuid.toString() }
?: emptyList()
val pairs = record.serviceData.entries.map { (uuid, value) ->
val key = uuid.toString()
return@map Pair(key, value)
}.toTypedArray()
val serviceData = mapOf<String?, ByteArray?>(*pairs)
MyAdvertisementArgs(name, manufacturerSpecificData, serviceUUIDs, serviceData)
}
}
private fun SparseArray<ByteArray>.toMyArgs(): Map<Long?, ByteArray?> {
var index = 0
val size = size()
val values = mutableMapOf<Long?, ByteArray>()
while (index < size) {
val key = keyAt(index).toLong()
val value = valueAt(index)
values[key] = value
index++
}
return values
}
//private val ScanRecord.rawValues: Map<Byte, ByteArray>
// get() {
// val rawValues = mutableMapOf<Byte, ByteArray>()
// var index = 0
// val size = bytes.size
// while (index < size) {
// val length = bytes[index++].toInt() and 0xff
// if (length == 0) {
// break
// }
// val end = index + length
// val type = bytes[index++]
// val value = bytes.slice(index until end).toByteArray()
// rawValues[type] = value
// index = end
// }
// return rawValues.toMap()
// }
private fun BluetoothGattService.toMyArgs(): MyGattServiceArgs {
val key = hashCode().toLong()
val uuid = this.uuid.toString()
return MyGattServiceArgs(key, uuid)
}
private fun BluetoothGattCharacteristic.toMyArgs(): MyGattCharacteristicArgs {
val key = hashCode().toLong()
val uuid = this.uuid.toString()
return MyGattCharacteristicArgs(key, uuid, myPropertyNumbers)
}
private val BluetoothGattCharacteristic.myPropertyNumbers: List<Long>
get() {
val numbers = mutableListOf<Long>()
if (properties and BluetoothGattCharacteristic.PROPERTY_READ != 0) {
val number = MyGattCharacteristicPropertyArgs.READ.raw.toLong()
numbers.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) {
val number = MyGattCharacteristicPropertyArgs.WRITE.raw.toLong()
numbers.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) {
val number = MyGattCharacteristicPropertyArgs.WRITEWITHOUTRESPONSE.raw.toLong()
numbers.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {
val number = MyGattCharacteristicPropertyArgs.NOTIFY.raw.toLong()
numbers.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) {
val number = MyGattCharacteristicPropertyArgs.INDICATE.raw.toLong()
numbers.add(number)
}
return numbers
}
private fun BluetoothGattDescriptor.toMyArgs(): MyGattDescriptorArgs {
val key = hashCode().toLong()
val uuid = this.uuid.toString()
return MyGattDescriptorArgs(key, uuid)
}
private fun Long.toMyGattCharacteristicTypeArgs(): MyGattCharacteristicWriteTypeArgs {
val raw = toInt()
return MyGattCharacteristicWriteTypeArgs.ofRaw(raw) ?: throw IllegalArgumentException()
}
private fun MyGattCharacteristicWriteTypeArgs.toType(): Int {
return when (this) {
MyGattCharacteristicWriteTypeArgs.WITHRESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
MyGattCharacteristicWriteTypeArgs.WITHOUTRESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
}
}

View File

@ -0,0 +1,627 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import io.flutter.plugin.common.BinaryMessenger
class MyCentralManager(private val context: Context, binaryMessenger: BinaryMessenger) : MyBluetoothLowEnergyManager(context), MyCentralManagerHostApi {
private val scanner get() = adapter.bluetoothLeScanner
private val api = MyCentralManagerFlutterApi(binaryMessenger)
private val scanCallback = MyScanCallback(this)
private val bluetoothGattCallback = MyBluetoothGattCallback(this, executor)
private val devices = mutableMapOf<Long, BluetoothDevice>()
private val bluetoothGATTs = mutableMapOf<Long, BluetoothGatt>()
private val services = mutableMapOf<Long, BluetoothGattService>()
private val characteristics = mutableMapOf<Long, BluetoothGattCharacteristic>()
private val descriptors = mutableMapOf<Long, BluetoothGattDescriptor>()
private val peripheralsArgs = mutableMapOf<Int, MyPeripheralArgs>()
private val servicesArgsOfPeripherals = mutableMapOf<Long, List<MyGattServiceArgs>>()
private val servicesArgs = mutableMapOf<Int, MyGattServiceArgs>()
private val characteristicsArgs = mutableMapOf<Int, MyGattCharacteristicArgs>()
private val descriptorsArgs = mutableMapOf<Int, MyGattDescriptorArgs>()
private var registered = false
private var discovering = false
private var setUpCallback: ((Result<MyCentralManagerArgs>) -> Unit)? = null
private var startDiscoveryCallback: ((Result<Unit>) -> Unit)? = null
private val connectCallbacks = mutableMapOf<Long, (Result<Unit>) -> Unit>()
private val disconnectCallbacks = mutableMapOf<Long, (Result<Unit>) -> Unit>()
private val getMaximumWriteLengthCallbacks = mutableMapOf<Long, (Result<Long>) -> Unit>()
private val readRssiCallbacks = mutableMapOf<Long, (Result<Long>) -> Unit>()
private val discoverGattCallbacks = mutableMapOf<Long, (Result<List<MyGattServiceArgs>>) -> Unit>()
private val readCharacteristicCallbacks = mutableMapOf<Long, (Result<ByteArray>) -> Unit>()
private val writeCharacteristicCallbacks = mutableMapOf<Long, (Result<Unit>) -> Unit>()
private val readDescriptorCallbacks = mutableMapOf<Long, (Result<ByteArray>) -> Unit>()
private val writeDescriptorCallbacks = mutableMapOf<Long, (Result<Unit>) -> Unit>()
override fun setUp(callback: (Result<MyCentralManagerArgs>) -> Unit) {
try {
if (setUpCallback != null) {
throw IllegalStateException()
}
tearDown()
if (unsupported) {
val stateNumberArgs = MyBluetoothLowEnergyStateArgs.UNSUPPORTED.raw.toLong()
val args = MyCentralManagerArgs(stateNumberArgs)
callback(Result.success(args))
}
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_CONNECT)
} else {
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
}
authorize(permissions)
setUpCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
private fun tearDown() {
if (registered) {
unregister()
}
if (discovering) {
stopDiscovery()
}
for (gatt in bluetoothGATTs.values) {
gatt.disconnect()
}
devices.clear()
bluetoothGATTs.clear()
services.clear()
characteristics.clear()
descriptors.clear()
peripheralsArgs.clear()
servicesArgsOfPeripherals.clear()
servicesArgs.clear()
characteristicsArgs.clear()
descriptorsArgs.clear()
setUpCallback = null
startDiscoveryCallback = null
connectCallbacks.clear()
disconnectCallbacks.clear()
getMaximumWriteLengthCallbacks.clear()
readRssiCallbacks.clear()
discoverGattCallbacks.clear()
readCharacteristicCallbacks.clear()
writeCharacteristicCallbacks.clear()
readDescriptorCallbacks.clear()
writeDescriptorCallbacks.clear()
}
override fun register() {
super.register()
registered = true
}
override fun unregister() {
super.unregister()
registered = false
}
override fun startDiscovery(callback: (Result<Unit>) -> Unit) {
try {
if (startDiscoveryCallback != null) {
throw IllegalStateException()
}
val filters = emptyList<ScanFilter>()
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
scanner.startScan(filters, settings, scanCallback)
executor.execute { onScanSucceed() }
startDiscoveryCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun stopDiscovery() {
scanner.stopScan(scanCallback)
discovering = false
}
override fun connect(peripheralHashCodeArgs: Long, callback: (Result<Unit>) -> Unit) {
try {
val unfinishedCallback = connectCallbacks[peripheralHashCodeArgs]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val device = devices[peripheralHashCodeArgs] as BluetoothDevice
val autoConnect = false
// Add to bluetoothGATTs cache.
bluetoothGATTs[peripheralHashCodeArgs] = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val transport = BluetoothDevice.TRANSPORT_LE
device.connectGatt(context, autoConnect, bluetoothGattCallback, transport)
} else {
device.connectGatt(context, autoConnect, bluetoothGattCallback)
}
connectCallbacks[peripheralHashCodeArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun disconnect(peripheralHashCodeArgs: Long, callback: (Result<Unit>) -> Unit) {
try {
val unfinishedCallback = disconnectCallbacks[peripheralHashCodeArgs]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val gatt = bluetoothGATTs[peripheralHashCodeArgs] as BluetoothGatt
gatt.disconnect()
disconnectCallbacks[peripheralHashCodeArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun getMaximumWriteLength(peripheralHashCodeArgs: Long, callback: (Result<Long>) -> Unit) {
try {
val unfinishedCallback = getMaximumWriteLengthCallbacks[peripheralHashCodeArgs]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val gatt = bluetoothGATTs[peripheralHashCodeArgs] as BluetoothGatt
val requesting = gatt.requestMtu(517)
if (!requesting) {
throw IllegalStateException()
}
getMaximumWriteLengthCallbacks[peripheralHashCodeArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun readRSSI(peripheralHashCodeArgs: Long, callback: (Result<Long>) -> Unit) {
try {
val unfinishedCallback = readRssiCallbacks[peripheralHashCodeArgs]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val gatt = bluetoothGATTs[peripheralHashCodeArgs] as BluetoothGatt
val reading = gatt.readRemoteRssi()
if (!reading) {
throw IllegalStateException()
}
readRssiCallbacks[peripheralHashCodeArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun discoverGATT(peripheralHashCodeArgs: Long, callback: (Result<List<MyGattServiceArgs>>) -> Unit) {
try {
val unfinishedCallback = discoverGattCallbacks[peripheralHashCodeArgs]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val gatt = bluetoothGATTs[peripheralHashCodeArgs] as BluetoothGatt
val discovering = gatt.discoverServices()
if (!discovering) {
throw IllegalStateException()
}
discoverGattCallbacks[peripheralHashCodeArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun readCharacteristic(peripheralHashCodeArgs: Long, characteristicHashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit) {
try {
val unfinishedCallback = readCharacteristicCallbacks[characteristicHashCodeArgs]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val gatt = bluetoothGATTs[peripheralHashCodeArgs] as BluetoothGatt
val characteristic = characteristics[characteristicHashCodeArgs] as BluetoothGattCharacteristic
val reading = gatt.readCharacteristic(characteristic)
if (!reading) {
throw IllegalStateException()
}
readCharacteristicCallbacks[characteristicHashCodeArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun writeCharacteristic(peripheralHashCodeArgs: Long, characteristicHashCodeArgs: Long, valueArgs: ByteArray, typeNumberArgs: Long, callback: (Result<Unit>) -> Unit) {
try {
val unfinishedCallback = writeCharacteristicCallbacks[characteristicHashCodeArgs]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val gatt = bluetoothGATTs[peripheralHashCodeArgs] as BluetoothGatt
val characteristic = characteristics[characteristicHashCodeArgs] as BluetoothGattCharacteristic
val typeArgs = typeNumberArgs.toWriteTypeArgs()
val type = typeArgs.toType()
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val code = gatt.writeCharacteristic(characteristic, valueArgs, type)
code == BluetoothStatusCodes.SUCCESS
} else {
// TODO: remove this when minSdkVersion >= 33
characteristic.value = valueArgs
characteristic.writeType = type
gatt.writeCharacteristic(characteristic)
}
if (!writing) {
throw IllegalStateException()
}
writeCharacteristicCallbacks[characteristicHashCodeArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun notifyCharacteristic(peripheralHashCodeArgs: Long, characteristicHashCodeArgs: Long, stateArgs: Boolean, callback: (Result<Unit>) -> Unit) {
try {
val gatt = bluetoothGATTs[peripheralHashCodeArgs] as BluetoothGatt
val characteristic = characteristics[characteristicHashCodeArgs] as BluetoothGattCharacteristic
val notifying = gatt.setCharacteristicNotification(characteristic, stateArgs)
if (!notifying) {
throw IllegalStateException()
}
// TODO: Seems the docs is not correct, this operation is necessary for all characteristics.
// https://developer.android.com/guide/topics/connectivity/bluetooth/transfer-ble-data#notification
// This is specific to Heart Rate Measurement.
// if (characteristic.uuid == UUID_HEART_RATE_MEASUREMENT) {
val descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID)
val descriptorHashCode = descriptor.hashCode()
val descriptorArgs = descriptorsArgs[descriptorHashCode] as MyGattDescriptorArgs
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
val unfinishedCallback = writeDescriptorCallbacks[descriptorHashCodeArgs]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val value = if (stateArgs) BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
else BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val code = gatt.writeDescriptor(descriptor, value)
code == BluetoothStatusCodes.SUCCESS
} else {
// TODO: remove this when minSdkVersion >= 33
descriptor.value = value
gatt.writeDescriptor(descriptor)
}
if (!writing) {
throw IllegalStateException()
}
writeDescriptorCallbacks[descriptorHashCodeArgs] = callback
// } else {
// callback(Result.success(Unit))
// }
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun readDescriptor(peripheralHashCodeArgs: Long, descriptorHashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit) {
try {
val unfinishedCallback = readDescriptorCallbacks[descriptorHashCodeArgs]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val gatt = bluetoothGATTs[peripheralHashCodeArgs] as BluetoothGatt
val descriptor = descriptors[descriptorHashCodeArgs] as BluetoothGattDescriptor
val reading = gatt.readDescriptor(descriptor)
if (!reading) {
throw IllegalStateException()
}
readDescriptorCallbacks[descriptorHashCodeArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun writeDescriptor(peripheralHashCodeArgs: Long, descriptorHashCodeArgs: Long, valueArgs: ByteArray, callback: (Result<Unit>) -> Unit) {
try {
val unfinishedCallback = writeDescriptorCallbacks[descriptorHashCodeArgs]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val gatt = bluetoothGATTs[peripheralHashCodeArgs] as BluetoothGatt
val descriptor = descriptors[descriptorHashCodeArgs] as BluetoothGattDescriptor
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val code = gatt.writeDescriptor(descriptor, valueArgs)
code == BluetoothStatusCodes.SUCCESS
} else {
// TODO: remove this when minSdkVersion >= 33
descriptor.value = valueArgs
gatt.writeDescriptor(descriptor)
}
if (!writing) {
throw IllegalStateException()
}
writeDescriptorCallbacks[descriptorHashCodeArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun onRequestPermissionsResult(requestCode: Int, results: IntArray): Boolean {
if (requestCode != REQUEST_CODE) {
return false
}
val authorized = results.all { r -> r == PackageManager.PERMISSION_GRANTED }
val callback = setUpCallback ?: return false
setUpCallback = null
val stateArgs = if (authorized) adapter.stateArgs
else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
val stateNumberArgs = stateArgs.raw.toLong()
val args = MyCentralManagerArgs(stateNumberArgs)
callback(Result.success(args))
if (authorized) {
register()
}
return true
}
override fun onReceive(intent: Intent) {
val action = intent.action
if (action != BluetoothAdapter.ACTION_STATE_CHANGED) {
return
}
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
val stateArgs = state.toBluetoothLowEnergyStateArgs()
val stateNumberArgs = stateArgs.raw.toLong()
api.onStateChanged(stateNumberArgs) {}
}
private fun onScanSucceed() {
discovering = true
val callback = startDiscoveryCallback ?: return
startDiscoveryCallback = null
callback(Result.success(Unit))
}
fun onScanFailed(errorCode: Int) {
val callback = startDiscoveryCallback ?: return
startDiscoveryCallback = null
val error = IllegalStateException("Start discovery failed with error code: $errorCode")
callback(Result.failure(error))
}
fun onScanResult(result: ScanResult) {
val device = result.device
val peripheralArgs = device.toPeripheralArgs()
val hashCode = device.hashCode()
val hashCodeArgs = peripheralArgs.hashCodeArgs
this.devices[hashCodeArgs] = device
this.peripheralsArgs[hashCode] = peripheralArgs
val rssiArgs = result.rssi.toLong()
val advertiseDataArgs = result.advertiseDataArgs
api.onDiscovered(peripheralArgs, rssiArgs, advertiseDataArgs) {}
}
fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val device = gatt.device
val deviceHashCode = device.hashCode()
val peripheralArgs = peripheralsArgs[deviceHashCode] as MyPeripheralArgs
val peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
// Check callbacks
if (newState != BluetoothProfile.STATE_CONNECTED) {
gatt.close()
bluetoothGATTs.remove(peripheralHashCodeArgs)
val error = IllegalStateException("GATT is disconnected with status: $status")
val getMaximumWriteLengthCallback = getMaximumWriteLengthCallbacks.remove(peripheralHashCodeArgs)
if (getMaximumWriteLengthCallback != null) {
getMaximumWriteLengthCallback(Result.failure(error))
}
val readRssiCallback = readRssiCallbacks.remove(peripheralHashCodeArgs)
if (readRssiCallback != null) {
readRssiCallback(Result.failure(error))
}
val discoverGattCallback = discoverGattCallbacks.remove(peripheralHashCodeArgs)
if (discoverGattCallback != null) {
discoverGattCallback(Result.failure(error))
}
val servicesArgs = servicesArgsOfPeripherals[peripheralHashCodeArgs] ?: emptyList()
for (serviceArgs in servicesArgs) {
val characteristicsArgs = serviceArgs.characteristicsArgs.filterNotNull()
for (characteristicArgs in characteristicsArgs) {
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
val readCharacteristicCallback = readCharacteristicCallbacks.remove(characteristicHashCodeArgs)
val writeCharacteristicCallback = writeCharacteristicCallbacks.remove(characteristicHashCodeArgs)
if (readCharacteristicCallback != null) {
readCharacteristicCallback(Result.failure(error))
}
if (writeCharacteristicCallback != null) {
writeCharacteristicCallback(Result.failure(error))
}
val descriptorsArgs = characteristicArgs.descriptorsArgs.filterNotNull()
for (descriptorArgs in descriptorsArgs) {
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
val readDescriptorCallback = readDescriptorCallbacks.remove(descriptorHashCodeArgs)
val writeDescriptorCallback = writeDescriptorCallbacks.remove(descriptorHashCodeArgs)
if (readDescriptorCallback != null) {
readDescriptorCallback(Result.failure(error))
}
if (writeDescriptorCallback != null) {
writeDescriptorCallback(Result.failure(error))
}
}
}
}
}
// Check state
val connectCallback = connectCallbacks.remove(peripheralHashCodeArgs)
val disconnectCallback = disconnectCallbacks.remove(peripheralHashCodeArgs)
if (connectCallback == null && disconnectCallback == null) {
// State changed.
val stateArgs = newState == BluetoothProfile.STATE_CONNECTED
api.onPeripheralStateChanged(peripheralArgs, stateArgs) {}
} else {
if (connectCallback != null) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// Connect succeed.
connectCallback(Result.success(Unit))
api.onPeripheralStateChanged(peripheralArgs, true) {}
} else {
// Connect failed.
val error = IllegalStateException("Connect failed with status: $status")
connectCallback(Result.failure(error))
}
}
if (disconnectCallback != null) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// Disconnect succeed.
disconnectCallback(Result.success(Unit))
api.onPeripheralStateChanged(peripheralArgs, false) {}
} else {
// Disconnect failed.
val error = IllegalStateException("Connect failed with status: $status")
disconnectCallback(Result.failure(error))
}
}
}
}
fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
val device = gatt.device
val hashCode = device.hashCode()
val peripheralArgs = peripheralsArgs[hashCode] as MyPeripheralArgs
val peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
val callback = getMaximumWriteLengthCallbacks.remove(peripheralHashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
val maximumWriteLengthArgs = (mtu - 3).toLong()
callback(Result.success(maximumWriteLengthArgs))
} else {
val error = IllegalStateException("Get maximum write length failed with status: $status")
callback(Result.failure(error))
}
}
fun onReadRemoteRssi(gatt: BluetoothGatt, rssi: Int, status: Int) {
val device = gatt.device
val hashCode = device.hashCode()
val peripheralArgs = peripheralsArgs[hashCode] as MyPeripheralArgs
val peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
val callback = readRssiCallbacks.remove(peripheralHashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
val rssiArgs = rssi.toLong()
callback(Result.success(rssiArgs))
} else {
val error = IllegalStateException("Read rssi failed with status: $status")
callback(Result.failure(error))
}
}
fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
val device = gatt.device
val deviceHashCode = device.hashCode()
val peripheralArgs = peripheralsArgs[deviceHashCode] as MyPeripheralArgs
val peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
val callback = discoverGattCallbacks.remove(peripheralHashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
val services = gatt.services
val servicesArgs = mutableListOf<MyGattServiceArgs>()
for (service in services) {
val characteristics = service.characteristics
val characteristicsArgs = mutableListOf<MyGattCharacteristicArgs>()
for (characteristic in characteristics) {
val descriptors = characteristic.descriptors
val descriptorsArgs = mutableListOf<MyGattDescriptorArgs>()
for (descriptor in descriptors) {
val descriptorArgs = descriptor.toManufacturerSpecificDataArgs()
val descriptorHashCode = descriptor.hashCode()
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
this.descriptors[descriptorHashCodeArgs] = descriptor
this.descriptorsArgs[descriptorHashCode] = descriptorArgs
descriptorsArgs.add(descriptorArgs)
}
val characteristicArgs = characteristic.toManufacturerSpecificDataArgs(descriptorsArgs)
val characteristicHashCode = characteristic.hashCode()
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
this.characteristics[characteristicHashCodeArgs] = characteristic
this.characteristicsArgs[characteristicHashCode] = characteristicArgs
characteristicsArgs.add(characteristicArgs)
}
val serviceArgs = service.toManufacturerSpecificDataArgs(characteristicsArgs)
val serviceHashCode = service.hashCode()
val serviceHashCodeArgs = serviceArgs.hashCodeArgs
this.services[serviceHashCodeArgs] = service
this.servicesArgs[serviceHashCode] = serviceArgs
servicesArgs.add(serviceArgs)
}
servicesArgsOfPeripherals[peripheralHashCodeArgs] = servicesArgs
callback(Result.success(servicesArgs))
} else {
val error = IllegalStateException("Discover GATT failed with status: $status")
callback(Result.failure(error))
}
}
fun onCharacteristicRead(characteristic: BluetoothGattCharacteristic, status: Int, value: ByteArray) {
val hashCode = characteristic.hashCode()
val characteristicArgs = characteristicsArgs[hashCode] as MyGattCharacteristicArgs
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
val callback = readCharacteristicCallbacks.remove(characteristicHashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(value))
} else {
val error = IllegalStateException("Read characteristic failed with status: $status.")
callback(Result.failure(error))
}
}
fun onCharacteristicWrite(characteristic: BluetoothGattCharacteristic, status: Int) {
val hashCode = characteristic.hashCode()
val characteristicArgs = characteristicsArgs[hashCode] as MyGattCharacteristicArgs
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
val callback = writeCharacteristicCallbacks.remove(characteristicHashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val error = IllegalStateException("Write characteristic failed with status: $status.")
callback(Result.failure(error))
}
}
fun onCharacteristicChanged(characteristic: BluetoothGattCharacteristic, value: ByteArray) {
val hashCode = characteristic.hashCode()
val characteristicArgs = characteristicsArgs[hashCode] as MyGattCharacteristicArgs
api.onCharacteristicValueChanged(characteristicArgs, value) {}
}
fun onDescriptorRead(descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
val hashCode = descriptor.hashCode()
val descriptorArgs = descriptorsArgs[hashCode] as MyGattDescriptorArgs
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
val callback = readDescriptorCallbacks.remove(descriptorHashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(value))
} else {
val error = IllegalStateException("Read descriptor failed with status: $status.")
callback(Result.failure(error))
}
}
fun onDescriptorWrite(descriptor: BluetoothGattDescriptor, status: Int) {
val hashCode = descriptor.hashCode()
val descriptorArgs = descriptorsArgs[hashCode] as MyGattDescriptorArgs
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
val callback = writeDescriptorCallbacks.remove(descriptorHashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val error = IllegalStateException("Write descriptor failed with status: $status.")
callback(Result.failure(error))
}
}
}

View File

@ -0,0 +1,423 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.AdvertiseSettings
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import io.flutter.plugin.common.BinaryMessenger
class MyPeripheralManager(private val context: Context, binaryMessenger: BinaryMessenger) : MyBluetoothLowEnergyManager(context), MyPeripheralManagerHostApi {
private val server by lazy { manager.openGattServer(context, bluetoothGattServerCallback) }
private val advertiser get() = adapter.bluetoothLeAdvertiser
private val api = MyPeripheralManagerFlutterApi(binaryMessenger)
private val bluetoothGattServerCallback = MyBluetoothGattServerCallback(this, executor)
private val advertiseCallback = MyAdvertiseCallback(this)
private val devices = mutableMapOf<Long, BluetoothDevice>()
private val services = mutableMapOf<Long, BluetoothGattService>()
private val characteristics = mutableMapOf<Long, BluetoothGattCharacteristic>()
private val descriptors = mutableMapOf<Long, BluetoothGattDescriptor>()
private val mtus = mutableMapOf<Long, Int>()
private val confirms = mutableMapOf<Long, Boolean>()
private val centralsArgs = mutableMapOf<Int, MyCentralArgs>()
private val servicesArgs = mutableMapOf<Int, MyGattServiceArgs>()
private val characteristicsArgs = mutableMapOf<Int, MyGattCharacteristicArgs>()
private val descriptorsArgs = mutableMapOf<Int, MyGattDescriptorArgs>()
private var registered = false
private var advertising = false
private var setUpCallback: ((Result<MyPeripheralManagerArgs>) -> Unit)? = null
private var addServiceCallback: ((Result<Unit>) -> Unit)? = null
private var startAdvertisingCallback: ((Result<Unit>) -> Unit)? = null
private val notifyCharacteristicValueChangedCallbacks = mutableMapOf<Long, (Result<Unit>) -> Unit>()
override fun setUp(callback: (Result<MyPeripheralManagerArgs>) -> Unit) {
try {
val unfinishedCallback = setUpCallback
if (unfinishedCallback != null) {
throw IllegalStateException()
}
tearDown()
if (unsupported) {
val stateNumberArgs = MyBluetoothLowEnergyStateArgs.UNSUPPORTED.raw.toLong()
val args = MyPeripheralManagerArgs(stateNumberArgs)
callback(Result.success(args))
}
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT)
} else {
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
}
authorize(permissions)
setUpCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
private fun tearDown() {
if (registered) {
unregister()
}
if (advertising) {
stopAdvertising()
}
devices.clear()
services.clear()
characteristics.clear()
descriptors.clear()
mtus.clear()
confirms.clear()
centralsArgs.clear()
servicesArgs.clear()
characteristicsArgs.clear()
descriptorsArgs.clear()
setUpCallback = null
addServiceCallback = null
startAdvertisingCallback = null
notifyCharacteristicValueChangedCallbacks.clear()
}
override fun register() {
super.register()
registered = true
}
override fun unregister() {
super.unregister()
registered = false
}
override fun addService(serviceArgs: MyGattServiceArgs, callback: (Result<Unit>) -> Unit) {
try {
val unfinishedCallback = addServiceCallback
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val service = serviceArgs.toService()
val characteristicsArgs = serviceArgs.characteristicsArgs.filterNotNull()
for (characteristicArgs in characteristicsArgs) {
val characteristic = characteristicArgs.toCharacteristic()
val cccDescriptor = BluetoothGattDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID, BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE)
val cccDescriptorAdded = characteristic.addDescriptor(cccDescriptor)
if (!cccDescriptorAdded) {
throw IllegalStateException()
}
val descriptorsArgs = characteristicArgs.descriptorsArgs.filterNotNull()
for (descriptorArgs in descriptorsArgs) {
val descriptor = descriptorArgs.toDescriptor()
if (descriptor.uuid == CLIENT_CHARACTERISTIC_CONFIG_UUID) {
// Already added.
continue
}
val descriptorAdded = characteristic.addDescriptor(descriptor)
if (!descriptorAdded) {
throw IllegalStateException()
}
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
val descriptorHashCode = descriptor.hashCode()
this.descriptorsArgs[descriptorHashCode] = descriptorArgs
this.descriptors[descriptorHashCodeArgs] = descriptor
}
val characteristicAdded = service.addCharacteristic(characteristic)
if (!characteristicAdded) {
throw IllegalStateException()
}
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
val characteristicHashCode = characteristic.hashCode()
this.characteristicsArgs[characteristicHashCode] = characteristicArgs
this.characteristics[characteristicHashCodeArgs] = characteristic
}
val adding = server.addService(service)
if (!adding) {
throw IllegalStateException()
}
val serviceHashCodeArgs = serviceArgs.hashCodeArgs
val serviceHashCode = service.hashCode()
this.servicesArgs[serviceHashCode] = serviceArgs
this.services[serviceHashCodeArgs] = service
addServiceCallback = callback
} catch (e: Throwable) {
freeService(serviceArgs)
callback(Result.failure(e))
}
}
override fun removeService(serviceHashCodeArgs: Long) {
val service = services[serviceHashCodeArgs] as BluetoothGattService
val serviceHashCode = service.hashCode()
val serviceArgs = servicesArgs[serviceHashCode] as MyGattServiceArgs
val removed = server.removeService(service)
if (!removed) {
throw IllegalStateException()
}
freeService(serviceArgs)
}
private fun freeService(serviceArgs: MyGattServiceArgs) {
val characteristicsArgs = serviceArgs.characteristicsArgs.filterNotNull()
for (characteristicArgs in characteristicsArgs) {
val descriptorsArgs = characteristicArgs.descriptorsArgs.filterNotNull()
for (descriptorArgs in descriptorsArgs) {
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
val descriptor = this.descriptors.remove(descriptorHashCodeArgs) ?: continue
val descriptorHashCode = descriptor.hashCode()
this.descriptorsArgs.remove(descriptorHashCode)
}
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
val characteristic = this.characteristics.remove(characteristicHashCodeArgs) ?: continue
this.confirms.remove(characteristicHashCodeArgs)
val characteristicHashCode = characteristic.hashCode()
this.characteristicsArgs.remove(characteristicHashCode)
}
val serviceHashCodeArgs = serviceArgs.hashCodeArgs
val service = services.remove(serviceHashCodeArgs) ?: return
val serviceHashCode = service.hashCode()
servicesArgs.remove(serviceHashCode)
}
override fun clearServices() {
server.clearServices()
val servicesArgs = this.servicesArgs.values
for (serviceArgs in servicesArgs) {
freeService(serviceArgs)
}
}
override fun startAdvertising(advertiseDataArgs: MyAdvertiseDataArgs, callback: (Result<Unit>) -> Unit) {
try {
if (startAdvertisingCallback != null) {
throw IllegalStateException()
}
val settings = AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).setConnectable(true).build()
val advertiseData = advertiseDataArgs.toAdvertiseData(adapter)
advertiser.startAdvertising(settings, advertiseData, advertiseCallback)
startAdvertisingCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun stopAdvertising() {
advertiser.stopAdvertising(advertiseCallback)
advertising = false
}
override fun getMaximumWriteLength(centralHashCodeArgs: Long): Long {
val mtu = mtus[centralHashCodeArgs] ?: 23
return (mtu - 3).toLong()
}
override fun sendReadCharacteristicReply(centralHashCodeArgs: Long, characteristicHashCodeArgs: Long, idArgs: Long, offsetArgs: Long, statusArgs: Boolean, valueArgs: ByteArray) {
val device = devices[centralHashCodeArgs] as BluetoothDevice
val requestId = idArgs.toInt()
val status = if (statusArgs) BluetoothGatt.GATT_SUCCESS
else BluetoothGatt.GATT_FAILURE
val offset = offsetArgs.toInt()
val sent = server.sendResponse(device, requestId, status, offset, valueArgs)
if (!sent) {
throw IllegalStateException("Send read characteristic reply failed.")
}
}
override fun sendWriteCharacteristicReply(centralHashCodeArgs: Long, characteristicHashCodeArgs: Long, idArgs: Long, offsetArgs: Long, statusArgs: Boolean) {
val device = devices[centralHashCodeArgs] as BluetoothDevice
val requestId = idArgs.toInt()
val status = if (statusArgs) BluetoothGatt.GATT_SUCCESS
else BluetoothGatt.GATT_FAILURE
val offset = offsetArgs.toInt()
val value = null
val sent = server.sendResponse(device, requestId, status, offset, value)
if (!sent) {
throw IllegalStateException("Send write characteristic reply failed.")
}
}
override fun notifyCharacteristicValueChanged(centralHashCodeArgs: Long, characteristicHashCodeArgs: Long, valueArgs: ByteArray, callback: (Result<Unit>) -> Unit) {
try {
val unfinishedCallback = notifyCharacteristicValueChangedCallbacks[centralHashCodeArgs]
if (unfinishedCallback != null) {
throw IllegalStateException()
}
val device = devices[centralHashCodeArgs] as BluetoothDevice
val characteristic = characteristics[characteristicHashCodeArgs] as BluetoothGattCharacteristic
val confirm = confirms[characteristicHashCodeArgs]
?: throw IllegalStateException("The characteristic is not subscribed.")
val notifying = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val statusCode = server.notifyCharacteristicChanged(device, characteristic, confirm, valueArgs)
statusCode == BluetoothStatusCodes.SUCCESS
} else {
characteristic.value = valueArgs
server.notifyCharacteristicChanged(device, characteristic, confirm)
}
if (!notifying) {
throw IllegalStateException()
}
notifyCharacteristicValueChangedCallbacks[centralHashCodeArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun onRequestPermissionsResult(requestCode: Int, results: IntArray): Boolean {
if (requestCode != REQUEST_CODE) {
return false
}
val authorized = results.all { r -> r == PackageManager.PERMISSION_GRANTED }
val callback = setUpCallback ?: return false
setUpCallback = null
val stateArgs = if (authorized) adapter.stateArgs
else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
val stateNumberArgs = stateArgs.raw.toLong()
val args = MyPeripheralManagerArgs(stateNumberArgs)
callback(Result.success(args))
if (authorized) {
register()
}
return true
}
override fun onReceive(intent: Intent) {
val action = intent.action
if (action != BluetoothAdapter.ACTION_STATE_CHANGED) {
return
}
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
val stateArgs = state.toBluetoothLowEnergyStateArgs()
val stateNumberArgs = stateArgs.raw.toLong()
api.onStateChanged(stateNumberArgs) {}
}
fun onServiceAdded(status: Int, service: BluetoothGattService) {
val callback = addServiceCallback ?: return
addServiceCallback = null
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val hashCode = service.hashCode()
val serviceArgs = servicesArgs[hashCode] as MyGattServiceArgs
freeService(serviceArgs)
val error = IllegalStateException("Read rssi failed with status: $status")
callback(Result.failure(error))
}
}
fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
advertising = true
val callback = startAdvertisingCallback ?: return
startAdvertisingCallback = null
callback(Result.success(Unit))
}
fun onStartFailure(errorCode: Int) {
val callback = startAdvertisingCallback ?: return
startAdvertisingCallback = null
val error = IllegalStateException("Start advertising failed with error code: $errorCode")
callback(Result.failure(error))
}
fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
val hashCode = device.hashCode()
val centralArgs = centralsArgs.getOrPut(hashCode) { device.toCentralArgs() }
val centralHashCodeArgs = centralArgs.hashCodeArgs
devices[centralHashCodeArgs] = device
mtus[centralHashCodeArgs] = mtu
}
fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) {
val deviceHashCode = device.hashCode()
val centralArgs = centralsArgs.getOrPut(deviceHashCode) { device.toCentralArgs() }
val centralHashCodeArgs = centralArgs.hashCodeArgs
devices[centralHashCodeArgs] = device
val characteristicHashCode = characteristic.hashCode()
val characteristicArgs = characteristicsArgs[characteristicHashCode] as MyGattCharacteristicArgs
val idArgs = requestId.toLong()
val offsetArgs = offset.toLong()
api.onReadCharacteristicCommandReceived(centralArgs, characteristicArgs, idArgs, offsetArgs) {}
}
fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
val deviceHashCode = device.hashCode()
val centralArgs = centralsArgs.getOrPut(deviceHashCode) { device.toCentralArgs() }
val centralHashCodeArgs = centralArgs.hashCodeArgs
devices[centralHashCodeArgs] = device
val characteristicHashCode = characteristic.hashCode()
val characteristicArgs = characteristicsArgs[characteristicHashCode] as MyGattCharacteristicArgs
val idArgs = requestId.toLong()
val offsetArgs = offset.toLong()
api.onWriteCharacteristicCommandReceived(centralArgs, characteristicArgs, idArgs, offsetArgs, value) {}
}
fun onDescriptorReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor) {
val status = BluetoothGatt.GATT_SUCCESS
val descriptorHashCode = descriptor.hashCode()
val descriptorArgs = descriptorsArgs[descriptorHashCode] as MyGattDescriptorArgs
val value = descriptorArgs.valueArgs
val sent = server.sendResponse(device, requestId, status, offset, value)
if (!sent) {
Log.e(TAG, "onDescriptorReadRequest: send response failed.")
}
}
fun onDescriptorWriteRequest(device: BluetoothDevice, requestId: Int, descriptor: BluetoothGattDescriptor, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
val status = if (descriptor.uuid == CLIENT_CHARACTERISTIC_CONFIG_UUID) {
val deviceHashCode = device.hashCode()
val centralArgs = centralsArgs.getOrPut(deviceHashCode) { device.toCentralArgs() }
val centralHashCodeArgs = centralArgs.hashCodeArgs
devices[centralHashCodeArgs] = device
val characteristic = descriptor.characteristic
val characteristicHashCode = characteristic.hashCode()
val characteristicArgs = characteristicsArgs[characteristicHashCode] as MyGattCharacteristicArgs
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
// TODO: what is 中缀?
if (value contentEquals BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) {
confirms[characteristicHashCodeArgs] = false
val stateArgs = true
api.onNotifyCharacteristicCommandReceived(centralArgs, characteristicArgs, stateArgs) {}
BluetoothGatt.GATT_SUCCESS
} else if (value contentEquals BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) {
confirms[characteristicHashCodeArgs] = true
val stateArgs = true
api.onNotifyCharacteristicCommandReceived(centralArgs, characteristicArgs, stateArgs) {}
BluetoothGatt.GATT_SUCCESS
} else if (value contentEquals BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE) {
confirms.remove(characteristicHashCodeArgs)
val stateArgs = false
api.onNotifyCharacteristicCommandReceived(centralArgs, characteristicArgs, stateArgs) {}
BluetoothGatt.GATT_SUCCESS
} else {
BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED
}
} else BluetoothGatt.GATT_SUCCESS
val sent = server.sendResponse(device, requestId, status, offset, value)
if (!sent) {
Log.e(TAG, "onDescriptorReadRequest: send response failed.")
}
}
fun onNotificationSent(device: BluetoothDevice, status: Int) {
val deviceHashCode = device.hashCode()
val centralArgs = centralsArgs[deviceHashCode] as MyCentralArgs
val centralHashCodeArgs = centralArgs.hashCodeArgs
val callback = notifyCharacteristicValueChangedCallbacks.remove(centralHashCodeArgs)
?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val error = IllegalStateException("Notify characteristic value changed failed with status: $status")
callback(Result.failure(error))
}
}
}

View File

@ -2,8 +2,8 @@ package dev.yanshouwang.bluetooth_low_energy_android
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
class MyRequestPermissionResultListener(private val myCentralController: MyCentralController) : RequestPermissionsResultListener {
class MyRequestPermissionResultListener(private val bluetoothLowEnergyManager: MyBluetoothLowEnergyManager) : RequestPermissionsResultListener {
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean {
return myCentralController.onRequestPermissionsResult(requestCode, results)
return bluetoothLowEnergyManager.onRequestPermissionsResult(requestCode, results)
}
}

View File

@ -3,14 +3,14 @@ package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
class MyScanCallback(private val myCentralController: MyCentralController) : ScanCallback() {
class MyScanCallback(private val centralManager: MyCentralManager) : ScanCallback() {
override fun onScanFailed(errorCode: Int) {
super.onScanFailed(errorCode)
myCentralController.onScanFailed(errorCode)
centralManager.onScanFailed(errorCode)
}
override fun onScanResult(callbackType: Int, result: ScanResult) {
super.onScanResult(callbackType, result)
myCentralController.onScanResult(result)
centralManager.onScanResult(result)
}
}

View File

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

View File

@ -0,0 +1,224 @@
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_gatt_characteristic2.dart';
import 'my_gatt_descriptor2.dart';
import 'my_gatt_service2.dart';
export 'my_api.g.dart';
extension MyBluetoothLowEnergyStateArgsX on MyBluetoothLowEnergyStateArgs {
BluetoothLowEnergyState toState() {
return BluetoothLowEnergyState.values[index];
}
}
extension MyAdvertiseDataArgsX on MyAdvertiseDataArgs {
AdvertiseData toAdvertiseData() {
final name = nameArgs;
final serviceUUIDs = serviceUUIDsArgs
.cast<String>()
.map((args) => UUID.fromString(args))
.toList();
final serviceData = serviceDataArgs.cast<String, Uint8List>().map(
(uuidArgs, dataArgs) {
final uuid = UUID.fromString(uuidArgs);
final data = dataArgs;
return MapEntry(uuid, data);
},
);
final manufacturerSpecificData =
manufacturerSpecificDataArgs?.toManufacturerSpecificData();
return AdvertiseData(
name: name,
serviceUUIDs: serviceUUIDs,
serviceData: serviceData,
manufacturerSpecificData: manufacturerSpecificData,
);
}
}
extension MyManufacturerSpecificDataArgsX on MyManufacturerSpecificDataArgs {
ManufacturerSpecificData toManufacturerSpecificData() {
final id = idArgs;
final data = dataArgs;
return ManufacturerSpecificData(
id: id,
data: data,
);
}
}
extension MyGattCharacteristicPropertyArgsX
on MyGattCharacteristicPropertyArgs {
GattCharacteristicProperty toProperty() {
return GattCharacteristicProperty.values[index];
}
}
extension GattCharacteristicWriteTypeX on GattCharacteristicWriteType {
MyGattCharacteristicWriteTypeArgs toArgs() {
return MyGattCharacteristicWriteTypeArgs.values[index];
}
}
extension MyPeripheralArgsX on MyPeripheralArgs {
MyPeripheral toPeripheral() {
final hashCode = hashCodeArgs;
final uuid = UUID.fromString(uuidArgs);
return MyPeripheral(
hashCode: hashCode,
uuid: uuid,
);
}
}
extension MyGattServiceArgsX on MyGattServiceArgs {
MyGattService2 toService2() {
final hashCode = hashCodeArgs;
final uuid = UUID.fromString(uuidArgs);
final characteristics = characteristicsArgs
.cast<MyGattCharacteristicArgs>()
.map((args) => args.toCharacteristic2())
.toList();
return MyGattService2(
hashCode: hashCode,
uuid: uuid,
characteristics: characteristics,
);
}
}
extension MyGattCharacteristicArgsX on MyGattCharacteristicArgs {
MyGattCharacteristic2 toCharacteristic2() {
final hashCode = hashCodeArgs;
final uuid = UUID.fromString(uuidArgs);
final properties = propertyNumbersArgs.cast<int>().map(
(args) {
final propertyArgs = MyGattCharacteristicPropertyArgs.values[args];
return propertyArgs.toProperty();
},
).toList();
final descriptors = descriptorsArgs
.cast<MyGattDescriptorArgs>()
.map((args) => args.toDescriptor2())
.toList();
return MyGattCharacteristic2(
hashCode: hashCode,
uuid: uuid,
properties: properties,
descriptors: descriptors,
);
}
}
extension MyGattDescriptorArgsX on MyGattDescriptorArgs {
MyGattDescriptor2 toDescriptor2() {
final hashCode = hashCodeArgs;
final uuid = UUID.fromString(uuidArgs);
return MyGattDescriptor2(
hashCode: hashCode,
uuid: uuid,
);
}
}
extension MyCentralArgsX on MyCentralArgs {
MyCentral toCentral() {
final hashCode = hashCodeArgs;
final uuid = UUID.fromString(uuidArgs);
return MyCentral(
hashCode: hashCode,
uuid: uuid,
);
}
}
extension AdvertiseDataX on AdvertiseData {
MyAdvertiseDataArgs toArgs() {
final nameArgs = name;
final serviceUUIDsArgs =
serviceUUIDs.map((uuid) => uuid.toString()).toList();
final serviceDataArgs = serviceData.map((uuid, data) {
final uuidArgs = uuid.toString();
final dataArgs = data;
return MapEntry(uuidArgs, dataArgs);
});
final manufacturerSpecificDataArgs = manufacturerSpecificData?.toArgs();
return MyAdvertiseDataArgs(
nameArgs: nameArgs,
serviceUUIDsArgs: serviceUUIDsArgs,
serviceDataArgs: serviceDataArgs,
manufacturerSpecificDataArgs: manufacturerSpecificDataArgs,
);
}
}
extension ManufacturerSpecificDataX on ManufacturerSpecificData {
MyManufacturerSpecificDataArgs toArgs() {
final idArgs = id;
final dataArgs = data;
return MyManufacturerSpecificDataArgs(
idArgs: idArgs,
dataArgs: dataArgs,
);
}
}
extension MyGattServiceX on MyGattService {
MyGattServiceArgs toArgs() {
final hashCodeArgs = hashCode;
final uuidArgs = uuid.toString();
final characteristicsArgs = characteristics
.cast<MyGattCharacteristic>()
.map((characteristic) => characteristic.toArgs())
.toList();
return MyGattServiceArgs(
hashCodeArgs: hashCodeArgs,
uuidArgs: uuidArgs,
characteristicsArgs: characteristicsArgs,
);
}
}
extension MyGattCharacteristicX on MyGattCharacteristic {
MyGattCharacteristicArgs toArgs() {
final hashCodeArgs = hashCode;
final uuidArgs = uuid.toString();
final propertyNumbersArgs = properties.map((property) {
final propertyArgs = property.toArgs();
return propertyArgs.index;
}).toList();
final descriptorsArgs = descriptors
.cast<MyGattDescriptor>()
.map((descriptor) => descriptor.toArgs())
.toList();
return MyGattCharacteristicArgs(
hashCodeArgs: hashCodeArgs,
uuidArgs: uuidArgs,
propertyNumbersArgs: propertyNumbersArgs,
descriptorsArgs: descriptorsArgs,
);
}
}
extension MyGattDescriptorX on MyGattDescriptor {
MyGattDescriptorArgs toArgs() {
final hashCodeArgs = hashCode;
final uuidArgs = uuid.toString();
final valueArgs = value;
return MyGattDescriptorArgs(
hashCodeArgs: hashCodeArgs,
uuidArgs: uuidArgs,
valueArgs: valueArgs,
);
}
}
extension GattCharacteristicPropertyX on GattCharacteristicProperty {
MyGattCharacteristicPropertyArgs toArgs() {
return MyGattCharacteristicPropertyArgs.values[index];
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_central_manager.dart';
import 'my_peripheral_manager.dart';
class MyBluetoothLowEnergy extends BluetoothLowEnergy {
@override
final MyCentralManager centralManager;
@override
final MyPeripheralManager peripheralManager;
MyBluetoothLowEnergy()
: centralManager = MyCentralManager(),
peripheralManager = MyPeripheralManager();
}

View File

@ -0,0 +1,39 @@
import 'dart:async';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:flutter/foundation.dart';
abstract class MyBluetoothLowEnergyManager extends BluetoothLowEnergyManager {
MyBluetoothLowEnergyManager()
: _state = BluetoothLowEnergyState.unknown,
_stateChangedController = StreamController.broadcast();
final StreamController<BluetoothLowEnergyStateChangedEventArgs>
_stateChangedController;
BluetoothLowEnergyState _state;
@override
BluetoothLowEnergyState get state => _state;
@protected
set state(BluetoothLowEnergyState value) {
if (_state == value) {
return;
}
_state = value;
final eventArgs = BluetoothLowEnergyStateChangedEventArgs(state);
_stateChangedController.add(eventArgs);
}
@override
Stream<BluetoothLowEnergyStateChangedEventArgs> get stateChanged =>
_stateChangedController.stream;
@protected
Future<void> throwWithoutState(BluetoothLowEnergyState state) async {
if (this.state != state) {
throw BluetoothLowEnergyError(
'$state is expected, but current state is ${this.state}.',
);
}
}
}

View File

@ -1,366 +0,0 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_gatt_characteristic.dart';
import 'my_gatt_descriptor.dart';
import 'my_gatt_service.dart';
import 'my_peripheral.dart';
class MyCentralController extends CentralController
implements MyCentralControllerFlutterApi {
MyCentralController()
: _myApi = MyCentralControllerHostApi(),
_stateChangedController = StreamController.broadcast(),
_discoveredController = StreamController.broadcast(),
_peripheralStateChangedController = StreamController.broadcast(),
_characteristicValueChangedController = StreamController.broadcast(),
_myPeripherals = {},
_myServices = {},
_myCharacteristics = {},
_myDescriptors = {},
_state = CentralState.unknown;
final MyCentralControllerHostApi _myApi;
final StreamController<CentralStateChangedEventArgs> _stateChangedController;
final StreamController<CentralDiscoveredEventArgs> _discoveredController;
final StreamController<PeripheralStateChangedEventArgs>
_peripheralStateChangedController;
final StreamController<GattCharacteristicValueChangedEventArgs>
_characteristicValueChangedController;
final Map<int, MyPeripheral> _myPeripherals;
final Map<int, MyGattService> _myServices;
final Map<int, MyGattCharacteristic> _myCharacteristics;
final Map<int, MyGattDescriptor> _myDescriptors;
CentralState _state;
@override
CentralState get state => _state;
@override
Stream<CentralStateChangedEventArgs> get stateChanged =>
_stateChangedController.stream;
@override
Stream<CentralDiscoveredEventArgs> get discovered =>
_discoveredController.stream;
@override
Stream<PeripheralStateChangedEventArgs> get peripheralStateChanged =>
_peripheralStateChangedController.stream;
@override
Stream<GattCharacteristicValueChangedEventArgs>
get characteristicValueChanged =>
_characteristicValueChangedController.stream;
Future<void> _throwWithState(CentralState state) async {
if (this.state == state) {
throw BluetoothLowEnergyError('$state is unexpected.');
}
}
Future<void> _throwWithoutState(CentralState state) async {
if (this.state != state) {
throw BluetoothLowEnergyError(
'$state is expected, but current state is ${this.state}.',
);
}
}
@override
Future<void> setUp() async {
await _throwWithoutState(CentralState.unknown);
final args = await _myApi.setUp();
final myStateArgs = MyCentralStateArgs.values[args.myStateNumber];
_state = myStateArgs.toState();
MyCentralControllerFlutterApi.setup(this);
}
@override
Future<void> tearDown() async {
await _throwWithState(CentralState.unknown);
await _myApi.tearDown();
MyCentralControllerFlutterApi.setup(null);
_myPeripherals.clear();
_myServices.clear();
_myCharacteristics.clear();
_myDescriptors.clear();
_state = CentralState.unknown;
}
@override
Future<void> startDiscovery() async {
await _throwWithoutState(CentralState.poweredOn);
await _myApi.startDiscovery();
}
@override
Future<void> stopDiscovery() async {
await _throwWithoutState(CentralState.poweredOn);
await _myApi.stopDiscovery();
}
@override
Future<void> connect(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
await _myApi.connect(myPeripheral.hashCode);
}
@override
Future<void> disconnect(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
await _myApi.disconnect(myPeripheral.hashCode);
}
@override
Future<int> getMaximumWriteLength(
Peripheral peripheral, {
required GattCharacteristicWriteType type,
}) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
final maximumWriteLength = await _myApi.getMaximumWriteLength(
myPeripheral.hashCode,
);
return maximumWriteLength;
}
@override
Future<void> discoverGATT(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
await _myApi.discoverGATT(myPeripheral.hashCode);
}
@override
Future<List<GattService>> getServices(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
final myServiceArgses = await _myApi.getServices(myPeripheral.hashCode);
return myServiceArgses.cast<MyGattServiceArgs>().map(
(myServiceArgs) {
final myService = MyGattService.fromMyArgs(
myPeripheral,
myServiceArgs,
);
_myServices[myService.hashCode] = myService;
return myService;
},
).toList();
}
@override
Future<List<GattCharacteristic>> getCharacteristics(
GattService service,
) async {
await _throwWithoutState(CentralState.poweredOn);
final myService = service as MyGattService;
final myCharactersiticArgses = await _myApi.getCharacteristics(
myService.hashCode,
);
return myCharactersiticArgses.cast<MyGattCharacteristicArgs>().map(
(myCharacteristicArgs) {
final myCharacteristic = MyGattCharacteristic.fromMyArgs(
myService,
myCharacteristicArgs,
);
_myCharacteristics[myCharacteristic.hashCode] = myCharacteristic;
return myCharacteristic;
},
).toList();
}
@override
Future<List<GattDescriptor>> getDescriptors(
GattCharacteristic characteristic,
) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final myDescriptorArgses = await _myApi.getDescriptors(
myCharacteristic.hashCode,
);
return myDescriptorArgses.cast<MyGattDescriptorArgs>().map(
(myDescriptorArgs) {
final myDescriptor = MyGattDescriptor.fromMyArgs(
myCharacteristic,
myDescriptorArgs,
);
_myDescriptors[myDescriptor.hashCode] = myDescriptor;
return myDescriptor;
},
).toList();
}
@override
Future<Uint8List> readCharacteristic(
GattCharacteristic characteristic) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final myService = myCharacteristic.myService;
final myPeripheral = myService.myPeripheral;
final value = await _myApi.readCharacteristic(
myPeripheral.hashCode,
myService.hashCode,
myCharacteristic.hashCode,
);
return value;
}
@override
Future<void> writeCharacteristic(
GattCharacteristic characteristic, {
required Uint8List value,
required GattCharacteristicWriteType type,
}) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final myService = myCharacteristic.myService;
final myPeripheral = myService.myPeripheral;
final myTypeArgs = type.toMyArgs();
final myTypeNumber = myTypeArgs.index;
await _myApi.writeCharacteristic(
myPeripheral.hashCode,
myService.hashCode,
myCharacteristic.hashCode,
value,
myTypeNumber,
);
}
@override
Future<void> notifyCharacteristic(
GattCharacteristic characteristic, {
required bool state,
}) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final myService = myCharacteristic.myService;
final myPeripheral = myService.myPeripheral;
await _myApi.notifyCharacteristic(
myPeripheral.hashCode,
myService.hashCode,
myCharacteristic.hashCode,
state,
);
}
@override
Future<Uint8List> readDescriptor(GattDescriptor descriptor) async {
await _throwWithoutState(CentralState.poweredOn);
final myDescriptor = descriptor as MyGattDescriptor;
final myCharacteristic = myDescriptor.myCharacteristic;
final myService = myCharacteristic.myService;
final myPeripheral = myService.myPeripheral;
final value = await _myApi.readDescriptor(
myPeripheral.hashCode,
myCharacteristic.hashCode,
myDescriptor.hashCode,
);
return value;
}
@override
Future<void> writeDescriptor(
GattDescriptor descriptor, {
required Uint8List value,
}) async {
await _throwWithoutState(CentralState.poweredOn);
final myDescriptor = descriptor as MyGattDescriptor;
final myCharacteristic = myDescriptor.myCharacteristic;
final myService = myCharacteristic.myService;
final myPeripheral = myService.myPeripheral;
await _myApi.writeDescriptor(
myPeripheral.hashCode,
myCharacteristic.hashCode,
myDescriptor.hashCode,
value,
);
}
@override
void onStateChanged(int myStateNumber) {
final myStateArgs = MyCentralStateArgs.values[myStateNumber];
final state = myStateArgs.toState();
if (_state == state) {
return;
}
_state = state;
final eventArgs = CentralStateChangedEventArgs(state);
_stateChangedController.add(eventArgs);
}
@override
void onDiscovered(
MyPeripheralArgs myPeripheralArgs,
int rssi,
MyAdvertisementArgs myAdvertisementArgs,
) {
final myPeripheral = MyPeripheral.fromMyArgs(myPeripheralArgs);
_myPeripherals[myPeripheral.hashCode] = myPeripheral;
final advertisement = myAdvertisementArgs.toAdvertisement();
final eventArgs = CentralDiscoveredEventArgs(
myPeripheral,
rssi,
advertisement,
);
_discoveredController.add(eventArgs);
}
@override
void onPeripheralStateChanged(int myPeripheralKey, bool state) {
final myPeripheral = _myPeripherals[myPeripheralKey];
if (myPeripheral == null) {
return;
}
final eventArgs = PeripheralStateChangedEventArgs(myPeripheral, state);
_peripheralStateChangedController.add(eventArgs);
}
@override
void onCharacteristicValueChanged(int myCharacteristicKey, Uint8List value) {
final myCharacteristic =
_myCharacteristics[myCharacteristicKey] as MyGattCharacteristic;
final eventArgs = GattCharacteristicValueChangedEventArgs(
myCharacteristic,
value,
);
_characteristicValueChangedController.add(eventArgs);
}
}
extension on MyAdvertisementArgs {
Advertisement toAdvertisement() {
final serviceUUIDs = this
.serviceUUIDs
.cast<String>()
.map((uuid) => UUID.fromString(uuid))
.toList();
final serviceData = this.serviceData.cast<String, Uint8List>().map(
(uuid, data) {
final key = UUID.fromString(uuid);
final value = data;
return MapEntry(key, value);
},
);
return Advertisement(
name: name,
manufacturerSpecificData: manufacturerSpecificData.cast<int, Uint8List>(),
serviceUUIDs: serviceUUIDs,
serviceData: serviceData,
);
}
}
extension on MyCentralStateArgs {
CentralState toState() {
return CentralState.values[index];
}
}
extension on GattCharacteristicWriteType {
MyGattCharacteristicWriteTypeArgs toMyArgs() {
return MyGattCharacteristicWriteTypeArgs.values[index];
}
}

View File

@ -0,0 +1,267 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.dart';
import 'my_bluetooth_low_energy_manager.dart';
import 'my_gatt_characteristic2.dart';
import 'my_gatt_descriptor2.dart';
class MyCentralManager extends MyBluetoothLowEnergyManager
implements CentralManager, MyCentralManagerFlutterApi {
final MyCentralManagerHostApi _api;
final StreamController<DiscoveredEventArgs> _discoveredController;
final StreamController<PeripheralStateChangedEventArgs>
_peripheralStateChangedController;
final StreamController<GattCharacteristicValueChangedEventArgs>
_characteristicValueChangedController;
MyCentralManager()
: _api = MyCentralManagerHostApi(),
_discoveredController = StreamController.broadcast(),
_peripheralStateChangedController = StreamController.broadcast(),
_characteristicValueChangedController = StreamController.broadcast();
@override
Stream<DiscoveredEventArgs> get discovered => _discoveredController.stream;
@override
Stream<PeripheralStateChangedEventArgs> get peripheralStateChanged =>
_peripheralStateChangedController.stream;
@override
Stream<GattCharacteristicValueChangedEventArgs>
get characteristicValueChanged =>
_characteristicValueChangedController.stream;
@override
Future<void> setUp() async {
final args = await _api.setUp();
final stateArgs =
MyBluetoothLowEnergyStateArgs.values[args.stateNumberArgs];
state = stateArgs.toState();
MyCentralManagerFlutterApi.setup(this);
}
@override
Future<void> startDiscovery() async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
await _api.startDiscovery();
}
@override
Future<void> stopDiscovery() async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
await _api.stopDiscovery();
}
@override
Future<void> connect(Peripheral peripheral) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final peripheralHashCodeArgs = peripheral.hashCode;
await _api.connect(peripheralHashCodeArgs);
}
@override
Future<void> disconnect(Peripheral peripheral) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final peripheralHashCodeArgs = peripheral.hashCode;
await _api.disconnect(peripheralHashCodeArgs);
}
@override
Future<int> getMaximumWriteLength(
Peripheral peripheral, {
required GattCharacteristicWriteType type,
}) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final peripheralHashCodeArgs = peripheral.hashCode;
final maximumWriteLength =
await _api.getMaximumWriteLength(peripheralHashCodeArgs);
return maximumWriteLength;
}
@override
Future<int> readRSSI(Peripheral peripheral) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final peripheralHashCodeArgs = peripheral.hashCode;
final rssi = await _api.readRSSI(peripheralHashCodeArgs);
return rssi;
}
@override
Future<List<GattService>> discoverGATT(Peripheral peripheral) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (peripheral is! MyPeripheral) {
throw TypeError();
}
final peripheralHashCodeArgs = peripheral.hashCode;
final servicesArgs = await _api.discoverGATT(peripheralHashCodeArgs);
final services = servicesArgs
.cast<MyGattServiceArgs>()
.map((args) => args.toService2())
.toList();
for (var service in services) {
for (var charactersitic in service.characteristics) {
for (var descriptor in charactersitic.descriptors) {
descriptor.characteristic = charactersitic;
}
charactersitic.service = service;
}
service.peripheral = peripheral;
}
return services;
}
@override
Future<Uint8List> readCharacteristic(
GattCharacteristic characteristic,
) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (characteristic is! MyGattCharacteristic2) {
throw TypeError();
}
final service = characteristic.service;
final peripheral = service.peripheral;
final peripheralHashCodeArgs = peripheral.hashCode;
final characteristcHashCodeArgs = characteristic.hashCode;
final value = await _api.readCharacteristic(
peripheralHashCodeArgs,
characteristcHashCodeArgs,
);
return value;
}
@override
Future<void> writeCharacteristic(
GattCharacteristic characteristic, {
required Uint8List value,
required GattCharacteristicWriteType type,
}) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (characteristic is! MyGattCharacteristic2) {
throw TypeError();
}
final service = characteristic.service;
final peripheral = service.peripheral;
final peripheralHashCodeArgs = peripheral.hashCode;
final characteristcHashCodeArgs = characteristic.hashCode;
final valueArgs = value;
final typeArgs = type.toArgs();
final typeNumberArgs = typeArgs.index;
await _api.writeCharacteristic(
peripheralHashCodeArgs,
characteristcHashCodeArgs,
valueArgs,
typeNumberArgs,
);
}
@override
Future<void> notifyCharacteristic(
GattCharacteristic characteristic, {
required bool state,
}) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (characteristic is! MyGattCharacteristic2) {
throw TypeError();
}
final service = characteristic.service;
final peripheral = service.peripheral;
final peripheralHashCodeArgs = peripheral.hashCode;
final characteristcHashCodeArgs = characteristic.hashCode;
final stateArgs = state;
await _api.notifyCharacteristic(
peripheralHashCodeArgs,
characteristcHashCodeArgs,
stateArgs,
);
}
@override
Future<Uint8List> readDescriptor(GattDescriptor descriptor) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (descriptor is! MyGattDescriptor2) {
throw TypeError();
}
final characteristic = descriptor.characteristic;
final service = characteristic.service;
final peripheral = service.peripheral;
final peripheralHashCodeArgs = peripheral.hashCode;
final descriptorHashCodeArgs = descriptor.hashCode;
final value = await _api.readDescriptor(
peripheralHashCodeArgs,
descriptorHashCodeArgs,
);
return value;
}
@override
Future<void> writeDescriptor(
GattDescriptor descriptor, {
required Uint8List value,
}) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (descriptor is! MyGattDescriptor2) {
throw TypeError();
}
final characteristic = descriptor.characteristic;
final service = characteristic.service;
final peripheral = service.peripheral;
final peripheralHashCodeArgs = peripheral.hashCode;
final descriptorHashCodeArgs = descriptor.hashCode;
final valueArgs = value;
await _api.writeDescriptor(
peripheralHashCodeArgs,
descriptorHashCodeArgs,
valueArgs,
);
}
@override
void onStateChanged(int stateNumberArgs) {
final stateArgs = MyBluetoothLowEnergyStateArgs.values[stateNumberArgs];
state = stateArgs.toState();
}
@override
void onDiscovered(
MyPeripheralArgs peripheralArgs,
int rssiArgs,
MyAdvertiseDataArgs advertiseDataArgs,
) {
final peripheral = peripheralArgs.toPeripheral();
final rssi = rssiArgs;
final advertiseData = advertiseDataArgs.toAdvertiseData();
final eventArgs = DiscoveredEventArgs(
peripheral,
rssi,
advertiseData,
);
_discoveredController.add(eventArgs);
}
@override
void onPeripheralStateChanged(
MyPeripheralArgs peripheralArgs,
bool stateArgs,
) {
final peripheral = peripheralArgs.toPeripheral();
final state = stateArgs;
final eventArgs = PeripheralStateChangedEventArgs(peripheral, state);
_peripheralStateChangedController.add(eventArgs);
}
@override
void onCharacteristicValueChanged(
MyGattCharacteristicArgs characteristicArgs,
Uint8List valueArgs,
) {
final characteristic = characteristicArgs.toCharacteristic2();
final value = valueArgs;
final eventArgs = GattCharacteristicValueChangedEventArgs(
characteristic,
value,
);
_characteristicValueChangedController.add(eventArgs);
}
}

View File

@ -1,42 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_gatt_service.dart';
import 'my_object.dart';
class MyGattCharacteristic extends MyObject implements GattCharacteristic {
final MyGattService myService;
@override
final UUID uuid;
@override
final List<GattCharacteristicProperty> properties;
MyGattCharacteristic(
super.hashCode,
this.myService,
this.uuid,
this.properties,
);
factory MyGattCharacteristic.fromMyArgs(
MyGattService myService,
MyGattCharacteristicArgs myArgs,
) {
final hashCode = myArgs.key;
final uuid = UUID.fromString(myArgs.uuid);
final properties = myArgs.myPropertyNumbers.cast<int>().map(
(myPropertyNumber) {
final myPropertyArgs =
MyGattCharacteristicPropertyArgs.values[myPropertyNumber];
return myPropertyArgs.toProperty();
},
).toList();
return MyGattCharacteristic(hashCode, myService, uuid, properties);
}
}
extension on MyGattCharacteristicPropertyArgs {
GattCharacteristicProperty toProperty() {
return GattCharacteristicProperty.values[index];
}
}

View File

@ -0,0 +1,19 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_gatt_descriptor2.dart';
import 'my_gatt_service2.dart';
class MyGattCharacteristic2 extends MyGattCharacteristic {
late final MyGattService2 service;
MyGattCharacteristic2({
super.hashCode,
required super.uuid,
required super.properties,
required List<MyGattDescriptor2> descriptors,
}) : super(descriptors: descriptors);
@override
List<MyGattDescriptor2> get descriptors =>
super.descriptors.cast<MyGattDescriptor2>();
}

View File

@ -1,22 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_gatt_characteristic.dart';
import 'my_object.dart';
class MyGattDescriptor extends MyObject implements GattDescriptor {
final MyGattCharacteristic myCharacteristic;
@override
final UUID uuid;
MyGattDescriptor(super.hashCode, this.myCharacteristic, this.uuid);
factory MyGattDescriptor.fromMyArgs(
MyGattCharacteristic myCharacteristic,
MyGattDescriptorArgs myArgs,
) {
final hashCode = myArgs.key;
final uuid = UUID.fromString(myArgs.uuid);
return MyGattDescriptor(hashCode, myCharacteristic, uuid);
}
}

View File

@ -0,0 +1,12 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_gatt_characteristic2.dart';
class MyGattDescriptor2 extends MyGattDescriptor {
late final MyGattCharacteristic2 characteristic;
MyGattDescriptor2({
super.hashCode,
required super.uuid,
});
}

View File

@ -1,22 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_object.dart';
import 'my_peripheral.dart';
class MyGattService extends MyObject implements GattService {
final MyPeripheral myPeripheral;
@override
final UUID uuid;
MyGattService(super.hashCode, this.myPeripheral, this.uuid);
factory MyGattService.fromMyArgs(
MyPeripheral myPeripheral,
MyGattServiceArgs myArgs,
) {
final hashCode = myArgs.key;
final uuid = UUID.fromString(myArgs.uuid);
return MyGattService(hashCode, myPeripheral, uuid);
}
}

View File

@ -0,0 +1,17 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_gatt_characteristic2.dart';
class MyGattService2 extends MyGattService {
late final MyPeripheral peripheral;
MyGattService2({
super.hashCode,
required super.uuid,
required List<MyGattCharacteristic2> characteristics,
}) : super(characteristics: characteristics);
@override
List<MyGattCharacteristic2> get characteristics =>
super.characteristics.cast<MyGattCharacteristic2>();
}

View File

@ -1,17 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_object.dart';
class MyPeripheral extends MyObject implements Peripheral {
@override
final UUID uuid;
MyPeripheral(super.hashCode, this.uuid);
factory MyPeripheral.fromMyArgs(MyPeripheralArgs myArgs) {
final hashCode = myArgs.key;
final uuid = UUID.fromString(myArgs.uuid);
return MyPeripheral(hashCode, uuid);
}
}

View File

@ -0,0 +1,228 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.dart';
import 'my_bluetooth_low_energy_manager.dart';
class MyPeripheralManager extends MyBluetoothLowEnergyManager
implements PeripheralManager, MyPeripheralManagerFlutterApi {
final MyPeripheralManagerHostApi _api;
final StreamController<ReadGattCharacteristicCommandEventArgs>
_readCharacteristicCommandReceivedController;
final StreamController<WriteGattCharacteristicCommandEventArgs>
_writeCharacteristicCommandReceivedController;
final StreamController<NotifyGattCharacteristicCommandEventArgs>
_notifyCharacteristicCommandReceivedController;
MyPeripheralManager()
: _api = MyPeripheralManagerHostApi(),
_readCharacteristicCommandReceivedController =
StreamController.broadcast(),
_writeCharacteristicCommandReceivedController =
StreamController.broadcast(),
_notifyCharacteristicCommandReceivedController =
StreamController.broadcast();
@override
Stream<ReadGattCharacteristicCommandEventArgs>
get readCharacteristicCommandReceived =>
_readCharacteristicCommandReceivedController.stream;
@override
Stream<WriteGattCharacteristicCommandEventArgs>
get writeCharacteristicCommandReceived =>
_writeCharacteristicCommandReceivedController.stream;
@override
Stream<NotifyGattCharacteristicCommandEventArgs>
get notifyCharacteristicCommandReceived =>
_notifyCharacteristicCommandReceivedController.stream;
@override
Future<void> setUp() async {
final args = await _api.setUp();
final stateArgs =
MyBluetoothLowEnergyStateArgs.values[args.stateNumberArgs];
state = stateArgs.toState();
MyPeripheralManagerFlutterApi.setup(this);
}
@override
Future<void> addService(GattService service) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (service is! MyGattService) {
throw TypeError();
}
final serviceArgs = service.toArgs();
await _api.addService(serviceArgs);
}
@override
Future<void> removeService(GattService service) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final serviceHashCodeArgs = service.hashCode;
await _api.removeService(serviceHashCodeArgs);
}
@override
Future<void> clearServices() async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
await _api.clearServices();
}
@override
Future<void> startAdvertising(AdvertiseData advertiseData) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final advertiseDataArgs = advertiseData.toArgs();
await _api.startAdvertising(advertiseDataArgs);
}
@override
Future<void> stopAdvertising() async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
await _api.stopAdvertising();
}
@override
Future<int> getMaximumWriteLength(Central central) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final centralHashCodeArgs = central.hashCode;
final maximumWriteLength =
await _api.getMaximumWriteLength(centralHashCodeArgs);
return maximumWriteLength;
}
@override
Future<void> sendReadCharacteristicReply(
Central central,
GattCharacteristic characteristic,
int id,
int offset,
bool status,
Uint8List value,
) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final centralHashCodeArgs = central.hashCode;
final characteristicHashCodeArgs = characteristic.hashCode;
final idArgs = id;
final offsetArgs = offset;
final statusArgs = status;
final valueArgs = value;
await _api.sendReadCharacteristicReply(
centralHashCodeArgs,
characteristicHashCodeArgs,
idArgs,
offsetArgs,
statusArgs,
valueArgs,
);
}
@override
Future<void> sendWriteCharacteristicReply(
Central central,
GattCharacteristic characteristic,
int id,
int offset,
bool status,
) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final centralHashCodeArgs = central.hashCode;
final characteristicHashCodeArgs = characteristic.hashCode;
final idArgs = id;
final offsetArgs = offset;
final statusArgs = status;
await _api.sendWriteCharacteristicReply(
centralHashCodeArgs,
characteristicHashCodeArgs,
idArgs,
offsetArgs,
statusArgs,
);
}
@override
Future<void> notifyCharacteristicValueChanged(
Central central,
GattCharacteristic characteristic,
Uint8List value,
) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final centralHashCodeArgs = central.hashCode;
final characteristicHashCodeArgs = characteristic.hashCode;
final valueArgs = value;
await _api.notifyCharacteristicValueChanged(
centralHashCodeArgs,
characteristicHashCodeArgs,
valueArgs,
);
}
@override
void onStateChanged(int stateNumberArgs) {
final stateArgs = MyBluetoothLowEnergyStateArgs.values[stateNumberArgs];
state = stateArgs.toState();
}
@override
void onReadCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
int idArgs,
int offsetArgs,
) {
final central = centralArgs.toCentral();
final characteristic = characteristicArgs.toCharacteristic2();
final id = idArgs;
final offset = offsetArgs;
final eventArgs = ReadGattCharacteristicCommandEventArgs(
central,
characteristic,
id,
offset,
);
_readCharacteristicCommandReceivedController.add(eventArgs);
}
@override
void onWriteCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
int idArgs,
int offsetArgs,
Uint8List valueArgs,
) {
final central = centralArgs.toCentral();
final characteristic = characteristicArgs.toCharacteristic2();
final id = idArgs;
final offset = offsetArgs;
final value = valueArgs;
final eventArgs = WriteGattCharacteristicCommandEventArgs(
central,
characteristic,
id,
offset,
value,
);
_writeCharacteristicCommandReceivedController.add(eventArgs);
}
@override
void onNotifyCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
bool stateArgs,
) {
final central = centralArgs.toCentral();
final characteristic = characteristicArgs.toCharacteristic2();
final state = stateArgs;
final eventArgs = NotifyGattCharacteristicCommandEventArgs(
central,
characteristic,
state,
);
_notifyCharacteristicCommandReceivedController.add(eventArgs);
}
}

View File

@ -12,126 +12,215 @@ import 'package:pigeon/pigeon.dart';
),
)
@HostApi()
abstract class MyCentralControllerHostApi {
abstract class MyCentralManagerHostApi {
@async
MyCentralControllerArgs setUp();
void tearDown();
MyCentralManagerArgs setUp();
@async
void startDiscovery();
void stopDiscovery();
@async
void connect(int myPeripheralKey);
void connect(int peripheralHashCodeArgs);
@async
void disconnect(int myPeripheralKey);
void disconnect(int peripheralHashCodeArgs);
@async
int getMaximumWriteLength(int myPeripheralKey);
int getMaximumWriteLength(int peripheralHashCodeArgs);
@async
void discoverGATT(int myPeripheralKey);
List<MyGattServiceArgs> getServices(int myPeripheralKey);
List<MyGattCharacteristicArgs> getCharacteristics(int myServiceKey);
List<MyGattDescriptorArgs> getDescriptors(int myCharacteristicKey);
int readRSSI(int peripheralHashCodeArgs);
@async
List<MyGattServiceArgs> discoverGATT(int peripheralHashCodeArgs);
@async
Uint8List readCharacteristic(
int myPeripheralKey,
int myServiceKey,
int myCharacteristicKey,
int peripheralHashCodeArgs,
int characteristicHashCodeArgs,
);
@async
void writeCharacteristic(
int myPeripheralKey,
int myServiceKey,
int myCharacteristicKey,
Uint8List value,
int myTypeNumber,
int peripheralHashCodeArgs,
int characteristicHashCodeArgs,
Uint8List valueArgs,
int typeNumberArgs,
);
@async
void notifyCharacteristic(
int myPeripheralKey,
int myServiceKey,
int myCharacteristicKey,
bool state,
int peripheralHashCodeArgs,
int characteristicHashCodeArgs,
bool stateArgs,
);
@async
Uint8List readDescriptor(
int myPeripheralKey,
int myCharacteristicKey,
int myDescriptorKey,
int peripheralHashCodeArgs,
int descriptorHashCodeArgs,
);
@async
void writeDescriptor(
int myPeripheralKey,
int myCharacteristicKey,
int myDescriptorKey,
Uint8List value,
int peripheralHashCodeArgs,
int descriptorHashCodeArgs,
Uint8List valueArgs,
);
}
@FlutterApi()
abstract class MyCentralControllerFlutterApi {
void onStateChanged(int myStateNumber);
abstract class MyCentralManagerFlutterApi {
void onStateChanged(int stateNumberArgs);
void onDiscovered(
MyPeripheralArgs myPeripheralArgs,
int rssi,
MyAdvertisementArgs myAdvertisementArgs,
MyPeripheralArgs peripheralArgs,
int rssiArgs,
MyAdvertiseDataArgs advertiseDataArgs,
);
void onPeripheralStateChanged(
MyPeripheralArgs peripheralArgs,
bool stateArgs,
);
void onCharacteristicValueChanged(
MyGattCharacteristicArgs characteristicArgs,
Uint8List valueArgs,
);
void onPeripheralStateChanged(int myPeripheralKey, bool state);
void onCharacteristicValueChanged(int myCharacteristicKey, Uint8List value);
}
class MyCentralControllerArgs {
final int myStateNumber;
@HostApi()
abstract class MyPeripheralManagerHostApi {
@async
MyPeripheralManagerArgs setUp();
@async
void addService(MyGattServiceArgs serviceArgs);
void removeService(int serviceHashCodeArgs);
void clearServices();
@async
void startAdvertising(MyAdvertiseDataArgs advertiseDataArgs);
void stopAdvertising();
int getMaximumWriteLength(int centralHashCodeArgs);
void sendReadCharacteristicReply(
int centralHashCodeArgs,
int characteristicHashCodeArgs,
int idArgs,
int offsetArgs,
bool statusArgs,
Uint8List valueArgs,
);
void sendWriteCharacteristicReply(
int centralHashCodeArgs,
int characteristicHashCodeArgs,
int idArgs,
int offsetArgs,
bool statusArgs,
);
@async
void notifyCharacteristicValueChanged(
int centralHashCodeArgs,
int characteristicHashCodeArgs,
Uint8List valueArgs,
);
}
MyCentralControllerArgs(this.myStateNumber);
@FlutterApi()
abstract class MyPeripheralManagerFlutterApi {
void onStateChanged(int stateNumberArgs);
void onReadCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
int idArgs,
int offsetArgs,
);
void onWriteCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
int idArgs,
int offsetArgs,
Uint8List valueArgs,
);
void onNotifyCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
bool stateArgs,
);
}
class MyCentralManagerArgs {
final int stateNumberArgs;
MyCentralManagerArgs(this.stateNumberArgs);
}
class MyPeripheralManagerArgs {
final int stateNumberArgs;
MyPeripheralManagerArgs(this.stateNumberArgs);
}
class MyCentralArgs {
final int hashCodeArgs;
final String uuidArgs;
MyCentralArgs(this.hashCodeArgs, this.uuidArgs);
}
class MyPeripheralArgs {
final int key;
final String uuid;
final int hashCodeArgs;
final String uuidArgs;
MyPeripheralArgs(this.key, this.uuid);
MyPeripheralArgs(this.hashCodeArgs, this.uuidArgs);
}
class MyAdvertisementArgs {
final String? name;
final Map<int?, Uint8List?> manufacturerSpecificData;
final List<String?> serviceUUIDs;
final Map<String?, Uint8List?> serviceData;
class MyAdvertiseDataArgs {
final String? nameArgs;
final List<String?> serviceUUIDsArgs;
final Map<String?, Uint8List?> serviceDataArgs;
final MyManufacturerSpecificDataArgs? manufacturerSpecificDataArgs;
MyAdvertisementArgs(
this.name,
this.manufacturerSpecificData,
this.serviceUUIDs,
this.serviceData,
MyAdvertiseDataArgs(
this.nameArgs,
this.serviceUUIDsArgs,
this.serviceDataArgs,
this.manufacturerSpecificDataArgs,
);
}
class MyGattServiceArgs {
final int key;
final String uuid;
class MyManufacturerSpecificDataArgs {
final int idArgs;
final Uint8List dataArgs;
MyGattServiceArgs(this.key, this.uuid);
MyManufacturerSpecificDataArgs(this.idArgs, this.dataArgs);
}
class MyGattServiceArgs {
final int hashCodeArgs;
final String uuidArgs;
final List<MyGattCharacteristicArgs?> characteristicsArgs;
MyGattServiceArgs(
this.hashCodeArgs,
this.uuidArgs,
this.characteristicsArgs,
);
}
class MyGattCharacteristicArgs {
final int key;
final String uuid;
final List<int?> myPropertyNumbers;
final int hashCodeArgs;
final String uuidArgs;
final List<int?> propertyNumbersArgs;
final List<MyGattDescriptorArgs?> descriptorsArgs;
MyGattCharacteristicArgs(
this.key,
this.uuid,
this.myPropertyNumbers,
this.hashCodeArgs,
this.uuidArgs,
this.propertyNumbersArgs,
this.descriptorsArgs,
);
}
class MyGattDescriptorArgs {
final int key;
final String uuid;
final int hashCodeArgs;
final String uuidArgs;
final Uint8List? valueArgs;
MyGattDescriptorArgs(this.key, this.uuid);
MyGattDescriptorArgs(
this.hashCodeArgs,
this.uuidArgs,
this.valueArgs,
);
}
enum MyCentralStateArgs {
enum MyBluetoothLowEnergyStateArgs {
unknown,
unsupported,
unauthorized,

View File

@ -1,6 +1,6 @@
name: bluetooth_low_energy_android
description: Android implementation of the bluetooth_low_energy plugin.
version: 2.2.1
version: 3.0.0
homepage: https://github.com/yanshouwang/bluetooth_low_energy
environment:
@ -10,13 +10,13 @@ environment:
dependencies:
flutter:
sdk: flutter
bluetooth_low_energy_platform_interface: ^2.2.0
bluetooth_low_energy_platform_interface: ^3.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
pigeon: ^10.1.6
pigeon: ^11.0.1
flutter:
plugin:

View File

@ -1,3 +1,37 @@
## 3.0.0
* Add `PeripheralManager` api.
* Add `CentralManager#readRSSI` method.
* Add `CentralManager.instance` api.
* Add `PeripheralManager.instance` api.
* Move `CentralController` to `CentralManager`.
* Move `CentralState` to `BluetoothLowEnergyState`.
* Move `CentralDiscoveredEventArgs` to `DiscoveredEventArgs`.
* Move `Advertisement` class to `AdvertiseData` class.
* Move `setUp` method from `BluetoothLowEnergy` class to `BluetoothLowEnergyManger` class.
* Change the type of `manufacturerSpecificData` from `Map<int, Uint8List>` to `ManufacturerSpecificData`.
* [Fix the issue that `UUID.fromString()` throw FormatException with 32 bits UUID string.](https://github.com/yanshouwang/bluetooth_low_energy/issues/13)
* Fix known issues.
## 3.0.0-dev.4
* Move `Advertisement` class to `AdvertiseData` class.
## 3.0.0-dev.3
* [Fix the issue that `UUID.fromString()` throw FormatException with 32 bits UUID string.](https://github.com/yanshouwang/bluetooth_low_energy/issues/13)
* Change the type of `manufacturerSpecificData` from `Map<int, Uint8List>` to `ManufacturerSpecificData`.
## 3.0.0-dev.2
* Move `setUp` method from `BluetoothLowEnergy` class to `BluetoothLowEnergyManger` class.
* Add `CentralManager.instance` api.
* Add `PeripheralManager.instance` api.
## 3.0.0-dev.1
* Implement new api.
## 2.2.0
* Add `CentralController#getMaximumWriteLength` method.

View File

@ -17,7 +17,9 @@ public class BluetoothLowEnergyDarwin: NSObject, FlutterPlugin {
#else
#error("Unsupported platform.")
#endif
let centralController = MyCentralController(binaryMessenger)
MyCentralControllerHostApiSetup.setUp(binaryMessenger: binaryMessenger, api: centralController)
let centralManager = MyCentralManager(binaryMessenger)
let peripheralManager = MyPeripheralManager(binaryMessenger)
MyCentralManagerHostApiSetup.setUp(binaryMessenger: binaryMessenger, api: centralManager)
MyPeripheralManagerHostApiSetup.setUp(binaryMessenger: binaryMessenger, api: peripheralManager)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,286 @@
//
// MyApi.swift
// bluetooth_low_energy_darwin
//
// Created by on 2023/9/28.
//
import Foundation
import CoreBluetooth
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
extension CBManagerState {
func toArgs() -> MyBluetoothLowEnergyStateArgs {
switch self {
case .unauthorized:
return .unauthorized
case .poweredOff:
return .poweredOff
case .poweredOn:
return .poweredOn
default:
return .unsupported
}
}
}
extension CBPeer {
var uuidArgs: String { identifier.uuidString }
}
extension CBCentral {
func toArgs() -> MyCentralArgs {
let hashCodeArgs = Int64(hash)
return MyCentralArgs(hashCodeArgs: hashCodeArgs, uuidArgs: uuidArgs)
}
}
extension CBPeripheral {
func toArgs() -> MyPeripheralArgs {
let hashCodeArgs = Int64(hash)
return MyPeripheralArgs(hashCodeArgs: hashCodeArgs, uuidArgs: uuidArgs)
}
}
extension CBAttribute {
var uuidArgs: String { uuid.uuidString }
}
extension CBService {
func toArgs(_ characteristicsArgs: [MyGattCharacteristicArgs]) -> MyGattServiceArgs {
let hashCodeArgs = Int64(hash)
return MyGattServiceArgs(hashCodeArgs: hashCodeArgs, uuidArgs: uuidArgs, characteristicsArgs: characteristicsArgs)
}
}
extension CBCharacteristic {
func toArgs(_ descriptorsArgs: [MyGattDescriptorArgs]) -> MyGattCharacteristicArgs {
let hashCodeArgs = Int64(hash)
return MyGattCharacteristicArgs(hashCodeArgs: hashCodeArgs, uuidArgs: uuidArgs, propertyNumbersArgs: propertyNumbersArgs, descriptorsArgs: descriptorsArgs)
}
var propertyNumbersArgs: [Int64] {
var propertiesArgs = [MyGattCharacteristicPropertyArgs]()
let properties = self.properties
if properties.contains(.read) {
propertiesArgs.append(.read)
}
if properties.contains(.write) {
propertiesArgs.append(.write)
}
if properties.contains(.writeWithoutResponse) {
propertiesArgs.append(.writeWithoutResponse)
}
if properties.contains(.notify) {
propertiesArgs.append(.notify)
}
if properties.contains(.indicate) {
propertiesArgs.append(.indicate)
}
return propertiesArgs.map { args in Int64(args.rawValue) }
}
}
extension CBDescriptor {
func toArgs() -> MyGattDescriptorArgs {
let hashCodeArgs = Int64(hash)
return MyGattDescriptorArgs(hashCodeArgs: hashCodeArgs, uuidArgs: uuidArgs)
}
}
extension MyGattCharacteristicWriteTypeArgs {
func toWriteType() -> CBCharacteristicWriteType {
switch self {
case .withResponse:
return .withResponse
case .withoutResponse:
return .withoutResponse
}
}
}
extension String {
var data: Data { data(using: String.Encoding.utf8)! }
}
extension NSNumber {
var data: Data {
var source = self
return Data(bytes: &source, count: MemoryLayout<NSNumber>.size)
}
}
extension UInt16 {
var data: Data {
var source = self
return Data(bytes: &source, count: MemoryLayout<UInt16>.size)
}
}
extension [String: Any] {
func toAdvertiseDataArgs() -> MyAdvertiseDataArgs {
let nameArgs = self[CBAdvertisementDataLocalNameKey] as? String
let serviceUUIDs = self[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] ?? []
let serviceUUIDsArgs = serviceUUIDs.map { uuid in uuid.uuidString }
let serviceData = self[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] ?? [:]
let serviceDataArgsKeyWithValues = serviceData.map { (uuid, data) in
let uuidArgs = uuid.uuidString
let dataArgs = FlutterStandardTypedData(bytes: data)
return (uuidArgs, dataArgs)
}
let serviceDataArgs = [String?: FlutterStandardTypedData?](uniqueKeysWithValues: serviceDataArgsKeyWithValues)
let manufacturerSpecificData = self[CBAdvertisementDataManufacturerDataKey] as? Data
let manufacturerSpecificDataArgs = manufacturerSpecificData?.toManufacturerSpecificDataArgs()
return MyAdvertiseDataArgs(nameArgs: nameArgs, serviceUUIDsArgs: serviceUUIDsArgs, serviceDataArgs: serviceDataArgs, manufacturerSpecificDataArgs: manufacturerSpecificDataArgs)
}
}
extension Data {
func toManufacturerSpecificDataArgs() -> MyManufacturerSpecificDataArgs? {
if count > 2 {
let idArgs = Int64(self[0]) | (Int64(self[1]) << 8)
let data = self[2...count - 1]
let dataArgs = FlutterStandardTypedData(bytes: data)
return MyManufacturerSpecificDataArgs(idArgs: idArgs, dataArgs: dataArgs)
} else {
return nil
}
}
}
extension MyAdvertiseDataArgs {
func toAdvertiseData() throws -> [String : Any] {
// CoreBluetooth only support `CBAdvertisementDataLocalNameKey` and `CBAdvertisementDataServiceUUIDsKey`, see https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/1393252-startadvertising
var advertiseData = [String: Any]()
if nameArgs != nil {
let name = nameArgs!
advertiseData[CBAdvertisementDataLocalNameKey] = name
}
if serviceUUIDsArgs.count > 0 {
var serviceUUIDs = [CBUUID]()
for args in serviceUUIDsArgs {
guard let uuidArgs = args else {
throw MyError.illegalArgument
}
let uuid = CBUUID(string: uuidArgs)
serviceUUIDs.append(uuid)
}
advertiseData[CBAdvertisementDataServiceUUIDsKey] = serviceUUIDs
}
// if serviceDataArgs.count > 0 {
// var serviceData = [CBUUID: Data]()
// for args in serviceDataArgs {
// guard let uuidArgs = args.key else {
// throw MyError.illegalArgument
// }
// guard let dataArgs = args.value else {
// throw MyError.illegalArgument
// }
// let uuid = CBUUID(string: uuidArgs)
// let data = dataArgs.data
// serviceData[uuid] = data
// }
// advertiseData[CBAdvertisementDataServiceDataKey] = serviceData
// }
// if manufacturerSpecificDataArgs != nil {
// let manufacturerSpecificData = manufacturerSpecificDataArgs!.toManufacturerSpecificData()
// advertiseData[CBAdvertisementDataManufacturerDataKey] = manufacturerSpecificData
// }
return advertiseData
}
}
//extension MyManufacturerSpecificDataArgs {
// func toManufacturerSpecificData() -> Data {
// let id = UInt16(idArgs).data
// let data = dataArgs.data
// return id + data
// }
//}
extension MyGattServiceArgs {
func toService() -> CBMutableService {
let type = CBUUID(string: uuidArgs)
let primary = true
return CBMutableService(type: type, primary: primary)
}
}
extension MyGattCharacteristicArgs {
func toCharacteristic() -> CBMutableCharacteristic {
let type = CBUUID(string: uuidArgs)
return CBMutableCharacteristic(type: type, properties: properties, value: nil, permissions: permissions)
}
var properties: CBCharacteristicProperties {
var properties: CBCharacteristicProperties = []
for args in propertyNumbersArgs {
guard let rawArgs = args else {
continue
}
let rawValue = Int(rawArgs)
let property = MyGattCharacteristicPropertyArgs(rawValue: rawValue)
switch property {
case .read:
properties.insert(.read)
case .write:
properties.insert(.write)
case .writeWithoutResponse:
properties.insert(.writeWithoutResponse)
case .notify:
properties.insert(.notify)
case .indicate:
properties.insert(.indicate)
default:
continue
}
}
return properties
}
var permissions: CBAttributePermissions {
var permissions: CBAttributePermissions = []
for args in propertyNumbersArgs {
guard let rawArgs = args else {
continue
}
let rawValue = Int(rawArgs)
let property = MyGattCharacteristicPropertyArgs(rawValue: rawValue)
switch property {
case .read:
permissions.insert(.readable)
case .write, .writeWithoutResponse:
permissions.insert(.writeable)
default:
continue
}
}
return permissions
}
}
extension MyGattDescriptorArgs {
func toDescriptor() -> CBMutableDescriptor {
let type = CBUUID(string: uuidArgs)
let value = valueArgs?.data
return CBMutableDescriptor(type: type, value: value)
}
}
extension Dictionary {
mutating func getOrPut(_ key: Key, _ defaultValue: () -> Value) -> Value {
guard let value = self[key] else {
let value1 = defaultValue()
self[key] = value1
return value1
}
return value
}
}

View File

@ -1,737 +0,0 @@
//
// MyCentralController.swift
// bluetooth_low_energy_ios
//
// Created by on 2023/8/13.
//
import Foundation
import CoreBluetooth
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
class MyCentralController: MyCentralControllerHostApi {
init(_ binaryMessenger: FlutterBinaryMessenger) {
myApi = MyCentralControllerFlutterApi(binaryMessenger: binaryMessenger)
}
private let myApi: MyCentralControllerFlutterApi
private lazy var myCentralManagerDelegate = MyCentralManagerDelegate(self)
private lazy var myPeripheralDelegate = MyPeripheralDelegate(self)
private let centralManager = CBCentralManager()
private var cachedPeripherals = [Int: CBPeripheral]()
private var cachedServices = [Int: [Int: CBService]]()
private var cachedCharacteristics = [Int: [Int: CBCharacteristic]]()
private var cachedDescriptors = [Int: [Int: CBDescriptor]]()
var setUpCompletion: ((Result<MyCentralControllerArgs, Error>) -> Void)?
var connectCompletions = [Int: (Result<Void, Error>) -> Void]()
var disconnectCompletions = [Int: (Result<Void, Error>) -> Void]()
var discoverGattCompletions = [Int: (Result<Void, Error>) -> Void]()
var unfinishedServices = [Int: [CBService]]()
var unfinishedCharacteristics = [Int: [CBCharacteristic]]()
var readCharacteristicCompletions = [Int: (Result<FlutterStandardTypedData, Error>) -> Void]()
var writeCharacteristicCompletions = [Int: (Result<Void, Error>) -> Void]()
var notifyCharacteristicCompletions = [Int: (Result<Void, Error>) -> Void]()
var readDescriptorCompletions = [Int: (Result<FlutterStandardTypedData, Error>) -> Void]()
var writeDescriptorCompletions = [Int: (Result<Void, Error>) -> Void]()
func setUp(completion: @escaping (Result<MyCentralControllerArgs, Error>) -> Void) {
do {
let unfinishedCompletion = setUpCompletion
if unfinishedCompletion != nil {
throw MyError.illegalState
}
centralManager.delegate = myCentralManagerDelegate
if centralManager.state == .unknown {
setUpCompletion = completion
} else {
let myStateArgs = centralManager.state.toMyArgs()
let myStateNumber = Int64(myStateArgs.rawValue)
let myArgs = MyCentralControllerArgs(myStateNumber: myStateNumber)
completion(.success(myArgs))
}
} catch {
completion(.failure(error))
}
}
func tearDown() throws {
centralManager.delegate = nil
if(centralManager.isScanning) {
centralManager.stopScan()
}
for peripheral in cachedPeripherals.values {
peripheral.delegate = nil
if peripheral.state != .disconnected {
centralManager.cancelPeripheralConnection(peripheral)
}
}
cachedPeripherals.removeAll()
cachedServices.removeAll()
cachedCharacteristics.removeAll()
cachedDescriptors.removeAll()
}
func startDiscovery() throws {
let options = [CBCentralManagerScanOptionAllowDuplicatesKey: true]
centralManager.scanForPeripherals(withServices: nil, options: options)
}
func stopDiscovery() throws {
centralManager.stopScan()
}
func connect(myPeripheralKey: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let peripheralKey = Int(myPeripheralKey)
let unfinishedCompletion = connectCompletions[peripheralKey]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = cachedPeripherals[peripheralKey] else {
throw MyError.illegalArgument
}
centralManager.connect(peripheral)
connectCompletions[peripheralKey] = completion
} catch {
completion(.failure(error))
}
}
func disconnect(myPeripheralKey: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let peripheralKey = Int(myPeripheralKey)
let unfinishedCompletion = disconnectCompletions[peripheralKey]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = cachedPeripherals[peripheralKey] else {
throw MyError.illegalArgument
}
centralManager.cancelPeripheralConnection(peripheral)
disconnectCompletions[peripheralKey] = completion
} catch {
completion(.failure(error))
}
}
func getMaximumWriteLength(myPeripheralKey: Int64, myTypeNumber: Int64) throws -> Int64 {
let peripheralKey = Int(myPeripheralKey)
guard let peripheral = cachedPeripherals[peripheralKey] else {
throw MyError.illegalArgument
}
let myTypeRawValue = Int(myTypeNumber)
guard let myTypeArgs = MyGattCharacteristicWriteTypeArgs(rawValue: myTypeRawValue) else {
throw MyError.illegalArgument
}
let type = myTypeArgs.toType()
let maximumWriteLength32 = peripheral.maximumWriteValueLength(for: type)
let maximumWriteLength = Int64(maximumWriteLength32)
return maximumWriteLength
}
func discoverGATT(myPeripheralKey: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let peripheralKey = Int(myPeripheralKey)
let unfinishedCompletion = discoverGattCompletions[peripheralKey]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = cachedPeripherals[peripheralKey] else {
throw MyError.illegalArgument
}
peripheral.discoverServices(nil)
discoverGattCompletions[peripheralKey] = completion
} catch {
completion(.failure(error))
}
}
func getServices(myPeripheralKey: Int64) throws -> [MyGattServiceArgs] {
let peripheralKey = Int(myPeripheralKey)
guard let services = cachedServices[peripheralKey] else {
throw MyError.illegalArgument
}
return services.map { (key, service) in
return service.toMyArgs()
}
}
func getCharacteristics(myServiceKey: Int64) throws -> [MyGattCharacteristicArgs] {
let serviceKey = Int(myServiceKey)
guard let characteristics = cachedCharacteristics[serviceKey] else {
throw MyError.illegalArgument
}
return characteristics.map { (key, characteristic) in
return characteristic.toMyArgs()
}
}
func getDescriptors(myCharacteristicKey: Int64) throws -> [MyGattDescriptorArgs] {
let characteristicKey = Int(myCharacteristicKey)
guard let descriptors = cachedDescriptors[characteristicKey] else {
throw MyError.illegalArgument
}
return descriptors.map { (key, descriptor) in
return descriptor.toMyArgs()
}
}
func readCharacteristic(myPeripheralKey: Int64, myServiceKey: Int64, myCharacteristicKey: Int64, completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void) {
do {
let peripheralKey = Int(myPeripheralKey)
guard let peripheral = cachedPeripherals[peripheralKey] else {
throw MyError.illegalArgument
}
let serviceKey = Int(myServiceKey)
guard let characteristics = cachedCharacteristics[serviceKey] else {
throw MyError.illegalArgument
}
let characteristicKey = Int(myCharacteristicKey)
guard let characteristic = characteristics[characteristicKey] else {
throw MyError.illegalArgument
}
let unfinishedCompletion = readCharacteristicCompletions[characteristicKey]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
peripheral.readValue(for: characteristic)
readCharacteristicCompletions[characteristicKey] = completion
} catch {
completion(.failure(error))
}
}
func writeCharacteristic(myPeripheralKey: Int64, myServiceKey: Int64, myCharacteristicKey: Int64, value: FlutterStandardTypedData, myTypeNumber: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let peripheralKey = Int(myPeripheralKey)
guard let peripheral = cachedPeripherals[peripheralKey] else {
throw MyError.illegalArgument
}
let serviceKey = Int(myServiceKey)
guard let characteristics = cachedCharacteristics[serviceKey] else {
throw MyError.illegalArgument
}
let characteristicKey = Int(myCharacteristicKey)
guard let characteristic = characteristics[characteristicKey] else {
throw MyError.illegalArgument
}
let data = value.data
let myTypeRawValue = Int(myTypeNumber)
guard let myTypeArgs = MyGattCharacteristicWriteTypeArgs(rawValue: myTypeRawValue) else {
throw MyError.illegalArgument
}
let type = myTypeArgs.toType()
let unfinishedCompletion = writeCharacteristicCompletions[characteristicKey]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
peripheral.writeValue(data, for: characteristic, type: type)
writeCharacteristicCompletions[characteristicKey] = completion
} catch {
completion(.failure(error))
}
}
func notifyCharacteristic(myPeripheralKey: Int64, myServiceKey: Int64, myCharacteristicKey: Int64, state: Bool, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let peripheralKey = Int(myPeripheralKey)
guard let peripheral = cachedPeripherals[peripheralKey] else {
throw MyError.illegalArgument
}
let serviceKey = Int(myServiceKey)
guard let characteristics = cachedCharacteristics[serviceKey] else {
throw MyError.illegalArgument
}
let characteristicKey = Int(myCharacteristicKey)
guard let characteristic = characteristics[characteristicKey] else {
throw MyError.illegalArgument
}
let unfinishedCompletion = notifyCharacteristicCompletions[characteristicKey]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
peripheral.setNotifyValue(state, for: characteristic)
notifyCharacteristicCompletions[characteristicKey] = completion
} catch {
completion(.failure(error))
}
}
func readDescriptor(myPeripheralKey: Int64, myCharacteristicKey: Int64, myDescriptorKey: Int64, completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void) {
do {
let peripheralKey = Int(myPeripheralKey)
guard let peripheral = cachedPeripherals[peripheralKey] else {
throw MyError.illegalArgument
}
let characteristicKey = Int(myCharacteristicKey)
guard let descriptors = cachedDescriptors[characteristicKey] else {
throw MyError.illegalArgument
}
let descriptorKey = Int(myDescriptorKey)
guard let descriptor = descriptors[descriptorKey] else {
throw MyError.illegalArgument
}
let unfinishedCompletion = readDescriptorCompletions[descriptorKey]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
peripheral.readValue(for: descriptor)
readDescriptorCompletions[descriptorKey] = completion
} catch {
completion(.failure(error))
}
}
func writeDescriptor(myPeripheralKey: Int64, myCharacteristicKey: Int64, myDescriptorKey: Int64, value: FlutterStandardTypedData, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let peripheralKey = Int(myPeripheralKey)
guard let peripheral = cachedPeripherals[peripheralKey] else {
throw MyError.illegalArgument
}
let characteristicKey = Int(myCharacteristicKey)
guard let descriptors = cachedDescriptors[characteristicKey] else {
throw MyError.illegalArgument
}
let descriptorKey = Int(myDescriptorKey)
guard let descriptor = descriptors[descriptorKey] else {
throw MyError.illegalArgument
}
let data = value.data
let unfinishedCompletion = writeDescriptorCompletions[descriptorKey]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
peripheral.writeValue(data, for: descriptor)
writeDescriptorCompletions[descriptorKey] = completion
} catch {
completion(.failure(error))
}
}
func didUpdateState(_ state: CBManagerState) {
let completion = setUpCompletion
if state != .unknown && completion != nil {
let myStateArgs = state.toMyArgs()
let myStateNumber = Int64(myStateArgs.rawValue)
let myArgs = MyCentralControllerArgs(myStateNumber: myStateNumber)
completion!(.success(myArgs))
setUpCompletion = nil
}
let myStateArgs = state.toMyArgs()
let myStateNumber = Int64(myStateArgs.rawValue)
myApi.onStateChanged(myStateNumber: myStateNumber) {}
}
func didDiscover(_ peripheral: CBPeripheral, _ advertisementData: [String : Any], _ rssiNumber: NSNumber) {
let peripheralKey = peripheral.hash
if cachedPeripherals[peripheralKey] == nil {
peripheral.delegate = myPeripheralDelegate
cachedPeripherals[peripheralKey] = peripheral
}
let myPeripheralArgs = peripheral.toMyArgs()
let rssi = rssiNumber.int64Value
let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String
let rawManufacturerSpecificData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
var manufacturerSpecificData = [Int64: FlutterStandardTypedData]()
if rawManufacturerSpecificData != nil {
do {
guard let data = rawManufacturerSpecificData else {
throw MyError.illegalArgument
}
guard data.count >= 2 else {
throw MyError.illegalArgument
}
let key = Int64(data[0]) | (Int64(data[1]) << 8)
let bytes = data.count > 2 ? data[2...data.count-1] : Data()
let value = FlutterStandardTypedData(bytes: bytes)
manufacturerSpecificData[key] = value
} catch {
manufacturerSpecificData = [:]
}
}
let rawServiceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] ?? []
let serviceUUIDs = rawServiceUUIDs.map { uuid in uuid.uuidString }
let rawServiceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] ?? [:]
let elements = rawServiceData.map { (uuid, data) in
let key = uuid.uuidString
let value = FlutterStandardTypedData(bytes: data)
return (key, value)
}
let serviceData = [String?: FlutterStandardTypedData?](uniqueKeysWithValues: elements)
let myAdvertisementArgs = MyAdvertisementArgs(name: name, manufacturerSpecificData: manufacturerSpecificData, serviceUUIDs: serviceUUIDs, serviceData: serviceData)
myApi.onDiscovered(myPeripheralArgs: myPeripheralArgs, rssi: rssi, myAdvertisementArgs: myAdvertisementArgs) {}
}
func didConnect(_ peripheral: CBPeripheral) {
let peripheralKey = peripheral.hash
let myPeripheralKey = Int64(peripheralKey)
myApi.onPeripheralStateChanged(myPeripheralKey: myPeripheralKey, state: true) {}
guard let completion = connectCompletions.removeValue(forKey: peripheralKey) else {
return
}
completion(.success(()))
}
func didFailToConnect(_ peripheral: CBPeripheral, _ error: Error?) {
let peripheralKey = peripheral.hash
guard let completion = connectCompletions.removeValue(forKey: peripheralKey) else {
return
}
completion(.failure(error ?? MyError.unknown))
}
func didDisconnectPeripheral(_ peripheral: CBPeripheral, _ error: Error?) {
let peripheralKey = peripheral.hash
let myPeripheralKey = Int64(peripheralKey)
let discoverGattCompletion = discoverGattCompletions.removeValue(forKey: peripheralKey)
if discoverGattCompletion != nil {
didDiscoverGATT(peripheral, error ?? MyError.unknown)
}
let services = cachedServices[peripheralKey] ?? [:]
for service in services {
let characteristics = cachedCharacteristics[service.key] ?? [:]
for characteristic in characteristics {
let readCharacteristicCompletion = readCharacteristicCompletions.removeValue(forKey: characteristic.key)
let writeCharacteristicCompletion = writeCharacteristicCompletions.removeValue(forKey: characteristic.key)
if readCharacteristicCompletion != nil {
readCharacteristicCompletion!(.failure(MyError.illegalState))
}
if writeCharacteristicCompletion != nil {
writeCharacteristicCompletion!(.failure(MyError.illegalState))
}
let descriptors = cachedDescriptors[characteristic.key] ?? [:]
for descriptor in descriptors {
let readDescriptorCompletion = readDescriptorCompletions.removeValue(forKey: descriptor.key)
let writeDescriptorCompletion = writeDescriptorCompletions.removeValue(forKey: descriptor.key)
if readDescriptorCompletion != nil {
readDescriptorCompletion!(.failure(MyError.illegalState))
}
if writeDescriptorCompletion != nil {
writeDescriptorCompletion!(.failure(MyError.illegalState))
}
}
}
}
myApi.onPeripheralStateChanged(myPeripheralKey: myPeripheralKey, state: false) {}
guard let completion = disconnectCompletions.removeValue(forKey: peripheralKey) else {
return
}
if error == nil {
completion(.success(()))
} else {
completion(.failure(error!))
}
}
func didDiscoverServices(_ peripheral: CBPeripheral, _ error: Error?) {
let peripheralKey = peripheral.hash
if error == nil {
var services = peripheral.services ?? []
if services.isEmpty {
didDiscoverGATT(peripheral, error)
} else {
let service = services.removeFirst()
unfinishedServices[peripheralKey] = services
peripheral.discoverCharacteristics(nil, for: service)
}
} else {
didDiscoverGATT(peripheral, error)
}
}
func didDiscoverCharacteristics(_ peripheral: CBPeripheral, _ service: CBService, _ error: Error?) {
let peripheralKey = peripheral.hash
if error == nil {
var characteristics = service.characteristics ?? []
if characteristics.isEmpty {
var services = unfinishedServices.removeValue(forKey: peripheralKey) ?? []
if services.isEmpty {
didDiscoverGATT(peripheral, error)
} else {
let service = services.removeFirst()
unfinishedServices[peripheralKey] = services
peripheral.discoverCharacteristics(nil, for: service)
}
} else {
let characteristic = characteristics.removeFirst()
unfinishedCharacteristics[peripheralKey] = characteristics
peripheral.discoverDescriptors(for: characteristic)
}
} else {
didDiscoverGATT(peripheral, error)
}
}
func didDiscoverDescriptors(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic, _ error: Error?) {
let peripheralKey = peripheral.hash
if error == nil {
var characteristics = unfinishedCharacteristics.removeValue(forKey: peripheralKey) ?? []
if (characteristics.isEmpty) {
var services = unfinishedServices.removeValue(forKey: peripheralKey) ?? []
if services.isEmpty {
didDiscoverGATT(peripheral, error)
} else {
let service = services.removeFirst()
unfinishedServices[peripheralKey] = services
peripheral.discoverCharacteristics(nil, for: service)
}
} else {
let characteristic = characteristics.removeFirst()
unfinishedCharacteristics[peripheralKey] = characteristics
peripheral.discoverDescriptors(for: characteristic)
}
} else {
didDiscoverGATT(peripheral, error)
}
}
private func didDiscoverGATT(_ peripheral: CBPeripheral, _ error: Error?) {
let peripheralKey = peripheral.hash
unfinishedServices.removeValue(forKey: peripheralKey)
unfinishedCharacteristics.removeValue(forKey: peripheralKey)
guard let completion = discoverGattCompletions.removeValue(forKey: peripheralKey) else {
return
}
if error == nil {
let services = peripheral.services ?? []
var cachedServices = [Int: CBService]()
for service in services {
let serviceKey = service.hash
cachedServices[serviceKey] = service
let characteristics = service.characteristics ?? []
var cachedCharacteristics = [Int: CBCharacteristic]()
for characteristic in characteristics {
let characteristicKey = characteristic.hash
cachedCharacteristics[characteristicKey] = characteristic
let descriptors = characteristic.descriptors ?? []
var cachedDescriptors = [Int: CBDescriptor]()
for descriptor in descriptors {
let descriptorKey = descriptor.hash
cachedDescriptors[descriptorKey] = descriptor
}
self.cachedDescriptors[characteristicKey] = cachedDescriptors
}
self.cachedCharacteristics[serviceKey] = cachedCharacteristics
}
self.cachedServices[peripheralKey] = cachedServices
completion(.success(()))
} else {
completion(.failure(error!))
}
}
func didUpdateCharacteristicValue(_ characteristic: CBCharacteristic, _ error: Error?) {
let characteristicKey = characteristic.hash
guard let completion = readCharacteristicCompletions.removeValue(forKey: characteristicKey) else {
let myCharacteristicKey = Int64(characteristicKey)
let rawValue = characteristic.value ?? Data()
let value = FlutterStandardTypedData(bytes: rawValue)
myApi.onCharacteristicValueChanged(myCharacteristicKey: myCharacteristicKey, value: value) {}
return
}
if error == nil {
let rawValue = characteristic.value ?? Data()
let value = FlutterStandardTypedData(bytes: rawValue)
completion(.success(value))
} else {
completion(.failure(error!))
}
}
func didWriteCharacteristicValue(_ characteristic: CBCharacteristic, _ error: Error?) {
let characteristicKey = characteristic.hash
guard let completion = writeCharacteristicCompletions.removeValue(forKey: characteristicKey) else {
return
}
if error == nil {
completion(.success(()))
} else {
completion(.failure(error!))
}
}
func didUpdateNotificationState(_ characteristic: CBCharacteristic, _ error: Error?) {
let characteristicKey = characteristic.hash
guard let completion = notifyCharacteristicCompletions.removeValue(forKey: characteristicKey) else {
return
}
if error == nil {
completion(.success(()))
} else {
completion(.failure(error!))
}
}
func didUpdateDescriptorValue(_ descriptor: CBDescriptor, _ error: Error?) {
let descriptorKey = descriptor.hash
guard let completion = readDescriptorCompletions.removeValue(forKey: descriptorKey) else {
return
}
if error == nil {
// TODO: Need to confirm wheather the corresponding descriptor type and value is correct.
let value: FlutterStandardTypedData
let rawValue = descriptor.value
do {
switch descriptor.uuid.uuidString {
case CBUUIDCharacteristicExtendedPropertiesString:
fallthrough
case CBUUIDClientCharacteristicConfigurationString:
fallthrough
case CBUUIDServerCharacteristicConfigurationString:
guard let rawNumber = rawValue as? NSNumber else {
throw MyError.illegalArgument
}
value = FlutterStandardTypedData(bytes: rawNumber.data)
case CBUUIDCharacteristicUserDescriptionString:
fallthrough
case CBUUIDCharacteristicAggregateFormatString:
guard let rawString = rawValue as? String else {
throw MyError.illegalArgument
}
value = FlutterStandardTypedData(bytes: rawString.data)
case CBUUIDCharacteristicFormatString:
guard let rawData = rawValue as? Data else {
throw MyError.illegalArgument
}
value = FlutterStandardTypedData(bytes: rawData)
case CBUUIDL2CAPPSMCharacteristicString:
guard let rawU16 = rawValue as? UInt16 else {
throw MyError.illegalArgument
}
value = FlutterStandardTypedData(bytes: rawU16.data)
default:
throw MyError.illegalArgument
}
} catch {
value = FlutterStandardTypedData()
}
completion(.success((value)))
} else {
completion(.failure(error!))
}
}
func didWriteDescriptorValue(_ descriptor: CBDescriptor, _ error: Error?) {
let descriptorKey = descriptor.hash
guard let completion = writeDescriptorCompletions.removeValue(forKey: descriptorKey) else {
return
}
if error == nil {
completion(.success(()))
} else {
completion(.failure(error!))
}
}
}
extension CBManagerState {
func toMyArgs() -> MyCentralStateArgs {
switch self {
case .unauthorized:
return .unauthorized
case .poweredOff:
return .poweredOff
case .poweredOn:
return .poweredOn
default:
return .unsupported
}
}
}
extension CBPeripheral {
func toMyArgs() -> MyPeripheralArgs {
let key = Int64(hash)
let uuid = identifier.uuidString
return MyPeripheralArgs(key: key, uuid: uuid)
}
}
extension CBService {
func toMyArgs() -> MyGattServiceArgs {
let key = Int64(hash)
let uuid = uuid.uuidString
return MyGattServiceArgs(key: key, uuid: uuid)
}
}
extension CBCharacteristic {
func toMyArgs() -> MyGattCharacteristicArgs {
let key = Int64(hash)
let uuid = uuid.uuidString
let myPropertyArgses = properties.toMyArgses()
let myPropertyNumbers = myPropertyArgses.map { myPropertyArgs in Int64(myPropertyArgs.rawValue) }
return MyGattCharacteristicArgs(key: key, uuid: uuid, myPropertyNumbers: myPropertyNumbers)
}
}
extension CBDescriptor {
func toMyArgs() -> MyGattDescriptorArgs {
let key = Int64(hash)
let uuid = uuid.uuidString
return MyGattDescriptorArgs(key: key, uuid: uuid)
}
}
extension CBCharacteristicProperties {
func toMyArgses() -> [MyGattCharacteristicPropertyArgs] {
var myPropertyArgs = [MyGattCharacteristicPropertyArgs]()
if contains(.read) {
myPropertyArgs.append(.read)
}
if contains(.write) {
myPropertyArgs.append(.write)
}
if contains(.writeWithoutResponse) {
myPropertyArgs.append(.writeWithoutResponse)
}
if contains(.notify) {
myPropertyArgs.append(.notify)
}
if contains(.indicate) {
myPropertyArgs.append(.indicate)
}
return myPropertyArgs
}
}
extension MyGattCharacteristicWriteTypeArgs {
func toType() -> CBCharacteristicWriteType {
switch self {
case .withResponse:
return .withResponse
case .withoutResponse:
return .withoutResponse
}
}
}
extension NSNumber {
var data: Data {
var source = self
return Data(bytes: &source, count: MemoryLayout<NSNumber>.size)
}
}
extension String {
var data: Data {
return data(using: String.Encoding.utf8)!
}
}
extension UInt16 {
var data: Data {
var source = self
return Data(bytes: &source, count: MemoryLayout<UInt16>.size)
}
}

View File

@ -0,0 +1,650 @@
//
// MyCentralController.swift
// bluetooth_low_energy_ios
//
// Created by on 2023/8/13.
//
import Foundation
import CoreBluetooth
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
class MyCentralManager: MyCentralManagerHostApi {
init(_ binaryMessenger: FlutterBinaryMessenger) {
self.binaryMessenger = binaryMessenger
}
private let binaryMessenger: FlutterBinaryMessenger
private let centralManager = CBCentralManager()
private lazy var api = MyCentralManagerFlutterApi(binaryMessenger: binaryMessenger)
private lazy var centralManagerDelegate = MyCentralManagerDelegate(self)
private lazy var peripheralDelegate = MyPeripheralDelegate(self)
private var peripherals = [Int64: CBPeripheral]()
private var services = [Int64: CBService]()
private var characteristics = [Int64: CBCharacteristic]()
private var descriptors = [Int64: CBDescriptor]()
private var peripheralsArgs = [Int: MyPeripheralArgs]()
private var servicesArgsOfPeripheralsArgs = [Int64: [MyGattServiceArgs]]()
private var servicesArgs = [Int: MyGattServiceArgs]()
private var characteristicsArgs = [Int: MyGattCharacteristicArgs]()
private var descriptorsArgs = [Int: MyGattDescriptorArgs]()
private var setUpCompletion: ((Result<MyCentralManagerArgs, Error>) -> Void)?
private var connectCompletions = [Int64: (Result<Void, Error>) -> Void]()
private var disconnectCompletions = [Int64: (Result<Void, Error>) -> Void]()
private var readRssiCompletions = [Int64: (Result<Int64, Error>) -> Void]()
private var discoverGattCompletions = [Int64: (Result<[MyGattServiceArgs], Error>) -> Void]()
private var unfinishedServices = [Int64: [CBService]]()
private var unfinishedCharacteristics = [Int64: [CBCharacteristic]]()
private var readCharacteristicCompletions = [Int64: (Result<FlutterStandardTypedData, Error>) -> Void]()
private var writeCharacteristicCompletions = [Int64: (Result<Void, Error>) -> Void]()
private var notifyCharacteristicCompletions = [Int64: (Result<Void, Error>) -> Void]()
private var readDescriptorCompletions = [Int64: (Result<FlutterStandardTypedData, Error>) -> Void]()
private var writeDescriptorCompletions = [Int64: (Result<Void, Error>) -> Void]()
func setUp(completion: @escaping (Result<MyCentralManagerArgs, Error>) -> Void) {
do {
if setUpCompletion != nil {
throw MyError.illegalState
}
try tearDown()
centralManager.delegate = centralManagerDelegate
if centralManager.state == .unknown {
setUpCompletion = completion
} else {
let stateArgs = centralManager.state.toArgs()
let stateNumberArgs = Int64(stateArgs.rawValue)
let args = MyCentralManagerArgs(stateNumberArgs: stateNumberArgs)
completion(.success(args))
}
} catch {
completion(.failure(error))
}
}
func tearDown() throws {
if(centralManager.isScanning) {
centralManager.stopScan()
}
for peripheral in peripherals.values {
if peripheral.state != .disconnected {
centralManager.cancelPeripheralConnection(peripheral)
}
}
peripherals.removeAll()
services.removeAll()
characteristics.removeAll()
descriptors.removeAll()
peripheralsArgs.removeAll()
servicesArgsOfPeripheralsArgs.removeAll()
servicesArgs.removeAll()
characteristicsArgs.removeAll()
descriptorsArgs.removeAll()
setUpCompletion = nil
connectCompletions.removeAll()
disconnectCompletions.removeAll()
readRssiCompletions.removeAll()
discoverGattCompletions.removeAll()
unfinishedServices.removeAll()
unfinishedCharacteristics.removeAll()
readCharacteristicCompletions.removeAll()
writeCharacteristicCompletions.removeAll()
notifyCharacteristicCompletions.removeAll()
readDescriptorCompletions.removeAll()
writeDescriptorCompletions.removeAll()
}
func startDiscovery() throws {
let options = [CBCentralManagerScanOptionAllowDuplicatesKey: true]
centralManager.scanForPeripherals(withServices: nil, options: options)
}
func stopDiscovery() throws {
centralManager.stopScan()
}
func connect(peripheralHashCodeArgs: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let unfinishedCompletion = connectCompletions[peripheralHashCodeArgs]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = peripherals[peripheralHashCodeArgs] else {
throw MyError.illegalArgument
}
centralManager.connect(peripheral)
connectCompletions[peripheralHashCodeArgs] = completion
} catch {
completion(.failure(error))
}
}
func disconnect(peripheralHashCodeArgs: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let unfinishedCompletion = disconnectCompletions[peripheralHashCodeArgs]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = peripherals[peripheralHashCodeArgs] else {
throw MyError.illegalArgument
}
centralManager.cancelPeripheralConnection(peripheral)
disconnectCompletions[peripheralHashCodeArgs] = completion
} catch {
completion(.failure(error))
}
}
func getMaximumWriteLength(peripheralHashCodeArgs: Int64, typeNumberArgs: Int64) throws -> Int64 {
guard let peripheral = peripherals[peripheralHashCodeArgs] else {
throw MyError.illegalArgument
}
let typeRawValue = Int(typeNumberArgs)
guard let typeArgs = MyGattCharacteristicWriteTypeArgs(rawValue: typeRawValue) else {
throw MyError.illegalArgument
}
let type = typeArgs.toWriteType()
let maximumWriteLength = peripheral.maximumWriteValueLength(for: type)
let maximumWriteLengthArgs = Int64(maximumWriteLength)
return maximumWriteLengthArgs
}
func readRSSI(peripheralHashCodeArgs: Int64, completion: @escaping (Result<Int64, Error>) -> Void) {
do {
let unfinishedCompletion = readRssiCompletions[peripheralHashCodeArgs]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = peripherals[peripheralHashCodeArgs] else {
throw MyError.illegalArgument
}
peripheral.readRSSI()
readRssiCompletions[peripheralHashCodeArgs] = completion
} catch {
completion(.failure(error))
}
}
func discoverGATT(peripheralHashCodeArgs: Int64, completion: @escaping (Result<[MyGattServiceArgs], Error>) -> Void) {
do {
let unfinishedCompletion = discoverGattCompletions[peripheralHashCodeArgs]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = peripherals[peripheralHashCodeArgs] else {
throw MyError.illegalArgument
}
peripheral.discoverServices(nil)
discoverGattCompletions[peripheralHashCodeArgs] = completion
} catch {
completion(.failure(error))
}
}
func readCharacteristic(peripheralHashCodeArgs: Int64, characteristicHashCodeArgs: Int64, completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void) {
do {
let unfinishedCompletion = readCharacteristicCompletions[characteristicHashCodeArgs]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = peripherals[peripheralHashCodeArgs] else {
throw MyError.illegalArgument
}
guard let characteristic = characteristics[characteristicHashCodeArgs] else {
throw MyError.illegalArgument
}
peripheral.readValue(for: characteristic)
readCharacteristicCompletions[characteristicHashCodeArgs] = completion
} catch {
completion(.failure(error))
}
}
func writeCharacteristic(peripheralHashCodeArgs: Int64, characteristicHashCodeArgs: Int64, valueArgs: FlutterStandardTypedData, typeNumberArgs: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let unfinishedCompletion = writeCharacteristicCompletions[characteristicHashCodeArgs]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = peripherals[peripheralHashCodeArgs] else {
throw MyError.illegalArgument
}
guard let characteristic = characteristics[characteristicHashCodeArgs] else {
throw MyError.illegalArgument
}
let data = valueArgs.data
let typeRawValue = Int(typeNumberArgs)
guard let typeArgs = MyGattCharacteristicWriteTypeArgs(rawValue: typeRawValue) else {
throw MyError.illegalArgument
}
let type = typeArgs.toWriteType()
peripheral.writeValue(data, for: characteristic, type: type)
writeCharacteristicCompletions[characteristicHashCodeArgs] = completion
} catch {
completion(.failure(error))
}
}
func notifyCharacteristic(peripheralHashCodeArgs: Int64, characteristicHashCodeArgs: Int64, stateArgs: Bool, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let unfinishedCompletion = notifyCharacteristicCompletions[characteristicHashCodeArgs]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = peripherals[peripheralHashCodeArgs] else {
throw MyError.illegalArgument
}
guard let characteristic = characteristics[characteristicHashCodeArgs] else {
throw MyError.illegalArgument
}
let enabled = stateArgs
peripheral.setNotifyValue(enabled, for: characteristic)
notifyCharacteristicCompletions[characteristicHashCodeArgs] = completion
} catch {
completion(.failure(error))
}
}
func readDescriptor(peripheralHashCodeArgs: Int64, descriptorHashCodeArgs: Int64, completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void) {
do {
let unfinishedCompletion = readDescriptorCompletions[descriptorHashCodeArgs]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = peripherals[peripheralHashCodeArgs] else {
throw MyError.illegalArgument
}
guard let descriptor = descriptors[descriptorHashCodeArgs] else {
throw MyError.illegalArgument
}
peripheral.readValue(for: descriptor)
readDescriptorCompletions[descriptorHashCodeArgs] = completion
} catch {
completion(.failure(error))
}
}
func writeDescriptor(peripheralHashCodeArgs: Int64, descriptorHashCodeArgs: Int64, valueArgs: FlutterStandardTypedData, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let unfinishedCompletion = writeDescriptorCompletions[descriptorHashCodeArgs]
if unfinishedCompletion != nil {
throw MyError.illegalState
}
guard let peripheral = peripherals[peripheralHashCodeArgs] else {
throw MyError.illegalArgument
}
guard let descriptor = descriptors[descriptorHashCodeArgs] else {
throw MyError.illegalArgument
}
let data = valueArgs.data
peripheral.writeValue(data, for: descriptor)
writeDescriptorCompletions[descriptorHashCodeArgs] = completion
} catch {
completion(.failure(error))
}
}
func didUpdateState() {
let state = centralManager.state
let stateArgs = state.toArgs()
let stateNumberArgs = Int64(stateArgs.rawValue)
if state != .unknown && setUpCompletion != nil {
let args = MyCentralManagerArgs(stateNumberArgs: stateNumberArgs)
setUpCompletion!(.success(args))
setUpCompletion = nil
}
api.onStateChanged(stateNumberArgs: stateNumberArgs) {}
}
func didDiscover(_ peripheral: CBPeripheral, _ advertisementData: [String : Any], _ rssi: NSNumber) {
let peripheralArgs = peripheral.toArgs()
let peripheralHashCode = peripheral.hash
let peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
peripheral.delegate = peripheralDelegate
peripherals[peripheralHashCodeArgs] = peripheral
peripheralsArgs[peripheralHashCode] = peripheralArgs
let rssiArgs = rssi.int64Value
let advertiseDataArgs = advertisementData.toAdvertiseDataArgs()
api.onDiscovered(peripheralArgs: peripheralArgs, rssiArgs: rssiArgs, advertiseDataArgs: advertiseDataArgs) {}
}
func didConnect(_ peripheral: CBPeripheral) {
let peripheralHashCode = peripheral.hash
guard let peripheralArgs = peripheralsArgs[peripheralHashCode] else {
return
}
let peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
let completion = connectCompletions.removeValue(forKey: peripheralHashCodeArgs)
completion?(.success(()))
let stateArgs = true
api.onPeripheralStateChanged(peripheralArgs: peripheralArgs, stateArgs: stateArgs) {}
}
func didFailToConnect(_ peripheral: CBPeripheral, _ error: Error?) {
let peripheralHashCode = peripheral.hash
guard let peripheralArgs = peripheralsArgs[peripheralHashCode] else {
return
}
let peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
let completion = connectCompletions.removeValue(forKey: peripheralHashCodeArgs)
completion?(.failure(error ?? MyError.unknown))
}
func didDisconnectPeripheral(_ peripheral: CBPeripheral, _ error: Error?) {
let peripheralHashCode = peripheral.hash
guard let peripheralArgs = peripheralsArgs[peripheralHashCode] else {
return
}
let peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
let readRssiCompletion = readRssiCompletions.removeValue(forKey: peripheralHashCodeArgs)
readRssiCompletion?(.failure(error ?? MyError.unknown))
let discoverGattCompletion = discoverGattCompletions.removeValue(forKey: peripheralHashCodeArgs)
discoverGattCompletion?(.failure(error ?? MyError.unknown))
unfinishedServices.removeValue(forKey: peripheralHashCodeArgs)
unfinishedCharacteristics.removeValue(forKey: peripheralHashCodeArgs)
let servicesArgs = servicesArgsOfPeripheralsArgs[peripheralHashCodeArgs] ?? []
for serviceArgs in servicesArgs {
let characteristicsArgs = serviceArgs.characteristicsArgs.map { args in args! }
for characteristicArgs in characteristicsArgs {
let characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
let readCharacteristicCompletion = readCharacteristicCompletions.removeValue(forKey: characteristicHashCodeArgs)
let writeCharacteristicCompletion = writeCharacteristicCompletions.removeValue(forKey: characteristicHashCodeArgs)
let notifyCharacteristicCompletion = notifyCharacteristicCompletions.removeValue(forKey: characteristicHashCodeArgs)
readCharacteristicCompletion?(.failure(error ?? MyError.unknown))
writeCharacteristicCompletion?(.failure(error ?? MyError.unknown))
notifyCharacteristicCompletion?(.failure(error ?? MyError.unknown))
let descriptorsArgs = characteristicArgs.descriptorsArgs.map { args in args! }
for descriptorArgs in descriptorsArgs {
let descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
let readDescriptorCompletion = readDescriptorCompletions.removeValue(forKey: descriptorHashCodeArgs)
let writeDescriptorCompletion = writeDescriptorCompletions.removeValue(forKey: descriptorHashCodeArgs)
readDescriptorCompletion?(.failure(error ?? MyError.unknown))
writeDescriptorCompletion?(.failure(error ?? MyError.unknown))
}
}
}
let stateArgs = false
api.onPeripheralStateChanged(peripheralArgs: peripheralArgs, stateArgs: stateArgs) {}
guard let completion = disconnectCompletions.removeValue(forKey: peripheralHashCodeArgs) else {
return
}
if error == nil {
completion(.success(()))
} else {
completion(.failure(error!))
}
}
func didReadRSSI(_ peripheral: CBPeripheral, _ rssi: NSNumber, _ error: Error?) {
let peripheralHashCode = peripheral.hash
guard let peripheralArgs = peripheralsArgs[peripheralHashCode] else {
return
}
let peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
guard let completion = readRssiCompletions.removeValue(forKey: peripheralHashCodeArgs) else {
return
}
if error == nil {
let rssiArgs = rssi.int64Value
completion(.success((rssiArgs)))
} else {
completion(.failure(error!))
}
}
func didDiscoverServices(_ peripheral: CBPeripheral, _ error: Error?) {
let peripheralHashCode = peripheral.hash
guard let peripheralArgs = peripheralsArgs[peripheralHashCode] else {
return
}
let peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
if error == nil {
var services = peripheral.services ?? []
if services.isEmpty {
didDiscoverGATT(peripheral, error)
} else {
let service = services.removeFirst()
unfinishedServices[peripheralHashCodeArgs] = services
peripheral.discoverCharacteristics(nil, for: service)
}
} else {
didDiscoverGATT(peripheral, error)
}
}
func didDiscoverCharacteristics(_ peripheral: CBPeripheral, _ service: CBService, _ error: Error?) {
let peripheralHashCode = peripheral.hash
guard let peripheralArgs = peripheralsArgs[peripheralHashCode] else {
return
}
let peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
if error == nil {
var characteristics = service.characteristics ?? []
if characteristics.isEmpty {
var services = unfinishedServices.removeValue(forKey: peripheralHashCodeArgs) ?? []
if services.isEmpty {
didDiscoverGATT(peripheral, error)
} else {
let service = services.removeFirst()
unfinishedServices[peripheralHashCodeArgs] = services
peripheral.discoverCharacteristics(nil, for: service)
}
} else {
let characteristic = characteristics.removeFirst()
unfinishedCharacteristics[peripheralHashCodeArgs] = characteristics
peripheral.discoverDescriptors(for: characteristic)
}
} else {
didDiscoverGATT(peripheral, error)
}
}
func didDiscoverDescriptors(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic, _ error: Error?) {
let peripheralHashCode = peripheral.hash
guard let peripheralArgs = peripheralsArgs[peripheralHashCode] else {
return
}
let peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
if error == nil {
var characteristics = unfinishedCharacteristics.removeValue(forKey: peripheralHashCodeArgs) ?? []
if (characteristics.isEmpty) {
var services = unfinishedServices.removeValue(forKey: peripheralHashCodeArgs) ?? []
if services.isEmpty {
didDiscoverGATT(peripheral, error)
} else {
let service = services.removeFirst()
unfinishedServices[peripheralHashCodeArgs] = services
peripheral.discoverCharacteristics(nil, for: service)
}
} else {
let characteristic = characteristics.removeFirst()
unfinishedCharacteristics[peripheralHashCodeArgs] = characteristics
peripheral.discoverDescriptors(for: characteristic)
}
} else {
didDiscoverGATT(peripheral, error)
}
}
private func didDiscoverGATT(_ peripheral: CBPeripheral, _ error: Error?) {
let peripheralHashCode = peripheral.hash
guard let peripheralArgs = peripheralsArgs[peripheralHashCode] else {
return
}
let peripheralhashCodeArgs = peripheralArgs.hashCodeArgs
guard let completion = discoverGattCompletions.removeValue(forKey: peripheralhashCodeArgs) else {
return
}
if error == nil {
let services = peripheral.services ?? []
var servicesArgs = [MyGattServiceArgs]()
for service in services {
let characteristics = service.characteristics ?? []
var characteristicsArgs = [MyGattCharacteristicArgs]()
for characteristic in characteristics {
let descriptors = characteristic.descriptors ?? []
var descriptorsArgs = [MyGattDescriptorArgs]()
for descriptor in descriptors {
let descriptorArgs = descriptor.toArgs()
let descriptorHashCode = descriptor.hash
let descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
self.descriptors[descriptorHashCodeArgs] = descriptor
self.descriptorsArgs[descriptorHashCode] = descriptorArgs
descriptorsArgs.append(descriptorArgs)
}
let characteristicArgs = characteristic.toArgs(descriptorsArgs)
let characteristicHashCode = characteristic.hash
let characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
self.characteristics[characteristicHashCodeArgs] = characteristic
self.characteristicsArgs[characteristicHashCode] = characteristicArgs
characteristicsArgs.append(characteristicArgs)
}
let serviceArgs = service.toArgs(characteristicsArgs)
let serviceHashCode = service.hash
let servcieHashCodeArgs = serviceArgs.hashCodeArgs
self.services[servcieHashCodeArgs] = service
self.servicesArgs[serviceHashCode] = serviceArgs
servicesArgs.append(serviceArgs)
}
servicesArgsOfPeripheralsArgs[peripheralhashCodeArgs] = servicesArgs
completion(.success((servicesArgs)))
} else {
completion(.failure(error!))
unfinishedServices.removeValue(forKey: peripheralhashCodeArgs)
unfinishedCharacteristics.removeValue(forKey: peripheralhashCodeArgs)
}
}
func didUpdateCharacteristicValue(_ characteristic: CBCharacteristic, _ error: Error?) {
let characteristicHashCode = characteristic.hash
guard let characteristicArgs = characteristicsArgs[characteristicHashCode] else {
return
}
let characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
guard let completion = readCharacteristicCompletions.removeValue(forKey: characteristicHashCodeArgs) else {
let value = characteristic.value ?? Data()
let valueArgs = FlutterStandardTypedData(bytes: value)
api.onCharacteristicValueChanged(characteristicArgs: characteristicArgs, valueArgs: valueArgs) {}
return
}
if error == nil {
let value = characteristic.value ?? Data()
let valueArgs = FlutterStandardTypedData(bytes: value)
completion(.success(valueArgs))
} else {
completion(.failure(error!))
}
}
func didWriteCharacteristicValue(_ characteristic: CBCharacteristic, _ error: Error?) {
let characteristicHashCode = characteristic.hash
guard let characteristicArgs = characteristicsArgs[characteristicHashCode] else {
return
}
let characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
guard let completion = writeCharacteristicCompletions.removeValue(forKey: characteristicHashCodeArgs) else {
return
}
if error == nil {
completion(.success(()))
} else {
completion(.failure(error!))
}
}
func didUpdateNotificationState(_ characteristic: CBCharacteristic, _ error: Error?) {
let characteristicHashCode = characteristic.hash
guard let characteristicArgs = characteristicsArgs[characteristicHashCode] else {
return
}
let characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
guard let completion = notifyCharacteristicCompletions.removeValue(forKey: characteristicHashCodeArgs) else {
return
}
if error == nil {
completion(.success(()))
} else {
completion(.failure(error!))
}
}
func didUpdateDescriptorValue(_ descriptor: CBDescriptor, _ error: Error?) {
let descriptorHashCode = descriptor.hash
guard let descriptorArgs = descriptorsArgs[descriptorHashCode] else {
return
}
let descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
guard let completion = readDescriptorCompletions.removeValue(forKey: descriptorHashCodeArgs) else {
return
}
if error == nil {
// TODO: Need to confirm wheather the corresponding descriptor type and value is correct.
let valueArgs: FlutterStandardTypedData
let value = descriptor.value
do {
switch descriptor.uuid.uuidString {
case CBUUIDCharacteristicExtendedPropertiesString:
fallthrough
case CBUUIDClientCharacteristicConfigurationString:
fallthrough
case CBUUIDServerCharacteristicConfigurationString:
guard let numberValue = value as? NSNumber else {
throw MyError.illegalArgument
}
valueArgs = FlutterStandardTypedData(bytes: numberValue.data)
case CBUUIDCharacteristicUserDescriptionString:
fallthrough
case CBUUIDCharacteristicAggregateFormatString:
guard let stringValue = value as? String else {
throw MyError.illegalArgument
}
valueArgs = FlutterStandardTypedData(bytes: stringValue.data)
case CBUUIDCharacteristicFormatString:
guard let bytes = value as? Data else {
throw MyError.illegalArgument
}
valueArgs = FlutterStandardTypedData(bytes: bytes)
case CBUUIDL2CAPPSMCharacteristicString:
guard let uint16Value = value as? UInt16 else {
throw MyError.illegalArgument
}
valueArgs = FlutterStandardTypedData(bytes: uint16Value.data)
default:
throw MyError.illegalArgument
}
} catch {
valueArgs = FlutterStandardTypedData()
}
completion(.success((valueArgs)))
} else {
completion(.failure(error!))
}
}
func didWriteDescriptorValue(_ descriptor: CBDescriptor, _ error: Error?) {
let descriptorHashCode = descriptor.hash
guard let descriptorArgs = descriptorsArgs[descriptorHashCode] else {
return
}
let descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
guard let completion = writeDescriptorCompletions.removeValue(forKey: descriptorHashCodeArgs) else {
return
}
if error == nil {
completion(.success(()))
} else {
completion(.failure(error!))
}
}
}

View File

@ -9,30 +9,29 @@ import Foundation
import CoreBluetooth
class MyCentralManagerDelegate: NSObject, CBCentralManagerDelegate {
private let myCentralController: MyCentralController
init(_ myCentralController: MyCentralController) {
self.myCentralController = myCentralController
init(_ centralManager: MyCentralManager) {
self.centralManager = centralManager
}
private let centralManager: MyCentralManager
func centralManagerDidUpdateState(_ central: CBCentralManager) {
let state = central.state
myCentralController.didUpdateState(state)
centralManager.didUpdateState()
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
myCentralController.didDiscover(peripheral, advertisementData, RSSI)
centralManager.didDiscover(peripheral, advertisementData, RSSI)
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
myCentralController.didConnect(peripheral)
centralManager.didConnect(peripheral)
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
myCentralController.didFailToConnect(peripheral, error)
centralManager.didFailToConnect(peripheral, error)
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
myCentralController.didDisconnectPeripheral(peripheral, error)
centralManager.didDisconnectPeripheral(peripheral, error)
}
}

View File

@ -9,41 +9,45 @@ import Foundation
import CoreBluetooth
class MyPeripheralDelegate: NSObject, CBPeripheralDelegate {
private let myCentralController: MyCentralController
private let centralManager: MyCentralManager
init(_ myCentralController: MyCentralController) {
self.myCentralController = myCentralController
init(_ centralManager: MyCentralManager) {
self.centralManager = centralManager
}
func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
centralManager.didReadRSSI(peripheral, RSSI, error)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
myCentralController.didDiscoverServices(peripheral, error)
centralManager.didDiscoverServices(peripheral, error)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
myCentralController.didDiscoverCharacteristics(peripheral, service, error)
centralManager.didDiscoverCharacteristics(peripheral, service, error)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
myCentralController.didDiscoverDescriptors(peripheral, characteristic, error)
centralManager.didDiscoverDescriptors(peripheral, characteristic, error)
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
myCentralController.didUpdateCharacteristicValue(characteristic, error)
centralManager.didUpdateCharacteristicValue(characteristic, error)
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
myCentralController.didWriteCharacteristicValue(characteristic, error)
centralManager.didWriteCharacteristicValue(characteristic, error)
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
myCentralController.didUpdateNotificationState(characteristic, error)
centralManager.didUpdateNotificationState(characteristic, error)
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
myCentralController.didUpdateDescriptorValue(descriptor, error)
centralManager.didUpdateDescriptorValue(descriptor, error)
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
myCentralController.didWriteDescriptorValue(descriptor, error)
centralManager.didWriteDescriptorValue(descriptor, error)
}
}

View File

@ -0,0 +1,375 @@
//
// MyPeripheralManager.swift
// bluetooth_low_energy_darwin
//
// Created by on 2023/10/7.
//
import Foundation
import CoreBluetooth
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
class MyPeripheralManager: MyPeripheralManagerHostApi {
init(_ binaryMessenger: FlutterBinaryMessenger) {
self.binaryMessenger = binaryMessenger
}
private let binaryMessenger: FlutterBinaryMessenger
private let peripheralManager = CBPeripheralManager()
private lazy var api = MyPeripheralManagerFlutterApi(binaryMessenger: binaryMessenger)
private lazy var peripheralManagerDelegate = MyPeripheralManagerDelegate(self)
private var centrals = [Int64: CBCentral]()
private var services = [Int64: CBMutableService]()
private var characteristics = [Int64: CBMutableCharacteristic]()
private var descriptors = [Int64: CBMutableDescriptor]()
private var requests = [Int64: CBATTRequest]()
private var centralsArgs = [Int: MyCentralArgs]()
private var servicesArgs = [Int: MyGattServiceArgs]()
private var characteristicsArgs = [Int: MyGattCharacteristicArgs]()
private var descriptorsArgs = [Int: MyGattDescriptorArgs]()
private var setUpCompletion: ((Result<MyPeripheralManagerArgs, Error>) -> Void)?
private var addServiceCompletion: ((Result<Void, Error>) -> Void)?
private var startAdvertisingCompletion: ((Result<Void, Error>) -> Void)?
private var notifyCharacteristicValueChangedCallbacks = [() -> Void]()
func setUp(completion: @escaping (Result<MyPeripheralManagerArgs, Error>) -> Void) {
do {
if setUpCompletion != nil {
throw MyError.illegalState
}
try tearDown()
peripheralManager.delegate = peripheralManagerDelegate
if peripheralManager.state == .unknown {
setUpCompletion = completion
} else {
let stateArgs = peripheralManager.state.toArgs()
let stateNumberArgs = Int64(stateArgs.rawValue)
let args = MyPeripheralManagerArgs(stateNumberArgs: stateNumberArgs)
completion(.success(args))
}
} catch {
completion(.failure(error))
}
}
func tearDown() throws {
if(peripheralManager.isAdvertising) {
peripheralManager.stopAdvertising()
}
centrals.removeAll()
services.removeAll()
characteristics.removeAll()
descriptors.removeAll()
requests.removeAll()
centralsArgs.removeAll()
servicesArgs.removeAll()
characteristicsArgs.removeAll()
descriptorsArgs.removeAll()
setUpCompletion = nil
addServiceCompletion = nil
startAdvertisingCompletion = nil
notifyCharacteristicValueChangedCallbacks.removeAll()
}
func addService(serviceArgs: MyGattServiceArgs, completion: @escaping (Result<Void, Error>) -> Void) {
do {
if addServiceCompletion != nil {
throw MyError.illegalState
}
let service = serviceArgs.toService()
var characteristics = [CBMutableCharacteristic]()
let characteristicsArgs = serviceArgs.characteristicsArgs
for args in characteristicsArgs {
guard let characteristicArgs = args else {
continue
}
let characteristic = characteristicArgs.toCharacteristic()
var descriptors = [CBMutableDescriptor]()
let descriptorsArgs = characteristicArgs.descriptorsArgs
for args in descriptorsArgs {
guard let descriptorArgs = args else {
continue
}
let descriptor = descriptorArgs.toDescriptor()
descriptors.append(descriptor)
let descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
let descriptorHashCode = descriptor.hash
self.descriptorsArgs[descriptorHashCode] = descriptorArgs
self.descriptors[descriptorHashCodeArgs] = descriptor
}
characteristic.descriptors = descriptors
characteristics.append(characteristic)
let characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
let characteristicHashCode = characteristic.hash
self.characteristicsArgs[characteristicHashCode] = characteristicArgs
self.characteristics[characteristicHashCodeArgs] = characteristic
}
service.characteristics = characteristics
let serviceHashCodeArgs = serviceArgs.hashCodeArgs
let serviceHashCode = service.hash
self.servicesArgs[serviceHashCode] = serviceArgs
self.services[serviceHashCodeArgs] = service
peripheralManager.add(service)
addServiceCompletion = completion
} catch {
freeService(serviceArgs)
completion(.failure(error))
}
}
func removeService(serviceHashCodeArgs: Int64) throws {
guard let service = services[serviceHashCodeArgs] else {
throw MyError.illegalArgument
}
let serviceHashCode = service.hash
guard let serviceArgs = servicesArgs[serviceHashCode] else {
throw MyError.illegalArgument
}
peripheralManager.remove(service)
freeService(serviceArgs)
}
func clearServices() throws {
peripheralManager.removeAllServices()
let servicesArgs = self.servicesArgs.values
for serviceArgs in servicesArgs {
freeService(serviceArgs)
}
}
private func freeService(_ serviceArgs: MyGattServiceArgs) {
let characteristicsArgs = serviceArgs.characteristicsArgs
for args in characteristicsArgs {
guard let characteristicArgs = args else {
continue
}
let descriptorsArgs = characteristicArgs.descriptorsArgs
for args in descriptorsArgs {
guard let descriptorArgs = args else {
continue
}
let descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
guard let descriptor = self.descriptors.removeValue(forKey: descriptorHashCodeArgs) else {
continue
}
let descriptorHashCode = descriptor.hash
self.descriptorsArgs.removeValue(forKey: descriptorHashCode)
}
let characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
guard let characteristic = self.characteristics.removeValue(forKey: characteristicHashCodeArgs) else {
continue
}
let characteristicHashCode = characteristic.hash
self.characteristicsArgs.removeValue(forKey: characteristicHashCode)
}
let serviceHashCodeArgs = serviceArgs.hashCodeArgs
guard let service = self.services.removeValue(forKey: serviceHashCodeArgs) else {
return
}
let serviceHashCode = service.hash
self.servicesArgs.removeValue(forKey: serviceHashCode)
}
func startAdvertising(advertiseDataArgs: MyAdvertiseDataArgs, completion: @escaping (Result<Void, Error>) -> Void) {
do {
if startAdvertisingCompletion != nil {
throw MyError.illegalState
}
let advertisementData = try advertiseDataArgs.toAdvertiseData()
peripheralManager.startAdvertising(advertisementData)
startAdvertisingCompletion = completion
} catch {
completion(.failure(error))
}
}
func stopAdvertising() throws {
peripheralManager.stopAdvertising()
}
func getMaximumWriteLength(centralHashCodeArgs: Int64) throws -> Int64 {
guard let central = centrals[centralHashCodeArgs] else {
throw MyError.illegalArgument
}
let maximumWriteLength = central.maximumUpdateValueLength
let maximumWriteLengthArgs = Int64(maximumWriteLength)
return maximumWriteLengthArgs
}
func sendReadCharacteristicReply(centralHashCodeArgs: Int64, characteristicHashCodeArgs: Int64, idArgs: Int64, offsetArgs: Int64, statusArgs: Bool, valueArgs: FlutterStandardTypedData) throws {
guard let request = requests[idArgs] else {
throw MyError.illegalArgument
}
request.value = valueArgs.data
let result = statusArgs ? CBATTError.Code.success : CBATTError.Code.requestNotSupported
peripheralManager.respond(to: request, withResult: result)
}
func sendWriteCharacteristicReply(centralHashCodeArgs: Int64, characteristicHashCodeArgs: Int64, idArgs: Int64, offsetArgs: Int64, statusArgs: Bool) throws {
guard let request = requests[idArgs] else {
throw MyError.illegalArgument
}
let result = statusArgs ? CBATTError.Code.success : CBATTError.Code.requestNotSupported
peripheralManager.respond(to: request, withResult: result)
}
func notifyCharacteristicValueChanged(centralHashCodeArgs: Int64, characteristicHashCodeArgs: Int64, valueArgs: FlutterStandardTypedData, completion: @escaping (Result<Void, Error>) -> Void) {
do {
if notifyCharacteristicValueChangedCallbacks.count > 0 {
throw MyError.illegalState
}
let value = valueArgs.data
guard let characteristic = characteristics[characteristicHashCodeArgs] else {
throw MyError.illegalArgument
}
guard let central = centrals[centralHashCodeArgs] else {
throw MyError.illegalArgument
}
let centrals = [central]
let updated = peripheralManager.updateValue(value, for: characteristic, onSubscribedCentrals: centrals)
if updated {
completion(.success(()))
} else {
notifyCharacteristicValueChangedCallbacks.append {
let updated = self.peripheralManager.updateValue(value, for: characteristic, onSubscribedCentrals: centrals)
if updated {
completion(.success(()))
} else {
completion(.failure(MyError.unknown))
}
}
}
} catch {
completion(.failure(error))
}
}
func didUpdateState() {
let state = peripheralManager.state
let stateArgs = state.toArgs()
let stateNumberArgs = Int64(stateArgs.rawValue)
if state != .unknown && setUpCompletion != nil {
let args = MyPeripheralManagerArgs(stateNumberArgs: stateNumberArgs)
setUpCompletion!(.success(args))
setUpCompletion = nil
}
api.onStateChanged(stateNumberArgs: stateNumberArgs) {}
}
func didAdd(_ service: CBService, _ error: Error?) {
guard let completion = addServiceCompletion else {
return
}
addServiceCompletion = nil
if error == nil {
completion(.success(()))
} else {
completion(.failure(error!))
let serviceHashCode = service.hash
guard let serviceArgs = servicesArgs[serviceHashCode] else {
return
}
freeService(serviceArgs)
}
}
func didStartAdvertising(_ error: Error?) {
guard let completion = startAdvertisingCompletion else {
return
}
startAdvertisingCompletion = nil
if error == nil {
completion(.success(()))
} else {
completion(.failure(error!))
}
}
func didReceiveRead(_ request: CBATTRequest) {
let central = request.central
let centralHashCode = central.hash
let centralArgs = centralsArgs.getOrPut(centralHashCode) { central.toArgs() }
let centralHashCodeArgs = centralArgs.hashCodeArgs
centrals[centralHashCodeArgs] = central
let characteristic = request.characteristic
let characteristicHashCode = characteristic.hash
guard let characteristicArgs = characteristicsArgs[characteristicHashCode] else {
peripheralManager.respond(to: request, withResult: .attributeNotFound)
return
}
let idArgs = Int64(request.hash)
requests[idArgs] = request
let offsetArgs = Int64(request.offset)
api.onReadCharacteristicCommandReceived(centralArgs: centralArgs, characteristicArgs: characteristicArgs, idArgs: idArgs, offsetArgs: offsetArgs) {}
}
func didReceiveWrite(_ requests: [CBATTRequest]) {
for request in requests {
let central = request.central
let centralHashCode = central.hash
let centralArgs = centralsArgs.getOrPut(centralHashCode) { central.toArgs() }
let centralHashCodeArgs = centralArgs.hashCodeArgs
centrals[centralHashCodeArgs] = central
let characteristic = request.characteristic
let characteristicHashCode = characteristic.hash
guard let characteristicArgs = characteristicsArgs[characteristicHashCode] else {
peripheralManager.respond(to: request, withResult: .attributeNotFound)
return
}
let idArgs = Int64(request.hash)
self.requests[idArgs] = request
let offsetArgs = Int64(request.offset)
guard let value = request.value else {
peripheralManager.respond(to: request, withResult: .requestNotSupported)
return
}
let valueArgs = FlutterStandardTypedData(bytes: value)
api.onWriteCharacteristicCommandReceived(centralArgs: centralArgs, characteristicArgs: characteristicArgs, idArgs: idArgs, offsetArgs: offsetArgs, valueArgs: valueArgs) {}
}
}
func didSubscribeTo(_ central: CBCentral, _ characteristic: CBCharacteristic) {
let centralHashCode = central.hash
let centralArgs = centralsArgs.getOrPut(centralHashCode) { central.toArgs() }
let centralHashCodeArgs = centralArgs.hashCodeArgs
centrals[centralHashCodeArgs] = central
let characteristicHashCode = characteristic.hash
guard let characteristicArgs = characteristicsArgs[characteristicHashCode] else {
return
}
let stateArgs = true
api.onNotifyCharacteristicCommandReceived(centralArgs: centralArgs, characteristicArgs: characteristicArgs, stateArgs: stateArgs) {}
}
func didUnsubscribeFrom(_ central: CBCentral, _ characteristic: CBCharacteristic) {
let centralHashCode = central.hash
let centralArgs = centralsArgs.getOrPut(centralHashCode) { central.toArgs() }
let centralHashCodeArgs = centralArgs.hashCodeArgs
centrals[centralHashCodeArgs] = central
let characteristicHashCode = characteristic.hash
guard let characteristicArgs = characteristicsArgs[characteristicHashCode] else {
return
}
let stateArgs = false
api.onNotifyCharacteristicCommandReceived(centralArgs: centralArgs, characteristicArgs: characteristicArgs, stateArgs: stateArgs) {}
}
func isReadyToUpdateSubscribers() {
let callbacks = notifyCharacteristicValueChangedCallbacks
notifyCharacteristicValueChangedCallbacks.removeAll()
for callback in callbacks {
callback()
}
}
}

View File

@ -0,0 +1,49 @@
//
// MyPeripheralManagerDelegate.swift
// bluetooth_low_energy_darwin
//
// Created by on 2023/10/7.
//
import Foundation
import CoreBluetooth
class MyPeripheralManagerDelegate: NSObject, CBPeripheralManagerDelegate {
init(_ peripheralManager: MyPeripheralManager) {
self.peripheralManager = peripheralManager
}
private let peripheralManager: MyPeripheralManager
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
peripheralManager.didUpdateState()
}
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
peripheralManager.didAdd(service, error)
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
peripheralManager.didStartAdvertising(error)
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
peripheralManager.didReceiveRead(request)
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
peripheralManager.didReceiveWrite(requests)
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
peripheralManager.didSubscribeTo(central, characteristic)
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
peripheralManager.didUnsubscribeFrom(central, characteristic)
}
func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) {
peripheralManager.isReadyToUpdateSubscribers()
}
}

View File

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

View File

@ -0,0 +1,224 @@
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_gatt_characteristic2.dart';
import 'my_gatt_descriptor2.dart';
import 'my_gatt_service2.dart';
export 'my_api.g.dart';
extension MyBluetoothLowEnergyStateArgsX on MyBluetoothLowEnergyStateArgs {
BluetoothLowEnergyState toState() {
return BluetoothLowEnergyState.values[index];
}
}
extension MyAdvertiseDataArgsX on MyAdvertiseDataArgs {
AdvertiseData toAdvertiseData() {
final name = nameArgs;
final serviceUUIDs = serviceUUIDsArgs
.cast<String>()
.map((args) => UUID.fromString(args))
.toList();
final serviceData = serviceDataArgs.cast<String, Uint8List>().map(
(uuidArgs, dataArgs) {
final uuid = UUID.fromString(uuidArgs);
final data = dataArgs;
return MapEntry(uuid, data);
},
);
final manufacturerSpecificData =
manufacturerSpecificDataArgs?.toManufacturerSpecificData();
return AdvertiseData(
name: name,
serviceUUIDs: serviceUUIDs,
serviceData: serviceData,
manufacturerSpecificData: manufacturerSpecificData,
);
}
}
extension MyManufacturerSpecificDataArgsX on MyManufacturerSpecificDataArgs {
ManufacturerSpecificData toManufacturerSpecificData() {
final id = idArgs;
final data = dataArgs;
return ManufacturerSpecificData(
id: id,
data: data,
);
}
}
extension MyGattCharacteristicPropertyArgsX
on MyGattCharacteristicPropertyArgs {
GattCharacteristicProperty toProperty() {
return GattCharacteristicProperty.values[index];
}
}
extension GattCharacteristicWriteTypeX on GattCharacteristicWriteType {
MyGattCharacteristicWriteTypeArgs toArgs() {
return MyGattCharacteristicWriteTypeArgs.values[index];
}
}
extension MyPeripheralArgsX on MyPeripheralArgs {
Peripheral toPeripheral() {
final hashCode = hashCodeArgs;
final uuid = UUID.fromString(uuidArgs);
return MyPeripheral(
hashCode: hashCode,
uuid: uuid,
);
}
}
extension MyGattServiceArgsX on MyGattServiceArgs {
MyGattService2 toService2() {
final hashCode = hashCodeArgs;
final uuid = UUID.fromString(uuidArgs);
final characteristics = characteristicsArgs
.cast<MyGattCharacteristicArgs>()
.map((args) => args.toCharacteristic2())
.toList();
return MyGattService2(
hashCode: hashCode,
uuid: uuid,
characteristics: characteristics,
);
}
}
extension MyGattCharacteristicArgsX on MyGattCharacteristicArgs {
MyGattCharacteristic2 toCharacteristic2() {
final hashCode = hashCodeArgs;
final uuid = UUID.fromString(uuidArgs);
final properties = propertyNumbersArgs.cast<int>().map(
(args) {
final propertyArgs = MyGattCharacteristicPropertyArgs.values[args];
return propertyArgs.toProperty();
},
).toList();
final descriptors = descriptorsArgs
.cast<MyGattDescriptorArgs>()
.map((args) => args.toDescriptor2())
.toList();
return MyGattCharacteristic2(
hashCode: hashCode,
uuid: uuid,
properties: properties,
descriptors: descriptors,
);
}
}
extension MyGattDescriptorArgsX on MyGattDescriptorArgs {
MyGattDescriptor2 toDescriptor2() {
final hashCode = hashCodeArgs;
final uuid = UUID.fromString(uuidArgs);
return MyGattDescriptor2(
hashCode: hashCode,
uuid: uuid,
);
}
}
extension MyCentralArgsX on MyCentralArgs {
MyCentral toCentral() {
final hashCode = hashCodeArgs;
final uuid = UUID.fromString(uuidArgs);
return MyCentral(
hashCode: hashCode,
uuid: uuid,
);
}
}
extension AdvertiseDataX on AdvertiseData {
MyAdvertiseDataArgs toArgs() {
final nameArgs = name;
final serviceUUIDsArgs =
serviceUUIDs.map((uuid) => uuid.toString()).toList();
final serviceDataArgs = serviceData.map((uuid, data) {
final uuidArgs = uuid.toString();
final dataArgs = data;
return MapEntry(uuidArgs, dataArgs);
});
final manufacturerSpecificDataArgs = manufacturerSpecificData?.toArgs();
return MyAdvertiseDataArgs(
nameArgs: nameArgs,
serviceUUIDsArgs: serviceUUIDsArgs,
serviceDataArgs: serviceDataArgs,
manufacturerSpecificDataArgs: manufacturerSpecificDataArgs,
);
}
}
extension ManufacturerSpecificDataX on ManufacturerSpecificData {
MyManufacturerSpecificDataArgs toArgs() {
final idArgs = id;
final dataArgs = data;
return MyManufacturerSpecificDataArgs(
idArgs: idArgs,
dataArgs: dataArgs,
);
}
}
extension MyGattServiceX on MyGattService {
MyGattServiceArgs toArgs() {
final hashCodeArgs = hashCode;
final uuidArgs = uuid.toString();
final characteristicsArgs = characteristics
.cast<MyGattCharacteristic>()
.map((characteristic) => characteristic.toArgs())
.toList();
return MyGattServiceArgs(
hashCodeArgs: hashCodeArgs,
uuidArgs: uuidArgs,
characteristicsArgs: characteristicsArgs,
);
}
}
extension MyGattCharacteristicX on MyGattCharacteristic {
MyGattCharacteristicArgs toArgs() {
final hashCodeArgs = hashCode;
final uuidArgs = uuid.toString();
final propertyNumbersArgs = properties.map((property) {
final propertyArgs = property.toArgs();
return propertyArgs.index;
}).toList();
final descriptorsArgs = descriptors
.cast<MyGattDescriptor>()
.map((descriptor) => descriptor.toArgs())
.toList();
return MyGattCharacteristicArgs(
hashCodeArgs: hashCodeArgs,
uuidArgs: uuidArgs,
propertyNumbersArgs: propertyNumbersArgs,
descriptorsArgs: descriptorsArgs,
);
}
}
extension MyGattDescriptorX on MyGattDescriptor {
MyGattDescriptorArgs toArgs() {
final hashCodeArgs = hashCode;
final uuidArgs = uuid.toString();
final valueArgs = value;
return MyGattDescriptorArgs(
hashCodeArgs: hashCodeArgs,
uuidArgs: uuidArgs,
valueArgs: valueArgs,
);
}
}
extension GattCharacteristicPropertyX on GattCharacteristicProperty {
MyGattCharacteristicPropertyArgs toArgs() {
return MyGattCharacteristicPropertyArgs.values[index];
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_central_manager.dart';
import 'my_peripheral_manager.dart';
class MyBluetoothLowEnergy extends BluetoothLowEnergy {
@override
final MyCentralManager centralManager;
@override
final MyPeripheralManager peripheralManager;
MyBluetoothLowEnergy()
: centralManager = MyCentralManager(),
peripheralManager = MyPeripheralManager();
}

View File

@ -0,0 +1,39 @@
import 'dart:async';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:flutter/foundation.dart';
abstract class MyBluetoothLowEnergyManager extends BluetoothLowEnergyManager {
MyBluetoothLowEnergyManager()
: _state = BluetoothLowEnergyState.unknown,
_stateChangedController = StreamController.broadcast();
final StreamController<BluetoothLowEnergyStateChangedEventArgs>
_stateChangedController;
BluetoothLowEnergyState _state;
@override
BluetoothLowEnergyState get state => _state;
@protected
set state(BluetoothLowEnergyState value) {
if (_state == value) {
return;
}
_state = value;
final eventArgs = BluetoothLowEnergyStateChangedEventArgs(state);
_stateChangedController.add(eventArgs);
}
@override
Stream<BluetoothLowEnergyStateChangedEventArgs> get stateChanged =>
_stateChangedController.stream;
@protected
Future<void> throwWithoutState(BluetoothLowEnergyState state) async {
if (this.state != state) {
throw BluetoothLowEnergyError(
'$state is expected, but current state is ${this.state}.',
);
}
}
}

View File

@ -1,370 +0,0 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_gatt_characteristic.dart';
import 'my_gatt_descriptor.dart';
import 'my_gatt_service.dart';
import 'my_peripheral.dart';
class MyCentralController extends CentralController
implements MyCentralControllerFlutterApi {
MyCentralController()
: _myApi = MyCentralControllerHostApi(),
_stateChangedController = StreamController.broadcast(),
_discoveredController = StreamController.broadcast(),
_peripheralStateChangedController = StreamController.broadcast(),
_characteristicValueChangedController = StreamController.broadcast(),
_myPeripherals = {},
_myServices = {},
_myCharacteristics = {},
_myDescriptors = {},
_state = CentralState.unknown;
final MyCentralControllerHostApi _myApi;
final StreamController<CentralStateChangedEventArgs> _stateChangedController;
final StreamController<CentralDiscoveredEventArgs> _discoveredController;
final StreamController<PeripheralStateChangedEventArgs>
_peripheralStateChangedController;
final StreamController<GattCharacteristicValueChangedEventArgs>
_characteristicValueChangedController;
final Map<int, MyPeripheral> _myPeripherals;
final Map<int, MyGattService> _myServices;
final Map<int, MyGattCharacteristic> _myCharacteristics;
final Map<int, MyGattDescriptor> _myDescriptors;
CentralState _state;
@override
CentralState get state => _state;
@override
Stream<CentralStateChangedEventArgs> get stateChanged =>
_stateChangedController.stream;
@override
Stream<CentralDiscoveredEventArgs> get discovered =>
_discoveredController.stream;
@override
Stream<PeripheralStateChangedEventArgs> get peripheralStateChanged =>
_peripheralStateChangedController.stream;
@override
Stream<GattCharacteristicValueChangedEventArgs>
get characteristicValueChanged =>
_characteristicValueChangedController.stream;
Future<void> _throwWithState(CentralState state) async {
if (this.state == state) {
throw BluetoothLowEnergyError('$state is unexpected.');
}
}
Future<void> _throwWithoutState(CentralState state) async {
if (this.state != state) {
throw BluetoothLowEnergyError(
'$state is expected, but current state is ${this.state}.',
);
}
}
@override
Future<void> setUp() async {
await _throwWithoutState(CentralState.unknown);
final args = await _myApi.setUp();
final myStateArgs = MyCentralStateArgs.values[args.myStateNumber];
_state = myStateArgs.toState();
MyCentralControllerFlutterApi.setup(this);
}
@override
Future<void> tearDown() async {
await _throwWithState(CentralState.unknown);
await _myApi.tearDown();
MyCentralControllerFlutterApi.setup(null);
_myPeripherals.clear();
_myServices.clear();
_myCharacteristics.clear();
_myDescriptors.clear();
_state = CentralState.unknown;
}
@override
Future<void> startDiscovery() async {
await _throwWithoutState(CentralState.poweredOn);
await _myApi.startDiscovery();
}
@override
Future<void> stopDiscovery() async {
await _throwWithoutState(CentralState.poweredOn);
await _myApi.stopDiscovery();
}
@override
Future<void> connect(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
await _myApi.connect(myPeripheral.hashCode);
}
@override
Future<void> disconnect(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
await _myApi.disconnect(myPeripheral.hashCode);
}
@override
Future<int> getMaximumWriteLength(
Peripheral peripheral, {
required GattCharacteristicWriteType type,
}) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
final myTypeArgs = type.toMyArgs();
final myTypeNumber = myTypeArgs.index;
final maximumWriteLength = await _myApi.getMaximumWriteLength(
myPeripheral.hashCode,
myTypeNumber,
);
return maximumWriteLength;
}
@override
Future<void> discoverGATT(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
await _myApi.discoverGATT(myPeripheral.hashCode);
}
@override
Future<List<GattService>> getServices(Peripheral peripheral) async {
await _throwWithoutState(CentralState.poweredOn);
final myPeripheral = peripheral as MyPeripheral;
final myServiceArgses = await _myApi.getServices(myPeripheral.hashCode);
return myServiceArgses.cast<MyGattServiceArgs>().map(
(myServiceArgs) {
final myService = MyGattService.fromMyArgs(
myPeripheral,
myServiceArgs,
);
_myServices[myService.hashCode] = myService;
return myService;
},
).toList();
}
@override
Future<List<GattCharacteristic>> getCharacteristics(
GattService service,
) async {
await _throwWithoutState(CentralState.poweredOn);
final myService = service as MyGattService;
final myCharactersiticArgses = await _myApi.getCharacteristics(
myService.hashCode,
);
return myCharactersiticArgses.cast<MyGattCharacteristicArgs>().map(
(myCharacteristicArgs) {
final myCharacteristic = MyGattCharacteristic.fromMyArgs(
myService,
myCharacteristicArgs,
);
_myCharacteristics[myCharacteristic.hashCode] = myCharacteristic;
return myCharacteristic;
},
).toList();
}
@override
Future<List<GattDescriptor>> getDescriptors(
GattCharacteristic characteristic,
) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final myDescriptorArgses = await _myApi.getDescriptors(
myCharacteristic.hashCode,
);
return myDescriptorArgses.cast<MyGattDescriptorArgs>().map(
(myDescriptorArgs) {
final myDescriptor = MyGattDescriptor.fromMyArgs(
myCharacteristic,
myDescriptorArgs,
);
_myDescriptors[myDescriptor.hashCode] = myDescriptor;
return myDescriptor;
},
).toList();
}
@override
Future<Uint8List> readCharacteristic(
GattCharacteristic characteristic,
) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final myService = myCharacteristic.myService;
final myPeripheral = myService.myPeripheral;
final value = await _myApi.readCharacteristic(
myPeripheral.hashCode,
myService.hashCode,
myCharacteristic.hashCode,
);
return value;
}
@override
Future<void> writeCharacteristic(
GattCharacteristic characteristic, {
required Uint8List value,
required GattCharacteristicWriteType type,
}) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final myService = myCharacteristic.myService;
final myPeripheral = myService.myPeripheral;
final myTypeArgs = type.toMyArgs();
final myTypeNumber = myTypeArgs.index;
await _myApi.writeCharacteristic(
myPeripheral.hashCode,
myService.hashCode,
myCharacteristic.hashCode,
value,
myTypeNumber,
);
}
@override
Future<void> notifyCharacteristic(
GattCharacteristic characteristic, {
required bool state,
}) async {
await _throwWithoutState(CentralState.poweredOn);
final myCharacteristic = characteristic as MyGattCharacteristic;
final myService = myCharacteristic.myService;
final myPeripheral = myService.myPeripheral;
await _myApi.notifyCharacteristic(
myPeripheral.hashCode,
myService.hashCode,
myCharacteristic.hashCode,
state,
);
}
@override
Future<Uint8List> readDescriptor(GattDescriptor descriptor) async {
await _throwWithoutState(CentralState.poweredOn);
final myDescriptor = descriptor as MyGattDescriptor;
final myCharacteristic = myDescriptor.myCharacteristic;
final myService = myCharacteristic.myService;
final myPeripheral = myService.myPeripheral;
final value = await _myApi.readDescriptor(
myPeripheral.hashCode,
myCharacteristic.hashCode,
myDescriptor.hashCode,
);
return value;
}
@override
Future<void> writeDescriptor(
GattDescriptor descriptor, {
required Uint8List value,
}) async {
await _throwWithoutState(CentralState.poweredOn);
final myDescriptor = descriptor as MyGattDescriptor;
final myCharacteristic = myDescriptor.myCharacteristic;
final myService = myCharacteristic.myService;
final myPeripheral = myService.myPeripheral;
await _myApi.writeDescriptor(
myPeripheral.hashCode,
myCharacteristic.hashCode,
myDescriptor.hashCode,
value,
);
}
@override
void onStateChanged(int myStateNumber) {
final myStateArgs = MyCentralStateArgs.values[myStateNumber];
final state = myStateArgs.toState();
if (_state == state) {
return;
}
_state = state;
final eventArgs = CentralStateChangedEventArgs(state);
_stateChangedController.add(eventArgs);
}
@override
void onDiscovered(
MyPeripheralArgs myPeripheralArgs,
int rssi,
MyAdvertisementArgs myAdvertisementArgs,
) {
final myPeripheral = MyPeripheral.fromMyArgs(myPeripheralArgs);
_myPeripherals[myPeripheral.hashCode] = myPeripheral;
final advertisement = myAdvertisementArgs.toAdvertisement();
final eventArgs = CentralDiscoveredEventArgs(
myPeripheral,
rssi,
advertisement,
);
_discoveredController.add(eventArgs);
}
@override
void onPeripheralStateChanged(int myPeripheralKey, bool state) {
final myPeripheral = _myPeripherals[myPeripheralKey];
if (myPeripheral == null) {
return;
}
final eventArgs = PeripheralStateChangedEventArgs(myPeripheral, state);
_peripheralStateChangedController.add(eventArgs);
}
@override
void onCharacteristicValueChanged(int myCharacteristicKey, Uint8List value) {
final myCharacteristic =
_myCharacteristics[myCharacteristicKey] as MyGattCharacteristic;
final eventArgs = GattCharacteristicValueChangedEventArgs(
myCharacteristic,
value,
);
_characteristicValueChangedController.add(eventArgs);
}
}
extension on MyAdvertisementArgs {
Advertisement toAdvertisement() {
final serviceUUIDs = this
.serviceUUIDs
.cast<String>()
.map((uuid) => UUID.fromString(uuid))
.toList();
final serviceData = this.serviceData.cast<String, Uint8List>().map(
(uuid, data) {
final key = UUID.fromString(uuid);
final value = data;
return MapEntry(key, value);
},
);
return Advertisement(
name: name,
manufacturerSpecificData: manufacturerSpecificData.cast<int, Uint8List>(),
serviceUUIDs: serviceUUIDs,
serviceData: serviceData,
);
}
}
extension on MyCentralStateArgs {
CentralState toState() {
return CentralState.values[index];
}
}
extension on GattCharacteristicWriteType {
MyGattCharacteristicWriteTypeArgs toMyArgs() {
return MyGattCharacteristicWriteTypeArgs.values[index];
}
}

View File

@ -0,0 +1,272 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.dart';
import 'my_bluetooth_low_energy_manager.dart';
import 'my_gatt_characteristic2.dart';
import 'my_gatt_descriptor2.dart';
class MyCentralManager extends MyBluetoothLowEnergyManager
implements CentralManager, MyCentralManagerFlutterApi {
MyCentralManager()
: _api = MyCentralManagerHostApi(),
_discoveredController = StreamController.broadcast(),
_peripheralStateChangedController = StreamController.broadcast(),
_characteristicValueChangedController = StreamController.broadcast();
final MyCentralManagerHostApi _api;
final StreamController<DiscoveredEventArgs> _discoveredController;
final StreamController<PeripheralStateChangedEventArgs>
_peripheralStateChangedController;
final StreamController<GattCharacteristicValueChangedEventArgs>
_characteristicValueChangedController;
@override
Stream<DiscoveredEventArgs> get discovered => _discoveredController.stream;
@override
Stream<PeripheralStateChangedEventArgs> get peripheralStateChanged =>
_peripheralStateChangedController.stream;
@override
Stream<GattCharacteristicValueChangedEventArgs>
get characteristicValueChanged =>
_characteristicValueChangedController.stream;
@override
Future<void> setUp() async {
await throwWithoutState(BluetoothLowEnergyState.unknown);
final args = await _api.setUp();
final stateArgs =
MyBluetoothLowEnergyStateArgs.values[args.stateNumberArgs];
state = stateArgs.toState();
MyCentralManagerFlutterApi.setup(this);
}
@override
Future<void> startDiscovery() async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
await _api.startDiscovery();
}
@override
Future<void> stopDiscovery() async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
await _api.stopDiscovery();
}
@override
Future<void> connect(Peripheral peripheral) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final peripheralHashCodeArgs = peripheral.hashCode;
await _api.connect(peripheralHashCodeArgs);
}
@override
Future<void> disconnect(Peripheral peripheral) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final peripheralHashCodeArgs = peripheral.hashCode;
await _api.disconnect(peripheralHashCodeArgs);
}
@override
Future<int> getMaximumWriteLength(
Peripheral peripheral, {
required GattCharacteristicWriteType type,
}) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final peripheralHashCodeArgs = peripheral.hashCode;
final typeArgs = type.toArgs();
final typeNumberArgs = typeArgs.index;
final maximumWriteLength = await _api.getMaximumWriteLength(
peripheralHashCodeArgs,
typeNumberArgs,
);
return maximumWriteLength;
}
@override
Future<int> readRSSI(Peripheral peripheral) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final peripheralHashCodeArgs = peripheral.hashCode;
final rssi = await _api.readRSSI(peripheralHashCodeArgs);
return rssi;
}
@override
Future<List<GattService>> discoverGATT(Peripheral peripheral) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (peripheral is! MyPeripheral) {
throw TypeError();
}
final peripheralHashCodeArgs = peripheral.hashCode;
final servicesArgs = await _api.discoverGATT(peripheralHashCodeArgs);
final services = servicesArgs
.cast<MyGattServiceArgs>()
.map((args) => args.toService2())
.toList();
for (var service in services) {
for (var charactersitic in service.characteristics) {
for (var descriptor in charactersitic.descriptors) {
descriptor.characteristic = charactersitic;
}
charactersitic.service = service;
}
service.peripheral = peripheral;
}
return services;
}
@override
Future<Uint8List> readCharacteristic(
GattCharacteristic characteristic,
) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (characteristic is! MyGattCharacteristic2) {
throw TypeError();
}
final service = characteristic.service;
final peripheral = service.peripheral;
final peripheralHashCodeArgs = peripheral.hashCode;
final characteristicHashCodeArgs = characteristic.hashCode;
final value = await _api.readCharacteristic(
peripheralHashCodeArgs,
characteristicHashCodeArgs,
);
return value;
}
@override
Future<void> writeCharacteristic(
GattCharacteristic characteristic, {
required Uint8List value,
required GattCharacteristicWriteType type,
}) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (characteristic is! MyGattCharacteristic2) {
throw TypeError();
}
final service = characteristic.service;
final peripheral = service.peripheral;
final peripheralHashCodeArgs = peripheral.hashCode;
final characteristicHashCodeArgs = characteristic.hashCode;
final valueArgs = value;
final typeArgs = type.toArgs();
final typeNumberArgs = typeArgs.index;
await _api.writeCharacteristic(
peripheralHashCodeArgs,
characteristicHashCodeArgs,
valueArgs,
typeNumberArgs,
);
}
@override
Future<void> notifyCharacteristic(
GattCharacteristic characteristic, {
required bool state,
}) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (characteristic is! MyGattCharacteristic2) {
throw TypeError();
}
final service = characteristic.service;
final peripheral = service.peripheral;
final peripheralHashCodeArgs = peripheral.hashCode;
final characteristicHashCodeArgs = characteristic.hashCode;
final stateArgs = state;
await _api.notifyCharacteristic(
peripheralHashCodeArgs,
characteristicHashCodeArgs,
stateArgs,
);
}
@override
Future<Uint8List> readDescriptor(GattDescriptor descriptor) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (descriptor is! MyGattDescriptor2) {
throw TypeError();
}
final characteristic = descriptor.characteristic;
final service = characteristic.service;
final peripheral = service.peripheral;
final peripheralHashCodeArgs = peripheral.hashCode;
final descriptorHashCodeArgs = descriptor.hashCode;
final value = await _api.readDescriptor(
peripheralHashCodeArgs,
descriptorHashCodeArgs,
);
return value;
}
@override
Future<void> writeDescriptor(
GattDescriptor descriptor, {
required Uint8List value,
}) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (descriptor is! MyGattDescriptor2) {
throw TypeError();
}
final characteristic = descriptor.characteristic;
final service = characteristic.service;
final peripheral = service.peripheral;
final peripheralHashCodeArgs = peripheral.hashCode;
final descriptorHashCodeArgs = descriptor.hashCode;
final valueArgs = value;
await _api.writeDescriptor(
peripheralHashCodeArgs,
descriptorHashCodeArgs,
valueArgs,
);
}
@override
void onStateChanged(int stateNumberArgs) {
final stateArgs = MyBluetoothLowEnergyStateArgs.values[stateNumberArgs];
state = stateArgs.toState();
}
@override
void onDiscovered(
MyPeripheralArgs peripheralArgs,
int rssiArgs,
MyAdvertiseDataArgs advertiseDataArgs,
) {
final peripheral = peripheralArgs.toPeripheral();
final rssi = rssiArgs;
final advertiseData = advertiseDataArgs.toAdvertiseData();
final eventArgs = DiscoveredEventArgs(
peripheral,
rssi,
advertiseData,
);
_discoveredController.add(eventArgs);
}
@override
void onPeripheralStateChanged(
MyPeripheralArgs peripheralArgs,
bool stateArgs,
) {
final peripheral = peripheralArgs.toPeripheral();
final state = stateArgs;
final eventArgs = PeripheralStateChangedEventArgs(peripheral, state);
_peripheralStateChangedController.add(eventArgs);
}
@override
void onCharacteristicValueChanged(
MyGattCharacteristicArgs characteristicArgs,
Uint8List valueArgs,
) {
final characteristic = characteristicArgs.toCharacteristic2();
final value = valueArgs;
final eventArgs = GattCharacteristicValueChangedEventArgs(
characteristic,
value,
);
_characteristicValueChangedController.add(eventArgs);
}
}

View File

@ -1,42 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_gatt_service.dart';
import 'my_object.dart';
class MyGattCharacteristic extends MyObject implements GattCharacteristic {
final MyGattService myService;
@override
final UUID uuid;
@override
final List<GattCharacteristicProperty> properties;
MyGattCharacteristic(
super.hashCode,
this.myService,
this.uuid,
this.properties,
);
factory MyGattCharacteristic.fromMyArgs(
MyGattService myService,
MyGattCharacteristicArgs myArgs,
) {
final hashCode = myArgs.key;
final uuid = UUID.fromString(myArgs.uuid);
final properties = myArgs.myPropertyNumbers.cast<int>().map(
(myPropertyNumber) {
final myPropertyArgs =
MyGattCharacteristicPropertyArgs.values[myPropertyNumber];
return myPropertyArgs.toProperty();
},
).toList();
return MyGattCharacteristic(hashCode, myService, uuid, properties);
}
}
extension on MyGattCharacteristicPropertyArgs {
GattCharacteristicProperty toProperty() {
return GattCharacteristicProperty.values[index];
}
}

View File

@ -0,0 +1,19 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_gatt_descriptor2.dart';
import 'my_gatt_service2.dart';
class MyGattCharacteristic2 extends MyGattCharacteristic {
late final MyGattService2 service;
MyGattCharacteristic2({
super.hashCode,
required super.uuid,
required super.properties,
required List<MyGattDescriptor2> descriptors,
}) : super(descriptors: descriptors);
@override
List<MyGattDescriptor2> get descriptors =>
super.descriptors.cast<MyGattDescriptor2>();
}

View File

@ -1,22 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_gatt_characteristic.dart';
import 'my_object.dart';
class MyGattDescriptor extends MyObject implements GattDescriptor {
final MyGattCharacteristic myCharacteristic;
@override
final UUID uuid;
MyGattDescriptor(super.hashCode, this.myCharacteristic, this.uuid);
factory MyGattDescriptor.fromMyArgs(
MyGattCharacteristic myCharacteristic,
MyGattDescriptorArgs myArgs,
) {
final hashCode = myArgs.key;
final uuid = UUID.fromString(myArgs.uuid);
return MyGattDescriptor(hashCode, myCharacteristic, uuid);
}
}

View File

@ -0,0 +1,12 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_gatt_characteristic2.dart';
class MyGattDescriptor2 extends MyGattDescriptor {
late final MyGattCharacteristic2 characteristic;
MyGattDescriptor2({
super.hashCode,
required super.uuid,
});
}

View File

@ -1,22 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_object.dart';
import 'my_peripheral.dart';
class MyGattService extends MyObject implements GattService {
final MyPeripheral myPeripheral;
@override
final UUID uuid;
MyGattService(super.hashCode, this.myPeripheral, this.uuid);
factory MyGattService.fromMyArgs(
MyPeripheral myPeripheral,
MyGattServiceArgs myArgs,
) {
final hashCode = myArgs.key;
final uuid = UUID.fromString(myArgs.uuid);
return MyGattService(hashCode, myPeripheral, uuid);
}
}

View File

@ -0,0 +1,17 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_gatt_characteristic2.dart';
class MyGattService2 extends MyGattService {
late final MyPeripheral peripheral;
MyGattService2({
super.hashCode,
required super.uuid,
required List<MyGattCharacteristic2> characteristics,
}) : super(characteristics: characteristics);
@override
List<MyGattCharacteristic2> get characteristics =>
super.characteristics.cast<MyGattCharacteristic2>();
}

View File

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

View File

@ -1,17 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_object.dart';
class MyPeripheral extends MyObject implements Peripheral {
@override
final UUID uuid;
MyPeripheral(super.hashCode, this.uuid);
factory MyPeripheral.fromMyArgs(MyPeripheralArgs myArgs) {
final hashCode = myArgs.key;
final uuid = UUID.fromString(myArgs.uuid);
return MyPeripheral(hashCode, uuid);
}
}

View File

@ -0,0 +1,228 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.dart';
import 'my_bluetooth_low_energy_manager.dart';
class MyPeripheralManager extends MyBluetoothLowEnergyManager
implements PeripheralManager, MyPeripheralManagerFlutterApi {
final MyPeripheralManagerHostApi _api;
final StreamController<ReadGattCharacteristicCommandEventArgs>
_readCharacteristicCommandReceivedController;
final StreamController<WriteGattCharacteristicCommandEventArgs>
_writeCharacteristicCommandReceivedController;
final StreamController<NotifyGattCharacteristicCommandEventArgs>
_notifyCharacteristicCommandReceivedController;
MyPeripheralManager()
: _api = MyPeripheralManagerHostApi(),
_readCharacteristicCommandReceivedController =
StreamController.broadcast(),
_writeCharacteristicCommandReceivedController =
StreamController.broadcast(),
_notifyCharacteristicCommandReceivedController =
StreamController.broadcast();
@override
Stream<ReadGattCharacteristicCommandEventArgs>
get readCharacteristicCommandReceived =>
_readCharacteristicCommandReceivedController.stream;
@override
Stream<WriteGattCharacteristicCommandEventArgs>
get writeCharacteristicCommandReceived =>
_writeCharacteristicCommandReceivedController.stream;
@override
Stream<NotifyGattCharacteristicCommandEventArgs>
get notifyCharacteristicCommandReceived =>
_notifyCharacteristicCommandReceivedController.stream;
@override
Future<void> setUp() async {
final args = await _api.setUp();
final stateArgs =
MyBluetoothLowEnergyStateArgs.values[args.stateNumberArgs];
state = stateArgs.toState();
MyPeripheralManagerFlutterApi.setup(this);
}
@override
Future<void> addService(GattService service) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
if (service is! MyGattService) {
throw TypeError();
}
final serviceArgs = service.toArgs();
await _api.addService(serviceArgs);
}
@override
Future<void> removeService(GattService service) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final serviceHashCodeArgs = service.hashCode;
await _api.removeService(serviceHashCodeArgs);
}
@override
Future<void> clearServices() async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
await _api.clearServices();
}
@override
Future<void> startAdvertising(AdvertiseData advertiseData) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final advertiseDataArgs = advertiseData.toArgs();
await _api.startAdvertising(advertiseDataArgs);
}
@override
Future<void> stopAdvertising() async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
await _api.stopAdvertising();
}
@override
Future<int> getMaximumWriteLength(Central central) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final centralHashCodeArgs = central.hashCode;
final maximumWriteLength =
await _api.getMaximumWriteLength(centralHashCodeArgs);
return maximumWriteLength;
}
@override
Future<void> sendReadCharacteristicReply(
Central central,
GattCharacteristic characteristic,
int id,
int offset,
bool status,
Uint8List value,
) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final centralHashCodeArgs = central.hashCode;
final characteristicHashCodeArgs = characteristic.hashCode;
final idArgs = id;
final offsetArgs = offset;
final statusArgs = status;
final valueArgs = value;
await _api.sendReadCharacteristicReply(
centralHashCodeArgs,
characteristicHashCodeArgs,
idArgs,
offsetArgs,
statusArgs,
valueArgs,
);
}
@override
Future<void> sendWriteCharacteristicReply(
Central central,
GattCharacteristic characteristic,
int id,
int offset,
bool status,
) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final centralHashCodeArgs = central.hashCode;
final characteristicHashCodeArgs = characteristic.hashCode;
final idArgs = id;
final offsetArgs = offset;
final statusArgs = status;
await _api.sendWriteCharacteristicReply(
centralHashCodeArgs,
characteristicHashCodeArgs,
idArgs,
offsetArgs,
statusArgs,
);
}
@override
Future<void> notifyCharacteristicValueChanged(
Central central,
GattCharacteristic characteristic,
Uint8List value,
) async {
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
final centralHashCodeArgs = central.hashCode;
final characteristicHashCodeArgs = characteristic.hashCode;
final valueArgs = value;
await _api.notifyCharacteristicValueChanged(
centralHashCodeArgs,
characteristicHashCodeArgs,
valueArgs,
);
}
@override
void onStateChanged(int stateNumberArgs) {
final stateArgs = MyBluetoothLowEnergyStateArgs.values[stateNumberArgs];
state = stateArgs.toState();
}
@override
void onReadCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
int idArgs,
int offsetArgs,
) {
final central = centralArgs.toCentral();
final characteristic = characteristicArgs.toCharacteristic2();
final id = idArgs;
final offset = offsetArgs;
final eventArgs = ReadGattCharacteristicCommandEventArgs(
central,
characteristic,
id,
offset,
);
_readCharacteristicCommandReceivedController.add(eventArgs);
}
@override
void onWriteCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
int idArgs,
int offsetArgs,
Uint8List valueArgs,
) {
final central = centralArgs.toCentral();
final characteristic = characteristicArgs.toCharacteristic2();
final id = idArgs;
final offset = offsetArgs;
final value = valueArgs;
final eventArgs = WriteGattCharacteristicCommandEventArgs(
central,
characteristic,
id,
offset,
value,
);
_writeCharacteristicCommandReceivedController.add(eventArgs);
}
@override
void onNotifyCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
bool stateArgs,
) {
final central = centralArgs.toCentral();
final characteristic = characteristicArgs.toCharacteristic2();
final state = stateArgs;
final eventArgs = NotifyGattCharacteristicCommandEventArgs(
central,
characteristic,
state,
);
_notifyCharacteristicCommandReceivedController.add(eventArgs);
}
}

View File

@ -9,124 +9,213 @@ import 'package:pigeon/pigeon.dart';
),
)
@HostApi()
abstract class MyCentralControllerHostApi {
abstract class MyCentralManagerHostApi {
@async
MyCentralControllerArgs setUp();
void tearDown();
MyCentralManagerArgs setUp();
void startDiscovery();
void stopDiscovery();
@async
void connect(int myPeripheralKey);
void connect(int peripheralHashCodeArgs);
@async
void disconnect(int myPeripheralKey);
int getMaximumWriteLength(int myPeripheralKey, int myTypeNumber);
void disconnect(int peripheralHashCodeArgs);
int getMaximumWriteLength(int peripheralHashCodeArgs, int typeNumberArgs);
@async
void discoverGATT(int myPeripheralKey);
List<MyGattServiceArgs> getServices(int myPeripheralKey);
List<MyGattCharacteristicArgs> getCharacteristics(int myServiceKey);
List<MyGattDescriptorArgs> getDescriptors(int myCharacteristicKey);
int readRSSI(int peripheralHashCodeArgs);
@async
List<MyGattServiceArgs> discoverGATT(int peripheralHashCodeArgs);
@async
Uint8List readCharacteristic(
int myPeripheralKey,
int myServiceKey,
int myCharacteristicKey,
int peripheralHashCodeArgs,
int characteristicHashCodeArgs,
);
@async
void writeCharacteristic(
int myPeripheralKey,
int myServiceKey,
int myCharacteristicKey,
Uint8List value,
int myTypeNumber,
int peripheralHashCodeArgs,
int characteristicHashCodeArgs,
Uint8List valueArgs,
int typeNumberArgs,
);
@async
void notifyCharacteristic(
int myPeripheralKey,
int myServiceKey,
int myCharacteristicKey,
bool state,
int peripheralHashCodeArgs,
int characteristicHashCodeArgs,
bool stateArgs,
);
@async
Uint8List readDescriptor(
int myPeripheralKey,
int myCharacteristicKey,
int myDescriptorKey,
int peripheralHashCodeArgs,
int descriptorHashCodeArgs,
);
@async
void writeDescriptor(
int myPeripheralKey,
int myCharacteristicKey,
int myDescriptorKey,
Uint8List value,
int peripheralHashCodeArgs,
int descriptorHashCodeArgs,
Uint8List valueArgs,
);
}
@FlutterApi()
abstract class MyCentralControllerFlutterApi {
void onStateChanged(int myStateNumber);
abstract class MyCentralManagerFlutterApi {
void onStateChanged(int stateNumberArgs);
void onDiscovered(
MyPeripheralArgs myPeripheralArgs,
int rssi,
MyAdvertisementArgs myAdvertisementArgs,
MyPeripheralArgs peripheralArgs,
int rssiArgs,
MyAdvertiseDataArgs advertiseDataArgs,
);
void onPeripheralStateChanged(
MyPeripheralArgs peripheralArgs,
bool stateArgs,
);
void onCharacteristicValueChanged(
MyGattCharacteristicArgs characteristicArgs,
Uint8List valueArgs,
);
void onPeripheralStateChanged(int myPeripheralKey, bool state);
void onCharacteristicValueChanged(int myCharacteristicKey, Uint8List value);
}
class MyCentralControllerArgs {
final int myStateNumber;
@HostApi()
abstract class MyPeripheralManagerHostApi {
@async
MyPeripheralManagerArgs setUp();
@async
void addService(MyGattServiceArgs serviceArgs);
void removeService(int serviceHashCodeArgs);
void clearServices();
@async
void startAdvertising(MyAdvertiseDataArgs advertiseDataArgs);
void stopAdvertising();
int getMaximumWriteLength(int centralHashCodeArgs);
void sendReadCharacteristicReply(
int centralHashCodeArgs,
int characteristicHashCodeArgs,
int idArgs,
int offsetArgs,
bool statusArgs,
Uint8List valueArgs,
);
void sendWriteCharacteristicReply(
int centralHashCodeArgs,
int characteristicHashCodeArgs,
int idArgs,
int offsetArgs,
bool statusArgs,
);
@async
void notifyCharacteristicValueChanged(
int centralHashCodeArgs,
int characteristicHashCodeArgs,
Uint8List valueArgs,
);
}
MyCentralControllerArgs(this.myStateNumber);
@FlutterApi()
abstract class MyPeripheralManagerFlutterApi {
void onStateChanged(int stateNumberArgs);
void onReadCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
int idArgs,
int offsetArgs,
);
void onWriteCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
int idArgs,
int offsetArgs,
Uint8List valueArgs,
);
void onNotifyCharacteristicCommandReceived(
MyCentralArgs centralArgs,
MyGattCharacteristicArgs characteristicArgs,
bool stateArgs,
);
}
class MyCentralManagerArgs {
final int stateNumberArgs;
MyCentralManagerArgs(this.stateNumberArgs);
}
class MyPeripheralManagerArgs {
final int stateNumberArgs;
MyPeripheralManagerArgs(this.stateNumberArgs);
}
class MyCentralArgs {
final int hashCodeArgs;
final String uuidArgs;
MyCentralArgs(this.hashCodeArgs, this.uuidArgs);
}
class MyPeripheralArgs {
final int key;
final String uuid;
final int hashCodeArgs;
final String uuidArgs;
MyPeripheralArgs(this.key, this.uuid);
MyPeripheralArgs(this.hashCodeArgs, this.uuidArgs);
}
class MyAdvertisementArgs {
final String? name;
final Map<int?, Uint8List?> manufacturerSpecificData;
final List<String?> serviceUUIDs;
final Map<String?, Uint8List?> serviceData;
class MyAdvertiseDataArgs {
final String? nameArgs;
final List<String?> serviceUUIDsArgs;
final Map<String?, Uint8List?> serviceDataArgs;
final MyManufacturerSpecificDataArgs? manufacturerSpecificDataArgs;
MyAdvertisementArgs(
this.name,
this.manufacturerSpecificData,
this.serviceUUIDs,
this.serviceData,
MyAdvertiseDataArgs(
this.nameArgs,
this.serviceUUIDsArgs,
this.serviceDataArgs,
this.manufacturerSpecificDataArgs,
);
}
class MyGattServiceArgs {
final int key;
final String uuid;
class MyManufacturerSpecificDataArgs {
final int idArgs;
final Uint8List dataArgs;
MyGattServiceArgs(this.key, this.uuid);
MyManufacturerSpecificDataArgs(this.idArgs, this.dataArgs);
}
class MyGattServiceArgs {
final int hashCodeArgs;
final String uuidArgs;
final List<MyGattCharacteristicArgs?> characteristicsArgs;
MyGattServiceArgs(
this.hashCodeArgs,
this.uuidArgs,
this.characteristicsArgs,
);
}
class MyGattCharacteristicArgs {
final int key;
final String uuid;
final List<int?> myPropertyNumbers;
final int hashCodeArgs;
final String uuidArgs;
final List<int?> propertyNumbersArgs;
final List<MyGattDescriptorArgs?> descriptorsArgs;
MyGattCharacteristicArgs(
this.key,
this.uuid,
this.myPropertyNumbers,
this.hashCodeArgs,
this.uuidArgs,
this.propertyNumbersArgs,
this.descriptorsArgs,
);
}
class MyGattDescriptorArgs {
final int key;
final String uuid;
final int hashCodeArgs;
final String uuidArgs;
final Uint8List? valueArgs;
MyGattDescriptorArgs(this.key, this.uuid);
MyGattDescriptorArgs(
this.hashCodeArgs,
this.uuidArgs,
this.valueArgs,
);
}
enum MyCentralStateArgs {
enum MyBluetoothLowEnergyStateArgs {
unknown,
unsupported,
unauthorized,

View File

@ -1,6 +1,6 @@
name: bluetooth_low_energy_darwin
description: iOS and macOS implementation of the bluetooth_low_energy plugin.
version: 2.2.0
version: 3.0.0
homepage: https://github.com/yanshouwang/bluetooth_low_energy
environment:
@ -10,13 +10,13 @@ environment:
dependencies:
flutter:
sdk: flutter
bluetooth_low_energy_platform_interface: ^2.2.0
bluetooth_low_energy_platform_interface: ^3.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
pigeon: ^10.1.6
pigeon: ^11.0.1
flutter:
plugin:

View File

@ -1,3 +1,37 @@
## 3.0.0
* Add `PeripheralManager` api.
* Add `CentralManager#readRSSI` method.
* Add `CentralManager.instance` api.
* Add `PeripheralManager.instance` api.
* Move `CentralController` to `CentralManager`.
* Move `CentralState` to `BluetoothLowEnergyState`.
* Move `CentralDiscoveredEventArgs` to `DiscoveredEventArgs`.
* Move `Advertisement` class to `AdvertiseData` class.
* Move `setUp` method from `BluetoothLowEnergy` class to `BluetoothLowEnergyManger` class.
* Change the type of `manufacturerSpecificData` from `Map<int, Uint8List>` to `ManufacturerSpecificData`.
* [Fix the issue that `UUID.fromString()` throw FormatException with 32 bits UUID string.](https://github.com/yanshouwang/bluetooth_low_energy/issues/13)
* Fix known issues.
## 3.0.0-dev.4
* Move `Advertisement` class to `AdvertiseData` class.
## 3.0.0-dev.3
* [Fix the issue that `UUID.fromString()` throw FormatException with 32 bits UUID string.](https://github.com/yanshouwang/bluetooth_low_energy/issues/13)
* Change the type of `manufacturerSpecificData` from `Map<int, Uint8List>` to `ManufacturerSpecificData`.
## 3.0.0-dev.2
* Move `setUp` method from `BluetoothLowEnergy` class to `BluetoothLowEnergyManger` class.
* Add `CentralManager.instance` api.
* Add `PeripheralManager.instance` api.
## 3.0.0-dev.1
* Implement new central manager api.
## 2.2.0
* Add `CentralController#getMaximumWriteLength` method.

View File

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

View File

@ -0,0 +1,12 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_central_manager.dart';
class MyBluetoothLowEnergy extends BluetoothLowEnergy {
@override
final MyCentralManager centralManager;
@override
PeripheralManager get peripheralManager => throw UnimplementedError();
MyBluetoothLowEnergy() : centralManager = MyCentralManager();
}

View File

@ -3,54 +3,87 @@ import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluez/bluez.dart';
extension MyBlueZAdapter on BlueZAdapter {
CentralState get state {
return powered ? CentralState.poweredOn : CentralState.poweredOff;
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 MyBlueZDevice on BlueZDevice {
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");
}
Advertisement get advertisement {
final name = this.name.isNotEmpty ? this.name : null;
final manufacturerSpecificData = manufacturerData.map((key, value) {
final id = key.id;
final data = Uint8List.fromList(value);
return MapEntry(id, data);
UUID get myUUID => uuid.toMyUUID();
List<MyGattService2> get myServices =>
gattServices.map((service) => MyGattService2(service)).toList();
AdvertiseData get myAdvertiseData {
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);
});
final serviceUUIDs = uuids.map((uuid) => uuid.toUUID()).toList();
final serviceData = this.serviceData.map((uuid, data) {
final key = uuid.toUUID();
final value = Uint8List.fromList(data);
return MapEntry(key, value);
});
return Advertisement(
name: name,
manufacturerSpecificData: manufacturerSpecificData,
serviceUUIDs: serviceUUIDs,
serviceData: serviceData,
return AdvertiseData(
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 MyBlueZUUID on BlueZUUID {
UUID toUUID() => UUID(value);
}
extension BlueZGattServiceX on BlueZGattService {
UUID get myUUID => uuid.toMyUUID();
extension MyBlueZGattCharacteristic on BlueZGattCharacteristic {
List<GattCharacteristicProperty> get properties => flags
.map((e) => e.toProperty())
.whereType<GattCharacteristicProperty>()
List<MyGattCharacteristic2> get myCharacteristics => characteristics
.map((characteristic) => MyGattCharacteristic2(characteristic))
.toList();
}
extension MyBlueZGattCharacteristicFlag on BlueZGattCharacteristicFlag {
GattCharacteristicProperty? toProperty() {
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) {
case BlueZGattCharacteristicFlag.read:
return GattCharacteristicProperty.read;
@ -68,8 +101,8 @@ extension MyBlueZGattCharacteristicFlag on BlueZGattCharacteristicFlag {
}
}
extension MyGattCharacteristicWriteType on GattCharacteristicWriteType {
BlueZGattCharacteristicWriteType toBlueZ() {
extension GattCharacteristicWriteTypeX on GattCharacteristicWriteType {
BlueZGattCharacteristicWriteType toBlueZWriteType() {
switch (this) {
case GattCharacteristicWriteType.withResponse:
return BlueZGattCharacteristicWriteType.request;

View File

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

View File

@ -0,0 +1,342 @@
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 {
MyCentralManager()
: _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 BluetoothLowEnergyError(
'$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 BluetoothLowEnergyError('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.myAdvertiseData;
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

@ -1,9 +1,8 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluez/bluez.dart';
import 'my_peripheral.dart';
class BlueZDeviceServicesResolvedEventArgs extends EventArgs {
final BlueZDevice device;
class MyPeripheralDiscoveredEventArgs extends EventArgs {
final MyPeripheral myPeripheral;
MyPeripheralDiscoveredEventArgs(this.myPeripheral);
BlueZDeviceServicesResolvedEventArgs(this.device);
}

View File

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

View File

@ -0,0 +1,16 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
class MyGattCharacteristic2 extends MyGattCharacteristic {
final BlueZGattCharacteristic characteristic;
MyGattCharacteristic2(this.characteristic)
: super(
hashCode: characteristic.hashCode,
uuid: characteristic.myUUID,
properties: characteristic.myProperties,
descriptors: characteristic.myDescriptors,
);
}

View File

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

View File

@ -0,0 +1,14 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
class MyGattDescriptor2 extends MyGattDescriptor {
final BlueZGattDescriptor descriptor;
MyGattDescriptor2(this.descriptor)
: super(
hashCode: descriptor.hashCode,
uuid: descriptor.myUUID,
);
}

View File

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

View File

@ -0,0 +1,15 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluez/bluez.dart';
import 'my_bluez.dart';
class MyGattService2 extends MyGattService {
final BlueZGattService service;
MyGattService2(this.service)
: super(
hashCode: service.hashCode,
uuid: service.myUUID,
characteristics: service.myCharacteristics,
);
}

View File

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

View File

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

View File

@ -1,6 +1,6 @@
name: bluetooth_low_energy_linux
description: Linux implementation of the bluetooth_low_energy plugin.
version: 2.2.0
version: 3.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: ^2.2.0
bluetooth_low_energy_platform_interface: ^3.0.0
bluez: ^0.8.1
dev_dependencies:

View File

@ -1,3 +1,45 @@
## 3.0.0
* Add `PeripheralManager` api.
* Add `CentralManager#readRSSI` method.
* Add `CentralManager.instance` api.
* Add `PeripheralManager.instance` api.
* Move `CentralController` to `CentralManager`.
* Move `CentralState` to `BluetoothLowEnergyState`.
* Move `CentralDiscoveredEventArgs` to `DiscoveredEventArgs`.
* Move `Advertisement` class to `AdvertiseData` class.
* Move `setUp` method from `BluetoothLowEnergy` class to `BluetoothLowEnergyManger` class.
* Change the type of `manufacturerSpecificData` from `Map<int, Uint8List>` to `ManufacturerSpecificData`.
* [Fix the issue that `UUID.fromString()` throw FormatException with 32 bits UUID string.](https://github.com/yanshouwang/bluetooth_low_energy/issues/13)
* Fix known issues.
## 3.0.0-dev.5
* Move `Advertisement` class to `AdvertiseData` class.
## 3.0.0-dev.4
* Fix issues.
## 3.0.0-dev.3
* [Fix the issue that `UUID.fromString()` throw FormatException with 32 bits UUID string.](https://github.com/yanshouwang/bluetooth_low_energy/issues/13)
* Change the type of `manufacturerSpecificData` from `Map<int, Uint8List>` to `ManufacturerSpecificData`.
## 3.0.0-dev.2
* Move `setUp` method from `BluetoothLowEnergy` class to `BluetoothLowEnergyManger` class.
* Add `CentralManager.instance` api.
* Add `PeripheralManager.instance` api.
## 3.0.0-dev.1
* Add `PeripheralManager` api.
* Add `CentralManager#readRSSI` method.
* Move `CentralController` to `CentralManager`.
* Move `CentralState` to `BluetoothLowEnergyState`.
* Move `CentralDiscoveredEventArgs` to `DiscoveredEventArgs`.
## 2.2.0
* Add `GattCharacteristicWriteType` argument to `CentralController#getMaximumWriteLength` method.

View File

@ -1,12 +1,24 @@
export 'src/errors.dart';
export 'src/event_args.dart';
export 'src/central_controller.dart';
export 'src/central_state.dart';
export 'src/bluetooth_low_energy.dart';
export 'src/bluetooth_low_energy_manager.dart';
export 'src/bluetooth_low_energy_state.dart';
export 'src/central_manager.dart';
export 'src/peripheral_manager.dart';
export 'src/bluetooth_low_energy_peer.dart';
export 'src/central.dart';
export 'src/peripheral.dart';
export 'src/uuid.dart';
export 'src/advertisement.dart';
export 'src/advertise_data.dart';
export 'src/manufacturer_specific_data.dart';
export 'src/gatt_service.dart';
export 'src/gatt_characteristic.dart';
export 'src/gatt_characteristic_property.dart';
export 'src/gatt_characteristic_write_type.dart';
export 'src/gatt_descriptor.dart';
export 'src/my_object.dart';
export 'src/my_peripheral.dart';
export 'src/my_gatt_service.dart';
export 'src/my_gatt_characteristic.dart';
export 'src/my_gatt_descriptor.dart';
export 'src/my_central.dart';

View File

@ -1,26 +1,27 @@
import 'dart:typed_data';
import 'manufacturer_specific_data.dart';
import 'uuid.dart';
/// The advertisement discovered from a peripheral.
class Advertisement {
/// The advertise data discovered from a peripheral.
class AdvertiseData {
/// The name of the peripheral.
final String? name;
/// The manufacturer specific data of the peripheral.
final Map<int, Uint8List> manufacturerSpecificData;
/// The GATT service uuids of the peripheral.
final List<UUID> serviceUUIDs;
/// The GATT service data of the peripheral.
final Map<UUID, Uint8List> serviceData;
/// Constructs an [Advertisement].
Advertisement({
/// The manufacturer specific data of the peripheral.
final ManufacturerSpecificData? manufacturerSpecificData;
/// Constructs an [AdvertiseData].
AdvertiseData({
this.name,
this.manufacturerSpecificData = const {},
this.serviceUUIDs = const [],
this.serviceData = const {},
this.manufacturerSpecificData,
});
}

View File

@ -0,0 +1,41 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'central_manager.dart';
import 'peripheral_manager.dart';
/// The bluetooth low energy interface.
///
/// Call `setUp` before use any api.
abstract class BluetoothLowEnergy extends PlatformInterface {
/// Constructs a [BluetoothLowEnergy].
BluetoothLowEnergy() : super(token: _token);
static final Object _token = Object();
static BluetoothLowEnergy? _instance;
/// The default instance of [BluetoothLowEnergy] to use.
static BluetoothLowEnergy get instance {
final instance = _instance;
if (instance == null) {
throw UnimplementedError(
'`BluetoothLowEnergy` is not implemented on this platform.',
);
}
return instance;
}
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [BluetoothLowEnergy] when
/// they register themselves.
static set instance(BluetoothLowEnergy instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
/// Gets the instance of central manager.
CentralManager get centralManager;
/// Gets the instance of peripheral manager.
PeripheralManager get peripheralManager;
}

View File

@ -0,0 +1,14 @@
import 'bluetooth_low_energy_state.dart';
import 'event_args.dart';
/// The abstract base class that manages central and peripheral objects.
abstract class BluetoothLowEnergyManager {
/// The current state of the manager.
BluetoothLowEnergyState get state;
/// Tells the managers state updated.
Stream<BluetoothLowEnergyStateChangedEventArgs> get stateChanged;
/// Sets up this bluetooth low energy manager.
Future<void> setUp();
}

View File

@ -0,0 +1,7 @@
import 'uuid.dart';
/// An object that represents a remote device.
abstract class BluetoothLowEnergyPeer {
/// The UUID associated with the peer.
UUID get uuid;
}

View File

@ -0,0 +1,17 @@
/// The state of the bluetooth low energy.
enum BluetoothLowEnergyState {
/// The bluetooth low energy is unknown.
unknown,
/// The bluetooth low energy is unsupported.
unsupported,
/// The bluetooth low energy is unauthorized.
unauthorized,
/// The bluetooth low energy is powered off.
poweredOff,
/// The bluetooth low energy is powered on.
poweredOn,
}

View File

@ -0,0 +1,4 @@
import 'bluetooth_low_energy_peer.dart';
/// A remote device connected to a local app, which is acting as a peripheral.
abstract class Central extends BluetoothLowEnergyPeer {}

View File

@ -1,120 +0,0 @@
import 'dart:typed_data';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'central_state.dart';
import 'event_args.dart';
import 'gatt_characteristic.dart';
import 'gatt_characteristic_write_type.dart';
import 'gatt_descriptor.dart';
import 'gatt_service.dart';
import 'peripheral.dart';
/// The central controller used to communicate with peripherals.
/// Call `setUp` before use any api, and call `tearDown` when it is no longer needed.
abstract class CentralController extends PlatformInterface {
/// Constructs a [CentralController].
CentralController() : super(token: _token);
static final Object _token = Object();
static CentralController? _instance;
/// The default instance of [CentralController] to use.
static CentralController get instance {
final instance = _instance;
if (instance == null) {
const message =
'`BluetoothLowEnergy` is not implemented on this platform.';
throw UnimplementedError(message);
}
return instance;
}
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [CentralController] when
/// they register themselves.
static set instance(CentralController instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
/// Gets the state of the central.
CentralState get state;
/// Used to listen the central state changed event.
Stream<CentralStateChangedEventArgs> get stateChanged;
/// Used to listen the central discovered event.
Stream<CentralDiscoveredEventArgs> get discovered;
/// Used to listen peripherals state changed event.
Stream<PeripheralStateChangedEventArgs> get peripheralStateChanged;
/// Used to listen GATT characteristics value changed event.
Stream<GattCharacteristicValueChangedEventArgs>
get characteristicValueChanged;
/// Sets up the central controller.
Future<void> setUp();
/// Tears down the central controller.
Future<void> tearDown();
/// Starts to discover peripherals.
Future<void> startDiscovery();
/// Stops to discover peripherals.
Future<void> stopDiscovery();
/// Connects to the peripheral.
Future<void> connect(Peripheral peripheral);
/// Disconnects form the peripheral.
Future<void> disconnect(Peripheral peripheral);
/// Gets the max length in bytes for a single write type of the peripheral.
Future<int> getMaximumWriteLength(
Peripheral peripheral, {
required GattCharacteristicWriteType type,
});
/// Discovers GATT of the peripheral.
Future<void> discoverGATT(Peripheral peripheral);
/// Gets GATT services of the peripheral.
Future<List<GattService>> getServices(Peripheral peripheral);
/// Gets GATT characteristics of the GATT service.
Future<List<GattCharacteristic>> getCharacteristics(GattService service);
/// Gets GATT descriptors of the GATT characteristic.
Future<List<GattDescriptor>> getDescriptors(
GattCharacteristic characteristic,
);
/// Reads value of the GATT characteristic.
Future<Uint8List> readCharacteristic(GattCharacteristic characteristic);
/// Writes value of the GATT characteristic.
Future<void> writeCharacteristic(
GattCharacteristic characteristic, {
required Uint8List value,
required GattCharacteristicWriteType type,
});
/// Notifies value of the GATT characteristic.
Future<void> notifyCharacteristic(
GattCharacteristic characteristic, {
required bool state,
});
/// Reads value of the GATT descriptor.
Future<Uint8List> readDescriptor(GattDescriptor descriptor);
/// Writes value of the GATT descriptor.
Future<void> writeDescriptor(
GattDescriptor descriptor, {
required Uint8List value,
});
}

View File

@ -0,0 +1,76 @@
import 'dart:typed_data';
import 'bluetooth_low_energy.dart';
import 'bluetooth_low_energy_manager.dart';
import 'event_args.dart';
import 'gatt_characteristic.dart';
import 'gatt_characteristic_write_type.dart';
import 'gatt_descriptor.dart';
import 'gatt_service.dart';
import 'peripheral.dart';
/// An object that scans for, discovers, connects to, and manages peripherals.
abstract class CentralManager extends BluetoothLowEnergyManager {
/// Gets the instance of [CentralManager].
static CentralManager get instance =>
BluetoothLowEnergy.instance.centralManager;
/// Tells the central manager discovered a peripheral while scanning for devices.
Stream<DiscoveredEventArgs> get discovered;
/// Tells that retrieving the specified peripheral's state changed.
Stream<PeripheralStateChangedEventArgs> get peripheralStateChanged;
/// Tells that retrieving the specified characteristics value changed.
Stream<GattCharacteristicValueChangedEventArgs>
get characteristicValueChanged;
/// Scans for peripherals that are advertising services.
Future<void> startDiscovery();
/// Asks the central manager to stop scanning for peripherals.
Future<void> stopDiscovery();
/// Establishes a local connection to a peripheral.
Future<void> connect(Peripheral peripheral);
/// Cancels an active or pending local connection to a peripheral.
Future<void> disconnect(Peripheral peripheral);
/// Gets the maximum amount of data, in bytes, you can send to a characteristic in a single write type.
Future<int> getMaximumWriteLength(
Peripheral peripheral, {
required GattCharacteristicWriteType type,
});
/// Retrieves the current RSSI value for the peripheral while connected to the central manager.
Future<int> readRSSI(Peripheral peripheral);
/// Discovers the GATT services, characteristics and descriptors of the peripheral.
Future<List<GattService>> discoverGATT(Peripheral peripheral);
/// Retrieves the value of a specified characteristic.
Future<Uint8List> readCharacteristic(GattCharacteristic characteristic);
/// Writes the value of a characteristic.
Future<void> writeCharacteristic(
GattCharacteristic characteristic, {
required Uint8List value,
required GattCharacteristicWriteType type,
});
/// Sets notifications or indications for the value of a specified characteristic.
Future<void> notifyCharacteristic(
GattCharacteristic characteristic, {
required bool state,
});
/// Retrieves the value of a specified characteristic descriptor.
Future<Uint8List> readDescriptor(GattDescriptor descriptor);
/// Writes the value of a characteristic descriptor.
Future<void> writeDescriptor(
GattDescriptor descriptor, {
required Uint8List value,
});
}

View File

@ -1,17 +0,0 @@
/// The state of the central.
enum CentralState {
/// The central is unknown.
unknown,
/// The central is unsupported.
unsupported,
/// The central is unauthorized.
unauthorized,
/// The central is powered off.
poweredOff,
/// The central is powered on.
poweredOn,
}

View File

@ -1,35 +1,36 @@
import 'dart:typed_data';
import 'advertisement.dart';
import 'central_state.dart';
import 'advertise_data.dart';
import 'bluetooth_low_energy_state.dart';
import 'central.dart';
import 'gatt_characteristic.dart';
import 'peripheral.dart';
/// The base event arguments.
abstract class EventArgs {}
/// The central state changed event arguments.
class CentralStateChangedEventArgs extends EventArgs {
/// The new state of the central.
final CentralState state;
/// The bluetooth low energy state changed event arguments.
class BluetoothLowEnergyStateChangedEventArgs extends EventArgs {
/// The new state of the bluetooth low energy.
final BluetoothLowEnergyState state;
/// Constructs a [CentralStateChangedEventArgs].
CentralStateChangedEventArgs(this.state);
/// Constructs a [BluetoothLowEnergyStateChangedEventArgs].
BluetoothLowEnergyStateChangedEventArgs(this.state);
}
/// The central discovered event arguments.
class CentralDiscoveredEventArgs extends EventArgs {
/// The discovered event arguments.
class DiscoveredEventArgs extends EventArgs {
/// The disvered peripheral.
final Peripheral peripheral;
/// The rssi of the peripheral.
final int rssi;
/// The advertisement of the peripheral.
final Advertisement advertisement;
/// The advertise data of the peripheral.
final AdvertiseData advertiseData;
/// Constructs a [CentralDiscoveredEventArgs].
CentralDiscoveredEventArgs(this.peripheral, this.rssi, this.advertisement);
/// Constructs a [DiscoveredEventArgs].
DiscoveredEventArgs(this.peripheral, this.rssi, this.advertiseData);
}
/// The peripheral state changed event arguments.
@ -55,3 +56,45 @@ class GattCharacteristicValueChangedEventArgs extends EventArgs {
/// Constructs a [GattCharacteristicValueChangedEventArgs].
GattCharacteristicValueChangedEventArgs(this.characteristic, this.value);
}
class ReadGattCharacteristicCommandEventArgs {
final Central central;
final GattCharacteristic characteristic;
final int id;
final int offset;
ReadGattCharacteristicCommandEventArgs(
this.central,
this.characteristic,
this.id,
this.offset,
);
}
class WriteGattCharacteristicCommandEventArgs {
final Central central;
final GattCharacteristic characteristic;
final int id;
final int offset;
final Uint8List value;
WriteGattCharacteristicCommandEventArgs(
this.central,
this.characteristic,
this.id,
this.offset,
this.value,
);
}
class NotifyGattCharacteristicCommandEventArgs {
final Central central;
final GattCharacteristic characteristic;
final bool state;
NotifyGattCharacteristicCommandEventArgs(
this.central,
this.characteristic,
this.state,
);
}

View File

@ -0,0 +1,7 @@
import 'uuid.dart';
/// A representation of common aspects of services offered by a peripheral.
abstract class GattAttribute {
/// The Bluetooth-specific UUID of the attribute.
UUID get uuid;
}

View File

@ -1,17 +1,27 @@
import 'gatt_attribute.dart';
import 'gatt_characteristic_property.dart';
import 'gatt_descriptor.dart';
import 'my_gatt_characteristic.dart';
import 'my_gatt_descriptor.dart';
import 'uuid.dart';
/// The GATT characteristic.
class GattCharacteristic {
/// The [UUID] of this GATT characteristic.
final UUID uuid;
/// A characteristic of a remote peripherals service.
abstract class GattCharacteristic extends GattAttribute {
/// The properties of the characteristic.
List<GattCharacteristicProperty> get properties;
/// The properties of this GATT characteristic.
final List<GattCharacteristicProperty> properties;
/// A list of the descriptors discovered in this characteristic.
List<GattDescriptor> get descriptors;
/// Constructs a [GattCharacteristic].
GattCharacteristic({
required this.uuid,
required this.properties,
});
factory GattCharacteristic({
required UUID uuid,
required List<GattCharacteristicProperty> properties,
required List<GattDescriptor> descriptors,
}) =>
MyGattCharacteristic(
uuid: uuid,
properties: properties,
descriptors: descriptors.cast<MyGattDescriptor>(),
);
}

View File

@ -1,12 +1,18 @@
import 'dart:typed_data';
import 'gatt_attribute.dart';
import 'my_gatt_descriptor.dart';
import 'uuid.dart';
/// The GATT characteristic.
class GattDescriptor {
/// The [UUID] of this GATT descriptor.
final UUID uuid;
/// An object that provides further information about a remote peripherals characteristic.
abstract class GattDescriptor extends GattAttribute {
/// Constructs a [GattDescriptor].
GattDescriptor({
required this.uuid,
});
factory GattDescriptor({
required UUID uuid,
required Uint8List value,
}) =>
MyGattDescriptor(
uuid: uuid,
value: value,
);
}

View File

@ -1,12 +1,21 @@
import 'gatt_attribute.dart';
import 'gatt_characteristic.dart';
import 'my_gatt_characteristic.dart';
import 'my_gatt_service.dart';
import 'uuid.dart';
/// The GATT service.
class GattService {
/// The [UUID] of this GATT service.
final UUID uuid;
/// A collection of data and associated behaviors that accomplish a function or feature of a device.
abstract class GattService extends GattAttribute {
/// A list of characteristics discovered in this service.
List<GattCharacteristic> get characteristics;
/// Constructs a [GattService].
GattService({
required this.uuid,
});
factory GattService({
required UUID uuid,
required List<GattCharacteristic> characteristics,
}) =>
MyGattService(
uuid: uuid,
characteristics: characteristics.cast<MyGattCharacteristic>(),
);
}

View File

@ -0,0 +1,16 @@
import 'dart:typed_data';
/// The manufacturer specific data of the peripheral
class ManufacturerSpecificData {
/// The manufacturer id.
final int id;
/// The manufacturer data.
final Uint8List data;
/// Constructs an [ManufacturerSpecificData].
ManufacturerSpecificData({
required this.id,
required this.data,
});
}

View File

@ -0,0 +1,13 @@
import 'central.dart';
import 'my_object.dart';
import 'uuid.dart';
class MyCentral extends MyObject implements Central {
@override
final UUID uuid;
MyCentral({
required super.hashCode,
required this.uuid,
});
}

Some files were not shown because too many files have changed in this diff Show More