remove dependency again

This commit is contained in:
Jonas Bark
2025-03-29 10:28:00 +01:00
parent de8ef5f10b
commit 56ecad07cd
223 changed files with 6 additions and 20332 deletions

View File

@@ -495,10 +495,11 @@ packages:
universal_ble:
dependency: "direct main"
description:
path: universal_ble
relative: true
source: path
version: "0.18.0"
name: universal_ble
sha256: "35d210e93a5938c6a6d1fd3c710cf4ac90b1bdd1b11c8eb2beeb32600672e6e6"
url: "https://pub.dev"
source: hosted
version: "0.17.0"
url_launcher:
dependency: "direct main"
description:

View File

@@ -12,8 +12,7 @@ dependencies:
url_launcher: ^6.3.1
flutter_local_notifications: ^19.0.0
universal_ble:
path: universal_ble
universal_ble: any
protobuf: ^3.1.0
dartx: any
pointycastle: any

View File

@@ -1 +0,0 @@
github: navideck

View File

@@ -1,36 +0,0 @@
name: Build web app
on:
push:
branches: [ "main" ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
- run: flutter --version
- name: Cache pub dependencies
uses: actions/cache@v2
with:
path: ${{ env.FLUTTER_HOME }}/.pub-cache
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
restore-keys: ${{ runner.os }}-pub-
- name: Download pub dependencies
run: flutter pub get
- name: Build Web App
run: cd example && flutter build web --base-href=/universal_ble/
- name: Deploy to GitHub Pages 🚀
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: example/build/web

View File

@@ -1,36 +0,0 @@
name: BleUuidParser Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
- name: Install dependencies
run: flutter pub get
- name: Analyze project source
run: flutter analyze
- name: Run Flutter tests
run: flutter test
- name: Install Chrome
uses: browser-actions/setup-chrome@latest
- name: Run Flutter tests on Chrome
uses: coactions/setup-xvfb@v1
with:
run: flutter test --platform chrome

View File

@@ -1,30 +0,0 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

View File

@@ -1,42 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "d211f42860350d914a5ad8102f9ec32764dc6d06"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: android
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: ios
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: linux
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: macos
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: windows
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -1,21 +0,0 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.20348.0",
"compilerPath": "cl.exe",
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "windows-msvc-x64"
}
],
"version": 4
}

View File

@@ -1,44 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "example",
"cwd": "example",
"request": "launch",
"type": "dart"
},
{
"name": "example release",
"cwd": "example",
"request": "launch",
"type": "dart",
"flutterMode": "release"
},
{
"name": "example_mock",
"cwd": "example",
"request": "launch",
"type": "dart",
"args": [
"--dart-define",
"MOCK=true",
]
},
{
"name": "example_web",
"cwd": "example",
"request": "launch",
"type": "dart",
"program": "lib/main.dart",
"args": [
"-d",
"web-server",
"--web-hostname=127.0.0.1",
"--web-port=8080"
],
},
],
}

View File

@@ -1,150 +0,0 @@
{
"cSpell.words": [
"bluez",
"BTLE",
"BTNHIGHLIGHT",
"bytevc",
"cccd",
"connnection",
"Cupertino",
"DFACE",
"HFONT",
"HINSTANCE",
"HMENU",
"hresult",
"hstring",
"HWND",
"Inspectable",
"LOWORD",
"LPARAM",
"lpfn",
"LPOSVERSIONINFO",
"lpsz",
"LRESULT",
"MAXIMIZEBOX",
"microtask",
"MINIMIZEBOX",
"modalias",
"msix",
"navideck",
"NOMOVE",
"NOSIZE",
"NSUUID",
"NTDDI",
"ntdll",
"NTSTATUS",
"OSVERSIONINFOW",
"osvi",
"OVERLAPPEDWINDOW",
"pairable",
"PNTSTATUS",
"PRTL",
"pubspec",
"RRSI",
"rssi",
"SETFONT",
"SETTEXT",
"subdata",
"sublist",
"THICKFRAME",
"unawaited",
"UNIVERSALBLE",
"Unpair",
"Unpairing",
"unwatch",
"uuidstr",
"winrt",
"WNDCLASS",
"WPARAM",
"ZUUID"
],
"cmake.configureOnOpen": false,
"files.associations": {
"xstring": "cpp",
"chrono": "cpp",
"xlocale": "cpp",
"optional": "cpp",
"memory": "cpp",
"algorithm": "cpp",
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"cctype": "cpp",
"charconv": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"coroutine": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"exception": "cpp",
"format": "cpp",
"forward_list": "cpp",
"functional": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"ios": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"iterator": "cpp",
"limits": "cpp",
"list": "cpp",
"locale": "cpp",
"map": "cpp",
"mutex": "cpp",
"new": "cpp",
"ostream": "cpp",
"ratio": "cpp",
"set": "cpp",
"shared_mutex": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"string": "cpp",
"system_error": "cpp",
"thread": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeinfo": "cpp",
"unordered_map": "cpp",
"utility": "cpp",
"variant": "cpp",
"vector": "cpp",
"xfacet": "cpp",
"xhash": "cpp",
"xiosbase": "cpp",
"xlocbuf": "cpp",
"xlocinfo": "cpp",
"xlocmes": "cpp",
"xlocmon": "cpp",
"xlocnum": "cpp",
"xloctime": "cpp",
"xmemory": "cpp",
"xtr1common": "cpp",
"xtree": "cpp",
"xutility": "cpp",
"deque": "cpp",
"fstream": "cpp",
"unordered_set": "cpp",
"complex": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"filesystem": "cpp",
"ranges": "cpp",
"span": "cpp",
"stack": "cpp",
"typeindex": "cpp",
"future": "cpp",
"regex": "cpp"
}
}

View File

@@ -1,171 +0,0 @@
## 0.18.0
* Improve docs for isPaired
## 0.17.0
* Fix Windows crash when calling pair APIs with an unknown deviceId
* Fix Windows crash when calling startScan again after some time
* Fix Windows warning `Unable to establish connection on channel` when hot restarting
* Refactor native Windows code
* Improve Android permission documentation
## 0.16.0
* BREAKING CHANGE: `payload` is now `payloadPrefix`
* BREAKING CHANGE: `mask` is now `payloadMask`
* Bump bluez to 0.8.3
* Do not normalize UUIDs on native side
* Reverse _permissionStatus return values in example app
* Improve readme
* Improve ManufacturerDataFilter
## 0.15.0
* `getSystemDevices(withServices:)` now sets several generic services by default as filter
* `getConnectionState` on Android will now return `BleConnectionState.disconnected` if device is connected to the system but not to the app
* Improve callback error handling
## 0.14.0
* BREAKING CHANGE: `bleDevice.name` now filters out non-printable characters
* Add `bleDevice.rawName`
* Add `disableBluetooth` API
* Fix Apple CBCentralManager lazy initialization on `startScan`
* Fix Android error `scan too frequently` and auto retry scan once sufficient time has passed
* Fix duplicate `onValueChanged` events on Android 11 or earlier
* Fix Windows `withServices` filter
* Fix Windows Bluetooth availability state in release builds
* Throw error on Apple if Bluetooth permission is denied on `startScan`
* Do not throw `Characteristic already notifying` error. Subscribing to characteristic notifications multiple times is now handled safely
* Bump flutter_web_bluetooth to 1.0.0
## 0.13.0
* BREAKING CHANGE: `scanFilter` filters are now in OR relation
* BREAKING CHANGE: `manufacturerDataHead` is removed from `BleDevice`
* BREAKING CHANGE: `WebConfig` is now `WebOptions`
* BREAKING CHANGE: `ManufacturerDataFilter.data` is now `ManufacturerDataFilter.payload`
* BREAKING CHANGE: `connect()` does not return a boolean anymore. It will throw error on connection failure
* BREAKING CHANGE: `pair()` does not return a boolean anymore. It will throw error on connection failure
* BREAKING CHANGE: `onConnectionChange` returns error as well
* BREAKING CHANGE: rename in-app pairing capabilities
* Deprecation: `manufacturerData` is deprecated in BleDevice and will be removed in the future
* Improve `scanFilter` handling
* Use `ManufacturerData` object instead of `Uint8List` for manufacturerData
* Add `manufacturerDataList` as `List<ManufacturerData>` in `BleDevice`
* Auto convert all services passed to `getSystemDevices()`
* Return false for receivesAdvertisements on Linux/Web
* Add 1s delay in discoverServices on Linux
* Add `connectionStream` API to get connection updates as stream
## 0.12.0
* BREAKING CHANGE: `unPair` is now `unpair`
* BREAKING CHANGE: `onPairingStateChange` does not return error anymore
* Add `pair()`, `isPaired` and `onPairingStateChange` support for Apple and web
* `connect()` and `pair()` now return a bool result
* Add `PlatformConfig` property in `StartScan`
* Add `WebConfig` property in `PlatformConfig`
* Fix notifications for characteristics without cccd on Android
* Promote Linux to stable
## 0.11.1
* Trim spaces in UUIDs
* Receive advertisement events on web
* Improve cleanup after disconnection on web
## 0.11.0
* Unify UUID format across all platforms, 128-bit lowercase
* Add BleUuidParser utility methods for UUID parsing
## 0.10.1
* Improve Android error handling
* Fix Android disconnection events sometimes missed
* Improve cleanup after disconnection on Apple and Android
* Support pairing on Apple
* Improve code level documentation
## 0.10.0
* BREAKING CHANGE: `ScanResult` is now `BleDevice`
* BREAKING CHANGE: `getConnectedDevices` is now `getSystemDevices`
* BREAKING CHANGE: `isPaired` is now nullable
* BREAKING CHANGE: `onValueChanged` is now `onValueChange`
* BREAKING CHANGE: `onConnectionChanged` is now `onConnectionChange`
* Add `connectionState` property to BleDevice
* Add `isSystemDevice` property to BleDevice
* Add `.perDevice` queue
* Support "ProvidePin" pairing on Windows 10/11
* Get RRSI updates on Apple platforms
* Improve enum parsing performance
* Improve code level documentation
## 0.9.11
* Add device name prefix filtering
## 0.9.10
* Fix Windows scan filter
* Remove scan result caching on Windows
* Improve service discovery on Linux
* Use asynchronous callbacks for SetNotifiable
* Remove dependency on `convert`
* Remove dependency on `collection`
* Update Android example app Gradle
## 0.9.9
* Improve service discovery on Apple
* Improve reconnection on Apple
* Persist long scan result name on Windows
* Fix Android serviceUuids in scanResults
* Example app improvements
## 0.9.8
* Improve manufacturer data discovery on Windows
* Example app improvements
## 0.9.7
* Fix characteristic keying on linux allowing receiving data from multiple BLE devices at the same time
## 0.9.6
* Fix a Windows issue where characteristics of certain services would not be discovered
* Improve readme
## 0.9.5
* Windows support has graduated from beta to stable
* Support filtering by manufacturer data
* Unify web's optionalServices API with filtering API
* Implement requestMtu in Linux
* Fix wrong manufacturer data in Windows release builds
* Improve logging
## 0.9.4
* Fix Windows crash when no Bluetooth adapter is present
## 0.9.3
* Add scan filter (withServices:) in `startScan()`
* Add service UUIDs from advertisements in `BleScanResult`
* Fix Windows release build compilation
## 0.9.2
* Add command queue
* Improve error handling on Android, iOS, macOS, Windows, Linux and web
## 0.9.1
* Improve logging
## 0.9.0
* Improve windows device name discovery
* Improve linux implementation
* Improve titles in example app
## 0.8.4
* Add `WebRequestOptionsBuilder.defaultServices` for convenience when setting `optionalServices` when scanning on web
* Add "try online" URL in readme
* Improve readme
* Improve example app
## 0.8.3
* Rename onPairStateChange *> onPairingStateChange
* Improve readme
## 0.8.2
* Improve readme
## 0.8.1
* Update supported platforms
## 0.8.0
* Initial release

View File

