* 调整接口

* 临时提交

* 重构 Android 平台代码

* 临时提交

* 临时提交

* Android 6.0.0-dev.0

* 临时提交

* 实现 Windows 接口

* windows-6.0.0-dev.0

* Darwin 6.0.0-dev.0

* 临时提交

* 1

* 临时提交

* 调整接口

* windows-6.0.0-dev.1

* 临时提交

* interface-6.0.0-dev.7

* interface-6.0.0-dev.8

* 临时提交

* windows-6.0.0-dev.2

* 删除多余脚本

* interface-6.0.0-dev.9

* 临时提交

* 临时提交

* interface-6.0.0-dev.10

* android-6.0.0-dev.1

* windows-6.0.0-dev.3

* 临时提交

* interface-6.0.0-dev.11

* windows-6.0.0-dev.4

* 更新 pubspec.lock

* 1

* interface-6.0.0-dev.12

* interface-6.0.0-dev.13

* interface-6.0.0-dev.14

* 临时提交

* interface-6.0.0-dev.15

* 临时提交

* interface-6.0.0-dev.16

* android-6.0.0-dev.2

* 临时提交

* windows-6.0.0-dev.5

* 临时提交

* 临时提交

* windows-6.0.0-dev.6

* 优化注释和代码样式

* 优化代码

* 临时提交

* 实现 Dart 接口

* darwin-6.0.0-dev.0

* linux-6.0.0-dev.0

* 修复已知问题

* 修复问题

* 6.0.0-dev.0

* 修改包名

* 更新版本

* 移除原生部分

* 临时提交

* 修复问题

* 更新 pigeon 19.0.0

* 更新 README,添加迁移文档

* linux-6.0.0-dev.1

* 解析扫描回复和扩展广播

* 修复 googletest 版本警告问题

* Use centralArgs instead of addressArgs

* interface-6.0.0-dev.18

* android-6.0.0-dev.4

* linux-6.0.0-dev.2

* windows-6.0.0-dev.8

* darwin-6.0.0-dev.2

* 6.0.0-dev.1

* Update LICENSE

* clang-format

* Combine ADV_IND and SCAN_RES

* TEMP commit: update exampe

* Adjust advertisement combine logic

* Implement `MyPeripheralMananger` on Windows

* Added NuGet auto download and scan for names on peripheral (#67)

* fetch nuget using other technique

* move FetchContent to right location in CMakeLists.txt

* also added hash for googletest

---------

Co-authored-by: Kevin De Keyser <kevin@dekeyser.ch>

* Fix errors.

* Check BluetoothAdapter role supported state and implement PeripheralManager on Flutter side.

* Sort code

* Fix known errors

* interface-6.0.0-dev.19

* windows-6.0.0-dev.9

* Optimize example

* android-6.0.0-dev.5

* Optimize the Adverrtisement BottomSheet.

* linux-6.0.0-dev.3

* Update dependency

* Fix example errors.

* Temp commit.

* darwin-6.0.0-dev.3

* 6.0.0-dev.2

* Update README.md

* 6.0.0

* darwin-6.0.0-dev.4

* android-6.0.0-dev.6

* 6.0.0-dev.3

* Update docs.

* interface-6.0.0

* android-6.0.0

* darwin-6.0.0

* linux-6.0.0

* windows-6.0.0

* 6.0.0

* Update dependency

---------

Co-authored-by: Kevin De Keyser <dekeyser.kevin97@gmail.com>
Co-authored-by: Kevin De Keyser <kevin@dekeyser.ch>
This commit is contained in:
渐渐被你吸引
2024-06-04 00:44:39 +08:00
committed by GitHub
parent 71de531ceb
commit 108b6a804f
380 changed files with 23782 additions and 14127 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
.vscode/
.DS_Store

21
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,21 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.22621.0",
"compilerPath": "cl.exe",
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "windows-msvc-arm64"
}
],
"version": 4
}

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 yanshouwang
Copyright (c) 2024 hebei.dev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -26,5 +26,4 @@ migrate_working_dir/
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

View File

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: "efbf63d9c66b9f6ec30e9ad4611189aa80003d31"
revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1"
channel: "stable"
project_type: plugin
@ -13,8 +13,8 @@ project_type: plugin
migration:
platforms:
- platform: root
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
# User provided section

View File

