remove dependency again
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
1
universal_ble/.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
||||
github: navideck
|
||||
@@ -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
|
||||
@@ -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
|
||||
30
universal_ble/.gitignore
vendored
@@ -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/
|
||||
@@ -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'
|
||||
21
universal_ble/.vscode/c_cpp_properties.json
vendored
@@ -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
|
||||
}
|
||||
44
universal_ble/.vscode/launch.json
vendored
@@ -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"
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
150
universal_ble/.vscode/settings.json
vendored
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -1,448 +0,0 @@
|
||||
# UniversalBLE
|
||||
|
||||
[](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());
|
||||
```
|
||||
@@ -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
|
||||
9
universal_ble/android/.gitignore
vendored
@@ -1,9 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
rootProject.name = 'universal_ble'
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
38
universal_ble/darwin/.gitignore
vendored
@@ -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
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
47
universal_ble/example/.gitignore
vendored
@@ -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
|
||||
@@ -1,3 +0,0 @@
|
||||
# universal_ble_example
|
||||
|
||||
Demonstrates how to use the universal_ble plugin.
|
||||
@@ -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
|
||||
13
universal_ble/example/android/.gitignore
vendored
@@ -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
|
||||
@@ -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 {}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.navideck.universal_ble_example
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
Before Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
@@ -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
|
||||
@@ -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"
|
||||
34
universal_ble/example/ios/.gitignore
vendored
@@ -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
|
||||
@@ -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>
|
||||
@@ -1,2 +0,0 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -1,2 +0,0 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 */;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 282 B |
|
Before Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 704 B |
|
Before Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 586 B |
|
Before Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 762 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -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"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
@@ -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.
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1 +0,0 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||