3.0.2 (#21)
* fix: 优化界面 * fix: 修复已知问题 * fix: 代码优化 * fix: 修复已知问题 * fix: 优化 getMaximumWriteLength 方法 * fix: 修改版本号 * fix: 修改版本号 * fix: 修改 CHANGELOG
This commit is contained in:
@ -1,9 +1,15 @@
|
|||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
* `Android` `iOS` Fix the issue that `getMaximumWriteLength` is wrong and coerce the value from 20 to 512.
|
||||||
|
* `Android` `iOS` Fix the issue that the peripheral manager response is wrong.
|
||||||
|
* `Android` Request MTU with 517 automatically.
|
||||||
|
|
||||||
## 3.0.1
|
## 3.0.1
|
||||||
|
|
||||||
* [Android] Clear cache when disconnected.
|
* `Android` Clear cache when disconnected.
|
||||||
* [Android] Fix GATT server error aftter bluetooth reopened.
|
* `Android` Fix GATT server error aftter bluetooth reopened.
|
||||||
* [iOS] Fix the issue that write characteristic will never complete when write without response.
|
* `iOS` Fix the issue that write characteristic will never complete when write without response.
|
||||||
* [iOS] Fix the issue that write characteristic will never complete after disconnected.
|
* `iOS` Fix the issue that write characteristic will never complete after disconnected.
|
||||||
|
|
||||||
## 3.0.0
|
## 3.0.0
|
||||||
|
|
||||||
|
@ -47,13 +47,11 @@ Remember to call `await CentralController.setUp()` before use any apis of this p
|
|||||||
|
|
||||||
Make sure you have a `miniSdkVersion` with 21 or higher in your `android/app/build.gradle` file.
|
Make sure you have a `miniSdkVersion` with 21 or higher in your `android/app/build.gradle` file.
|
||||||
|
|
||||||
*Note:* Don't call `getMaximumWriteLength` immediately when connected to a peripheral after Android 13, the `onMtuChanged` callback maybe called with connection events after Android 13, and `getMaximumWriteLength` will call `requestMtu` will also triggered `onMtuChanged`, if you called this before the connection `onMtuChanged`, you will get a fake completion and cause all methods you called before the real `onMtuChanged` triggered will never complete!
|
|
||||||
|
|
||||||
### iOS and macOS
|
### 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 Apple's [documents](https://developer.apple.com/documentation/corebluetooth/), you must include the [`NSBluetoothAlwaysUsageDescription`](https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothalwaysusagedescription) on or after iOS 13, and include the [`NSBluetoothPeripheralUsageDescription`](https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothperipheralusagedescription) key before iOS 13.
|
||||||
|
|
||||||
The `PeripheralManager#startAdvertising` only support `name` and `serviceUUIDs`, see [the startAdvertising document](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/1393252-startadvertising)
|
*Note:* The `PeripheralManager#startAdvertising` only support `name` and `serviceUUIDs`, see [the startAdvertising document](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/1393252-startadvertising)
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
|
@ -329,7 +329,7 @@ class _ScannerViewState extends State<ScannerView> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
trailing: Text('$rssi'),
|
trailing: RssiWidget(rssi),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, i) {
|
separatorBuilder: (context, i) {
|
||||||
@ -375,10 +375,13 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
late final ValueNotifier<GattCharacteristic?> characteristic;
|
late final ValueNotifier<GattCharacteristic?> characteristic;
|
||||||
late final ValueNotifier<GattCharacteristicWriteType> writeType;
|
late final ValueNotifier<GattCharacteristicWriteType> writeType;
|
||||||
late final ValueNotifier<int> maximumWriteLength;
|
late final ValueNotifier<int> maximumWriteLength;
|
||||||
|
late final ValueNotifier<int> rssi;
|
||||||
late final ValueNotifier<List<Log>> logs;
|
late final ValueNotifier<List<Log>> logs;
|
||||||
late final TextEditingController writeController;
|
late final TextEditingController writeController;
|
||||||
late final StreamSubscription stateChangedSubscription;
|
late final StreamSubscription stateChangedSubscription;
|
||||||
late final StreamSubscription valueChangedSubscription;
|
late final StreamSubscription valueChangedSubscription;
|
||||||
|
late final StreamSubscription rssiChangedSubscription;
|
||||||
|
late final Timer rssiTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -390,7 +393,8 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
service = ValueNotifier(null);
|
service = ValueNotifier(null);
|
||||||
characteristic = ValueNotifier(null);
|
characteristic = ValueNotifier(null);
|
||||||
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
|
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
|
||||||
maximumWriteLength = ValueNotifier(20);
|
maximumWriteLength = ValueNotifier(0);
|
||||||
|
rssi = ValueNotifier(-100);
|
||||||
logs = ValueNotifier([]);
|
logs = ValueNotifier([]);
|
||||||
writeController = TextEditingController();
|
writeController = TextEditingController();
|
||||||
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
|
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
|
||||||
@ -423,6 +427,17 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
rssiTimer = Timer.periodic(
|
||||||
|
const Duration(seconds: 1),
|
||||||
|
(timer) async {
|
||||||
|
final state = this.state.value;
|
||||||
|
if (state) {
|
||||||
|
rssi.value = await centralManager.readRSSI(eventArgs.peripheral);
|
||||||
|
} else {
|
||||||
|
rssi.value = -100;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -455,10 +470,18 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
final peripheral = eventArgs.peripheral;
|
final peripheral = eventArgs.peripheral;
|
||||||
if (state) {
|
if (state) {
|
||||||
await centralManager.disconnect(peripheral);
|
await centralManager.disconnect(peripheral);
|
||||||
|
maximumWriteLength.value = 0;
|
||||||
|
rssi.value = 0;
|
||||||
} else {
|
} else {
|
||||||
await centralManager.connect(peripheral);
|
await centralManager.connect(peripheral);
|
||||||
services.value =
|
services.value =
|
||||||
await centralManager.discoverGATT(peripheral);
|
await centralManager.discoverGATT(peripheral);
|
||||||
|
maximumWriteLength.value =
|
||||||
|
await centralManager.getMaximumWriteLength(
|
||||||
|
peripheral,
|
||||||
|
type: writeType.value,
|
||||||
|
);
|
||||||
|
rssi.value = await centralManager.readRSSI(peripheral);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
|
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
|
||||||
@ -592,72 +615,82 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
ValueListenableBuilder(
|
||||||
child: Center(
|
|
||||||
child: ValueListenableBuilder(
|
|
||||||
valueListenable: writeType,
|
valueListenable: writeType,
|
||||||
builder: (context, writeType, child) {
|
builder: (context, writeType, child) {
|
||||||
final items =
|
return ToggleButtons(
|
||||||
GattCharacteristicWriteType.values.map((type) {
|
onPressed: (i) async {
|
||||||
return DropdownMenuItem(
|
final type = GattCharacteristicWriteType.values[i];
|
||||||
value: type,
|
|
||||||
child: Text(
|
|
||||||
type.name,
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
return DropdownButton(
|
|
||||||
items: items,
|
|
||||||
onChanged: (type) {
|
|
||||||
if (type == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.writeType.value = type;
|
this.writeType.value = type;
|
||||||
},
|
|
||||||
value: writeType,
|
|
||||||
underline: const Offstage(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ValueListenableBuilder(
|
|
||||||
valueListenable: state,
|
|
||||||
builder: (context, state, child) {
|
|
||||||
return TextButton(
|
|
||||||
onPressed: state
|
|
||||||
? () async {
|
|
||||||
maximumWriteLength.value =
|
maximumWriteLength.value =
|
||||||
await centralManager.getMaximumWriteLength(
|
await centralManager.getMaximumWriteLength(
|
||||||
eventArgs.peripheral,
|
eventArgs.peripheral,
|
||||||
type: writeType.value,
|
type: type,
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
: null,
|
constraints: const BoxConstraints(
|
||||||
child: ValueListenableBuilder(
|
minWidth: 0.0,
|
||||||
|
minHeight: 0.0,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(4.0),
|
||||||
|
isSelected: GattCharacteristicWriteType.values
|
||||||
|
.map((type) => type == writeType)
|
||||||
|
.toList(),
|
||||||
|
children: GattCharacteristicWriteType.values.map((type) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 4.0,
|
||||||
|
),
|
||||||
|
child: Text(type.name),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
// final segments =
|
||||||
|
// GattCharacteristicWriteType.values.map((type) {
|
||||||
|
// return ButtonSegment(
|
||||||
|
// value: type,
|
||||||
|
// label: Text(type.name),
|
||||||
|
// );
|
||||||
|
// }).toList();
|
||||||
|
// return SegmentedButton(
|
||||||
|
// segments: segments,
|
||||||
|
// selected: {writeType},
|
||||||
|
// showSelectedIcon: false,
|
||||||
|
// style: OutlinedButton.styleFrom(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
// padding: EdgeInsets.zero,
|
||||||
|
// visualDensity: VisualDensity.compact,
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(8.0),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
ValueListenableBuilder(
|
||||||
|
valueListenable: state,
|
||||||
|
builder: (context, state, child) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
valueListenable: maximumWriteLength,
|
valueListenable: maximumWriteLength,
|
||||||
builder: (context, maximumWriteLength, child) {
|
builder: (context, maximumWriteLength, child) {
|
||||||
return Text('MTU: $maximumWriteLength');
|
return Text('$maximumWriteLength');
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
const Spacer(),
|
||||||
IconButton(
|
ValueListenableBuilder(
|
||||||
onPressed: () async {
|
valueListenable: rssi,
|
||||||
final rssi =
|
builder: (context, rssi, child) {
|
||||||
await centralManager.readRSSI(eventArgs.peripheral);
|
return RssiWidget(rssi);
|
||||||
log('RSSI: $rssi');
|
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.signal_wifi_4_bar),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 16.0),
|
margin: const EdgeInsets.only(bottom: 16.0),
|
||||||
height: 160.0,
|
height: 160.0,
|
||||||
child: ValueListenableBuilder(
|
child: ValueListenableBuilder(
|
||||||
valueListenable: characteristic,
|
valueListenable: characteristic,
|
||||||
@ -755,6 +788,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
rssiTimer.cancel();
|
||||||
stateChangedSubscription.cancel();
|
stateChangedSubscription.cancel();
|
||||||
valueChangedSubscription.cancel();
|
valueChangedSubscription.cancel();
|
||||||
state.dispose();
|
state.dispose();
|
||||||
@ -764,6 +798,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
characteristic.dispose();
|
characteristic.dispose();
|
||||||
writeType.dispose();
|
writeType.dispose();
|
||||||
maximumWriteLength.dispose();
|
maximumWriteLength.dispose();
|
||||||
|
rssi.dispose();
|
||||||
logs.dispose();
|
logs.dispose();
|
||||||
writeController.dispose();
|
writeController.dispose();
|
||||||
}
|
}
|
||||||
@ -1070,3 +1105,25 @@ enum LogType {
|
|||||||
notify,
|
notify,
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RssiWidget extends StatelessWidget {
|
||||||
|
final int rssi;
|
||||||
|
|
||||||
|
const RssiWidget(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,23 +23,23 @@ packages:
|
|||||||
path: ".."
|
path: ".."
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.1"
|
version: "3.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: "54fbbc1ce1a25ae20692a71585e551f1d5e2d565f41c6f9dce13457f00e9bae8"
|
sha256: "0fa5c4625ac01b6d4bbf78b10d0389d8e9907ae7c3fbc9601b481ddd4eaa86b6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.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: "114e492a020ac1efaa3b169eba8b6c01f50f97f2bbcda2f1fff890a5abf353ae"
|
sha256: "797d3803de3b124ffb13267910f8d727ae4884fdcc621ccc0995076107468bb6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.2"
|
||||||
bluetooth_low_energy_linux:
|
bluetooth_low_energy_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: bluetooth_low_energy
|
name: bluetooth_low_energy
|
||||||
description: A Flutter plugin for controlling the bluetooth low energy.
|
description: A Flutter plugin for controlling the bluetooth low energy.
|
||||||
version: 3.0.1
|
version: 3.0.2
|
||||||
homepage: https://github.com/yanshouwang/bluetooth_low_energy
|
homepage: https://github.com/yanshouwang/bluetooth_low_energy
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
@ -11,8 +11,8 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
bluetooth_low_energy_platform_interface: ^3.0.0
|
bluetooth_low_energy_platform_interface: ^3.0.0
|
||||||
bluetooth_low_energy_android: ^3.0.1
|
bluetooth_low_energy_android: ^3.0.3
|
||||||
bluetooth_low_energy_darwin: ^3.0.1
|
bluetooth_low_energy_darwin: ^3.0.2
|
||||||
bluetooth_low_energy_linux: ^3.0.0
|
bluetooth_low_energy_linux: ^3.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
|
## 3.0.3
|
||||||
|
|
||||||
|
* Fix the issue that `getMaximumWriteLength` is wrong and coerce the value from 20 to 512.
|
||||||
|
|
||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
* Request MTU with 517 automatically.
|
||||||
|
* Fix the issue taht `CentralManager#getMaximumWriteLength` is wrong when write with response and coerce the value from 20 to 512.
|
||||||
|
* Fix the issue that the GATT server response is wrong.
|
||||||
|
|
||||||
## 3.0.1
|
## 3.0.1
|
||||||
|
|
||||||
* Clear cache when disconnected.
|
* Clear cache when disconnected.
|
||||||
|
@ -351,9 +351,10 @@ interface MyCentralManagerHostApi {
|
|||||||
fun stopDiscovery()
|
fun stopDiscovery()
|
||||||
fun connect(peripheralHashCodeArgs: Long, callback: (Result<Unit>) -> Unit)
|
fun connect(peripheralHashCodeArgs: Long, callback: (Result<Unit>) -> Unit)
|
||||||
fun disconnect(peripheralHashCodeArgs: Long, callback: (Result<Unit>) -> Unit)
|
fun disconnect(peripheralHashCodeArgs: Long, callback: (Result<Unit>) -> Unit)
|
||||||
fun getMaximumWriteLength(peripheralHashCodeArgs: Long, callback: (Result<Long>) -> Unit)
|
fun getMaximumWriteLength(peripheralHashCodeArgs: Long, typeNumberArgs: Long): Long
|
||||||
fun readRSSI(peripheralHashCodeArgs: Long, callback: (Result<Long>) -> Unit)
|
fun readRSSI(peripheralHashCodeArgs: Long, callback: (Result<Long>) -> Unit)
|
||||||
fun discoverGATT(peripheralHashCodeArgs: Long, callback: (Result<List<MyGattServiceArgs>>) -> Unit)
|
fun discoverGATT(peripheralHashCodeArgs: Long, callback: (Result<List<MyGattServiceArgs>>) -> Unit)
|
||||||
|
fun requestMTU(peripheralHashCodeArgs: Long, mtuArgs: Long, callback: (Result<Long>) -> Unit)
|
||||||
fun readCharacteristic(peripheralHashCodeArgs: Long, characteristicHashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit)
|
fun readCharacteristic(peripheralHashCodeArgs: Long, characteristicHashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit)
|
||||||
fun writeCharacteristic(peripheralHashCodeArgs: Long, characteristicHashCodeArgs: Long, valueArgs: ByteArray, typeNumberArgs: Long, callback: (Result<Unit>) -> Unit)
|
fun writeCharacteristic(peripheralHashCodeArgs: Long, characteristicHashCodeArgs: Long, valueArgs: ByteArray, typeNumberArgs: Long, callback: (Result<Unit>) -> Unit)
|
||||||
fun notifyCharacteristic(peripheralHashCodeArgs: Long, characteristicHashCodeArgs: Long, stateArgs: Boolean, callback: (Result<Unit>) -> Unit)
|
fun notifyCharacteristic(peripheralHashCodeArgs: Long, characteristicHashCodeArgs: Long, stateArgs: Boolean, callback: (Result<Unit>) -> Unit)
|
||||||
@ -464,15 +465,14 @@ interface MyCentralManagerHostApi {
|
|||||||
channel.setMessageHandler { message, reply ->
|
channel.setMessageHandler { message, reply ->
|
||||||
val args = message as List<Any?>
|
val args = message as List<Any?>
|
||||||
val peripheralHashCodeArgsArg = args[0].let { if (it is Int) it.toLong() else it as Long }
|
val peripheralHashCodeArgsArg = args[0].let { if (it is Int) it.toLong() else it as Long }
|
||||||
api.getMaximumWriteLength(peripheralHashCodeArgsArg) { result: Result<Long> ->
|
val typeNumberArgsArg = args[1].let { if (it is Int) it.toLong() else it as Long }
|
||||||
val error = result.exceptionOrNull()
|
var wrapped: List<Any?>
|
||||||
if (error != null) {
|
try {
|
||||||
reply.reply(wrapError(error))
|
wrapped = listOf<Any?>(api.getMaximumWriteLength(peripheralHashCodeArgsArg, typeNumberArgsArg))
|
||||||
} else {
|
} catch (exception: Throwable) {
|
||||||
val data = result.getOrNull()
|
wrapped = wrapError(exception)
|
||||||
reply.reply(wrapResult(data))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
channel.setMessageHandler(null)
|
channel.setMessageHandler(null)
|
||||||
@ -518,6 +518,27 @@ interface MyCentralManagerHostApi {
|
|||||||
channel.setMessageHandler(null)
|
channel.setMessageHandler(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.bluetooth_low_energy_android.MyCentralManagerHostApi.requestMTU", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val peripheralHashCodeArgsArg = args[0].let { if (it is Int) it.toLong() else it as Long }
|
||||||
|
val mtuArgsArg = args[1].let { if (it is Int) it.toLong() else it as Long }
|
||||||
|
api.requestMTU(peripheralHashCodeArgsArg, mtuArgsArg) { result: Result<Long> ->
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
if (error != null) {
|
||||||
|
reply.reply(wrapError(error))
|
||||||
|
} else {
|
||||||
|
val data = result.getOrNull()
|
||||||
|
reply.reply(wrapResult(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
run {
|
run {
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.bluetooth_low_energy_android.MyCentralManagerHostApi.readCharacteristic", codec)
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.bluetooth_low_energy_android.MyCentralManagerHostApi.readCharacteristic", codec)
|
||||||
if (api != null) {
|
if (api != null) {
|
||||||
|
@ -15,6 +15,13 @@ class MyBluetoothGattServerCallback(private val peripheralManager: MyPeripheralM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
|
||||||
|
super.onConnectionStateChange(device, status, newState)
|
||||||
|
executor.execute {
|
||||||
|
peripheralManager.onConnectionStateChange(device, status, newState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
|
override fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
|
||||||
super.onMtuChanged(device, mtu)
|
super.onMtuChanged(device, mtu)
|
||||||
executor.execute {
|
executor.execute {
|
||||||
@ -36,6 +43,13 @@ class MyBluetoothGattServerCallback(private val peripheralManager: MyPeripheralM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
|
||||||
|
super.onExecuteWrite(device, requestId, execute)
|
||||||
|
executor.execute {
|
||||||
|
peripheralManager.onExecuteWrite(device, requestId, execute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onNotificationSent(device: BluetoothDevice, status: Int) {
|
override fun onNotificationSent(device: BluetoothDevice, status: Int) {
|
||||||
super.onNotificationSent(device, status)
|
super.onNotificationSent(device, status)
|
||||||
executor.execute {
|
executor.execute {
|
||||||
|
@ -29,6 +29,7 @@ class MyCentralManager(private val context: Context, binaryMessenger: BinaryMess
|
|||||||
private val services = mutableMapOf<Long, BluetoothGattService>()
|
private val services = mutableMapOf<Long, BluetoothGattService>()
|
||||||
private val characteristics = mutableMapOf<Long, BluetoothGattCharacteristic>()
|
private val characteristics = mutableMapOf<Long, BluetoothGattCharacteristic>()
|
||||||
private val descriptors = mutableMapOf<Long, BluetoothGattDescriptor>()
|
private val descriptors = mutableMapOf<Long, BluetoothGattDescriptor>()
|
||||||
|
private val mtus = mutableMapOf<Long, Int>()
|
||||||
|
|
||||||
private val peripheralsArgs = mutableMapOf<Int, MyPeripheralArgs>()
|
private val peripheralsArgs = mutableMapOf<Int, MyPeripheralArgs>()
|
||||||
private val servicesArgsOfPeripherals = mutableMapOf<Long, List<MyGattServiceArgs>>()
|
private val servicesArgsOfPeripherals = mutableMapOf<Long, List<MyGattServiceArgs>>()
|
||||||
@ -43,7 +44,7 @@ class MyCentralManager(private val context: Context, binaryMessenger: BinaryMess
|
|||||||
private var startDiscoveryCallback: ((Result<Unit>) -> Unit)? = null
|
private var startDiscoveryCallback: ((Result<Unit>) -> Unit)? = null
|
||||||
private val connectCallbacks = mutableMapOf<Long, (Result<Unit>) -> Unit>()
|
private val connectCallbacks = mutableMapOf<Long, (Result<Unit>) -> Unit>()
|
||||||
private val disconnectCallbacks = mutableMapOf<Long, (Result<Unit>) -> Unit>()
|
private val disconnectCallbacks = mutableMapOf<Long, (Result<Unit>) -> Unit>()
|
||||||
private val getMaximumWriteLengthCallbacks = mutableMapOf<Long, (Result<Long>) -> Unit>()
|
private val requestMtuCallbacks = mutableMapOf<Long, (Result<Long>) -> Unit>()
|
||||||
private val readRssiCallbacks = mutableMapOf<Long, (Result<Long>) -> Unit>()
|
private val readRssiCallbacks = mutableMapOf<Long, (Result<Long>) -> Unit>()
|
||||||
private val discoverGattCallbacks = mutableMapOf<Long, (Result<List<MyGattServiceArgs>>) -> Unit>()
|
private val discoverGattCallbacks = mutableMapOf<Long, (Result<List<MyGattServiceArgs>>) -> Unit>()
|
||||||
private val readCharacteristicCallbacks = mutableMapOf<Long, (Result<ByteArray>) -> Unit>()
|
private val readCharacteristicCallbacks = mutableMapOf<Long, (Result<ByteArray>) -> Unit>()
|
||||||
@ -89,6 +90,7 @@ class MyCentralManager(private val context: Context, binaryMessenger: BinaryMess
|
|||||||
services.clear()
|
services.clear()
|
||||||
characteristics.clear()
|
characteristics.clear()
|
||||||
descriptors.clear()
|
descriptors.clear()
|
||||||
|
mtus.clear()
|
||||||
peripheralsArgs.clear()
|
peripheralsArgs.clear()
|
||||||
servicesArgsOfPeripherals.clear()
|
servicesArgsOfPeripherals.clear()
|
||||||
servicesArgs.clear()
|
servicesArgs.clear()
|
||||||
@ -98,7 +100,7 @@ class MyCentralManager(private val context: Context, binaryMessenger: BinaryMess
|
|||||||
startDiscoveryCallback = null
|
startDiscoveryCallback = null
|
||||||
connectCallbacks.clear()
|
connectCallbacks.clear()
|
||||||
disconnectCallbacks.clear()
|
disconnectCallbacks.clear()
|
||||||
getMaximumWriteLengthCallbacks.clear()
|
requestMtuCallbacks.clear()
|
||||||
readRssiCallbacks.clear()
|
readRssiCallbacks.clear()
|
||||||
discoverGattCallbacks.clear()
|
discoverGattCallbacks.clear()
|
||||||
readCharacteristicCallbacks.clear()
|
readCharacteristicCallbacks.clear()
|
||||||
@ -172,23 +174,33 @@ class MyCentralManager(private val context: Context, binaryMessenger: BinaryMess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMaximumWriteLength(peripheralHashCodeArgs: Long, callback: (Result<Long>) -> Unit) {
|
override fun requestMTU(peripheralHashCodeArgs: Long, mtuArgs: Long, callback: (Result<Long>) -> Unit) {
|
||||||
try {
|
try {
|
||||||
val unfinishedCallback = getMaximumWriteLengthCallbacks[peripheralHashCodeArgs]
|
val unfinishedCallback = requestMtuCallbacks[peripheralHashCodeArgs]
|
||||||
if (unfinishedCallback != null) {
|
if (unfinishedCallback != null) {
|
||||||
throw IllegalStateException()
|
throw IllegalStateException()
|
||||||
}
|
}
|
||||||
val gatt = bluetoothGATTs[peripheralHashCodeArgs] as BluetoothGatt
|
val gatt = bluetoothGATTs[peripheralHashCodeArgs] as BluetoothGatt
|
||||||
val requesting = gatt.requestMtu(517)
|
val mtu = mtuArgs.toInt()
|
||||||
|
val requesting = gatt.requestMtu(mtu)
|
||||||
if (!requesting) {
|
if (!requesting) {
|
||||||
throw IllegalStateException()
|
throw IllegalStateException()
|
||||||
}
|
}
|
||||||
getMaximumWriteLengthCallbacks[peripheralHashCodeArgs] = callback
|
requestMtuCallbacks[peripheralHashCodeArgs] = callback
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
callback(Result.failure(e))
|
callback(Result.failure(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getMaximumWriteLength(peripheralHashCodeArgs: Long, typeNumberArgs: Long): Long {
|
||||||
|
val mtu = mtus[peripheralHashCodeArgs] ?: 23
|
||||||
|
val maximumWriteLength = when (typeNumberArgs.toWriteTypeArgs()) {
|
||||||
|
MyGattCharacteristicWriteTypeArgs.WITHRESPONSE -> 512
|
||||||
|
MyGattCharacteristicWriteTypeArgs.WITHOUTRESPONSE -> (mtu - 3).coerceIn(20, 512)
|
||||||
|
}
|
||||||
|
return maximumWriteLength.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
override fun readRSSI(peripheralHashCodeArgs: Long, callback: (Result<Long>) -> Unit) {
|
override fun readRSSI(peripheralHashCodeArgs: Long, callback: (Result<Long>) -> Unit) {
|
||||||
try {
|
try {
|
||||||
val unfinishedCallback = readRssiCallbacks[peripheralHashCodeArgs]
|
val unfinishedCallback = readRssiCallbacks[peripheralHashCodeArgs]
|
||||||
@ -414,14 +426,15 @@ class MyCentralManager(private val context: Context, binaryMessenger: BinaryMess
|
|||||||
val deviceHashCode = device.hashCode()
|
val deviceHashCode = device.hashCode()
|
||||||
val peripheralArgs = peripheralsArgs[deviceHashCode] as MyPeripheralArgs
|
val peripheralArgs = peripheralsArgs[deviceHashCode] as MyPeripheralArgs
|
||||||
val peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
|
val peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
|
||||||
// Check callbacks
|
// check connection state.
|
||||||
if (newState != BluetoothProfile.STATE_CONNECTED) {
|
if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||||
gatt.close()
|
gatt.close()
|
||||||
bluetoothGATTs.remove(peripheralHashCodeArgs)
|
bluetoothGATTs.remove(peripheralHashCodeArgs)
|
||||||
|
mtus.remove(peripheralHashCodeArgs)
|
||||||
val error = IllegalStateException("GATT is disconnected with status: $status")
|
val error = IllegalStateException("GATT is disconnected with status: $status")
|
||||||
val getMaximumWriteLengthCallback = getMaximumWriteLengthCallbacks.remove(peripheralHashCodeArgs)
|
val requestMtuCallback = requestMtuCallbacks.remove(peripheralHashCodeArgs)
|
||||||
if (getMaximumWriteLengthCallback != null) {
|
if (requestMtuCallback != null) {
|
||||||
getMaximumWriteLengthCallback(Result.failure(error))
|
requestMtuCallback(Result.failure(error))
|
||||||
}
|
}
|
||||||
val readRssiCallback = readRssiCallbacks.remove(peripheralHashCodeArgs)
|
val readRssiCallback = readRssiCallbacks.remove(peripheralHashCodeArgs)
|
||||||
if (readRssiCallback != null) {
|
if (readRssiCallback != null) {
|
||||||
@ -470,50 +483,43 @@ class MyCentralManager(private val context: Context, binaryMessenger: BinaryMess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check state
|
|
||||||
val connectCallback = connectCallbacks.remove(peripheralHashCodeArgs)
|
|
||||||
val disconnectCallback = disconnectCallbacks.remove(peripheralHashCodeArgs)
|
|
||||||
if (connectCallback == null && disconnectCallback == null) {
|
|
||||||
// State changed.
|
|
||||||
val stateArgs = newState == BluetoothProfile.STATE_CONNECTED
|
val stateArgs = newState == BluetoothProfile.STATE_CONNECTED
|
||||||
api.onPeripheralStateChanged(peripheralArgs, stateArgs) {}
|
api.onPeripheralStateChanged(peripheralArgs, stateArgs) {}
|
||||||
} else {
|
// check connect & disconnect callbacks.
|
||||||
|
val connectCallback = connectCallbacks.remove(peripheralHashCodeArgs)
|
||||||
|
val disconnectCallback = disconnectCallbacks.remove(peripheralHashCodeArgs)
|
||||||
if (connectCallback != null) {
|
if (connectCallback != null) {
|
||||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
// Connect succeed.
|
|
||||||
connectCallback(Result.success(Unit))
|
connectCallback(Result.success(Unit))
|
||||||
api.onPeripheralStateChanged(peripheralArgs, true) {}
|
|
||||||
} else {
|
} else {
|
||||||
// Connect failed.
|
|
||||||
val error = IllegalStateException("Connect failed with status: $status")
|
val error = IllegalStateException("Connect failed with status: $status")
|
||||||
connectCallback(Result.failure(error))
|
connectCallback(Result.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (disconnectCallback != null) {
|
if (disconnectCallback != null) {
|
||||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
// Disconnect succeed.
|
|
||||||
disconnectCallback(Result.success(Unit))
|
disconnectCallback(Result.success(Unit))
|
||||||
api.onPeripheralStateChanged(peripheralArgs, false) {}
|
|
||||||
} else {
|
} else {
|
||||||
// Disconnect failed.
|
val error = IllegalStateException("Disconnect failed with status: $status")
|
||||||
val error = IllegalStateException("Connect failed with status: $status")
|
|
||||||
disconnectCallback(Result.failure(error))
|
disconnectCallback(Result.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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 hashCode = device.hashCode()
|
val hashCode = device.hashCode()
|
||||||
val peripheralArgs = peripheralsArgs[hashCode] as MyPeripheralArgs
|
val peripheralArgs = peripheralsArgs[hashCode] as MyPeripheralArgs
|
||||||
val peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
|
val peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
|
||||||
val callback = getMaximumWriteLengthCallbacks.remove(peripheralHashCodeArgs) ?: return
|
|
||||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
val maximumWriteLengthArgs = (mtu - 3).toLong()
|
mtus[peripheralHashCodeArgs] = mtu
|
||||||
callback(Result.success(maximumWriteLengthArgs))
|
}
|
||||||
|
val callback = requestMtuCallbacks.remove(peripheralHashCodeArgs) ?: return
|
||||||
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
val mtuArgs = mtu.toLong()
|
||||||
|
callback(Result.success(mtuArgs))
|
||||||
} else {
|
} else {
|
||||||
val error = IllegalStateException("Get maximum write length failed with status: $status")
|
val error = IllegalStateException("Request MTU failed with status: $status")
|
||||||
callback(Result.failure(error))
|
callback(Result.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,9 @@ class MyPeripheralManager(private val context: Context, binaryMessenger: BinaryM
|
|||||||
private val descriptors = mutableMapOf<Long, BluetoothGattDescriptor>()
|
private val descriptors = mutableMapOf<Long, BluetoothGattDescriptor>()
|
||||||
private val mtus = mutableMapOf<Long, Int>()
|
private val mtus = mutableMapOf<Long, Int>()
|
||||||
private val confirms = mutableMapOf<Long, Boolean>()
|
private val confirms = mutableMapOf<Long, Boolean>()
|
||||||
|
private val preparedCharacteristics = mutableMapOf<Int, BluetoothGattCharacteristic>()
|
||||||
|
private val preparedValues = mutableMapOf<Int, ByteArray>()
|
||||||
|
private val values = mutableMapOf<Long, ByteArray>()
|
||||||
|
|
||||||
private val centralsArgs = mutableMapOf<Int, MyCentralArgs>()
|
private val centralsArgs = mutableMapOf<Int, MyCentralArgs>()
|
||||||
private val servicesArgs = mutableMapOf<Int, MyGattServiceArgs>()
|
private val servicesArgs = mutableMapOf<Int, MyGattServiceArgs>()
|
||||||
@ -81,6 +84,9 @@ class MyPeripheralManager(private val context: Context, binaryMessenger: BinaryM
|
|||||||
descriptors.clear()
|
descriptors.clear()
|
||||||
mtus.clear()
|
mtus.clear()
|
||||||
confirms.clear()
|
confirms.clear()
|
||||||
|
preparedCharacteristics.clear()
|
||||||
|
preparedValues.clear()
|
||||||
|
values.clear()
|
||||||
centralsArgs.clear()
|
centralsArgs.clear()
|
||||||
servicesArgs.clear()
|
servicesArgs.clear()
|
||||||
characteristicsArgs.clear()
|
characteristicsArgs.clear()
|
||||||
@ -218,7 +224,8 @@ class MyPeripheralManager(private val context: Context, binaryMessenger: BinaryM
|
|||||||
|
|
||||||
override fun getMaximumWriteLength(centralHashCodeArgs: Long): Long {
|
override fun getMaximumWriteLength(centralHashCodeArgs: Long): Long {
|
||||||
val mtu = mtus[centralHashCodeArgs] ?: 23
|
val mtu = mtus[centralHashCodeArgs] ?: 23
|
||||||
return (mtu - 3).toLong()
|
val maximumWriteLength = (mtu - 3).coerceIn(20, 512)
|
||||||
|
return maximumWriteLength.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendReadCharacteristicReply(centralHashCodeArgs: Long, characteristicHashCodeArgs: Long, idArgs: Long, offsetArgs: Long, statusArgs: Boolean, valueArgs: ByteArray) {
|
override fun sendReadCharacteristicReply(centralHashCodeArgs: Long, characteristicHashCodeArgs: Long, idArgs: Long, offsetArgs: Long, statusArgs: Boolean, valueArgs: ByteArray) {
|
||||||
@ -239,7 +246,7 @@ class MyPeripheralManager(private val context: Context, binaryMessenger: BinaryM
|
|||||||
val status = if (statusArgs) BluetoothGatt.GATT_SUCCESS
|
val status = if (statusArgs) BluetoothGatt.GATT_SUCCESS
|
||||||
else BluetoothGatt.GATT_FAILURE
|
else BluetoothGatt.GATT_FAILURE
|
||||||
val offset = offsetArgs.toInt()
|
val offset = offsetArgs.toInt()
|
||||||
val value = null
|
val value = values.remove(idArgs) as ByteArray
|
||||||
val sent = server.sendResponse(device, requestId, status, offset, value)
|
val sent = server.sendResponse(device, requestId, status, offset, value)
|
||||||
if (!sent) {
|
if (!sent) {
|
||||||
throw IllegalStateException("Send write characteristic reply failed.")
|
throw IllegalStateException("Send write characteristic reply failed.")
|
||||||
@ -335,6 +342,13 @@ class MyPeripheralManager(private val context: Context, binaryMessenger: BinaryM
|
|||||||
callback(Result.failure(error))
|
callback(Result.failure(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
|
||||||
|
val hashCode = device.hashCode()
|
||||||
|
val centralArgs = centralsArgs.getOrPut(hashCode) { device.toCentralArgs() }
|
||||||
|
val centralHashCodeArgs = centralArgs.hashCodeArgs
|
||||||
|
devices[centralHashCodeArgs] = device
|
||||||
|
}
|
||||||
|
|
||||||
fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
|
fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
|
||||||
val hashCode = device.hashCode()
|
val hashCode = device.hashCode()
|
||||||
val centralArgs = centralsArgs.getOrPut(hashCode) { device.toCentralArgs() }
|
val centralArgs = centralsArgs.getOrPut(hashCode) { device.toCentralArgs() }
|
||||||
@ -360,12 +374,50 @@ class MyPeripheralManager(private val context: Context, binaryMessenger: BinaryM
|
|||||||
val centralArgs = centralsArgs.getOrPut(deviceHashCode) { device.toCentralArgs() }
|
val centralArgs = centralsArgs.getOrPut(deviceHashCode) { device.toCentralArgs() }
|
||||||
val centralHashCodeArgs = centralArgs.hashCodeArgs
|
val centralHashCodeArgs = centralArgs.hashCodeArgs
|
||||||
devices[centralHashCodeArgs] = device
|
devices[centralHashCodeArgs] = device
|
||||||
|
if (preparedWrite) {
|
||||||
|
val preparedCharacteristic = preparedCharacteristics[deviceHashCode]
|
||||||
|
if (preparedCharacteristic != null && preparedCharacteristic != characteristic) {
|
||||||
|
val status = BluetoothGatt.GATT_CONNECTION_CONGESTED
|
||||||
|
server.sendResponse(device, requestId, status, offset, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val preparedValue = preparedValues[deviceHashCode]
|
||||||
|
if (preparedValue == null) {
|
||||||
|
preparedCharacteristics[deviceHashCode] = characteristic
|
||||||
|
preparedValues[deviceHashCode] = value
|
||||||
|
} else {
|
||||||
|
preparedValues[deviceHashCode] = preparedValue.plus(value)
|
||||||
|
}
|
||||||
|
val status = BluetoothGatt.GATT_SUCCESS
|
||||||
|
server.sendResponse(device, requestId, status, offset, value)
|
||||||
|
} else {
|
||||||
val characteristicHashCode = characteristic.hashCode()
|
val characteristicHashCode = characteristic.hashCode()
|
||||||
val characteristicArgs = characteristicsArgs[characteristicHashCode] as MyGattCharacteristicArgs
|
val characteristicArgs = characteristicsArgs[characteristicHashCode] as MyGattCharacteristicArgs
|
||||||
val idArgs = requestId.toLong()
|
val idArgs = requestId.toLong()
|
||||||
val offsetArgs = offset.toLong()
|
val offsetArgs = offset.toLong()
|
||||||
|
values[idArgs] = value
|
||||||
api.onWriteCharacteristicCommandReceived(centralArgs, characteristicArgs, idArgs, offsetArgs, value) {}
|
api.onWriteCharacteristicCommandReceived(centralArgs, characteristicArgs, idArgs, offsetArgs, value) {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
|
||||||
|
val deviceHashCode = device.hashCode()
|
||||||
|
val centralArgs = centralsArgs[deviceHashCode] as MyCentralArgs
|
||||||
|
val characteristic = preparedCharacteristics.remove(deviceHashCode) as BluetoothGattCharacteristic
|
||||||
|
val characteristicHashCode = characteristic.hashCode()
|
||||||
|
val characteristicArgs = characteristicsArgs[characteristicHashCode] as MyGattCharacteristicArgs
|
||||||
|
val value = preparedValues.remove(deviceHashCode) as ByteArray
|
||||||
|
if (execute) {
|
||||||
|
val idArgs = requestId.toLong()
|
||||||
|
val offsetArgs = 0L
|
||||||
|
values[idArgs] = value
|
||||||
|
api.onWriteCharacteristicCommandReceived(centralArgs, characteristicArgs, idArgs, offsetArgs, value) {}
|
||||||
|
} else {
|
||||||
|
val status = BluetoothGatt.GATT_SUCCESS
|
||||||
|
val offset = 0
|
||||||
|
server.sendResponse(device, requestId, status, offset, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onDescriptorReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor) {
|
fun onDescriptorReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor) {
|
||||||
val status = BluetoothGatt.GATT_SUCCESS
|
val status = BluetoothGatt.GATT_SUCCESS
|
||||||
|
@ -329,7 +329,7 @@ class _ScannerViewState extends State<ScannerView> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
trailing: Text('$rssi'),
|
trailing: RssiWidget(rssi),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, i) {
|
separatorBuilder: (context, i) {
|
||||||
@ -375,10 +375,13 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
late final ValueNotifier<GattCharacteristic?> characteristic;
|
late final ValueNotifier<GattCharacteristic?> characteristic;
|
||||||
late final ValueNotifier<GattCharacteristicWriteType> writeType;
|
late final ValueNotifier<GattCharacteristicWriteType> writeType;
|
||||||
late final ValueNotifier<int> maximumWriteLength;
|
late final ValueNotifier<int> maximumWriteLength;
|
||||||
|
late final ValueNotifier<int> rssi;
|
||||||
late final ValueNotifier<List<Log>> logs;
|
late final ValueNotifier<List<Log>> logs;
|
||||||
late final TextEditingController writeController;
|
late final TextEditingController writeController;
|
||||||
late final StreamSubscription stateChangedSubscription;
|
late final StreamSubscription stateChangedSubscription;
|
||||||
late final StreamSubscription valueChangedSubscription;
|
late final StreamSubscription valueChangedSubscription;
|
||||||
|
late final StreamSubscription rssiChangedSubscription;
|
||||||
|
late final Timer rssiTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -390,7 +393,8 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
service = ValueNotifier(null);
|
service = ValueNotifier(null);
|
||||||
characteristic = ValueNotifier(null);
|
characteristic = ValueNotifier(null);
|
||||||
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
|
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
|
||||||
maximumWriteLength = ValueNotifier(20);
|
maximumWriteLength = ValueNotifier(0);
|
||||||
|
rssi = ValueNotifier(-100);
|
||||||
logs = ValueNotifier([]);
|
logs = ValueNotifier([]);
|
||||||
writeController = TextEditingController();
|
writeController = TextEditingController();
|
||||||
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
|
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
|
||||||
@ -423,6 +427,17 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
rssiTimer = Timer.periodic(
|
||||||
|
const Duration(seconds: 1),
|
||||||
|
(timer) async {
|
||||||
|
final state = this.state.value;
|
||||||
|
if (state) {
|
||||||
|
rssi.value = await centralManager.readRSSI(eventArgs.peripheral);
|
||||||
|
} else {
|
||||||
|
rssi.value = -100;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -455,10 +470,18 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
final peripheral = eventArgs.peripheral;
|
final peripheral = eventArgs.peripheral;
|
||||||
if (state) {
|
if (state) {
|
||||||
await centralManager.disconnect(peripheral);
|
await centralManager.disconnect(peripheral);
|
||||||
|
maximumWriteLength.value = 0;
|
||||||
|
rssi.value = 0;
|
||||||
} else {
|
} else {
|
||||||
await centralManager.connect(peripheral);
|
await centralManager.connect(peripheral);
|
||||||
services.value =
|
services.value =
|
||||||
await centralManager.discoverGATT(peripheral);
|
await centralManager.discoverGATT(peripheral);
|
||||||
|
maximumWriteLength.value =
|
||||||
|
await centralManager.getMaximumWriteLength(
|
||||||
|
peripheral,
|
||||||
|
type: writeType.value,
|
||||||
|
);
|
||||||
|
rssi.value = await centralManager.readRSSI(peripheral);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
|
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
|
||||||
@ -592,72 +615,82 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
ValueListenableBuilder(
|
||||||
child: Center(
|
|
||||||
child: ValueListenableBuilder(
|
|
||||||
valueListenable: writeType,
|
valueListenable: writeType,
|
||||||
builder: (context, writeType, child) {
|
builder: (context, writeType, child) {
|
||||||
final items =
|
return ToggleButtons(
|
||||||
GattCharacteristicWriteType.values.map((type) {
|
onPressed: (i) async {
|
||||||
return DropdownMenuItem(
|
final type = GattCharacteristicWriteType.values[i];
|
||||||
value: type,
|
|
||||||
child: Text(
|
|
||||||
type.name,
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
return DropdownButton(
|
|
||||||
items: items,
|
|
||||||
onChanged: (type) {
|
|
||||||
if (type == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.writeType.value = type;
|
this.writeType.value = type;
|
||||||
},
|
|
||||||
value: writeType,
|
|
||||||
underline: const Offstage(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ValueListenableBuilder(
|
|
||||||
valueListenable: state,
|
|
||||||
builder: (context, state, child) {
|
|
||||||
return TextButton(
|
|
||||||
onPressed: state
|
|
||||||
? () async {
|
|
||||||
maximumWriteLength.value =
|
maximumWriteLength.value =
|
||||||
await centralManager.getMaximumWriteLength(
|
await centralManager.getMaximumWriteLength(
|
||||||
eventArgs.peripheral,
|
eventArgs.peripheral,
|
||||||
type: writeType.value,
|
type: type,
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
: null,
|
constraints: const BoxConstraints(
|
||||||
child: ValueListenableBuilder(
|
minWidth: 0.0,
|
||||||
|
minHeight: 0.0,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(4.0),
|
||||||
|
isSelected: GattCharacteristicWriteType.values
|
||||||
|
.map((type) => type == writeType)
|
||||||
|
.toList(),
|
||||||
|
children: GattCharacteristicWriteType.values.map((type) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 4.0,
|
||||||
|
),
|
||||||
|
child: Text(type.name),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
// final segments =
|
||||||
|
// GattCharacteristicWriteType.values.map((type) {
|
||||||
|
// return ButtonSegment(
|
||||||
|
// value: type,
|
||||||
|
// label: Text(type.name),
|
||||||
|
// );
|
||||||
|
// }).toList();
|
||||||
|
// return SegmentedButton(
|
||||||
|
// segments: segments,
|
||||||
|
// selected: {writeType},
|
||||||
|
// showSelectedIcon: false,
|
||||||
|
// style: OutlinedButton.styleFrom(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
// padding: EdgeInsets.zero,
|
||||||
|
// visualDensity: VisualDensity.compact,
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(8.0),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
ValueListenableBuilder(
|
||||||
|
valueListenable: state,
|
||||||
|
builder: (context, state, child) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
valueListenable: maximumWriteLength,
|
valueListenable: maximumWriteLength,
|
||||||
builder: (context, maximumWriteLength, child) {
|
builder: (context, maximumWriteLength, child) {
|
||||||
return Text('MTU: $maximumWriteLength');
|
return Text('$maximumWriteLength');
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
const Spacer(),
|
||||||
IconButton(
|
ValueListenableBuilder(
|
||||||
onPressed: () async {
|
valueListenable: rssi,
|
||||||
final rssi =
|
builder: (context, rssi, child) {
|
||||||
await centralManager.readRSSI(eventArgs.peripheral);
|
return RssiWidget(rssi);
|
||||||
log('RSSI: $rssi');
|
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.signal_wifi_4_bar),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 16.0),
|
margin: const EdgeInsets.only(bottom: 16.0),
|
||||||
height: 160.0,
|
height: 160.0,
|
||||||
child: ValueListenableBuilder(
|
child: ValueListenableBuilder(
|
||||||
valueListenable: characteristic,
|
valueListenable: characteristic,
|
||||||
@ -755,6 +788,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
rssiTimer.cancel();
|
||||||
stateChangedSubscription.cancel();
|
stateChangedSubscription.cancel();
|
||||||
valueChangedSubscription.cancel();
|
valueChangedSubscription.cancel();
|
||||||
state.dispose();
|
state.dispose();
|
||||||
@ -764,6 +798,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
characteristic.dispose();
|
characteristic.dispose();
|
||||||
writeType.dispose();
|
writeType.dispose();
|
||||||
maximumWriteLength.dispose();
|
maximumWriteLength.dispose();
|
||||||
|
rssi.dispose();
|
||||||
logs.dispose();
|
logs.dispose();
|
||||||
writeController.dispose();
|
writeController.dispose();
|
||||||
}
|
}
|
||||||
@ -1070,3 +1105,25 @@ enum LogType {
|
|||||||
notify,
|
notify,
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RssiWidget extends StatelessWidget {
|
||||||
|
final int rssi;
|
||||||
|
|
||||||
|
const RssiWidget(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,7 +15,7 @@ packages:
|
|||||||
path: ".."
|
path: ".."
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0"
|
version: "3.0.3"
|
||||||
bluetooth_low_energy_platform_interface:
|
bluetooth_low_energy_platform_interface:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -448,12 +448,12 @@ class MyCentralManagerHostApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getMaximumWriteLength(int arg_peripheralHashCodeArgs) async {
|
Future<int> getMaximumWriteLength(int arg_peripheralHashCodeArgs, int arg_typeNumberArgs) async {
|
||||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.bluetooth_low_energy_android.MyCentralManagerHostApi.getMaximumWriteLength', codec,
|
'dev.flutter.pigeon.bluetooth_low_energy_android.MyCentralManagerHostApi.getMaximumWriteLength', codec,
|
||||||
binaryMessenger: _binaryMessenger);
|
binaryMessenger: _binaryMessenger);
|
||||||
final List<Object?>? replyList =
|
final List<Object?>? replyList =
|
||||||
await channel.send(<Object?>[arg_peripheralHashCodeArgs]) as List<Object?>?;
|
await channel.send(<Object?>[arg_peripheralHashCodeArgs, arg_typeNumberArgs]) as List<Object?>?;
|
||||||
if (replyList == null) {
|
if (replyList == null) {
|
||||||
throw PlatformException(
|
throw PlatformException(
|
||||||
code: 'channel-error',
|
code: 'channel-error',
|
||||||
@ -529,6 +529,33 @@ class MyCentralManagerHostApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> requestMTU(int arg_peripheralHashCodeArgs, int arg_mtuArgs) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.bluetooth_low_energy_android.MyCentralManagerHostApi.requestMTU', codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_peripheralHashCodeArgs, arg_mtuArgs]) as List<Object?>?;
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel.',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: replyList[0]! as String,
|
||||||
|
message: replyList[1] as String?,
|
||||||
|
details: replyList[2],
|
||||||
|
);
|
||||||
|
} else if (replyList[0] == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'null-error',
|
||||||
|
message: 'Host platform returned null value for non-null return value.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (replyList[0] as int?)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Uint8List> readCharacteristic(int arg_peripheralHashCodeArgs, int arg_characteristicHashCodeArgs) async {
|
Future<Uint8List> readCharacteristic(int arg_peripheralHashCodeArgs, int arg_characteristicHashCodeArgs) async {
|
||||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.bluetooth_low_energy_android.MyCentralManagerHostApi.readCharacteristic', codec,
|
'dev.flutter.pigeon.bluetooth_low_energy_android.MyCentralManagerHostApi.readCharacteristic', codec,
|
||||||
|
@ -75,8 +75,12 @@ class MyCentralManager extends MyBluetoothLowEnergyManager
|
|||||||
}) async {
|
}) async {
|
||||||
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
|
await throwWithoutState(BluetoothLowEnergyState.poweredOn);
|
||||||
final peripheralHashCodeArgs = peripheral.hashCode;
|
final peripheralHashCodeArgs = peripheral.hashCode;
|
||||||
final maximumWriteLength =
|
final typeArgs = type.toArgs();
|
||||||
await _api.getMaximumWriteLength(peripheralHashCodeArgs);
|
final typeNumberArgs = typeArgs.index;
|
||||||
|
final maximumWriteLength = await _api.getMaximumWriteLength(
|
||||||
|
peripheralHashCodeArgs,
|
||||||
|
typeNumberArgs,
|
||||||
|
);
|
||||||
return maximumWriteLength;
|
return maximumWriteLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +100,12 @@ class MyCentralManager extends MyBluetoothLowEnergyManager
|
|||||||
}
|
}
|
||||||
final peripheralHashCodeArgs = peripheral.hashCode;
|
final peripheralHashCodeArgs = peripheral.hashCode;
|
||||||
final servicesArgs = await _api.discoverGATT(peripheralHashCodeArgs);
|
final servicesArgs = await _api.discoverGATT(peripheralHashCodeArgs);
|
||||||
|
// 部分外围设备连接后会触发 onMtuChanged 回调,若在此之前调用协商 MTU 的方法,会在协商完成前返回,
|
||||||
|
// 此时如果继续调用其他方法(如发现服务)会导致回调无法触发,
|
||||||
|
// 因此为避免此情况发生,需要延迟到发现服务完成后再协商 MTU。
|
||||||
|
// TODO: 思考更好的解决方式,可以在连接后立即协商 MTU。
|
||||||
|
const mtuArgs = 517;
|
||||||
|
await _api.requestMTU(peripheralHashCodeArgs, mtuArgs);
|
||||||
final services = servicesArgs
|
final services = servicesArgs
|
||||||
.cast<MyGattServiceArgs>()
|
.cast<MyGattServiceArgs>()
|
||||||
.map((args) => args.toService2())
|
.map((args) => args.toService2())
|
||||||
|
@ -22,13 +22,14 @@ abstract class MyCentralManagerHostApi {
|
|||||||
void connect(int peripheralHashCodeArgs);
|
void connect(int peripheralHashCodeArgs);
|
||||||
@async
|
@async
|
||||||
void disconnect(int peripheralHashCodeArgs);
|
void disconnect(int peripheralHashCodeArgs);
|
||||||
@async
|
int getMaximumWriteLength(int peripheralHashCodeArgs, int typeNumberArgs);
|
||||||
int getMaximumWriteLength(int peripheralHashCodeArgs);
|
|
||||||
@async
|
@async
|
||||||
int readRSSI(int peripheralHashCodeArgs);
|
int readRSSI(int peripheralHashCodeArgs);
|
||||||
@async
|
@async
|
||||||
List<MyGattServiceArgs> discoverGATT(int peripheralHashCodeArgs);
|
List<MyGattServiceArgs> discoverGATT(int peripheralHashCodeArgs);
|
||||||
@async
|
@async
|
||||||
|
int requestMTU(int peripheralHashCodeArgs, int mtuArgs);
|
||||||
|
@async
|
||||||
Uint8List readCharacteristic(
|
Uint8List readCharacteristic(
|
||||||
int peripheralHashCodeArgs,
|
int peripheralHashCodeArgs,
|
||||||
int characteristicHashCodeArgs,
|
int characteristicHashCodeArgs,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: bluetooth_low_energy_android
|
name: bluetooth_low_energy_android
|
||||||
description: Android implementation of the bluetooth_low_energy plugin.
|
description: Android implementation of the bluetooth_low_energy plugin.
|
||||||
version: 3.0.1
|
version: 3.0.3
|
||||||
homepage: https://github.com/yanshouwang/bluetooth_low_energy
|
homepage: https://github.com/yanshouwang/bluetooth_low_energy
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
* Fix the issue that `getMaximumWriteLength` is wrong and coerce the value from 20 to 512.
|
||||||
|
* Fix the issue that the peripheral manager response is wrong.
|
||||||
|
|
||||||
## 3.0.1
|
## 3.0.1
|
||||||
|
|
||||||
* Fix the issue that write characteristic will never complete when write without response.
|
* Fix the issue that write characteristic will never complete when write without response.
|
||||||
|
@ -274,6 +274,21 @@ extension MyGattDescriptorArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Int {
|
||||||
|
func coerceIn(_ minimum: Int, _ maximum: Int) throws -> Int {
|
||||||
|
if minimum > maximum {
|
||||||
|
throw MyError.illegalArgument
|
||||||
|
}
|
||||||
|
if self < minimum {
|
||||||
|
return minimum
|
||||||
|
}
|
||||||
|
if self > maximum {
|
||||||
|
return maximum
|
||||||
|
}
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Dictionary {
|
extension Dictionary {
|
||||||
mutating func getOrPut(_ key: Key, _ defaultValue: () -> Value) -> Value {
|
mutating func getOrPut(_ key: Key, _ defaultValue: () -> Value) -> Value {
|
||||||
guard let value = self[key] else {
|
guard let value = self[key] else {
|
||||||
|
@ -154,7 +154,7 @@ class MyCentralManager: MyCentralManagerHostApi {
|
|||||||
throw MyError.illegalArgument
|
throw MyError.illegalArgument
|
||||||
}
|
}
|
||||||
let type = typeArgs.toWriteType()
|
let type = typeArgs.toWriteType()
|
||||||
let maximumWriteLength = peripheral.maximumWriteValueLength(for: type)
|
let maximumWriteLength = try peripheral.maximumWriteValueLength(for: type).coerceIn(20, 512)
|
||||||
let maximumWriteLengthArgs = Int64(maximumWriteLength)
|
let maximumWriteLengthArgs = Int64(maximumWriteLength)
|
||||||
return maximumWriteLengthArgs
|
return maximumWriteLengthArgs
|
||||||
}
|
}
|
||||||
@ -310,7 +310,6 @@ class MyCentralManager: MyCentralManagerHostApi {
|
|||||||
api.onStateChanged(stateNumberArgs: stateNumberArgs) {}
|
api.onStateChanged(stateNumberArgs: stateNumberArgs) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func didDiscover(_ peripheral: CBPeripheral, _ advertisementData: [String : Any], _ rssi: NSNumber) {
|
func didDiscover(_ peripheral: CBPeripheral, _ advertisementData: [String : Any], _ rssi: NSNumber) {
|
||||||
let peripheralArgs = peripheral.toArgs()
|
let peripheralArgs = peripheral.toArgs()
|
||||||
let peripheralHashCode = peripheral.hash
|
let peripheralHashCode = peripheral.hash
|
||||||
@ -329,10 +328,12 @@ class MyCentralManager: MyCentralManagerHostApi {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
|
let peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
|
||||||
let completion = connectCompletions.removeValue(forKey: peripheralHashCodeArgs)
|
|
||||||
completion?(.success(()))
|
|
||||||
let stateArgs = true
|
let stateArgs = true
|
||||||
api.onPeripheralStateChanged(peripheralArgs: peripheralArgs, stateArgs: stateArgs) {}
|
api.onPeripheralStateChanged(peripheralArgs: peripheralArgs, stateArgs: stateArgs) {}
|
||||||
|
guard let completion = connectCompletions.removeValue(forKey: peripheralHashCodeArgs) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
completion(.success(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func didFailToConnect(_ peripheral: CBPeripheral, _ error: Error?) {
|
func didFailToConnect(_ peripheral: CBPeripheral, _ error: Error?) {
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
// TODO: 优化错误内容
|
||||||
enum MyError: Error {
|
enum MyError: Error {
|
||||||
case illegalArgument
|
case illegalArgument
|
||||||
case illegalState
|
case illegalState
|
||||||
|
@ -202,13 +202,13 @@ class MyPeripheralManager: MyPeripheralManagerHostApi {
|
|||||||
guard let central = centrals[centralHashCodeArgs] else {
|
guard let central = centrals[centralHashCodeArgs] else {
|
||||||
throw MyError.illegalArgument
|
throw MyError.illegalArgument
|
||||||
}
|
}
|
||||||
let maximumWriteLength = central.maximumUpdateValueLength
|
let maximumWriteLength = try central.maximumUpdateValueLength.coerceIn(20, 512)
|
||||||
let maximumWriteLengthArgs = Int64(maximumWriteLength)
|
let maximumWriteLengthArgs = Int64(maximumWriteLength)
|
||||||
return maximumWriteLengthArgs
|
return maximumWriteLengthArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendReadCharacteristicReply(centralHashCodeArgs: Int64, characteristicHashCodeArgs: Int64, idArgs: Int64, offsetArgs: Int64, statusArgs: Bool, valueArgs: FlutterStandardTypedData) throws {
|
func sendReadCharacteristicReply(centralHashCodeArgs: Int64, characteristicHashCodeArgs: Int64, idArgs: Int64, offsetArgs: Int64, statusArgs: Bool, valueArgs: FlutterStandardTypedData) throws {
|
||||||
guard let request = requests[idArgs] else {
|
guard let request = requests.removeValue(forKey: idArgs) else {
|
||||||
throw MyError.illegalArgument
|
throw MyError.illegalArgument
|
||||||
}
|
}
|
||||||
request.value = valueArgs.data
|
request.value = valueArgs.data
|
||||||
@ -217,7 +217,7 @@ class MyPeripheralManager: MyPeripheralManagerHostApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendWriteCharacteristicReply(centralHashCodeArgs: Int64, characteristicHashCodeArgs: Int64, idArgs: Int64, offsetArgs: Int64, statusArgs: Bool) throws {
|
func sendWriteCharacteristicReply(centralHashCodeArgs: Int64, characteristicHashCodeArgs: Int64, idArgs: Int64, offsetArgs: Int64, statusArgs: Bool) throws {
|
||||||
guard let request = requests[idArgs] else {
|
guard let request = requests.removeValue(forKey: idArgs) else {
|
||||||
throw MyError.illegalArgument
|
throw MyError.illegalArgument
|
||||||
}
|
}
|
||||||
let result = statusArgs ? CBATTError.Code.success : CBATTError.Code.requestNotSupported
|
let result = statusArgs ? CBATTError.Code.success : CBATTError.Code.requestNotSupported
|
||||||
@ -315,7 +315,15 @@ class MyPeripheralManager: MyPeripheralManagerHostApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func didReceiveWrite(_ requests: [CBATTRequest]) {
|
func didReceiveWrite(_ requests: [CBATTRequest]) {
|
||||||
for request in requests {
|
// 根据官方文档,仅响应第一个写入请求
|
||||||
|
guard let request = requests.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if requests.count > 1 {
|
||||||
|
// TODO: 支持多写入请求,暂时不清楚此处应如何处理
|
||||||
|
let result = CBATTError.requestNotSupported
|
||||||
|
peripheralManager.respond(to: request, withResult: result)
|
||||||
|
}
|
||||||
let central = request.central
|
let central = request.central
|
||||||
let centralHashCode = central.hash
|
let centralHashCode = central.hash
|
||||||
let centralArgs = centralsArgs.getOrPut(centralHashCode) { central.toArgs() }
|
let centralArgs = centralsArgs.getOrPut(centralHashCode) { central.toArgs() }
|
||||||
@ -337,7 +345,6 @@ class MyPeripheralManager: MyPeripheralManagerHostApi {
|
|||||||
let valueArgs = FlutterStandardTypedData(bytes: value)
|
let valueArgs = FlutterStandardTypedData(bytes: value)
|
||||||
api.onWriteCharacteristicCommandReceived(centralArgs: centralArgs, characteristicArgs: characteristicArgs, idArgs: idArgs, offsetArgs: offsetArgs, valueArgs: valueArgs) {}
|
api.onWriteCharacteristicCommandReceived(centralArgs: centralArgs, characteristicArgs: characteristicArgs, idArgs: idArgs, offsetArgs: offsetArgs, valueArgs: valueArgs) {}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func didSubscribeTo(_ central: CBCentral, _ characteristic: CBCharacteristic) {
|
func didSubscribeTo(_ central: CBCentral, _ characteristic: CBCharacteristic) {
|
||||||
let centralHashCode = central.hash
|
let centralHashCode = central.hash
|
||||||
|
@ -329,7 +329,7 @@ class _ScannerViewState extends State<ScannerView> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
trailing: Text('$rssi'),
|
trailing: RssiWidget(rssi),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, i) {
|
separatorBuilder: (context, i) {
|
||||||
@ -375,10 +375,13 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
late final ValueNotifier<GattCharacteristic?> characteristic;
|
late final ValueNotifier<GattCharacteristic?> characteristic;
|
||||||
late final ValueNotifier<GattCharacteristicWriteType> writeType;
|
late final ValueNotifier<GattCharacteristicWriteType> writeType;
|
||||||
late final ValueNotifier<int> maximumWriteLength;
|
late final ValueNotifier<int> maximumWriteLength;
|
||||||
|
late final ValueNotifier<int> rssi;
|
||||||
late final ValueNotifier<List<Log>> logs;
|
late final ValueNotifier<List<Log>> logs;
|
||||||
late final TextEditingController writeController;
|
late final TextEditingController writeController;
|
||||||
late final StreamSubscription stateChangedSubscription;
|
late final StreamSubscription stateChangedSubscription;
|
||||||
late final StreamSubscription valueChangedSubscription;
|
late final StreamSubscription valueChangedSubscription;
|
||||||
|
late final StreamSubscription rssiChangedSubscription;
|
||||||
|
late final Timer rssiTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -390,7 +393,8 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
service = ValueNotifier(null);
|
service = ValueNotifier(null);
|
||||||
characteristic = ValueNotifier(null);
|
characteristic = ValueNotifier(null);
|
||||||
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
|
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
|
||||||
maximumWriteLength = ValueNotifier(20);
|
maximumWriteLength = ValueNotifier(0);
|
||||||
|
rssi = ValueNotifier(-100);
|
||||||
logs = ValueNotifier([]);
|
logs = ValueNotifier([]);
|
||||||
writeController = TextEditingController();
|
writeController = TextEditingController();
|
||||||
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
|
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
|
||||||
@ -423,6 +427,17 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
rssiTimer = Timer.periodic(
|
||||||
|
const Duration(seconds: 1),
|
||||||
|
(timer) async {
|
||||||
|
final state = this.state.value;
|
||||||
|
if (state) {
|
||||||
|
rssi.value = await centralManager.readRSSI(eventArgs.peripheral);
|
||||||
|
} else {
|
||||||
|
rssi.value = -100;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -455,10 +470,18 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
final peripheral = eventArgs.peripheral;
|
final peripheral = eventArgs.peripheral;
|
||||||
if (state) {
|
if (state) {
|
||||||
await centralManager.disconnect(peripheral);
|
await centralManager.disconnect(peripheral);
|
||||||
|
maximumWriteLength.value = 0;
|
||||||
|
rssi.value = 0;
|
||||||
} else {
|
} else {
|
||||||
await centralManager.connect(peripheral);
|
await centralManager.connect(peripheral);
|
||||||
services.value =
|
services.value =
|
||||||
await centralManager.discoverGATT(peripheral);
|
await centralManager.discoverGATT(peripheral);
|
||||||
|
maximumWriteLength.value =
|
||||||
|
await centralManager.getMaximumWriteLength(
|
||||||
|
peripheral,
|
||||||
|
type: writeType.value,
|
||||||
|
);
|
||||||
|
rssi.value = await centralManager.readRSSI(peripheral);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
|
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
|
||||||
@ -592,72 +615,82 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
ValueListenableBuilder(
|
||||||
child: Center(
|
|
||||||
child: ValueListenableBuilder(
|
|
||||||
valueListenable: writeType,
|
valueListenable: writeType,
|
||||||
builder: (context, writeType, child) {
|
builder: (context, writeType, child) {
|
||||||
final items =
|
return ToggleButtons(
|
||||||
GattCharacteristicWriteType.values.map((type) {
|
onPressed: (i) async {
|
||||||
return DropdownMenuItem(
|
final type = GattCharacteristicWriteType.values[i];
|
||||||
value: type,
|
|
||||||
child: Text(
|
|
||||||
type.name,
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
return DropdownButton(
|
|
||||||
items: items,
|
|
||||||
onChanged: (type) {
|
|
||||||
if (type == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.writeType.value = type;
|
this.writeType.value = type;
|
||||||
},
|
|
||||||
value: writeType,
|
|
||||||
underline: const Offstage(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ValueListenableBuilder(
|
|
||||||
valueListenable: state,
|
|
||||||
builder: (context, state, child) {
|
|
||||||
return TextButton(
|
|
||||||
onPressed: state
|
|
||||||
? () async {
|
|
||||||
maximumWriteLength.value =
|
maximumWriteLength.value =
|
||||||
await centralManager.getMaximumWriteLength(
|
await centralManager.getMaximumWriteLength(
|
||||||
eventArgs.peripheral,
|
eventArgs.peripheral,
|
||||||
type: writeType.value,
|
type: type,
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
: null,
|
constraints: const BoxConstraints(
|
||||||
child: ValueListenableBuilder(
|
minWidth: 0.0,
|
||||||
|
minHeight: 0.0,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(4.0),
|
||||||
|
isSelected: GattCharacteristicWriteType.values
|
||||||
|
.map((type) => type == writeType)
|
||||||
|
.toList(),
|
||||||
|
children: GattCharacteristicWriteType.values.map((type) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 4.0,
|
||||||
|
),
|
||||||
|
child: Text(type.name),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
// final segments =
|
||||||
|
// GattCharacteristicWriteType.values.map((type) {
|
||||||
|
// return ButtonSegment(
|
||||||
|
// value: type,
|
||||||
|
// label: Text(type.name),
|
||||||
|
// );
|
||||||
|
// }).toList();
|
||||||
|
// return SegmentedButton(
|
||||||
|
// segments: segments,
|
||||||
|
// selected: {writeType},
|
||||||
|
// showSelectedIcon: false,
|
||||||
|
// style: OutlinedButton.styleFrom(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
// padding: EdgeInsets.zero,
|
||||||
|
// visualDensity: VisualDensity.compact,
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(8.0),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
ValueListenableBuilder(
|
||||||
|
valueListenable: state,
|
||||||
|
builder: (context, state, child) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
valueListenable: maximumWriteLength,
|
valueListenable: maximumWriteLength,
|
||||||
builder: (context, maximumWriteLength, child) {
|
builder: (context, maximumWriteLength, child) {
|
||||||
return Text('MTU: $maximumWriteLength');
|
return Text('$maximumWriteLength');
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
const Spacer(),
|
||||||
IconButton(
|
ValueListenableBuilder(
|
||||||
onPressed: () async {
|
valueListenable: rssi,
|
||||||
final rssi =
|
builder: (context, rssi, child) {
|
||||||
await centralManager.readRSSI(eventArgs.peripheral);
|
return RssiWidget(rssi);
|
||||||
log('RSSI: $rssi');
|
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.signal_wifi_4_bar),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 16.0),
|
margin: const EdgeInsets.only(bottom: 16.0),
|
||||||
height: 160.0,
|
height: 160.0,
|
||||||
child: ValueListenableBuilder(
|
child: ValueListenableBuilder(
|
||||||
valueListenable: characteristic,
|
valueListenable: characteristic,
|
||||||
@ -755,6 +788,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
rssiTimer.cancel();
|
||||||
stateChangedSubscription.cancel();
|
stateChangedSubscription.cancel();
|
||||||
valueChangedSubscription.cancel();
|
valueChangedSubscription.cancel();
|
||||||
state.dispose();
|
state.dispose();
|
||||||
@ -764,6 +798,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
characteristic.dispose();
|
characteristic.dispose();
|
||||||
writeType.dispose();
|
writeType.dispose();
|
||||||
maximumWriteLength.dispose();
|
maximumWriteLength.dispose();
|
||||||
|
rssi.dispose();
|
||||||
logs.dispose();
|
logs.dispose();
|
||||||
writeController.dispose();
|
writeController.dispose();
|
||||||
}
|
}
|
||||||
@ -1070,3 +1105,25 @@ enum LogType {
|
|||||||
notify,
|
notify,
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RssiWidget extends StatelessWidget {
|
||||||
|
final int rssi;
|
||||||
|
|
||||||
|
const RssiWidget(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,7 +15,7 @@ packages:
|
|||||||
path: ".."
|
path: ".."
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0"
|
version: "3.0.2"
|
||||||
bluetooth_low_energy_platform_interface:
|
bluetooth_low_energy_platform_interface:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: bluetooth_low_energy_darwin
|
name: bluetooth_low_energy_darwin
|
||||||
description: iOS and macOS implementation of the bluetooth_low_energy plugin.
|
description: iOS and macOS implementation of the bluetooth_low_energy plugin.
|
||||||
version: 3.0.1
|
version: 3.0.2
|
||||||
homepage: https://github.com/yanshouwang/bluetooth_low_energy
|
homepage: https://github.com/yanshouwang/bluetooth_low_energy
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
@ -329,7 +329,7 @@ class _ScannerViewState extends State<ScannerView> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
trailing: Text('$rssi'),
|
trailing: RssiWidget(rssi),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, i) {
|
separatorBuilder: (context, i) {
|
||||||
@ -375,10 +375,13 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
late final ValueNotifier<GattCharacteristic?> characteristic;
|
late final ValueNotifier<GattCharacteristic?> characteristic;
|
||||||
late final ValueNotifier<GattCharacteristicWriteType> writeType;
|
late final ValueNotifier<GattCharacteristicWriteType> writeType;
|
||||||
late final ValueNotifier<int> maximumWriteLength;
|
late final ValueNotifier<int> maximumWriteLength;
|
||||||
|
late final ValueNotifier<int> rssi;
|
||||||
late final ValueNotifier<List<Log>> logs;
|
late final ValueNotifier<List<Log>> logs;
|
||||||
late final TextEditingController writeController;
|
late final TextEditingController writeController;
|
||||||
late final StreamSubscription stateChangedSubscription;
|
late final StreamSubscription stateChangedSubscription;
|
||||||
late final StreamSubscription valueChangedSubscription;
|
late final StreamSubscription valueChangedSubscription;
|
||||||
|
late final StreamSubscription rssiChangedSubscription;
|
||||||
|
late final Timer rssiTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -390,7 +393,8 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
service = ValueNotifier(null);
|
service = ValueNotifier(null);
|
||||||
characteristic = ValueNotifier(null);
|
characteristic = ValueNotifier(null);
|
||||||
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
|
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
|
||||||
maximumWriteLength = ValueNotifier(20);
|
maximumWriteLength = ValueNotifier(0);
|
||||||
|
rssi = ValueNotifier(-100);
|
||||||
logs = ValueNotifier([]);
|
logs = ValueNotifier([]);
|
||||||
writeController = TextEditingController();
|
writeController = TextEditingController();
|
||||||
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
|
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
|
||||||
@ -423,6 +427,17 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
rssiTimer = Timer.periodic(
|
||||||
|
const Duration(seconds: 1),
|
||||||
|
(timer) async {
|
||||||
|
final state = this.state.value;
|
||||||
|
if (state) {
|
||||||
|
rssi.value = await centralManager.readRSSI(eventArgs.peripheral);
|
||||||
|
} else {
|
||||||
|
rssi.value = -100;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -455,10 +470,18 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
final peripheral = eventArgs.peripheral;
|
final peripheral = eventArgs.peripheral;
|
||||||
if (state) {
|
if (state) {
|
||||||
await centralManager.disconnect(peripheral);
|
await centralManager.disconnect(peripheral);
|
||||||
|
maximumWriteLength.value = 0;
|
||||||
|
rssi.value = 0;
|
||||||
} else {
|
} else {
|
||||||
await centralManager.connect(peripheral);
|
await centralManager.connect(peripheral);
|
||||||
services.value =
|
services.value =
|
||||||
await centralManager.discoverGATT(peripheral);
|
await centralManager.discoverGATT(peripheral);
|
||||||
|
maximumWriteLength.value =
|
||||||
|
await centralManager.getMaximumWriteLength(
|
||||||
|
peripheral,
|
||||||
|
type: writeType.value,
|
||||||
|
);
|
||||||
|
rssi.value = await centralManager.readRSSI(peripheral);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
|
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
|
||||||
@ -592,72 +615,82 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
ValueListenableBuilder(
|
||||||
child: Center(
|
|
||||||
child: ValueListenableBuilder(
|
|
||||||
valueListenable: writeType,
|
valueListenable: writeType,
|
||||||
builder: (context, writeType, child) {
|
builder: (context, writeType, child) {
|
||||||
final items =
|
return ToggleButtons(
|
||||||
GattCharacteristicWriteType.values.map((type) {
|
onPressed: (i) async {
|
||||||
return DropdownMenuItem(
|
final type = GattCharacteristicWriteType.values[i];
|
||||||
value: type,
|
|
||||||
child: Text(
|
|
||||||
type.name,
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
return DropdownButton(
|
|
||||||
items: items,
|
|
||||||
onChanged: (type) {
|
|
||||||
if (type == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.writeType.value = type;
|
this.writeType.value = type;
|
||||||
},
|
|
||||||
value: writeType,
|
|
||||||
underline: const Offstage(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ValueListenableBuilder(
|
|
||||||
valueListenable: state,
|
|
||||||
builder: (context, state, child) {
|
|
||||||
return TextButton(
|
|
||||||
onPressed: state
|
|
||||||
? () async {
|
|
||||||
maximumWriteLength.value =
|
maximumWriteLength.value =
|
||||||
await centralManager.getMaximumWriteLength(
|
await centralManager.getMaximumWriteLength(
|
||||||
eventArgs.peripheral,
|
eventArgs.peripheral,
|
||||||
type: writeType.value,
|
type: type,
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
: null,
|
constraints: const BoxConstraints(
|
||||||
child: ValueListenableBuilder(
|
minWidth: 0.0,
|
||||||
|
minHeight: 0.0,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(4.0),
|
||||||
|
isSelected: GattCharacteristicWriteType.values
|
||||||
|
.map((type) => type == writeType)
|
||||||
|
.toList(),
|
||||||
|
children: GattCharacteristicWriteType.values.map((type) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 4.0,
|
||||||
|
),
|
||||||
|
child: Text(type.name),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
// final segments =
|
||||||
|
// GattCharacteristicWriteType.values.map((type) {
|
||||||
|
// return ButtonSegment(
|
||||||
|
// value: type,
|
||||||
|
// label: Text(type.name),
|
||||||
|
// );
|
||||||
|
// }).toList();
|
||||||
|
// return SegmentedButton(
|
||||||
|
// segments: segments,
|
||||||
|
// selected: {writeType},
|
||||||
|
// showSelectedIcon: false,
|
||||||
|
// style: OutlinedButton.styleFrom(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
// padding: EdgeInsets.zero,
|
||||||
|
// visualDensity: VisualDensity.compact,
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(8.0),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
ValueListenableBuilder(
|
||||||
|
valueListenable: state,
|
||||||
|
builder: (context, state, child) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
valueListenable: maximumWriteLength,
|
valueListenable: maximumWriteLength,
|
||||||
builder: (context, maximumWriteLength, child) {
|
builder: (context, maximumWriteLength, child) {
|
||||||
return Text('MTU: $maximumWriteLength');
|
return Text('$maximumWriteLength');
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
const Spacer(),
|
||||||
IconButton(
|
ValueListenableBuilder(
|
||||||
onPressed: () async {
|
valueListenable: rssi,
|
||||||
final rssi =
|
builder: (context, rssi, child) {
|
||||||
await centralManager.readRSSI(eventArgs.peripheral);
|
return RssiWidget(rssi);
|
||||||
log('RSSI: $rssi');
|
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.signal_wifi_4_bar),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 16.0),
|
margin: const EdgeInsets.only(bottom: 16.0),
|
||||||
height: 160.0,
|
height: 160.0,
|
||||||
child: ValueListenableBuilder(
|
child: ValueListenableBuilder(
|
||||||
valueListenable: characteristic,
|
valueListenable: characteristic,
|
||||||
@ -755,6 +788,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
rssiTimer.cancel();
|
||||||
stateChangedSubscription.cancel();
|
stateChangedSubscription.cancel();
|
||||||
valueChangedSubscription.cancel();
|
valueChangedSubscription.cancel();
|
||||||
state.dispose();
|
state.dispose();
|
||||||
@ -764,6 +798,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
characteristic.dispose();
|
characteristic.dispose();
|
||||||
writeType.dispose();
|
writeType.dispose();
|
||||||
maximumWriteLength.dispose();
|
maximumWriteLength.dispose();
|
||||||
|
rssi.dispose();
|
||||||
logs.dispose();
|
logs.dispose();
|
||||||
writeController.dispose();
|
writeController.dispose();
|
||||||
}
|
}
|
||||||
@ -1070,3 +1105,25 @@ enum LogType {
|
|||||||
notify,
|
notify,
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RssiWidget extends StatelessWidget {
|
||||||
|
final int rssi;
|
||||||
|
|
||||||
|
const RssiWidget(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -329,7 +329,7 @@ class _ScannerViewState extends State<ScannerView> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
trailing: Text('$rssi'),
|
trailing: RssiWidget(rssi),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, i) {
|
separatorBuilder: (context, i) {
|
||||||
@ -375,10 +375,13 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
late final ValueNotifier<GattCharacteristic?> characteristic;
|
late final ValueNotifier<GattCharacteristic?> characteristic;
|
||||||
late final ValueNotifier<GattCharacteristicWriteType> writeType;
|
late final ValueNotifier<GattCharacteristicWriteType> writeType;
|
||||||
late final ValueNotifier<int> maximumWriteLength;
|
late final ValueNotifier<int> maximumWriteLength;
|
||||||
|
late final ValueNotifier<int> rssi;
|
||||||
late final ValueNotifier<List<Log>> logs;
|
late final ValueNotifier<List<Log>> logs;
|
||||||
late final TextEditingController writeController;
|
late final TextEditingController writeController;
|
||||||
late final StreamSubscription stateChangedSubscription;
|
late final StreamSubscription stateChangedSubscription;
|
||||||
late final StreamSubscription valueChangedSubscription;
|
late final StreamSubscription valueChangedSubscription;
|
||||||
|
late final StreamSubscription rssiChangedSubscription;
|
||||||
|
late final Timer rssiTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -390,7 +393,8 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
service = ValueNotifier(null);
|
service = ValueNotifier(null);
|
||||||
characteristic = ValueNotifier(null);
|
characteristic = ValueNotifier(null);
|
||||||
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
|
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
|
||||||
maximumWriteLength = ValueNotifier(20);
|
maximumWriteLength = ValueNotifier(0);
|
||||||
|
rssi = ValueNotifier(-100);
|
||||||
logs = ValueNotifier([]);
|
logs = ValueNotifier([]);
|
||||||
writeController = TextEditingController();
|
writeController = TextEditingController();
|
||||||
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
|
stateChangedSubscription = centralManager.peripheralStateChanged.listen(
|
||||||
@ -423,6 +427,17 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
rssiTimer = Timer.periodic(
|
||||||
|
const Duration(seconds: 1),
|
||||||
|
(timer) async {
|
||||||
|
final state = this.state.value;
|
||||||
|
if (state) {
|
||||||
|
rssi.value = await centralManager.readRSSI(eventArgs.peripheral);
|
||||||
|
} else {
|
||||||
|
rssi.value = -100;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -455,10 +470,18 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
final peripheral = eventArgs.peripheral;
|
final peripheral = eventArgs.peripheral;
|
||||||
if (state) {
|
if (state) {
|
||||||
await centralManager.disconnect(peripheral);
|
await centralManager.disconnect(peripheral);
|
||||||
|
maximumWriteLength.value = 0;
|
||||||
|
rssi.value = 0;
|
||||||
} else {
|
} else {
|
||||||
await centralManager.connect(peripheral);
|
await centralManager.connect(peripheral);
|
||||||
services.value =
|
services.value =
|
||||||
await centralManager.discoverGATT(peripheral);
|
await centralManager.discoverGATT(peripheral);
|
||||||
|
maximumWriteLength.value =
|
||||||
|
await centralManager.getMaximumWriteLength(
|
||||||
|
peripheral,
|
||||||
|
type: writeType.value,
|
||||||
|
);
|
||||||
|
rssi.value = await centralManager.readRSSI(peripheral);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
|
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
|
||||||
@ -592,72 +615,82 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
ValueListenableBuilder(
|
||||||
child: Center(
|
|
||||||
child: ValueListenableBuilder(
|
|
||||||
valueListenable: writeType,
|
valueListenable: writeType,
|
||||||
builder: (context, writeType, child) {
|
builder: (context, writeType, child) {
|
||||||
final items =
|
return ToggleButtons(
|
||||||
GattCharacteristicWriteType.values.map((type) {
|
onPressed: (i) async {
|
||||||
return DropdownMenuItem(
|
final type = GattCharacteristicWriteType.values[i];
|
||||||
value: type,
|
|
||||||
child: Text(
|
|
||||||
type.name,
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
return DropdownButton(
|
|
||||||
items: items,
|
|
||||||
onChanged: (type) {
|
|
||||||
if (type == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.writeType.value = type;
|
this.writeType.value = type;
|
||||||
},
|
|
||||||
value: writeType,
|
|
||||||
underline: const Offstage(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ValueListenableBuilder(
|
|
||||||
valueListenable: state,
|
|
||||||
builder: (context, state, child) {
|
|
||||||
return TextButton(
|
|
||||||
onPressed: state
|
|
||||||
? () async {
|
|
||||||
maximumWriteLength.value =
|
maximumWriteLength.value =
|
||||||
await centralManager.getMaximumWriteLength(
|
await centralManager.getMaximumWriteLength(
|
||||||
eventArgs.peripheral,
|
eventArgs.peripheral,
|
||||||
type: writeType.value,
|
type: type,
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
: null,
|
constraints: const BoxConstraints(
|
||||||
child: ValueListenableBuilder(
|
minWidth: 0.0,
|
||||||
|
minHeight: 0.0,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(4.0),
|
||||||
|
isSelected: GattCharacteristicWriteType.values
|
||||||
|
.map((type) => type == writeType)
|
||||||
|
.toList(),
|
||||||
|
children: GattCharacteristicWriteType.values.map((type) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 4.0,
|
||||||
|
),
|
||||||
|
child: Text(type.name),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
// final segments =
|
||||||
|
// GattCharacteristicWriteType.values.map((type) {
|
||||||
|
// return ButtonSegment(
|
||||||
|
// value: type,
|
||||||
|
// label: Text(type.name),
|
||||||
|
// );
|
||||||
|
// }).toList();
|
||||||
|
// return SegmentedButton(
|
||||||
|
// segments: segments,
|
||||||
|
// selected: {writeType},
|
||||||
|
// showSelectedIcon: false,
|
||||||
|
// style: OutlinedButton.styleFrom(
|
||||||
|
// tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
// padding: EdgeInsets.zero,
|
||||||
|
// visualDensity: VisualDensity.compact,
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(8.0),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
ValueListenableBuilder(
|
||||||
|
valueListenable: state,
|
||||||
|
builder: (context, state, child) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
valueListenable: maximumWriteLength,
|
valueListenable: maximumWriteLength,
|
||||||
builder: (context, maximumWriteLength, child) {
|
builder: (context, maximumWriteLength, child) {
|
||||||
return Text('MTU: $maximumWriteLength');
|
return Text('$maximumWriteLength');
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
const Spacer(),
|
||||||
IconButton(
|
ValueListenableBuilder(
|
||||||
onPressed: () async {
|
valueListenable: rssi,
|
||||||
final rssi =
|
builder: (context, rssi, child) {
|
||||||
await centralManager.readRSSI(eventArgs.peripheral);
|
return RssiWidget(rssi);
|
||||||
log('RSSI: $rssi');
|
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.signal_wifi_4_bar),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 16.0),
|
margin: const EdgeInsets.only(bottom: 16.0),
|
||||||
height: 160.0,
|
height: 160.0,
|
||||||
child: ValueListenableBuilder(
|
child: ValueListenableBuilder(
|
||||||
valueListenable: characteristic,
|
valueListenable: characteristic,
|
||||||
@ -755,6 +788,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
rssiTimer.cancel();
|
||||||
stateChangedSubscription.cancel();
|
stateChangedSubscription.cancel();
|
||||||
valueChangedSubscription.cancel();
|
valueChangedSubscription.cancel();
|
||||||
state.dispose();
|
state.dispose();
|
||||||
@ -764,6 +798,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
|||||||
characteristic.dispose();
|
characteristic.dispose();
|
||||||
writeType.dispose();
|
writeType.dispose();
|
||||||
maximumWriteLength.dispose();
|
maximumWriteLength.dispose();
|
||||||
|
rssi.dispose();
|
||||||
logs.dispose();
|
logs.dispose();
|
||||||
writeController.dispose();
|
writeController.dispose();
|
||||||
}
|
}
|
||||||
@ -1070,3 +1105,25 @@ enum LogType {
|
|||||||
notify,
|
notify,
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RssiWidget extends StatelessWidget {
|
||||||
|
final int rssi;
|
||||||
|
|
||||||
|
const RssiWidget(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user