@ -1,3 +1,113 @@
## 6.0.0
* [Add `CentralManager#retrieveConnectedPeripherals` method.](https://github.com/yanshouwang/bluetooth_low_energy/issues/61)
* [Add optional `serviceUUIDs` argument to the `CentralManager#startDiscovery` method.](https://github.com/yanshouwang/bluetooth_low_energy/issues/53)
* [Add `CentralManager#mtuChanged` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/57).
* [Add `PeripheralManager#mtuChanged` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/57).
* Add `BluetoothLowEnergyManager#authorize` method.
* Add `BluetoothLowEnergyManager#showAppSettings` method.
* [Add `CentralManager#requestMTU` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/57).
* [Add `CentralManager#getMaximumWriteLength` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/57).
* Add `PeripheralManager#connectionStateChanged` event.
* [Add `PeripheralManager#characteristicReadRequested` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#characteristicWriteRequested` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#descriptorReadRequested` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#descriptorWriteRequested` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#getMaximumNotifyLength` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/57).
* [Add `PeripheralManager#respondReadRequestWithValue` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#respondReadRequestWithError` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#respondWriteRequest` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#respondWriteRequestWithError` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* Add `ConnectionState` enum.
* Add `GATTService.isPrimary` field.
* Add `GATTService#includedServices` field.
* Add `MutableGATTCharacteristic#permissions` field.
* Add `MutableGATTDescriptor#permissions` field.
* Add `int` type to `UUID#fromAddress`.
* Move `CentralManger.instance` to factory constructor.
* Move `PeripheralManager.instance` to factory constructor.
* Move the type of `Advertisement#manufacturerSpecificData` to `List<ManufacturerSpecificData>`.
* Move `BluetoothLowEnergyManager#getState` to `BluetoothLowEnergyManager#state`.
* Move `PeripheralManager#clearServices` to `PeripheralManager#removeAllServices`.
* Remove `BluetoothLowEnergyManager#setUp` method.
* Remove `PeripheralManager#characteristicRead` event.
* Remove `PeripheralManager#characteristicWritten` event.
* Remove `PeripheralManager#readCharacteristic` method.
* Remove `PeripheralManager#writeCharacteristic` method.
* Fix the issue that [`Cannot access value of empty optional`](https://github.com/yanshouwang/bluetooth_low_energy/issues/63).
* Fix known issues.
* Rewrite example with MVVM.
## 6.0.0-dev.3
* Implement `CentralMananger#showAppSettings` on iOS.
* Implement `PeripheralManager#showAppSettings` on iOS.
* Fix known issues.
## 6.0.0-dev.2
* Add `int` type to `UUID#fromAddress`.
* Move the type of `Advertisement#manufacturerSpecificData` to `List<ManufacturerSpecificData>`.
* Rewrite example with MVVM.
* Fix known issues.
## 6.0.0-dev.1
* Add `PeripheralManager#respondReadRequestWithValue`.
* Add `PeripheralManager#respondReadRequestWithError`.
* Add `PeripheralManager#respondWriteRequest`.
* Add `PeripheralManager#respondWriteRequestWithError`.
* Remove `PeripheralManager#respondCharacteristicReadRequestWithValue`.
* Remove `PeripheralManager#respondCharacteristicReadRequestWithError`.
* Remove `PeripheralManager#respondCharacteristicWriteRequest`.
* Remove `PeripheralManager#respondCharacteristicWriteRequestWithError`.
* Remove `PeripheralManager#respondDescriptorReadRequestWithValue`.
* Remove `PeripheralManager#respondDescriptorReadRequestWithError`.
* Remove `PeripheralManager#respondDescriptorWriteRequest`.
* Remove `PeripheralManager#respondDescriptorWriteRequestWithError`.
## 6.0.0-dev.0
* [Add `CentralManager#retrieveConnectedPeripherals` method.](https://github.com/yanshouwang/bluetooth_low_energy/issues/61)
* [Add optional `serviceUUIDs` argument to the `CentralManager#startDiscovery` method.](https://github.com/yanshouwang/bluetooth_low_energy/issues/53)
* [Add `CentralManager#mtuChanged` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/57).
* [Add `PeripheralManager#mtuChanged` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/57).
* Add `BluetoothLowEnergyManager#authorize` method.
* Add `BluetoothLowEnergyManager#showAppSettings` method.
* [Add `CentralManager#requestMTU` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/57).
* [Add `CentralManager#getMaximumWriteLength` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/57).
* Add `PeripheralManager#connectionStateChanged` event.
* [Add `PeripheralManager#characteristicReadRequested` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#characteristicWriteRequested` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#descriptorReadRequested` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#descriptorWriteRequested` event](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#getMaximumNotifyLength` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/57).
* [Add `PeripheralManager#respondCharacteristicReadRequestWithValue` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#respondCharacteristicReadRequestWithError` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#respondCharacteristicWriteRequest` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#respondCharacteristicWriteRequestWithError` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#respondDescriptorReadRequestWithValue` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#respondDescriptorReadRequestWithError` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#respondDescriptorWriteRequest` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* [Add `PeripheralManager#respondDescriptorWriteRequestWithError` method](https://github.com/yanshouwang/bluetooth_low_energy/issues/45).
* Add `ConnectionState` enum.
* Add `GATTService.isPrimary` field.
* Add `GATTService#includedServices` field.
* Add `MutableGATTCharacteristic#permissions` field.
* Add `MutableGATTDescriptor#permissions` field.
* Move `BluetoothLowEnergyManager#getState` to `BluetoothLowEnergyManager#state`.
* Move `PeripheralManager#clearServices` to `PeripheralManager#removeAllServices`.
* Move `CentralManger.instance` to factory constructor.
* Move `PeripheralManager.instance` to factory constructor.
* Remove `BluetoothLowEnergyManager#setUp` method.
* Remove `GATTCharacteristicReadEventArgs` class.
* Remove `GATTCharacteristicWrittenEventArgs` class.
* Remove `PeripheralManager#characteristicRead` event.
* Remove `PeripheralManager#characteristicWritten` event.
* Remove `PeripheralManager#readCharacteristic` method.
* Remove `PeripheralManager#writeCharacteristic` method.
* Fix the issue that [`Cannot access value of empty optional`](https://github.com/yanshouwang/bluetooth_low_energy/issues/63).
## 5.0.7
* `Android` Fix the issue that [Advertisement resolve failed with `NullPointerException`](https://github.com/yanshouwang/bluetooth_low_energy/issues/59)

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 yanshouwang
Copyright (c) 2024 hebei.dev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,61 +1,103 @@
# bluetooth_low_energy
A Flutter plugin for controlling the bluetooth low energy.
A Flutter plugin for controlling the bluetooth low energy, supports central and peripheral roles.
## Features
## CentralManager
### CentralManager
|API|Android|iOS|macOS|Windows|Linux|
|:-|:-:|:-:|:-:|:-:|:-:|
|logLevel|✅|✅|✅|✅|✅|
|state|✅|✅|✅|✅|✅|
|stateChanged|✅|✅|✅|✅|✅|
|authorize|✅|||||
|showAppSettings|✅|✅||||
|discovered|✅|✅|✅|✅|✅|
|connectionStateChanged|✅|✅|✅|✅|✅|
|mtuChanged|✅|||✅||
|characteristicNotified|✅|✅|✅|✅|✅|
|startDiscovery|✅|✅|✅|✅|✅|
|stopDiscovery|✅|✅|✅|✅|✅|
|retrieveConnectedPeripherals|✅|✅|✅||✅|
|connect|✅|✅|✅|✅|✅|
|disconnect|✅|✅|✅|✅|✅|
|requestMTU|✅|||||
|getMaximumWriteLength|✅|✅|✅|✅|✅|
|readRSSI|✅|✅|✅||✅|
|readCharacteristic|✅|✅|✅|✅|✅|
|writeCharacteristic|✅|✅|✅|✅|✅|
|setCharacteristicNotifyState|✅|✅|✅|✅|✅|
|readDescriptor|✅|✅|✅|✅|✅|
|writeDescriptor|✅|✅|✅|✅|✅|
- [x] Get/Listen the state of the central manager.
- [x] Listen connection state cahgned.
- [x] Listen GATT characteristic notified.
- [x] Start/Stop discovery.
- [x] Connect/Disconnect peripherals.
- [x] Read RSSI of peripherals.
- [x] Discover GATT.
- [x] Read/Write GATT characteristics.
- [x] Set GATT characteristics notify state.
- [x] Read/Write GATT descriptors.
## PeripheralManager
### PeripheralManager
- [x] Get/Listen the state of the peripheral manager.
- [x] Listen GATT characteristic read/written/notifyStateChanged.
- [x] Add/Remove/Clear service(s).
- [x] Start/Stop advertising.
- [x] Read/Write(Notify) GATT characteristics.
|API|Android|iOS|macOS|Windows|Linux|
|:-|:-:|:-:|:-:|:-:|:-:|
|logLevel|✅|✅|✅|✅||
|state|✅|✅|✅|✅||
|stateChanged|✅|✅|✅|✅||
|authorize|✅|||||
|showAppSettings|✅|✅||||
|connectionStateChanged|✅|||||
|mtuChanged|✅|||✅||
|characteristicReadRequested|✅|✅|✅|✅||
|characteristicWriteRequested|✅|✅|✅|✅||
|characteristicNotifyStateChanged|✅|✅|✅|✅||
|descriptorReadRequested|✅|||✅||
|descriptorWriteRequested|✅|||✅||
|addService|✅|✅|✅|✅||
|removeService|✅|✅|✅|✅||
|removeAllServices|✅|✅|✅|✅||
|startAdvertising|✅|✅|✅|✅||
|stopAdvertising|✅|✅|✅|✅||
|getMaximumNotifyLength|✅|✅|✅|✅||
|respondReadRequestWithValue|✅|✅|✅|✅||
|respondReadRequestWithError|✅|✅|✅|✅||
|respondWriteRequest|✅|✅|✅|✅||
|respondWriteRequestWithError|✅|✅|✅|✅||
|notifyCharacteristic|✅|✅|✅|✅||
## Getting Started
Add `bluetooth_low_energy` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/).
Add `bluetooth_low_energy` as a [dependency][1] in your pubspec.yaml file.
``` Yaml
``` YAML
dependencies:
bluetooth_low_energy: ^<latest-version>
```
Remember to call `await CentralManager.setUp()` and `await PeripheralManager.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.
### Android
Make sure you have a `miniSdkVersion` with 21 or higher in your `android/app/build.gradle` file.
Make sure you have a `minSdk` with 21 or higher in your `android/app/build.gradle` file.
### 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.
According to the [Apple's documents][2], you must include the [`NSBluetoothAlwaysUsageDescription`][3] on or after iOS 13, and include the [`NSBluetoothPeripheralUsageDescription`][4] key before iOS 13.
*Note:* The `PeripheralManager#startAdvertising` only support `name` and `serviceUUIDs`, see [the startAdvertising document](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/1393252-startadvertising).
When use bluetooth or other hardwares on macOS, developers need to [configure the app sandbox][5].
*Note:* When use bluetooth or other hardwares on macOS, developers need to [configure the app sandbox](https://developer.apple.com/documentation/xcode/configuring-the-macos-app-sandbox#Enable-access-to-restricted-resources).
*Note:* The `PeripheralManager#startAdvertising` only support `name` and `serviceUUIDs`, see the [Apple's document][6].
### Winodows
*Note:* The `PeripheralManager#startAdvertising` not support `name`, see the [Microsoft's document][7].
### Linux
PeripheralManager is not implemented 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).
The `PeripheralManager` API is not implemented since the [`bluez`][8] didn't support this feature yet.
### Windows
## Migrations
PeripheralManager is not implemented, it will be implemented in the future.
* [Migrate from 5.x to 6.x][9]
*Note:* The `CentralManager#readRSSI` method is not implemented on windows(windows doesn't support read RSSI after connected), avoid call this when running on windows devices.
[1]: https://docs.flutter.dev/packages-and-plugins/using-packages
[2]: https://developer.apple.com/documentation/corebluetooth
[3]: https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothalwaysusagedescription
[4]: https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothperipheralusagedescription
[5]: https://developer.apple.com/documentation/xcode/configuring-the-macos-app-sandbox#Enable-access-to-restricted-resources
[6]: https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/1393252-startadvertising
[7]: https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.advertisement.bluetoothleadvertisementpublisher.advertisement?view=winrt-22621
[8]: https://github.com/canonical/bluez.dart
[9]: doc/migrations/migration-v6.md

View File

@ -0,0 +1,279 @@
# Migrate form 5.x to 6.x
## Capitalization Rules
* `GattCharacteristicNotifiedEventArgs` -> `GATTCharacteristicNotifiedEventArgs`
* `GattAttribute` -> `GATTAttribute`
* `GattDescriptor` -> `GATTDescriptor`
* `GattCharacteristic` -> `GATTCharacteristic `
* `GattCharacteristicProperty` -> `GATTCharacteristicProperty`
* `GattCharacteristicWriteType` -> `GATTCharacteristicWriteType`
* `GattService` -> `GATTService`
* `GattCharacteristicNotifyStateChangedEventArgs` -> `GATTCharacteristicNotifyStateChangedEventArgs`
## CentralManager
1. Use the factory method to get the instance.
``` Dart
// 5.x
// final centralManager = CentralManager.instance;
// 6.x
final centralManager = CentralManager();
```
2. The `setUp` method is removed.
``` Dart
// 5.x
// await centralManager.setUp();
```
3. Authorize manually on Android.
``` Dart
// The authorize method is no longer called automatically on Android
// after 6.x, listen the stateChanged stream and call the authorize
// method when the state is unauthorized.
final stateChangedSubscription = centralManager.stateChanged.listen((eventArgs) async {
final state = eventArgs.state;
if (Platform.isAndroid && state == BluetoothLowEnergyState.unauthorized) {
await centralManager.authorize();
}
this.state.value = state;
});
```
4. The type of `manufacturerSpecificData` in `Advertisement` changed from `ManufacturerSpecificData` to `List<ManufacturerSpecificData>`.
``` Dart
// 5.x
final manufacturerSpecificData = advertisement.manufacturerSpecificData; // ManufacturerSpecificData
// 6.0
final manufacturerSpecificData = advertisement.manufacturerSpecificData; // List<ManufacturerSpecificData>
```
5. Request MTU manually on Android.
``` Dart
// The mtu is no longer requested automatically on Android
// after 6.x, call the requestMTU method after connected.
await centralManager.connect(peripheral);
services.value = await centralManager.discoverGATT(peripheral);
if (Platform.isAndroid) {
await centralManager.requestMTU(
peripheral,
mtu: 517,
);
}
```
6. Fragment the value manually when write characteristics.
``` Dart
// The value is no longer fragmented automatically after
// 6.x, fragment the value with the maximum write length.
final fragmentSize = await centralManager.getMaximumWriteLength(
peripheral,
type: type,
);
var start = 0;
while (start < value.length) {
final end = start + fragmentSize;
final fragmentedValue = end < value.length
? value.sublist(start, end)
: value.sublist(start);
await centralManager.writeCharacteristic(
peripheral,
characteristic,
value: fragmentedValue,
type: type,
);
start = end;
}
```
## PeripheralManager
1. Use the factory method to get the instance.
``` Dart
// 5.x
// final peripheralManager = PeripheralManager.instance;
// 6.x
final peripheralManager = PeripheralManager();
```
2. The `setUp` method is removed.
``` Dart
// 5.x
// await peripheralManager.setUp();
```
3. Authorize manually on Android.
``` Dart
// The authorize method is no longer called automatically on Android
// after 6.x, listen the stateChanged stream and call the authorize
// method when the state is unauthorized.
final stateChangedSubscription = peripheralManager.stateChanged.listen((eventArgs) async {
final state = eventArgs.state;
if (Platform.isAndroid && state == BluetoothLowEnergyState.unauthorized) {
await peripheralManager.authorize();
}
this.state.value = state;
});
```
4. Use the factory methods to create GATTService and GATTDescriptor.
``` Dart
// 5.x
// final service = GattService(
// uuid: serviceUUID,
// characteristics: [
// GattCharacteristic(
// uuid: characteristicUUID,
// properties: [
// GattCharacteristicProperty.read,
// GattCharacteristicProperty.write,
// GattCharacteristicProperty.writeWithoutResponse,
// GattCharacteristicProperty.notify,
// GattCharacteristicProperty.indicate,
// ],
// value: characteristicValue,
// descriptors: [
// GattDescriptor(
// uuid: descriptorUUID,
// value: descriptorValue,
// ),
// ],
// ),
// ],
// );
// 6.x
final service = GATTService(
uuid: serviceUUID,
characteristics: [
GATTCharacteristic.immutable(
uuid: immutableCharacteristicUUID,
value: immutableCharacteristicValue,
descriptors: [
GATTDescriptor.immutable(
uuid: immutableDescriptorUUID,
value: immutableDescriptorValue,
),
]
),
GATTCharacteristic.mutable(
uuid: mutableCharacteristicUUID,
properties: [
GATTCharacteristicProperty.read,
GATTCharacteristicProperty.write,
GATTCharacteristicProperty.writeWithoutResponse,
GATTCharacteristicProperty.notify,
GATTCharacteristicProperty.indicate,
],
permissions: [
GATTCharacteristicPermission.read,
GATTCharacteristicPermission.write,
],
descriptors: [
// This is not supported on iOS and macOS.
GATTDescriptor.mutable(
uuid: mutableDescriptorUUID,
permissions: [
GATTCharacteristicPermission.read,
GATTCharacteristicPermission.write,
],
),
]
),
],
);
```
5. The type of `manufacturerSpecificData` in `Advertisement` changed from `ManufacturerSpecificData` to `List<ManufacturerSpecificData>`.
``` Dart
// 5.x
final advertisement = Advertisement(
manufacturerSpecificData: ManufacturerSpecificData(
id: 0x2e19,
data: Uint8List.fromList([0x01, 0x02, 0x03]),
),
);
// 6.x
final advertisement = Advertisement(
manufacturerSpecificData: [
ManufacturerSpecificData(
id: 0x2e19,
data: Uint8List.fromList([0x01, 0x02, 0x03]),
),
],
);
```
6. Respond read and write requests manually.
``` Dart
// The read and write requests are no longer responded automatically
// after 6.x, listen the characteristicReadRequested,
// characteristicWriteRequested, descriptorReadRequested and
// descriptorWriteRequested, then respond the requests.
// 5.x
// final characteristicReadSubscription = PeripheralManager.instance.characteristicRead.listen((eventArgs) async {
// });
// final characteristicWrittenSubscription = PeripheralManager.instance.characteristicWritten.listen((eventArgs) // async {
// });
// 6.x
// These streams are only available when the characteristic is mutable.
final characteristicReadRequestedSubscription = peripheralManager.characteristicReadRequested.listen((eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final request = eventArgs.request;
final offset = request.offset;
final trimmedValue = value.sublist(offset);
await peripheralManager.respondReadRequestWithValue(
request,
value: trimmedValue,
);
});
final characteristicWriteRequestedSubscription = peripheralManager.characteristicWriteRequested.listen((eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final request = eventArgs.request;
final offset = request.offset;
final value = request.value;
await peripheralManager.respondWriteRequest(request);
});
```
7. Fragment the value manually when notify characteristics.
``` Dart
// The value is no longer fragmented automatically after
// 6.x, fragment the value with the maximum notify length.
final fragmentSize = await peripheralManager.getMaximumNotifyLength(
peripheral,
type: type,
);
var start = 0;
while (start < value.length) {
final end = start + fragmentSize;
final fragmentedValue = end < value.length
? value.sublist(start, end)
: value.sublist(start);
await peripheralManager.notifyCharacteristic(
central,
characteristic,
value: value,
);
start = end;
}
```

View File

@ -27,7 +27,6 @@ migrate_working_dir/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

View File

@ -1,68 +1,58 @@
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
flutterVersionCode = '1'
flutterVersionCode = "1"
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
flutterVersionName = '1.0'
flutterVersionName = "1.0"
}
android {
namespace "dev.yanshouwang.bluetooth_low_energy_example"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
namespace = "dev.hebei.bluetooth_low_energy_example"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "dev.yanshouwang.bluetooth_low_energy_example"
applicationId = "dev.hebei.bluetooth_low_energy_example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
// minSdkVersion flutter.minSdkVersion
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
signingConfig = signingConfigs.debug
}
}
}
flutter {
source '../..'
source = "../.."
}
dependencies {}

View File

@ -7,6 +7,7 @@
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
@ -30,4 +31,15 @@
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@ -0,0 +1,5 @@
package dev.hebei.bluetooth_low_energy_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View File

@ -1,6 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -1,16 +1,3 @@
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
@ -18,12 +5,12 @@ allprojects {
}
}
rootProject.buildDir = '../build'
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {

View File

@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip

View File

@ -5,16 +5,21 @@ pluginManagement {
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
}()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
plugins {
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
include ":app"
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}
apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
include ":app"

View File

@ -6,17 +6,8 @@
// For more information about Flutter integration tests, please see
// https://docs.flutter.dev/cookbook/testing/integration/introduction
// import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// testWidgets('getPlatformVersion test', (WidgetTester tester) async {
// final BluetoothLowEnergy plugin = BluetoothLowEnergy();
// final String? version = await plugin.getPlatformVersion();
// // The version string depends on the host platform running the test, so
// // just assert that some non-empty string is returned.
// expect(version?.isNotEmpty, true);
// });
}

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
<string>12.0</string>
</dict>
</plist>

View File

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '11.0'
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -1,5 +1,5 @@
PODS:
- bluetooth_low_energy_darwin (2.0.2):
- bluetooth_low_energy_darwin (0.0.1):
- Flutter
- FlutterMacOS
- Flutter (1.0.0)
@ -20,10 +20,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/integration_test/ios"
SPEC CHECKSUMS:
bluetooth_low_energy_darwin: e37c1af3337ebc99a60807137dc5e47c6195eac7
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
integration_test: 13825b8a9334a850581300559b8839134b124670
bluetooth_low_energy_darwin: 764d8d1ae5abefbcdb839e812b4b25c0061fcf8b
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
COCOAPODS: 1.14.3
COCOAPODS: 1.15.2

View File

@ -8,14 +8,14 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
2AABF0E228395C6F7AA780A3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE7ADD24BADD6FBE9CC26391 /* Pods_Runner.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
34E52FEFFBA34148D7F4058F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2353C45A2E0DF37667D194A1 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
80E5EA1F311778FC0849CADE /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A108F55DE130C33FD897F6F /* Pods_RunnerTests.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
F6040B5AA5E5A63D395CB345 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 076A35C4833796B0B42E0237 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -42,21 +42,19 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
03284B9B33BEA09EAC1AFD47 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
076A35C4833796B0B42E0237 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2353C45A2E0DF37667D194A1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2E15B810DAA5A0DB3492E697 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
2FE7B38855AAF28688CD47C8 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
3280CE474DAFF9A6D8555987 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
4BF6EEA10984CDD1A14F950F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
54C98B3103488CA87E98A6F4 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
710DD54C8BBDEA22C3F7F293 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
6A108F55DE130C33FD897F6F /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
71D231D0A1BFD0F83AA161B9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
93DA77ACC45858A97F558838 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -64,15 +62,17 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9C6D6BDEFF4878166372D24A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
9A4F151ED2AB361B13D31B8F /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
AC6B889B1461AF17271694EC /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
BE7ADD24BADD6FBE9CC26391 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
6B5302E24483403072236BBB /* Frameworks */ = {
3B93A9D775DDA5660AD942F8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F6040B5AA5E5A63D395CB345 /* Pods_RunnerTests.framework in Frameworks */,
80E5EA1F311778FC0849CADE /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -80,22 +80,13 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
34E52FEFFBA34148D7F4058F /* Pods_Runner.framework in Frameworks */,
2AABF0E228395C6F7AA780A3 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0FAACD3CD9E188E482384358 /* Frameworks */ = {
isa = PBXGroup;
children = (
2353C45A2E0DF37667D194A1 /* Pods_Runner.framework */,
076A35C4833796B0B42E0237 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
@ -104,17 +95,13 @@
path = RunnerTests;
sourceTree = "<group>";
};
3C9A3D0714D61C07F02BFBB4 /* Pods */ = {
600205600FA5A8A2CFBD1657 /* Frameworks */ = {
isa = PBXGroup;
children = (
93DA77ACC45858A97F558838 /* Pods-Runner.debug.xcconfig */,
4BF6EEA10984CDD1A14F950F /* Pods-Runner.release.xcconfig */,
03284B9B33BEA09EAC1AFD47 /* Pods-Runner.profile.xcconfig */,
9C6D6BDEFF4878166372D24A /* Pods-RunnerTests.debug.xcconfig */,
54C98B3103488CA87E98A6F4 /* Pods-RunnerTests.release.xcconfig */,
710DD54C8BBDEA22C3F7F293 /* Pods-RunnerTests.profile.xcconfig */,
BE7ADD24BADD6FBE9CC26391 /* Pods_Runner.framework */,
6A108F55DE130C33FD897F6F /* Pods_RunnerTests.framework */,
);
path = Pods;
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
@ -135,8 +122,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
3C9A3D0714D61C07F02BFBB4 /* Pods */,
0FAACD3CD9E188E482384358 /* Frameworks */,
F72D04BB94F3E957C55625BE /* Pods */,
600205600FA5A8A2CFBD1657 /* Frameworks */,
);
sourceTree = "<group>";
};
@ -164,6 +151,19 @@
path = Runner;
sourceTree = "<group>";
};
F72D04BB94F3E957C55625BE /* Pods */ = {
isa = PBXGroup;
children = (
71D231D0A1BFD0F83AA161B9 /* Pods-Runner.debug.xcconfig */,
2E15B810DAA5A0DB3492E697 /* Pods-Runner.release.xcconfig */,
2FE7B38855AAF28688CD47C8 /* Pods-Runner.profile.xcconfig */,
9A4F151ED2AB361B13D31B8F /* Pods-RunnerTests.debug.xcconfig */,
3280CE474DAFF9A6D8555987 /* Pods-RunnerTests.release.xcconfig */,
AC6B889B1461AF17271694EC /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -171,10 +171,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
A3C3FEADF4EBF04832B143F6 /* [CP] Check Pods Manifest.lock */,
BEFD77958F331CBECEF42D43 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
6B5302E24483403072236BBB /* Frameworks */,
3B93A9D775DDA5660AD942F8 /* Frameworks */,
);
buildRules = (
);
@ -190,14 +190,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
67E1A4356061A10A5EC11286 /* [CP] Check Pods Manifest.lock */,
10E9D52FCC12E90EB7D107E2 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
066C9063BC370AC8304B71D9 /* [CP] Embed Pods Frameworks */,
2DAAF975682F92424B5A52E3 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -215,7 +215,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
@ -269,7 +269,29 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
066C9063BC370AC8304B71D9 /* [CP] Embed Pods Frameworks */ = {
10E9D52FCC12E90EB7D107E2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
2DAAF975682F92424B5A52E3 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -302,28 +324,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
67E1A4356061A10A5EC11286 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -339,7 +339,7 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
A3C3FEADF4EBF04832B143F6 /* [CP] Check Pods Manifest.lock */ = {
BEFD77958F331CBECEF42D43 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -415,6 +415,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@ -444,6 +445,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -452,7 +454,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@ -468,14 +470,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = T5YVNX2NAC;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample;
PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -485,14 +487,14 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9C6D6BDEFF4878166372D24A /* Pods-RunnerTests.debug.xcconfig */;
baseConfigurationReference = 9A4F151ED2AB361B13D31B8F /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -503,14 +505,14 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 54C98B3103488CA87E98A6F4 /* Pods-RunnerTests.release.xcconfig */;
baseConfigurationReference = 3280CE474DAFF9A6D8555987 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -519,14 +521,14 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 710DD54C8BBDEA22C3F7F293 /* Pods-RunnerTests.profile.xcconfig */;
baseConfigurationReference = AC6B889B1461AF17271694EC /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -537,6 +539,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@ -566,6 +569,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@ -580,7 +584,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -592,6 +596,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@ -621,6 +626,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -629,7 +635,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@ -647,14 +653,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = T5YVNX2NAC;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample;
PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -670,14 +676,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = T5YVNX2NAC;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample;
PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,5 +1,5 @@
import UIKit
import Flutter
import UIKit
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

View File

@ -27,7 +27,7 @@
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Hello Bluetooth LowEnergy!</string>
<string>Need to use the bluetooth to communicate with peripherals</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
export 'models/log.dart';

View File

@ -0,0 +1,10 @@
class Log {
final DateTime time;
final String type;
final String message;
Log({
required this.type,
required this.message,
}) : time = DateTime.now();
}

View File

@ -0,0 +1,85 @@
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:clover/clover.dart';
import 'package:collection/collection.dart';
import 'package:go_router/go_router.dart';
import 'view_models.dart';
import 'views.dart';
final routerConfig = GoRouter(
redirect: (context, state) {
if (state.matchedLocation == '/') {
return '/central';
}
return null;
},
routes: [
StatefulShellRoute(
// builder: (context, state, navigationShell) {
// return HomeView(navigationShell: navigationShell);
// },
builder: (context, state, navigationShell) => navigationShell,
navigatorContainerBuilder: (context, navigationShell, children) {
final navigators = children.mapIndexed(
(index, element) {
if (index == 0) {
return ViewModelBinding(
viewBuilder: (context) => element,
viewModelBuilder: (context) => CentralManagerViewModel(),
);
} else {
return element;
}
},
).toList();
return HomeView(
navigationShell: navigationShell,
navigators: navigators,
);
},
branches: [
StatefulShellBranch(
routes: [
GoRoute(
path: '/central',
builder: (context, state) {
return const CentralManagerView();
},
routes: [
GoRoute(
path: ':uuid',
builder: (context, state) {
final uuidValue = state.pathParameters['uuid']!;
final uuid = UUID.fromString(uuidValue);
final viewModel =
ViewModel.of<CentralManagerViewModel>(context);
final eventArgs = viewModel.discoveries.firstWhere(
(discovery) => discovery.peripheral.uuid == uuid);
return ViewModelBinding(
viewBuilder: (context) => PeripheralView(),
viewModelBuilder: (context) =>
PeripheralViewModel(eventArgs),
);
},
),
],
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: '/peripheral',
builder: (context, state) {
return ViewModelBinding(
viewBuilder: (context) => const PeripheralManagerView(),
viewModelBuilder: (context) => PeripheralManagerViewModel(),
);
},
),
],
),
],
),
],
);

View File

@ -0,0 +1,6 @@
export 'view_models/central_manager_view_model.dart';
export 'view_models/peripheral_view_model.dart';
export 'view_models/service_view_model.dart';
export 'view_models/characteristic_view_model.dart';
export 'view_models/descriptor_view_model.dart';
export 'view_models/peripheral_manager_view_model.dart';

View File

@ -0,0 +1,76 @@
import 'dart:async';
import 'dart:io';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:clover/clover.dart';
import 'package:logging/logging.dart';
class CentralManagerViewModel extends ViewModel {
final CentralManager _manager;
final List<DiscoveredEventArgs> _discoveries;
bool _discovering;
late final StreamSubscription _stateChangedSubscription;
late final StreamSubscription _discoveredSubscription;
CentralManagerViewModel()
: _manager = CentralManager()..logLevel = Level.INFO,
_discoveries = [],
_discovering = false {
_stateChangedSubscription = _manager.stateChanged.listen((eventArgs) async {
if (eventArgs.state == BluetoothLowEnergyState.unauthorized &&
Platform.isAndroid) {
await _manager.authorize();
}
notifyListeners();
});
_discoveredSubscription = _manager.discovered.listen((eventArgs) {
final peripheral = eventArgs.peripheral;
final index = _discoveries.indexWhere((i) => i.peripheral == peripheral);
if (index < 0) {
_discoveries.add(eventArgs);
} else {
_discoveries[index] = eventArgs;
}
notifyListeners();
});
}
BluetoothLowEnergyState get state => _manager.state;
bool get discovering => _discovering;
List<DiscoveredEventArgs> get discoveries => _discoveries;
Future<void> showAppSettings() async {
await _manager.showAppSettings();
}
Future<void> startDiscovery({
List<UUID>? serviceUUIDs,
}) async {
if (_discovering) {
return;
}
_discoveries.clear();
await _manager.startDiscovery(
serviceUUIDs: serviceUUIDs,
);
_discovering = true;
notifyListeners();
}
Future<void> stopDiscovery() async {
if (!_discovering) {
return;
}
await _manager.stopDiscovery();
_discovering = false;
notifyListeners();
}
@override
void dispose() {
_stateChangedSubscription.cancel();
_discoveredSubscription.cancel();
super.dispose();
}
}

View File

@ -0,0 +1,145 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:bluetooth_low_energy_example/models.dart';
import 'package:clover/clover.dart';
import 'descriptor_view_model.dart';
class CharacteristicViewModel extends ViewModel {
final CentralManager _manager;
final Peripheral _peripheral;
final GATTCharacteristic _characteristic;
final List<DescriptorViewModel> _descriptorViewModels;
final List<Log> _logs;
GATTCharacteristicWriteType _writeType;
bool _notifyState;
late final StreamSubscription _characteristicNotifiedSubscription;
CharacteristicViewModel({
required CentralManager manager,
required Peripheral peripheral,
required GATTCharacteristic characteristic,
}) : _manager = manager,
_peripheral = peripheral,
_characteristic = characteristic,
_descriptorViewModels = characteristic.descriptors
.map((descriptor) => DescriptorViewModel(descriptor))
.toList(),
_logs = [],
_writeType = GATTCharacteristicWriteType.withResponse,
_notifyState = false {
if (!canWrite && canWriteWithoutResponse) {
_writeType = GATTCharacteristicWriteType.withoutResponse;
}
_characteristicNotifiedSubscription =
_manager.characteristicNotified.listen((eventArgs) {
if (eventArgs.characteristic != _characteristic) {
return;
}
final log = Log(
type: 'Notified',
message: '[${eventArgs.value.length}] ${eventArgs.value}',
);
_logs.add(log);
notifyListeners();
});
}
UUID get uuid => _characteristic.uuid;
bool get canRead =>
_characteristic.properties.contains(GATTCharacteristicProperty.read);
bool get canWrite =>
_characteristic.properties.contains(GATTCharacteristicProperty.write);
bool get canWriteWithoutResponse => _characteristic.properties
.contains(GATTCharacteristicProperty.writeWithoutResponse);
bool get canNotify =>
_characteristic.properties.contains(GATTCharacteristicProperty.notify) ||
_characteristic.properties.contains(GATTCharacteristicProperty.indicate);
List<DescriptorViewModel> get descriptorViewModels => _descriptorViewModels;
List<Log> get logs => _logs;
GATTCharacteristicWriteType get writeType => _writeType;
bool get notifyState => _notifyState;
Future<void> read() async {
final value = await _manager.readCharacteristic(
_peripheral,
_characteristic,
);
final log = Log(
type: 'Read',
message: '[${value.length}] $value',
);
_logs.add(log);
notifyListeners();
}
void setWriteType(GATTCharacteristicWriteType type) {
if (type == GATTCharacteristicWriteType.withResponse && !canWrite) {
throw ArgumentError.value(type);
}
if (type == GATTCharacteristicWriteType.withoutResponse &&
!canWriteWithoutResponse) {
throw ArgumentError.value(type);
}
_writeType = type;
notifyListeners();
}
Future<void> write(Uint8List value) async {
// Fragments the value by maximumWriteLength.
final fragmentSize = await _manager.getMaximumWriteLength(
_peripheral,
type: writeType,
);
var start = 0;
while (start < value.length) {
final end = start + fragmentSize;
final fragmentedValue =
end < value.length ? value.sublist(start, end) : value.sublist(start);
final type = writeType;
await _manager.writeCharacteristic(
_peripheral,
_characteristic,
value: fragmentedValue,
type: type,
);
final log = Log(
type: type == GATTCharacteristicWriteType.withResponse
? 'Write'
: 'Write without response',
message: '[${value.length}] $value',
);
_logs.add(log);
notifyListeners();
start = end;
}
}
Future<void> setNotifyState(bool state) async {
await _manager.setCharacteristicNotifyState(
_peripheral,
_characteristic,
state: state,
);
_notifyState = state;
notifyListeners();
}
void clearLogs() {
_logs.clear();
notifyListeners();
}
@override
void dispose() {
_characteristicNotifiedSubscription.cancel();
for (var descriptorViewModel in descriptorViewModels) {
descriptorViewModel.dispose();
}
super.dispose();
}
}

View File

@ -0,0 +1,10 @@
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:clover/clover.dart';
class DescriptorViewModel extends ViewModel {
final GATTDescriptor _descriptor;
DescriptorViewModel(this._descriptor);
UUID get uuid => _descriptor.uuid;
}

View File

@ -0,0 +1,174 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:bluetooth_low_energy_example/models.dart';
import 'package:clover/clover.dart';
import 'package:logging/logging.dart';
class PeripheralManagerViewModel extends ViewModel {
final PeripheralManager _manager;
final List<Log> _logs;
bool _advertising;
late final StreamSubscription _stateChangedSubscription;
late final StreamSubscription _characteristicReadRequestedSubscription;
late final StreamSubscription _characteristicWriteRequestedSubscription;
late final StreamSubscription _characteristicNotifyStateChangedSubscription;
PeripheralManagerViewModel()
: _manager = PeripheralManager()..logLevel = Level.INFO,
_logs = [],
_advertising = false {
_stateChangedSubscription = _manager.stateChanged.listen((eventArgs) async {
if (eventArgs.state == BluetoothLowEnergyState.unauthorized &&
Platform.isAndroid) {
await _manager.authorize();
}
notifyListeners();
});
_characteristicReadRequestedSubscription =
_manager.characteristicReadRequested.listen((eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final request = eventArgs.request;
final offset = request.offset;
final log = Log(
type: 'Characteristic read requested',
message: '${central.uuid}, ${characteristic.uuid}, $offset',
);
_logs.add(log);
notifyListeners();
final elements = List.generate(100, (i) => i % 256);
final value = Uint8List.fromList(elements);
final trimmedValue = value.sublist(offset);
await _manager.respondReadRequestWithValue(
request,
value: trimmedValue,
);
});
_characteristicWriteRequestedSubscription =
_manager.characteristicWriteRequested.listen((eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final request = eventArgs.request;
final offset = request.offset;
final value = request.value;
final log = Log(
type: 'Characteristic write requested',
message:
'[${value.length}] ${central.uuid}, ${characteristic.uuid}, $offset, $value',
);
_logs.add(log);
notifyListeners();
await _manager.respondWriteRequest(request);
});
_characteristicNotifyStateChangedSubscription =
_manager.characteristicNotifyStateChanged.listen((eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final state = eventArgs.state;
final log = Log(
type: 'Characteristic notify state changed',
message: '${central.uuid}, ${characteristic.uuid}, $state',
);
_logs.add(log);
notifyListeners();
// Write someting to the central when notify started.
if (state) {
final maximumNotifyLength =
await _manager.getMaximumNotifyLength(central);
final elements = List.generate(maximumNotifyLength, (i) => i % 256);
final value = Uint8List.fromList(elements);
await _manager.notifyCharacteristic(
central,
characteristic,
value: value,
);
}
});
}
BluetoothLowEnergyState get state => _manager.state;
bool get advertising => _advertising;
List<Log> get logs => _logs;
Future<void> showAppSettings() async {
await _manager.showAppSettings();
}
Future<void> startAdvertising() async {
if (_advertising) {
return;
}
await _manager.removeAllServices();
final elements = List.generate(100, (i) => i % 256);
final value = Uint8List.fromList(elements);
final service = GATTService(
uuid: UUID.short(100),
isPrimary: true,
includedServices: [],
characteristics: [
GATTCharacteristic.immutable(
uuid: UUID.short(200),
value: value,
descriptors: [],
),
GATTCharacteristic.mutable(
uuid: UUID.short(201),
properties: [
GATTCharacteristicProperty.read,
GATTCharacteristicProperty.write,
GATTCharacteristicProperty.writeWithoutResponse,
GATTCharacteristicProperty.notify,
GATTCharacteristicProperty.indicate,
],
permissions: [
GATTCharacteristicPermission.read,
GATTCharacteristicPermission.write,
],
descriptors: [],
),
],
);
await _manager.addService(service);
final advertisement = Advertisement(
name: Platform.isWindows ? null : 'BLE-12138',
manufacturerSpecificData: Platform.isIOS || Platform.isMacOS
? []
: [
ManufacturerSpecificData(
id: 0x2e19,
data: Uint8List.fromList([0x01, 0x02, 0x03]),
)
],
);
await _manager.startAdvertising(advertisement);
_advertising = true;
notifyListeners();
}
Future<void> stopAdvertising() async {
if (!_advertising) {
return;
}
await _manager.stopAdvertising();
_advertising = false;
notifyListeners();
}
void clearLogs() {
_logs.clear();
notifyListeners();
}
@override
void dispose() {
_stateChangedSubscription.cancel();
_characteristicReadRequestedSubscription.cancel();
_characteristicWriteRequestedSubscription.cancel();
_characteristicNotifyStateChangedSubscription.cancel();
super.dispose();
}
}

View File

@ -0,0 +1,88 @@
import 'dart:async';
import 'dart:io';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:clover/clover.dart';
import 'package:hybrid_logging/hybrid_logging.dart';
import 'service_view_model.dart';
class PeripheralViewModel extends ViewModel with TypeLogger {
final CentralManager _manager;
final Peripheral _peripheral;
final String? _name;
bool _connected;
List<ServiceViewModel> _serviceViewModels;
late final StreamSubscription _connectionStateChangedSubscription;
late final StreamSubscription _mtuChangedChangedSubscription;
PeripheralViewModel(DiscoveredEventArgs eventArgs)
: _manager = CentralManager(),
_peripheral = eventArgs.peripheral,
_name = eventArgs.advertisement.name,
_connected = false,
_serviceViewModels = [] {
_connectionStateChangedSubscription =
_manager.connectionStateChanged.listen((eventArgs) {
if (eventArgs.peripheral != _peripheral) {
return;
}
if (eventArgs.state == ConnectionState.connected) {
_connected = true;
} else {
_connected = false;
_serviceViewModels = [];
}
notifyListeners();
});
if (Platform.isAndroid || Platform.isWindows) {
_mtuChangedChangedSubscription = _manager.mtuChanged.listen((eventArgs) {
if (eventArgs.peripheral != _peripheral) {
return;
}
logger.info('MTU chanaged: ${eventArgs.mtu}');
});
}
}
UUID get uuid => _peripheral.uuid;
String? get name => _name;
bool get connected => _connected;
List<ServiceViewModel> get serviceViewModels => _serviceViewModels;
Future<void> connect() async {
await _manager.connect(_peripheral);
}
Future<void> disconnect() async {
await _manager.disconnect(_peripheral);
}
Future<void> discoverGATT() async {
final services = await _manager.discoverGATT(_peripheral);
_serviceViewModels = services
.map((service) => ServiceViewModel(
manager: _manager,
peripheral: _peripheral,
service: service,
))
.toList();
notifyListeners();
}
@override
void dispose() {
if (connected) {
disconnect();
}
_connectionStateChangedSubscription.cancel();
if (Platform.isAndroid || Platform.isWindows) {
_mtuChangedChangedSubscription.cancel();
}
for (var serviceViewModel in serviceViewModels) {
serviceViewModel.dispose();
}
super.dispose();
}
}

View File

@ -0,0 +1,50 @@
import 'dart:io';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:clover/clover.dart';
import 'characteristic_view_model.dart';
class ServiceViewModel extends ViewModel {
final GATTService _service;
final List<ServiceViewModel> _includedServiceViewModels;
final List<CharacteristicViewModel> _characteristicViewModels;
ServiceViewModel({
required CentralManager manager,
required Peripheral peripheral,
required GATTService service,
}) : _service = service,
_includedServiceViewModels = Platform.isLinux
? []
: service.includedServices
.map((service) => ServiceViewModel(
manager: manager,
peripheral: peripheral,
service: service,
))
.toList(),
_characteristicViewModels = service.characteristics
.map((characteristic) => CharacteristicViewModel(
manager: manager,
peripheral: peripheral,
characteristic: characteristic,
))
.toList();
UUID get uuid => _service.uuid;
bool get isPrimary => _service.isPrimary;
List<ServiceViewModel> get includedServiceViewModels =>
_includedServiceViewModels;
List<CharacteristicViewModel> get characteristicViewModels =>
_characteristicViewModels;
@override
void dispose() {
for (var characteristicViewModel in characteristicViewModels) {
characteristicViewModel.dispose();
}
super.dispose();
}
}

View File

@ -0,0 +1,4 @@
export 'views/home_view.dart';
export 'views/central_manager_view.dart';
export 'views/peripheral_manager_view.dart';
export 'views/peripheral_view.dart';

View File

@ -0,0 +1,69 @@
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:convert/convert.dart';
import 'package:flutter/material.dart';
class AdvertisementView extends StatelessWidget {
final Advertisement advertisement;
const AdvertisementView({
super.key,
required this.advertisement,
});
@override
Widget build(BuildContext context) {
final manufacturerSpecificData = advertisement.manufacturerSpecificData;
return LayoutBuilder(
builder: (context, constraints) {
const idWidth = 100.0;
final valueWidth = constraints.maxWidth - idWidth - 16.0 * 2.0;
return DataTable(
columnSpacing: 0.0,
horizontalMargin: 16.0,
columns: [
DataColumn(
label: Container(
width: idWidth,
alignment: Alignment.center,
child: const Text('Id'),
),
),
DataColumn(
label: Container(
width: valueWidth,
alignment: Alignment.center,
child: const Text('Value'),
),
),
],
rows: manufacturerSpecificData.map((item) {
final id = '0x${item.id.toRadixString(16).padLeft(4, '0')}';
final value = hex.encode(item.data);
return DataRow(
cells: [
DataCell(
Container(
width: idWidth,
alignment: Alignment.center,
child: Text(id),
),
),
DataCell(
Container(
width: valueWidth,
alignment: Alignment.center,
child: Text(
value,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
],
);
}).toList(),
);
},
);
}
}

View File

@ -0,0 +1,127 @@
import 'dart:io';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:bluetooth_low_energy_example/view_models.dart';
import 'package:bluetooth_low_energy_example/widgets.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'advertisement_view.dart';
class CentralManagerView extends StatelessWidget {
const CentralManagerView({super.key});
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<CentralManagerViewModel>(context);
final state = viewModel.state;
final discovering = viewModel.discovering;
return Scaffold(
appBar: AppBar(
title: const Text('Central Manager'),
actions: [
TextButton(
onPressed: state == BluetoothLowEnergyState.poweredOn
? () async {
if (discovering) {
await viewModel.stopDiscovery();
} else {
await viewModel.startDiscovery();
}
}
: null,
child: Text(discovering ? 'END' : 'BEGIN'),
),
],
),
body: buildBody(context),
);
}
Widget buildBody(BuildContext context) {
final viewModel = ViewModel.of<CentralManagerViewModel>(context);
final state = viewModel.state;
final isMobile = Platform.isAndroid || Platform.isIOS;
if (state == BluetoothLowEnergyState.unauthorized && isMobile) {
return Center(
child: TextButton(
onPressed: () => viewModel.showAppSettings(),
child: const Text('Go to settings'),
),
);
} else if (state == BluetoothLowEnergyState.poweredOn) {
final discoveries = viewModel.discoveries;
return ListView.separated(
itemBuilder: (context, index) {
final theme = Theme.of(context);
final discovery = discoveries[index];
final uuid = discovery.peripheral.uuid;
final name = discovery.advertisement.name;
final rssi = discovery.rssi;
return ListTile(
onTap: () {
onTapDissovery(context, discovery);
},
onLongPress: () {
onLongPressDiscovery(context, discovery);
},
title: Text(name ?? ''),
subtitle: Text(
'$uuid',
style: theme.textTheme.bodySmall,
softWrap: false,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
RSSIIndicator(rssi),
Text('$rssi'),
],
),
);
},
separatorBuilder: (context, i) {
return const Divider(
height: 0.0,
);
},
itemCount: discoveries.length,
);
} else {
return Center(
child: Text(
'$state',
style: Theme.of(context).textTheme.titleMedium,
),
);
}
}
void onTapDissovery(
BuildContext context, DiscoveredEventArgs discovery) async {
final viewModel = ViewModel.of<CentralManagerViewModel>(context);
if (viewModel.discovering) {
await viewModel.stopDiscovery();
if (!context.mounted) {
return;
}
}
final uuid = discovery.peripheral.uuid;
context.go('/central/$uuid');
}
void onLongPressDiscovery(
BuildContext context, DiscoveredEventArgs discovery) async {
await showModalBottomSheet(
context: context,
builder: (context) {
return AdvertisementView(
advertisement: discovery.advertisement,
);
},
);
}
}

View File

@ -0,0 +1,18 @@
import 'package:bluetooth_low_energy_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
class CharacteristicTreeNodeView extends StatelessWidget {
const CharacteristicTreeNodeView({super.key});
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<CharacteristicViewModel>(context);
return Text(
'${viewModel.uuid}',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
);
}
}

View File

@ -0,0 +1,178 @@
import 'dart:convert';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:bluetooth_low_energy_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'log_view.dart';
class CharacteristicView extends StatefulWidget {
const CharacteristicView({super.key});
@override
State<CharacteristicView> createState() => _CharacteristicViewState();
}
class _CharacteristicViewState extends State<CharacteristicView> {
late final TextEditingController _textController;
@override
void initState() {
super.initState();
_textController = TextEditingController();
}
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<CharacteristicViewModel>(context);
final logs = viewModel.logs;
return Column(
children: [
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return OverflowBox(
alignment: Alignment.topCenter,
maxHeight: constraints.maxHeight + 1.0,
child: Row(
children: [
Expanded(
child: InputDecorator(
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(12.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.vertical(
top: const Radius.circular(12.0),
bottom: viewModel.canWrite ||
viewModel.canWriteWithoutResponse
? Radius.zero
: const Radius.circular(12.0),
),
),
),
child: ListView.builder(
itemBuilder: (context, i) {
final log = logs[i];
return LogView(
log: log,
);
},
itemCount: logs.length,
),
),
),
Column(
children: [
Visibility(
visible: viewModel.canNotify,
child: viewModel.notifyState
? IconButton.filled(
onPressed: () async {
await viewModel.setNotifyState(false);
},
icon:
const Icon(Symbols.notifications_active),
)
: IconButton.filledTonal(
onPressed: () async {
await viewModel.setNotifyState(true);
},
icon: const Icon(Symbols.notifications_off),
),
),
Visibility(
visible: viewModel.canRead,
child: IconButton.filled(
onPressed: () async {
await viewModel.read();
},
icon: const Icon(Symbols.arrow_downward),
),
),
Visibility(
visible: viewModel.canWrite ||
viewModel.canWriteWithoutResponse,
child: viewModel.writeType ==
GATTCharacteristicWriteType.withResponse
? IconButton.filled(
onPressed: viewModel.canWriteWithoutResponse
? () {
viewModel.setWriteType(
GATTCharacteristicWriteType
.withoutResponse);
}
: null,
icon: const Icon(Symbols.swap_vert),
)
: IconButton.filledTonal(
onPressed: viewModel.canWrite
? () {
viewModel.setWriteType(
GATTCharacteristicWriteType
.withResponse);
}
: null,
icon: const Icon(Symbols.arrow_upward),
),
),
IconButton.filled(
onPressed: () => viewModel.clearLogs(),
icon: const Icon(Symbols.delete),
),
],
),
],
),
);
},
),
),
Visibility(
visible: viewModel.canWrite || viewModel.canWriteWithoutResponse,
child: Row(
children: [
Expanded(
child: TextField(
controller: _textController,
decoration: const InputDecoration(
contentPadding: EdgeInsets.symmetric(
horizontal: 12.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(12.0),
),
),
),
),
),
ValueListenableBuilder(
valueListenable: _textController,
builder: (context, tev, child) {
final text = tev.text;
return IconButton.filled(
onPressed: text.isEmpty
? null
: () async {
final value = utf8.encode(text);
await viewModel.write(value);
},
icon: const Icon(Symbols.pets),
);
},
),
],
),
),
],
);
}
@override
void dispose() {
_textController.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,18 @@
import 'package:bluetooth_low_energy_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
class DescriptorTreeNodeView extends StatelessWidget {
const DescriptorTreeNodeView({super.key});
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<DescriptorViewModel>(context);
return Text(
'${viewModel.uuid}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.tertiary,
),
);
}
}

View File

@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class HomeView extends StatefulWidget {
final StatefulNavigationShell navigationShell;
final List<Widget> navigators;
const HomeView({
super.key,
required this.navigationShell,
required this.navigators,
});
@override
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
late final PageController _controller;
@override
void initState() {
super.initState();
_controller = PageController(
initialPage: widget.navigationShell.currentIndex,
);
}
@override
Widget build(BuildContext context) {
final navigationShell = widget.navigationShell;
final navigators = widget.navigators;
return Scaffold(
// body: navigationShell,
body: PageView.builder(
controller: _controller,
onPageChanged: (index) {
// Ignore tap events.
if (index == navigationShell.currentIndex) {
return;
}
navigationShell.goBranch(
index,
initialLocation: false,
);
},
itemBuilder: (context, index) {
return navigators[index];
},
itemCount: navigators.length,
),
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
navigationShell.goBranch(
index,
initialLocation: index == navigationShell.currentIndex,
);
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.radar),
label: 'Central',
),
BottomNavigationBarItem(
icon: Icon(Icons.sensors),
label: 'Peripheral',
),
],
currentIndex: widget.navigationShell.currentIndex,
),
);
}
@override
void didUpdateWidget(covariant HomeView oldWidget) {
super.didUpdateWidget(oldWidget);
final navigationShell = widget.navigationShell;
final page = _controller.page ?? _controller.initialPage;
final index = page.round();
// Ignore swipe events.
if (index == navigationShell.currentIndex) {
return;
}
_controller.animateToPage(
navigationShell.currentIndex,
duration: const Duration(milliseconds: 300),
curve: Curves.linear,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,43 @@
import 'package:bluetooth_low_energy_example/models.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class LogView extends StatelessWidget {
final Log log;
const LogView({
super.key,
required this.log,
});
@override
Widget build(BuildContext context) {
final formatter = DateFormat.Hms();
final time = formatter.format(log.time);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text.rich(
TextSpan(
text: '[$time] ',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.error,
),
children: [
TextSpan(
text: log.type,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.primary,
)),
],
),
),
Text(
log.message,
style: Theme.of(context).textTheme.bodySmall,
),
],
);
}
}

View File

@ -0,0 +1,79 @@
import 'dart:io';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:bluetooth_low_energy_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'log_view.dart';
class PeripheralManagerView extends StatelessWidget {
const PeripheralManagerView({super.key});
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<PeripheralManagerViewModel>(context);
final state = viewModel.state;
final advertising = viewModel.advertising;
return Scaffold(
appBar: AppBar(
title: const Text('Peripheral Manager'),
actions: [
TextButton(
onPressed: state == BluetoothLowEnergyState.poweredOn
? () async {
if (advertising) {
await viewModel.stopAdvertising();
} else {
await viewModel.startAdvertising();
}
}
: null,
child: Text(advertising ? 'END' : 'BEGIN'),
),
],
),
body: buildBody(context),
floatingActionButton: state == BluetoothLowEnergyState.poweredOn
? FloatingActionButton(
onPressed: () => viewModel.clearLogs(),
child: const Icon(Symbols.delete),
)
: null,
);
}
Widget buildBody(BuildContext context) {
final viewModel = ViewModel.of<PeripheralManagerViewModel>(context);
final state = viewModel.state;
final isMobile = Platform.isAndroid || Platform.isIOS;
if (state == BluetoothLowEnergyState.unauthorized && isMobile) {
return Center(
child: TextButton(
onPressed: () => viewModel.showAppSettings(),
child: const Text('Go to settings'),
),
);
} else if (state == BluetoothLowEnergyState.poweredOn) {
final logs = viewModel.logs;
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
final log = logs[i];
return LogView(
log: log,
);
},
itemCount: logs.length,
);
} else {
return Center(
child: Text(
'$state',
style: Theme.of(context).textTheme.titleMedium,
),
);
}
}
}

