3.0.2 (#21)
* fix: 优化界面 * fix: 修复已知问题 * fix: 代码优化 * fix: 修复已知问题 * fix: 优化 getMaximumWriteLength 方法 * fix: 修改版本号 * fix: 修改版本号 * fix: 修改 CHANGELOG
This commit is contained in:
@ -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
|
||||
|
||||
* 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 {
|
||||
mutating func getOrPut(_ key: Key, _ defaultValue: () -> Value) -> Value {
|
||||
guard let value = self[key] else {
|
||||
|
@ -154,7 +154,7 @@ class MyCentralManager: MyCentralManagerHostApi {
|
||||
throw MyError.illegalArgument
|
||||
}
|
||||
let type = typeArgs.toWriteType()
|
||||
let maximumWriteLength = peripheral.maximumWriteValueLength(for: type)
|
||||
let maximumWriteLength = try peripheral.maximumWriteValueLength(for: type).coerceIn(20, 512)
|
||||
let maximumWriteLengthArgs = Int64(maximumWriteLength)
|
||||
return maximumWriteLengthArgs
|
||||
}
|
||||
@ -310,7 +310,6 @@ class MyCentralManager: MyCentralManagerHostApi {
|
||||
api.onStateChanged(stateNumberArgs: stateNumberArgs) {}
|
||||
}
|
||||
|
||||
|
||||
func didDiscover(_ peripheral: CBPeripheral, _ advertisementData: [String : Any], _ rssi: NSNumber) {
|
||||
let peripheralArgs = peripheral.toArgs()
|
||||
let peripheralHashCode = peripheral.hash
|
||||
@ -329,10 +328,12 @@ class MyCentralManager: MyCentralManagerHostApi {
|
||||
return
|
||||
}
|
||||
let peripheralHashCodeArgs = peripheralArgs.hashCodeArgs
|
||||
let completion = connectCompletions.removeValue(forKey: peripheralHashCodeArgs)
|
||||
completion?(.success(()))
|
||||
let stateArgs = true
|
||||
api.onPeripheralStateChanged(peripheralArgs: peripheralArgs, stateArgs: stateArgs) {}
|
||||
guard let completion = connectCompletions.removeValue(forKey: peripheralHashCodeArgs) else {
|
||||
return
|
||||
}
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func didFailToConnect(_ peripheral: CBPeripheral, _ error: Error?) {
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
// TODO: 优化错误内容
|
||||
enum MyError: Error {
|
||||
case illegalArgument
|
||||
case illegalState
|
||||
|
@ -202,13 +202,13 @@ class MyPeripheralManager: MyPeripheralManagerHostApi {
|
||||
guard let central = centrals[centralHashCodeArgs] else {
|
||||
throw MyError.illegalArgument
|
||||
}
|
||||
let maximumWriteLength = central.maximumUpdateValueLength
|
||||
let maximumWriteLength = try central.maximumUpdateValueLength.coerceIn(20, 512)
|
||||
let maximumWriteLengthArgs = Int64(maximumWriteLength)
|
||||
return maximumWriteLengthArgs
|
||||
}
|
||||
|
||||
func sendReadCharacteristicReply(centralHashCodeArgs: Int64, characteristicHashCodeArgs: Int64, idArgs: Int64, offsetArgs: Int64, statusArgs: Bool, valueArgs: FlutterStandardTypedData) throws {
|
||||
guard let request = requests[idArgs] else {
|
||||
guard let request = requests.removeValue(forKey: idArgs) else {
|
||||
throw MyError.illegalArgument
|
||||
}
|
||||
request.value = valueArgs.data
|
||||
@ -217,7 +217,7 @@ class MyPeripheralManager: MyPeripheralManagerHostApi {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
let result = statusArgs ? CBATTError.Code.success : CBATTError.Code.requestNotSupported
|
||||
@ -315,28 +315,35 @@ class MyPeripheralManager: MyPeripheralManagerHostApi {
|
||||
}
|
||||
|
||||
func didReceiveWrite(_ requests: [CBATTRequest]) {
|
||||
for request in requests {
|
||||
let central = request.central
|
||||
let centralHashCode = central.hash
|
||||
let centralArgs = centralsArgs.getOrPut(centralHashCode) { central.toArgs() }
|
||||
let centralHashCodeArgs = centralArgs.hashCodeArgs
|
||||
centrals[centralHashCodeArgs] = central
|
||||
let characteristic = request.characteristic
|
||||
let characteristicHashCode = characteristic.hash
|
||||
guard let characteristicArgs = characteristicsArgs[characteristicHashCode] else {
|
||||
peripheralManager.respond(to: request, withResult: .attributeNotFound)
|
||||
return
|
||||
}
|
||||
let idArgs = Int64(request.hash)
|
||||
self.requests[idArgs] = request
|
||||
let offsetArgs = Int64(request.offset)
|
||||
guard let value = request.value else {
|
||||
peripheralManager.respond(to: request, withResult: .requestNotSupported)
|
||||
return
|
||||
}
|
||||
let valueArgs = FlutterStandardTypedData(bytes: value)
|
||||
api.onWriteCharacteristicCommandReceived(centralArgs: centralArgs, characteristicArgs: characteristicArgs, idArgs: idArgs, offsetArgs: offsetArgs, valueArgs: valueArgs) {}
|
||||
// 根据官方文档,仅响应第一个写入请求
|
||||
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 centralHashCode = central.hash
|
||||
let centralArgs = centralsArgs.getOrPut(centralHashCode) { central.toArgs() }
|
||||
let centralHashCodeArgs = centralArgs.hashCodeArgs
|
||||
centrals[centralHashCodeArgs] = central
|
||||
let characteristic = request.characteristic
|
||||
let characteristicHashCode = characteristic.hash
|
||||
guard let characteristicArgs = characteristicsArgs[characteristicHashCode] else {
|
||||
peripheralManager.respond(to: request, withResult: .attributeNotFound)
|
||||
return
|
||||
}
|
||||
let idArgs = Int64(request.hash)
|
||||
self.requests[idArgs] = request
|
||||
let offsetArgs = Int64(request.offset)
|
||||
guard let value = request.value else {
|
||||
peripheralManager.respond(to: request, withResult: .requestNotSupported)
|
||||
return
|
||||
}
|
||||
let valueArgs = FlutterStandardTypedData(bytes: value)
|
||||
api.onWriteCharacteristicCommandReceived(centralArgs: centralArgs, characteristicArgs: characteristicArgs, idArgs: idArgs, offsetArgs: offsetArgs, valueArgs: valueArgs) {}
|
||||
}
|
||||
|
||||
func didSubscribeTo(_ central: CBCentral, _ characteristic: CBCharacteristic) {
|
||||
|
@ -329,7 +329,7 @@ class _ScannerViewState extends State<ScannerView> {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: Text('$rssi'),
|
||||
trailing: RssiWidget(rssi),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, i) {
|
||||
@ -375,10 +375,13 @@ class _PeripheralViewState extends State<PeripheralView> {
|
||||
late final ValueNotifier<GattCharacteristic?> characteristic;
|
||||
late final ValueNotifier<GattCharacteristicWriteType> writeType;
|
||||
late final ValueNotifier<int> maximumWriteLength;
|
||||
late final ValueNotifier<int> rssi;
|
||||
late final ValueNotifier<List<Log>> logs;
|
||||
late final TextEditingController writeController;
|
||||
late final StreamSubscription stateChangedSubscription;
|
||||
late final StreamSubscription valueChangedSubscription;
|
||||
late final StreamSubscription rssiChangedSubscription;
|
||||
late final Timer rssiTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -390,7 +393,8 @@ class _PeripheralViewState extends State<PeripheralView> {
|
||||
service = ValueNotifier(null);
|
||||
characteristic = ValueNotifier(null);
|
||||
writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
|
||||
maximumWriteLength = ValueNotifier(20);
|
||||
maximumWriteLength = ValueNotifier(0);
|
||||
rssi = ValueNotifier(-100);
|
||||
logs = ValueNotifier([]);
|
||||
writeController = TextEditingController();
|
||||
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
|
||||
@ -455,10 +470,18 @@ class _PeripheralViewState extends State<PeripheralView> {
|
||||
final peripheral = eventArgs.peripheral;
|
||||
if (state) {
|
||||
await centralManager.disconnect(peripheral);
|
||||
maximumWriteLength.value = 0;
|
||||
rssi.value = 0;
|
||||
} else {
|
||||
await centralManager.connect(peripheral);
|
||||
services.value =
|
||||
await centralManager.discoverGATT(peripheral);
|
||||
maximumWriteLength.value =
|
||||
await centralManager.getMaximumWriteLength(
|
||||
peripheral,
|
||||
type: writeType.value,
|
||||
);
|
||||
rssi.value = await centralManager.readRSSI(peripheral);
|
||||
}
|
||||
},
|
||||
child: Text(state ? 'DISCONNECT' : 'CONNECT'),
|
||||
@ -592,72 +615,82 @@ class _PeripheralViewState extends State<PeripheralView> {
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: writeType,
|
||||
builder: (context, writeType, child) {
|
||||
final items =
|
||||
GattCharacteristicWriteType.values.map((type) {
|
||||
return DropdownMenuItem(
|
||||
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;
|
||||
},
|
||||
value: writeType,
|
||||
underline: const Offstage(),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: writeType,
|
||||
builder: (context, writeType, child) {
|
||||
return ToggleButtons(
|
||||
onPressed: (i) async {
|
||||
final type = GattCharacteristicWriteType.values[i];
|
||||
this.writeType.value = type;
|
||||
maximumWriteLength.value =
|
||||
await centralManager.getMaximumWriteLength(
|
||||
eventArgs.peripheral,
|
||||
type: type,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: state,
|
||||
builder: (context, state, child) {
|
||||
return TextButton(
|
||||
onPressed: state
|
||||
? () async {
|
||||
maximumWriteLength.value =
|
||||
await centralManager.getMaximumWriteLength(
|
||||
eventArgs.peripheral,
|
||||
type: writeType.value,
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: maximumWriteLength,
|
||||
builder: (context, maximumWriteLength, child) {
|
||||
return Text('MTU: $maximumWriteLength');
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
final rssi =
|
||||
await centralManager.readRSSI(eventArgs.peripheral);
|
||||
log('RSSI: $rssi');
|
||||
constraints: const BoxConstraints(
|
||||
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,
|
||||
builder: (context, maximumWriteLength, child) {
|
||||
return Text('$maximumWriteLength');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: rssi,
|
||||
builder: (context, rssi, child) {
|
||||
return RssiWidget(rssi);
|
||||
},
|
||||
icon: const Icon(Icons.signal_wifi_4_bar),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
margin: const EdgeInsets.only(bottom: 16.0),
|
||||
height: 160.0,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: characteristic,
|
||||
@ -755,6 +788,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
rssiTimer.cancel();
|
||||
stateChangedSubscription.cancel();
|
||||
valueChangedSubscription.cancel();
|
||||
state.dispose();
|
||||
@ -764,6 +798,7 @@ class _PeripheralViewState extends State<PeripheralView> {
|
||||
characteristic.dispose();
|
||||
writeType.dispose();
|
||||
maximumWriteLength.dispose();
|
||||
rssi.dispose();
|
||||
logs.dispose();
|
||||
writeController.dispose();
|
||||
}
|
||||
@ -1070,3 +1105,25 @@ enum LogType {
|
||||
notify,
|
||||
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: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "3.0.0"
|
||||
version: "3.0.2"
|
||||
bluetooth_low_energy_platform_interface:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1,6 +1,6 @@
|
||||
name: bluetooth_low_energy_darwin
|
||||
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
|
||||
|
||||
environment:
|
||||
|
Reference in New Issue
Block a user