@@ -1,31 +0,0 @@
BSD 3-Clause License
Copyright (c) 2024, Navideck Labs OÜ
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
Copyright (c) 2020, Woodemi Co,Ltd.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,448 +0,0 @@
# UniversalBLE
[![universal_ble version](https://img.shields.io/pub/v/universal_ble?label=universal_ble)](https://pub.dev/packages/universal_ble)
A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter.
[Try it online](https://navideck.github.io/universal_ble/), provided your browser supports [Web Bluetooth](https://caniuse.com/web-bluetooth).
## Features
- [Scanning](#scanning)
- [Connecting](#connecting)
- [Discovering Services](#discovering-services)
- [Reading & Writing data](#reading--writing-data)
- [Pairing](#pairing)
- [Bluetooth Availability](#bluetooth-availability)
- [Command Queue](#command-queue)
- [Timeout](#timeout)
- [UUID Format Agnostic](#uuid-format-agnostic)
## Usage
### API Support Matrix
| API | Android | iOS | macOS | Windows | Linux | Web |
| :------------------- | :-----: | :-: | :---: | :-----: | :----------: | :-: |
| startScan/stopScan | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| connect/disconnect | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| getSystemDevices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
| discoverServices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| readValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| writeValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| setNotifiable | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| pair | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ⏺ |
| unpair | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
| isPaired | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| onPairingStateChange | ✔️ | ⏺ | ⏺ | ✔️ | ✔️ | ⏺ |
| getBluetoothAvailabilityState | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
| enable/disable Bluetooth | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
| onAvailabilityChange | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| requestMtu | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
## Getting Started
Add universal_ble in your pubspec.yaml:
```yaml
dependencies:
universal_ble:
```
and import it wherever you want to use it:
```dart
import 'package:universal_ble/universal_ble.dart';
```
### Scanning
```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: [ManufacturerDataFilter(companyIdentifier: 0x004c)],
withNamePrefix: ["NAME_PREFIX"],
)
);
// Stop scanning
UniversalBle.stopScan();
```
Before initiating a scan, ensure that Bluetooth is available:
```dart
AvailabilityState state = await UniversalBle.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 the [Bluetooth Availability](#bluetooth-availability) section for more.
#### System Devices
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 using `getSystemDevices()`.
```dart
// Get already connected devices.
// You can set `withServices` to narrow down the results.
// On `Apple`, `withServices` is required to get any connected devices. If not passed, several [18XX] generic services will be set by default.
List<BleDevice> devices = await UniversalBle.getSystemDevices(withServices: []);
```
For each such device the `isSystemDevice` property will be `true`.
You still need to explicitly [connect](#connecting) to them before being able to use them.
#### Scan Filter
You 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.
##### With Services
When setting this parameter, the scan results will only include devices that advertise any of the specified services.
```dart
List<String> withServices;
```
Note: On web **you have to** specify services before you are able to use them. See the [web](#web) section for more details.
##### With ManufacturerData
Use 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.
You can filter manufacturer data by company identifier, payload prefix, or payload mask.
```dart
List<ManufacturerDataFilter> withManufacturerData = [ManufacturerDataFilter(
companyIdentifier: 0x004c,
payloadPrefix: Uint8List.fromList([0x001D,0x001A]),
payloadMask: Uint8List.fromList([1,0,1,1]))
];
```
##### With namePrefix
Use 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.
```dart
List<String> withNamePrefix;
```
### Connecting
```dart
// Connect to a device using the `deviceId` of the BleDevice received from `UniversalBle.onScanResult`
String deviceId = bleDevice.deviceId;
UniversalBle.connect(deviceId);
// Disconnect from a device
UniversalBle.disconnect(deviceId);
// Get connection/disconnection updates
UniversalBle.onConnectionChange = (String deviceId, bool isConnected, String? error) {
debugPrint('OnConnectionChange $deviceId, $isConnected Error: $error');
}
// Get current connection state
// Can be connected, disconnected, connecting or disconnecting
BleConnectionState connectionState = await bleDevice.connectionState;
```
### Discovering Services
After establishing a connection, you need to discover services. This method will discover all services and their characteristics.
```dart
// Discover services of a specific device
UniversalBle.discoverServices(deviceId);
```
### Reading & Writing data
You need to first [discover services](#discovering-services) before you are able to read and write to characteristics.
```dart
// 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 = (String deviceId, String characteristicId, Uint8List value) {
debugPrint('onValueChange $deviceId, $characteristicId, ${hex.encode(value)}');
}
// Unsubscribe from a characteristic
UniversalBle.setNotifiable(deviceId, serviceId, characteristicId, BleInputProperty.disabled);
```
### Pairing
#### Trigger pairing
##### Pair on Android, Windows, Linux
```dart
await UniversalBle.pair(deviceId);
```
##### Pair on Apple and web
For 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.
Calling `UniversalBle.pair(deviceId)` will only trigger pairing if the device has an *encrypted read characteristic*.
If 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`.
```dart
UniversalBle.pair(deviceId, pairingCommand: BleCommand(service:"SERVICE", characteristic:"ENCRYPTED_CHARACTERISTIC"));
```
After pairing you can check the pairing status.
#### Pairing status
##### Pair on Android, Windows, Linux
```dart
// Check current pairing state
bool? isPaired = UniversalBle.isPaired(deviceId);
```
##### Pair on Apple and web
For `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`.
```dart
bool? isPaired = await UniversalBle.isPaired(deviceId, pairingCommand: BleCommand(service:"SERVICE", characteristic:"ENCRYPTED_CHARACTERISTIC"));
```
##### Discovering encrypted characteristic
To 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")`.
#### Pairing state changes
```dart
UniversalBle.onPairingStateChange = (String deviceId, bool isPaired) {
// Handle pairing state change
}
```
#### Unpair
```dart
UniversalBle.unpair(deviceId);
```
### Bluetooth Availability
```dart
// Get current Bluetooth availability state
AvailabilityState availabilityState = 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();
// Disable Bluetooth programmatically
UniversalBle.disableBluetooth();
```
### Request MTU
This 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.
```dart
int mtu = await UniversalBle.requestMtu(widget.deviceId, 247);
```
#### Platform Limitations
On most platforms, the MTU can only be queried but not manually set:
- **iOS/macOS**: System automatically sets MTU to 185 bytes maximum
- **Android 14+**: System automatically sets MTU to 517 bytes for the first GATT client
- **Windows**: MTU can only be queried
- **Linux**: MTU can only be queried
- **Web**: No mechanism to query or modify MTU size
#### Best Practices
When developing cross-platform BLE applications and devices:
- Design for default MTU size (23 bytes) as default
- Dynamically adapt to use larger packet sizes when the system provides them
- Take advantage of the increased throughput when available without requiring it
- Implement data fragmentation for larger transfers
- Handle platform-specific MTU size based on current value
## Command Queue
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:
```dart
// 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:
```dart
// 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 set `queueType` to `none`.
You can get queue updates by setting:
```dart
// Get queue state updates
UniversalBle.onQueueUpdate = (String id, int remainingItems) {
debugPrint("Queue: $id Remaining: $remainingItems");
};
```
## Timeout
By default, all commands have a timeout of 10 seconds.
```dart
// Change timeout
UniversalBle.timeout = const Duration(seconds: 10);
// Disable timeout
UniversalBle.timeout = null;
```
## UUID Format Agnostic
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 in **lowercase 128-bit format**, across all platforms, e.g. `0000180a-0000-1000-8000-00805f9b34fb`.
### Utility Methods
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:
```dart
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:
```dart
BleUuidParser.number(0x180A); // "0000180a-0000-1000-8000-00805f9b34fb"
```
- `BleUuidParser.compare()` compares two differently formatted UUIDs:
```dart
BleUuidParser.compare("180a","0000180A-0000-1000-8000-00805F9B34FB"); // true
```
## Platform-specific Setup
### Android
Add the following permissions to your AndroidManifest.xml file:
```xml
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
```
If your app uses iBeacons or BLUETOOTH_SCAN to determine location, change the last 2 permissions to:
```xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
```
### iOS / macOS
Add `NSBluetoothPeripheralUsageDescription` and `NSBluetoothAlwaysUsageDescription` to Info.plist of your iOS and macOS app.
Add the `Bluetooth` capability to the macOS app from Xcode.
### Windows / Linux
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 following [capabilities](https://learn.microsoft.com/en-us/windows/uwp/packaging/app-capability-declarations): `bluetooth, radios`.
When publishing on Linux as a snap, you need to declare the `bluez` plug in `snapcraft.yaml`.
```
...
plugs:
- bluez
```
### Web
On 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.
```dart
ScanFilter(
withServices: kIsWeb ? ["SERVICE_UUID"] : [],
)
```
If you don't want to apply any filter for these services but still want to access them, after connection, use `PlatformConfig`.
```dart
UniversalBle.startScan(
platformConfig: PlatformConfig(
web: WebOptions(
optionalServices: ["SERVICE_UUID"]
)
)
)
```
## Customizing Platform Implementation of UniversalBle
```dart
// Create a class that extends UniversalBlePlatform
class UniversalBleMock extends UniversalBlePlatform {
// Implement all commands
}
UniversalBle.setInstance(UniversalBleMock());
```

View File

@@ -1,4 +0,0 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -1,9 +0,0 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

View File

@@ -1,68 +0,0 @@
group 'com.navideck.universal_ble'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
if (project.android.hasProperty("namespace")) {
namespace 'com.navideck.universal_ble'
}
compileSdkVersion 34
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
test.java.srcDirs += 'src/test/kotlin'
}
defaultConfig {
minSdkVersion 21
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'
}
testOptions {
unitTests.all {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}

View File

@@ -1 +0,0 @@
rootProject.name = 'universal_ble'

View File

@@ -1,10 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.navideck.universal_ble">
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
</manifest>

View File

@@ -1,79 +0,0 @@
package com.navideck.universal_ble
import android.annotation.SuppressLint
import android.bluetooth.BluetoothManager
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanSettings
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import java.util.LinkedList
private const val NUM_SCAN_DURATIONS_KEPT = 5
private const val EXCESSIVE_SCANNING_PERIOD_MS = 30 * 1000L
private const val TAG = "UniversalBlePlugin"
/**
* A safe wrapper for Bluetooth LE scanning operations that prevents excessive scanning.
*
* This class manages BLE scanning while adhering to Android's scanning frequency limits by:
* - Tracking scan start times over a 30-second window
* - Limiting to 5 scan operations within this window
* - Automatically scheduling delayed scans when frequency limits are exceeded
* - Providing safe start/stop scan operations with error handling
*
* The scanner will automatically delay new scan requests if the frequency limit is reached,
* and will retry once sufficient time has passed. This helps prevent scan failure errors
* and ensures compliance with Android's scanning restrictions.
*
* @property bluetoothManager The system's BluetoothManager used for scanning operations
*/
@SuppressLint("MissingPermission")
class SafeScanner(private val bluetoothManager: BluetoothManager) {
private val handler = Handler(Looper.myLooper()!!)
private val startTimes = LinkedList<Long>()
private var awaitingScan = false
fun startScan(filters: List<ScanFilter>, settings: ScanSettings, callback: ScanCallback) {
val now = System.currentTimeMillis()
startTimes.removeAll { now - it > EXCESSIVE_SCANNING_PERIOD_MS }
if (startTimes.size >= NUM_SCAN_DURATIONS_KEPT) {
if (awaitingScan) {
Log.e(TAG, "startScan: too frequent, awaiting scan..")
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
callback.onScanFailed(ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY)
}
awaitingScan = true
val delay = startTimes.first + EXCESSIVE_SCANNING_PERIOD_MS - now + 2_000
Log.e(TAG, "startScan: too frequent, schedule auto-start after $delay ms $startTimes")
handler.postDelayed({
Log.d(TAG, "Retrying scan after delay")
awaitingScan = false
startScan(filters, settings, callback)
}, delay)
} else {
awaitingScan = false
startTimes.addLast(now)
try {
bluetoothManager.adapter.bluetoothLeScanner?.startScan(filters, settings, callback)
} catch (e: Exception) {
Log.e(TAG, "Failed to start Scan : $e")
}
}
}
fun stopScan(callback: ScanCallback) {
awaitingScan = false
handler.removeCallbacksAndMessages(null)
bluetoothManager.adapter.bluetoothLeScanner?.stopScan(callback)
}
}

View File

@@ -1,725 +0,0 @@
// Autogenerated from Pigeon (v22.6.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package com.navideck.universal_ble
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
private fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
private fun createConnectionError(channelName: String): FlutterError {
return FlutterError("channel-error", "Unable to establish connection on channel: '$channelName'.", "")}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : Throwable()
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBleScanResult (
val deviceId: String,
val name: String? = null,
val isPaired: Boolean? = null,
val rssi: Long? = null,
val manufacturerDataList: List<UniversalManufacturerData>? = null,
val services: List<String>? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalBleScanResult {
val deviceId = pigeonVar_list[0] as String
val name = pigeonVar_list[1] as String?
val isPaired = pigeonVar_list[2] as Boolean?
val rssi = pigeonVar_list[3] as Long?
val manufacturerDataList = pigeonVar_list[4] as List<UniversalManufacturerData>?
val services = pigeonVar_list[5] as List<String>?
return UniversalBleScanResult(deviceId, name, isPaired, rssi, manufacturerDataList, services)
}
}
fun toList(): List<Any?> {
return listOf(
deviceId,
name,
isPaired,
rssi,
manufacturerDataList,
services,
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBleService (
val uuid: String,
val characteristics: List<UniversalBleCharacteristic>? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalBleService {
val uuid = pigeonVar_list[0] as String
val characteristics = pigeonVar_list[1] as List<UniversalBleCharacteristic>?
return UniversalBleService(uuid, characteristics)
}
}
fun toList(): List<Any?> {
return listOf(
uuid,
characteristics,
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBleCharacteristic (
val uuid: String,
val properties: List<Long>
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalBleCharacteristic {
val uuid = pigeonVar_list[0] as String
val properties = pigeonVar_list[1] as List<Long>
return UniversalBleCharacteristic(uuid, properties)
}
}
fun toList(): List<Any?> {
return listOf(
uuid,
properties,
)
}
}
/**
* Scan Filters
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class UniversalScanFilter (
val withServices: List<String>,
val withNamePrefix: List<String>,
val withManufacturerData: List<UniversalManufacturerDataFilter>
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalScanFilter {
val withServices = pigeonVar_list[0] as List<String>
val withNamePrefix = pigeonVar_list[1] as List<String>
val withManufacturerData = pigeonVar_list[2] as List<UniversalManufacturerDataFilter>
return UniversalScanFilter(withServices, withNamePrefix, withManufacturerData)
}
}
fun toList(): List<Any?> {
return listOf(
withServices,
withNamePrefix,
withManufacturerData,
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalManufacturerDataFilter (
val companyIdentifier: Long,
val data: ByteArray? = null,
val mask: ByteArray? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalManufacturerDataFilter {
val companyIdentifier = pigeonVar_list[0] as Long
val data = pigeonVar_list[1] as ByteArray?
val mask = pigeonVar_list[2] as ByteArray?
return UniversalManufacturerDataFilter(companyIdentifier, data, mask)
}
}
fun toList(): List<Any?> {
return listOf(
companyIdentifier,
data,
mask,
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalManufacturerData (
val companyIdentifier: Long,
val data: ByteArray
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalManufacturerData {
val companyIdentifier = pigeonVar_list[0] as Long
val data = pigeonVar_list[1] as ByteArray
return UniversalManufacturerData(companyIdentifier, data)
}
}
fun toList(): List<Any?> {
return listOf(
companyIdentifier,
data,
)
}
}
private open class UniversalBlePigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBleScanResult.fromList(it)
}
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBleService.fromList(it)
}
}
131.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBleCharacteristic.fromList(it)
}
}
132.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalScanFilter.fromList(it)
}
}
133.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalManufacturerDataFilter.fromList(it)
}
}
134.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalManufacturerData.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is UniversalBleScanResult -> {
stream.write(129)
writeValue(stream, value.toList())
}
is UniversalBleService -> {
stream.write(130)
writeValue(stream, value.toList())
}
is UniversalBleCharacteristic -> {
stream.write(131)
writeValue(stream, value.toList())
}
is UniversalScanFilter -> {
stream.write(132)
writeValue(stream, value.toList())
}
is UniversalManufacturerDataFilter -> {
stream.write(133)
writeValue(stream, value.toList())
}
is UniversalManufacturerData -> {
stream.write(134)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/**
* Flutter -> Native
*
* Generated interface from Pigeon that represents a handler of messages from Flutter.
*/
interface UniversalBlePlatformChannel {
fun getBluetoothAvailabilityState(callback: (Result<Long>) -> Unit)
fun enableBluetooth(callback: (Result<Boolean>) -> Unit)
fun disableBluetooth(callback: (Result<Boolean>) -> Unit)
fun startScan(filter: UniversalScanFilter?)
fun stopScan()
fun connect(deviceId: String)
fun disconnect(deviceId: String)
fun setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Long, callback: (Result<Unit>) -> Unit)
fun discoverServices(deviceId: String, callback: (Result<List<UniversalBleService>>) -> Unit)
fun readValue(deviceId: String, service: String, characteristic: String, callback: (Result<ByteArray>) -> Unit)
fun requestMtu(deviceId: String, expectedMtu: Long, callback: (Result<Long>) -> Unit)
fun writeValue(deviceId: String, service: String, characteristic: String, value: ByteArray, bleOutputProperty: Long, callback: (Result<Unit>) -> Unit)
fun isPaired(deviceId: String, callback: (Result<Boolean>) -> Unit)
fun pair(deviceId: String, callback: (Result<Boolean>) -> Unit)
fun unPair(deviceId: String)
fun getSystemDevices(withServices: List<String>, callback: (Result<List<UniversalBleScanResult>>) -> Unit)
fun getConnectionState(deviceId: String): Long
companion object {
/** The codec used by UniversalBlePlatformChannel. */
val codec: MessageCodec<Any?> by lazy {
UniversalBlePigeonCodec()
}
/** Sets up an instance of `UniversalBlePlatformChannel` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: UniversalBlePlatformChannel?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.getBluetoothAvailabilityState{ 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 {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.enableBluetooth{ result: Result<Boolean> ->
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 {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disableBluetooth$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.disableBluetooth{ result: Result<Boolean> ->
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 {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val filterArg = args[0] as UniversalScanFilter?
val wrapped: List<Any?> = try {
api.startScan(filterArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.stopScan()
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val wrapped: List<Any?> = try {
api.connect(deviceIdArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val wrapped: List<Any?> = try {
api.disconnect(deviceIdArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val serviceArg = args[1] as String
val characteristicArg = args[2] as String
val bleInputPropertyArg = args[3] as Long
api.setNotifiable(deviceIdArg, serviceArg, characteristicArg, bleInputPropertyArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
reply.reply(wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
api.discoverServices(deviceIdArg) { result: Result<List<UniversalBleService>> ->
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 {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val serviceArg = args[1] as String
val characteristicArg = args[2] as String
api.readValue(deviceIdArg, serviceArg, characteristicArg) { result: Result<ByteArray> ->
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 {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val expectedMtuArg = args[1] as Long
api.requestMtu(deviceIdArg, expectedMtuArg) { 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 {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val serviceArg = args[1] as String
val characteristicArg = args[2] as String
val valueArg = args[3] as ByteArray
val bleOutputPropertyArg = args[4] as Long
api.writeValue(deviceIdArg, serviceArg, characteristicArg, valueArg, bleOutputPropertyArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
reply.reply(wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
api.isPaired(deviceIdArg) { result: Result<Boolean> ->
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 {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
api.pair(deviceIdArg) { result: Result<Boolean> ->
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 {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val wrapped: List<Any?> = try {
api.unPair(deviceIdArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val withServicesArg = args[0] as List<String>
api.getSystemDevices(withServicesArg) { result: Result<List<UniversalBleScanResult>> ->
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 {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectionState$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val wrapped: List<Any?> = try {
listOf(api.getConnectionState(deviceIdArg))
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
/**
* Native -> Flutter
*
* Generated class from Pigeon that represents Flutter messages that can be called from Kotlin.
*/
class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") {
companion object {
/** The codec used by UniversalBleCallbackChannel. */
val codec: MessageCodec<Any?> by lazy {
UniversalBlePigeonCodec()
}
}
fun onAvailabilityChanged(stateArg: Long, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(stateArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(createConnectionError(channelName)))
}
}
}
fun onPairStateChange(deviceIdArg: String, isPairedArg: Boolean, errorArg: String?, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(deviceIdArg, isPairedArg, errorArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(createConnectionError(channelName)))
}
}
}
fun onScanResult(resultArg: UniversalBleScanResult, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(resultArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(createConnectionError(channelName)))
}
}
}
fun onValueChanged(deviceIdArg: String, characteristicIdArg: String, valueArg: ByteArray, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(deviceIdArg, characteristicIdArg, valueArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(createConnectionError(channelName)))
}
}
}
fun onConnectionChanged(deviceIdArg: String, connectedArg: Boolean, errorArg: String?, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(deviceIdArg, connectedArg, errorArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(createConnectionError(channelName)))
}
}
}
}

View File

@@ -1,164 +0,0 @@
package com.navideck.universal_ble
import android.annotation.SuppressLint
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.os.ParcelUuid
import android.util.Log
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.UUID
import kotlin.experimental.and
private const val TAG = "UniversalBlePlugin"
@SuppressLint("MissingPermission")
class UniversalBleFilterUtil {
var scanFilter: UniversalScanFilter? = null
var serviceFilterUUIDS: List<UUID> = emptyList()
fun filterDevice(
name: String?,
manufacturerDataList: List<UniversalManufacturerData>,
serviceUuids: Array<UUID>,
): Boolean {
val filter = scanFilter ?: return true
val hasNamePrefixFilter = filter.withNamePrefix.isNotEmpty()
val hasServiceFilter = filter.withServices.isNotEmpty()
val hasManufacturerDataFilter = filter.withManufacturerData.isNotEmpty()
// If there is no filter at all, then allow device
if (!hasNamePrefixFilter &&
!hasServiceFilter &&
!hasManufacturerDataFilter
) {
return true
}
// For not, we only have DeviceName filter
return hasNamePrefixFilter && isNameMatchingFilters(filter, name) ||
hasServiceFilter && isServicesMatchingFilters(serviceUuids) ||
hasManufacturerDataFilter && isManufacturerDataMatchingFilters(
filter,
manufacturerDataList
)
}
private fun isNameMatchingFilters(scanFilter: UniversalScanFilter, name: String?): Boolean {
val namePrefixFilter = scanFilter.withNamePrefix.filterNotNull()
if (namePrefixFilter.isEmpty()) {
return true
}
if (name.isNullOrEmpty()) {
return false
}
return namePrefixFilter.any { name.startsWith(it) }
}
private fun isServicesMatchingFilters(
serviceUuids: Array<UUID>,
): Boolean {
if (serviceFilterUUIDS.isEmpty()) {
return true
}
if (serviceUuids.isEmpty()) {
return false
}
return serviceFilterUUIDS.any {
serviceUuids.contains(it)
}
}
private fun isManufacturerDataMatchingFilters(
scanFilter: UniversalScanFilter,
manufacturerDataList: List<UniversalManufacturerData>,
): Boolean {
val filters = scanFilter.withManufacturerData.filterNotNull()
if (filters.isEmpty()) return true
if (manufacturerDataList.isEmpty()) return false
return manufacturerDataList.any { msd ->
filters.any { filter ->
msd.companyIdentifier == filter.companyIdentifier &&
isDataMatching(filter.data, msd.data, filter.mask)
}
}
}
private fun isDataMatching(
filterData: ByteArray?,
deviceData: ByteArray,
filterMask: ByteArray?,
): Boolean {
if (filterData == null) return true
if (filterData.size > deviceData.size) return false
val mask = filterMask ?: ByteArray(filterData.size) { 0xFF.toByte() }
if (filterData.size != mask.size) return false
return filterData.indices.all { i ->
(filterData[i] and mask[i]) == (deviceData[i] and mask[i])
}
}
}
fun UniversalScanFilter.usesCustomFilters(): Boolean {
// NamePrefix Filtering is not supported in native filters
return withNamePrefix.isNotEmpty()
}
// Convert UniversalScanFilter to ScanFilter
fun UniversalScanFilter.toScanFilters(serviceUuids: List<UUID>): List<ScanFilter> {
val scanFilters: ArrayList<ScanFilter> = arrayListOf()
// Add withServices Filter
for (service in serviceUuids) {
try {
service.let {
scanFilters.add(
ScanFilter.Builder().setServiceUuid(ParcelUuid(it)).build()
)
Log.e(TAG, "scanFilters: $it")
}
} catch (e: Exception) {
Log.e(TAG, e.toString())
throw FlutterError(
"illegalIllegalArgument",
"Invalid serviceId: $service",
e.toString()
)
}
}
// Add ManufacturerData Filter
for (manufacturerData in this.withManufacturerData) {
try {
manufacturerData?.companyIdentifier?.let {
val data: ByteArray = manufacturerData.data ?: ByteArray(0)
val mask: ByteArray? = manufacturerData.mask
if (mask == null) {
scanFilters.add(
ScanFilter.Builder().setManufacturerData(
it.toInt(), data
).build()
)
} else {
scanFilters.add(
ScanFilter.Builder().setManufacturerData(
it.toInt(), data, mask
).build()
)
}
}
} catch (e: Exception) {
Log.e(TAG, e.toString())
throw FlutterError(
"illegalIllegalArgument",
"Invalid manufacturerData: ${manufacturerData?.companyIdentifier} ${manufacturerData?.data} ${manufacturerData?.mask}",
e.toString()
)
}
}
return scanFilters.toList()
}

View File

@@ -1,358 +0,0 @@
package com.navideck.universal_ble
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED
import android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED
import android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED
import android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR
import android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES
import android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY
import android.bluetooth.le.ScanResult
import android.util.Log
import android.util.SparseArray
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.UUID
private const val TAG = "UniversalBlePlugin"
private val knownGatts = mutableMapOf<String, BluetoothGatt>()
val ccdCharacteristic: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
enum class BleConnectionState(val value: Long) {
Connected(0),
Disconnected(1),
Connecting(2),
Disconnecting(3)
}
enum class AvailabilityState(val value: Long) {
Unknown(0),
Resetting(1),
Unsupported(2),
Unauthorized(3),
PoweredOff(4),
PoweredOn(5);
}
enum class BleInputProperty(val value: Long) {
Disabled(0),
Notification(1),
Indication(2);
}
enum class BleOutputProperty(val value: Long) {
withResponse(0),
withoutResponse(1);
}
enum class CharacteristicProperty(val value: Long) {
Broadcast(0),
Read(1),
WriteWithoutResponse(2),
Write(3),
Notify(4),
Indicate(5),
AuthenticatedSignedWrites(6),
ExtendedProperties(7)
}
fun Int.toBleConnectionState(): BleConnectionState {
return when (this) {
BluetoothGatt.STATE_CONNECTED -> BleConnectionState.Connected
BluetoothGatt.STATE_CONNECTING -> BleConnectionState.Connecting
BluetoothGatt.STATE_DISCONNECTING -> BleConnectionState.Disconnecting
BluetoothGatt.STATE_DISCONNECTED -> BleConnectionState.Disconnected
else -> BleConnectionState.Disconnected
}
}
fun List<String>.toUUIDList(): List<UUID> {
return this.map { UUID.fromString(it) }
}
fun String.toBluetoothGatt(): BluetoothGatt {
return this.findGatt()
?: throw FlutterError("IllegalArgument", "Unknown deviceId: $this", null)
}
fun String.isKnownGatt(): Boolean {
return this.findGatt() != null
}
fun String.findGatt(): BluetoothGatt? {
return knownGatts[this]
}
fun BluetoothGatt.saveCacheIfNeeded() {
knownGatts[this.device.address] = this
}
fun BluetoothGatt.removeCache() {
knownGatts.remove(this.device.address)
}
fun Int.toAvailabilityState(): Long {
return when (this) {
BluetoothAdapter.STATE_OFF -> AvailabilityState.PoweredOff.value
BluetoothAdapter.STATE_ON -> AvailabilityState.PoweredOn.value
BluetoothAdapter.STATE_TURNING_ON -> AvailabilityState.Resetting.value
BluetoothAdapter.STATE_TURNING_OFF -> AvailabilityState.Resetting.value
else -> AvailabilityState.Unknown.value
}
}
fun Int.parseScanErrorMessage(): String {
return when (this) {
SCAN_FAILED_ALREADY_STARTED -> "SCAN_FAILED_ALREADY_STARTED"
SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> "SCAN_FAILED_APPLICATION_REGISTRATION_FAILED"
SCAN_FAILED_FEATURE_UNSUPPORTED -> "SCAN_FAILED_FEATURE_UNSUPPORTED"
SCAN_FAILED_INTERNAL_ERROR -> "SCAN_FAILED_INTERNAL_ERROR"
SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES -> "SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES"
SCAN_FAILED_SCANNING_TOO_FREQUENTLY -> "SCAN_FAILED_SCANNING_TOO_FREQUENTLY"
else -> "ErrorCode: $this"
}
}
fun Int.parseBluetoothStatusCodeError(): String? {
if (this == BluetoothStatusCodes.SUCCESS) return null
return when (this) {
BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED -> "ERROR_BLUETOOTH_NOT_ENABLED"
BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED -> "ERROR_BLUETOOTH_NOT_ALLOWED"
BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED -> "ERROR_DEVICE_NOT_BONDED"
BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED -> "ERROR_GATT_WRITE_NOT_ALLOWED"
BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY -> "ERROR_GATT_WRITE_REQUEST_BUSY"
BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION -> "ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION"
BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND -> "ERROR_PROFILE_SERVICE_NOT_BOUND"
BluetoothStatusCodes.ERROR_UNKNOWN -> "ERROR_UNKNOWN"
BluetoothStatusCodes.FEATURE_NOT_CONFIGURED -> "FEATURE_NOT_CONFIGURED"
BluetoothStatusCodes.FEATURE_NOT_SUPPORTED -> "FEATURE_NOT_SUPPORTED"
BluetoothStatusCodes.FEATURE_SUPPORTED -> "FEATURE_SUPPORTED"
else -> "ErrorCode: $this"
}
}
fun Int.parseGattErrorCode(): String? {
return when (this) {
BluetoothGatt.GATT_SUCCESS -> null
BluetoothGatt.GATT_READ_NOT_PERMITTED -> "GATT_READ_NOT_PERMITTED"
BluetoothGatt.GATT_WRITE_NOT_PERMITTED -> "GATT_WRITE_NOT_PERMITTED"
BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION -> "GATT_INSUFFICIENT_AUTHENTICATION"
BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED -> "GATT_REQUEST_NOT_SUPPORTED"
BluetoothGatt.GATT_INVALID_OFFSET -> "GATT_INVALID_OFFSET"
BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION -> "GATT_INSUFFICIENT_AUTHORIZATION"
BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH -> "GATT_INVALID_ATTRIBUTE_LENGTH"
BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION -> "GATT_INSUFFICIENT_ENCRYPTION"
BluetoothGatt.GATT_CONNECTION_CONGESTED -> "GATT_CONNECTION_CONGESTED"
BluetoothGatt.GATT_FAILURE -> "GATT_FAILURE"
0x01 -> "GATT_INVALID_HANDLE"
0x04 -> "GATT_INVALID_PDU"
0x09 -> "GATT_PREPARE_QUEUE_FULL"
0x0a -> "GATT_ATTR_NOT_FOUND"
0x0b -> "GATT_ATTR_NOT_LONG"
0x0c -> "GATT_INSUFFICIENT_KEY_SIZE"
0x0e -> "GATT_UNLIKELY"
0x10 -> "GATT_UNSUPPORTED_GROUP"
0x11 -> "GATT_INSUFFICIENT_RESOURCES"
else -> "Unknown Error: $this"
}
}
val ScanResult.manufacturerDataList: List<UniversalManufacturerData>
get() {
return scanRecord?.manufacturerSpecificData?.toList()?.map { (key, value) ->
UniversalManufacturerData(key.toLong(), value)
} ?: emptyList()
}
fun <T> SparseArray<T>.toList(): List<Pair<Int, T>> {
return (0 until size()).map { index ->
keyAt(index) to valueAt(index)
}
}
fun BluetoothGatt.getCharacteristic(
service: String,
characteristic: String,
): BluetoothGattCharacteristic? {
return getService(UUID.fromString(service))?.getCharacteristic(UUID.fromString(characteristic))
}
fun subscriptionFailedError(error: String? = null): Result<Unit> {
return Result.failure(
FlutterError(
"Failed",
"Failed to update subscription state",
error
)
)
}
fun BluetoothDevice.removeBond() {
try {
javaClass.getMethod("removeBond").invoke(this)
} catch (e: Exception) {
Log.e(TAG, "Removing bond failed. ${e.message}")
}
}
fun BluetoothGattCharacteristic.getPropertiesList(): ArrayList<Long> {
val propertiesList = arrayListOf<Long>()
if (properties and BluetoothGattCharacteristic.PROPERTY_BROADCAST > 0) {
propertiesList.add(CharacteristicProperty.Broadcast.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_READ > 0) {
propertiesList.add(CharacteristicProperty.Read.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE > 0) {
propertiesList.add(CharacteristicProperty.WriteWithoutResponse.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE > 0) {
propertiesList.add(CharacteristicProperty.Write.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY > 0) {
propertiesList.add(CharacteristicProperty.Notify.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE > 0) {
propertiesList.add(CharacteristicProperty.Indicate.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE > 0) {
propertiesList.add(CharacteristicProperty.AuthenticatedSignedWrites.value)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS > 0) {
propertiesList.add(CharacteristicProperty.ExtendedProperties.value)
}
return propertiesList
}
fun Short.toByteArray(byteOrder: ByteOrder = ByteOrder.LITTLE_ENDIAN): ByteArray =
ByteBuffer.allocate(2 /*Short.SIZE_BYTES*/).order(byteOrder).putShort(this).array()
// Errors
fun unknownCharacteristicError(char: String) =
FlutterError("IllegalArgument", "Unknown error", null)
val DeviceDisconnectedError: FlutterError = FlutterError(
"DeviceDisconnected",
"Device Disconnected",
null
)
fun Int.parseHciErrorCode(): String? {
return when (this) {
BluetoothGatt.GATT_SUCCESS -> null
0x01 -> "Unknown HCI Command"
0x02 -> "Unknown Connection Identifier"
0x03 -> "Hardware Failure"
0x04 -> "Page Timeout"
0x05 -> "Authentication Failure"
0x06 -> "PIN or Key Missing"
0x07 -> "Memory Capacity Exceeded"
0x08 -> "Connection Timeout"
0x09 -> "Connection Limit Exceeded"
0x0A -> "Synchronous Connection Limit To A Device Exceeded"
0x0B -> "Connection Already Exists"
0x0C -> "Command Disallowed"
0x0D -> "Connection Rejected due to Limited Resources"
0x0E -> "Connection Rejected Due To Security Reasons"
0x0F -> "Connection Rejected due to Unacceptable BD_ADDR"
0x10 -> "Connection Accept Timeout Exceeded"
0x11 -> "Unsupported Feature or Parameter Value"
0x12 -> "Invalid HCI Command Parameters"
0x13 -> "Remote User Terminated Connection"
0x14 -> "Remote Device Terminated Connection due to Low Resources"
0x15 -> "Remote Device Terminated Connection due to Power Off"
0x16 -> "Connection Terminated By Local Host"
0x17 -> "Repeated Attempts"
0x18 -> "Pairing Not Allowed"
0x19 -> "Unknown LMP PDU"
0x1A -> "Unsupported Remote Feature / Unsupported LMP Feature"
0x1B -> "SCO Offset Rejected"
0x1C -> "SCO Interval Rejected"
0x1D -> "SCO Air Mode Rejected"
0x1E -> "Invalid LMP Parameters / Invalid LL Parameters"
0x1F -> "Unspecified Error"
0x20 -> "Unsupported LMP Parameter Value / Unsupported LL Parameter Value"
0x21 -> "Role Change Not Allowed"
0x22 -> "LMP Response Timeout / LL Response Timeout"
0x23 -> "LMP Error Transaction Collision / LL Procedure Collision"
0x24 -> "LMP PDU Not Allowed"
0x25 -> "Encryption Mode Not Acceptable"
0x26 -> "Link Key cannot be Changed"
0x27 -> "Requested QoS Not Supported"
0x28 -> "Instant Passed"
0x29 -> "Pairing With Unit Key Not Supported"
0x2A -> "Different Transaction Collision"
0x2B -> "Reserved for future use"
0x2C -> "QoS Unacceptable Parameter"
0x2D -> "QoS Rejected"
0x2E -> "Channel Classification Not Supported"
0x2F -> "Insufficient Security"
0x30 -> "Parameter Out Of Mandatory Range"
0x31 -> "Reserved for future use"
0x32 -> "Role Switch Pending"
0x33 -> "Reserved for future use"
0x34 -> "Reserved Slot Violation"
0x35 -> "Role Switch Failed"
0x36 -> "Extended Inquiry Response Too Large"
0x37 -> "Secure Simple Pairing Not Supported By Host"
0x38 -> "Host Busy - Pairing"
0x39 -> "Connection Rejected due to No Suitable Channel Found"
0x3A -> "Controller Busy"
0x3B -> "Unacceptable Connection Parameters"
0x3C -> "Advertising Timeout"
0x3D -> "Connection Terminated due to MIC Failure"
0x3E -> "Connection Failed to be Established / Synchronization Timeout"
0x3F -> "MAC Connection Failed"
0x40 -> "Coarse Clock Adjustment Rejected but Will Try to Adjust Using Clock Dragging"
0x41 -> "Type0 Submap Not Defined"
0x42 -> "Unknown Advertising Identifier"
0x43 -> "Limit Reached"
0x44 -> "Operation Cancelled by Host"
0x45 -> "Packet Too Long"
else -> "Unknown Error $this"
}
}
// Future result classes
class DiscoverServicesFuture(
val deviceId: String,
val result: (Result<List<UniversalBleService>>) -> Unit,
)
class MtuResultFuture(
val deviceId: String,
val result: (Result<Long>) -> Unit,
)
class ReadResultFuture(
val deviceId: String,
val characteristicId: String,
val serviceId: String,
val result: (Result<ByteArray>) -> Unit,
)
class WriteResultFuture(
val deviceId: String,
val characteristicId: String,
val serviceId: String,
val result: (Result<Unit>) -> Unit,
)
class SubscriptionResultFuture(
val deviceId: String,
val characteristicId: String,
val serviceId: String,
val result: (Result<Unit>) -> Unit,
)

View File

@@ -1,27 +0,0 @@
package com.navideck.universal_ble
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlin.test.Test
import org.mockito.Mockito
/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
internal class UniversalBlePluginTest {
@Test
fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
val plugin = UniversalBlePlugin()
val call = MethodCall("getPlatformVersion", null)
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
plugin.onMethodCall(call, mockResult)
Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
}
}

View File

@@ -1,38 +0,0 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/flutter_export_environment.sh

View File

@@ -1,729 +0,0 @@
// Autogenerated from Pigeon (v22.6.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
/// Error class for passing custom error details to Dart side.
final class PigeonError: Error {
let code: String
let message: String?
let details: Any?
init(code: String, message: String?, details: Any?) {
self.code = code
self.message = message
self.details = details
}
var localizedDescription: String {
return
"PigeonError(code: \(code), message: \(message ?? "<nil>"), details: \(details ?? "<nil>")"
}
}
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func createConnectionError(withChannelName channelName: String) -> PigeonError {
return PigeonError(code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "")
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalBleScanResult {
var deviceId: String
var name: String? = nil
var isPaired: Bool? = nil
var rssi: Int64? = nil
var manufacturerDataList: [UniversalManufacturerData]? = nil
var services: [String]? = nil
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> UniversalBleScanResult? {
let deviceId = pigeonVar_list[0] as! String
let name: String? = nilOrValue(pigeonVar_list[1])
let isPaired: Bool? = nilOrValue(pigeonVar_list[2])
let rssi: Int64? = nilOrValue(pigeonVar_list[3])
let manufacturerDataList: [UniversalManufacturerData]? = nilOrValue(pigeonVar_list[4])
let services: [String]? = nilOrValue(pigeonVar_list[5])
return UniversalBleScanResult(
deviceId: deviceId,
name: name,
isPaired: isPaired,
rssi: rssi,
manufacturerDataList: manufacturerDataList,
services: services
)
}
func toList() -> [Any?] {
return [
deviceId,
name,
isPaired,
rssi,
manufacturerDataList,
services,
]
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalBleService {
var uuid: String
var characteristics: [UniversalBleCharacteristic]? = nil
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> UniversalBleService? {
let uuid = pigeonVar_list[0] as! String
let characteristics: [UniversalBleCharacteristic]? = nilOrValue(pigeonVar_list[1])
return UniversalBleService(
uuid: uuid,
characteristics: characteristics
)
}
func toList() -> [Any?] {
return [
uuid,
characteristics,
]
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalBleCharacteristic {
var uuid: String
var properties: [Int64]
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> UniversalBleCharacteristic? {
let uuid = pigeonVar_list[0] as! String
let properties = pigeonVar_list[1] as! [Int64]
return UniversalBleCharacteristic(
uuid: uuid,
properties: properties
)
}
func toList() -> [Any?] {
return [
uuid,
properties,
]
}
}
/// Scan Filters
///
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalScanFilter {
var withServices: [String]
var withNamePrefix: [String]
var withManufacturerData: [UniversalManufacturerDataFilter]
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> UniversalScanFilter? {
let withServices = pigeonVar_list[0] as! [String]
let withNamePrefix = pigeonVar_list[1] as! [String]
let withManufacturerData = pigeonVar_list[2] as! [UniversalManufacturerDataFilter]
return UniversalScanFilter(
withServices: withServices,
withNamePrefix: withNamePrefix,
withManufacturerData: withManufacturerData
)
}
func toList() -> [Any?] {
return [
withServices,
withNamePrefix,
withManufacturerData,
]
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalManufacturerDataFilter {
var companyIdentifier: Int64
var data: FlutterStandardTypedData? = nil
var mask: FlutterStandardTypedData? = nil
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> UniversalManufacturerDataFilter? {
let companyIdentifier = pigeonVar_list[0] as! Int64
let data: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[1])
let mask: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[2])
return UniversalManufacturerDataFilter(
companyIdentifier: companyIdentifier,
data: data,
mask: mask
)
}
func toList() -> [Any?] {
return [
companyIdentifier,
data,
mask,
]
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalManufacturerData {
var companyIdentifier: Int64
var data: FlutterStandardTypedData
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> UniversalManufacturerData? {
let companyIdentifier = pigeonVar_list[0] as! Int64
let data = pigeonVar_list[1] as! FlutterStandardTypedData
return UniversalManufacturerData(
companyIdentifier: companyIdentifier,
data: data
)
}
func toList() -> [Any?] {
return [
companyIdentifier,
data,
]
}
}
private class UniversalBlePigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 129:
return UniversalBleScanResult.fromList(self.readValue() as! [Any?])
case 130:
return UniversalBleService.fromList(self.readValue() as! [Any?])
case 131:
return UniversalBleCharacteristic.fromList(self.readValue() as! [Any?])
case 132:
return UniversalScanFilter.fromList(self.readValue() as! [Any?])
case 133:
return UniversalManufacturerDataFilter.fromList(self.readValue() as! [Any?])
case 134:
return UniversalManufacturerData.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class UniversalBlePigeonCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? UniversalBleScanResult {
super.writeByte(129)
super.writeValue(value.toList())
} else if let value = value as? UniversalBleService {
super.writeByte(130)
super.writeValue(value.toList())
} else if let value = value as? UniversalBleCharacteristic {
super.writeByte(131)
super.writeValue(value.toList())
} else if let value = value as? UniversalScanFilter {
super.writeByte(132)
super.writeValue(value.toList())
} else if let value = value as? UniversalManufacturerDataFilter {
super.writeByte(133)
super.writeValue(value.toList())
} else if let value = value as? UniversalManufacturerData {
super.writeByte(134)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class UniversalBlePigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return UniversalBlePigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return UniversalBlePigeonCodecWriter(data: data)
}
}
class UniversalBlePigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = UniversalBlePigeonCodec(readerWriter: UniversalBlePigeonCodecReaderWriter())
}
/// Flutter -> Native
///
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol UniversalBlePlatformChannel {
func getBluetoothAvailabilityState(completion: @escaping (Result<Int64, Error>) -> Void)
func enableBluetooth(completion: @escaping (Result<Bool, Error>) -> Void)
func disableBluetooth(completion: @escaping (Result<Bool, Error>) -> Void)
func startScan(filter: UniversalScanFilter?) throws
func stopScan() throws
func connect(deviceId: String) throws
func disconnect(deviceId: String) throws
func setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Int64, completion: @escaping (Result<Void, Error>) -> Void)
func discoverServices(deviceId: String, completion: @escaping (Result<[UniversalBleService], Error>) -> Void)
func readValue(deviceId: String, service: String, characteristic: String, completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void)
func requestMtu(deviceId: String, expectedMtu: Int64, completion: @escaping (Result<Int64, Error>) -> Void)
func writeValue(deviceId: String, service: String, characteristic: String, value: FlutterStandardTypedData, bleOutputProperty: Int64, completion: @escaping (Result<Void, Error>) -> Void)
func isPaired(deviceId: String, completion: @escaping (Result<Bool, Error>) -> Void)
func pair(deviceId: String, completion: @escaping (Result<Bool, Error>) -> Void)
func unPair(deviceId: String) throws
func getSystemDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void)
func getConnectionState(deviceId: String) throws -> Int64
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class UniversalBlePlatformChannelSetup {
static var codec: FlutterStandardMessageCodec { UniversalBlePigeonCodec.shared }
/// Sets up an instance of `UniversalBlePlatformChannel` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: UniversalBlePlatformChannel?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let getBluetoothAvailabilityStateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getBluetoothAvailabilityStateChannel.setMessageHandler { _, reply in
api.getBluetoothAvailabilityState { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
getBluetoothAvailabilityStateChannel.setMessageHandler(nil)
}
let enableBluetoothChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
enableBluetoothChannel.setMessageHandler { _, reply in
api.enableBluetooth { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
enableBluetoothChannel.setMessageHandler(nil)
}
let disableBluetoothChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disableBluetooth\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
disableBluetoothChannel.setMessageHandler { _, reply in
api.disableBluetooth { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
disableBluetoothChannel.setMessageHandler(nil)
}
let startScanChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
startScanChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let filterArg: UniversalScanFilter? = nilOrValue(args[0])
do {
try api.startScan(filter: filterArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
startScanChannel.setMessageHandler(nil)
}
let stopScanChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
stopScanChannel.setMessageHandler { _, reply in
do {
try api.stopScan()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
stopScanChannel.setMessageHandler(nil)
}
let connectChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
connectChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
do {
try api.connect(deviceId: deviceIdArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
connectChannel.setMessageHandler(nil)
}
let disconnectChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
disconnectChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
do {
try api.disconnect(deviceId: deviceIdArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
disconnectChannel.setMessageHandler(nil)
}
let setNotifiableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
setNotifiableChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
let serviceArg = args[1] as! String
let characteristicArg = args[2] as! String
let bleInputPropertyArg = args[3] as! Int64
api.setNotifiable(deviceId: deviceIdArg, service: serviceArg, characteristic: characteristicArg, bleInputProperty: bleInputPropertyArg) { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
setNotifiableChannel.setMessageHandler(nil)
}
let discoverServicesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
discoverServicesChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
api.discoverServices(deviceId: deviceIdArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
discoverServicesChannel.setMessageHandler(nil)
}
let readValueChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
readValueChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
let serviceArg = args[1] as! String
let characteristicArg = args[2] as! String
api.readValue(deviceId: deviceIdArg, service: serviceArg, characteristic: characteristicArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
readValueChannel.setMessageHandler(nil)
}
let requestMtuChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
requestMtuChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
let expectedMtuArg = args[1] as! Int64
api.requestMtu(deviceId: deviceIdArg, expectedMtu: expectedMtuArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
requestMtuChannel.setMessageHandler(nil)
}
let writeValueChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
writeValueChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
let serviceArg = args[1] as! String
let characteristicArg = args[2] as! String
let valueArg = args[3] as! FlutterStandardTypedData
let bleOutputPropertyArg = args[4] as! Int64
api.writeValue(deviceId: deviceIdArg, service: serviceArg, characteristic: characteristicArg, value: valueArg, bleOutputProperty: bleOutputPropertyArg) { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
writeValueChannel.setMessageHandler(nil)
}
let isPairedChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
isPairedChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
api.isPaired(deviceId: deviceIdArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
isPairedChannel.setMessageHandler(nil)
}
let pairChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
pairChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
api.pair(deviceId: deviceIdArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
pairChannel.setMessageHandler(nil)
}
let unPairChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
unPairChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
do {
try api.unPair(deviceId: deviceIdArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
unPairChannel.setMessageHandler(nil)
}
let getSystemDevicesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getSystemDevicesChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let withServicesArg = args[0] as! [String]
api.getSystemDevices(withServices: withServicesArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
getSystemDevicesChannel.setMessageHandler(nil)
}
let getConnectionStateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectionState\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getConnectionStateChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
do {
let result = try api.getConnectionState(deviceId: deviceIdArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
getConnectionStateChannel.setMessageHandler(nil)
}
}
}
/// Native -> Flutter
///
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
protocol UniversalBleCallbackChannelProtocol {
func onAvailabilityChanged(state stateArg: Int64, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onPairStateChange(deviceId deviceIdArg: String, isPaired isPairedArg: Bool, error errorArg: String?, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onScanResult(result resultArg: UniversalBleScanResult, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onValueChanged(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, value valueArg: FlutterStandardTypedData, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onConnectionChanged(deviceId deviceIdArg: String, connected connectedArg: Bool, error errorArg: String?, completion: @escaping (Result<Void, PigeonError>) -> Void)
}
class UniversalBleCallbackChannel: UniversalBleCallbackChannelProtocol {
private let binaryMessenger: FlutterBinaryMessenger
private let messageChannelSuffix: String
init(binaryMessenger: FlutterBinaryMessenger, messageChannelSuffix: String = "") {
self.binaryMessenger = binaryMessenger
self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
}
var codec: UniversalBlePigeonCodec {
return UniversalBlePigeonCodec.shared
}
func onAvailabilityChanged(state stateArg: Int64, completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([stateArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(PigeonError(code: code, message: message, details: details)))
} else {
completion(.success(Void()))
}
}
}
func onPairStateChange(deviceId deviceIdArg: String, isPaired isPairedArg: Bool, error errorArg: String?, completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([deviceIdArg, isPairedArg, errorArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(PigeonError(code: code, message: message, details: details)))
} else {
completion(.success(Void()))
}
}
}
func onScanResult(result resultArg: UniversalBleScanResult, completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([resultArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(PigeonError(code: code, message: message, details: details)))
} else {
completion(.success(Void()))
}
}
}
func onValueChanged(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, value valueArg: FlutterStandardTypedData, completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([deviceIdArg, characteristicIdArg, valueArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(PigeonError(code: code, message: message, details: details)))
} else {
completion(.success(Void()))
}
}
}
func onConnectionChanged(deviceId deviceIdArg: String, connected connectedArg: Bool, error errorArg: String?, completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([deviceIdArg, connectedArg, errorArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(PigeonError(code: code, message: message, details: details)))
} else {
completion(.success(Void()))
}
}
}
}

View File

@@ -1,107 +0,0 @@
//
// UniversalBleFilterUtil.swift
// universal_ble
//
// Created by Rohit Sangwan on 23/08/24.
//
import CoreBluetooth
import Foundation
public class UniversalBleFilterUtil {
var scanFilter: UniversalScanFilter?
var scanFilterServicesUUID: [CBUUID] = []
func filterDevice(name: String?, manufacturerData: UniversalManufacturerData?, services: [CBUUID]?) -> Bool {
guard let filter = scanFilter else {
return true
}
let hasNamePrefixFilter = !filter.withNamePrefix.isEmpty
let hasServiceFilter = !filter.withServices.isEmpty
let hasManufacturerDataFilter = !filter.withManufacturerData.isEmpty
// If there is no filter at all, then allow device
if !hasNamePrefixFilter && !hasServiceFilter && !hasManufacturerDataFilter {
return true
}
// Else check one of the filter passes
return hasNamePrefixFilter && isNameMatchingFilters(filter: filter, name: name) ||
hasServiceFilter && isServicesMatchingFilters(services: services) ||
hasManufacturerDataFilter && isManufacturerDataMatchingFilters(scanFilter: filter, msd: manufacturerData)
}
func isNameMatchingFilters(filter: UniversalScanFilter, name: String?) -> Bool {
let prefixFilters = filter.withNamePrefix.compactMap { $0 }.filter { !$0.isEmpty }
guard !prefixFilters.isEmpty else {
return true
}
guard let name = name, !name.isEmpty else {
return false
}
return prefixFilters.contains { name.hasPrefix($0) }
}
func isServicesMatchingFilters(services: [CBUUID]?) -> Bool {
let serviceFilters = Set(scanFilterServicesUUID.compactMap { $0 })
guard !serviceFilters.isEmpty else {
return true
}
guard let services = services, !services.isEmpty else {
return false
}
return !Set(services).isDisjoint(with: serviceFilters)
}
func isManufacturerDataMatchingFilters(scanFilter: UniversalScanFilter, msd: UniversalManufacturerData?) -> Bool {
let filters = scanFilter.withManufacturerData.compactMap { $0 }
if filters.isEmpty {
return true
}
guard let msd = msd else {
return false
}
for filter in filters {
let companyIdentifier: Int64 = filter.companyIdentifier
if msd.companyIdentifier == companyIdentifier && findData(find: filter.data?.toData(), inData: msd.data.toData(), usingMask: filter.mask?.toData()) {
return true
}
}
return false
}
func findData(find: Data?, inData data: Data, usingMask mask: Data?) -> Bool {
if let find = find {
// If mask is null, use a default mask of all 1s
let mask = mask ?? Data(repeating: 0xFF, count: find.count)
// Ensure find & mask are same length
guard find.count == mask.count else {
return false
}
for i in 0 ..< find.count {
// Perform bitwise AND with mask and then compare
if (find[i] & mask[i]) != (data[i] & mask[i]) {
return false
}
}
}
return true
}
}
extension UniversalScanFilter {
var usesCustomFilters: Bool {
return !withManufacturerData.isEmpty || !withNamePrefix.isEmpty
}
}

View File

@@ -1,203 +0,0 @@
//
// UniversalBleHelper.swift
// universal_ble
//
// Created by Rohit Sangwan on 25/10/23.
//
import CoreBluetooth
import Foundation
#if os(iOS)
import Flutter
#elseif os(OSX)
import FlutterMacOS
#endif
enum BleInputProperty: Int {
case disabled = 0
case notification = 1
case indication = 2
}
enum BleOutputProperty: Int {
case withResponse = 0
case withoutResponse = 1
}
enum BlueConnectionState: Int64 {
case connected = 0
case disconnected = 1
case connecting = 2
case disconnecting = 3
}
enum AvailabilityState: Int64 {
case unknown = 0
case resetting = 1
case unsupported = 2
case unauthorized = 3
case poweredOff = 4
case poweredOn = 5
}
enum CharacteristicProperty: Int64 {
case broadcast = 0
case read = 1
case writeWithoutResponse = 2
case write = 3
case notify = 4
case indicate = 5
case authenticatedSignedWrites = 6
case extendedProperties = 7
}
extension CBCharacteristicProperties {
var toCharacteristicProperty: [Int64] {
var properties = [Int64]()
if contains(.broadcast) {
properties.append(CharacteristicProperty.broadcast.rawValue)
}
if contains(.read) {
properties.append(CharacteristicProperty.read.rawValue)
}
if contains(.writeWithoutResponse) {
properties.append(CharacteristicProperty.writeWithoutResponse.rawValue)
}
if contains(.write) {
properties.append(CharacteristicProperty.write.rawValue)
}
if contains(.notify) {
properties.append(CharacteristicProperty.notify.rawValue)
}
if contains(.indicate) {
properties.append(CharacteristicProperty.indicate.rawValue)
}
if contains(.authenticatedSignedWrites) {
properties.append(CharacteristicProperty.authenticatedSignedWrites.rawValue)
}
if contains(.extendedProperties) {
properties.append(CharacteristicProperty.extendedProperties.rawValue)
}
return properties
}
}
extension CBManagerState {
func toAvailabilityState() -> AvailabilityState {
switch self {
case .unknown:
return AvailabilityState.unknown
case .resetting:
return AvailabilityState.resetting
case .unsupported:
return AvailabilityState.unsupported
case .unauthorized:
return AvailabilityState.unauthorized
case .poweredOff:
return AvailabilityState.poweredOff
case .poweredOn:
return AvailabilityState.poweredOn
@unknown default:
return AvailabilityState.unknown
}
}
}
extension Error {
func toPigeonError() -> PigeonError {
let nsError = self as NSError
let errorCode: String = .init(nsError.code)
let errorDescription: String = nsError.localizedDescription
return PigeonError(code: errorCode, message: errorDescription, details: nil)
}
}
public extension CBUUID {
var uuidStr: String {
uuidString.lowercased()
}
}
public extension CBPeripheral {
// FIXME: https://forums.developer.apple.com/thread/84375
var uuid: UUID {
value(forKey: "identifier") as! NSUUID as UUID
}
func getCharacteristic(_ characteristic: String, of service: String) -> CBCharacteristic? {
let GSS_SUFFIX = "0000-1000-8000-00805f9b34fb"
let s = services?.first {
$0.uuid.uuidStr.lowercased() == service.lowercased() || service.lowercased() == "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)".lowercased()
}
let c = s?.characteristics?.first {
$0.uuid.uuidStr.lowercased() == characteristic.lowercased() || characteristic.lowercased() == "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)".lowercased()
}
return c
}
func setNotifiable(_ bleInputProperty: String, for characteristic: String, of service: String) {
guard let characteristic = getCharacteristic(characteristic, of: service) else {
return
}
setNotifyValue(bleInputProperty != "disabled", for: characteristic)
}
}
extension FlutterStandardTypedData {
func toData() -> Data {
return Data(data)
}
}
// Future classes
class CharacteristicReadFuture {
let deviceId: String
let characteristicId: String
let serviceId: String?
let result: (Result<FlutterStandardTypedData, Error>) -> Void
init(deviceId: String, characteristicId: String, serviceId: String?, result: @escaping (Result<FlutterStandardTypedData, Error>) -> Void) {
self.deviceId = deviceId
self.characteristicId = characteristicId
self.serviceId = serviceId
self.result = result
}
}
class CharacteristicWriteFuture {
let deviceId: String
let characteristicId: String
let serviceId: String?
let result: (Result<Void, Error>) -> Void
init(deviceId: String, characteristicId: String, serviceId: String?, result: @escaping (Result<Void, Error>) -> Void) {
self.deviceId = deviceId
self.characteristicId = characteristicId
self.serviceId = serviceId
self.result = result
}
}
class CharacteristicNotifyFuture {
let deviceId: String
let characteristicId: String
let serviceId: String?
let result: (Result<Void, Error>) -> Void
init(deviceId: String, characteristicId: String, serviceId: String?, result: @escaping (Result<Void, Error>) -> Void) {
self.deviceId = deviceId
self.characteristicId = characteristicId
self.serviceId = serviceId
self.result = result
}
}
class DiscoverServicesFuture {
let deviceId: String
let result: (Result<[UniversalBleService], Error>) -> Void
init(deviceId: String, result: @escaping (Result<[UniversalBleService], Error>) -> Void) {
self.deviceId = deviceId
self.result = result
}
}

View File

@@ -1,501 +0,0 @@
import CoreBluetooth
#if os(iOS)
import Flutter
import UIKit
#elseif os(OSX)
import Cocoa
import FlutterMacOS
#endif
public class UniversalBlePlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
var messenger: FlutterBinaryMessenger
#if os(iOS)
messenger = registrar.messenger()
#elseif os(macOS)
messenger = registrar.messenger
#endif
let callbackChannel = UniversalBleCallbackChannel(binaryMessenger: messenger)
let api = BleCentralDarwin(callbackChannel: callbackChannel)
UniversalBlePlatformChannelSetup.setUp(binaryMessenger: messenger, api: api)
}
}
private var discoveredPeripherals = [String: CBPeripheral]()
private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentralManagerDelegate, CBPeripheralDelegate {
var callbackChannel: UniversalBleCallbackChannel
private var universalBleFilterUtil = UniversalBleFilterUtil()
private lazy var manager: CBCentralManager = .init(delegate: self, queue: nil)
private var availabilityStateUpdateHandlers: [(Result<Int64, Error>) -> Void] = []
private var discoveredServicesProgressMap: [String: [UniversalBleService]] = [:]
private var characteristicReadFutures = [CharacteristicReadFuture]()
private var characteristicWriteFutures = [CharacteristicWriteFuture]()
private var characteristicNotifyFutures = [CharacteristicNotifyFuture]()
private var discoverServicesFutures = [DiscoverServicesFuture]()
init(callbackChannel: UniversalBleCallbackChannel) {
self.callbackChannel = callbackChannel
super.init()
}
func getBluetoothAvailabilityState(completion: @escaping (Result<Int64, Error>) -> Void) {
if manager.state != .unknown {
completion(.success(manager.state.toAvailabilityState().rawValue))
} else {
availabilityStateUpdateHandlers.append(completion)
_ = manager
}
}
func enableBluetooth(completion: @escaping (Result<Bool, Error>) -> Void) {
completion(Result.failure(PigeonError(code: "NotSupported", message: nil, details: nil)))
}
func disableBluetooth(completion: @escaping (Result<Bool, any Error>) -> Void) {
completion(Result.failure(PigeonError(code: "NotSupported", message: nil, details: nil)))
}
func startScan(filter: UniversalScanFilter?) throws {
// If filter has any other filter other than official one
let usesCustomFilters = filter?.usesCustomFilters ?? false
// Apply services filter
var withServices: [CBUUID] = try filter?.withServices.compactMap { $0 }.toCBUUID() ?? []
if usesCustomFilters {
print("Using Custom Filters")
universalBleFilterUtil.scanFilter = filter
universalBleFilterUtil.scanFilterServicesUUID = withServices
withServices = []
} else {
universalBleFilterUtil.scanFilter = nil
universalBleFilterUtil.scanFilterServicesUUID = []
}
let options = [CBCentralManagerScanOptionAllowDuplicatesKey: true]
manager.scanForPeripherals(withServices: withServices, options: options)
}
func stopScan() throws {
manager.stopScan()
}
func connect(deviceId: String) throws {
let peripheral = try deviceId.getPeripheral(manager: manager)
peripheral.delegate = self
manager.connect(peripheral)
}
func disconnect(deviceId: String) throws {
let peripheral = try deviceId.getPeripheral(manager: manager)
if peripheral.state != CBPeripheralState.disconnected {
manager.cancelPeripheralConnection(peripheral)
}
cleanUpConnection(deviceId: deviceId)
}
func getConnectionState(deviceId: String) throws -> Int64 {
let peripheral = try deviceId.getPeripheral(manager: manager)
switch peripheral.state {
case .connecting:
return BlueConnectionState.connecting.rawValue
case .connected:
return BlueConnectionState.connected.rawValue
case .disconnecting:
return BlueConnectionState.disconnecting.rawValue
case .disconnected:
return BlueConnectionState.disconnected.rawValue
@unknown default:
fatalError()
}
}
func cleanUpConnection(deviceId: String) {
characteristicReadFutures.removeAll { future in
if future.deviceId == deviceId {
future.result(
Result.failure(PigeonError(code: "DeviceDisconnected", message: "Device Disconnected", details: nil))
)
return true
}
return false
}
characteristicWriteFutures.removeAll { future in
if future.deviceId == deviceId {
future.result(
Result.failure(PigeonError(code: "DeviceDisconnected", message: "Device Disconnected", details: nil))
)
return true
}
return false
}
characteristicNotifyFutures.removeAll { future in
if future.deviceId == deviceId {
future.result(
Result.failure(PigeonError(code: "DeviceDisconnected", message: "Device Disconnected", details: nil))
)
return true
}
return false
}
discoverServicesFutures.removeAll { future in
if future.deviceId == deviceId {
future.result(
Result.failure(PigeonError(code: "DeviceDisconnected", message: "Device Disconnected", details: nil))
)
return true
}
return false
}
discoveredServicesProgressMap[deviceId] = nil
}
func discoverServices(deviceId: String, completion: @escaping (Result<[UniversalBleService], Error>) -> Void) {
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
completion(
Result.failure(PigeonError(code: "IllegalArgument", message: "Unknown deviceId:\(self)", details: nil))
)
return
}
if discoveredServicesProgressMap[deviceId] != nil {
print("Services discovery already in progress for :\(deviceId), waiting for completion.")
discoverServicesFutures.append(DiscoverServicesFuture(deviceId: deviceId, result: completion))
return
}
if let cachedServices = peripheral.services {
// If services already discovered no need to discover again
if !cachedServices.isEmpty {
// print("Services already cached for this peripheral")
discoverServicesFutures.append(DiscoverServicesFuture(deviceId: deviceId, result: completion))
self.peripheral(peripheral, didDiscoverServices: nil)
return
}
}
peripheral.discoverServices(nil)
discoverServicesFutures.append(DiscoverServicesFuture(deviceId: deviceId, result: completion))
}
private func onServicesDiscovered(deviceId: String, services: [UniversalBleService]) {
discoverServicesFutures.removeAll { future in
if future.deviceId == deviceId {
future.result(Result.success(services))
return true
}
return false
}
}
func setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Int64, completion: @escaping (Result<Void, any Error>) -> Void) {
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
completion(Result.failure(PigeonError(code: "IllegalArgument", message: "Unknown deviceId:\(self)", details: nil)))
return
}
guard let gattCharacteristic = peripheral.getCharacteristic(characteristic, of: service) else {
completion(Result.failure(PigeonError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil)))
return
}
if bleInputProperty == BleInputProperty.notification.rawValue && !gattCharacteristic.properties.contains(.notify) {
completion(Result.failure(PigeonError(code: "InvalidAction", message: "Characteristic does not support notify", details: nil)))
return
}
if bleInputProperty == BleInputProperty.indication.rawValue && !gattCharacteristic.properties.contains(.indicate) {
/*completion(Result.failure(PigeonError(code: "InvalidAction", message: "Characteristic does not support indicate", details: nil)))
return*/
}
let shouldNotify = bleInputProperty != BleInputProperty.disabled.rawValue
peripheral.setNotifyValue(shouldNotify, for: gattCharacteristic)
characteristicNotifyFutures.append(CharacteristicNotifyFuture(deviceId: deviceId, characteristicId: gattCharacteristic.uuid.uuidStr, serviceId: gattCharacteristic.service?.uuid.uuidStr, result: completion))
}
func readValue(deviceId: String, service: String, characteristic: String, completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void) {
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
completion(Result.failure(PigeonError(code: "IllegalArgument", message: "Unknown deviceId:\(self)", details: nil)))
return
}
guard let gattCharacteristic = peripheral.getCharacteristic(characteristic, of: service) else {
completion(Result.failure(PigeonError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil)))
return
}
if !gattCharacteristic.properties.contains(.read) {
completion(Result.failure(PigeonError(code: "InvalidAction", message: "Characteristic does not support read", details: nil)))
return
}
peripheral.readValue(for: gattCharacteristic)
characteristicReadFutures.append(CharacteristicReadFuture(deviceId: deviceId, characteristicId: gattCharacteristic.uuid.uuidStr, serviceId: gattCharacteristic.service?.uuid.uuidStr, result: completion))
}
func writeValue(deviceId: String, service: String, characteristic: String, value: FlutterStandardTypedData, bleOutputProperty: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
completion(Result.failure(PigeonError(code: "IllegalArgument", message: "Unknown deviceId:\(self)", details: nil)))
return
}
guard let gattCharacteristic = peripheral.getCharacteristic(characteristic, of: service) else {
completion(Result.failure(PigeonError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil)))
return
}
let type = bleOutputProperty == BleOutputProperty.withoutResponse.rawValue ? CBCharacteristicWriteType.withoutResponse : CBCharacteristicWriteType.withResponse
if type == CBCharacteristicWriteType.withResponse {
if !gattCharacteristic.properties.contains(.write) {
completion(Result.failure(PigeonError(code: "InvalidAction", message: "Characteristic does not support write withResponse", details: nil)))
return
}
} else if type == CBCharacteristicWriteType.withoutResponse {
if !gattCharacteristic.properties.contains(.writeWithoutResponse) {
completion(Result.failure(PigeonError(code: "InvalidAction", message: "Characteristic does not support write withoutResponse", details: nil)))
return
}
}
peripheral.writeValue(value.data, for: gattCharacteristic, type: type)
if type == CBCharacteristicWriteType.withResponse {
// Wait for future response
characteristicWriteFutures.append(CharacteristicWriteFuture(deviceId: deviceId, characteristicId: gattCharacteristic.uuid.uuidStr, serviceId: gattCharacteristic.service?.uuid.uuidStr, result: completion))
} else {
completion(Result.success({}()))
}
}
func requestMtu(deviceId: String, expectedMtu _: Int64, completion: @escaping (Result<Int64, Error>) -> Void) {
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
completion(Result.failure(PigeonError(code: "IllegalArgument", message: "Unknown deviceId:\(self)", details: nil)))
return
}
let mtu = peripheral.maximumWriteValueLength(for: CBCharacteristicWriteType.withoutResponse)
let GATT_HEADER_LENGTH = 3
let mtuResult = Int64(mtu + GATT_HEADER_LENGTH)
completion(Result.success(mtuResult))
}
func isPaired(deviceId _: String, completion: @escaping (Result<Bool, Error>) -> Void) {
completion(Result.failure(PigeonError(code: "NotSupported", message: nil, details: nil)))
}
func pair(deviceId _: String, completion: @escaping (Result<Bool, Error>) -> Void) {
completion(Result.failure(PigeonError(code: "Implemented in Dart", message: nil, details: nil)))
}
func unPair(deviceId _: String) throws {
throw PigeonError(code: "NotSupported", message: nil, details: nil)
}
func getSystemDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void) {
var servicesFilter = withServices
if servicesFilter.isEmpty {
print("No services filter was set for getting system connected devices. Using default services...")
// Add several generic services
servicesFilter = ["1800", "1801", "180A", "180D", "1810", "181B", "1808", "181D", "1816", "1814", "181A", "1802", "1803", "1804", "1815", "1805", "1807", "1806", "1848", "185E", "180F", "1812", "180E", "1813"]
}
var filterCBUUID = servicesFilter.map { CBUUID(string: $0) }
let bleDevices = manager.retrieveConnectedPeripherals(withServices: filterCBUUID)
bleDevices.forEach { $0.saveCache() }
completion(Result.success(bleDevices.map {
UniversalBleScanResult(
deviceId: $0.uuid.uuidString,
name: $0.name ?? ""
)
}))
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
let state = central.state.toAvailabilityState().rawValue
callbackChannel.onAvailabilityChanged(state: state) { _ in }
// Complete Pending state handler
availabilityStateUpdateHandlers.removeAll { handler in
handler(.success(state))
return true
}
}
public func centralManager(_: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
// Store the discovered peripheral using its UUID as the key
peripheral.saveCache()
// Extract manufacturer data and service UUIDs from the advertisement data
let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
let services = (advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID])
var manufacturerDataList: [UniversalManufacturerData] = []
var universalManufacturerData: UniversalManufacturerData? = nil
if let msd = manufacturerData, msd.count > 2 {
let companyIdentifier = msd.prefix(2).withUnsafeBytes { $0.load(as: UInt16.self) }
let data = FlutterStandardTypedData(bytes: msd.suffix(from: 2))
universalManufacturerData = UniversalManufacturerData(companyIdentifier: Int64(companyIdentifier), data: data)
manufacturerDataList.append(universalManufacturerData!)
}
// Apply custom filters and return early if the peripheral doesn't match
if !universalBleFilterUtil.filterDevice(name: peripheral.name, manufacturerData: universalManufacturerData, services: services) {
return
}
callbackChannel.onScanResult(result: UniversalBleScanResult(
deviceId: peripheral.uuid.uuidString,
name: peripheral.name,
isPaired: nil,
rssi: RSSI as? Int64,
manufacturerDataList: manufacturerDataList,
services: services?.map { $0.uuidStr }
)) { _ in }
}
public func centralManager(_: CBCentralManager, didConnect peripheral: CBPeripheral) {
callbackChannel.onConnectionChanged(deviceId: peripheral.uuid.uuidString, connected: true, error: nil) { _ in }
}
public func centralManager(_: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error _: Error?) {
callbackChannel.onConnectionChanged(deviceId: peripheral.uuid.uuidString, connected: false, error: nil) { _ in }
cleanUpConnection(deviceId: peripheral.uuid.uuidString)
}
public func centralManager(_: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
callbackChannel.onConnectionChanged(deviceId: peripheral.uuid.uuidString, connected: false, error: error?.localizedDescription) { _ in }
cleanUpConnection(deviceId: peripheral.uuid.uuidString)
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices _: Error?) {
let deviceId = peripheral.identifier.uuidString
guard let services = peripheral.services else {
onServicesDiscovered(deviceId: deviceId, services: [])
return
}
discoveredServicesProgressMap[deviceId] = services.map { UniversalBleService(uuid: $0.uuid.uuidString, characteristics: nil) }
for service in services {
if let cachedChar = service.characteristics {
if !cachedChar.isEmpty {
self.peripheral(peripheral, didDiscoverCharacteristicsFor: service, error: nil)
continue
}
}
peripheral.discoverCharacteristics(nil, for: service)
}
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error _: Error?) {
let deviceId = peripheral.identifier.uuidString
var universalBleCharacteristicsList: [UniversalBleCharacteristic] = []
for characteristic in service.characteristics ?? [] {
universalBleCharacteristicsList.append(
UniversalBleCharacteristic(uuid: characteristic.uuid.uuidString, properties: characteristic.properties.toCharacteristicProperty))
}
// Update discoveredServicesProgressMap
if let index = discoveredServicesProgressMap[deviceId]?.firstIndex(where: { $0.uuid == service.uuid.uuidString }) {
discoveredServicesProgressMap[deviceId]?[index] = UniversalBleService(uuid: service.uuid.uuidString, characteristics: universalBleCharacteristicsList)
}
// Check if all services and their characteristics have been discovered
if discoveredServicesProgressMap[deviceId]?.allSatisfy({ $0.characteristics != nil }) ?? false {
onServicesDiscovered(deviceId: deviceId, services: discoveredServicesProgressMap[deviceId] ?? [])
discoveredServicesProgressMap[deviceId] = nil
}
}
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
characteristicWriteFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr {
if let pigeonError = error?.toPigeonError() {
future.result(Result.failure(pigeonError))
} else {
future.result(Result.success({}()))
}
return true
}
return false
}
}
public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
characteristicNotifyFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr {
if let pigeonError = error?.toPigeonError() {
future.result(Result.failure(pigeonError))
} else {
future.result(Result.success({}()))
}
return true
}
return false
}
}
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
// Update callbackChannel if notifying
if characteristic.isNotifying {
if let characteristicValue = characteristic.value {
callbackChannel.onValueChanged(deviceId: peripheral.uuid.uuidString, characteristicId: characteristic.uuid.uuidStr, value: FlutterStandardTypedData(bytes: characteristicValue)) { _ in }
}
}
if characteristicReadFutures.count == 0 {
return
}
// Update futures for readValue
characteristicReadFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr {
if let pigeonError = error?.toPigeonError() {
future.result(Result.failure(pigeonError))
} else {
if let characteristicValue = characteristic.value {
future.result(Result.success(FlutterStandardTypedData(bytes: characteristicValue)))
} else {
future.result(Result.failure(PigeonError(code: "ReadFailed", message: "No value", details: nil)))
}
}
return true
}
return false
}
}
}
extension CBPeripheral {
func saveCache(){
discoveredPeripherals[self.uuid.uuidString] = self
}
}
extension String {
func getPeripheral(manager: CBCentralManager) throws -> CBPeripheral {
guard let peripheral = findPeripheral(manager: manager) else {
throw PigeonError(code: "IllegalArgument", message: "Unknown deviceId:\(self)", details: nil)
}
return peripheral
}
func findPeripheral(manager: CBCentralManager) -> CBPeripheral? {
if let peripheral = discoveredPeripherals[self] {
return peripheral
}
if let uuid = UUID(uuidString: self) {
let peripherals = manager.retrievePeripherals(withIdentifiers: [uuid])
if let peripheral = peripherals.first {
return peripheral
}
}
return nil
}
}
extension [String] {
func toCBUUID() throws -> [CBUUID] {
return try compactMap { serviceUUID in
guard UUID(uuidString: serviceUUID) != nil else {
throw PigeonError(code: "IllegalArgument", message: "Invalid service UUID:\(serviceUUID)", details: nil)
}
return CBUUID(string: serviceUUID)
}
}
}

View File

@@ -1,25 +0,0 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint universal_ble.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'universal_ble'
s.version = '0.0.1'
s.summary = 'A new Flutter plugin project.'
s.description = <<-DESC
A new Flutter plugin project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.ios.dependency 'Flutter'
s.osx.dependency 'FlutterMacOS'
s.ios.deployment_target = '9.0'
s.osx.deployment_target = '10.12'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
end

View File

@@ -1,47 +0,0 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
.metadata

View File

@@ -1,3 +0,0 @@
# universal_ble_example
Demonstrates how to use the universal_ble plugin.

View File

@@ -1,28 +0,0 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -1,13 +0,0 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@@ -1,65 +0,0 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
android {
namespace "com.navideck.universal_ble_example"
compileSdkVersion 34
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
applicationId "com.navideck.universal_ble_example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 21
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {}

View File

@@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -1,38 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" />
<application
android:label="universal_ble_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@@ -1,6 +0,0 @@
package com.navideck.universal_ble_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -1,18 +0,0 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@@ -1,3 +0,0 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip

View File

@@ -1,25 +0,0 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}
include ":app"

View File

@@ -1,34 +0,0 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

View File

@@ -1,2 +0,0 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@@ -1,2 +0,0 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

View File

@@ -1,44 +0,0 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View File

@@ -1,41 +0,0 @@
PODS:
- device_info_plus (0.0.1):
- Flutter
- Flutter (1.0.0)
- integration_test (0.0.1):
- Flutter
- permission_handler_apple (9.1.1):
- Flutter
- universal_ble (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- universal_ble (from `.symlinks/plugins/universal_ble/darwin`)
EXTERNAL SOURCES:
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
Flutter:
:path: Flutter
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
universal_ble:
:path: ".symlinks/plugins/universal_ble/darwin"
SPEC CHECKSUMS:
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
integration_test: 13825b8a9334a850581300559b8839134b124670
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
COCOAPODS: 1.15.2

View File

@@ -1,724 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3C020DDC565FF8D37F3235C5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E92536821956EA00892DD639 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
B2742FE9E6C1C51A78D26865 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AB88605F389EEED340E5872 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0D9A3CA727A1375BDCCC70CF /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
30B9BF32CD25D1ECB8BB5DCA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
411AF23EC127806AE083685B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
4414252E2351EBDB7761F83A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
6CF659084054FE2807275A3A /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9AB88605F389EEED340E5872 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A11EED44BE0324AD23055698 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
E92536821956EA00892DD639 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8EE4584064C335A1F66BB6D9 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B2742FE9E6C1C51A78D26865 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3C020DDC565FF8D37F3235C5 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
68033A1F47BF85C19A1ED513 /* Frameworks */ = {
isa = PBXGroup;
children = (
E92536821956EA00892DD639 /* Pods_Runner.framework */,
9AB88605F389EEED340E5872 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
FAF8A3758B0770B81319B3AE /* Pods */,
68033A1F47BF85C19A1ED513 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
FAF8A3758B0770B81319B3AE /* Pods */ = {
isa = PBXGroup;
children = (
30B9BF32CD25D1ECB8BB5DCA /* Pods-Runner.debug.xcconfig */,
4414252E2351EBDB7761F83A /* Pods-Runner.release.xcconfig */,
A11EED44BE0324AD23055698 /* Pods-Runner.profile.xcconfig */,
411AF23EC127806AE083685B /* Pods-RunnerTests.debug.xcconfig */,
0D9A3CA727A1375BDCCC70CF /* Pods-RunnerTests.release.xcconfig */,
6CF659084054FE2807275A3A /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
7E79F47357B64513A488AC3E /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
8EE4584064C335A1F66BB6D9 /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
3CE2E408302E9BF272A3BE07 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
2F5EDC3CC74E7431E4CB95DF /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
2F5EDC3CC74E7431E4CB95DF /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
3CE2E408302E9BF272A3BE07 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
7E79F47357B64513A488AC3E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = QQAZR43YM7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 411AF23EC127806AE083685B /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0D9A3CA727A1375BDCCC70CF /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 6CF659084054FE2807275A3A /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = QQAZR43YM7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = QQAZR43YM7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalBleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -1,98 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -1,13 +0,0 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -1,122 +0,0 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

View File

@@ -1,5 +0,0 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Universal BLE</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>universal_ble_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Discover Bluetooth devices</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Discover Bluetooth devices</string>
</dict>
</plist>

View File

@@ -1 +0,0 @@
#import "GeneratedPluginRegistrant.h"

View File

@@ -1,26 +0,0 @@
import Flutter
import UIKit
import XCTest
@testable import universal_ble
// This demonstrates a simple unit test of the Swift portion of this plugin's implementation.
//
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
class RunnerTests: XCTestCase {
func testGetPlatformVersion() {
let plugin = UniversalBlePlugin()
let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: [])
let resultExpectation = expectation(description: "result block must be called.")
plugin.handle(call) { result in
XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion)
resultExpectation.fulfill()
}
waitForExpectations(timeout: 1)
}
}

View File

@@ -1,124 +0,0 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:universal_ble/universal_ble.dart';
/// Mock implementation of [UniversalBlePlatform] for testing
class MockUniversalBle extends UniversalBlePlatform {
final _mockBleDevice = BleDevice(
name: 'MockDevice',
deviceId: 'MockDeviceId',
rssi: 50,
manufacturerDataList: [],
);
Uint8List _serviceValue = utf8.encode('Result');
final BleService _mockService = BleService('180', [
BleCharacteristic('180A', [
CharacteristicProperty.read,
CharacteristicProperty.write,
CharacteristicProperty.notify,
]),
]);
@override
Future<void> startScan({
ScanFilter? scanFilter,
PlatformConfig? platformConfig,
}) async =>
updateScanResult(_mockBleDevice);
@override
Future<void> stopScan() async {}
@override
Future<void> connect(String deviceId, {Duration? connectionTimeout}) async {
updateConnection(deviceId, true);
}
@override
Future<void> disconnect(String deviceId) async {
updateConnection(deviceId, false);
}
@override
Future<List<BleService>> discoverServices(String deviceId) async {
return [_mockService];
}
@override
Future<bool> enableBluetooth() async {
await Future.delayed(const Duration(milliseconds: 500));
return true;
}
@override
Future<AvailabilityState> getBluetoothAvailabilityState() async {
return AvailabilityState.poweredOn;
}
@override
Future<List<BleDevice>> getSystemDevices(List<String>? withServices) async {
return [];
}
@override
Future<Uint8List> readValue(
String deviceId,
String service,
String characteristic, {
final Duration? timeout,
}) async {
await Future.delayed(const Duration(milliseconds: 500));
return _serviceValue;
}
@override
Future<void> writeValue(
String deviceId,
String service,
String characteristic,
Uint8List value,
BleOutputProperty bleOutputProperty) async {
await Future.delayed(const Duration(milliseconds: 500));
_serviceValue = value;
}
@override
Future<int> requestMtu(String deviceId, int expectedMtu) async {
await Future.delayed(const Duration(seconds: 1));
return 512;
}
@override
Future<void> setNotifiable(String deviceId, String service,
String characteristic, BleInputProperty bleInputProperty) async {}
@override
Future<bool> isPaired(String deviceId) async {
await Future.delayed(const Duration(milliseconds: 500));
return true;
}
@override
Future<bool> pair(String deviceId) async {
updatePairingState(deviceId, true);
return true;
}
@override
Future<void> unpair(String deviceId) async {
updatePairingState(deviceId, false);
}
@override
Future<BleConnectionState> getConnectionState(String deviceId) {
throw UnimplementedError();
}
@override
Future<bool> disableBluetooth() {
throw UnimplementedError();
}
}

View File

@@ -1,101 +0,0 @@
// ignore_for_file: avoid_print
import 'dart:async';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:permission_handler/permission_handler.dart';
/*
Required Permissions :
<----------->
IOS :
- Bluetooth
<----------->
Android :
if AndroidVersions < 12
- Location
- Bluetooth
else
- Bluetooth Scan
- Bluetooth Connect
<----------->
Macos :
<----------->
Windows : None
<----------->
Linux : None
<----------->
Web :
Check if Browser Supports Bluetooth
*/
class PermissionHandler {
static Future<bool> arePermissionsGranted() async {
if (!isMobilePlatform) return true;
var status = await _permissionStatus;
bool blePermissionGranted = status[0];
bool locationPermissionGranted = status[1];
if (locationPermissionGranted && blePermissionGranted) return true;
if (!blePermissionGranted) {
PermissionStatus blePermissionCheck =
await Permission.bluetooth.request();
if (blePermissionCheck.isPermanentlyDenied) {
print("Bluetooth Permission Permanently Denied");
openAppSettings();
}
return false;
}
if (!locationPermissionGranted) {
PermissionStatus locationPermissionCheck =
await Permission.location.request();
if (locationPermissionCheck.isPermanentlyDenied) {
print("Location Permission Permanently Denied");
openAppSettings();
}
return false;
}
return false;
}
static Future<List<bool>> get _permissionStatus async {
bool blePermissionGranted = false;
bool locationPermissionGranted = false;
if (await requiresExplicitAndroidBluetoothPermissions) {
bool bleConnectPermission =
(await Permission.bluetoothConnect.request()).isGranted;
bool bleScanPermission =
(await Permission.bluetoothScan.request()).isGranted;
blePermissionGranted = bleConnectPermission && bleScanPermission;
locationPermissionGranted = true;
} else {
PermissionStatus permissionStatus = await Permission.bluetooth.request();
blePermissionGranted = permissionStatus.isGranted;
locationPermissionGranted = await requiresLocationPermission
? (await Permission.locationWhenInUse.request()).isGranted
: true;
}
return [blePermissionGranted, locationPermissionGranted];
}
static bool get isMobilePlatform =>
!kIsWeb && (Platform.isAndroid || Platform.isIOS);
static Future<bool> get requiresLocationPermission async =>
!kIsWeb &&
Platform.isAndroid &&
(!await requiresExplicitAndroidBluetoothPermissions);
static Future<bool> get requiresExplicitAndroidBluetoothPermissions async {
if (kIsWeb || !Platform.isAndroid) return false;
AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo;
return androidInfo.version.sdkInt >= 31;
}
}

View File

@@ -1,282 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:universal_ble/universal_ble.dart';
import 'package:universal_ble_example/data/mock_universal_ble.dart';
import 'package:universal_ble_example/home/widgets/scan_filter_widget.dart';
import 'package:universal_ble_example/home/widgets/scanned_devices_placeholder_widget.dart';
import 'package:universal_ble_example/home/widgets/scanned_item_widget.dart';
import 'package:universal_ble_example/data/permission_handler.dart';
import 'package:universal_ble_example/peripheral_details/peripheral_detail_page.dart';
import 'package:universal_ble_example/widgets/platform_button.dart';
import 'package:universal_ble_example/widgets/responsive_buttons_grid.dart';
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _bleDevices = <BleDevice>[];
bool _isScanning = false;
QueueType _queueType = QueueType.global;
TextEditingController servicesFilterController = TextEditingController();
TextEditingController namePrefixController = TextEditingController();
TextEditingController manufacturerDataController = TextEditingController();
AvailabilityState? bleAvailabilityState;
ScanFilter? scanFilter;
@override
void initState() {
super.initState();
/// Set mock instance for testing
if (const bool.fromEnvironment('MOCK')) {
UniversalBle.setInstance(MockUniversalBle());
}
/// Setup queue and timeout
UniversalBle.queueType = _queueType;
UniversalBle.timeout = const Duration(seconds: 10);
UniversalBle.onAvailabilityChange = (state) {
setState(() {
bleAvailabilityState = state;
});
};
UniversalBle.onScanResult = (result) {
// log(result.toString());
int index = _bleDevices.indexWhere((e) => e.deviceId == result.deviceId);
if (index == -1) {
_bleDevices.add(result);
} else {
if (result.name == null && _bleDevices[index].name != null) {
result.name = _bleDevices[index].name;
}
_bleDevices[index] = result;
}
setState(() {});
};
// UniversalBle.onQueueUpdate = (String id, int remainingItems) {
// debugPrint("Queue: $id RemainingItems: $remainingItems");
// };
}
Future<void> startScan() async {
await UniversalBle.startScan(
scanFilter: scanFilter,
);
}
Future<void> _getSystemDevices() async {
// For macOS and iOS, it is recommended to set a filter to get system devices
if (defaultTargetPlatform == TargetPlatform.macOS ||
defaultTargetPlatform == TargetPlatform.iOS &&
(scanFilter?.withServices ?? []).isEmpty) {
showSnackbar(
"No services filter was set for getting system connected devices. Using default services...");
}
List<BleDevice> devices = await UniversalBle.getSystemDevices(
withServices: scanFilter?.withServices,
);
if (devices.isEmpty) {
showSnackbar("No System Connected Devices Found");
}
setState(() {
_bleDevices.clear();
_bleDevices.addAll(devices);
});
}
void _showScanFilterBottomSheet() {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) {
return ScanFilterWidget(
servicesFilterController: servicesFilterController,
namePrefixController: namePrefixController,
manufacturerDataController: manufacturerDataController,
onScanFilter: (ScanFilter? filter) {
setState(() {
scanFilter = filter;
});
},
);
},
);
}
void showSnackbar(message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message.toString())),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Universal BLE'),
elevation: 4,
actions: [
if (_isScanning)
const Padding(
padding: EdgeInsets.all(8.0),
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
)),
),
],
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: ResponsiveButtonsGrid(
children: [
PlatformButton(
text: 'Start Scan',
onPressed: () async {
setState(() {
_bleDevices.clear();
_isScanning = true;
});
try {
await startScan();
} catch (e) {
setState(() {
_isScanning = false;
});
showSnackbar(e);
}
},
),
PlatformButton(
text: 'Stop Scan',
onPressed: () async {
await UniversalBle.stopScan();
setState(() {
_isScanning = false;
});
},
),
if (BleCapabilities.supportsBluetoothEnableApi)
bleAvailabilityState != AvailabilityState.poweredOn
? PlatformButton(
text: 'Enable Bluetooth',
onPressed: () async {
bool isEnabled =
await UniversalBle.enableBluetooth();
showSnackbar("BluetoothEnabled: $isEnabled");
},
)
: PlatformButton(
text: 'Disable Bluetooth',
onPressed: () async {
bool isDisabled =
await UniversalBle.disableBluetooth();
showSnackbar("BluetoothDisabled: $isDisabled");
},
),
if (BleCapabilities.requiresRuntimePermission)
PlatformButton(
text: 'Check Permissions',
onPressed: () async {
bool hasPermissions =
await PermissionHandler.arePermissionsGranted();
if (hasPermissions) {
showSnackbar("Permissions granted");
}
},
),
if (BleCapabilities.supportsConnectedDevicesApi)
PlatformButton(
text: 'System Devices',
onPressed: _getSystemDevices,
),
PlatformButton(
text: 'Queue: ${_queueType.name}',
onPressed: () {
setState(() {
_queueType = switch (_queueType) {
QueueType.global => QueueType.perDevice,
QueueType.perDevice => QueueType.none,
QueueType.none => QueueType.global,
};
UniversalBle.queueType = _queueType;
});
},
),
PlatformButton(
text: 'Scan Filters',
onPressed: _showScanFilterBottomSheet,
),
if (_bleDevices.isNotEmpty)
PlatformButton(
text: 'Clear List',
onPressed: () {
setState(() {
_bleDevices.clear();
});
},
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Ble Availability : ${bleAvailabilityState?.name}',
),
),
],
),
const Divider(color: Colors.blue),
Expanded(
child: _isScanning && _bleDevices.isEmpty
? const Center(child: CircularProgressIndicator.adaptive())
: !_isScanning && _bleDevices.isEmpty
? const ScannedDevicesPlaceholderWidget()
: ListView.separated(
itemCount: _bleDevices.length,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
BleDevice device =
_bleDevices[_bleDevices.length - index - 1];
return ScannedItemWidget(
bleDevice: device,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PeripheralDetailPage(
device.deviceId,
device.name ?? "Unknown Peripheral",
),
));
UniversalBle.stopScan();
setState(() {
_isScanning = false;
});
},
);
},
),
),
],
),
);
}
}

View File

@@ -1,172 +0,0 @@
import 'package:flutter/material.dart';
import 'package:universal_ble/universal_ble.dart';
import 'package:universal_ble_example/widgets/platform_button.dart';
class ScanFilterWidget extends StatefulWidget {
final Function(ScanFilter? filter) onScanFilter;
final TextEditingController servicesFilterController;
final TextEditingController namePrefixController;
final TextEditingController manufacturerDataController;
const ScanFilterWidget({
super.key,
required this.onScanFilter,
required this.servicesFilterController,
required this.namePrefixController,
required this.manufacturerDataController,
});
@override
State<ScanFilterWidget> createState() => _ScanFilterWidgetState();
}
class _ScanFilterWidgetState extends State<ScanFilterWidget> {
String? error;
void applyFilter() {
setState(() {
error = null;
});
try {
List<String> serviceUUids = [];
List<String> namePrefixes = [];
List<ManufacturerDataFilter> manufacturerDataFilters = [];
// Parse Services
if (widget.servicesFilterController.text.isNotEmpty) {
List<String> services = widget.servicesFilterController.text.split(',');
for (String service in services) {
try {
serviceUUids.add(BleUuidParser.string(service.trim()));
} on FormatException catch (_) {
throw Exception("Invalid Service UUID $service");
}
}
}
// Parse Name Prefix
String namePrefix = widget.namePrefixController.text;
if (namePrefix.isNotEmpty) {
namePrefixes = namePrefix.split(',').map((e) => e.trim()).toList();
}
// Parse Manufacturer Data
String manufacturerDataText = widget.manufacturerDataController.text;
if (manufacturerDataText.isNotEmpty) {
List<String> manufacturerData = manufacturerDataText.split(',');
for (String manufacturer in manufacturerData) {
int? companyIdentifier = int.tryParse(manufacturer);
if (companyIdentifier == null) {
throw Exception("Invalid Manufacturer Data $manufacturer");
}
manufacturerDataFilters.add(
ManufacturerDataFilter(companyIdentifier: companyIdentifier));
}
}
if (serviceUUids.isEmpty &&
namePrefixes.isEmpty &&
manufacturerDataFilters.isEmpty) {
widget.onScanFilter(null);
} else {
widget.onScanFilter(
ScanFilter(
withServices: serviceUUids,
withNamePrefix: namePrefixes,
withManufacturerData: manufacturerDataFilters,
),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Filters Applied")),
);
}
Navigator.pop(context);
} catch (e) {
setState(() {
error = e.toString();
});
}
}
void clearFilter() {
widget.servicesFilterController.clear();
widget.namePrefixController.clear();
widget.manufacturerDataController.clear();
widget.onScanFilter(null);
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: MediaQuery.of(context).viewInsets.copyWith(left: 20, right: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 20),
Center(
child: Text(
"Scan Filters",
style: Theme.of(context).textTheme.titleLarge,
),
),
const Text("Use comma to add multiple values"),
const Divider(),
const SizedBox(height: 10),
TextFormField(
controller: widget.namePrefixController,
maxLines: 2,
decoration: const InputDecoration(
labelText: "Name Prefixes",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 10),
TextFormField(
controller: widget.servicesFilterController,
maxLines: 2,
decoration: const InputDecoration(
labelText: "Services",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 10),
TextFormField(
controller: widget.manufacturerDataController,
maxLines: 2,
decoration: const InputDecoration(
labelText: "Manufacturer Data Company IDs",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: PlatformButton(
text: 'Apply',
onPressed: applyFilter,
),
),
const SizedBox(width: 10),
Expanded(
child: PlatformButton(
text: 'Clear',
onPressed: clearFilter,
),
),
],
),
const SizedBox(height: 10),
if (error != null)
Text(
error!,
style: const TextStyle(color: Colors.red),
),
const SizedBox(height: 10),
],
),
);
}
}

View File

@@ -1,26 +0,0 @@
import 'package:flutter/material.dart';
class ScannedDevicesPlaceholderWidget extends StatelessWidget {
const ScannedDevicesPlaceholderWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(8.0),
child: Icon(
Icons.bluetooth,
color: Colors.grey,
size: 100,
),
),
Text(
'Scan For Devices',
style: TextStyle(color: Colors.grey, fontSize: 22),
)
],
);
}
}

View File

@@ -1,50 +0,0 @@
import 'package:flutter/material.dart';
import 'package:universal_ble/universal_ble.dart';
class ScannedItemWidget extends StatelessWidget {
final BleDevice bleDevice;
final VoidCallback? onTap;
const ScannedItemWidget({super.key, required this.bleDevice, this.onTap});
@override
Widget build(BuildContext context) {
String? name = bleDevice.name;
List<ManufacturerData> rawManufacturerData = bleDevice.manufacturerDataList;
ManufacturerData? manufacturerData;
if (rawManufacturerData.isNotEmpty) {
manufacturerData = rawManufacturerData.first;
}
if (name == null || name.isEmpty) name = 'N/A';
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Card(
child: ListTile(
title: Text(
'$name (${bleDevice.rssi})',
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(bleDevice.deviceId),
Visibility(
visible: manufacturerData != null,
child: Text(manufacturerData.toString()),
),
bleDevice.isPaired == true
? const Text(
"Paired",
style: TextStyle(color: Colors.green),
)
: const Text(
"Not Paired",
style: TextStyle(color: Colors.red),
),
],
),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: onTap,
),
),
);
}
}

Some files were not shown because too many files have changed in this diff Show More