View File

@ -0,0 +1,107 @@
import 'package:bluetooth_low_energy_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
import 'package:flutter_simple_treeview/flutter_simple_treeview.dart';
import 'characteristic_tree_node_view.dart';
import 'characteristic_view.dart';
import 'descriptor_tree_node_view.dart';
import 'service_tree_node_view.dart';
class PeripheralView extends StatelessWidget {
final TreeController _treeController;
PeripheralView({super.key})
: _treeController = TreeController(
allNodesExpanded: false,
);
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<PeripheralViewModel>(context);
final connected = viewModel.connected;
final serviceViewModels = viewModel.serviceViewModels;
return Scaffold(
appBar: AppBar(
title: Text(viewModel.name ?? '${viewModel.uuid}'),
actions: [
TextButton(
onPressed: () async {
if (connected) {
await viewModel.disconnect();
} else {
await viewModel.connect();
await viewModel.discoverGATT();
}
},
child: Text(connected ? 'DISCONNECT' : 'CONNECT'),
),
],
),
body: SingleChildScrollView(
child: TreeView(
treeController: _treeController,
indent: 0.0,
nodes: _buildServiceTreeNodes(serviceViewModels),
),
),
);
}
List<TreeNode> _buildServiceTreeNodes(List<ServiceViewModel> viewModels) {
return viewModels.map((viewModel) {
final includedServiceViewModels = viewModel.includedServiceViewModels;
final characteristicViewModels = viewModel.characteristicViewModels;
return TreeNode(
children: [
..._buildServiceTreeNodes(includedServiceViewModels),
..._buildCharacteristicTreeNodes(characteristicViewModels),
],
content: InheritedViewModel(
view: const ServiceTreeNodeView(),
viewModel: viewModel,
),
);
}).toList();
}
List<TreeNode> _buildCharacteristicTreeNodes(
List<CharacteristicViewModel> viewModels) {
return viewModels.map((viewModel) {
final descriptorViewModels = viewModel.descriptorViewModels;
return TreeNode(
children: [
TreeNode(
content: Expanded(
child: Container(
margin: const EdgeInsets.only(right: 40.0),
height: 360.0,
child: InheritedViewModel(
view: const CharacteristicView(),
viewModel: viewModel,
),
),
),
),
..._buildDescriptorTreeNodes(descriptorViewModels),
],
content: InheritedViewModel(
view: const CharacteristicTreeNodeView(),
viewModel: viewModel,
),
);
}).toList();
}
List<TreeNode> _buildDescriptorTreeNodes(
List<DescriptorViewModel> viewModels) {
return viewModels.map((viewModel) {
return TreeNode(
content: InheritedViewModel(
view: const DescriptorTreeNodeView(),
viewModel: viewModel,
),
);
}).toList();
}
}

