Compare commits

..

10 Commits

Author SHA1 Message Date
c68216031d Add packet handling and logging functionality for Bluetooth data transfer 2025-03-16 22:16:22 +08:00
a7700eb915 Add sponsors to README.md (#119)
* Add sponsors part to README.md

* Remove wrong README.md

* Add symbol link to README.md

* Update README.md

* Remove avatar.

* TODO

---------

Co-authored-by: yanshouwang <yanshouwang@outlook.com>
2025-01-05 21:46:50 +08:00
9b992b4800 force TRANSPORT_LE for connectGatt on Android 5 (#111) 2024-12-05 11:11:48 +08:00
e24d1bc302 Update close_inactive_issues.yml (#97) 2024-08-30 10:05:17 +08:00
78e74f7793 Use isMultipleAdvertisementSupported to check whether PeripheralManager is supported on this device, closes #83 (#85)
* Use `isMultipleAdvertisementSupported` to check whether `PeripheralManager` is supported on this device.

* 6.0.2

* 6.0.2

---------

Co-authored-by: yanshouwang <yanshouwang@outlook.com>
2024-06-30 20:17:57 +08:00
c9f0e7e5ee Fix the issue that advertisement name is wrong when advertising. (#78)
* Fix the issue that advertisement name is wrong when advertising.

* Update CHANGELOG.md

* Fix the warning issue.

* 6.0.1

---------

Co-authored-by: yanshouwang <yanshouwang@outlook.com>
2024-06-09 23:30:38 +08:00
87dfbc8ec6 Add state migration step #45 (#75) 2024-06-04 11:55:34 +08:00
108b6a804f 6.0.0 (#74)
* 调整接口

* 临时提交

* 重构 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>
2024-06-04 00:44:39 +08:00
71de531ceb 修复解析蓝牙广播时可能出现的空异常问题 (#60)
* 修复解析蓝牙广播时可能出现的空异常问题

* 更新 CHANGELOG

* 更新依赖项
2024-04-07 11:53:48 +08:00
f71f0862c5 更新 README.md (#51) 2024-02-01 19:25:39 +08:00
388 changed files with 25980 additions and 15101 deletions

View File

@ -13,10 +13,10 @@ jobs:
- uses: actions/stale@v5 - uses: actions/stale@v5
with: with:
days-before-issue-stale: 30 days-before-issue-stale: 30
days-before-issue-close: 14
stale-issue-label: "stale" stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." days-before-issue-close: -1
# close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1 days-before-pr-stale: -1
days-before-pr-close: -1 days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@ -1,2 +1 @@
.vscode/
.DS_Store .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 MIT License
Copyright (c) 2021 yanshouwang Copyright (c) 2024 hebei.dev
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,5 +0,0 @@
# bluetooth_low_energy
This repo contains the source code for the [`bluetooth_low_energy`][1] federated plugins.
[1]: https://pub.dev/packages/bluetooth_low_energy

1
README.md Symbolic link
View File

@ -0,0 +1 @@
bluetooth_low_energy/README.md

View File

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

View File

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

View File

@ -1,3 +1,125 @@
## 6.0.2
* `Android` [Use `isMultipleAdvertisementSupported` to check whether `PeripheralManager` is supported on this device.](https://github.com/yanshouwang/bluetooth_low_energy/issues/83).
## 6.0.1
* `Android` Fix the issue that [advertisement name is wrong when advertising](https://github.com/yanshouwang/bluetooth_low_energy/issues/62).
## 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)
## 5.0.6 ## 5.0.6
* `Android` Fix the issue that [throws when read the CCCD(Client Characteristic Config Descriptor, 0x2902)](https://github.com/yanshouwang/bluetooth_low_energy/issues/47). * `Android` Fix the issue that [throws when read the CCCD(Client Characteristic Config Descriptor, 0x2902)](https://github.com/yanshouwang/bluetooth_low_energy/issues/47).

View File

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

View File

@ -1,59 +1,120 @@
# bluetooth_low_energy # 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. ## PeripheralManager
- [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 |API|Android|iOS|macOS|Windows|Linux|
|:-|:-:|:-:|:-:|:-:|:-:|
- [x] Get/Listen the state of the peripheral manager. |logLevel|✅|✅|✅|✅||
- [x] Listen GATT characteristic read/written/notifyStateChanged. |state|✅|✅|✅|✅||
- [x] Add/Remove/Clear service(s). |stateChanged|✅|✅|✅|✅||
- [x] Start/Stop advertising. |authorize|✅|||||
- [x] Read/Write(Notify) GATT characteristics. |showAppSettings|✅|✅||||
|connectionStateChanged|✅|||||
|mtuChanged|✅|||✅||
|characteristicReadRequested|✅|✅|✅|✅||
|characteristicWriteRequested|✅|✅|✅|✅||
|characteristicNotifyStateChanged|✅|✅|✅|✅||
|descriptorReadRequested|✅|||✅||
|descriptorWriteRequested|✅|||✅||
|addService|✅|✅|✅|✅||
|removeService|✅|✅|✅|✅||
|removeAllServices|✅|✅|✅|✅||
|startAdvertising|✅|✅|✅|✅||
|stopAdvertising|✅|✅|✅|✅||
|getMaximumNotifyLength|✅|✅|✅|✅||
|respondReadRequestWithValue|✅|✅|✅|✅||
|respondReadRequestWithError|✅|✅|✅|✅||
|respondWriteRequest|✅|✅|✅|✅||
|respondWriteRequestWithError|✅|✅|✅|✅||
|notifyCharacteristic|✅|✅|✅|✅||
## Getting Started ## 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
dependencies: dependencies:
bluetooth_low_energy: ^<latest-version> 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. *Note:* Bluetooth Low Energy doesn't work on emulators, so use physical devices which has bluetooth features for development.
### Android ### 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 ### 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:* 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 ### 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. ## Sponsors
If you find this open-source project is helpful, consider to be a sponsor on GitHub Sponsors. Your support will help me dedicate more time on the open-source community.
* [Alipay](https://sponsors.hebei.dev/#/alipay)
* [WeChat Pay](https://sponsors.hebei.dev/#/wechat-pay)
* [Bitcoin Wallet](https://sponsors.hebei.dev/#/bitcoin-wallet)
<!-- TODO: Save this list to database and show this in the sponsors.hebei.dev -->
### Sponsors Details
|Date|Sponsor|Amount|
|:---:|:---:|:---:|
|2025.1.1|[Jeff-Lawson][10]|0.01177286BTC|
[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
[10]: https://github.com/Jeff-Lawson

View File

@ -0,0 +1,296 @@
# 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. Get state of the `CentralManager`.
``` Dart
// 5.x
// final state = await CentralManager.instance.getState();
// 6.x
final state = centralManager.state;
```
5. 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>
```
6. 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,
);
}
```
7. 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. Get state of the `PeripheralManager`.
``` Dart
// 5.x
// final state = await PeripheralManager.instance.getState();
// 6.x
final state = peripheralManager.state;
```
5. 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,
],
),
]
),
],
);
```
6. 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]),
),
],
);
```
7. 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);
});
```
8. 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/ .dart_tool/
.flutter-plugins .flutter-plugins
.flutter-plugins-dependencies .flutter-plugins-dependencies
.packages
.pub-cache/ .pub-cache/
.pub/ .pub/
/build/ /build/

View File

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

View File

@ -7,6 +7,7 @@
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
@ -30,4 +31,15 @@
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
</application> </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> </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 { allprojects {
repositories { repositories {
google() google()
@ -18,12 +5,12 @@ allprojects {
} }
} }
rootProject.buildDir = '../build' rootProject.buildDir = "../build"
subprojects { subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}" project.buildDir = "${rootProject.buildDir}/${project.name}"
} }
subprojects { subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(":app")
} }
tasks.register("clean", Delete) { tasks.register("clean", Delete) {

View File

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

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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") def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties" assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath return flutterSdkPath
} }()
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins { plugins {
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 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
} }
include ":app" include ":app"
apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"

View File

@ -6,17 +6,8 @@
// For more information about Flutter integration tests, please see // For more information about Flutter integration tests, please see
// https://docs.flutter.dev/cookbook/testing/integration/introduction // https://docs.flutter.dev/cookbook/testing/integration/introduction
// import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 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> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>11.0</string> <string>12.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # 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. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -1,5 +1,5 @@
PODS: PODS:
- bluetooth_low_energy_darwin (2.0.2): - bluetooth_low_energy_darwin (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- Flutter (1.0.0) - Flutter (1.0.0)
@ -20,10 +20,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/integration_test/ios" :path: ".symlinks/plugins/integration_test/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
bluetooth_low_energy_darwin: e37c1af3337ebc99a60807137dc5e47c6195eac7 bluetooth_low_energy_darwin: 764d8d1ae5abefbcdb839e812b4b25c0061fcf8b
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
integration_test: 13825b8a9334a850581300559b8839134b124670 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 */ /* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 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 */; }; 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 */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 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 */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -42,21 +42,19 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 6A108F55DE130C33FD897F6F /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; }; 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>"; };
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>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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 */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
6B5302E24483403072236BBB /* Frameworks */ = { 3B93A9D775DDA5660AD942F8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
F6040B5AA5E5A63D395CB345 /* Pods_RunnerTests.framework in Frameworks */, 80E5EA1F311778FC0849CADE /* Pods_RunnerTests.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -80,22 +80,13 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
34E52FEFFBA34148D7F4058F /* Pods_Runner.framework in Frameworks */, 2AABF0E228395C6F7AA780A3 /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
0FAACD3CD9E188E482384358 /* Frameworks */ = {
isa = PBXGroup;
children = (
2353C45A2E0DF37667D194A1 /* Pods_Runner.framework */,
076A35C4833796B0B42E0237 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
331C8082294A63A400263BE5 /* RunnerTests */ = { 331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -104,17 +95,13 @@
path = RunnerTests; path = RunnerTests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
3C9A3D0714D61C07F02BFBB4 /* Pods */ = { 600205600FA5A8A2CFBD1657 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
93DA77ACC45858A97F558838 /* Pods-Runner.debug.xcconfig */, BE7ADD24BADD6FBE9CC26391 /* Pods_Runner.framework */,
4BF6EEA10984CDD1A14F950F /* Pods-Runner.release.xcconfig */, 6A108F55DE130C33FD897F6F /* Pods_RunnerTests.framework */,
03284B9B33BEA09EAC1AFD47 /* Pods-Runner.profile.xcconfig */,
9C6D6BDEFF4878166372D24A /* Pods-RunnerTests.debug.xcconfig */,
54C98B3103488CA87E98A6F4 /* Pods-RunnerTests.release.xcconfig */,
710DD54C8BBDEA22C3F7F293 /* Pods-RunnerTests.profile.xcconfig */,
); );
path = Pods; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
9740EEB11CF90186004384FC /* Flutter */ = { 9740EEB11CF90186004384FC /* Flutter */ = {
@ -135,8 +122,8 @@
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */, 331C8082294A63A400263BE5 /* RunnerTests */,
3C9A3D0714D61C07F02BFBB4 /* Pods */, F72D04BB94F3E957C55625BE /* Pods */,
0FAACD3CD9E188E482384358 /* Frameworks */, 600205600FA5A8A2CFBD1657 /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -164,6 +151,19 @@
path = Runner; path = Runner;
sourceTree = "<group>"; 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 */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -171,10 +171,10 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = ( buildPhases = (
A3C3FEADF4EBF04832B143F6 /* [CP] Check Pods Manifest.lock */, BEFD77958F331CBECEF42D43 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */, 331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */, 331C807F294A63A400263BE5 /* Resources */,
6B5302E24483403072236BBB /* Frameworks */, 3B93A9D775DDA5660AD942F8 /* Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -190,14 +190,14 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
67E1A4356061A10A5EC11286 /* [CP] Check Pods Manifest.lock */, 10E9D52FCC12E90EB7D107E2 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */, 9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */, 97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */, 97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
066C9063BC370AC8304B71D9 /* [CP] Embed Pods Frameworks */, 2DAAF975682F92424B5A52E3 /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -215,7 +215,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = YES; BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430; LastUpgradeCheck = 1510;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
331C8080294A63A400263BE5 = { 331C8080294A63A400263BE5 = {
@ -269,7 +269,29 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase 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; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
@ -302,28 +324,6 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 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 */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;
@ -339,7 +339,7 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 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; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
@ -415,6 +415,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
@ -444,6 +445,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -452,7 +454,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -468,14 +470,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = T5YVNX2NAC;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample; PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -485,14 +487,14 @@
}; };
331C8088294A63A400263BE5 /* Debug */ = { 331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 9C6D6BDEFF4878166372D24A /* Pods-RunnerTests.debug.xcconfig */; baseConfigurationReference = 9A4F151ED2AB361B13D31B8F /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -503,14 +505,14 @@
}; };
331C8089294A63A400263BE5 /* Release */ = { 331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 54C98B3103488CA87E98A6F4 /* Pods-RunnerTests.release.xcconfig */; baseConfigurationReference = 3280CE474DAFF9A6D8555987 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -519,14 +521,14 @@
}; };
331C808A294A63A400263BE5 /* Profile */ = { 331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 710DD54C8BBDEA22C3F7F293 /* Pods-RunnerTests.profile.xcconfig */; baseConfigurationReference = AC6B889B1461AF17271694EC /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -537,6 +539,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
@ -566,6 +569,7 @@
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
@ -580,7 +584,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -592,6 +596,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
@ -621,6 +626,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -629,7 +635,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -647,14 +653,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = T5YVNX2NAC;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample; PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -670,14 +676,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = T5YVNX2NAC;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample; PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

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

View File

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

View File

@ -27,7 +27,7 @@
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSBluetoothAlwaysUsageDescription</key> <key>NSBluetoothAlwaysUsageDescription</key>
<string>Hello Bluetooth LowEnergy!</string> <string>Need to use the bluetooth to communicate with peripherals</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
<key>UILaunchStoryboardName</key> <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,5 @@
/// 辅助函数将数字转换为格式化的十六进制字符串带0x前缀且至少两位数
String toHexString(int value) {
String hex = value.toRadixString(16).padLeft(2, '0');
return '0x$hex';
}

View File

@ -0,0 +1,230 @@
import 'package:logger/logger.dart';
import 'package:logger/logger.dart' as log_show;
class logger {
static void info(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_logger.i(formattedText);
}
static void i(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_loggerSm.i(formattedText);
}
static void w(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_loggerSm.w(formattedText);
}
static void e(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_loggerSm.e(formattedText);
}
static void s(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
loggerSuccess.i(formattedText);
}
static void error(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_logger.e(formattedText);
}
static void warn(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_logger.w(formattedText);
}
static void debug(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_logger.d(formattedText);
}
static void success(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
loggerSuccess.i(formattedText);
}
static dynamic getString(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
List<dynamic> args = [
arg1,
arg2,
arg3,
arg4,
arg5,
arg6,
arg7,
arg8,
arg9,
arg10
];
// 过滤掉 null
args.removeWhere((element) => element == null);
if (args.isEmpty) return "";
if (args.length == 1) return args[0];
String formattedText = "";
for (var arg in args) {
int index = args.indexOf(arg);
if (arg == null) {
break;
}
// 如果是最后一个不加 \n
if (index == args.length - 1) {
formattedText += "$arg";
} else {
formattedText += "$arg\n";
}
}
return formattedText;
}
}
var _logger = log_show.Logger(
printer: PrettyPrinter(
stackTraceBeginIndex: 1,
methodCount: 3,
dateTimeFormat: DateTimeFormat.dateAndTime,
), // SimplePrinter PrefixPrinter LogfmtPrinter HybridPrinter PrettyPrinter
);
var _loggerSm = log_show.Logger(
printer: PrettyPrinter(
stackTraceBeginIndex: 0,
methodCount: 0,
// dateTimeFormat: DateTimeFormat.dateAndTime,
), // SimplePrinter PrefixPrinter LogfmtPrinter HybridPrinter PrettyPrinter
);
var loggerSuccess = log_show.Logger(
printer: PrettyPrinter(
stackTraceBeginIndex: 1,
methodCount: 3,
dateTimeFormat: DateTimeFormat.dateAndTime,
levelColors: {
Level.trace: AnsiColor.fg(AnsiColor.grey(0.5)),
Level.debug: const AnsiColor.none(),
Level.info: const AnsiColor.fg(41),
Level.warning: const AnsiColor.fg(208),
Level.error: const AnsiColor.fg(196),
Level.fatal: const AnsiColor.fg(199),
},
), // SimplePrinter PrefixPrinter LogfmtPrinter HybridPrinter PrettyPrinter
);

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,18 @@
// 使用crc16
import 'dart:typed_data';
int crc16(Uint8List data) {
int crc = 0;
for (int byte in data) {
crc = (crc ^ byte) & 0xFFFF;
for (int i = 0; i < 8; i++) {
if ((crc & 0x0001) != 0) {
crc = ((crc >> 1) ^ 0xA001) & 0xFFFF;
} else {
crc = (crc >> 1) & 0xFFFF;
}
}
}
return crc;
}

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,112 @@
// 包结构
import 'dart:typed_data';
import 'crc.dart';
class Packet {
PacketHeader header; // 包头
Uint8List data; // 数据
int crc; // 2字节CRC校验码
Packet(this.header, this.data, this.crc);
PacketType get type => header.type;
/// 将数据包序列化为字节数组(包头+数据+CRC
Uint8List toBytes() {
// 创建包头字节5字节
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value; // 类型
headerBytes[1] = (header.seqNum >> 8) & 0xFF; // 序列号高字节
headerBytes[2] = header.seqNum & 0xFF; // 序列号低字节
headerBytes[3] = (header.dataLen >> 8) & 0xFF; // 数据长度高字节
headerBytes[4] = header.dataLen & 0xFF; // 数据长度低字节
// 创建CRC字节2字节
Uint8List crcBytes = Uint8List(2);
crcBytes[0] = (crc >> 8) & 0xFF; // CRC高字节
crcBytes[1] = crc & 0xFF; // CRC低字节
// 合并所有部分:包头 + 数据 + CRC
return Uint8List.fromList([...headerBytes, ...data, ...crcBytes]);
}
@override
String toString() {
// 包类型、序列号、数据长度、CRC
return 'Packet{type=${header.type}, seqNum=${header.seqNum}, dataLen=${header.dataLen}, crc=${crc.toRadixString(16)},\nallData=${toBytes().map((e) => e.toRadixString(16)).join(",")}}';
}
}
// 包头结构
class PacketHeader {
PacketType type; // 1字节包类型
int seqNum; // 2字节序列号
int dataLen; // 2字节数据长度
PacketHeader(this.type, this.seqNum, this.dataLen);
}
/// 数据包类型
enum PacketType {
// 用于传输开始时发送,包含文件大小和分块大小等信息
start(0x01, '开始包'),
// 用于传输实际的文件数据块
data(0x02, '数据包'),
// 用于确认某个数据包已正确接收
ack(0x03, '确认包'),
// 用于指示某个数据包接收失败,请求重传
nak(0x04, '重传请求包'),
// 用于标识文件传输结束
end(0x05, '结束包'),
// 错误
error(0x06, '错误包'),
;
const PacketType(this.value, this.label);
final int value;
final String label;
static PacketType fromValue(int value) {
switch (value) {
case 0x01:
return start;
case 0x02:
return data;
case 0x03:
return ack;
case 0x04:
return nak;
case 0x05:
return end;
case 0x06:
return error;
// 默认为error
default:
return error;
}
}
}
// 打包函数
Packet packPacket(PacketType type, int seqNum, Uint8List? data) {
int dataLen = data?.length ?? 0;
PacketHeader header = PacketHeader(type, seqNum, dataLen);
Uint8List packetData = data ?? Uint8List(0);
// 创建包头字节5字节
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value; // 类型
headerBytes[1] = (header.seqNum >> 8) & 0xFF; // 序列号高字节
headerBytes[2] = header.seqNum & 0xFF; // 序列号低字节
headerBytes[3] = (header.dataLen >> 8) & 0xFF; // 数据长度高字节
headerBytes[4] = header.dataLen & 0xFF; // 数据长度低字节
// 合并包头和数据
Uint8List fullData = Uint8List.fromList(headerBytes + packetData);
// 计算CRC
int crc = crc16(fullData);
return Packet(header, packetData, crc);
}

View File

@ -0,0 +1,282 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'packet.dart';
// 检查数据长度是否合理 (可以设置一个最大值以防止异常)
const int maxDataLength = 1024 + 7;
/// 处理接收蓝牙数据的解析器,将字节流解析为数据包结构
class Parser {
/// 接收数据缓冲区
final List<int> _buffer = [];
final Function(Packet) onParser; // 回调函数
Parser(this.onParser);
/// 添加数据到缓冲区并尝试解析
void add(List<int> data) {
// 添加数据到缓冲区
_buffer.addAll(data);
// 尝试解析数据包
_processBuffer();
}
/// 处理缓冲区,尝试解析出完整的数据包
void _processBuffer() {
// 包头需要至少5个字节
while (_buffer.length >= 7) {
// 检查包类型是否有效
int typeValue = _buffer[0];
if (typeValue < 0x01 || typeValue > 0x06) {
// 无效包类型,丢弃当前字节并继续
_buffer.removeAt(0);
continue;
}
// 获取包类型
PacketType type = PacketType.fromValue(typeValue);
// 解析序列号和数据长度
int seqNum = (_buffer[1] << 8) | _buffer[2];
int dataLen = (_buffer[3] << 8) | _buffer[4];
// 检查数据长度是否合理 (可以设置一个最大值以防止异常)
if (dataLen < 0 || dataLen > maxDataLength) {
// 假设最大数据长度为1KB
// 数据长度异常,丢弃当前字节并继续
_buffer.removeAt(0);
continue;
}
// 检查缓冲区是否有足够的数据 (包头5字节 + 数据长度 + CRC 2字节)
int totalPacketLength = 5 + dataLen + 2;
if (_buffer.length < totalPacketLength) {
// 数据不完整,等待更多数据
break;
}
// 提取完整的数据包
List<int> packetBytes = _buffer.sublist(0, totalPacketLength);
// 验证CRC
int expectedCrc = (packetBytes[totalPacketLength - 2] << 8) | packetBytes[totalPacketLength - 1];
List<int> dataToCheck = packetBytes.sublist(0, totalPacketLength - 2);
int calculatedCrc = calculateCrc16(Uint8List.fromList(dataToCheck));
if (calculatedCrc != expectedCrc) {
// CRC校验失败丢弃当前字节并继续
_buffer.removeAt(0);
continue;
}
// CRC校验通过创建数据包对象
PacketHeader header = PacketHeader(type, seqNum, dataLen);
Uint8List data = Uint8List.fromList(_buffer.sublist(5, 5 + dataLen));
Packet packet = Packet(header, data, expectedCrc);
// 发送解析出的数据包
onParser(packet); // 回调函数
// 从缓冲区移除已处理的数据
_buffer.removeRange(0, totalPacketLength);
}
}
/// 计算CRC16校验值
int calculateCrc16(Uint8List data) {
int crc = 0;
for (int byte in data) {
crc = (crc ^ byte) & 0xFFFF;
for (int i = 0; i < 8; i++) {
if ((crc & 0x0001) != 0) {
crc = ((crc >> 1) ^ 0xA001) & 0xFFFF;
} else {
crc = (crc >> 1) & 0xFFFF;
}
}
}
return crc;
}
/// 创建开始包
Packet createStartPacket(int fileSize, int blockSize, String fileName) {
// 创建数据: [文件大小(4字节) + 块大小(2字节) + 文件名(n字节)]
// 文件大小: 高位在前
List<int> data = [
(fileSize >> 24) & 0xFF,
(fileSize >> 16) & 0xFF,
(fileSize >> 8) & 0xFF,
fileSize & 0xFF,
(blockSize >> 8) & 0xFF,
blockSize & 0xFF,
];
// 添加文件名的ASCII码
data.addAll(fileName.codeUnits);
// 创建包头
PacketHeader header = PacketHeader(PacketType.start, 0, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes, ...data]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, Uint8List.fromList(data), crc);
}
/// 创建数据包
Packet createDataPacket(int seqNum, Uint8List data) {
// 创建包头
PacketHeader header = PacketHeader(PacketType.data, seqNum, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes, ...data]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, data, crc);
}
/// 创建确认包
Packet createAckPacket(int seqNum) {
// 确认包的数据部分包含被确认的包序号
Uint8List data = Uint8List(2);
data[0] = (seqNum >> 8) & 0xFF;
data[1] = seqNum & 0xFF;
// 创建包头
PacketHeader header = PacketHeader(PacketType.ack, seqNum, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes, ...data]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, data, crc);
}
/// 创建重传请求包
Packet createNakPacket(int seqNum) {
// 重传请求包的数据部分包含需要重传的包序号
Uint8List data = Uint8List(2);
data[0] = (seqNum >> 8) & 0xFF;
data[1] = seqNum & 0xFF;
// 创建包头
PacketHeader header = PacketHeader(PacketType.nak, seqNum, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes, ...data]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, data, crc);
}
/// 创建结束包
Packet createEndPacket(int seqNum) {
// 结束包的数据部分为空
Uint8List data = Uint8List(0);
// 创建包头
PacketHeader header = PacketHeader(PacketType.end, seqNum, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, data, crc);
}
/// 创建错误包
Packet createErrorPacket(int errorCode) {
// 错误包的数据部分包含错误码
Uint8List data = Uint8List(1);
data[0] = errorCode & 0xFF;
// 创建包头 (错误包序号默认为0)
PacketHeader header = PacketHeader(PacketType.error, 0, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes, ...data]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, data, crc);
}
/// 解析开始包中的数据
Map<String, dynamic> parseStartPacketData(Uint8List data) {
// 文件大小: 4字节
int fileSize = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
// 块大小: 2字节
int blockSize = (data[4] << 8) | data[5];
// 文件名: 剩余字节
String fileName = String.fromCharCodes(data.sublist(6));
return {
'fileSize': fileSize,
'blockSize': blockSize,
'fileName': fileName,
};
}
/// 解析确认包和重传请求包中的序列号
int parseAckNakPacketData(Uint8List data) {
// 序列号: 2字节
return (data[0] << 8) | data[1];
}
/// 解析错误包中的错误码
int parseErrorPacketData(Uint8List data) {
// 错误码: 1字节
return data[0];
}
/// 重置解析器
void reset() => _buffer.clear();
}

View File

@ -0,0 +1,205 @@
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:bluetooth_low_energy_example/utils/logger.dart';
import 'package:clover/clover.dart';
import 'package:logging/logging.dart';
import 'packet.dart';
import 'parser.dart';
import 'receiver.dart';
class PeripheralManagerViewModel extends ViewModel {
final PeripheralManager _manager;
final List<Log> _logs;
bool _advertising;
late Parser parser = Parser(handleParser);
late Receiver receiver = Receiver(manager: _manager);
void handleParser(Packet packet) {
logger.i(
'收到 ${packet.type}',
'序号: ${packet.header.seqNum}',
'总长度: ${packet.toBytes().length},数据长度: ${packet.data.length}',
);
// 将接收到的数据包传递给 Receiver 处理
receiver.handlePacket(packet);
}
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,
);
});
/// 订阅写入数据请求
int receivedTotal = 0;
_characteristicWriteRequestedSubscription = _manager.characteristicWriteRequested.listen((eventArgs) async {
// 收到APP写入数据请求
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
receiver.central = eventArgs.central;
receiver.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.map((e) => e.toRadixString(16)).join(",")}',
);
_logs.add(log);
notifyListeners();
await _manager.respondWriteRequest(request);
// 解析数据包
// logger.i(
// '解析数据包,长度${eventArgs.request.value.length}',
// '[${eventArgs.request.value.map((e) => e.toRadixString(16)).join(",")}]',
// );
receivedTotal += value.length;
logger.i("接受总量:$receivedTotal");
parser.add(value);
});
_characteristicNotifyStateChangedSubscription = _manager.characteristicNotifyStateChanged.listen((eventArgs) async {
print('Characteristic notify state changed');
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,58 @@
// 接收方类
import 'dart:typed_data';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:bluetooth_low_energy_example/utils/common.dart';
import 'package:bluetooth_low_energy_example/utils/logger.dart';
import 'packet.dart';
class Receiver {
List<Uint8List> receivedChunks = [];
int expectedSeqNum = 1;
Central? central;
GATTCharacteristic? characteristic;
PeripheralManager? manager;
Receiver({this.central, this.characteristic, this.manager});
void handlePacket(Packet packet) {
if (packet.header.type == PacketType.start) {
int fileSize = (packet.data[0] << 24) | (packet.data[1] << 16) | (packet.data[2] << 8) | packet.data[3];
int chunkSize = (packet.data[4] << 8) | packet.data[5];
logger.i('收到Start包: 文件大小=$fileSize, 包大小=$chunkSize');
expectedSeqNum = 1;
receivedChunks = [];
sendAck(0);
} else if (packet.header.type == PacketType.data) {
if (packet.header.seqNum == expectedSeqNum) {
receivedChunks.add(packet.data);
// logger.i('收到Data包: 序号=${packet.header.seqNum}');
sendAck(packet.header.seqNum);
expectedSeqNum++;
} else {
logger.i('收到乱序Data包: 序号=${packet.header.seqNum},期望=$expectedSeqNum');
sendNak(expectedSeqNum);
}
} else if (packet.header.type == PacketType.end) {
logger.i('收到End包传输完成');
sendAck(packet.header.seqNum);
}
}
void sendAck(int seqNum) {
Packet ackPacket = packPacket(PacketType.ack, seqNum, Uint8List.fromList([seqNum >> 8, seqNum & 0xFF]));
logger.i('接收方发送ACK: ${ackPacket.toBytes().map((e) => toHexString(e)).join(',')}');
if (manager != null && central != null && characteristic != null) {
manager!.notifyCharacteristic(central!, characteristic!, value: ackPacket.toBytes());
}
}
void sendNak(int seqNum) {
Packet nakPacket = packPacket(PacketType.nak, seqNum, Uint8List.fromList([seqNum >> 8, seqNum & 0xFF]));
logger.i('接收方发送NAK: $seqNum');
if (manager != null && central != null && characteristic != null) {
manager!.notifyCharacteristic(central!, characteristic!, value: nakPacket.toBytes());
}
}
}

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") set(BINARY_NAME "bluetooth_low_energy_example")
# The unique GTK application identifier for this application. See: # The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID # 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 # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake. # versions of CMake.
@ -125,6 +125,12 @@ foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
COMPONENT Runtime) COMPONENT Runtime)
endforeach(bundled_library) 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 # Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install. # from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 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; 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. // Implements GObject::dispose.
static void my_application_dispose(GObject* object) { static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(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) { static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 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; G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
} }

View File

@ -8,5 +8,5 @@ import Foundation
import bluetooth_low_energy_darwin import bluetooth_low_energy_darwin
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 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: PODS:
- bluetooth_low_energy_darwin (2.0.2): - bluetooth_low_energy_darwin (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- FlutterMacOS (1.0.0) - FlutterMacOS (1.0.0)
@ -15,9 +15,9 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral :path: Flutter/ephemeral
SPEC CHECKSUMS: SPEC CHECKSUMS:
bluetooth_low_energy_darwin: e37c1af3337ebc99a60807137dc5e47c6195eac7 bluetooth_low_energy_darwin: 30da11d012831c0dbe57487b5f23bf344b90ab11
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
COCOAPODS: 1.14.3 COCOAPODS: 1.15.2

View File

@ -21,14 +21,14 @@
/* End PBXAggregateTarget section */ /* End PBXAggregateTarget section */
/* Begin PBXBuildFile 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 */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -62,9 +62,6 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; };
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>"; }; 60A2C50BFA238B286DE35299 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7618EFBF5D56DEA9B5BC6858 /* 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>"; }; 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>"; }; 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 */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -95,7 +95,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
11E67697E2DFF348C5E0FBA6 /* Pods_RunnerTests.framework in Frameworks */, 5FDEA1A8816A331CA78E7F6D /* Pods_RunnerTests.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -103,13 +103,27 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
98831461FCCC0C712B565489 /* Pods_Runner.framework in Frameworks */, 83CB8ED1FBA8CD35DAEB069E /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup 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 */ = { 331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -137,7 +151,7 @@
331C80D6294CF71000263BE5 /* RunnerTests */, 331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */, 33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */, D73912EC22F37F3D000D13A0 /* Frameworks */,
5413C8972DC878C53291A3D1 /* Pods */, 2E14B787045E896EB2B5DE61 /* Pods */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -185,24 +199,11 @@
path = Runner; path = Runner;
sourceTree = "<group>"; 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 */ = { D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7618EFBF5D56DEA9B5BC6858 /* Pods_Runner.framework */, 60A2C50BFA238B286DE35299 /* Pods_Runner.framework */,
12AE3247A53A0A6C66A10AB1 /* Pods_RunnerTests.framework */, AC81A8405E01D3966B98ABCC /* Pods_RunnerTests.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -214,7 +215,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = ( buildPhases = (
1AB4F7EB04EA489C9D3447CC /* [CP] Check Pods Manifest.lock */, 6E908EFB6D636F7BD9B901EE /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */, 331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */, 331C80D3294CF70F00263BE5 /* Resources */,
@ -233,13 +234,13 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
B609B32CB0330D926A7BF173 /* [CP] Check Pods Manifest.lock */, C410E8FE456DC70BDCFC7228 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */, 33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */, 33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */, 33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */, 3399D490228B24CF009A79C7 /* ShellScript */,
C553F2A78B180316DDD47899 /* [CP] Embed Pods Frameworks */, CBC02CA05960FFA536B17BBB /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -257,8 +258,9 @@
33CC10E52044A3C60003C045 /* Project object */ = { 33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0920; LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1430; LastUpgradeCheck = 1510;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
331C80D4294CF70F00263BE5 = { 331C80D4294CF70F00263BE5 = {
@ -321,28 +323,6 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase 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 */ = { 3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;
@ -381,7 +361,29 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; 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; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( 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"; 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; showEnvVarsInLog = 0;
}; };
C553F2A78B180316DDD47899 /* [CP] Embed Pods Frameworks */ = { CBC02CA05960FFA536B17BBB /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
@ -471,13 +473,13 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = { 331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 42843373A331898F762AE9FA /* Pods-RunnerTests.debug.xcconfig */; baseConfigurationReference = 7BDC5BC63E0570F1277EC2A6 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bluetooth_low_energy_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bluetooth_low_energy_example"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bluetooth_low_energy_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bluetooth_low_energy_example";
@ -486,13 +488,13 @@
}; };
331C80DC294CF71000263BE5 /* Release */ = { 331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 7F49A581B2FF1737F1557371 /* Pods-RunnerTests.release.xcconfig */; baseConfigurationReference = AEF60039D20E8A370442E404 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bluetooth_low_energy_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bluetooth_low_energy_example"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bluetooth_low_energy_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bluetooth_low_energy_example";
@ -501,13 +503,13 @@
}; };
331C80DD294CF71000263BE5 /* Profile */ = { 331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = BBBF70DBDF6B0B4E82F4E8A2 /* Pods-RunnerTests.profile.xcconfig */; baseConfigurationReference = ABB87EDC364CAC4062F6DE96 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/bluetooth_low_energy_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/bluetooth_low_energy_example"; 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 */; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@ -542,9 +545,11 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -592,6 +597,7 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@ -615,9 +621,11 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
@ -645,6 +653,7 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@ -668,9 +677,11 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;

View File

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

View File

@ -8,7 +8,7 @@
PRODUCT_NAME = bluetooth_low_energy_example PRODUCT_NAME = bluetooth_low_energy_example
// The application's bundle identifier // The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = dev.yanshouwang.bluetoothLowEnergyExample PRODUCT_BUNDLE_IDENTIFIER = dev.hebei.bluetoothLowEnergyExample
// The copyright displayed in application information // 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> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string> <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Hello Bluetooth LowEnergy!</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_COPYRIGHT)</string> <string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key> <key>NSMainNibFile</key>

View File

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

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -23,55 +23,55 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "5.0.6" version: "6.0.2"
bluetooth_low_energy_android: bluetooth_low_energy_android:
dependency: transitive dependency: transitive
description: description:
name: bluetooth_low_energy_android name: bluetooth_low_energy_android
sha256: b06da128d4a15e094dac67e52e701fd331441c81425b0c776598aab01a07ccfe sha256: f8cbef16b980f96c09df5d1d46b61be9f05683866151440e9987796607a4e7d8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.4" version: "6.0.3"
bluetooth_low_energy_darwin: bluetooth_low_energy_darwin:
dependency: transitive dependency: transitive
description: description:
name: bluetooth_low_energy_darwin name: bluetooth_low_energy_darwin
sha256: d8c37f27c48ad523e5c32d1002d8e67570b1f4e6d69c54e09bca152a07f6ab3f sha256: "849ba53f7d34845ad7491cd9cdb3784301aa54fe682c91cab804ed55cfd259d5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.5" version: "6.0.0"
bluetooth_low_energy_linux: bluetooth_low_energy_linux:
dependency: transitive dependency: transitive
description: description:
name: bluetooth_low_energy_linux name: bluetooth_low_energy_linux
sha256: "2809830a8b67eac5d53fdc6e907aa878ec61884d6e6a42da2b79b826410c8327" sha256: "4d1aaaede517f95320dcf9ad271091ab42c4ad8ba5bfa0e822744d850dcf0048"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.2" version: "6.0.0"
bluetooth_low_energy_platform_interface: bluetooth_low_energy_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: bluetooth_low_energy_platform_interface name: bluetooth_low_energy_platform_interface
sha256: "5af74eb8f97a896dfdbcff2da284d4fe5b4e2e49ebb2f46f826ac1810f66e4d7" sha256: bc2e8d97c141653e5747bcb3cdc9fe956541b6ecc6e5f158b99a2f3abc2d946a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.2" version: "6.0.0"
bluetooth_low_energy_windows: bluetooth_low_energy_windows:
dependency: transitive dependency: transitive
description: description:
name: bluetooth_low_energy_windows name: bluetooth_low_energy_windows
sha256: "5a0b22003d14dc84f3f16e73a77f2645df034badb9003f368e6f9bc02e8b249a" sha256: "4904530cb3e7e1dd7a66919b4c926f8a03ed9924c3c2ce068aef7e0e10ced555"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.3" version: "6.0.0"
bluez: bluez:
dependency: transitive dependency: transitive
description: description:
name: bluez name: bluez
sha256: bfd004c81e3de0f06dce8580bc39a4600e4a6efe465a866b31d4d954c9f356aa sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.1" version: "0.8.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -96,14 +96,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
collection: clover:
dependency: transitive dependency: "direct main"
description: description:
name: collection name: clover
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a sha256: eba28e69b32f174a51c093eef098cf5ae646470322727081d5d3d8f66c786487
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "3.0.0"
collection:
dependency: "direct main"
description:
name: collection
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.19.0"
convert: convert:
dependency: "direct main" dependency: "direct main"
description: description:
@ -116,10 +124,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.0.8"
dbus: dbus:
dependency: transitive dependency: transitive
description: description:
@ -140,18 +148,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
name: file name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.4" version: "7.0.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -166,20 +174,49 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" 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"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
fuchsia_remote_debug_protocol: fuchsia_remote_debug_protocol:
dependency: transitive dependency: transitive
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: cdae1b9c8bd7efadcef6112e81c903662ef2ce105cbd220a04bbb7c3425b5554
url: "https://pub.dev"
source: hosted
version: "14.2.0"
hybrid_logging:
dependency: "direct main"
description:
name: hybrid_logging
sha256: "54248d52ce68c14702a42fbc4083bac5c6be30f6afad8a41be4bbadd197b8af5"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
integration_test: integration_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -193,24 +230,48 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.19.0" version: "0.19.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.dev"
source: hosted
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.dev"
source: hosted
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "4.0.0"
log_service: logger:
dependency: transitive dependency: "direct main"
description: description:
name: log_service name: logger
sha256: "21124936899e227d1779268077921d46d57456e2592d1562e455be273594e2e4" sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "2.5.0"
logging: logging:
dependency: transitive dependency: "direct main"
description: description:
name: logging name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
@ -221,34 +282,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.11.1"
material_symbols_icons:
dependency: "direct main"
description:
name: material_symbols_icons
sha256: a2c78726048c755f0f90fd2b7c8799cd94338e2e9b7ab6498ae56503262c14bc
url: "https://pub.dev"
source: hosted
version: "4.2762.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.15.0"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -261,31 +330,31 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.2" version: "3.1.5"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.7" version: "2.1.8"
process: process:
dependency: transitive dependency: transitive
description: description:
name: process name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.4" version: "5.0.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.99" version: "0.0.0"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -298,10 +367,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.1" version: "1.12.0"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
@ -314,10 +383,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.3.0"
sync_http: sync_http:
dependency: transitive dependency: transitive
description: description:
@ -338,10 +407,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.7.3"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -362,26 +431,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.10.0" version: "14.3.0"
web:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev"
source: hosted
version: "0.3.0"
webdriver: webdriver:
dependency: transitive dependency: transitive
description: description:
name: webdriver name: webdriver
sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.4"
xml: xml:
dependency: transitive dependency: transitive
description: description:
@ -391,5 +452,5 @@ packages:
source: hosted source: hosted
version: "6.5.0" version: "6.5.0"
sdks: sdks:
dart: ">=3.2.0 <4.0.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.0.0" flutter: ">=3.18.0-18.0.pre.54"

View File

@ -1,11 +1,11 @@
name: bluetooth_low_energy_example 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 # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # 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 publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment: 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. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions
@ -27,9 +27,17 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.6
convert: ^3.1.1 clover: ^3.0.0
go_router: ^14.1.3
logging: ^1.2.0
hybrid_logging: ^1.0.0
intl: ^0.19.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
logger: #日志
dev_dependencies: dev_dependencies:
integration_test: integration_test:
@ -42,7 +50,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # 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 # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # 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 // 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. // tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart'; void main() {}
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,
);
});
}

View File

@ -89,6 +89,12 @@ if(PLUGIN_BUNDLED_LIBRARIES)
COMPONENT Runtime) COMPONENT Runtime)
endif() 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 # Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install. # from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets") set(FLUTTER_ASSET_DIR_NAME "flutter_assets")

View File

@ -6,9 +6,9 @@
#include "generated_plugin_registrant.h" #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) { void RegisterPlugins(flutter::PluginRegistry* registry) {
BluetoothLowEnergyWindowsCApiRegisterWithRegistrar( BluetoothLowEnergyWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BluetoothLowEnergyWindowsCApi")); registry->GetRegistrarForPlugin("BluetoothLowEnergyWindowsPluginCApi"));
} }

View File

@ -89,11 +89,11 @@ BEGIN
BEGIN BEGIN
BLOCK "040904e4" BLOCK "040904e4"
BEGIN BEGIN
VALUE "CompanyName", "dev.yanshouwang" "\0" VALUE "CompanyName", "dev.hebei" "\0"
VALUE "FileDescription", "bluetooth_low_energy_example" "\0" VALUE "FileDescription", "bluetooth_low_energy_example" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "bluetooth_low_energy_example" "\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 "OriginalFilename", "bluetooth_low_energy_example.exe" "\0"
VALUE "ProductName", "bluetooth_low_energy_example" "\0" VALUE "ProductName", "bluetooth_low_energy_example" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\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) { if (utf16_string == nullptr) {
return std::string(); return std::string();
} }
int target_length = ::WideCharToMultiByte( unsigned int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr) -1, nullptr, 0, nullptr, nullptr)
-1; // remove the trailing null character -1; // remove the trailing null character
int input_length = (int)wcslen(utf16_string); int input_length = (int)wcslen(utf16_string);
std::string utf8_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; return utf8_string;
} }
utf8_string.resize(target_length); 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' export 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart'
hide hide
MyBluetoothLowEnergyPeer, PlatformBluetoothLowEnergyManager,
MyCentral, PlatformCentralManager,
MyPeripheral, PlatformPeripheralManager,
MyGattAttribute, MutableGATTCharacteristic,
MyGattAttributeUint8List, ImmutableGATTCharacteristic,
MyGattDescriptor, MutableGATTDescriptor,
MyGattCharacteristic, ImmutableGATTDescriptor;
MyGattService;

View File

@ -1,25 +1,34 @@
name: bluetooth_low_energy name: bluetooth_low_energy
description: A Flutter plugin for controlling the bluetooth low energy, supports central and peripheral apis. description: "A Flutter plugin for controlling the bluetooth low energy, supports central and peripheral roles."
version: 5.0.6 version: 6.0.2
homepage: https://github.com/yanshouwang/bluetooth_low_energy 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: environment:
sdk: ">=3.0.0 <4.0.0" sdk: '>=3.0.0 <4.0.0'
flutter: ">=3.0.0" flutter: '>=3.7.0'
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
bluetooth_low_energy_platform_interface: ^5.0.2 bluetooth_low_energy_platform_interface: ^6.0.0
bluetooth_low_energy_android: ^5.0.4 bluetooth_low_energy_android: ^6.0.3
bluetooth_low_energy_darwin: ^5.0.5 bluetooth_low_energy_darwin: ^6.0.0
bluetooth_low_energy_windows: ^5.0.3 bluetooth_low_energy_windows: ^6.0.0
bluetooth_low_energy_linux: ^5.0.2 bluetooth_low_energy_linux: ^6.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^3.0.0 flutter_lints: ^4.0.0
flutter: flutter:
plugin: plugin:

View File

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

View File

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

View File

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

View File

@ -1,3 +1,76 @@
## 6.0.3
* [Use `isMultipleAdvertisementSupported` to check whether `PeripheralManager` is supported on this device.](https://github.com/yanshouwang/bluetooth_low_energy/issues/83).
## 6.0.2
* Fix the warning issue.
## 6.0.1
* Fix the issue that [advertisement name is wrong when advertising](https://github.com/yanshouwang/bluetooth_low_energy/issues/62).
## 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)
## 5.0.4 ## 5.0.4
* Change flutter minimum version to 3.0.0. * Change flutter minimum version to 3.0.0.

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <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_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_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.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
/** BluetoothLowEnergyAndroid */ /** BluetoothLowEnergyAndroidPlugin */
class BluetoothLowEnergyAndroid : FlutterPlugin, ActivityAware { class BluetoothLowEnergyAndroidPlugin : FlutterPlugin, ActivityAware {
private lateinit var mCentralManager: MyCentralManager private lateinit var mCentralManager: MyCentralManager
private lateinit var mPeripheralManager: MyPeripheralManager private lateinit var mPeripheralManager: MyPeripheralManager
@ -14,14 +14,14 @@ class BluetoothLowEnergyAndroid : FlutterPlugin, ActivityAware {
val binaryMessenger = binding.binaryMessenger val binaryMessenger = binding.binaryMessenger
mCentralManager = MyCentralManager(context, binaryMessenger) mCentralManager = MyCentralManager(context, binaryMessenger)
mPeripheralManager = MyPeripheralManager(context, binaryMessenger) mPeripheralManager = MyPeripheralManager(context, binaryMessenger)
MyCentralManagerHostApi.setUp(binaryMessenger, mCentralManager) MyCentralManagerHostAPI.setUp(binaryMessenger, mCentralManager)
MyPeripheralManagerHostApi.setUp(binaryMessenger, mPeripheralManager) MyPeripheralManagerHostAPI.setUp(binaryMessenger, mPeripheralManager)
} }
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
val binaryMessenger = binding.binaryMessenger val binaryMessenger = binding.binaryMessenger
MyCentralManagerHostApi.setUp(binaryMessenger, null) MyCentralManagerHostAPI.setUp(binaryMessenger, null)
MyPeripheralManagerHostApi.setUp(binaryMessenger, null) MyPeripheralManagerHostAPI.setUp(binaryMessenger, null)
} }
override fun onAttachedToActivity(binding: ActivityPluginBinding) { override fun onAttachedToActivity(binding: ActivityPluginBinding) {

View File

@ -0,0 +1,301 @@
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.AdvertiseSettings
import android.bluetooth.le.ScanResult
import android.os.Build
import android.os.ParcelUuid
import android.util.SparseArray
import java.util.UUID
//region ToObject
fun MyAdvertiseModeArgs.toAdvertiseMode(): Int {
return when (this) {
MyAdvertiseModeArgs.LOW_POWER -> AdvertiseSettings.ADVERTISE_MODE_LOW_POWER
MyAdvertiseModeArgs.BALANCED -> AdvertiseSettings.ADVERTISE_MODE_BALANCED
MyAdvertiseModeArgs.LOW_LATENCY -> AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY
}
}
fun MyTXPowerLevelArgs.toTXPowerLevel(): Int {
return when (this) {
MyTXPowerLevelArgs.ULTRA_LOW -> AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW
MyTXPowerLevelArgs.LOW -> AdvertiseSettings.ADVERTISE_TX_POWER_LOW
MyTXPowerLevelArgs.MEDIUM -> AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM
MyTXPowerLevelArgs.HIGH -> AdvertiseSettings.ADVERTISE_TX_POWER_HIGH
}
}
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 MyAdvertiseSettingsArgs.toAdvertiseSettings(): AdvertiseSettings {
val builder = AdvertiseSettings.Builder()
val modeArgs = this.modeArgs
if (modeArgs != null) {
val mode = modeArgs.toAdvertiseMode()
builder.setAdvertiseMode(mode)
}
val connectableArgs = this.connectableArgs
if (connectableArgs != null) {
builder.setConnectable(connectableArgs)
}
val timeoutArgs = this.timeoutArgs
if (timeoutArgs != null) {
val timeout = timeoutArgs.toInt()
builder.setTimeout(timeout)
}
val txPowerLevelArgs = this.txPowerLevelArgs
if (txPowerLevelArgs != null) {
val txPowerLevel = txPowerLevelArgs.toTXPowerLevel()
builder.setTxPowerLevel(txPowerLevel)
}
return builder.build()
}
fun MyAdvertiseDataArgs.toAdvertiseData(): AdvertiseData {
val builder = AdvertiseData.Builder()
val includeDeviceNameArgs = this.includeDeviceNameArgs
if (includeDeviceNameArgs != null) {
builder.setIncludeDeviceName(includeDeviceNameArgs)
}
val includeTXPowerLevelArgs = this.includeTXPowerLevelArgs
if (includeTXPowerLevelArgs != null) {
builder.setIncludeTxPowerLevel(includeTXPowerLevelArgs)
}
for (serviceUuidArgs in serviceUUIDsArgs) {
val serviceUUID = ParcelUuid.fromString(serviceUuidArgs)
builder.addServiceUuid(serviceUUID)
}
for (entry in serviceDataArgs) {
val serviceDataUUID = ParcelUuid.fromString(entry.key as String)
val serviceData = entry.value as ByteArray
builder.addServiceData(serviceDataUUID, serviceData)
}
for (args in manufacturerSpecificDataArgs) {
val itemArgs = args as MyManufacturerSpecificDataArgs
val manufacturerId = itemArgs.idArgs.toInt()
val manufacturerSpecificData = itemArgs.dataArgs
builder.addManufacturerData(manufacturerId, manufacturerSpecificData)
}
return builder.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

@ -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.AdvertiseCallback
import android.bluetooth.le.AdvertiseSettings 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.BluetoothGatt
import android.bluetooth.BluetoothGattCallback import android.bluetooth.BluetoothGattCallback
@ -7,8 +7,7 @@ import android.bluetooth.BluetoothGattDescriptor
import android.os.Build import android.os.Build
import java.util.concurrent.Executor import java.util.concurrent.Executor
class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) : class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) : BluetoothGattCallback() {
BluetoothGattCallback() {
private val mManager: MyCentralManager private val mManager: MyCentralManager
private val mExecutor: Executor private val mExecutor: Executor
@ -45,12 +44,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
} }
} }
override fun onCharacteristicRead( override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
status: Int
) {
super.onCharacteristicRead(gatt, characteristic, value, status) super.onCharacteristicRead(gatt, characteristic, value, status)
mExecutor.execute { mExecutor.execute {
mManager.onCharacteristicRead(gatt, characteristic, status, value) mManager.onCharacteristicRead(gatt, characteristic, status, value)
@ -58,9 +52,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
} }
// TODO: remove this override when minSdkVersion >= 33 // TODO: remove this override when minSdkVersion >= 33
override fun onCharacteristicRead( override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
) {
super.onCharacteristicRead(gatt, characteristic, status) super.onCharacteristicRead(gatt, characteristic, status)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return return
@ -71,18 +63,14 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
} }
} }
override fun onCharacteristicWrite( override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
) {
super.onCharacteristicWrite(gatt, characteristic, status) super.onCharacteristicWrite(gatt, characteristic, status)
mExecutor.execute { mExecutor.execute {
mManager.onCharacteristicWrite(gatt, characteristic, status) mManager.onCharacteristicWrite(gatt, characteristic, status)
} }
} }
override fun onCharacteristicChanged( override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray
) {
super.onCharacteristicChanged(gatt, characteristic, value) super.onCharacteristicChanged(gatt, characteristic, value)
mExecutor.execute { mExecutor.execute {
mManager.onCharacteristicChanged(gatt, characteristic, value) mManager.onCharacteristicChanged(gatt, characteristic, value)
@ -90,9 +78,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
} }
// TODO: remove this override when minSdkVersion >= 33 // TODO: remove this override when minSdkVersion >= 33
override fun onCharacteristicChanged( override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic
) {
super.onCharacteristicChanged(gatt, characteristic) super.onCharacteristicChanged(gatt, characteristic)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return return
@ -103,9 +89,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
} }
} }
override fun onDescriptorRead( override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray
) {
super.onDescriptorRead(gatt, descriptor, status, value) super.onDescriptorRead(gatt, descriptor, status, value)
mExecutor.execute { mExecutor.execute {
mManager.onDescriptorRead(gatt, descriptor, status, value) mManager.onDescriptorRead(gatt, descriptor, status, value)
@ -113,9 +97,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
} }
// TODO: remove this override when minSdkVersion >= 33 // TODO: remove this override when minSdkVersion >= 33
override fun onDescriptorRead( override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
) {
super.onDescriptorRead(gatt, descriptor, status) super.onDescriptorRead(gatt, descriptor, status)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return return
@ -126,9 +108,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
} }
} }
override fun onDescriptorWrite( override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
) {
super.onDescriptorWrite(gatt, descriptor, status) super.onDescriptorWrite(gatt, descriptor, status)
mExecutor.execute { mExecutor.execute {
mManager.onDescriptorWrite(gatt, descriptor, status) 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.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic import android.bluetooth.BluetoothGattCharacteristic
@ -7,14 +7,13 @@ import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService import android.bluetooth.BluetoothGattService
import java.util.concurrent.Executor import java.util.concurrent.Executor
class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Executor) : class MyBluetoothGattServerCallback(manager: MyPeripheralManager, mExecutor: Executor) : BluetoothGattServerCallback() {
BluetoothGattServerCallback() {
private val mManager: MyPeripheralManager private val mManager: MyPeripheralManager
private val mExecutor: Executor private val mExecutor: Executor
init { init {
mManager = manager mManager = manager
mExecutor = executor this.mExecutor = mExecutor
} }
override fun onServiceAdded(status: Int, service: BluetoothGattService) { override fun onServiceAdded(status: Int, service: BluetoothGattService) {
@ -38,41 +37,17 @@ class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Exec
} }
} }
override fun onCharacteristicReadRequest( override fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) {
device: BluetoothDevice,
requestId: Int,
offset: Int,
characteristic: BluetoothGattCharacteristic
) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic) super.onCharacteristicReadRequest(device, requestId, offset, characteristic)
mExecutor.execute { mExecutor.execute {
mManager.onCharacteristicReadRequest(device, requestId, offset, characteristic) mManager.onCharacteristicReadRequest(device, requestId, offset, characteristic)
} }
} }
override fun onCharacteristicWriteRequest( override fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
device: BluetoothDevice, super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value)
requestId: Int,
characteristic: BluetoothGattCharacteristic,
preparedWrite: Boolean,
responseNeeded: Boolean,
offset: Int,
value: ByteArray
) {
super.onCharacteristicWriteRequest(
device, requestId, characteristic, preparedWrite, responseNeeded, offset, value
)
mExecutor.execute { mExecutor.execute {
mManager.onCharacteristicWriteRequest( mManager.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value)
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)
} }
} }
@ -83,31 +58,24 @@ class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Exec
} }
} }
override fun onDescriptorReadRequest( override fun onDescriptorReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor) {
device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor
) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor) super.onDescriptorReadRequest(device, requestId, offset, descriptor)
mExecutor.execute { mExecutor.execute {
mManager.onDescriptorReadRequest(device, requestId, offset, descriptor) mManager.onDescriptorReadRequest(device, requestId, offset, descriptor)
} }
} }
override fun onDescriptorWriteRequest( override fun onDescriptorWriteRequest(device: BluetoothDevice, requestId: Int, descriptor: BluetoothGattDescriptor, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
device: BluetoothDevice, super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value)
requestId: Int,
descriptor: BluetoothGattDescriptor,
preparedWrite: Boolean,
responseNeeded: Boolean,
offset: Int,
value: ByteArray
) {
super.onDescriptorWriteRequest(
device, requestId, descriptor, preparedWrite, responseNeeded, offset, value
)
mExecutor.execute { mExecutor.execute {
mManager.onDescriptorWriteRequest( mManager.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value)
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,42 @@
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
}
private val mBroadcastReceiver: BroadcastReceiver by lazy { MyBroadcastReceiver(this) }
private val mRequestPermissionsResultListener: PluginRegistry.RequestPermissionsResultListener by lazy { MyRequestPermissionResultListener(this) }
private lateinit var mBinding: ActivityPluginBinding
init {
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)
context.registerReceiver(mBroadcastReceiver, filter)
}
val activity: Activity get() = mBinding.activity
fun onAttachedToActivity(binding: ActivityPluginBinding) {
binding.addRequestPermissionsResultListener(mRequestPermissionsResultListener)
mBinding = binding
}
fun onDetachedFromActivity() {
mBinding.removeRequestPermissionsResultListener(mRequestPermissionsResultListener)
}
abstract fun onReceive(context: Context, intent: Intent)
abstract fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): 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.BroadcastReceiver
import android.content.Context 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.BluetoothDevice
import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothStatusCodes import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.BluetoothLeScanner import android.bluetooth.le.BluetoothLeScanner
@ -13,47 +16,46 @@ import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings import android.bluetooth.le.ScanSettings
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build 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 io.flutter.plugin.common.BinaryMessenger
import java.lang.reflect.Method
import java.util.concurrent.Executor
class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) : class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) : MyBluetoothLowEnergyManager(context), MyCentralManagerHostAPI {
MyBluetoothLowEnergyManager(context), MyCentralManagerHostApi { private val mAPI: MyCentralManagerFlutterAPI
companion object {
const val REQUEST_CODE = 443
}
private val mContext: Context private val mScanCallback: ScanCallback by lazy { MyScanCallback(this) }
private val mApi: MyCentralManagerFlutterApi 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 var mDiscovering: Boolean
private val mDevices: MutableMap<String, BluetoothDevice> private val mDevices: MutableMap<String, BluetoothDevice>
private val mGATTs: MutableMap<String, BluetoothGatt> private val mGATTs: MutableMap<String, BluetoothGatt>
private val mCharacteristics: MutableMap<String, Map<Long, BluetoothGattCharacteristic>> private val mCharacteristics: MutableMap<String, MutableMap<Long, BluetoothGattCharacteristic>>
private val mDescriptors: MutableMap<String, Map<Long, BluetoothGattDescriptor>> private val mDescriptors: MutableMap<String, MutableMap<Long, BluetoothGattDescriptor>>
private var mSetUpCallback: ((Result<Unit>) -> Unit)? private var mAuthorizeCallback: ((Result<Boolean>) -> Unit)?
private var mStartDiscoveryCallback: ((Result<Unit>) -> Unit)? private var mStartDiscoveryCallback: ((Result<Unit>) -> Unit)?
private val mConnectCallbacks: MutableMap<String, (Result<Unit>) -> Unit> private val mConnectCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
private val mDisconnectCallbacks: MutableMap<String, (Result<Unit>) -> Unit> private val mDisconnectCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
private val mRequestMtuCallbacks: MutableMap<String, (Result<Long>) -> Unit> private val mRequestMtuCallbacks: MutableMap<String, (Result<Long>) -> Unit>
private val mReadRssiCallbacks: 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 mReadCharacteristicCallbacks: MutableMap<String, MutableMap<Long, (Result<ByteArray>) -> Unit>>
private val mWriteCharacteristicCallbacks: MutableMap<String, MutableMap<Long, (Result<Unit>) -> Unit>> private val mWriteCharacteristicCallbacks: MutableMap<String, MutableMap<Long, (Result<Unit>) -> Unit>>
private val mReadDescriptorCallbacks: MutableMap<String, MutableMap<Long, (Result<ByteArray>) -> Unit>> private val mReadDescriptorCallbacks: MutableMap<String, MutableMap<Long, (Result<ByteArray>) -> Unit>>
private val mWriteDescriptorCallbacks: MutableMap<String, MutableMap<Long, (Result<Unit>) -> Unit>> private val mWriteDescriptorCallbacks: MutableMap<String, MutableMap<Long, (Result<Unit>) -> Unit>>
init { init {
mContext = context mAPI = MyCentralManagerFlutterAPI(binaryMessenger)
mApi = MyCentralManagerFlutterApi(binaryMessenger)
mDiscovering = false mDiscovering = false
@ -62,7 +64,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
mCharacteristics = mutableMapOf() mCharacteristics = mutableMapOf()
mDescriptors = mutableMapOf() mDescriptors = mutableMapOf()
mSetUpCallback = null mAuthorizeCallback = null
mStartDiscoveryCallback = null mStartDiscoveryCallback = null
mConnectCallbacks = mutableMapOf() mConnectCallbacks = mutableMapOf()
mDisconnectCallbacks = mutableMapOf() mDisconnectCallbacks = mutableMapOf()
@ -75,56 +77,85 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
mWriteDescriptorCallbacks = mutableMapOf() mWriteDescriptorCallbacks = mutableMapOf()
} }
private val mScanner: BluetoothLeScanner get() = adapter.bluetoothLeScanner private val permissions: Array<String>
override val permissions: Array<String>
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf( arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_CONNECT)
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.BLUETOOTH_SCAN,
android.Manifest.permission.BLUETOOTH_CONNECT
)
} else { } else {
arrayOf( arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
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 for (gatt in mGATTs.values) {
get() = REQUEST_CODE gatt.disconnect()
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
} }
mDevices.clear()
mGATTs.clear()
mCharacteristics.clear()
mDescriptors.clear()
mAuthorizeCallback = 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 } 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) { } catch (e: Throwable) {
callback(Result.failure(e)) callback(Result.failure(e))
} }
} }
override fun startDiscovery(callback: (Result<Unit>) -> Unit) { override fun showAppSettings() {
try { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val filters = emptyList<ScanFilter>() intent.data = Uri.fromParts("package", activity.packageName, null)
val settings = val options = ActivityOptionsCompat.makeBasic().toBundle()
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build() ActivityCompat.startActivity(activity, intent, options)
mScanner.startScan(filters, settings, mScanCallback)
executor.execute {
onScanSucceed()
} }
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 mStartDiscoveryCallback = callback
} catch (e: Throwable) { } catch (e: Throwable) {
callback(Result.failure(e)) callback(Result.failure(e))
@ -132,20 +163,35 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
override fun stopDiscovery() { override fun stopDiscovery() {
mScanner.stopScan(mScanCallback) scanner.stopScan(mScanCallback)
mDiscovering = false mDiscovering = false
} }
override fun connect(addressArgs: String, callback: (Result<Unit>) -> Unit) { override fun connect(addressArgs: String, callback: (Result<Unit>) -> Unit) {
try { try {
val device = mDevices[addressArgs] as BluetoothDevice val device = mDevices[addressArgs] ?: throw IllegalArgumentException()
val autoConnect = false val autoConnect = false // Add to bluetoothGATTs cache.
// Add to bluetoothGATTs cache.
mGATTs[addressArgs] = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mGATTs[addressArgs] = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val transport = BluetoothDevice.TRANSPORT_LE val transport = BluetoothDevice.TRANSPORT_LE
device.connectGatt(mContext, autoConnect, mBluetoothGattCallback, transport) device.connectGatt(context, autoConnect, mBluetoothGattCallback, transport)
} else { } else {
device.connectGatt(mContext, autoConnect, mBluetoothGattCallback) try {
// From Android LOLLIPOP (21) the transport types exists, but it is private
// have to use reflection to call it for TRANSPORT_LE
val connectGattMethod: Method = device.javaClass.getDeclaredMethod(
"connectGatt",
Context::class.java,
Boolean::class.javaPrimitiveType,
BluetoothGattCallback::class.java,
Int::class.javaPrimitiveType
)
connectGattMethod.isAccessible = true
connectGattMethod.invoke(
device, context, autoConnect, mBluetoothGattCallback, 2 /* TRANSPORT_LE */) as BluetoothGatt
} catch (ex: Exception) {
// fall back to default method if reflection fails
device.connectGatt(context, autoConnect, mBluetoothGattCallback)
}
} }
mConnectCallbacks[addressArgs] = callback mConnectCallbacks[addressArgs] = callback
} catch (e: Throwable) { } catch (e: Throwable) {
@ -155,7 +201,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
override fun disconnect(addressArgs: String, callback: (Result<Unit>) -> Unit) { override fun disconnect(addressArgs: String, callback: (Result<Unit>) -> Unit) {
try { try {
val gatt = mGATTs[addressArgs] as BluetoothGatt val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
gatt.disconnect() gatt.disconnect()
mDisconnectCallbacks[addressArgs] = callback mDisconnectCallbacks[addressArgs] = callback
} catch (e: Throwable) { } catch (e: Throwable) {
@ -163,9 +209,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) { override fun requestMTU(addressArgs: String, mtuArgs: Long, callback: (Result<Long>) -> Unit) {
try { try {
val gatt = mGATTs[addressArgs] as BluetoothGatt val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val mtu = mtuArgs.toInt() val mtu = mtuArgs.toInt()
val requesting = gatt.requestMtu(mtu) val requesting = gatt.requestMtu(mtu)
if (!requesting) { if (!requesting) {
@ -179,7 +237,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
override fun readRSSI(addressArgs: String, callback: (Result<Long>) -> Unit) { override fun readRSSI(addressArgs: String, callback: (Result<Long>) -> Unit) {
try { try {
val gatt = mGATTs[addressArgs] as BluetoothGatt val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val reading = gatt.readRemoteRssi() val reading = gatt.readRemoteRssi()
if (!reading) { if (!reading) {
throw IllegalStateException() throw IllegalStateException()
@ -190,11 +248,9 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
} }
override fun discoverServices( override fun discoverGATT(addressArgs: String, callback: (Result<List<MyGATTServiceArgs>>) -> Unit) {
addressArgs: String, callback: (Result<List<MyGattServiceArgs>>) -> Unit
) {
try { try {
val gatt = mGATTs[addressArgs] as BluetoothGatt val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val discovering = gatt.discoverServices() val discovering = gatt.discoverServices()
if (!discovering) { if (!discovering) {
throw IllegalStateException() throw IllegalStateException()
@ -205,13 +261,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
} }
override fun readCharacteristic( override fun readCharacteristic(addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit) {
addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit
) {
try { try {
val gatt = mGATTs[addressArgs] as BluetoothGatt val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val characteristic = val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
mRetrieveCharacteristic(addressArgs, hashCodeArgs) as BluetoothGattCharacteristic
val reading = gatt.readCharacteristic(characteristic) val reading = gatt.readCharacteristic(characteristic)
if (!reading) { if (!reading) {
throw IllegalStateException() throw IllegalStateException()
@ -223,26 +276,15 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
} }
override fun writeCharacteristic( override fun writeCharacteristic(addressArgs: String, hashCodeArgs: Long, valueArgs: ByteArray, typeArgs: MyGATTCharacteristicWriteTypeArgs, callback: (Result<Unit>) -> Unit) {
addressArgs: String,
hashCodeArgs: Long,
valueArgs: ByteArray,
typeNumberArgs: Long,
callback: (Result<Unit>) -> Unit
) {
try { try {
val gatt = mGATTs[addressArgs] as BluetoothGatt val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val characteristic = val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
mRetrieveCharacteristic(addressArgs, hashCodeArgs) as BluetoothGattCharacteristic
val typeRawArgs = typeNumberArgs.toInt()
val typeArgs = MyGattCharacteristicWriteTypeArgs.ofRaw(typeRawArgs)
?: throw IllegalArgumentException()
val type = typeArgs.toType() val type = typeArgs.toType()
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val code = gatt.writeCharacteristic(characteristic, valueArgs, type) val code = gatt.writeCharacteristic(characteristic, valueArgs, type)
code == BluetoothStatusCodes.SUCCESS code == BluetoothStatusCodes.SUCCESS
} else { } else { // TODO: remove this when minSdkVersion >= 33
// TODO: remove this when minSdkVersion >= 33
characteristic.value = valueArgs characteristic.value = valueArgs
characteristic.writeType = type characteristic.writeType = type
gatt.writeCharacteristic(characteristic) gatt.writeCharacteristic(characteristic)
@ -257,47 +299,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
} }
override fun setCharacteristicNotifyState( override fun setCharacteristicNotification(addressArgs: String, hashCodeArgs: Long, enableArgs: Boolean) {
addressArgs: String, val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
hashCodeArgs: Long, val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
stateNumberArgs: Long, val notifying = gatt.setCharacteristicNotification(characteristic, enableArgs)
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)
if (!notifying) { if (!notifying) {
throw IllegalStateException() 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( override fun readDescriptor(addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit) {
addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit
) {
try { try {
val gatt = mGATTs[addressArgs] as BluetoothGatt val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val descriptor = val descriptor = retrieveDescriptor(addressArgs, hashCodeArgs)
mRetrieveDescriptor(addressArgs, hashCodeArgs) as BluetoothGattDescriptor
val reading = gatt.readDescriptor(descriptor) val reading = gatt.readDescriptor(descriptor)
if (!reading) { if (!reading) {
throw IllegalStateException() throw IllegalStateException()
@ -309,21 +323,14 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
} }
override fun writeDescriptor( override fun writeDescriptor(addressArgs: String, hashCodeArgs: Long, valueArgs: ByteArray, callback: (Result<Unit>) -> Unit) {
addressArgs: String,
hashCodeArgs: Long,
valueArgs: ByteArray,
callback: (Result<Unit>) -> Unit
) {
try { try {
val gatt = mGATTs[addressArgs] as BluetoothGatt val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val descriptor = val descriptor = retrieveDescriptor(addressArgs, hashCodeArgs)
mRetrieveDescriptor(addressArgs, hashCodeArgs) as BluetoothGattDescriptor
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val code = gatt.writeDescriptor(descriptor, valueArgs) val code = gatt.writeDescriptor(descriptor, valueArgs)
code == BluetoothStatusCodes.SUCCESS code == BluetoothStatusCodes.SUCCESS
} else { } else { // TODO: remove this when minSdkVersion >= 33
// TODO: remove this when minSdkVersion >= 33
descriptor.value = valueArgs descriptor.value = valueArgs
gatt.writeDescriptor(descriptor) gatt.writeDescriptor(descriptor)
} }
@ -337,22 +344,27 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
} }
override fun onPermissionsRequested(granted: Boolean) { override fun onReceive(context: Context, intent: Intent) {
val callback = mSetUpCallback ?: return if (intent.action != BluetoothAdapter.ACTION_STATE_CHANGED) {
val stateArgs = if (granted) { return
registerReceiver()
adapter.state.toBluetoothLowEnergyStateArgs()
} else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
mOnStateChanged(stateArgs)
callback(Result.success(Unit))
} }
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
override fun onAdapterStateChanged(state: Int) {
val stateArgs = state.toBluetoothLowEnergyStateArgs() 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
}
private fun onScanSucceeded() {
mDiscovering = true mDiscovering = true
val callback = mStartDiscoveryCallback ?: return val callback = mStartDiscoveryCallback ?: return
mStartDiscoveryCallback = null mStartDiscoveryCallback = null
@ -370,16 +382,15 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
val device = result.device val device = result.device
val peripheralArgs = device.toPeripheralArgs() val peripheralArgs = device.toPeripheralArgs()
val addressArgs = peripheralArgs.addressArgs val addressArgs = peripheralArgs.addressArgs
val rssiArgs = result.rssi.toLong() val rssiArgs = result.rssi.args
val advertisementArgs = result.toAdvertisementArgs() val advertisementArgs = result.toAdvertisementArgs()
mDevices[addressArgs] = device mDevices[addressArgs] = device
mApi.onDiscovered(peripheralArgs, rssiArgs, advertisementArgs) {} mAPI.onDiscovered(peripheralArgs, rssiArgs, advertisementArgs) {}
} }
fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val device = gatt.device val device = gatt.device
val addressArgs = device.address val addressArgs = device.address // check connection state.
// check connection state.
if (newState == BluetoothProfile.STATE_DISCONNECTED) { if (newState == BluetoothProfile.STATE_DISCONNECTED) {
gatt.close() gatt.close()
mGATTs.remove(addressArgs) mGATTs.remove(addressArgs)
@ -427,9 +438,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
} }
} }
val stateArgs = newState == BluetoothProfile.STATE_CONNECTED // check connect callback.
mApi.onConnectionStateChanged(addressArgs, stateArgs) {}
// check connect & disconnect callbacks.
val connectCallback = mConnectCallbacks.remove(addressArgs) val connectCallback = mConnectCallbacks.remove(addressArgs)
if (connectCallback != null) { if (connectCallback != null) {
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
@ -439,6 +448,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
connectCallback(Result.failure(error)) connectCallback(Result.failure(error))
} }
} }
// check disconnect callback.
val disconnectCallback = mDisconnectCallbacks.remove(addressArgs) val disconnectCallback = mDisconnectCallbacks.remove(addressArgs)
if (disconnectCallback != null) { if (disconnectCallback != null) {
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
@ -448,14 +458,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
disconnectCallback(Result.failure(error)) 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) { fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
val device = gatt.device val device = gatt.device
val addressArgs = device.address val addressArgs = device.address
val result = if (status == BluetoothGatt.GATT_SUCCESS) { val result = if (status == BluetoothGatt.GATT_SUCCESS) {
val mtuArgs = mtu.toLong() val peripheralArgs = device.toPeripheralArgs()
mApi.onMtuChanged(addressArgs, mtuArgs) {} val mtuArgs = mtu.args
mAPI.onMTUChanged(peripheralArgs, mtuArgs) {}
Result.success(mtuArgs) Result.success(mtuArgs)
} else { } else {
val error = IllegalStateException("Read RSSI failed with status: $status") val error = IllegalStateException("Read RSSI failed with status: $status")
@ -470,7 +485,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
val addressArgs = device.address val addressArgs = device.address
val callback = mReadRssiCallbacks.remove(addressArgs) ?: return val callback = mReadRssiCallbacks.remove(addressArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
val rssiArgs = rssi.toLong() val rssiArgs = rssi.args
callback(Result.success(rssiArgs)) callback(Result.success(rssiArgs))
} else { } else {
val error = IllegalStateException("Read RSSI failed with status: $status") val error = IllegalStateException("Read RSSI failed with status: $status")
@ -484,10 +499,9 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
val callback = mDiscoverServicesCallbacks.remove(addressArgs) ?: return val callback = mDiscoverServicesCallbacks.remove(addressArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
val services = gatt.services val services = gatt.services
val characteristics = services.flatMap { it.characteristics } for (service in services) {
val descriptors = characteristics.flatMap { it.descriptors } addService(addressArgs, service)
mCharacteristics[addressArgs] = characteristics.associateBy { it.hashCode().toLong() } }
mDescriptors[addressArgs] = descriptors.associateBy { it.hashCode().toLong() }
val servicesArgs = services.map { it.toArgs() } val servicesArgs = services.map { it.toArgs() }
callback(Result.success(servicesArgs)) callback(Result.success(servicesArgs))
} else { } else {
@ -496,15 +510,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
} }
fun onCharacteristicRead( fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int, value: ByteArray) {
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int,
value: ByteArray
) {
val device = gatt.device val device = gatt.device
val addressArgs = device.address val addressArgs = device.address
val hashCodeArgs = characteristic.hashCode().toLong() val hashCodeArgs = characteristic.hashCode.args
val callbacks = mReadCharacteristicCallbacks[addressArgs] ?: return val callbacks = mReadCharacteristicCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
@ -515,12 +524,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
} }
fun onCharacteristicWrite( fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
) {
val device = gatt.device val device = gatt.device
val addressArgs = device.address val addressArgs = device.address
val hashCodeArgs = characteristic.hashCode().toLong() val hashCodeArgs = characteristic.hashCode.args
val callbacks = mWriteCharacteristicCallbacks[addressArgs] ?: return val callbacks = mWriteCharacteristicCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
@ -531,21 +538,17 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
} }
fun onCharacteristicChanged( fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray
) {
val device = gatt.device val device = gatt.device
val addressArgs = device.address val peripheralArgs = device.toPeripheralArgs()
val hashCodeArgs = characteristic.hashCode().toLong() val characteristicArgs = characteristic.toArgs()
mApi.onCharacteristicNotified(addressArgs, hashCodeArgs, value) {} mAPI.onCharacteristicNotified(peripheralArgs, characteristicArgs, value) {}
} }
fun onDescriptorRead( fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray
) {
val device = gatt.device val device = gatt.device
val addressArgs = device.address val addressArgs = device.address
val hashCodeArgs = descriptor.hashCode().toLong() val hashCodeArgs = descriptor.hashCode.args
val callbacks = mReadDescriptorCallbacks[addressArgs] ?: return val callbacks = mReadDescriptorCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
@ -559,7 +562,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) { fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
val device = gatt.device val device = gatt.device
val addressArgs = device.address val addressArgs = device.address
val hashCodeArgs = descriptor.hashCode().toLong() val hashCodeArgs = descriptor.hashCode.args
val callbacks = mWriteDescriptorCallbacks[addressArgs] ?: return val callbacks = mWriteDescriptorCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
@ -570,47 +573,28 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
} }
} }
private fun mClearState() { private fun addService(addressArgs: String, service: BluetoothGattService) {
if (mDiscovering) { val includedServices = service.includedServices
stopDiscovery() for (includedService in includedServices) {
addService(addressArgs, includedService)
} }
for (gatt in mGATTs.values) { for (characteristic in service.characteristics) {
gatt.disconnect() 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) { private fun retrieveCharacteristic(addressArgs: String, hashCodeArgs: Long): BluetoothGattCharacteristic {
val stateNumberArgs = stateArgs.raw.toLong() val characteristics = mCharacteristics[addressArgs] ?: throw IllegalArgumentException()
mApi.onStateChanged(stateNumberArgs) {} return characteristics[hashCodeArgs] ?: throw IllegalArgumentException()
} }
private fun mRetrieveCharacteristic( private fun retrieveDescriptor(addressArgs: String, hashCodeArgs: Long): BluetoothGattDescriptor {
addressArgs: String, hashCodeArgs: Long val descriptors = mDescriptors[addressArgs] ?: throw IllegalArgumentException()
): BluetoothGattCharacteristic? { return descriptors[hashCodeArgs] ?: throw IllegalArgumentException()
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]
} }
} }

View File

@ -0,0 +1,447 @@
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 mSetNameCallback: ((Result<String?>) -> 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
mSetNameCallback = 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
mSetNameCallback = 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 {
// Use isMultipleAdvertisementSupported() to check whether LE Advertising is supported on this device before calling this method.
// See https://developer.android.com/reference/kotlin/android/bluetooth/BluetoothAdapter#getbluetoothleadvertiser
val supported = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) && adapter.isMultipleAdvertisementSupported
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() {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.fromParts("package", activity.packageName, null)
val options = ActivityOptionsCompat.makeBasic().toBundle()
ActivityCompat.startActivity(activity, intent, options)
}
override fun setName(nameArgs: String, callback: (Result<String?>) -> Unit) {
try {
val setting = adapter.setName(nameArgs)
if (!setting) {
throw IllegalStateException()
}
mSetNameCallback = 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(settingsArgs: MyAdvertiseSettingsArgs, advertiseDataArgs: MyAdvertiseDataArgs, scanResponseArgs: MyAdvertiseDataArgs, callback: (Result<Unit>) -> Unit) {
try {
val settings = settingsArgs.toAdvertiseSettings()
val advertiseData = advertiseDataArgs.toAdvertiseData()
val scanResponse = scanResponseArgs.toAdvertiseData()
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) {
when (intent.action) {
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
val stateArgs = state.toBluetoothLowEnergyStateArgs()
mAPI.onStateChanged(stateArgs) {}
}
BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED -> {
val callback = mSetNameCallback ?: return
mSetNameCallback = null
val nameArgs = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME)
callback(Result.success(nameArgs))
}
else -> {}
}
}
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
}
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.ScanCallback
import android.bluetooth.le.ScanResult 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()
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) {}
}
}

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