Add high level API (#160)
* Add high-level api extensions for BleDevice and Characteristic * Add doc comments and move few files to utils * Add advance documentation * Make the high level APIs the new default in the readme * More readme cleanups * Add pair api, improve notifications apis and deprecate writeValue * Use highLevel apis in Example * Rename HighLevel isPaired api to hasPairing api * Update Readme * Rename low level readValue api to read and update Readme * Update UniversalBle to use new apis instead of deprecated internally * Split queueCommand and queueCommandWithoutTimeout * Split setNotifiable into subscribeNotifications, subscribeIndications and unsubscribe * Fix web imports * Update changelog * Switch back to isPaired Co-authored-by: Rohit Sangwan <rohitsangwan647@gmail.com> * Fix BleDevice toString method * Switch back to isPaired * Change peripheral to device * Rename cached to preferCached * Remove preferCached from discoverServices() * Update changelog * Use cached services * Add /// * Reverse withoutResponse to withResponse to avoid negation * Link to low level API * Move connectionState to public readme * Rename disableSubscriptions to unsubscribe * Improve documentation * Fix getService cache * Add NotFoundException --------- Co-authored-by: Foti Dim <foti@navideck.com>
This commit is contained in:
@@ -41,13 +41,13 @@ class _MyAppState extends State<MyApp> {
|
||||
UniversalBle.queueType = _queueType;
|
||||
UniversalBle.timeout = const Duration(seconds: 10);
|
||||
|
||||
UniversalBle.onAvailabilityChange = (state) {
|
||||
UniversalBle.availabilityStream.listen((state) {
|
||||
setState(() {
|
||||
bleAvailabilityState = state;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
UniversalBle.onScanResult = (result) {
|
||||
UniversalBle.scanStream.listen((result) {
|
||||
// log(result.toString());
|
||||
int index = _bleDevices.indexWhere((e) => e.deviceId == result.deviceId);
|
||||
if (index == -1) {
|
||||
@@ -59,7 +59,7 @@ class _MyAppState extends State<MyApp> {
|
||||
_bleDevices[index] = result;
|
||||
}
|
||||
setState(() {});
|
||||
};
|
||||
});
|
||||
|
||||
// UniversalBle.onQueueUpdate = (String id, int remainingItems) {
|
||||
// debugPrint("Queue: $id RemainingItems: $remainingItems");
|
||||
@@ -259,13 +259,11 @@ class _MyAppState extends State<MyApp> {
|
||||
bleDevice: device,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PeripheralDetailPage(
|
||||
device.deviceId,
|
||||
device.name ?? "Unknown Peripheral",
|
||||
),
|
||||
));
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => PeripheralDetailPage(device),
|
||||
),
|
||||
);
|
||||
UniversalBle.stopScan();
|
||||
setState(() {
|
||||
_isScanning = false;
|
||||
|
||||
@@ -30,7 +30,7 @@ class ScannedItemWidget extends StatelessWidget {
|
||||
visible: manufacturerData != null,
|
||||
child: Text(manufacturerData.toString()),
|
||||
),
|
||||
bleDevice.isPaired == true
|
||||
bleDevice.paired == true
|
||||
? const Text(
|
||||
"Paired",
|
||||
style: TextStyle(color: Colors.green),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: avoid_print, depend_on_referenced_packages
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:convert/convert.dart';
|
||||
@@ -13,9 +11,8 @@ import 'package:universal_ble_example/widgets/responsive_buttons_grid.dart';
|
||||
import 'package:universal_ble_example/widgets/responsive_view.dart';
|
||||
|
||||
class PeripheralDetailPage extends StatefulWidget {
|
||||
final String deviceId;
|
||||
final String deviceName;
|
||||
const PeripheralDetailPage(this.deviceId, this.deviceName, {super.key});
|
||||
final BleDevice bleDevice;
|
||||
const PeripheralDetailPage(this.bleDevice, {super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
@@ -24,30 +21,32 @@ class PeripheralDetailPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
late final bleDevice = widget.bleDevice;
|
||||
bool isConnected = false;
|
||||
GlobalKey<FormState> valueFormKey = GlobalKey<FormState>();
|
||||
List<BleService> discoveredServices = [];
|
||||
final List<String> _logs = [];
|
||||
final binaryCode = TextEditingController();
|
||||
|
||||
({
|
||||
BleService service,
|
||||
BleCharacteristic characteristic
|
||||
})? selectedCharacteristic;
|
||||
StreamSubscription? connectionStreamSubscription;
|
||||
StreamSubscription? pairingStateSubscription;
|
||||
BleService? selectedService;
|
||||
BleCharacteristic? selectedCharacteristic;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
UniversalBle.onConnectionChange = _handleConnectionChange;
|
||||
|
||||
connectionStreamSubscription =
|
||||
bleDevice.connectionStream.listen(_handleConnectionChange);
|
||||
pairingStateSubscription =
|
||||
bleDevice.pairingStateStream.listen(_handlePairingStateChange);
|
||||
UniversalBle.onValueChange = _handleValueChange;
|
||||
UniversalBle.onPairingStateChange = _handlePairingStateChange;
|
||||
_asyncInits();
|
||||
}
|
||||
|
||||
void _asyncInits() {
|
||||
UniversalBle.getConnectionState(
|
||||
widget.deviceId,
|
||||
).then((state) {
|
||||
bleDevice.connectionState.then((state) {
|
||||
if (state == BleConnectionState.connected) {
|
||||
setState(() {
|
||||
isConnected = true;
|
||||
@@ -59,7 +58,8 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
UniversalBle.onConnectionChange = null;
|
||||
connectionStreamSubscription?.cancel();
|
||||
pairingStateSubscription?.cancel();
|
||||
UniversalBle.onValueChange = null;
|
||||
}
|
||||
|
||||
@@ -69,18 +69,10 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
});
|
||||
}
|
||||
|
||||
void _handleConnectionChange(
|
||||
String deviceId,
|
||||
bool isConnected,
|
||||
String? error,
|
||||
) {
|
||||
print(
|
||||
'_handleConnectionChange $deviceId, $isConnected ${error != null ? 'Error: $error' : ''}',
|
||||
);
|
||||
void _handleConnectionChange(bool isConnected) {
|
||||
debugPrint('_handleConnectionChange $isConnected');
|
||||
setState(() {
|
||||
if (deviceId == widget.deviceId) {
|
||||
this.isConnected = isConnected;
|
||||
}
|
||||
this.isConnected = isConnected;
|
||||
});
|
||||
_addLog('Connection', isConnected ? "Connected" : "Disconnected");
|
||||
// Auto Discover Services
|
||||
@@ -93,12 +85,12 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
String deviceId, String characteristicId, Uint8List value) {
|
||||
String s = String.fromCharCodes(value);
|
||||
String data = '$s\nraw : ${value.toString()}';
|
||||
print('_handleValueChange $deviceId, $characteristicId, $s');
|
||||
debugPrint('_handleValueChange $characteristicId, $s');
|
||||
_addLog("Value", data);
|
||||
}
|
||||
|
||||
void _handlePairingStateChange(String deviceId, bool isPaired) {
|
||||
print('isPaired $deviceId, $isPaired');
|
||||
void _handlePairingStateChange(bool isPaired) {
|
||||
debugPrint('isPaired $isPaired');
|
||||
_addLog("PairingStateChange - isPaired", isPaired);
|
||||
}
|
||||
|
||||
@@ -106,10 +98,9 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
const webWarning =
|
||||
"Note: Only services added in ScanFilter or WebOptions will be discovered";
|
||||
try {
|
||||
var services = await UniversalBle.discoverServices(widget.deviceId);
|
||||
print('${services.length} services discovered');
|
||||
print(services);
|
||||
discoveredServices.clear();
|
||||
var services = await bleDevice.discoverServices();
|
||||
debugPrint('${services.length} services discovered');
|
||||
debugPrint(services.toString());
|
||||
setState(() {
|
||||
discoveredServices = services;
|
||||
});
|
||||
@@ -126,13 +117,10 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
}
|
||||
|
||||
Future<void> _readValue() async {
|
||||
BleCharacteristic? selectedCharacteristic = this.selectedCharacteristic;
|
||||
if (selectedCharacteristic == null) return;
|
||||
try {
|
||||
Uint8List value = await UniversalBle.readValue(
|
||||
widget.deviceId,
|
||||
selectedCharacteristic!.service.uuid,
|
||||
selectedCharacteristic!.characteristic.uuid,
|
||||
);
|
||||
Uint8List value = await selectedCharacteristic.read();
|
||||
String s = String.fromCharCodes(value);
|
||||
String data = '$s\nraw : ${value.toString()}';
|
||||
_addLog('Read', data);
|
||||
@@ -142,6 +130,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
}
|
||||
|
||||
Future<void> _writeValue() async {
|
||||
BleCharacteristic? selectedCharacteristic = this.selectedCharacteristic;
|
||||
if (selectedCharacteristic == null ||
|
||||
!valueFormKey.currentState!.validate() ||
|
||||
binaryCode.text.isEmpty) {
|
||||
@@ -157,54 +146,59 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
}
|
||||
|
||||
try {
|
||||
await UniversalBle.writeValue(
|
||||
widget.deviceId,
|
||||
selectedCharacteristic!.service.uuid,
|
||||
selectedCharacteristic!.characteristic.uuid,
|
||||
await selectedCharacteristic.write(
|
||||
value,
|
||||
_hasSelectedCharacteristicProperty(
|
||||
[CharacteristicProperty.writeWithoutResponse])
|
||||
? BleOutputProperty.withoutResponse
|
||||
: BleOutputProperty.withResponse,
|
||||
withResponse: _hasSelectedCharacteristicProperty(
|
||||
[CharacteristicProperty.writeWithoutResponse],
|
||||
),
|
||||
);
|
||||
_addLog('Write', value);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
debugPrint(e.toString());
|
||||
_addLog('WriteError', e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setBleInputProperty(BleInputProperty inputProperty) async {
|
||||
Future<void> _subscribeChar() async {
|
||||
BleCharacteristic? selectedCharacteristic = this.selectedCharacteristic;
|
||||
if (selectedCharacteristic == null) return;
|
||||
try {
|
||||
if (inputProperty != BleInputProperty.disabled) {
|
||||
List<CharacteristicProperty> properties =
|
||||
selectedCharacteristic!.characteristic.properties;
|
||||
if (properties.contains(CharacteristicProperty.notify)) {
|
||||
inputProperty = BleInputProperty.notification;
|
||||
} else if (properties.contains(CharacteristicProperty.indicate)) {
|
||||
inputProperty = BleInputProperty.indication;
|
||||
} else {
|
||||
throw 'No notify or indicate property';
|
||||
}
|
||||
}
|
||||
await UniversalBle.setNotifiable(
|
||||
widget.deviceId,
|
||||
selectedCharacteristic!.service.uuid,
|
||||
selectedCharacteristic!.characteristic.uuid,
|
||||
inputProperty,
|
||||
);
|
||||
_addLog('BleInputProperty', inputProperty);
|
||||
var subscription = _getCharacteristicSubscription(selectedCharacteristic);
|
||||
if (subscription == null) throw 'No notify or indicate property';
|
||||
await subscription.subscribe();
|
||||
_addLog('BleCharSubscription', 'Subscribed');
|
||||
// Updates can also be handled by
|
||||
// subscription.listen((data) {});
|
||||
} catch (e) {
|
||||
_addLog('NotifyError', e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _unsubscribeChar() async {
|
||||
try {
|
||||
await selectedCharacteristic?.unsubscribe();
|
||||
_addLog('BleCharSubscription', 'UnSubscribed');
|
||||
} catch (e) {
|
||||
_addLog('NotifyError', e);
|
||||
}
|
||||
}
|
||||
|
||||
CharacteristicSubscription? _getCharacteristicSubscription(
|
||||
BleCharacteristic characteristic) {
|
||||
var properties = characteristic.properties;
|
||||
if (properties.contains(CharacteristicProperty.notify)) {
|
||||
return characteristic.notifications;
|
||||
} else if (properties.contains(CharacteristicProperty.indicate)) {
|
||||
return characteristic.indications;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("${widget.deviceName} - ${widget.deviceId}"),
|
||||
title: Text("${bleDevice.name ?? "Unknown"} - ${bleDevice.deviceId}"),
|
||||
elevation: 4,
|
||||
actions: [
|
||||
Padding(
|
||||
@@ -234,13 +228,10 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
: ServicesListWidget(
|
||||
discoveredServices: discoveredServices,
|
||||
scrollable: true,
|
||||
onTap: (BleService service,
|
||||
BleCharacteristic characteristic) {
|
||||
onTap: (service, characteristic) {
|
||||
setState(() {
|
||||
selectedCharacteristic = (
|
||||
service: service,
|
||||
characteristic: characteristic
|
||||
);
|
||||
selectedService = service;
|
||||
selectedCharacteristic = characteristic;
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -264,9 +255,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
enabled: !isConnected,
|
||||
onPressed: () async {
|
||||
try {
|
||||
await UniversalBle.connect(
|
||||
widget.deviceId,
|
||||
);
|
||||
await bleDevice.connect();
|
||||
_addLog("ConnectionResult", true);
|
||||
} catch (e) {
|
||||
_addLog('ConnectError (${e.runtimeType})', e);
|
||||
@@ -277,7 +266,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
text: 'Disconnect',
|
||||
enabled: isConnected,
|
||||
onPressed: () {
|
||||
UniversalBle.disconnect(widget.deviceId);
|
||||
bleDevice.disconnect();
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -294,17 +283,17 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
child: Card(
|
||||
child: ListTile(
|
||||
title: SelectableText(
|
||||
"Characteristic: ${selectedCharacteristic!.characteristic.uuid}",
|
||||
"Characteristic: ${selectedCharacteristic?.uuid}",
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableText(
|
||||
"Service: ${selectedCharacteristic!.service.uuid}",
|
||||
"Service: ${selectedService?.uuid}",
|
||||
),
|
||||
Text(
|
||||
"Properties: ${selectedCharacteristic!.characteristic.properties.map((e) => e.name)}",
|
||||
"Properties: ${selectedCharacteristic?.properties.map((e) => e.name)}",
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -360,9 +349,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
onPressed: () async {
|
||||
_addLog(
|
||||
'ConnectionState',
|
||||
await UniversalBle.getConnectionState(
|
||||
widget.deviceId,
|
||||
),
|
||||
await bleDevice.connectionState,
|
||||
);
|
||||
},
|
||||
text: 'Connection State',
|
||||
@@ -371,8 +358,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
PlatformButton(
|
||||
enabled: isConnected,
|
||||
onPressed: () async {
|
||||
int mtu = await UniversalBle.requestMtu(
|
||||
widget.deviceId, 247);
|
||||
int mtu = await bleDevice.requestMtu(247);
|
||||
_addLog('MTU', mtu);
|
||||
},
|
||||
text: 'Request Mtu',
|
||||
@@ -403,8 +389,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
CharacteristicProperty.notify,
|
||||
CharacteristicProperty.indicate
|
||||
]),
|
||||
onPressed: () => _setBleInputProperty(
|
||||
BleInputProperty.notification),
|
||||
onPressed: _subscribeChar,
|
||||
text: 'Subscribe',
|
||||
),
|
||||
PlatformButton(
|
||||
@@ -414,21 +399,19 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
CharacteristicProperty.notify,
|
||||
CharacteristicProperty.indicate
|
||||
]),
|
||||
onPressed: () => _setBleInputProperty(
|
||||
BleInputProperty.disabled),
|
||||
onPressed: _unsubscribeChar,
|
||||
text: 'Unsubscribe',
|
||||
),
|
||||
PlatformButton(
|
||||
enabled: BleCapabilities.supportsAllPairingKinds,
|
||||
onPressed: () async {
|
||||
try {
|
||||
await UniversalBle.pair(
|
||||
widget.deviceId,
|
||||
// pairingCommand: BleCommand(
|
||||
// service: "",
|
||||
// characteristic: "",
|
||||
// ),
|
||||
);
|
||||
await bleDevice.pair(
|
||||
// pairingCommand: BleCommand(
|
||||
// service: "",
|
||||
// characteristic: "",
|
||||
// ),
|
||||
);
|
||||
_addLog("Pairing Result", true);
|
||||
} catch (e) {
|
||||
_addLog('PairError (${e.runtimeType})', e);
|
||||
@@ -438,20 +421,19 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
),
|
||||
PlatformButton(
|
||||
onPressed: () async {
|
||||
bool? isPaired = await UniversalBle.isPaired(
|
||||
widget.deviceId,
|
||||
// pairingCommand: BleCommand(
|
||||
// service: "",
|
||||
// characteristic: "",
|
||||
// ),
|
||||
);
|
||||
_addLog('IsPaired', isPaired);
|
||||
bool? isPaired = await bleDevice.isPaired(
|
||||
// pairingCommand: BleCommand(
|
||||
// service: "",
|
||||
// characteristic: "",
|
||||
// ),
|
||||
);
|
||||
_addLog('isPaired', isPaired);
|
||||
},
|
||||
text: 'IsPaired',
|
||||
text: 'isPaired',
|
||||
),
|
||||
PlatformButton(
|
||||
onPressed: () async {
|
||||
await UniversalBle.unpair(widget.deviceId);
|
||||
await bleDevice.unpair();
|
||||
},
|
||||
text: 'Unpair',
|
||||
),
|
||||
@@ -462,13 +444,10 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
if (deviceType != DeviceType.desktop)
|
||||
ServicesListWidget(
|
||||
discoveredServices: discoveredServices,
|
||||
onTap: (BleService service,
|
||||
BleCharacteristic characteristic) {
|
||||
onTap: (service, characteristic) {
|
||||
setState(() {
|
||||
selectedCharacteristic = (
|
||||
service: service,
|
||||
characteristic: characteristic
|
||||
);
|
||||
selectedService = service;
|
||||
selectedCharacteristic = characteristic;
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -497,9 +476,8 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
}
|
||||
|
||||
bool _hasSelectedCharacteristicProperty(
|
||||
List<CharacteristicProperty> properties) =>
|
||||
properties.any((property) =>
|
||||
selectedCharacteristic?.characteristic.properties
|
||||
.contains(property) ??
|
||||
false);
|
||||
List<CharacteristicProperty> properties) {
|
||||
return properties.any((property) =>
|
||||
selectedCharacteristic?.properties.contains(property) ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/universal_ble/darwin
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
||||
device_info_plus: b0fafc687fb901e2af612763340f1b0d4352f8e5
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
|
||||
universal_ble: ff19787898040d721109c6324472e5dd4bc86adc
|
||||
|
||||
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
|
||||
|
||||
|
||||
@@ -402,7 +402,7 @@ packages:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.19.1"
|
||||
version: "0.20.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user