View File

@ -0,0 +1,18 @@
import 'package:bluetooth_low_energy_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
class ServiceTreeNodeView extends StatelessWidget {
const ServiceTreeNodeView({super.key});
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<ServiceViewModel>(context);
return Text(
'${viewModel.uuid}',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
);
}
}

View File

@ -0,0 +1 @@
export 'widgets/rssi_indicator.dart';

View File

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
class RSSIIndicator extends StatelessWidget {
final int rssi;
const RSSIIndicator(
this.rssi, {
super.key,
});
@override
Widget build(BuildContext context) {
final IconData icon;
if (rssi > -70) {
icon = Icons.wifi_rounded;
} else if (rssi > -100) {
icon = Icons.wifi_2_bar_rounded;
} else {
icon = Icons.wifi_1_bar_rounded;
}
return Icon(icon);
}
}

View File

@ -7,7 +7,7 @@ project(runner LANGUAGES CXX)
set(BINARY_NAME "bluetooth_low_energy_example")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "dev.yanshouwang.bluetooth_low_energy")
set(APPLICATION_ID "dev.hebei.bluetooth_low_energy")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
@ -125,6 +125,12 @@ foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")

View File

@ -81,6 +81,24 @@ static gboolean my_application_local_command_line(GApplication* application, gch
return TRUE;
}
// Implements GApplication::startup.
static void my_application_startup(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application startup.
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
}
// Implements GApplication::shutdown.
static void my_application_shutdown(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application shutdown.
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
@ -91,6 +109,8 @@ static void my_application_dispose(GObject* object) {
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}

View File

@ -8,5 +8,5 @@ import Foundation
import bluetooth_low_energy_darwin
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
BluetoothLowEnergyDarwin.register(with: registry.registrar(forPlugin: "BluetoothLowEnergyDarwin"))
BluetoothLowEnergyDarwinPlugin.register(with: registry.registrar(forPlugin: "BluetoothLowEnergyDarwinPlugin"))
}

View File

@ -1,5 +1,5 @@
PODS:
- bluetooth_low_energy_darwin (2.0.2):
- bluetooth_low_energy_darwin (0.0.1):
- Flutter
- FlutterMacOS
- FlutterMacOS (1.0.0)
@ -15,9 +15,9 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral
SPEC CHECKSUMS:
bluetooth_low_energy_darwin: e37c1af3337ebc99a60807137dc5e47c6195eac7
bluetooth_low_energy_darwin: 30da11d012831c0dbe57487b5f23bf344b90ab11
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
COCOAPODS: 1.14.3
COCOAPODS: 1.15.2

View File

@ -21,14 +21,14 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
11E67697E2DFF348C5E0FBA6 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12AE3247A53A0A6C66A10AB1 /* Pods_RunnerTests.framework */; };
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
98831461FCCC0C712B565489 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7618EFBF5D56DEA9B5BC6858 /* Pods_Runner.framework */; };
5FDEA1A8816A331CA78E7F6D /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC81A8405E01D3966B98ABCC /* Pods_RunnerTests.framework */; };
83CB8ED1FBA8CD35DAEB069E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60A2C50BFA238B286DE35299 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -62,9 +62,6 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
032DEBF51E6D235331EAFB6D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
12AE3247A53A0A6C66A10AB1 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2C73F6F52B9D612E7A3ED61B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
@ -81,13 +78,16 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
42843373A331898F762AE9FA /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
69A28A72349A6C45CE7FD12D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
7618EFBF5D56DEA9B5BC6858 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50A13F12221F58302552523F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
60A2C50BFA238B286DE35299 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
7F49A581B2FF1737F1557371 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
7BDC5BC63E0570F1277EC2A6 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
BBBF70DBDF6B0B4E82F4E8A2 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
977C1A7873ABE953C0EA17FA /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
ABB87EDC364CAC4062F6DE96 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
AC81A8405E01D3966B98ABCC /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AEF60039D20E8A370442E404 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
D545088443E99CCE89A693B8 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -95,7 +95,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
11E67697E2DFF348C5E0FBA6 /* Pods_RunnerTests.framework in Frameworks */,
5FDEA1A8816A331CA78E7F6D /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -103,13 +103,27 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
98831461FCCC0C712B565489 /* Pods_Runner.framework in Frameworks */,
83CB8ED1FBA8CD35DAEB069E /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2E14B787045E896EB2B5DE61 /* Pods */ = {
isa = PBXGroup;
children = (
50A13F12221F58302552523F /* Pods-Runner.debug.xcconfig */,
977C1A7873ABE953C0EA17FA /* Pods-Runner.release.xcconfig */,
D545088443E99CCE89A693B8 /* Pods-Runner.profile.xcconfig */,
7BDC5BC63E0570F1277EC2A6 /* Pods-RunnerTests.debug.xcconfig */,
AEF60039D20E8A370442E404 /* Pods-RunnerTests.release.xcconfig */,
ABB87EDC364CAC4062F6DE96 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
@ -137,7 +151,7 @@
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
5413C8972DC878C53291A3D1 /* Pods */,
2E14B787045E896EB2B5DE61 /* Pods */,
);
sourceTree = "<group>";
};
@ -185,24 +199,11 @@
path = Runner;
sourceTree = "<group>";
};
5413C8972DC878C53291A3D1 /* Pods */ = {
isa = PBXGroup;
children = (
2C73F6F52B9D612E7A3ED61B /* Pods-Runner.debug.xcconfig */,
69A28A72349A6C45CE7FD12D /* Pods-Runner.release.xcconfig */,
032DEBF51E6D235331EAFB6D /* Pods-Runner.profile.xcconfig */,
42843373A331898F762AE9FA /* Pods-RunnerTests.debug.xcconfig */,
7F49A581B2FF1737F1557371 /* Pods-RunnerTests.release.xcconfig */,
BBBF70DBDF6B0B4E82F4E8A2 /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
7618EFBF5D56DEA9B5BC6858 /* Pods_Runner.framework */,
12AE3247A53A0A6C66A10AB1 /* Pods_RunnerTests.framework */,
60A2C50BFA238B286DE35299 /* Pods_Runner.framework */,
AC81A8405E01D3966B98ABCC /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -214,7 +215,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
1AB4F7EB04EA489C9D3447CC /* [CP] Check Pods Manifest.lock */,
6E908EFB6D636F7BD9B901EE /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
@ -233,13 +234,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
B609B32CB0330D926A7BF173 /* [CP] Check Pods Manifest.lock */,
C410E8FE456DC70BDCFC7228 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
C553F2A78B180316DDD47899 /* [CP] Embed Pods Frameworks */,
CBC02CA05960FFA536B17BBB /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -257,8 +258,9 @@
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
@ -321,28 +323,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
1AB4F7EB04EA489C9D3447CC /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -381,7 +361,29 @@
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
B609B32CB0330D926A7BF173 /* [CP] Check Pods Manifest.lock */ = {
6E908EFB6D636F7BD9B901EE /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
C410E8FE456DC70BDCFC7228 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -403,7 +405,7 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
C553F2A78B180316DDD47899 /* [CP] Embed Pods Frameworks */ = {
CBC02CA05960FFA536B17BBB /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -471,13 +473,13 @@
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 42843373A331898F762AE9FA /* Pods-RunnerTests.debug.xcconfig */;
baseConfigurationReference = 7BDC5BC63E0570F1277EC2A6 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bluetooth_low_energy_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bluetooth_low_energy_example";
@ -486,13 +488,13 @@
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7F49A581B2FF1737F1557371 /* Pods-RunnerTests.release.xcconfig */;
baseConfigurationReference = AEF60039D20E8A370442E404 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bluetooth_low_energy_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bluetooth_low_energy_example";
@ -501,13 +503,13 @@
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = BBBF70DBDF6B0B4E82F4E8A2 /* Pods-RunnerTests.profile.xcconfig */;
baseConfigurationReference = ABB87EDC364CAC4062F6DE96 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bluetooth_low_energy_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bluetooth_low_energy_example";
@ -519,6 +521,7 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@ -542,9 +545,11 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -592,6 +597,7 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@ -615,9 +621,11 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@ -645,6 +653,7 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@ -668,9 +677,11 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -8,7 +8,7 @@
PRODUCT_NAME = bluetooth_low_energy_example
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample
PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2023 dev.yanshouwang. All rights reserved.
PRODUCT_COPYRIGHT = Copyright © 2024 dev.hebei. All rights reserved.

View File

@ -22,8 +22,6 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Hello Bluetooth LowEnergy!</string>
<key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key>

View File

@ -1,5 +1,5 @@
import FlutterMacOS
import Cocoa
import FlutterMacOS
import XCTest
@testable import bluetooth_low_energy

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.5.0"
async:
dependency: transitive
description:
@ -23,47 +23,47 @@ packages:
path: ".."
relative: true
source: path
version: "5.0.7"
version: "6.0.0"
bluetooth_low_energy_android:
dependency: transitive
description:
name: bluetooth_low_energy_android
sha256: "281ca129677e10cd22d4763bbb7a9fb53d801cf25fcc54815e4023073e35f22c"
sha256: "39284bb33a10cc3bc126e18968865f47fc0155495a7473afc705eb4be8b07728"
url: "https://pub.dev"
source: hosted
version: "5.0.5"
version: "6.0.0"
bluetooth_low_energy_darwin:
dependency: transitive
description:
name: bluetooth_low_energy_darwin
sha256: d8c37f27c48ad523e5c32d1002d8e67570b1f4e6d69c54e09bca152a07f6ab3f
sha256: "849ba53f7d34845ad7491cd9cdb3784301aa54fe682c91cab804ed55cfd259d5"
url: "https://pub.dev"
source: hosted
version: "5.0.5"
version: "6.0.0"
bluetooth_low_energy_linux:
dependency: transitive
description:
name: bluetooth_low_energy_linux
sha256: "2809830a8b67eac5d53fdc6e907aa878ec61884d6e6a42da2b79b826410c8327"
sha256: "4d1aaaede517f95320dcf9ad271091ab42c4ad8ba5bfa0e822744d850dcf0048"
url: "https://pub.dev"
source: hosted
version: "5.0.2"
version: "6.0.0"
bluetooth_low_energy_platform_interface:
dependency: transitive
description:
name: bluetooth_low_energy_platform_interface
sha256: "5af74eb8f97a896dfdbcff2da284d4fe5b4e2e49ebb2f46f826ac1810f66e4d7"
sha256: bc2e8d97c141653e5747bcb3cdc9fe956541b6ecc6e5f158b99a2f3abc2d946a
url: "https://pub.dev"
source: hosted
version: "5.0.2"
version: "6.0.0"
bluetooth_low_energy_windows:
dependency: transitive
description:
name: bluetooth_low_energy_windows
sha256: "5a0b22003d14dc84f3f16e73a77f2645df034badb9003f368e6f9bc02e8b249a"
sha256: "4904530cb3e7e1dd7a66919b4c926f8a03ed9924c3c2ce068aef7e0e10ced555"
url: "https://pub.dev"
source: hosted
version: "5.0.3"
version: "6.0.0"
bluez:
dependency: transitive
description:
@ -96,8 +96,16 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
clover:
dependency: "direct main"
description:
name: clover
sha256: eba28e69b32f174a51c093eef098cf5ae646470322727081d5d3d8f66c786487
url: "https://pub.dev"
source: hosted
version: "3.0.0"
collection:
dependency: transitive
dependency: "direct main"
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
@ -116,10 +124,10 @@ packages:
dependency: "direct main"
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.6"
version: "1.0.8"
dbus:
dependency: transitive
description:
@ -166,7 +174,15 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
flutter_simple_treeview:
dependency: "direct main"
description:
name: flutter_simple_treeview
sha256: ad4978d2668dd078d3a09966832da111bef9102dd636e572c50c80133b7ff4d9
url: "https://pub.dev"
source: hosted
version: "3.0.2"
@ -175,11 +191,32 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: "6ad5662b014c06c20fa46ab78715c96b2222a7fe4f87bf77e0289592c2539e86"
url: "https://pub.dev"
source: hosted
version: "14.1.3"
hybrid_logging:
dependency: "direct main"
description:
name: hybrid_logging
sha256: "54248d52ce68c14702a42fbc4083bac5c6be30f6afad8a41be4bbadd197b8af5"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
integration_test:
dependency: "direct dev"
description: flutter
@ -197,44 +234,36 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
log_service:
dependency: transitive
description:
name: log_service
sha256: "21124936899e227d1779268077921d46d57456e2592d1562e455be273594e2e4"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "4.0.0"
logging:
dependency: transitive
dependency: "direct main"
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
@ -257,14 +286,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.8.0"
material_symbols_icons:
dependency: "direct main"
description:
name: material_symbols_icons
sha256: b2d3cbc3c42b8a217715b0d97ff03aebb14b2b4592875736e5599c603fb2db7e
url: "https://pub.dev"
source: hosted
version: "4.2758.0"
meta:
dependency: transitive
description:
name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.12.0"
path:
dependency: transitive
description:
@ -362,10 +399,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
version: "0.7.0"
typed_data:
dependency: transitive
description:
@ -386,10 +423,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "13.0.0"
version: "14.2.1"
webdriver:
dependency: transitive
description:
@ -407,5 +444,5 @@ packages:
source: hosted
version: "6.5.0"
sdks:
dart: ">=3.3.0-279.1.beta <4.0.0"
flutter: ">=3.0.0"
dart: ">=3.4.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

View File

@ -1,11 +1,11 @@
name: bluetooth_low_energy_example
description: Demonstrates how to use the bluetooth_low_energy plugin.
description: "Demonstrates how to use the bluetooth_low_energy plugin."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: '>=3.0.0 <4.0.0'
sdk: '>=3.4.0 <4.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
@ -27,9 +27,16 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
convert: ^3.1.1
cupertino_icons: ^1.0.6
clover: ^3.0.0
go_router: ^14.1.3
logging: ^1.2.0
hybrid_logging: ^1.0.0
intl: ^0.19.0
collection: ^1.18.0
convert: ^3.1.1
flutter_simple_treeview: ^3.0.2
material_symbols_icons: ^4.2744.0
dev_dependencies:
integration_test:
@ -42,7 +49,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^3.0.0
flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View File

@ -5,23 +5,4 @@
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:bluetooth_low_energy_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text &&
widget.data!.startsWith('Running on:'),
),
findsOneWidget,
);
});
}
void main() {}

View File

@ -89,6 +89,12 @@ if(PLUGIN_BUNDLED_LIBRARIES)
COMPONENT Runtime)
endif()
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")

View File

@ -6,9 +6,9 @@
#include "generated_plugin_registrant.h"
#include <bluetooth_low_energy_windows/bluetooth_low_energy_windows_c_api.h>
#include <bluetooth_low_energy_windows/bluetooth_low_energy_windows_plugin_c_api.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
BluetoothLowEnergyWindowsCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BluetoothLowEnergyWindowsCApi"));
BluetoothLowEnergyWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BluetoothLowEnergyWindowsPluginCApi"));
}

View File

@ -89,11 +89,11 @@ BEGIN
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "dev.yanshouwang" "\0"
VALUE "CompanyName", "dev.hebei" "\0"
VALUE "FileDescription", "bluetooth_low_energy_example" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "bluetooth_low_energy_example" "\0"
VALUE "LegalCopyright", "Copyright (C) 2023 dev.yanshouwang. All rights reserved." "\0"
VALUE "LegalCopyright", "Copyright (C) 2024 dev.hebei. All rights reserved." "\0"
VALUE "OriginalFilename", "bluetooth_low_energy_example.exe" "\0"
VALUE "ProductName", "bluetooth_low_energy_example" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"

View File

