Compare commits
10 Commits
44efce78df
...
c68216031d
Author | SHA1 | Date | |
---|---|---|---|
c68216031d | |||
a7700eb915 | |||
9b992b4800 | |||
e24d1bc302 | |||
78e74f7793 | |||
c9f0e7e5ee | |||
87dfbc8ec6 | |||
108b6a804f | |||
71de531ceb | |||
f71f0862c5 |
4
.github/workflows/close_inactive_issues.yml
vendored
4
.github/workflows/close_inactive_issues.yml
vendored
@ -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
1
.gitignore
vendored
@ -1,2 +1 @@
|
|||||||
.vscode/
|
|
||||||
.DS_Store
|
.DS_Store
|
21
.vscode/c_cpp_properties.json
vendored
Normal file
21
.vscode/c_cpp_properties.json
vendored
Normal 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
|
||||||
|
}
|
2
LICENSE
2
LICENSE
@ -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
|
||||||
|
@ -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
bluetooth_low_energy/.gitignore
vendored
1
bluetooth_low_energy/.gitignore
vendored
@ -26,5 +26,4 @@ migrate_working_dir/
|
|||||||
/pubspec.lock
|
/pubspec.lock
|
||||||
**/doc/api/
|
**/doc/api/
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
.packages
|
|
||||||
build/
|
build/
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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).
|
||||||
|
@ -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
|
||||||
|
@ -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
|
296
bluetooth_low_energy/doc/migrations/migration-v6.md
Normal file
296
bluetooth_low_energy/doc/migrations/migration-v6.md
Normal 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;
|
||||||
|
}
|
||||||
|
```
|
1
bluetooth_low_energy/example/.gitignore
vendored
1
bluetooth_low_energy/example/.gitignore
vendored
@ -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/
|
||||||
|
@ -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 {}
|
|
||||||
|
@ -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>
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package dev.hebei.bluetooth_low_energy_example
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity()
|
@ -1,6 +0,0 @@
|
|||||||
package dev.yanshouwang.bluetooth_low_energy_example
|
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
|
||||||
}
|
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
|
||||||
plugins {
|
repositories {
|
||||||
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
plugins {
|
||||||
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
|
id "com.android.application" version "7.3.0" apply false
|
||||||
|
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
|
||||||
|
}
|
||||||
|
|
||||||
apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
include ":app"
|
||||||
|
@ -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);
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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'
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import UIKit
|
|
||||||
import Flutter
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
@ -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
1
bluetooth_low_energy/example/lib/models.dart
Normal file
1
bluetooth_low_energy/example/lib/models.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
export 'models/log.dart';
|
10
bluetooth_low_energy/example/lib/models/log.dart
Normal file
10
bluetooth_low_energy/example/lib/models/log.dart
Normal 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();
|
||||||
|
}
|
85
bluetooth_low_energy/example/lib/router_config.dart
Normal file
85
bluetooth_low_energy/example/lib/router_config.dart
Normal 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(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
5
bluetooth_low_energy/example/lib/utils/common.dart
Normal file
5
bluetooth_low_energy/example/lib/utils/common.dart
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/// 辅助函数:将数字转换为格式化的十六进制字符串(带0x前缀且至少两位数)
|
||||||
|
String toHexString(int value) {
|
||||||
|
String hex = value.toRadixString(16).padLeft(2, '0');
|
||||||
|
return '0x$hex';
|
||||||
|
}
|
230
bluetooth_low_energy/example/lib/utils/logger.dart
Normal file
230
bluetooth_low_energy/example/lib/utils/logger.dart
Normal 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
|
||||||
|
);
|
6
bluetooth_low_energy/example/lib/view_models.dart
Normal file
6
bluetooth_low_energy/example/lib/view_models.dart
Normal 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';
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
18
bluetooth_low_energy/example/lib/view_models/crc.dart
Normal file
18
bluetooth_low_energy/example/lib/view_models/crc.dart
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
112
bluetooth_low_energy/example/lib/view_models/packet.dart
Normal file
112
bluetooth_low_energy/example/lib/view_models/packet.dart
Normal 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);
|
||||||
|
}
|
282
bluetooth_low_energy/example/lib/view_models/parser.dart
Normal file
282
bluetooth_low_energy/example/lib/view_models/parser.dart
Normal 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();
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
58
bluetooth_low_energy/example/lib/view_models/receiver.dart
Normal file
58
bluetooth_low_energy/example/lib/view_models/receiver.dart
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
4
bluetooth_low_energy/example/lib/views.dart
Normal file
4
bluetooth_low_energy/example/lib/views.dart
Normal 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';
|
@ -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(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
127
bluetooth_low_energy/example/lib/views/central_manager_view.dart
Normal file
127
bluetooth_low_energy/example/lib/views/central_manager_view.dart
Normal 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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
178
bluetooth_low_energy/example/lib/views/characteristic_view.dart
Normal file
178
bluetooth_low_energy/example/lib/views/characteristic_view.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
96
bluetooth_low_energy/example/lib/views/home_view.dart
Normal file
96
bluetooth_low_energy/example/lib/views/home_view.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
43
bluetooth_low_energy/example/lib/views/log_view.dart
Normal file
43
bluetooth_low_energy/example/lib/views/log_view.dart
Normal 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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
107
bluetooth_low_energy/example/lib/views/peripheral_view.dart
Normal file
107
bluetooth_low_energy/example/lib/views/peripheral_view.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
1
bluetooth_low_energy/example/lib/widgets.dart
Normal file
1
bluetooth_low_energy/example/lib/widgets.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
export 'widgets/rssi_indicator.dart';
|
23
bluetooth_low_energy/example/lib/widgets/rssi_indicator.dart
Normal file
23
bluetooth_low_energy/example/lib/widgets/rssi_indicator.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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")
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"))
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
|
@ -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.
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
@ -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")
|
||||||
|
@ -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"));
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
|
||||||
|
@ -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:
|
||||||
|
1
bluetooth_low_energy/test/bluetooth_low_energy_test.dart
Normal file
1
bluetooth_low_energy/test/bluetooth_low_energy_test.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
void main() {}
|
1
bluetooth_low_energy_android/.gitignore
vendored
1
bluetooth_low_energy_android/.gitignore
vendored
@ -26,5 +26,4 @@ migrate_working_dir/
|
|||||||
/pubspec.lock
|
/pubspec.lock
|
||||||
**/doc/api/
|
**/doc/api/
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
.packages
|
|
||||||
build/
|
build/
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
<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" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH"
|
<uses-permission android:name="android.permission.BLUETOOTH"
|
||||||
android:maxSdkVersion="30" />
|
android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
|
||||||
android:maxSdkVersion="30" />
|
android:maxSdkVersion="30" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -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) {
|
File diff suppressed because it is too large
Load Diff
@ -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()
|
||||||
|
// }
|
@ -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
|
@ -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)
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
@ -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) {
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun authorize(callback: (Result<Boolean>) -> Unit) {
|
||||||
try {
|
try {
|
||||||
mClearState()
|
ActivityCompat.requestPermissions(activity, permissions, AUTHORIZE_CODE)
|
||||||
val stateArgs = if (hasFeature) {
|
mAuthorizeCallback = callback
|
||||||
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) {
|
} catch (e: Throwable) {
|
||||||
callback(Result.failure(e))
|
callback(Result.failure(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startDiscovery(callback: (Result<Unit>) -> Unit) {
|
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 startDiscovery(serviceUUIDsArgs: List<String>, callback: (Result<Unit>) -> Unit) {
|
||||||
try {
|
try {
|
||||||
val filters = emptyList<ScanFilter>()
|
val filters = mutableListOf<ScanFilter>()
|
||||||
val settings =
|
for (serviceUuidArgs in serviceUUIDsArgs) {
|
||||||
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
|
val serviceUUID = ParcelUuid.fromString(serviceUuidArgs)
|
||||||
mScanner.startScan(filters, settings, mScanCallback)
|
val filter = ScanFilter.Builder().setServiceUuid(serviceUUID).build()
|
||||||
executor.execute {
|
filters.add(filter)
|
||||||
onScanSucceed()
|
|
||||||
}
|
}
|
||||||
|
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
|
if (!notifying) {
|
||||||
) {
|
throw IllegalStateException()
|
||||||
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) {
|
|
||||||
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()
|
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
|
||||||
} else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
|
|
||||||
mOnStateChanged(stateArgs)
|
|
||||||
callback(Result.success(Unit))
|
|
||||||
}
|
|
||||||
|
|
||||||
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]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -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()
|
|
||||||
// }
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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
Reference in New Issue
Block a user