{"id":14969875,"url":"https://github.com/navideck/universal_ble","last_synced_at":"2025-04-06T20:11:37.305Z","repository":{"id":218909005,"uuid":"747702643","full_name":"Navideck/universal_ble","owner":"Navideck","description":"A cross-platform Android/iOS/macOS/Windows/Linux/Web Bluetooth Low Energy (BLE) plugin for Flutter","archived":false,"fork":false,"pushed_at":"2025-03-22T08:44:13.000Z","size":36225,"stargazers_count":79,"open_issues_count":17,"forks_count":21,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-03-30T19:05:55.741Z","etag":null,"topics":["android","ble","bluetooth","bluetooth-low-energy","flutter","ios","linux","macos","web","windows"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/universal_ble","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Navideck.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"navideck"}},"created_at":"2024-01-24T13:26:38.000Z","updated_at":"2025-03-22T08:44:17.000Z","dependencies_parsed_at":"2024-03-13T13:29:22.230Z","dependency_job_id":"49d3d6d8-0b05-441e-8336-c5b6e8ba80d2","html_url":"https://github.com/Navideck/universal_ble","commit_stats":{"total_commits":84,"total_committers":4,"mean_commits":21.0,"dds":0.5833333333333333,"last_synced_commit":"4e225b756cf75fb8013952eb6bf176d0d5f00f89"},"previous_names":["navideck/universal_ble"],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Navideck%2Funiversal_ble","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Navideck%2Funiversal_ble/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Navideck%2Funiversal_ble/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Navideck%2Funiversal_ble/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Navideck","download_url":"https://codeload.github.com/Navideck/universal_ble/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247543593,"owners_count":20955865,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","ble","bluetooth","bluetooth-low-energy","flutter","ios","linux","macos","web","windows"],"created_at":"2024-09-24T13:42:33.846Z","updated_at":"2025-04-06T20:11:37.283Z","avatar_url":"https://github.com/Navideck.png","language":"Dart","readme":"# UniversalBLE\n\n[![universal_ble version](https://img.shields.io/pub/v/universal_ble?label=universal_ble)](https://pub.dev/packages/universal_ble)\n\nA cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter.\n\n[Try it online](https://navideck.github.io/universal_ble/), provided your browser supports [Web Bluetooth](https://caniuse.com/web-bluetooth).\n\n## Features\n\n- [Scanning](#scanning)\n- [Connecting](#connecting)\n- [Discovering Services](#discovering-services)\n- [Reading \u0026 Writing data](#reading--writing-data)\n- [Pairing](#pairing)\n- [Bluetooth Availability](#bluetooth-availability)\n- [Command Queue](#command-queue)\n- [Timeout](#timeout)\n- [UUID Format Agnostic](#uuid-format-agnostic)\n\n## Usage\n\n### API Support Matrix\n\n| API                  | Android | iOS | macOS | Windows | Linux | Web |\n| :------------------- | :-----: | :-: | :---: | :-----: | :----------: | :-: |\n| startScan/stopScan   |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ✔️  |\n| connect/disconnect   |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ✔️  |\n| getSystemDevices     |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ❌  |\n| discoverServices     |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ✔️  |\n| readValue            |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ✔️  |\n| writeValue           |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ✔️  |\n| setNotifiable        |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ✔️  |\n| pair                 |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ⏺  |\n| unpair               |   ✔️    | ❌  |  ❌   |   ✔️    |      ✔️      | ❌  |\n| isPaired             |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ✔️  |\n| onPairingStateChange |   ✔️    | ⏺  |  ⏺   |   ✔️    |      ✔️      | ⏺  |\n| getBluetoothAvailabilityState |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ❌  |\n| enable/disable Bluetooth      |   ✔️    | ❌  |  ❌   |   ✔️    |      ✔️      | ❌  |\n| onAvailabilityChange |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ✔️  |\n| requestMtu           |   ✔️    | ✔️  |  ✔️   |   ✔️    |      ✔️      | ❌  |\n\n## Getting Started\n\nAdd universal_ble in your pubspec.yaml:\n\n```yaml\ndependencies:\n  universal_ble:\n```\n\nand import it wherever you want to use it:\n\n```dart\nimport 'package:universal_ble/universal_ble.dart';\n```\n\n### Scanning\n\n```dart\n// Set a scan result handler\nUniversalBle.onScanResult = (bleDevice) {\n  // e.g. Use BleDevice ID to connect\n}\n\n// Perform a scan\nUniversalBle.startScan();\n\n// Or optionally add a scan filter\nUniversalBle.startScan(\n  scanFilter: ScanFilter(\n    withServices: [\"SERVICE_UUID\"],\n    withManufacturerData: [ManufacturerDataFilter(companyIdentifier: 0x004c)],\n    withNamePrefix: [\"NAME_PREFIX\"],\n  )\n);\n\n// Stop scanning\nUniversalBle.stopScan();\n```\n\nBefore initiating a scan, ensure that Bluetooth is available:\n\n```dart\nAvailabilityState state = await UniversalBle.getBluetoothAvailabilityState();\n// Start scan only if Bluetooth is powered on\nif (state == AvailabilityState.poweredOn) {\n  UniversalBle.startScan();\n}\n\n// Or listen to bluetooth availability changes\nUniversalBle.onAvailabilityChange = (state) {\n  if (state == AvailabilityState.poweredOn) {\n    UniversalBle.startScan();\n  }\n};\n```\n\nSee the [Bluetooth Availability](#bluetooth-availability) section for more.\n\n#### System Devices\n\nAlready connected devices, connected either through previous sessions, other apps or through system settings, won't show up as scan results. You can get those using `getSystemDevices()`.\n\n```dart\n// Get already connected devices.\n// You can set `withServices` to narrow down the results.\n// On `Apple`, `withServices` is required to get any connected devices. If not passed, several [18XX] generic services will be set by default.\nList\u003cBleDevice\u003e devices = await UniversalBle.getSystemDevices(withServices: []);\n```\n\nFor each such device the `isSystemDevice` property will be `true`.\n\nYou still need to explicitly [connect](#connecting) to them before being able to use them.\n\n#### Scan Filter\n\nYou can optionally set a filter when scanning. A filter can have multiple conditions (services, manufacturerData, namePrefix) and all conditions are in `OR` relation, returning results that match any of the given conditions.\n\n##### With Services\n\nWhen setting this parameter, the scan results will only include devices that advertise any of the specified services. \n\n```dart\nList\u003cString\u003e withServices;\n```\n\nNote: On web **you have to** specify services before you are able to use them. See the [web](#web) section for more details.\n\n##### With ManufacturerData\n\nUse the `withManufacturerData` parameter to filter devices by manufacturer data. When you pass a list of `ManufacturerDataFilter` objects to this parameter, the scan results will only include devices that contain any of the specified manufacturer data.\n\nYou can filter manufacturer data by company identifier, payload prefix, or payload mask.\n\n```dart\nList\u003cManufacturerDataFilter\u003e withManufacturerData = [ManufacturerDataFilter(\n            companyIdentifier: 0x004c,\n            payloadPrefix: Uint8List.fromList([0x001D,0x001A]),\n            payloadMask: Uint8List.fromList([1,0,1,1]))\n          ];\n```\n\n##### With namePrefix\n\nUse the `withNamePrefix` 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.\n\n```dart\nList\u003cString\u003e withNamePrefix;\n```\n\n### Connecting\n\n```dart\n// Connect to a device using the `deviceId` of the BleDevice received from `UniversalBle.onScanResult`\nString deviceId = bleDevice.deviceId;\nUniversalBle.connect(deviceId);\n\n// Disconnect from a device\nUniversalBle.disconnect(deviceId);\n\n// Get connection/disconnection updates\nUniversalBle.onConnectionChange = (String deviceId, bool isConnected, String? error) {\n  debugPrint('OnConnectionChange $deviceId, $isConnected Error: $error');\n}\n\n// Get current connection state\n// Can be connected, disconnected, connecting or disconnecting\nBleConnectionState connectionState = await bleDevice.connectionState;\n```\n\n### Discovering Services\n\nAfter establishing a connection, you need to discover services. This method will discover all services and their characteristics.\n\n```dart\n// Discover services of a specific device\nUniversalBle.discoverServices(deviceId);\n```\n\n### Reading \u0026 Writing data\n\nYou need to first [discover services](#discovering-services) before you are able to read and write to characteristics.\n\n```dart\n// Read data from a characteristic\nUniversalBle.readValue(deviceId, serviceId, characteristicId);\n\n// Write data to a characteristic\nUniversalBle.writeValue(deviceId, serviceId, characteristicId, value);\n\n// Subscribe to a characteristic\nUniversalBle.setNotifiable(deviceId, serviceId, characteristicId, BleInputProperty.notification);\n\n// Get characteristic updates in `onValueChange`\nUniversalBle.onValueChange = (String deviceId, String characteristicId, Uint8List value) {\n  debugPrint('onValueChange $deviceId, $characteristicId, ${hex.encode(value)}');\n}\n\n// Unsubscribe from a characteristic\nUniversalBle.setNotifiable(deviceId, serviceId, characteristicId, BleInputProperty.disabled);\n```\n\n### Pairing\n\n#### Trigger pairing\n\n##### Pair on Android, Windows, Linux\n\n```dart\nawait UniversalBle.pair(deviceId);\n```\n\n##### Pair on Apple and web\nFor Apple and Web, pairing support depends on the device. Pairing is triggered automatically by the OS when you try to read/write from/to an encrypted characteristic.\n\nCalling `UniversalBle.pair(deviceId)` will only trigger pairing if the device has an *encrypted read characteristic*.\n\nIf your device only has encrypted write characteristics or you happen to know which encrypted read characteristic you want to use, you can pass it with a `pairingCommand`.\n\n```dart\nUniversalBle.pair(deviceId, pairingCommand: BleCommand(service:\"SERVICE\", characteristic:\"ENCRYPTED_CHARACTERISTIC\"));\n```\nAfter pairing you can check the pairing status.\n\n#### Pairing status\n\n##### Pair on Android, Windows, Linux\n\n```dart\n// Check current pairing state\nbool? isPaired = UniversalBle.isPaired(deviceId);\n```\n\n##### Pair on Apple and web\n\nFor `Apple` and `Web`, you have to pass a \"pairingCommand\" with an encrypted read or write characteristic. If you don't pass it then it will return `null`.\n\n```dart\nbool? isPaired = await UniversalBle.isPaired(deviceId, pairingCommand: BleCommand(service:\"SERVICE\", characteristic:\"ENCRYPTED_CHARACTERISTIC\"));\n```\n\n##### Discovering encrypted characteristic\nTo discover encrypted characteristics, make sure your device is not paired and use the example app to read/write to all discovered characteristics one by one. If one of them triggers pairing, that means it is encrypted and you can use it to construct `BleCommand(service:\"SERVICE\", characteristic:\"ENCRYPTED_CHARACTERISTIC\")`.\n\n#### Pairing state changes\n\n```dart\nUniversalBle.onPairingStateChange = (String deviceId, bool isPaired) {\n  // Handle pairing state change\n}\n```\n\n#### Unpair\n```dart\nUniversalBle.unpair(deviceId);\n```\n\n### Bluetooth Availability\n\n```dart\n// Get current Bluetooth availability state\nAvailabilityState availabilityState = UniversalBle.getBluetoothAvailabilityState(); // e.g. poweredOff or poweredOn,\n\n// Receive Bluetooth availability changes\nUniversalBle.onAvailabilityChange = (state) {\n  // Handle the new Bluetooth availability state\n};\n\n// Enable Bluetooth programmatically\nUniversalBle.enableBluetooth();\n\n// Disable Bluetooth programmatically\nUniversalBle.disableBluetooth();\n```\n\n### Request MTU\n\nThis method will **attempt** to set the MTU (Maximum Transmission Unit) but it is not guaranteed to succeed due to platform limitations. It will always return the current MTU.\n\n```dart\nint mtu = await UniversalBle.requestMtu(widget.deviceId, 247);\n```\n\n#### Platform Limitations\n\nOn most platforms, the MTU can only be queried but not manually set:\n\n- **iOS/macOS**: System automatically sets MTU to 185 bytes maximum\n- **Android 14+**: System automatically sets MTU to 517 bytes for the first GATT client\n- **Windows**: MTU can only be queried\n- **Linux**: MTU can only be queried\n- **Web**: No mechanism to query or modify MTU size\n\n#### Best Practices\n\nWhen developing cross-platform BLE applications and devices:\n\n- Design for default MTU size (23 bytes) as default\n- Dynamically adapt to use larger packet sizes when the system provides them\n- Take advantage of the increased throughput when available without requiring it\n- Implement data fragmentation for larger transfers\n- Handle platform-specific MTU size based on current value\n\n## Command Queue\n\nBy default, all commands are executed in a global queue (`QueueType.global`), with each command waiting for the previous one to finish.\n\nIf you want to parallelize commands between multiple devices, you can set:\n\n```dart\n// Create a separate queue for each device.\nUniversalBle.queueType = QueueType.perDevice;\n```\n\nYou can also disable the queue completely and parallelize all commands, even for the same device, by using:\n\n```dart\n// Disable queue\nUniversalBle.queueType = QueueType.none;\n```\n\nKeep 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 set `queueType` to `none`.\n\nYou can get queue updates by setting:\n\n```dart\n// Get queue state updates\nUniversalBle.onQueueUpdate = (String id, int remainingItems) {\n  debugPrint(\"Queue: $id Remaining: $remainingItems\");\n};\n```\n\n## Timeout\n\nBy default, all commands have a timeout of 10 seconds.\n\n```dart\n// Change timeout\nUniversalBle.timeout = const Duration(seconds: 10);\n\n// Disable timeout\nUniversalBle.timeout = null;\n```\n\n## UUID Format Agnostic\n\nUniversalBLE 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.\n\nFor consistency, all characteristic and service UUIDs will be returned in **lowercase 128-bit format**, across all platforms, e.g. `0000180a-0000-1000-8000-00805f9b34fb`.\n\n### Utility Methods\n\nIf you need to convert any UUIDs in your app you can use the following methods.\n\n- `BleUuidParser.string()` converts a string to a 128-bit UUID formatted string:\n\n```dart\nBleUuidParser.string(\"180A\"); // \"0000180a-0000-1000-8000-00805f9b34fb\"\n\nBleUuidParser.string(\"0000180A-0000-1000-8000-00805F9B34FB\"); // \"0000180a-0000-1000-8000-00805f9b34fb\"\n```\n\n- `BleUuidParser.number()` converts a number to a 128-bit UUID formatted string:\n\n```dart\nBleUuidParser.number(0x180A); // \"0000180a-0000-1000-8000-00805f9b34fb\"\n```\n\n- `BleUuidParser.compare()` compares two differently formatted UUIDs:\n\n```dart\nBleUuidParser.compare(\"180a\",\"0000180A-0000-1000-8000-00805F9B34FB\"); // true\n```\n\n## Platform-specific Setup\n\n### Android\n\nAdd the following permissions to your AndroidManifest.xml file:\n\n```xml\n\u003cuses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" /\u003e\n\u003cuses-permission android:name=\"android.permission.BLUETOOTH\" android:maxSdkVersion=\"30\" /\u003e\n\u003cuses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\" android:maxSdkVersion=\"30\" /\u003e\n\u003cuses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" android:maxSdkVersion=\"28\" /\u003e\n\u003cuses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" android:maxSdkVersion=\"30\" /\u003e\n\u003cuses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" android:usesPermissionFlags=\"neverForLocation\" /\u003e\n```\n\nIf your app uses iBeacons or BLUETOOTH_SCAN to determine location, change the last 2 permissions to:\n\n```xml\n\u003cuses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" /\u003e\n\u003cuses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" /\u003e\n```\n\n### iOS / macOS\n\nAdd `NSBluetoothPeripheralUsageDescription` and `NSBluetoothAlwaysUsageDescription` to Info.plist of your iOS and macOS app.\n\nAdd the `Bluetooth` capability to the macOS app from Xcode.\n\n### Windows / Linux\n\nYour 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.\n\nWhen publishing on Windows, you need to declare the following [capabilities](https://learn.microsoft.com/en-us/windows/uwp/packaging/app-capability-declarations): `bluetooth, radios`.\n\nWhen publishing on Linux as a snap, you need to declare the `bluez` plug in `snapcraft.yaml`.\n```\n...\n  plugs:\n    - bluez\n```\n\n### Web\n\nOn web, the `withServices` parameter in the ScanFilter is used as [optional_services](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/requestDevice#optionalservices) as well as a services filter. 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.\n\n```dart\nScanFilter(\n  withServices: kIsWeb ?  [\"SERVICE_UUID\"] : [],\n)\n```\n\nIf you don't want to apply any filter for these services but still want to access them, after connection, use `PlatformConfig`.\n\n```dart\nUniversalBle.startScan(\n  platformConfig: PlatformConfig(\n    web: WebOptions(\n      optionalServices: [\"SERVICE_UUID\"]\n    )\n  )\n)\n```\n\n## Customizing Platform Implementation of UniversalBle\n\n```dart\n// Create a class that extends UniversalBlePlatform\nclass UniversalBleMock extends UniversalBlePlatform {\n  // Implement all commands\n}\n\nUniversalBle.setInstance(UniversalBleMock());\n```\n","funding_links":["https://github.com/sponsors/navideck"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnavideck%2Funiversal_ble","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnavideck%2Funiversal_ble","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnavideck%2Funiversal_ble/lists"}