@ -45,13 +45,13 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
}
int target_length = ::WideCharToMultiByte(
unsigned int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr)
-1; // remove the trailing null character
int input_length = (int)wcslen(utf16_string);
std::string utf8_string;
if (target_length <= 0 || target_length > utf8_string.max_size()) {
if (target_length == 0 || target_length > utf8_string.max_size()) {
return utf8_string;
}
utf8_string.resize(target_length);

View File

@ -4,11 +4,10 @@ library bluetooth_low_energy;
export 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart'
hide
MyBluetoothLowEnergyPeer,
MyCentral,
MyPeripheral,
MyGattAttribute,
MyGattAttributeUint8List,
MyGattDescriptor,
MyGattCharacteristic,
MyGattService;
PlatformBluetoothLowEnergyManager,
PlatformCentralManager,
PlatformPeripheralManager,
MutableGATTCharacteristic,
ImmutableGATTCharacteristic,
MutableGATTDescriptor,
ImmutableGATTDescriptor;

View File

@ -1,25 +1,34 @@
name: bluetooth_low_energy
description: A Flutter plugin for controlling the bluetooth low energy, supports central and peripheral apis.
version: 5.0.7
description: "A Flutter plugin for controlling the bluetooth low energy, supports central and peripheral roles."
version: 6.0.0
homepage: https://github.com/yanshouwang/bluetooth_low_energy
repository: https://github.com/yanshouwang/bluetooth_low_energy
issue_tracker: https://github.com/yanshouwang/bluetooth_low_energy/issues
topics:
- bluetooth
- bluetooth-low-energy
- ble
funding:
- https://paypal.me/yanshouwang5112
- https://afdian.net/a/yanshouwang
environment:
sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.0.0"
sdk: '>=3.0.0 <4.0.0'
flutter: '>=3.7.0'
dependencies:
flutter:
sdk: flutter
bluetooth_low_energy_platform_interface: ^5.0.2
bluetooth_low_energy_android: ^5.0.5
bluetooth_low_energy_darwin: ^5.0.5
bluetooth_low_energy_windows: ^5.0.3
bluetooth_low_energy_linux: ^5.0.2
bluetooth_low_energy_platform_interface: ^6.0.0
bluetooth_low_energy_android: ^6.0.0
bluetooth_low_energy_darwin: ^6.0.0
bluetooth_low_energy_windows: ^6.0.0
bluetooth_low_energy_linux: ^6.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter_lints: ^4.0.0
flutter:
plugin:

View File

@ -0,0 +1 @@
void main() {}

View File

@ -26,5 +26,4 @@ migrate_working_dir/
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

View File

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: "efbf63d9c66b9f6ec30e9ad4611189aa80003d31"
revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1"
channel: "stable"
project_type: plugin
@ -13,11 +13,11 @@ project_type: plugin
migration:
platforms:
- platform: root
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
- platform: android
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
# User provided section

View File

@ -1,3 +1,60 @@
## 6.0.0
* Add `serviceUUIDs` argument to `CentralManager#startDiscovery` method.
* Add `CentralManager#requestMTU` method.
* Add `CentralManager#retrieveConnectedPeripherals` method.
* Move `BluetoothLowEnergyManager#getState` to `BluetoothLowEnergyManager#state`.
* Move `CentralManger.instance` to factory constructor.
* Move `PeripheralManager.instance` to factory constructor.
* Remove `BluetoothLowEnergyManager#setUp` method.
* Add `CentralManager#mtuChanged` event.
* Add `PeripheralManager#mtuChanged` event.
* Add `BluetoothLowEnergyManager.authorize()` method.
* Add `BluetoothLowEnergyManager.showAppSettings()` method.
* Rewrite example with MVVM.
* Fix known issues.
## 6.0.0-dev.6
* Fix known issues.
## 6.0.0-dev.5
* Rewrite example with MVVM.
* Fix known issues.
## 6.0.0-dev.4
* Implement new APIs.
## 6.0.0-dev.3
* Move organizaioin.
## 6.0.0-dev.2
* Implement new APIs.
## 6.0.0-dev.1
* Add `CentralManager#mtuChanged` event.
* Add `PeripheralManager#mtuChanged` event.
* Add `BluetoothLowEnergyManager.authorize()` method.
* Add `BluetoothLowEnergyManager.showAppSettings()` method.
* Add modifiers to all classes.
* Use new capitalization rules.
* Make it possible to change the `logLevel` before `initialize()`.
## 6.0.0-dev.0
* Add `serviceUUIDs` argument to `CentralManager#startDiscovery` method.
* Add `CentralManager#requestMTU` method.
* Add `CentralManager#retrieveConnectedPeripherals` method.
* Move `BluetoothLowEnergyManager#getState` to `BluetoothLowEnergyManager#state`.
* Move `CentralManger.instance` to factory constructor.
* Move `PeripheralManager.instance` to factory constructor.
* Remove `BluetoothLowEnergyManager#setUp` method.
## 5.0.5
* Fix the issue that [Advertisement resolve failed with `NullPointerException`](https://github.com/yanshouwang/bluetooth_low_energy/issues/59)

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 yanshouwang
Copyright (c) 2024 hebei.dev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,16 +1,16 @@
group 'dev.yanshouwang.bluetooth_low_energy_android'
version '1.0-SNAPSHOT'
group = "dev.hebei.bluetooth_low_energy_android"
version = "1.0-SNAPSHOT"
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = "1.7.10"
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath("com.android.tools.build:gradle:7.3.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
}
}
@ -21,37 +21,37 @@ allprojects {
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
if (project.android.hasProperty("namespace")) {
namespace 'dev.yanshouwang.bluetooth_low_energy_android'
namespace = "dev.hebei.bluetooth_low_energy_android"
}
compileSdkVersion 33
compileSdk = 34
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = "1.8"
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
test.java.srcDirs += 'src/test/kotlin'
main.java.srcDirs += "src/main/kotlin"
test.java.srcDirs += "src/test/kotlin"
}
defaultConfig {
minSdkVersion 21
minSdk = 21
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.yanshouwang.bluetooth_low_energy_android">
package="dev.hebei.bluetooth_low_energy_android">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

View File

@ -1,11 +1,11 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
/** BluetoothLowEnergyAndroid */
class BluetoothLowEnergyAndroid : FlutterPlugin, ActivityAware {
/** BluetoothLowEnergyAndroidPlugin */
class BluetoothLowEnergyAndroidPlugin : FlutterPlugin, ActivityAware {
private lateinit var mCentralManager: MyCentralManager
private lateinit var mPeripheralManager: MyPeripheralManager
@ -14,14 +14,14 @@ class BluetoothLowEnergyAndroid : FlutterPlugin, ActivityAware {
val binaryMessenger = binding.binaryMessenger
mCentralManager = MyCentralManager(context, binaryMessenger)
mPeripheralManager = MyPeripheralManager(context, binaryMessenger)
MyCentralManagerHostApi.setUp(binaryMessenger, mCentralManager)
MyPeripheralManagerHostApi.setUp(binaryMessenger, mPeripheralManager)
MyCentralManagerHostAPI.setUp(binaryMessenger, mCentralManager)
MyPeripheralManagerHostAPI.setUp(binaryMessenger, mPeripheralManager)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
val binaryMessenger = binding.binaryMessenger
MyCentralManagerHostApi.setUp(binaryMessenger, null)
MyPeripheralManagerHostApi.setUp(binaryMessenger, null)
MyCentralManagerHostAPI.setUp(binaryMessenger, null)
MyPeripheralManagerHostAPI.setUp(binaryMessenger, null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {

View File

@ -0,0 +1,258 @@
package dev.hebei.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.le.AdvertiseData
import android.bluetooth.le.ScanResult
import android.os.Build
import android.os.ParcelUuid
import android.util.SparseArray
import java.util.UUID
//region ToObject
fun MyGATTCharacteristicWriteTypeArgs.toType(): Int {
return when (this) {
MyGATTCharacteristicWriteTypeArgs.WITH_RESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
MyGATTCharacteristicWriteTypeArgs.WITHOUT_RESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
}
}
fun MyGATTStatusArgs.toStatus(): Int {
return when (this) {
MyGATTStatusArgs.SUCCESS -> BluetoothGatt.GATT_SUCCESS
MyGATTStatusArgs.READ_NOT_PERMITTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
MyGATTStatusArgs.WRITE_NOT_PERMITTED -> BluetoothGatt.GATT_WRITE_NOT_PERMITTED
MyGATTStatusArgs.INSUFFICIENT_AUTHENTICATION -> BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION
MyGATTStatusArgs.REQUEST_NOT_SUPPORTED -> BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED
MyGATTStatusArgs.INSUFFICIENT_ENCRYPTION -> BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION
MyGATTStatusArgs.INVALID_OFFSET -> BluetoothGatt.GATT_INVALID_OFFSET
MyGATTStatusArgs.INSUFFICIENT_AUTHORIZATION -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION
else BluetoothGatt.GATT_FAILURE
MyGATTStatusArgs.INVALID_ATTRIBUTE_LENGTH -> BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH
MyGATTStatusArgs.CONNECTION_CONGESTED -> BluetoothGatt.GATT_CONNECTION_CONGESTED
MyGATTStatusArgs.FAILURE -> BluetoothGatt.GATT_FAILURE
}
}
fun MyAdvertisementArgs.toAdvertiseData(): AdvertiseData {
val advertiseDataBuilder = AdvertiseData.Builder()
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)
}
for (args in manufacturerSpecificDataArgs) {
val itemArgs = args as MyManufacturerSpecificDataArgs
val manufacturerId = itemArgs.idArgs.toInt()
val manufacturerSpecificData = itemArgs.dataArgs
advertiseDataBuilder.addManufacturerData(manufacturerId, manufacturerSpecificData)
}
return advertiseDataBuilder.build()
}
fun MyAdvertisementArgs.toScanResponse(): AdvertiseData {
val advertiseDataBuilder = AdvertiseData.Builder()
val includeDeviceName = nameArgs != null
advertiseDataBuilder.setIncludeDeviceName(includeDeviceName)
return advertiseDataBuilder.build()
}
fun MyMutableGATTDescriptorArgs.toDescriptor(): BluetoothGattDescriptor {
val uuid = UUID.fromString(uuidArgs)
val permissions = BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE
return BluetoothGattDescriptor(uuid, permissions)
}
fun MyMutableGATTCharacteristicArgs.toCharacteristic(): BluetoothGattCharacteristic {
val uuid = UUID.fromString(uuidArgs)
val properties = getProperties()
val permissions = getPermissions()
return BluetoothGattCharacteristic(uuid, properties, permissions)
}
fun MyMutableGATTCharacteristicArgs.getProperties(): Int {
val propertiesArgs = propertyNumbersArgs.requireNoNulls().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.WRITE_WITHOUT_RESPONSE)
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
}
fun MyMutableGATTCharacteristicArgs.getPermissions(): Int {
val propertiesArgs = propertyNumbersArgs.requireNoNulls().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.WRITE_WITHOUT_RESPONSE)
var permissions = 0
if (read) permissions = permissions or BluetoothGattCharacteristic.PERMISSION_READ
if (write || writeWithoutResponse) permissions = permissions or BluetoothGattCharacteristic.PERMISSION_WRITE
return permissions
}
fun MyMutableGATTServiceArgs.toService(): BluetoothGattService {
val uuid = UUID.fromString(uuidArgs)
val serviceType = if (isPrimaryArgs) BluetoothGattService.SERVICE_TYPE_PRIMARY
else BluetoothGattService.SERVICE_TYPE_SECONDARY
return BluetoothGattService(uuid, serviceType)
} //endregion
//region ToArgs
fun Int.toBluetoothLowEnergyStateArgs(): MyBluetoothLowEnergyStateArgs {
return when (this) {
BluetoothAdapter.STATE_OFF -> MyBluetoothLowEnergyStateArgs.OFF
BluetoothAdapter.STATE_TURNING_ON -> MyBluetoothLowEnergyStateArgs.TURNING_ON
BluetoothAdapter.STATE_ON -> MyBluetoothLowEnergyStateArgs.ON
BluetoothAdapter.STATE_TURNING_OFF -> MyBluetoothLowEnergyStateArgs.TURNING_OFF
else -> MyBluetoothLowEnergyStateArgs.UNKNOWN
}
}
fun Int.toConnectionStateArgs(): MyConnectionStateArgs {
return when (this) {
BluetoothProfile.STATE_DISCONNECTED -> MyConnectionStateArgs.DISCONNECTED
BluetoothProfile.STATE_CONNECTING -> MyConnectionStateArgs.CONNECTING
BluetoothProfile.STATE_CONNECTED -> MyConnectionStateArgs.CONNECTED
BluetoothProfile.STATE_DISCONNECTING -> MyConnectionStateArgs.DISCONNECTING
else -> throw IllegalArgumentException()
}
}
fun SparseArray<ByteArray>.toManufacturerSpecificDataArgs(): List<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
}
fun ScanResult.toAdvertisementArgs(): MyAdvertisementArgs {
val record = scanRecord
return if (record == null) {
val nameArgs = null
val serviceUUIDsArgs = emptyList<String?>()
val serviceDataArgs = emptyMap<String?, ByteArray>()
val manufacturerSpecificDataArgs = emptyList<MyManufacturerSpecificDataArgs>()
MyAdvertisementArgs(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() ?: emptyArray()
val serviceDataArgs = mapOf<String?, ByteArray?>(*pairs)
val manufacturerSpecificDataArgs = record.manufacturerSpecificData?.toManufacturerSpecificDataArgs() ?: emptyList()
MyAdvertisementArgs(nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs)
}
}
fun BluetoothDevice.toCentralArgs(): MyCentralArgs {
val addressArgs = address
return MyCentralArgs(addressArgs)
}
fun BluetoothDevice.toPeripheralArgs(): MyPeripheralArgs {
val addressArgs = address
return MyPeripheralArgs(addressArgs)
}
fun BluetoothGattDescriptor.toArgs(): MyGATTDescriptorArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
return MyGATTDescriptorArgs(hashCodeArgs, uuidArgs)
}
fun BluetoothGattCharacteristic.getPropertyNumbersArgs(): List<Long> {
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.WRITE_WITHOUT_RESPONSE.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 BluetoothGattCharacteristic.toArgs(): MyGATTCharacteristicArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
val propertyNumbersArgs = getPropertyNumbersArgs()
val descriptorsArgs = descriptors.map { it.toArgs() }
return MyGATTCharacteristicArgs(hashCodeArgs, uuidArgs, propertyNumbersArgs, descriptorsArgs)
}
fun BluetoothGattService.toArgs(): MyGATTServiceArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = uuid.toString()
val isPrimaryArgs = type == BluetoothGattService.SERVICE_TYPE_PRIMARY
val includedServicesArgs = includedServices.map { it.toArgs() }
val characteristicsArgs = characteristics.map { it.toArgs() }
return MyGATTServiceArgs(hashCodeArgs, uuidArgs, isPrimaryArgs, includedServicesArgs, characteristicsArgs)
} //endregion
val Any.hashCode get() = this.hashCode()
val Int.args get() = this.toLong()
//val Any.TAG get() = this::class.java.simpleName as String
//
//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()
// }

View File

@ -0,0 +1,16 @@
package dev.hebei.bluetooth_low_energy_android
import android.content.Intent
import io.flutter.plugin.common.PluginRegistry
class MyActivityResultListener(manager: MyBluetoothLowEnergyManager) : PluginRegistry.ActivityResultListener {
private val mManager: MyBluetoothLowEnergyManager
init {
mManager = manager
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
return mManager.onActivityResult(requestCode, resultCode, data)
}
}

View File

@ -1,4 +1,4 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseSettings

View File

@ -1,4 +1,4 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
@ -7,8 +7,7 @@ import android.bluetooth.BluetoothGattDescriptor
import android.os.Build
import java.util.concurrent.Executor
class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
BluetoothGattCallback() {
class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) : BluetoothGattCallback() {
private val mManager: MyCentralManager
private val mExecutor: Executor
@ -45,12 +44,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
status: Int
) {
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
super.onCharacteristicRead(gatt, characteristic, value, status)
mExecutor.execute {
mManager.onCharacteristicRead(gatt, characteristic, status, value)
@ -58,9 +52,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
// TODO: remove this override when minSdkVersion >= 33
override fun onCharacteristicRead(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
) {
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
super.onCharacteristicRead(gatt, characteristic, status)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return
@ -71,18 +63,14 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
) {
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
super.onCharacteristicWrite(gatt, characteristic, status)
mExecutor.execute {
mManager.onCharacteristicWrite(gatt, characteristic, status)
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray
) {
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
super.onCharacteristicChanged(gatt, characteristic, value)
mExecutor.execute {
mManager.onCharacteristicChanged(gatt, characteristic, value)
@ -90,9 +78,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
// TODO: remove this override when minSdkVersion >= 33
override fun onCharacteristicChanged(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic
) {
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
super.onCharacteristicChanged(gatt, characteristic)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return
@ -103,9 +89,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
}
override fun onDescriptorRead(
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray
) {
override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
super.onDescriptorRead(gatt, descriptor, status, value)
mExecutor.execute {
mManager.onDescriptorRead(gatt, descriptor, status, value)
@ -113,9 +97,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
// TODO: remove this override when minSdkVersion >= 33
override fun onDescriptorRead(
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
) {
override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
super.onDescriptorRead(gatt, descriptor, status)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return
@ -126,9 +108,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
}
override fun onDescriptorWrite(
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
) {
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
super.onDescriptorWrite(gatt, descriptor, status)
mExecutor.execute {
mManager.onDescriptorWrite(gatt, descriptor, status)

View File

@ -1,4 +1,4 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic
@ -7,14 +7,13 @@ import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService
import java.util.concurrent.Executor
class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Executor) :
BluetoothGattServerCallback() {
class MyBluetoothGattServerCallback(manager: MyPeripheralManager, mExecutor: Executor) : BluetoothGattServerCallback() {
private val mManager: MyPeripheralManager
private val mExecutor: Executor
init {
mManager = manager
mExecutor = executor
this.mExecutor = mExecutor
}
override fun onServiceAdded(status: Int, service: BluetoothGattService) {
@ -38,41 +37,17 @@ class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Exec
}
}
override fun onCharacteristicReadRequest(
device: BluetoothDevice,
requestId: Int,
offset: Int,
characteristic: BluetoothGattCharacteristic
) {
override fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic)
mExecutor.execute {
mManager.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
)
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)
mExecutor.execute {
mManager.onCharacteristicWriteRequest(
device, requestId, characteristic, preparedWrite, responseNeeded, offset, value
)
}
}
override fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
super.onExecuteWrite(device, requestId, execute)
mExecutor.execute {
mManager.onExecuteWrite(device, requestId, execute)
mManager.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value)
}
}
@ -83,31 +58,24 @@ class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Exec
}
}
override fun onDescriptorReadRequest(
device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor
) {
override fun onDescriptorReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor)
mExecutor.execute {
mManager.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
)
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)
mExecutor.execute {
mManager.onDescriptorWriteRequest(
device, requestId, descriptor, preparedWrite, responseNeeded, offset, value
)
mManager.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value)
}
}
override fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
super.onExecuteWrite(device, requestId, execute)
mExecutor.execute {
mManager.onExecuteWrite(device, requestId, execute)
}
}
}

View File

@ -0,0 +1,46 @@
package dev.hebei.bluetooth_low_energy_android
import android.app.Activity
import android.bluetooth.BluetoothAdapter
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.PluginRegistry
abstract class MyBluetoothLowEnergyManager(val context: Context) {
companion object {
const val AUTHORIZE_CODE = 0x00
const val SHOW_APP_SETTINGS_CODE = 0x01
}
private val mBroadcastReceiver: BroadcastReceiver by lazy { MyBroadcastReceiver(this) }
private val mRequestPermissionsResultListener: PluginRegistry.RequestPermissionsResultListener by lazy { MyRequestPermissionResultListener(this) }
private val mActivityResultListener: PluginRegistry.ActivityResultListener by lazy { MyActivityResultListener(this) }
private lateinit var mBinding: ActivityPluginBinding
init {
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
context.registerReceiver(mBroadcastReceiver, filter)
}
val activity: Activity get() = mBinding.activity
fun onAttachedToActivity(binding: ActivityPluginBinding) {
binding.addRequestPermissionsResultListener(mRequestPermissionsResultListener)
binding.addActivityResultListener(mActivityResultListener)
mBinding = binding
}
fun onDetachedFromActivity() {
mBinding.removeRequestPermissionsResultListener(mRequestPermissionsResultListener)
mBinding.removeActivityResultListener(mActivityResultListener)
}
abstract fun onReceive(context: Context, intent: Intent)
abstract fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean
abstract fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean
}

View File

@ -1,4 +1,4 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.content.BroadcastReceiver
import android.content.Context

View File

