A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter.
Try it online,provided your browser supportsWeb Bluetooth.
- Scanning
- Connecting
- Discovering Services
- Reading & Writing data
- Pairing
- Bluetooth Availability
- Command Queue
- Timeout
- UUID Format Agnostic
API | Android | iOS | macOS | Windows | Linux (beta) | Web |
---|---|---|---|---|---|---|
startScan/stopScan | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
connect/disconnect | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
getSystemDevices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
discoverServices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
readValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
writeValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
setNotifiable | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
pair/unPair | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
onPairingStateChange | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
enableBluetooth | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
onAvailabilityChange | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
requestMtu | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
Add universal_ble in your pubspec.yaml:
dependencies:
universal_ble:
and import it wherever you want to use it:
import'package:universal_ble/universal_ble.dart';
// Set a scan result handler
UniversalBle.onScanResult=(bleDevice) {
// e.g. Use BleDevice ID to connect
}
// Perform a scan
UniversalBle.startScan();
// Or optionally add a scan filter
UniversalBle.startScan(
scanFilter:ScanFilter(
withServices:["SERVICE_UUID"],
withManufacturerData:["MANUFACTURER_DATA"]
)
);
// Stop scanning
UniversalBle.stopScan();
Before initiating a scan, ensure that Bluetooth is available:
AvailabilityStatestate=awaitUniversalBle.getBluetoothAvailabilityState();
// Start scan only if Bluetooth is powered on
if(state==AvailabilityState.poweredOn) {
UniversalBle.startScan();
}
// Or listen to bluetooth availability changes
UniversalBle.onAvailabilityChange=(state) {
if(state==AvailabilityState.poweredOn) {
UniversalBle.startScan();
}
};
See theBluetooth Availabilitysection for more.
Already connected devices, connected either through previous sessions, other apps or through system settings, won't show up as scan results. You can get those usinggetSystemDevices()
.
// Get already connected devices
// You can set `withServices` to narrow down the results
// On `Apple`, `withServices` is required to get connected devices, else [1800] service will be used as default filter.
List<BleDevice> devices=awaitUniversalBle.getSystemDevices(withServices:[]);
For each such device theisSystemDevice
property will betrue
.
You still need to explicitlyconnectto them before being able to use them.
You can optionally set filters when scanning.
When setting this parameter, the scan results will only include devices that advertise any of the specified services. This is the primary filter. All devices are first filtered by services, then further filtered by other criteria. This parameter is mandatory onwebif you want to access those services.
List<String> withServices;
Use thewithManufacturerData
parameter to filter devices by manufacturer data. When you pass a list ofManufacturerDataFilter
objects to this parameter, the scan results will only include devices that contain any of the specified manufacturer data.
List<ManufacturerDataFilter> withManufacturerData;
Use thewithNamePrefix
parameter to filter devices by names (case sensitive). When you pass a list of names, the scan results will only include devices that have this name or start with the provided parameter.
List<String> withNamePrefix;
// Connect to a device using the `deviceId` of the BleDevice received from `UniversalBle.onScanResult`
StringdeviceId=bleDevice.deviceId;
UniversalBle.connect(deviceId);
// Disconnect from a device
UniversalBle.disconnect(deviceId);
// Get connection/disconnection updates
UniversalBle.onConnectionChange=(StringdeviceId,boolisConnected) {
debugPrint('OnConnectionChange $deviceId,$isConnected');
}
// Get current connection state
// Can be connected, disconnected, connecting or disconnecting
BleConnectionStateconnectionState=awaitbleDevice.connectionState;
After establishing a connection, you need to discover services. This method will discover all services and their characteristics.
// Discover services of a specific device
UniversalBle.discoverServices(deviceId);
You need to firstdiscover servicesbefore you are able to read and write to characteristics.
// Read data from a characteristic
UniversalBle.readValue(deviceId, serviceId, characteristicId);
// Write data to a characteristic
UniversalBle.writeValue(deviceId, serviceId, characteristicId, value);
// Subscribe to a characteristic
UniversalBle.setNotifiable(deviceId, serviceId, characteristicId,BleInputProperty.notification);
// Get characteristic updates in `onValueChange`
UniversalBle.onValueChange=(StringdeviceId,StringcharacteristicId,Uint8Listvalue) {
debugPrint('onValueChange $deviceId,$characteristicId,${hex.encode(value)}');
}
// Unsubscribe from a characteristic
UniversalBle.setNotifiable(deviceId, serviceId, characteristicId,BleInputProperty.disabled);
// Pair
UniversalBle.pair(deviceId);
// Get the pairing result
UniversalBle.onPairingStateChange=(StringdeviceId,boolisPaired,String?error) {
// Handle Pairing state change
}
// Unpair
UniversalBle.unPair(deviceId);
// Check current pairing state
boolisPaired=UniversalBle.isPaired(deviceId);
// Get current Bluetooth availability state
AvailabilityStateavailabilityState=UniversalBle.getBluetoothAvailabilityState();// e.g. poweredOff or poweredOn,
// Receive Bluetooth availability changes
UniversalBle.onAvailabilityChange=(state) {
// Handle the new Bluetooth availability state
};
// Enable Bluetooth programmatically
UniversalBle.enableBluetooth();
By default, all commands are executed in a global queue (QueueType.global
), with each command waiting for the previous one to finish.
If you want to parallelize commands between multiple devices, you can set:
// Create a separate queue for each device.
UniversalBle.queueType=QueueType.perDevice;
You can also disable the queue completely and parallelize all commands, even for the same device, by using:
// Disable queue
UniversalBle.queueType=QueueType.none;
Keep in mind that some platforms (e.g. Android) may not handle well devices that fail to process consecutive commands without a minimum interval. Therefore, it is not advised to setqueueType
tonone
.
You can get queue updates by setting:
// Get queue state updates
UniversalBle.onQueueUpdate=(Stringid,intremainingItems) {
debugPrint("Queue: $idRemaining: $remainingItems");
};
By default, all commands have a timeout of 10 seconds.
// Change timeout
UniversalBle.timeout=constDuration(seconds:10);
// Disable timeout
UniversalBle.timeout=null;
UniversalBLE is agnostic to the UUID format of services and characteristics regardless of the platform the app runs on. When passing a UUID, you can pass it in any format (long/short) or character case (upper/lower case) you want. UniversalBLE will take care of necessary conversions, across all platforms, so that you don't need to worry about underlying platform differences.
For consistency, all characteristic and service UUIDs will be returned inlowercase 128-bit format,across all platforms, e.g.0000180a-0000-1000-8000-00805f9b34fb
.
If you need to convert any UUIDs in your app you can use the following methods.
BleUuidParser.string()
converts a string to a 128-bit UUID formatted string:
BleUuidParser.string("180A");// "0000180a-0000-1000-8000-00805f9b34fb"
BleUuidParser.string("0000180A-0000-1000-8000-00805F9B34FB");// "0000180a-0000-1000-8000-00805f9b34fb"
BleUuidParser.number()
converts a number to a 128-bit UUID formatted string:
BleUuidParser.number(0x180A);// "0000180a-0000-1000-8000-00805f9b34fb"
BleUuidParser pare()
compares two differently formatted UUIDs:
BleUuidParser.compare("180a","0000180A-0000-1000-8000-00805F9B34FB");// true
Add the following permissions to your AndroidManifest.xml file:
<uses-permissionandroid:name="android.permission.BLUETOOTH_SCAN"android:usesPermissionFlags="neverForLocation"/>
<uses-permissionandroid:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permissionandroid:name="android.permission.ACCESS_FINE_LOCATION"android:maxSdkVersion="30"/>
<uses-permissionandroid:name="android.permission.ACCESS_COARSE_LOCATION"android:maxSdkVersion="30"/>
If you useBLUETOOTH_SCAN
to determine location, modify your AndroidManifest.xml file to include the following entry:
<uses-permissionandroid:name="android.permission.BLUETOOTH_SCAN"tools:remove="android:usesPermissionFlags"tools:targetApi="s"/>
If your app uses location services, removeandroid:maxSdkVersion= "30"
from the location permission tags.
AddNSBluetoothPeripheralUsageDescription
andNSBluetoothAlwaysUsageDescription
to Info.plist of your iOS and macOS app.
Add theBluetooth
capability to the macOS app from Xcode.
Your Bluetooth adapter needs to support at least Bluetooth 4.0. If you have more than 1 adapters, the first one returned from the system will be picked.
When publishing on Windows you need to declare the followingcapabilities:bluetooth, radios
.
On web, thewithServices
parameter in the ScanFilter is used asoptional_servicesas well as a services filter. On web you have to set this parameter to ensure that you can access the specified services after connecting to the device. You can leave it empty for the rest of the platforms if your device does not advertise services.
ScanFilter(
withServices:kIsWeb?["SERVICE_UUID"]:[],
)
// Create a class that extends UniversalBlePlatform
classUniversalBleMockextendsUniversalBlePlatform{
// Implement all commands
}
UniversalBle.setInstance(UniversalBleMock());