@ -1,10 +1,13 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
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.BluetoothLeScanner
@ -13,47 +16,46 @@ 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.net.Uri
import android.os.Build
import android.os.ParcelUuid
import android.provider.Settings
import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import io.flutter.plugin.common.BinaryMessenger
import java.util.concurrent.Executor
class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
MyBluetoothLowEnergyManager(context), MyCentralManagerHostApi {
companion object {
const val REQUEST_CODE = 443
}
class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) : MyBluetoothLowEnergyManager(context), MyCentralManagerHostAPI {
private val mAPI: MyCentralManagerFlutterAPI
private val mContext: Context
private val mApi: MyCentralManagerFlutterApi
private val mScanCallback: ScanCallback by lazy {
MyScanCallback(this)
}
private val mBluetoothGattCallback: BluetoothGattCallback by lazy {
MyBluetoothGattCallback(this, executor)
}
private val mScanCallback: ScanCallback by lazy { MyScanCallback(this) }
private val mBluetoothGattCallback: BluetoothGattCallback by lazy { MyBluetoothGattCallback(this, executor) }
private var mDiscovering: Boolean
private val mDevices: MutableMap<String, BluetoothDevice>
private val mGATTs: MutableMap<String, BluetoothGatt>
private val mCharacteristics: MutableMap<String, Map<Long, BluetoothGattCharacteristic>>
private val mDescriptors: MutableMap<String, Map<Long, BluetoothGattDescriptor>>
private val mCharacteristics: MutableMap<String, MutableMap<Long, BluetoothGattCharacteristic>>
private val mDescriptors: MutableMap<String, MutableMap<Long, BluetoothGattDescriptor>>
private var mSetUpCallback: ((Result<Unit>) -> Unit)?
private var mAuthorizeCallback: ((Result<Boolean>) -> Unit)?
private var mShowAppSettingsCallback: ((Result<Unit>) -> Unit)?
private var mStartDiscoveryCallback: ((Result<Unit>) -> Unit)?
private val mConnectCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
private val mDisconnectCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
private val mRequestMtuCallbacks: MutableMap<String, (Result<Long>) -> Unit>
private val mReadRssiCallbacks: MutableMap<String, (Result<Long>) -> Unit>
private val mDiscoverServicesCallbacks: MutableMap<String, (Result<List<MyGattServiceArgs>>) -> Unit>
private val mDiscoverServicesCallbacks: MutableMap<String, (Result<List<MyGATTServiceArgs>>) -> Unit>
private val mReadCharacteristicCallbacks: MutableMap<String, MutableMap<Long, (Result<ByteArray>) -> Unit>>
private val mWriteCharacteristicCallbacks: MutableMap<String, MutableMap<Long, (Result<Unit>) -> Unit>>
private val mReadDescriptorCallbacks: MutableMap<String, MutableMap<Long, (Result<ByteArray>) -> Unit>>
private val mWriteDescriptorCallbacks: MutableMap<String, MutableMap<Long, (Result<Unit>) -> Unit>>
init {
mContext = context
mApi = MyCentralManagerFlutterApi(binaryMessenger)
mAPI = MyCentralManagerFlutterAPI(binaryMessenger)
mDiscovering = false
@ -62,7 +64,8 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
mCharacteristics = mutableMapOf()
mDescriptors = mutableMapOf()
mSetUpCallback = null
mAuthorizeCallback = null
mShowAppSettingsCallback = null
mStartDiscoveryCallback = null
mConnectCallbacks = mutableMapOf()
mDisconnectCallbacks = mutableMapOf()
@ -75,56 +78,91 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
mWriteDescriptorCallbacks = mutableMapOf()
}
private val mScanner: BluetoothLeScanner get() = adapter.bluetoothLeScanner
override val permissions: Array<String>
private val permissions: Array<String>
get() = 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
)
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
)
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
}
private val manager get() = ContextCompat.getSystemService(context, BluetoothManager::class.java) as BluetoothManager
private val adapter get() = manager.adapter as BluetoothAdapter
private val scanner: BluetoothLeScanner get() = adapter.bluetoothLeScanner
private val executor get() = ContextCompat.getMainExecutor(context) as Executor
override fun initialize(): MyCentralManagerArgs {
if (mDiscovering) {
stopDiscovery()
}
override val requestCode: Int
get() = REQUEST_CODE
override fun setUp(callback: (Result<Unit>) -> Unit) {
try {
mClearState()
val stateArgs = if (hasFeature) {
val granted = checkPermissions()
if (granted) {
registerReceiver()
adapter.state.toBluetoothLowEnergyStateArgs()
} else {
requestPermissions()
mSetUpCallback = callback
return
for (gatt in mGATTs.values) {
gatt.disconnect()
}
mDevices.clear()
mGATTs.clear()
mCharacteristics.clear()
mDescriptors.clear()
mAuthorizeCallback = null
mShowAppSettingsCallback = null
mStartDiscoveryCallback = null
mConnectCallbacks.clear()
mDisconnectCallbacks.clear()
mRequestMtuCallbacks.clear()
mReadRssiCallbacks.clear()
mDiscoverServicesCallbacks.clear()
mReadCharacteristicCallbacks.clear()
mWriteCharacteristicCallbacks.clear()
mReadDescriptorCallbacks.clear()
mWriteDescriptorCallbacks.clear()
val enableNotificationValue = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
val enableIndicationValue = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
val disableNotificationValue = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
return MyCentralManagerArgs(enableNotificationValue, enableIndicationValue, disableNotificationValue)
}
override fun getState(): MyBluetoothLowEnergyStateArgs {
val supported = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
return if (supported) {
val authorized = permissions.all { permission -> ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED }
return if (authorized) adapter.state.toBluetoothLowEnergyStateArgs()
else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
} else MyBluetoothLowEnergyStateArgs.UNSUPPORTED
mOnStateChanged(stateArgs)
callback(Result.success(Unit))
}
override fun authorize(callback: (Result<Boolean>) -> Unit) {
try {
ActivityCompat.requestPermissions(activity, permissions, AUTHORIZE_CODE)
mAuthorizeCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun startDiscovery(callback: (Result<Unit>) -> Unit) {
override fun showAppSettings(callback: (Result<Unit>) -> Unit) {
try {
val filters = emptyList<ScanFilter>()
val settings =
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
mScanner.startScan(filters, settings, mScanCallback)
executor.execute {
onScanSucceed()
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.fromParts("package", activity.packageName, null)
val options = ActivityOptionsCompat.makeBasic().toBundle()
ActivityCompat.startActivityForResult(activity, intent, SHOW_APP_SETTINGS_CODE, options)
mShowAppSettingsCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun startDiscovery(serviceUUIDsArgs: List<String>, callback: (Result<Unit>) -> Unit) {
try {
val filters = mutableListOf<ScanFilter>()
for (serviceUuidArgs in serviceUUIDsArgs) {
val serviceUUID = ParcelUuid.fromString(serviceUuidArgs)
val filter = ScanFilter.Builder().setServiceUuid(serviceUUID).build()
filters.add(filter)
}
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
scanner.startScan(filters, settings, mScanCallback)
executor.execute { onScanSucceeded() }
mStartDiscoveryCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
@ -132,20 +170,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
override fun stopDiscovery() {
mScanner.stopScan(mScanCallback)
scanner.stopScan(mScanCallback)
mDiscovering = false
}
override fun connect(addressArgs: String, callback: (Result<Unit>) -> Unit) {
try {
val device = mDevices[addressArgs] as BluetoothDevice
val autoConnect = false
// Add to bluetoothGATTs cache.
val device = mDevices[addressArgs] ?: throw IllegalArgumentException()
val autoConnect = false // Add to bluetoothGATTs cache.
mGATTs[addressArgs] = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val transport = BluetoothDevice.TRANSPORT_LE
device.connectGatt(mContext, autoConnect, mBluetoothGattCallback, transport)
device.connectGatt(context, autoConnect, mBluetoothGattCallback, transport)
} else {
device.connectGatt(mContext, autoConnect, mBluetoothGattCallback)
device.connectGatt(context, autoConnect, mBluetoothGattCallback)
}
mConnectCallbacks[addressArgs] = callback
} catch (e: Throwable) {
@ -155,7 +192,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
override fun disconnect(addressArgs: String, callback: (Result<Unit>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
gatt.disconnect()
mDisconnectCallbacks[addressArgs] = callback
} catch (e: Throwable) {
@ -163,9 +200,21 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun retrieveConnectedPeripherals(): List<MyPeripheralArgs> {
// The `BluetoothProfile.GATT` and `BluetoothProfile.GATT_SERVER` return same devices.
val devices = manager.getConnectedDevices(BluetoothProfile.GATT)
val peripheralsArgs = devices.map { device ->
val peripheralArgs = device.toPeripheralArgs()
val addressArgs = peripheralArgs.addressArgs
mDevices[addressArgs] = device
return@map peripheralArgs
}
return peripheralsArgs
}
override fun requestMTU(addressArgs: String, mtuArgs: Long, callback: (Result<Long>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val mtu = mtuArgs.toInt()
val requesting = gatt.requestMtu(mtu)
if (!requesting) {
@ -179,7 +228,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
override fun readRSSI(addressArgs: String, callback: (Result<Long>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val reading = gatt.readRemoteRssi()
if (!reading) {
throw IllegalStateException()
@ -190,11 +239,9 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun discoverServices(
addressArgs: String, callback: (Result<List<MyGattServiceArgs>>) -> Unit
) {
override fun discoverGATT(addressArgs: String, callback: (Result<List<MyGATTServiceArgs>>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val discovering = gatt.discoverServices()
if (!discovering) {
throw IllegalStateException()
@ -205,13 +252,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun readCharacteristic(
addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit
) {
override fun readCharacteristic(addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val characteristic =
mRetrieveCharacteristic(addressArgs, hashCodeArgs) as BluetoothGattCharacteristic
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
val reading = gatt.readCharacteristic(characteristic)
if (!reading) {
throw IllegalStateException()
@ -223,26 +267,15 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun writeCharacteristic(
addressArgs: String,
hashCodeArgs: Long,
valueArgs: ByteArray,
typeNumberArgs: Long,
callback: (Result<Unit>) -> Unit
) {
override fun writeCharacteristic(addressArgs: String, hashCodeArgs: Long, valueArgs: ByteArray, typeArgs: MyGATTCharacteristicWriteTypeArgs, callback: (Result<Unit>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val characteristic =
mRetrieveCharacteristic(addressArgs, hashCodeArgs) as BluetoothGattCharacteristic
val typeRawArgs = typeNumberArgs.toInt()
val typeArgs = MyGattCharacteristicWriteTypeArgs.ofRaw(typeRawArgs)
?: throw IllegalArgumentException()
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
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
} else { // TODO: remove this when minSdkVersion >= 33
characteristic.value = valueArgs
characteristic.writeType = type
gatt.writeCharacteristic(characteristic)
@ -257,47 +290,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun setCharacteristicNotifyState(
addressArgs: String,
hashCodeArgs: Long,
stateNumberArgs: Long,
callback: (Result<Unit>) -> Unit
) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val characteristic =
mRetrieveCharacteristic(addressArgs, hashCodeArgs) as BluetoothGattCharacteristic
val stateRawArgs = stateNumberArgs.toInt()
val stateArgs = MyGattCharacteristicNotifyStateArgs.ofRaw(stateRawArgs)
?: throw IllegalArgumentException()
val enable = stateArgs != MyGattCharacteristicNotifyStateArgs.NONE
val notifying = gatt.setCharacteristicNotification(characteristic, enable)
override fun setCharacteristicNotification(addressArgs: String, hashCodeArgs: Long, enableArgs: Boolean) {
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
val notifying = gatt.setCharacteristicNotification(characteristic, enableArgs)
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 cccDescriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID)
val cccHashCodeArgs = cccDescriptor.hashCode().toLong()
val valueArgs = stateArgs.toValue()
writeDescriptor(addressArgs, cccHashCodeArgs, valueArgs, callback)
//} else {
// callback(Result.success(Unit))
//}
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun readDescriptor(
addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit
) {
override fun readDescriptor(addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val descriptor =
mRetrieveDescriptor(addressArgs, hashCodeArgs) as BluetoothGattDescriptor
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val descriptor = retrieveDescriptor(addressArgs, hashCodeArgs)
val reading = gatt.readDescriptor(descriptor)
if (!reading) {
throw IllegalStateException()
@ -309,21 +314,14 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun writeDescriptor(
addressArgs: String,
hashCodeArgs: Long,
valueArgs: ByteArray,
callback: (Result<Unit>) -> Unit
) {
override fun writeDescriptor(addressArgs: String, hashCodeArgs: Long, valueArgs: ByteArray, callback: (Result<Unit>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val descriptor =
mRetrieveDescriptor(addressArgs, hashCodeArgs) as BluetoothGattDescriptor
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val descriptor = retrieveDescriptor(addressArgs, hashCodeArgs)
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
} else { // TODO: remove this when minSdkVersion >= 33
descriptor.value = valueArgs
gatt.writeDescriptor(descriptor)
}
@ -337,22 +335,37 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun onPermissionsRequested(granted: Boolean) {
val callback = mSetUpCallback ?: return
val stateArgs = if (granted) {
registerReceiver()
adapter.state.toBluetoothLowEnergyStateArgs()
} else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
mOnStateChanged(stateArgs)
callback(Result.success(Unit))
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != BluetoothAdapter.ACTION_STATE_CHANGED) {
return
}
override fun onAdapterStateChanged(state: Int) {
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
val stateArgs = state.toBluetoothLowEnergyStateArgs()
mOnStateChanged(stateArgs)
mAPI.onStateChanged(stateArgs) {}
}
private fun onScanSucceed() {
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean {
if (requestCode != AUTHORIZE_CODE) {
return false
}
val callback = mAuthorizeCallback ?: return false
mAuthorizeCallback = null
val authorized = permissions.contentEquals(this.permissions) && results.all { r -> r == PackageManager.PERMISSION_GRANTED }
callback(Result.success(authorized))
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (requestCode != SHOW_APP_SETTINGS_CODE) {
return false
}
val callback = mShowAppSettingsCallback ?: return false
mShowAppSettingsCallback = null
callback(Result.success(Unit))
return true
}
private fun onScanSucceeded() {
mDiscovering = true
val callback = mStartDiscoveryCallback ?: return
mStartDiscoveryCallback = null
@ -370,16 +383,15 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
val device = result.device
val peripheralArgs = device.toPeripheralArgs()
val addressArgs = peripheralArgs.addressArgs
val rssiArgs = result.rssi.toLong()
val rssiArgs = result.rssi.args
val advertisementArgs = result.toAdvertisementArgs()
mDevices[addressArgs] = device
mApi.onDiscovered(peripheralArgs, rssiArgs, advertisementArgs) {}
mAPI.onDiscovered(peripheralArgs, rssiArgs, advertisementArgs) {}
}
fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val device = gatt.device
val addressArgs = device.address
// check connection state.
val addressArgs = device.address // check connection state.
if (newState == BluetoothProfile.STATE_DISCONNECTED) {
gatt.close()
mGATTs.remove(addressArgs)
@ -427,9 +439,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
}
val stateArgs = newState == BluetoothProfile.STATE_CONNECTED
mApi.onConnectionStateChanged(addressArgs, stateArgs) {}
// check connect & disconnect callbacks.
// check connect callback.
val connectCallback = mConnectCallbacks.remove(addressArgs)
if (connectCallback != null) {
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -439,6 +449,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
connectCallback(Result.failure(error))
}
}
// check disconnect callback.
val disconnectCallback = mDisconnectCallbacks.remove(addressArgs)
if (disconnectCallback != null) {
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -448,14 +459,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
disconnectCallback(Result.failure(error))
}
}
// invoke connection state changed event.
val peripheralArgs = device.toPeripheralArgs()
val stateArgs = newState.toConnectionStateArgs()
mAPI.onConnectionStateChanged(peripheralArgs, stateArgs) {}
}
fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
val device = gatt.device
val addressArgs = device.address
val result = if (status == BluetoothGatt.GATT_SUCCESS) {
val mtuArgs = mtu.toLong()
mApi.onMtuChanged(addressArgs, mtuArgs) {}
val peripheralArgs = device.toPeripheralArgs()
val mtuArgs = mtu.args
mAPI.onMTUChanged(peripheralArgs, mtuArgs) {}
Result.success(mtuArgs)
} else {
val error = IllegalStateException("Read RSSI failed with status: $status")
@ -470,7 +486,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
val addressArgs = device.address
val callback = mReadRssiCallbacks.remove(addressArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
val rssiArgs = rssi.toLong()
val rssiArgs = rssi.args
callback(Result.success(rssiArgs))
} else {
val error = IllegalStateException("Read RSSI failed with status: $status")
@ -484,10 +500,9 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
val callback = mDiscoverServicesCallbacks.remove(addressArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
val services = gatt.services
val characteristics = services.flatMap { it.characteristics }
val descriptors = characteristics.flatMap { it.descriptors }
mCharacteristics[addressArgs] = characteristics.associateBy { it.hashCode().toLong() }
mDescriptors[addressArgs] = descriptors.associateBy { it.hashCode().toLong() }
for (service in services) {
addService(addressArgs, service)
}
val servicesArgs = services.map { it.toArgs() }
callback(Result.success(servicesArgs))
} else {
@ -496,15 +511,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int,
value: ByteArray
) {
fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int, value: ByteArray) {
val device = gatt.device
val addressArgs = device.address
val hashCodeArgs = characteristic.hashCode().toLong()
val hashCodeArgs = characteristic.hashCode.args
val callbacks = mReadCharacteristicCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -515,12 +525,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
fun onCharacteristicWrite(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
) {
fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
val device = gatt.device
val addressArgs = device.address
val hashCodeArgs = characteristic.hashCode().toLong()
val hashCodeArgs = characteristic.hashCode.args
val callbacks = mWriteCharacteristicCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -531,21 +539,17 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
fun onCharacteristicChanged(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray
) {
fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
val device = gatt.device
val addressArgs = device.address
val hashCodeArgs = characteristic.hashCode().toLong()
mApi.onCharacteristicNotified(addressArgs, hashCodeArgs, value) {}
val peripheralArgs = device.toPeripheralArgs()
val characteristicArgs = characteristic.toArgs()
mAPI.onCharacteristicNotified(peripheralArgs, characteristicArgs, value) {}
}
fun onDescriptorRead(
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray
) {
fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
val device = gatt.device
val addressArgs = device.address
val hashCodeArgs = descriptor.hashCode().toLong()
val hashCodeArgs = descriptor.hashCode.args
val callbacks = mReadDescriptorCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -559,7 +563,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
val device = gatt.device
val addressArgs = device.address
val hashCodeArgs = descriptor.hashCode().toLong()
val hashCodeArgs = descriptor.hashCode.args
val callbacks = mWriteDescriptorCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -570,47 +574,28 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
private fun mClearState() {
if (mDiscovering) {
stopDiscovery()
private fun addService(addressArgs: String, service: BluetoothGattService) {
val includedServices = service.includedServices
for (includedService in includedServices) {
addService(addressArgs, includedService)
}
for (gatt in mGATTs.values) {
gatt.disconnect()
for (characteristic in service.characteristics) {
for (descriptor in characteristic.descriptors) {
val descriptors = mDescriptors.getOrPut(addressArgs) { mutableMapOf() }
descriptors[descriptor.hashCode.args] = descriptor
}
val characteristics = mCharacteristics.getOrPut(addressArgs) { mutableMapOf() }
characteristics[characteristic.hashCode.args] = characteristic
}
mDevices.clear()
mGATTs.clear()
mCharacteristics.clear()
mDescriptors.clear()
mSetUpCallback = null
mStartDiscoveryCallback = null
mConnectCallbacks.clear()
mDisconnectCallbacks.clear()
mRequestMtuCallbacks.clear()
mReadRssiCallbacks.clear()
mDiscoverServicesCallbacks.clear()
mReadCharacteristicCallbacks.clear()
mWriteCharacteristicCallbacks.clear()
mReadDescriptorCallbacks.clear()
mWriteDescriptorCallbacks.clear()
}
private fun mOnStateChanged(stateArgs: MyBluetoothLowEnergyStateArgs) {
val stateNumberArgs = stateArgs.raw.toLong()
mApi.onStateChanged(stateNumberArgs) {}
private fun retrieveCharacteristic(addressArgs: String, hashCodeArgs: Long): BluetoothGattCharacteristic {
val characteristics = mCharacteristics[addressArgs] ?: throw IllegalArgumentException()
return characteristics[hashCodeArgs] ?: throw IllegalArgumentException()
}
private fun mRetrieveCharacteristic(
addressArgs: String, hashCodeArgs: Long
): BluetoothGattCharacteristic? {
val characteristics = mCharacteristics[addressArgs] ?: return null
return characteristics[hashCodeArgs]
}
private fun mRetrieveDescriptor(
addressArgs: String, hashCodeArgs: Long
): BluetoothGattDescriptor? {
val descriptors = mDescriptors[addressArgs] ?: return null
return descriptors[hashCodeArgs]
private fun retrieveDescriptor(addressArgs: String, hashCodeArgs: Long): BluetoothGattDescriptor {
val descriptors = mDescriptors[addressArgs] ?: throw IllegalArgumentException()
return descriptors[hashCodeArgs] ?: throw IllegalArgumentException()
}
}

View File

@ -0,0 +1,446 @@
package dev.hebei.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.BluetoothGattServer
import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseSettings
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import io.flutter.plugin.common.BinaryMessenger
import java.util.concurrent.Executor
class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) : MyBluetoothLowEnergyManager(context), MyPeripheralManagerHostAPI {
private val mAPI: MyPeripheralManagerFlutterAPI
private val mBluetoothGattServerCallback: BluetoothGattServerCallback by lazy { MyBluetoothGattServerCallback(this, executor) }
private val mAdvertiseCallback: AdvertiseCallback by lazy { MyAdvertiseCallback(this) }
private var mServer: BluetoothGattServer?
private var mAdvertising: Boolean
private val mServicesArgs: MutableMap<Int, MyMutableGATTServiceArgs>
private val mCharacteristicsArgs: MutableMap<Int, MyMutableGATTCharacteristicArgs>
private val mDescriptorsArgs: MutableMap<Int, MyMutableGATTDescriptorArgs>
private val mDevices: MutableMap<String, BluetoothDevice>
private val mServices: MutableMap<Long, BluetoothGattService>
private val mCharacteristics: MutableMap<Long, BluetoothGattCharacteristic>
private val mDescriptors: MutableMap<Long, BluetoothGattDescriptor>
private var mAuthorizeCallback: ((Result<Boolean>) -> Unit)?
private var mShowAppSettingsCallback: ((Result<Unit>) -> Unit)?
private var mAddServiceCallback: ((Result<Unit>) -> Unit)?
private var mStartAdvertisingCallback: ((Result<Unit>) -> Unit)?
private val mNotifyCharacteristicValueChangedCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
init {
mAPI = MyPeripheralManagerFlutterAPI(binaryMessenger)
mServer = null
mAdvertising = false
mServicesArgs = mutableMapOf()
mCharacteristicsArgs = mutableMapOf()
mDescriptorsArgs = mutableMapOf()
mDevices = mutableMapOf()
mServices = mutableMapOf()
mCharacteristics = mutableMapOf()
mDescriptors = mutableMapOf()
mAuthorizeCallback = null
mShowAppSettingsCallback = null
mAddServiceCallback = null
mStartAdvertisingCallback = null
mNotifyCharacteristicValueChangedCallbacks = mutableMapOf()
}
private val permissions: Array<String>
get() = 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)
}
private val manager get() = ContextCompat.getSystemService(context, BluetoothManager::class.java) as BluetoothManager
private val adapter get() = manager.adapter as BluetoothAdapter
private val advertiser get() = adapter.bluetoothLeAdvertiser
private val server get() = mServer ?: throw IllegalStateException()
private val executor get() = ContextCompat.getMainExecutor(context) as Executor
override fun initialize(): MyPeripheralManagerArgs {
if (mAdvertising) {
stopAdvertising()
}
mServer?.close()
mServicesArgs.clear()
mCharacteristicsArgs.clear()
mDescriptorsArgs.clear()
mDevices.clear()
mServices.clear()
mCharacteristics.clear()
mDescriptors.clear()
mAuthorizeCallback = null
mShowAppSettingsCallback = null
mAddServiceCallback = null
mStartAdvertisingCallback = null
mNotifyCharacteristicValueChangedCallbacks.clear()
val enableNotificationValue = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
val enableIndicationValue = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
val disableNotificationValue = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
return MyPeripheralManagerArgs(enableNotificationValue, enableIndicationValue, disableNotificationValue)
}
override fun getState(): MyBluetoothLowEnergyStateArgs {
val supported = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
return if (supported) {
val authorized = permissions.all { permission -> ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED }
return if (authorized) adapter.state.toBluetoothLowEnergyStateArgs()
else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
} else MyBluetoothLowEnergyStateArgs.UNSUPPORTED
}
override fun authorize(callback: (Result<Boolean>) -> Unit) {
try {
ActivityCompat.requestPermissions(activity, permissions, AUTHORIZE_CODE)
mAuthorizeCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun showAppSettings(callback: (Result<Unit>) -> Unit) {
try {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.fromParts("package", activity.packageName, null)
val options = ActivityOptionsCompat.makeBasic().toBundle()
ActivityCompat.startActivityForResult(activity, intent, SHOW_APP_SETTINGS_CODE, options)
mShowAppSettingsCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun openGATTServer() {
mServer = manager.openGattServer(context, mBluetoothGattServerCallback)
}
override fun closeGATTServer() {
server.close()
}
override fun addService(serviceArgs: MyMutableGATTServiceArgs, callback: (Result<Unit>) -> Unit) {
try {
val service = addServiceArgs(serviceArgs)
val adding = server.addService(service)
if (!adding) {
throw IllegalStateException()
}
mAddServiceCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun removeService(hashCodeArgs: Long) {
val service = mServices[hashCodeArgs] ?: throw IllegalArgumentException()
val removed = server.removeService(service)
if (!removed) {
throw IllegalStateException()
}
val hashCode = service.hashCode()
val serviceArgs = mServicesArgs[hashCode] ?: throw IllegalArgumentException()
removeServiceArgs(serviceArgs)
}
override fun removeAllServices() {
server.clearServices()
mServices.clear()
mCharacteristics.clear()
mDescriptors.clear()
mServicesArgs.clear()
mCharacteristicsArgs.clear()
mDescriptorsArgs.clear()
}
override fun startAdvertising(advertisementArgs: MyAdvertisementArgs, callback: (Result<Unit>) -> Unit) {
try {
val settings = AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).setConnectable(true).build()
// TODO: There is an issue that Android will use the cached name before setName takes effect.
// see https://stackoverflow.com/questions/8377558/change-the-android-bluetooth-device-name
val nameArgs = advertisementArgs.nameArgs
if (nameArgs != null) {
adapter.name = nameArgs
}
val advertiseData = advertisementArgs.toAdvertiseData()
val scanResponse = advertisementArgs.toScanResponse()
advertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback)
mStartAdvertisingCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun stopAdvertising() {
advertiser.stopAdvertising(mAdvertiseCallback)
mAdvertising = false
}
override fun sendResponse(addressArgs: String, idArgs: Long, statusArgs: MyGATTStatusArgs, offsetArgs: Long, valueArgs: ByteArray?) {
val device = mDevices[addressArgs] ?: throw IllegalArgumentException()
val requestId = idArgs.toInt()
val status = statusArgs.toStatus()
val offset = offsetArgs.toInt()
val sent = server.sendResponse(device, requestId, status, offset, valueArgs)
if (!sent) {
throw IllegalStateException("Send response failed.")
}
}
override fun notifyCharacteristicChanged(addressArgs: String, hashCodeArgs: Long, confirmArgs: Boolean, valueArgs: ByteArray, callback: (Result<Unit>) -> Unit) {
try {
val device = mDevices[addressArgs] ?: throw IllegalArgumentException()
val characteristic = mCharacteristics[hashCodeArgs] ?: throw IllegalArgumentException()
val notifying = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val statusCode = server.notifyCharacteristicChanged(device, characteristic, confirmArgs, valueArgs)
statusCode == BluetoothStatusCodes.SUCCESS
} else { // TODO: remove this when minSdkVersion >= 33
characteristic.value = valueArgs
server.notifyCharacteristicChanged(device, characteristic, confirmArgs)
}
if (!notifying) {
throw IllegalStateException()
}
mNotifyCharacteristicValueChangedCallbacks[addressArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != BluetoothAdapter.ACTION_STATE_CHANGED) {
return
}
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
val stateArgs = state.toBluetoothLowEnergyStateArgs()
mAPI.onStateChanged(stateArgs) {}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean {
if (requestCode != AUTHORIZE_CODE) {
return false
}
val callback = mAuthorizeCallback ?: return false
mAuthorizeCallback = null
val authorized = permissions.contentEquals(this.permissions) && results.all { r -> r == PackageManager.PERMISSION_GRANTED }
callback(Result.success(authorized))
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (requestCode != SHOW_APP_SETTINGS_CODE) {
return false
}
val callback = mShowAppSettingsCallback ?: return false
mShowAppSettingsCallback = null
callback(Result.success(Unit))
return true
}
fun onServiceAdded(status: Int, service: BluetoothGattService) {
val callback = mAddServiceCallback ?: return
mAddServiceCallback = null
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val error = IllegalStateException("Read rssi failed with status: $status")
callback(Result.failure(error))
}
}
fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
mAdvertising = true
val callback = mStartAdvertisingCallback ?: return
mStartAdvertisingCallback = null
callback(Result.success(Unit))
}
fun onStartFailure(errorCode: Int) {
val callback = mStartAdvertisingCallback ?: return
mStartAdvertisingCallback = null
val error = IllegalStateException("Start advertising failed with error code: $errorCode")
callback(Result.failure(error))
}
fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
val centralArgs = device.toCentralArgs()
val addressArgs = centralArgs.addressArgs
val statusArgs = status.args
val stateArgs = newState.toConnectionStateArgs()
mDevices[addressArgs] = device
mAPI.onConnectionStateChanged(centralArgs, statusArgs, stateArgs) {}
}
fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
val centralArgs = device.toCentralArgs()
val mtuArgs = mtu.args
mAPI.onMTUChanged(centralArgs, mtuArgs) {}
}
fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) {
val centralArgs = device.toCentralArgs()
val idArgs = requestId.args
val offsetArgs = offset.args
val hashCode = characteristic.hashCode()
val characteristicArgs = mCharacteristicsArgs[hashCode]
if (characteristicArgs == null) {
val status = BluetoothGatt.GATT_FAILURE
server.sendResponse(device, requestId, status, offset, null)
} else {
val hashCodeArgs = characteristicArgs.hashCodeArgs
mAPI.onCharacteristicReadRequest(centralArgs, idArgs, offsetArgs, hashCodeArgs) {}
}
}
fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
val centralArgs = device.toCentralArgs()
val idArgs = requestId.args
val hashCode = characteristic.hashCode()
val characteristicArgs = mCharacteristicsArgs[hashCode]
if (characteristicArgs == null) {
if (!responseNeeded) {
return
}
val status = BluetoothGatt.GATT_FAILURE
server.sendResponse(device, requestId, status, offset, null)
} else {
val hashCodeArgs = characteristicArgs.hashCodeArgs
val offsetArgs = offset.args
mAPI.onCharacteristicWriteRequest(centralArgs, idArgs, hashCodeArgs, preparedWrite, responseNeeded, offsetArgs, value) {}
}
}
fun onNotificationSent(device: BluetoothDevice, status: Int) {
val addressArgs = device.address
val callback = mNotifyCharacteristicValueChangedCallbacks.remove(addressArgs) ?: 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))
}
}
fun onDescriptorReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor) {
val centralArgs = device.toCentralArgs()
val idArgs = requestId.args
val offsetArgs = offset.args
val hashCode = descriptor.hashCode()
val descriptorArgs = mDescriptorsArgs[hashCode]
if (descriptorArgs == null) {
val status = BluetoothGatt.GATT_FAILURE
server.sendResponse(device, requestId, status, offset, null)
} else {
val hashCodeArgs = descriptorArgs.hashCodeArgs
mAPI.onDescriptorReadRequest(centralArgs, idArgs, offsetArgs, hashCodeArgs) {}
}
}
fun onDescriptorWriteRequest(device: BluetoothDevice, requestId: Int, descriptor: BluetoothGattDescriptor, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
val centralArgs = device.toCentralArgs()
val idArgs = requestId.args
val hashCode = descriptor.hashCode()
val descriptorArgs = mDescriptorsArgs[hashCode]
if (descriptorArgs == null) {
if (!responseNeeded) {
return
}
val status = BluetoothGatt.GATT_FAILURE
server.sendResponse(device, requestId, status, offset, null)
} else {
val hashCodeArgs = descriptorArgs.hashCodeArgs
val offsetArgs = offset.args
mAPI.onDescriptorWriteRequest(centralArgs, idArgs, hashCodeArgs, preparedWrite, responseNeeded, offsetArgs, value) {}
}
}
fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
val centralArgs = device.toCentralArgs()
val idArgs = requestId.args
mAPI.onExecuteWrite(centralArgs, idArgs, execute) {}
}
private fun addServiceArgs(serviceArgs: MyMutableGATTServiceArgs): BluetoothGattService {
val service = serviceArgs.toService()
this.mServicesArgs[service.hashCode] = serviceArgs
this.mServices[serviceArgs.hashCodeArgs] = service
val includedServicesArgs = serviceArgs.includedServicesArgs.requireNoNulls()
for (includedServiceArgs in includedServicesArgs) {
val includedService = addServiceArgs(includedServiceArgs)
val adding = service.addService(includedService)
if (!adding) {
throw IllegalStateException()
}
}
val characteristicsArgs = serviceArgs.characteristicsArgs.requireNoNulls()
for (characteristicArgs in characteristicsArgs) {
val characteristic = characteristicArgs.toCharacteristic()
this.mCharacteristicsArgs[characteristic.hashCode] = characteristicArgs
this.mCharacteristics[characteristicArgs.hashCodeArgs] = characteristic
val descriptorsArgs = characteristicArgs.descriptorsArgs.requireNoNulls()
for (descriptorArgs in descriptorsArgs) {
val descriptor = descriptorArgs.toDescriptor()
this.mDescriptorsArgs[descriptor.hashCode] = descriptorArgs
this.mDescriptors[descriptorArgs.hashCodeArgs] = descriptor
val descriptorAdded = characteristic.addDescriptor(descriptor)
if (!descriptorAdded) {
throw IllegalStateException()
}
}
val characteristicAdded = service.addCharacteristic(characteristic)
if (!characteristicAdded) {
throw IllegalStateException()
}
}
return service
}
private fun removeServiceArgs(serviceArgs: MyMutableGATTServiceArgs) {
val includedServicesArgs = serviceArgs.includedServicesArgs.requireNoNulls()
for (includedServiceArgs in includedServicesArgs) {
removeServiceArgs(includedServiceArgs)
}
val characteristicsArgs = serviceArgs.characteristicsArgs.requireNoNulls()
for (characteristicArgs in characteristicsArgs) {
val descriptorsArgs = characteristicArgs.descriptorsArgs.requireNoNulls()
for (descriptorArgs in descriptorsArgs) {
val descriptor = mDescriptors.remove(descriptorArgs.hashCodeArgs) ?: throw IllegalArgumentException()
this.mDescriptorsArgs.remove(descriptor.hashCode)
}
val characteristic = mCharacteristics.remove(characteristicArgs.hashCodeArgs) ?: throw IllegalArgumentException()
this.mCharacteristicsArgs.remove(characteristic.hashCode)
}
val service = mServices.remove(serviceArgs.hashCodeArgs) ?: throw IllegalArgumentException()
mServicesArgs.remove(service.hashCode)
}
}

View File

@ -0,0 +1,15 @@
package dev.hebei.bluetooth_low_energy_android
import io.flutter.plugin.common.PluginRegistry
class MyRequestPermissionResultListener(manager: MyBluetoothLowEnergyManager) : PluginRegistry.RequestPermissionsResultListener {
private val mManager: MyBluetoothLowEnergyManager
init {
mManager = manager
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean {
return mManager.onRequestPermissionsResult(requestCode, permissions, results)
}
}

View File

@ -1,4 +1,4 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult

View File

@ -1,270 +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.le.AdvertiseData
import android.bluetooth.le.ScanResult
import android.os.ParcelUuid
import android.util.SparseArray
import java.util.UUID
//region ToObj
fun MyGattCharacteristicWriteTypeArgs.toType(): Int {
return when (this) {
MyGattCharacteristicWriteTypeArgs.WITHRESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
MyGattCharacteristicWriteTypeArgs.WITHOUTRESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
}
}
fun MyGattCharacteristicNotifyStateArgs.toValue(): ByteArray {
return when (this) {
MyGattCharacteristicNotifyStateArgs.NONE -> BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
MyGattCharacteristicNotifyStateArgs.NOTIFY -> BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
MyGattCharacteristicNotifyStateArgs.INDICATE -> BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
}
}
fun MyGattStatusArgs.toStatus(): Int {
return when (this) {
MyGattStatusArgs.SUCCESS -> BluetoothGatt.GATT_SUCCESS
MyGattStatusArgs.READNOTPERMITTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
MyGattStatusArgs.WRITENOTPERMITTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
MyGattStatusArgs.REQUESTNOTSUPPORTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
MyGattStatusArgs.INVALIDOFFSET -> BluetoothGatt.GATT_INVALID_OFFSET
MyGattStatusArgs.INSUFFICIENTAUTHENTICATION -> BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION
MyGattStatusArgs.INSUFFICIENTENCRYPTION -> BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION
MyGattStatusArgs.INVALIDATTRIBUTELENGTH -> BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH
MyGattStatusArgs.CONNECTIONCONGESTED -> BluetoothGatt.GATT_CONNECTION_CONGESTED
MyGattStatusArgs.FAILURE -> BluetoothGatt.GATT_FAILURE
}
}
fun MyAdvertisementArgs.toAdvertiseData(adapter: BluetoothAdapter): AdvertiseData {
val advertiseDataBuilder = AdvertiseData.Builder()
if (nameArgs == null) {
advertiseDataBuilder.setIncludeDeviceName(false)
} else {
// TODO: There is an issue that Android will use the cached name before setName takes effect.
// see https://stackoverflow.com/questions/8377558/change-the-android-bluetooth-device-name
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 MyGattDescriptorArgs.toDescriptor(): BluetoothGattDescriptor {
val uuid = UUID.fromString(uuidArgs)
val permissions =
BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE
return BluetoothGattDescriptor(uuid, permissions)
}
fun MyGattCharacteristicArgs.toCharacteristic(): BluetoothGattCharacteristic {
val uuid = UUID.fromString(uuidArgs)
val properties = getProperties()
val permissions = getPermissions()
return BluetoothGattCharacteristic(uuid, properties, permissions)
}
fun MyGattCharacteristicArgs.getProperties(): Int {
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
}
fun MyGattCharacteristicArgs.getPermissions(): Int {
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 MyGattServiceArgs.toService(): BluetoothGattService {
val uuid = UUID.fromString(uuidArgs)
val serviceType = BluetoothGattService.SERVICE_TYPE_PRIMARY
return BluetoothGattService(uuid, serviceType)
}
//endregion
//region ToArgs
fun Int.toBluetoothLowEnergyStateArgs(): MyBluetoothLowEnergyStateArgs {
return when (this) {
BluetoothAdapter.STATE_OFF -> MyBluetoothLowEnergyStateArgs.OFF
BluetoothAdapter.STATE_TURNING_ON -> MyBluetoothLowEnergyStateArgs.TURNINGON
BluetoothAdapter.STATE_ON -> MyBluetoothLowEnergyStateArgs.ON
BluetoothAdapter.STATE_TURNING_OFF -> MyBluetoothLowEnergyStateArgs.TURNINGOFF
else -> MyBluetoothLowEnergyStateArgs.UNKNOWN
}
}
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()
}
fun ScanResult.toAdvertisementArgs(): MyAdvertisementArgs {
val record = scanRecord
return if (record == null) {
val nameArgs = null
val serviceUUIDsArgs = emptyList<String?>()
val serviceDataArgs = emptyMap<String?, ByteArray>()
val manufacturerSpecificDataArgs = null
MyAdvertisementArgs(
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() ?: emptyArray()
val serviceDataArgs = mapOf<String?, ByteArray?>(*pairs)
val manufacturerSpecificDataArgs =
record.manufacturerSpecificData?.toManufacturerSpecificDataArgs()
MyAdvertisementArgs(
nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs
)
}
}
fun BluetoothDevice.toCentralArgs(): MyCentralArgs {
val addressArgs = address
return MyCentralArgs(addressArgs)
}
fun BluetoothDevice.toPeripheralArgs(): MyPeripheralArgs {
val addressArgs = address
return MyPeripheralArgs(addressArgs)
}
fun BluetoothGattService.toArgs(): MyGattServiceArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
val characteristicsArgs = characteristics.map { it.toArgs() }
return MyGattServiceArgs(hashCodeArgs, uuidArgs, characteristicsArgs)
}
fun BluetoothGattCharacteristic.toArgs(): MyGattCharacteristicArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
val propertyNumbersArgs = getPropertyNumbersArgs()
val descriptorsArgs = descriptors.map { it.toArgs() }
return MyGattCharacteristicArgs(hashCodeArgs, uuidArgs, propertyNumbersArgs, descriptorsArgs)
}
fun BluetoothGattCharacteristic.getPropertyNumbersArgs(): List<Long> {
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.toArgs(): MyGattDescriptorArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
return MyGattDescriptorArgs(hashCodeArgs, uuidArgs, null)
}
fun ByteArray.toNotifyStateArgs(): MyGattCharacteristicNotifyStateArgs {
return if (this contentEquals BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) {
MyGattCharacteristicNotifyStateArgs.NOTIFY
} else if (this contentEquals BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) {
MyGattCharacteristicNotifyStateArgs.INDICATE
} else if (this contentEquals BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE) {
MyGattCharacteristicNotifyStateArgs.NONE
} else {
throw IllegalArgumentException()
}
}
//endregion
val Any.TAG get() = this::class.java.simpleName as String
//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()
// }

View File

@ -1,106 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
import java.util.UUID
import java.util.concurrent.Executor
abstract class MyBluetoothLowEnergyManager(context: Context) {
companion object {
val CLIENT_CHARACTERISTIC_CONFIG_UUID =
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") as UUID
}
private val mContext: Context
private val mRequestPermissionsResultListener: RequestPermissionsResultListener by lazy {
MyRequestPermissionResultListener(this)
}
private val mBroadcastReceiver: BroadcastReceiver by lazy {
MyBroadcastReceiver(this)
}
private var mRegistered: Boolean
private lateinit var mBinding: ActivityPluginBinding
init {
mContext = context
mRegistered = false
}
protected val executor get() = ContextCompat.getMainExecutor(mContext) as Executor
protected val hasFeature get() = mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
protected val manager
get() = ContextCompat.getSystemService(
mContext, BluetoothManager::class.java
) as BluetoothManager
protected val adapter get() = manager.adapter as BluetoothAdapter
protected fun checkPermissions(): Boolean {
return permissions.all { permission ->
ActivityCompat.checkSelfPermission(
mContext, permission
) == PackageManager.PERMISSION_GRANTED
}
}
protected fun requestPermissions() {
val activity = mBinding.activity
ActivityCompat.requestPermissions(activity, permissions, requestCode)
}
fun onAttachedToActivity(binding: ActivityPluginBinding) {
binding.addRequestPermissionsResultListener(mRequestPermissionsResultListener)
mBinding = binding
}
fun onDetachedFromActivity() {
mBinding.removeRequestPermissionsResultListener(mRequestPermissionsResultListener)
}
fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, results: IntArray
): Boolean {
if (this.requestCode != requestCode) {
return false
}
val granted =
permissions.contentEquals(this.permissions) && results.all { r -> r == PackageManager.PERMISSION_GRANTED }
onPermissionsRequested(granted)
return true
}
fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action != BluetoothAdapter.ACTION_STATE_CHANGED) {
return
}
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
onAdapterStateChanged(state)
}
protected fun registerReceiver() {
if (mRegistered) {
return
}
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
mContext.registerReceiver(mBroadcastReceiver, filter)
mRegistered = true
}
abstract val permissions: Array<String>
abstract val requestCode: Int
abstract fun onPermissionsRequested(granted: Boolean)
abstract fun onAdapterStateChanged(state: Int)
}

View File

@ -1,461 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattServer
import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseSettings
import android.content.Context
import android.os.Build
import io.flutter.plugin.common.BinaryMessenger
class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) :
MyBluetoothLowEnergyManager(context), MyPeripheralManagerHostApi {
companion object {
const val REQUEST_CODE = 445
}
private val advertiser get() = adapter.bluetoothLeAdvertiser
private val mContext: Context
private val mApi: MyPeripheralManagerFlutterApi
private val bluetoothGattServerCallback: BluetoothGattServerCallback by lazy {
MyBluetoothGattServerCallback(this, executor)
}
private val advertiseCallback: AdvertiseCallback by lazy {
MyAdvertiseCallback(this)
}
private lateinit var mServer: BluetoothGattServer
private var mOpening = false
private var mAdvertising = false
private val mServicesArgs: MutableMap<Int, MyGattServiceArgs>
private val mCharacteristicsArgs: MutableMap<Int, MyGattCharacteristicArgs>
private val mDescriptorsArgs: MutableMap<Int, MyGattDescriptorArgs>
private val mDevices: MutableMap<String, BluetoothDevice>
private val mServices: MutableMap<Long, BluetoothGattService>
private val mCharacteristics: MutableMap<Long, BluetoothGattCharacteristic>
private val mDescriptors: MutableMap<Long, BluetoothGattDescriptor>
private var mSetUpCallback: ((Result<Unit>) -> Unit)?
private var mAddServiceCallback: ((Result<Unit>) -> Unit)?
private var mStartAdvertisingCallback: ((Result<Unit>) -> Unit)?
private val mNotifyCharacteristicValueChangedCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
init {
mContext = context
mApi = MyPeripheralManagerFlutterApi(binaryMessenger)
mServicesArgs = mutableMapOf()
mCharacteristicsArgs = mutableMapOf()
mDescriptorsArgs = mutableMapOf()
mDevices = mutableMapOf()
mServices = mutableMapOf()
mCharacteristics = mutableMapOf()
mDescriptors = mutableMapOf()
mSetUpCallback = null
mAddServiceCallback = null
mStartAdvertisingCallback = null
mNotifyCharacteristicValueChangedCallbacks = mutableMapOf()
}
override val permissions: Array<String>
get() = 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
)
}
override val requestCode: Int
get() = REQUEST_CODE
override fun setUp(callback: (Result<Unit>) -> Unit) {
try {
mClearState()
val stateArgs = if (hasFeature) {
val granted = checkPermissions()
if (granted) {
registerReceiver()
adapter.state.toBluetoothLowEnergyStateArgs()
} else {
requestPermissions()
mSetUpCallback = callback
return
}
} else MyBluetoothLowEnergyStateArgs.UNSUPPORTED
mOnStateChanged(stateArgs)
callback(Result.success(Unit))
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun addService(serviceArgs: MyGattServiceArgs, callback: (Result<Unit>) -> Unit) {
try {
val service = serviceArgs.toService()
val characteristicsArgs = serviceArgs.characteristicsArgs.filterNotNull()
for (characteristicArgs in characteristicsArgs) {
val characteristic = characteristicArgs.toCharacteristic()
val descriptorsArgs = characteristicArgs.descriptorsArgs.filterNotNull()
for (descriptorArgs in descriptorsArgs) {
val descriptor = descriptorArgs.toDescriptor()
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
val descriptorHashCode = descriptor.hashCode()
this.mDescriptorsArgs[descriptorHashCode] = descriptorArgs
this.mDescriptors[descriptorHashCodeArgs] = descriptor
val descriptorAdded = characteristic.addDescriptor(descriptor)
if (!descriptorAdded) {
throw IllegalStateException()
}
}
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
val characteristicHashCode = characteristic.hashCode()
this.mCharacteristicsArgs[characteristicHashCode] = characteristicArgs
this.mCharacteristics[characteristicHashCodeArgs] = characteristic
val characteristicAdded = service.addCharacteristic(characteristic)
if (!characteristicAdded) {
throw IllegalStateException()
}
}
val serviceHashCodeArgs = serviceArgs.hashCodeArgs
val serviceHashCode = service.hashCode()
this.mServicesArgs[serviceHashCode] = serviceArgs
this.mServices[serviceHashCodeArgs] = service
val adding = mServer.addService(service)
if (!adding) {
throw IllegalStateException()
}
mAddServiceCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun removeService(hashCodeArgs: Long) {
val service = mServices.remove(hashCodeArgs) as BluetoothGattService
val removed = mServer.removeService(service)
if (!removed) {
throw IllegalStateException()
}
val hashCode = service.hashCode()
val serviceArgs = mServicesArgs.remove(hashCode) as MyGattServiceArgs
val characteristicsArgs = serviceArgs.characteristicsArgs.filterNotNull()
for (characteristicArgs in characteristicsArgs) {
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
val characteristic =
mCharacteristics.remove(characteristicHashCodeArgs) as BluetoothGattCharacteristic
val characteristicHashCode = characteristic.hashCode()
mCharacteristicsArgs.remove(characteristicHashCode)
val descriptorsArgs = characteristicArgs.descriptorsArgs.filterNotNull()
for (descriptorArgs in descriptorsArgs) {
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
val descriptor =
mDescriptors.remove(descriptorHashCodeArgs) as BluetoothGattDescriptor
val descriptorHashCode = descriptor.hashCode()
mDescriptorsArgs.remove(descriptorHashCode)
}
}
}
override fun clearServices() {
mServer.clearServices()
mServices.clear()
mCharacteristics.clear()
mDescriptors.clear()
mServicesArgs.clear()
mCharacteristicsArgs.clear()
mDescriptorsArgs.clear()
}
override fun startAdvertising(
advertisementArgs: MyAdvertisementArgs, callback: (Result<Unit>) -> Unit
) {
try {
val settings = AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).setConnectable(true)
.build()
val advertiseData = advertisementArgs.toAdvertiseData(adapter)
advertiser.startAdvertising(settings, advertiseData, advertiseCallback)
mStartAdvertisingCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun stopAdvertising() {
advertiser.stopAdvertising(advertiseCallback)
mAdvertising = false
}
override fun sendResponse(
addressArgs: String,
idArgs: Long,
statusNumberArgs: Long,
offsetArgs: Long,
valueArgs: ByteArray?
) {
val device = mDevices[addressArgs] as BluetoothDevice
val requestId = idArgs.toInt()
val statusRawArgs = statusNumberArgs.toInt()
val statusArgs = MyGattStatusArgs.ofRaw(statusRawArgs) ?: throw IllegalArgumentException()
val status = statusArgs.toStatus()
val offset = offsetArgs.toInt()
val sent = mServer.sendResponse(device, requestId, status, offset, valueArgs)
if (!sent) {
throw IllegalStateException("Send response failed.")
}
}
override fun notifyCharacteristicChanged(
hashCodeArgs: Long,
valueArgs: ByteArray,
confirmArgs: Boolean,
addressArgs: String,
callback: (Result<Unit>) -> Unit
) {
try {
val device = mDevices[addressArgs] as BluetoothDevice
val characteristic = mCharacteristics[hashCodeArgs] as BluetoothGattCharacteristic
val notifying = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val statusCode = mServer.notifyCharacteristicChanged(
device, characteristic, confirmArgs, valueArgs
)
statusCode == BluetoothStatusCodes.SUCCESS
} else {
// TODO: remove this when minSdkVersion >= 33
characteristic.value = valueArgs
mServer.notifyCharacteristicChanged(device, characteristic, confirmArgs)
}
if (!notifying) {
throw IllegalStateException()
}
mNotifyCharacteristicValueChangedCallbacks[addressArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun onPermissionsRequested(granted: Boolean) {
val callback = mSetUpCallback ?: return
val stateArgs = if (granted) {
registerReceiver()
adapter.state.toBluetoothLowEnergyStateArgs()
} else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
mOnStateChanged(stateArgs)
callback(Result.success(Unit))
}
override fun onAdapterStateChanged(state: Int) {
val stateArgs = state.toBluetoothLowEnergyStateArgs()
mOnStateChanged(stateArgs)
}
fun onServiceAdded(status: Int, service: BluetoothGattService) {
val callback = mAddServiceCallback ?: return
mAddServiceCallback = null
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val error = IllegalStateException("Read rssi failed with status: $status")
callback(Result.failure(error))
}
}
fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
mAdvertising = true
val callback = mStartAdvertisingCallback ?: return
mStartAdvertisingCallback = null
callback(Result.success(Unit))
}
fun onStartFailure(errorCode: Int) {
val callback = mStartAdvertisingCallback ?: return
mStartAdvertisingCallback = null
val error = IllegalStateException("Start advertising failed with error code: $errorCode")
callback(Result.failure(error))
}
fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
val centralArgs = device.toCentralArgs()
val addressArgs = centralArgs.addressArgs
val stateArgs = newState == BluetoothProfile.STATE_CONNECTED
mDevices[addressArgs] = device
mApi.onConnectionStateChanged(centralArgs, stateArgs) {}
}
fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
val addressArgs = device.address
val mtuArgs = mtu.toLong()
mApi.onMtuChanged(addressArgs, mtuArgs) {}
}
fun onCharacteristicReadRequest(
device: BluetoothDevice,
requestId: Int,
offset: Int,
characteristic: BluetoothGattCharacteristic
) {
val addressArgs = device.address
val hashCode = characteristic.hashCode()
val characteristicArgs = mCharacteristicsArgs[hashCode] ?: return
val hashCodeArgs = characteristicArgs.hashCodeArgs
val idArgs = requestId.toLong()
val offsetArgs = offset.toLong()
mApi.onCharacteristicReadRequest(addressArgs, hashCodeArgs, idArgs, offsetArgs) {}
}
fun onCharacteristicWriteRequest(
device: BluetoothDevice,
requestId: Int,
characteristic: BluetoothGattCharacteristic,
preparedWrite: Boolean,
responseNeeded: Boolean,
offset: Int,
value: ByteArray
) {
val addressArgs = device.address
val hashCode = characteristic.hashCode()
val characteristicArgs = mCharacteristicsArgs[hashCode] ?: return
val hashCodeArgs = characteristicArgs.hashCodeArgs
val idArgs = requestId.toLong()
val offsetArgs = offset.toLong()
mApi.onCharacteristicWriteRequest(
addressArgs, hashCodeArgs, idArgs, offsetArgs, value, preparedWrite, responseNeeded
) {}
}
fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
val addressArgs = device.address
val idArgs = requestId.toLong()
mApi.onExecuteWrite(addressArgs, idArgs, execute) {}
}
fun onDescriptorReadRequest(
device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor
) {
val addressArgs = device.address
val hashCode = descriptor.hashCode()
val descriptorArgs = mDescriptorsArgs[hashCode] ?: return
val hashCodeArgs = descriptorArgs.hashCodeArgs
val idArgs = requestId.toLong()
val offsetArgs = offset.toLong()
mApi.onDescriptorReadRequest(addressArgs, hashCodeArgs, idArgs, offsetArgs) {}
}
fun onDescriptorWriteRequest(
device: BluetoothDevice,
requestId: Int,
descriptor: BluetoothGattDescriptor,
preparedWrite: Boolean,
responseNeeded: Boolean,
offset: Int,
value: ByteArray
) {
val addressArgs = device.address
val hashCode = descriptor.hashCode()
val descriptorArgs = mDescriptorsArgs[hashCode] ?: return
val hashCodeArgs = descriptorArgs.hashCodeArgs
val idArgs = requestId.toLong()
val offsetArgs = offset.toLong()
mApi.onDescriptorWriteRequest(
addressArgs, hashCodeArgs, idArgs, offsetArgs, value, preparedWrite, responseNeeded
) {}
if (descriptor.uuid == CLIENT_CHARACTERISTIC_CONFIG_UUID) {
val characteristic = descriptor.characteristic
mOnCharacteristicNotifyStateChanged(device, characteristic, value)
}
}
fun onNotificationSent(device: BluetoothDevice, status: Int) {
val addressArgs = device.address
val callback = mNotifyCharacteristicValueChangedCallbacks.remove(addressArgs) ?: 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))
}
}
private fun mClearState() {
if (mAdvertising) {
stopAdvertising()
}
mServicesArgs.clear()
mCharacteristicsArgs.clear()
mDescriptorsArgs.clear()
mDevices.clear()
mServices.clear()
mCharacteristics.clear()
mDescriptors.clear()
mSetUpCallback = null
mAddServiceCallback = null
mStartAdvertisingCallback = null
mNotifyCharacteristicValueChangedCallbacks.clear()
}
private fun mOnStateChanged(stateArgs: MyBluetoothLowEnergyStateArgs) {
val stateNumberArgs = stateArgs.raw.toLong()
mApi.onStateChanged(stateNumberArgs) {}
// Renew GATT server when bluetooth adapter state changed.
when (stateArgs) {
MyBluetoothLowEnergyStateArgs.OFF -> mCloseServer()
MyBluetoothLowEnergyStateArgs.ON -> mOpenServer()
else -> {}
}
}
private fun mOpenServer() {
if (mOpening) {
return
}
mServer = manager.openGattServer(mContext, bluetoothGattServerCallback)
mOpening = true
}
private fun mCloseServer() {
if (!mOpening) {
return
}
mServer.close()
mOpening = false
}
private fun mOnCharacteristicNotifyStateChanged(
device: BluetoothDevice,
characteristic: BluetoothGattCharacteristic,
value: ByteArray
) {
val addressArgs = device.address
val hashCode = characteristic.hashCode()
val characteristicArgs = mCharacteristicsArgs[hashCode] ?: return
val hashCodeArgs = characteristicArgs.hashCodeArgs
val stateArgs = value.toNotifyStateArgs()
val stateNumberArgs = stateArgs.raw.toLong()
mApi.onCharacteristicNotifyStateChanged(addressArgs, hashCodeArgs, stateNumberArgs) {}
}
}

View File

@ -1,18 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_android
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
class MyRequestPermissionResultListener(manager: MyBluetoothLowEnergyManager) :
RequestPermissionsResultListener {
private val mManager: MyBluetoothLowEnergyManager
init {
mManager = manager
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, results: IntArray
): Boolean {
return mManager.onRequestPermissionsResult(requestCode, permissions, results)
}
}

View File

@ -0,0 +1,10 @@
package dev.hebei.bluetooth_low_energy_android
/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/

View File

@ -1,27 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_android
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlin.test.Test
import org.mockito.Mockito
/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
internal class BluetoothLowEnergyAndroidPluginTest {
@Test
fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
val plugin = BluetoothLowEnergyAndroidPlugin()
val call = MethodCall("getPlatformVersion", null)
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
plugin.onMethodCall(call, mockResult)
Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
}
}

View File

@ -27,7 +27,6 @@ migrate_working_dir/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

View File

@ -1,6 +1,6 @@
# bluetooth_low_energy_example
# bluetooth_low_energy_android_example
Demonstrates how to use the bluetooth_low_energy plugin.
Demonstrates how to use the bluetooth_low_energy_android plugin.
## Getting Started

View File

@ -1,68 +1,58 @@
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
flutterVersionCode = '1'
flutterVersionCode = "1"
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
flutterVersionName = '1.0'
flutterVersionName = "1.0"
}
android {
namespace "dev.yanshouwang.bluetooth_low_energy_example"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
namespace = "dev.hebei.bluetooth_low_energy_android_example"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "dev.yanshouwang.bluetooth_low_energy_example"
applicationId = "dev.hebei.bluetooth_low_energy_android_example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
// minSdkVersion flutter.minSdkVersion
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
signingConfig = signingConfigs.debug
}
}
}
flutter {
source '../..'
source = "../.."
}
dependencies {}

View File

@ -1,12 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="bluetooth_low_energy_example"
android:label="bluetooth_low_energy_android_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
@ -30,4 +31,15 @@
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@ -0,0 +1,5 @@
package dev.hebei.bluetooth_low_energy_android_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View File

@ -1,6 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

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