├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ └── feature_request.md
└── workflows
│ ├── ktlint.yml
│ └── main.yml
├── .gitignore
├── .swiftlint.yml
├── LICENSE
├── README.md
├── analysis_options.yaml
├── bin
├── flutter_customer_test.bat
├── flutter_customer_test.sh
└── quality_checks.sh
├── example
├── LICENSE
├── README.md
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── signify
│ │ │ │ └── hue
│ │ │ │ └── reactivebleexample
│ │ │ │ └── MainActivity.kt
│ │ │ └── res
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── values
│ │ │ └── styles.xml
│ ├── build.gradle
│ ├── config
│ │ └── detekt
│ │ │ └── detekt.yml
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle
├── ios
│ ├── .gitignore
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
├── lib
│ ├── main.dart
│ └── src
│ │ ├── ble
│ │ ├── ble_device_connector.dart
│ │ ├── ble_device_interactor.dart
│ │ ├── ble_logger.dart
│ │ ├── ble_scanner.dart
│ │ ├── ble_status_monitor.dart
│ │ └── reactive_state.dart
│ │ ├── ui
│ │ ├── ble_status_screen.dart
│ │ ├── device_detail
│ │ │ ├── characteristic_interaction_dialog.dart
│ │ │ ├── device_detail_screen.dart
│ │ │ ├── device_interaction_tab.dart
│ │ │ ├── device_interaction_tab.g.dart
│ │ │ └── device_log_tab.dart
│ │ └── device_list.dart
│ │ ├── utils.dart
│ │ └── widgets.dart
├── macos
│ ├── .gitignore
│ ├── Flutter
│ │ ├── Flutter-Debug.xcconfig
│ │ ├── Flutter-Release.xcconfig
│ │ └── GeneratedPluginRegistrant.swift
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── app_icon_1024.png
│ │ │ │ ├── app_icon_128.png
│ │ │ │ ├── app_icon_16.png
│ │ │ │ ├── app_icon_256.png
│ │ │ │ ├── app_icon_32.png
│ │ │ │ ├── app_icon_512.png
│ │ │ │ └── app_icon_64.png
│ │ ├── Base.lproj
│ │ │ └── MainMenu.xib
│ │ ├── Configs
│ │ │ ├── AppInfo.xcconfig
│ │ │ ├── Debug.xcconfig
│ │ │ ├── Release.xcconfig
│ │ │ └── Warnings.xcconfig
│ │ ├── DebugProfile.entitlements
│ │ ├── Info.plist
│ │ ├── MainFlutterWindow.swift
│ │ └── Release.entitlements
│ └── RunnerTests
│ │ └── RunnerTests.swift
├── pubspec.lock
├── pubspec.yaml
└── test
│ └── example_test.dart
├── melos.yaml
├── packages
├── flutter_reactive_ble
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── lib
│ │ ├── flutter_reactive_ble.dart
│ │ └── src
│ │ │ ├── connected_device_operation.dart
│ │ │ ├── debug_logger.dart
│ │ │ ├── device_connector.dart
│ │ │ ├── device_scanner.dart
│ │ │ ├── discovered_devices_registry.dart
│ │ │ ├── reactive_ble.dart
│ │ │ └── rx_ext
│ │ │ ├── repeater.dart
│ │ │ └── serial_disposable.dart
│ ├── pubspec.lock
│ ├── pubspec.yaml
│ └── test
│ │ ├── connected_device_operation_test.dart
│ │ ├── connected_device_operation_test.mocks.dart
│ │ ├── device_connector_test.dart
│ │ ├── device_connector_test.mocks.dart
│ │ ├── device_scanner_test.dart
│ │ ├── device_scanner_test.mocks.dart
│ │ ├── reactive_ble_test.dart
│ │ ├── reactive_ble_test.mocks.dart
│ │ └── rx_ext
│ │ └── serial_disposable_test.dart
├── reactive_ble_mobile
│ ├── .metadata
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── android
│ │ ├── build.gradle
│ │ ├── proguard-rules.txt
│ │ ├── settings.gradle
│ │ └── src
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── signify
│ │ │ │ └── hue
│ │ │ │ └── flutterreactiveble
│ │ │ │ ├── PluginController.kt
│ │ │ │ ├── ReactiveBlePlugin.kt
│ │ │ │ ├── ble
│ │ │ │ ├── BleClient.kt
│ │ │ │ ├── BleWrapper.kt
│ │ │ │ ├── ConnectionQueue.kt
│ │ │ │ ├── DeviceConnector.kt
│ │ │ │ ├── ReactiveBleClient.kt
│ │ │ │ └── extensions
│ │ │ │ │ └── RxBleConnectionExtension.kt
│ │ │ │ ├── channelhandlers
│ │ │ │ ├── BleStatusHandler.kt
│ │ │ │ ├── CharNotificationHandler.kt
│ │ │ │ ├── DeviceConnectionHandler.kt
│ │ │ │ └── ScanDevicesHandler.kt
│ │ │ │ ├── converters
│ │ │ │ ├── ManufacturerDataConverter.kt
│ │ │ │ ├── ProtobufMessageConverter.kt
│ │ │ │ └── UuidConverter.kt
│ │ │ │ ├── debugutils
│ │ │ │ ├── HexStringConversion.kt
│ │ │ │ └── PerformanceAnalyzer.kt
│ │ │ │ ├── model
│ │ │ │ ├── ConnectionState.kt
│ │ │ │ ├── ErrorType.kt
│ │ │ │ └── ScanMode.kt
│ │ │ │ └── utils
│ │ │ │ ├── BleWrapperExtensions.kt
│ │ │ │ ├── Discard.kt
│ │ │ │ └── Duration.kt
│ │ │ └── test
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── signify
│ │ │ └── hue
│ │ │ └── flutterreactiveble
│ │ │ ├── ble
│ │ │ ├── DeviceConnectorTest.kt
│ │ │ └── ReactiveBleClientTest.kt
│ │ │ ├── channelhandlers
│ │ │ └── ConnectionQueueTest.kt
│ │ │ └── converters
│ │ │ ├── ProtobufMessageConverterTest.kt
│ │ │ ├── ServicesWithCharacteristicsConverterTest.kt
│ │ │ └── UuidConverterTest.kt
│ ├── darwin
│ │ ├── .gitignore
│ │ ├── Assets
│ │ │ └── .gitkeep
│ │ ├── Classes
│ │ │ ├── BleData extras
│ │ │ │ ├── BLEStatus.swift
│ │ │ │ ├── CharacteristicInstance.swift
│ │ │ │ ├── ConnectionState.swift
│ │ │ │ └── FailureCodes.swift
│ │ │ ├── BleData
│ │ │ │ └── bledata.pb.swift
│ │ │ ├── Plugin
│ │ │ │ ├── Common
│ │ │ │ │ ├── EventSink.swift
│ │ │ │ │ ├── Failable.swift
│ │ │ │ │ ├── MethodHandler.swift
│ │ │ │ │ ├── PlatformMethod.swift
│ │ │ │ │ └── StreamHandler.swift
│ │ │ │ ├── PluginController.swift
│ │ │ │ ├── PluginError.swift
│ │ │ │ ├── StreamingTask.swift
│ │ │ │ └── SwiftReactiveBlePlugin.swift
│ │ │ ├── Prelude
│ │ │ │ ├── CoreBluetooth+Extensions.swift
│ │ │ │ ├── base.swift
│ │ │ │ └── papply.swift
│ │ │ ├── ReactiveBle
│ │ │ │ ├── Central.swift
│ │ │ │ ├── CentralManagerDelegate.swift
│ │ │ │ ├── OnOff.swift
│ │ │ │ ├── PeripheralDelegate.swift
│ │ │ │ └── Tasks
│ │ │ │ │ ├── CharacteristicNotify
│ │ │ │ │ ├── CharacteristicNotifyTaskController.swift
│ │ │ │ │ └── CharacteristicNotifyTaskSpec.swift
│ │ │ │ │ ├── CharacteristicWrite
│ │ │ │ │ ├── CharacteristicWriteTaskController.swift
│ │ │ │ │ └── CharacteristicWriteTaskSpec.swift
│ │ │ │ │ ├── Connect
│ │ │ │ │ ├── ConnectTaskController.swift
│ │ │ │ │ └── ConnectTaskSpec.swift
│ │ │ │ │ ├── PeripheralTaskRegistry
│ │ │ │ │ ├── PeripheralTask.swift
│ │ │ │ │ ├── PeripheralTaskController.swift
│ │ │ │ │ └── PeripheralTaskRegistry.swift
│ │ │ │ │ ├── ReadRssi
│ │ │ │ │ ├── ReadRssiTaskController.swift
│ │ │ │ │ └── ReadRssiTaskSpec.swift
│ │ │ │ │ └── ServicesWithCharacteristicsDiscovery
│ │ │ │ │ ├── ServicesWithCharacteristicsDiscoveryTaskController.swift
│ │ │ │ │ ├── ServicesWithCharacteristicsDiscoveryTaskSpec.swift
│ │ │ │ │ └── ServicesWithCharacteristicsToDiscover.swift
│ │ │ ├── ReactiveBlePlugin.h
│ │ │ └── ReactiveBlePlugin.m
│ │ └── reactive_ble_mobile.podspec
│ ├── lib
│ │ ├── reactive_ble_mobile.dart
│ │ └── src
│ │ │ ├── converter
│ │ │ ├── args_to_protubuf_converter.dart
│ │ │ └── protobuf_converter.dart
│ │ │ ├── generated
│ │ │ ├── bledata.pb.dart
│ │ │ ├── bledata.pbenum.dart
│ │ │ ├── bledata.pbjson.dart
│ │ │ └── bledata.pbserver.dart
│ │ │ ├── reactive_ble_mobile_platform.dart
│ │ │ └── select_from.dart
│ ├── protos
│ │ ├── README.md
│ │ └── bledata.proto
│ ├── pubspec.lock
│ ├── pubspec.yaml
│ └── test
│ │ ├── converter
│ │ ├── args_to_protobuf_converter_test.dart
│ │ └── protobuf_converter_test.dart
│ │ ├── reactive_ble_platform_test.dart
│ │ └── reactive_ble_platform_test.mocks.dart
└── reactive_ble_platform_interface
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── lib
│ ├── reactive_ble_platform_interface.dart
│ └── src
│ │ ├── logger.dart
│ │ ├── model
│ │ ├── ble_status.dart
│ │ ├── characteristic_instance.dart
│ │ ├── characteristic_value.dart
│ │ ├── clear_gatt_cache_error.dart
│ │ ├── connection_priority.dart
│ │ ├── connection_state_update.dart
│ │ ├── connection_state_update.g.dart
│ │ ├── discovered_characteristic.dart
│ │ ├── discovered_device.dart
│ │ ├── discovered_device.g.dart
│ │ ├── discovered_service.dart
│ │ ├── discovered_service.g.dart
│ │ ├── generic_failure.dart
│ │ ├── log_level.dart
│ │ ├── qualified_characteristic.dart
│ │ ├── result.dart
│ │ ├── scan_mode.dart
│ │ ├── scan_session.dart
│ │ ├── unit.dart
│ │ ├── uuid.dart
│ │ └── write_characteristic_info.dart
│ │ ├── models.dart
│ │ ├── reactive_ble_platform_interface.dart
│ │ └── select_from.dart
│ ├── pubspec.lock
│ ├── pubspec.yaml
│ └── test
│ └── select_from_test.dart
├── pubspec.lock
└── pubspec.yaml
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{kt,kts}]
2 | ktlint_standard_property-naming=disabled
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Connect to one / many devices using `connectTo()` / `connectToAdvertisingDevice()`
16 | 2. Read from / write to / subscribe to a protected / non-protected characteristic
17 | 3. Observe a failure with exception (including the part of the stack trace, belonging to this package) …
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | - [ ] I tried doing the same with a general BLE scanner application (e.g. [nRF Connect](https://duckduckgo.com/?q=nRF+connect)) and it exhibits the expected behavior as described above
23 |
24 | **Smartphone / tablet**
25 | - Device: [e.g. iPhone X]
26 | - OS: [e.g. iOS 13, Android 10]
27 | - Package version: [e.g. 1.0.0]
28 |
29 | **Peripheral device**
30 | - Vendor, model: [e.g. Fitbit Versa, iPhone SE, or CUSTOM]
31 | - Does it run a custom firmware / software: yes / no
32 |
33 | **Additional context**
34 | Add any other context about the problem here.
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? (please describe)**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/ktlint.yml:
--------------------------------------------------------------------------------
1 | name: ktlint
2 | on: [pull_request]
3 | jobs:
4 | ktlint:
5 | name: Check Code Quality
6 | runs-on: ubuntu-latest
7 |
8 | steps:
9 | - name: Clone repo
10 | uses: actions/checkout@master
11 | with:
12 | fetch-depth: 1
13 | - name: ktlint
14 | uses: ScaCap/action-ktlint@master
15 | with:
16 | github_token: ${{ secrets.github_token }}
17 | reporter: github-pr-review # Change reporter
18 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: "Build"
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - master
8 |
9 | jobs:
10 | build:
11 | name: Build
12 | runs-on: macos-latest
13 |
14 | steps:
15 | - name: Checkout Repository
16 | uses: actions/checkout@v3
17 |
18 | - name: Set Up Java
19 | uses: actions/setup-java@v3.12.0
20 | with:
21 | distribution: 'oracle'
22 | java-version: '19.0.2'
23 |
24 | - name: Set Up Flutter
25 | uses: subosito/flutter-action@v2
26 | with:
27 | flutter-version: '3.16.3'
28 | channel: 'stable'
29 |
30 | - name: Set up debug keystore
31 | run: |
32 | rm -f ~/.android/debug.keystore
33 | keytool -genkeypair \
34 | -alias androiddebugkey \
35 | -keypass android \
36 | -keystore ~/.android/debug.keystore \
37 | -storepass android \
38 | -dname 'CN=Android Debug,O=Android,C=US' \
39 | -keyalg 'RSA' \
40 | -keysize 2048 \
41 | -validity 10000
42 |
43 | - name: Quality checks monorepo
44 | run: |
45 | ./bin/quality_checks.sh
46 |
47 | - name: Build Android app & run native tests
48 | run: |
49 | #!/bin/bash -ex
50 | cd example
51 | flutter build apk --debug
52 |
53 | cd android
54 | ./gradlew detekt
55 | ./gradlew testDebugUnitTest
56 |
57 | - name: Build iOS app
58 | run: |
59 | find . -name "Podfile" -execdir pod install \;
60 | cd example && flutter build ios --debug --no-codesign
61 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | .vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .packages
28 | .pub-cache/
29 | .pub/
30 | build/
31 | .flutter-plugins-dependencies
32 | Flutter.podspec
33 | # Android related
34 | .gradle/
35 | **/android/.gradle
36 | **/android/captures/
37 | **/android/local.properties
38 | **/android/**/GeneratedPluginRegistrant.java
39 |
40 | # iOS/XCode related
41 | **/ios/**/*.mode1v3
42 | **/ios/**/*.mode2v3
43 | **/ios/**/*.moved-aside
44 | **/ios/**/*.pbxuser
45 | **/ios/**/*.perspectivev3
46 | **/ios/**/*sync/
47 | **/ios/**/.sconsign.dblite
48 | **/ios/**/.tags*
49 | **/ios/**/.vagrant/
50 | **/ios/**/DerivedData/
51 | **/ios/**/Icon?
52 | **/ios/**/Pods/
53 | **/ios/**/.symlinks/
54 | **/ios/**/profile
55 | **/ios/**/xcuserdata
56 | **/ios/.generated/
57 | **/ios/Flutter/App.framework
58 | **/ios/Flutter/Flutter.framework
59 | **/ios/Flutter/Generated.xcconfig
60 | **/ios/Flutter/app.flx
61 | **/ios/Flutter/app.zip
62 | **/ios/Flutter/flutter_assets/
63 | **/ios/Flutter/flutter_export_environment.sh
64 | **/ios/ServiceDefinitions.json
65 | **/ios/Runner/GeneratedPluginRegistrant.*
66 |
67 | # Exceptions to above rules.
68 | !**/ios/**/default.mode1v3
69 | !**/ios/**/default.mode2v3
70 | !**/ios/**/default.pbxuser
71 | !**/ios/**/default.perspectivev3
72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
73 |
74 | pubspec_overrides.yaml
75 | .ruby-version
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - cyclomatic_complexity
3 | - identifier_name
4 | - multiple_closures_with_trailing_closure
5 | - nesting
6 | - type_name
7 | - unused_closure_parameter
8 |
9 | excluded: # case-sensitive paths to ignore during linting. Takes precedence over `included`
10 | - Pods
11 | - "**/*.pb.swift" # exclude files with a wildcard
12 |
13 | line_length: 300
14 | type_body_length:
15 | - 600
16 |
17 | function_body_length:
18 | - 150
19 |
20 | file_length:
21 | warning: 800
22 |
23 | reporter: "xcode"
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD license
2 |
3 | ©2019 Signify Holding.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
6 | following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions
9 | and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
12 | disclaimer in the documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
15 | derived from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 |
--------------------------------------------------------------------------------
/bin/flutter_customer_test.bat:
--------------------------------------------------------------------------------
1 | PUSHD packages\flutter_reactive_ble
2 | CALL flutter analyze --no-fatal-infos || GOTO :END
3 | CALL flutter test || GOTO :END
4 | POPD
5 |
6 | PUSHD packages\reactive_ble_mobile
7 | CALL flutter analyze --no-fatal-infos || GOTO :END
8 | CALL flutter test || GOTO :END
9 | POPD
10 |
11 | PUSHD packages\reactive_ble_platform_interface
12 | CALL flutter analyze --no-fatal-infos || GOTO :END
13 | CALL flutter test || GOTO :END
14 | POPD
15 |
16 | @ECHO.
17 | @ECHO.
18 | @ECHO Testing complete.
19 | GOTO :EOF
20 |
21 | :END
22 | @ECHO.
23 | @ECHO.
24 | @ECHO Testing failed.
25 | EXIT /B 1
26 |
--------------------------------------------------------------------------------
/bin/flutter_customer_test.sh:
--------------------------------------------------------------------------------
1 | set -e
2 |
3 | (cd packages/flutter_reactive_ble; flutter analyze --no-fatal-infos; flutter test)
4 | (cd packages/reactive_ble_mobile; flutter analyze --no-fatal-infos; flutter test)
5 | (cd packages/reactive_ble_platform_interface; flutter analyze --no-fatal-infos; flutter test)
6 |
--------------------------------------------------------------------------------
/bin/quality_checks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -ex
2 | export PATH="$PATH:$FLUTTER_ROOT/.pub-cache/bin"
3 | export PATH="$PATH:$FLUTTER_ROOT/bin"
4 |
5 | dart pub global activate melos
6 |
7 | melos bootstrap
8 | melos run analyze
9 | melos run unittest
10 |
--------------------------------------------------------------------------------
/example/LICENSE:
--------------------------------------------------------------------------------
1 | BSD license
2 |
3 | ©2019 Signify Holding.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
6 | following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions
9 | and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
12 | disclaimer in the documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
15 | derived from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # flutter_reactive_ble
2 |
3 | Demonstrates how to use the `flutter_reactive_ble` plugin.
4 |
5 | ## Getting Started
6 |
7 | For help getting started with Flutter, view our online
8 | [documentation](http://flutter.dev/).
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | android {
26 | compileSdkVersion 33
27 |
28 | sourceSets {
29 | main.java.srcDirs += 'src/main/kotlin'
30 | }
31 |
32 |
33 | compileOptions {
34 | sourceCompatibility JavaVersion.VERSION_1_8
35 | targetCompatibility JavaVersion.VERSION_1_8
36 | }
37 |
38 | kotlinOptions {
39 | jvmTarget = JavaVersion.VERSION_1_8.toString()
40 | }
41 |
42 | defaultConfig {
43 | applicationId "com.signify.hue.reactivebleexample"
44 | minSdkVersion 21
45 | targetSdkVersion 33
46 | versionCode flutterVersionCode.toInteger()
47 | versionName flutterVersionName
48 | }
49 |
50 | buildTypes {
51 | release {
52 | // Signing with the debug keys for now, so `flutter run --release` works.
53 | signingConfig signingConfigs.debug
54 | }
55 | }
56 | namespace 'com.signify.hue.reactivebleexample'
57 |
58 | lint {
59 | disable 'InvalidPackage'
60 | }
61 | }
62 |
63 | flutter {
64 | source '../..'
65 | }
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
11 |
12 |
13 |
16 |
19 |
20 |
21 |
22 |
25 |
26 |
29 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/signify/hue/reactivebleexample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.reactivebleexample
2 |
3 | import androidx.annotation.NonNull
4 | import io.flutter.embedding.android.FlutterActivity
5 | import io.flutter.embedding.engine.FlutterEngine
6 | import io.flutter.plugins.GeneratedPluginRegistrant
7 |
8 | class MainActivity : FlutterActivity() {
9 | override fun configureFlutterEngine(
10 | @NonNull flutterEngine: FlutterEngine,
11 | ) {
12 | GeneratedPluginRegistrant.registerWith(flutterEngine)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | }
5 | }
6 |
7 | rootProject.buildDir = '../build'
8 | subprojects {
9 | project.buildDir = "${rootProject.buildDir}/${project.name}"
10 | }
11 | subprojects {
12 | project.evaluationDependsOn(':app')
13 | }
14 |
15 | tasks.register("clean", Delete) {
16 | delete rootProject.buildDir
17 | }
18 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | android.defaults.buildfeatures.buildconfig=true
2 | android.enableJetifier=false
3 | android.nonFinalResIds=false
4 | android.nonTransitiveRClass=false
5 | android.useAndroidX=true
6 | org.gradle.jvmargs=-Xmx1536M
7 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
7 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21 | id "com.android.application" version "8.0.2" apply false
22 | id "org.jetbrains.kotlin.android" version "1.8.21" apply false
23 | }
24 |
25 | include ":app"
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 12.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
4 | // See https://github.com/flutter/flutter/issues/17735#issuecomment-400837152
5 | FLUTTER_BUILD_MODE=debug
6 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - Protobuf (3.18.0)
4 | - reactive_ble_mobile (0.0.1):
5 | - Flutter
6 | - Protobuf (~> 3.5)
7 | - SwiftProtobuf (~> 1.0)
8 | - SwiftProtobuf (1.17.0)
9 |
10 | DEPENDENCIES:
11 | - Flutter (from `Flutter`)
12 | - reactive_ble_mobile (from `.symlinks/plugins/reactive_ble_mobile/ios`)
13 |
14 | SPEC REPOS:
15 | trunk:
16 | - Protobuf
17 | - SwiftProtobuf
18 |
19 | EXTERNAL SOURCES:
20 | Flutter:
21 | :path: Flutter
22 | reactive_ble_mobile:
23 | :path: ".symlinks/plugins/reactive_ble_mobile/ios"
24 |
25 | SPEC CHECKSUMS:
26 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
27 | Protobuf: 1a37ebea1338949e9ac35a3f06e80b3f536eec8d
28 | reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c
29 | SwiftProtobuf: 9c85136c6ba74b0a1b84279dbf0f6db8efb714e0
30 |
31 | PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011
32 |
33 | COCOAPODS: 1.15.2
34 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | 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.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | en
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | hue_ble_lib_example
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | NSBluetoothAlwaysUsageDescription
28 | The app uses bluetooth to find, connect and transfer data between different devices
29 | NSBluetoothPeripheralUsageDescription
30 | The app uses bluetooth to find, connect and transfer data between different devices
31 | UIApplicationSupportsIndirectInputEvents
32 |
33 | UIBackgroundModes
34 |
35 | bluetooth-central
36 |
37 | UILaunchStoryboardName
38 | LaunchScreen
39 | UIMainStoryboardFile
40 | Main
41 | UISupportedInterfaceOrientations
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationLandscapeLeft
45 | UIInterfaceOrientationLandscapeRight
46 |
47 | UISupportedInterfaceOrientations~ipad
48 |
49 | UIInterfaceOrientationPortrait
50 | UIInterfaceOrientationPortraitUpsideDown
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | UIViewControllerBasedStatusBarAppearance
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
3 | import 'package:flutter_reactive_ble_example/src/ble/ble_device_connector.dart';
4 | import 'package:flutter_reactive_ble_example/src/ble/ble_device_interactor.dart';
5 | import 'package:flutter_reactive_ble_example/src/ble/ble_scanner.dart';
6 | import 'package:flutter_reactive_ble_example/src/ble/ble_status_monitor.dart';
7 | import 'package:flutter_reactive_ble_example/src/ui/ble_status_screen.dart';
8 | import 'package:flutter_reactive_ble_example/src/ui/device_list.dart';
9 | import 'package:provider/provider.dart';
10 |
11 | import 'src/ble/ble_logger.dart';
12 |
13 | const _themeColor = Colors.lightGreen;
14 |
15 | void main() {
16 | WidgetsFlutterBinding.ensureInitialized();
17 |
18 | final _ble = FlutterReactiveBle();
19 | final _bleLogger = BleLogger(ble: _ble);
20 | final _scanner = BleScanner(ble: _ble, logMessage: _bleLogger.addToLog);
21 | final _monitor = BleStatusMonitor(_ble);
22 | final _connector = BleDeviceConnector(
23 | ble: _ble,
24 | logMessage: _bleLogger.addToLog,
25 | );
26 | final _serviceDiscoverer = BleDeviceInteractor(
27 | bleDiscoverServices: (deviceId) async {
28 | await _ble.discoverAllServices(deviceId);
29 | return _ble.getDiscoveredServices(deviceId);
30 | },
31 | logMessage: _bleLogger.addToLog,
32 | readRssi: _ble.readRssi,
33 | );
34 | runApp(
35 | MultiProvider(
36 | providers: [
37 | Provider.value(value: _scanner),
38 | Provider.value(value: _monitor),
39 | Provider.value(value: _connector),
40 | Provider.value(value: _serviceDiscoverer),
41 | Provider.value(value: _bleLogger),
42 | StreamProvider(
43 | create: (_) => _scanner.state,
44 | initialData: const BleScannerState(
45 | discoveredDevices: [],
46 | scanIsInProgress: false,
47 | ),
48 | ),
49 | StreamProvider(
50 | create: (_) => _monitor.state,
51 | initialData: BleStatus.unknown,
52 | ),
53 | StreamProvider(
54 | create: (_) => _connector.state,
55 | initialData: const ConnectionStateUpdate(
56 | deviceId: 'Unknown device',
57 | connectionState: DeviceConnectionState.disconnected,
58 | failure: null,
59 | ),
60 | ),
61 | ],
62 | child: MaterialApp(
63 | title: 'Flutter Reactive BLE example',
64 | color: _themeColor,
65 | theme: ThemeData(primarySwatch: _themeColor),
66 | home: const HomeScreen(),
67 | ),
68 | ),
69 | );
70 | }
71 |
72 | class HomeScreen extends StatelessWidget {
73 | const HomeScreen({
74 | Key? key,
75 | }) : super(key: key);
76 |
77 | @override
78 | Widget build(BuildContext context) => Consumer(
79 | builder: (_, status, __) {
80 | if (status == BleStatus.ready) {
81 | return const DeviceListScreen();
82 | } else {
83 | return BleStatusScreen(status: status ?? BleStatus.unknown);
84 | }
85 | },
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/example/lib/src/ble/ble_device_connector.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
4 | import 'package:flutter_reactive_ble_example/src/ble/reactive_state.dart';
5 |
6 | class BleDeviceConnector extends ReactiveState {
7 | BleDeviceConnector({
8 | required FlutterReactiveBle ble,
9 | required void Function(String message) logMessage,
10 | }) : _ble = ble,
11 | _logMessage = logMessage;
12 |
13 | final FlutterReactiveBle _ble;
14 | final void Function(String message) _logMessage;
15 |
16 | @override
17 | Stream get state => _deviceConnectionController.stream;
18 |
19 | final _deviceConnectionController = StreamController();
20 |
21 | // ignore: cancel_subscriptions
22 | late StreamSubscription _connection;
23 |
24 | Future connect(String deviceId) async {
25 | _logMessage('Start connecting to $deviceId');
26 | _connection = _ble.connectToDevice(id: deviceId).listen(
27 | (update) {
28 | _logMessage(
29 | 'ConnectionState for device $deviceId : ${update.connectionState}');
30 | _deviceConnectionController.add(update);
31 | },
32 | onError: (Object e) =>
33 | _logMessage('Connecting to device $deviceId resulted in error $e'),
34 | );
35 | }
36 |
37 | Future disconnect(String deviceId) async {
38 | try {
39 | _logMessage('disconnecting to device: $deviceId');
40 | await _connection.cancel();
41 | } on Exception catch (e, _) {
42 | _logMessage("Error disconnecting from a device: $e");
43 | } finally {
44 | // Since [_connection] subscription is terminated, the "disconnected" state cannot be received and propagated
45 | _deviceConnectionController.add(
46 | ConnectionStateUpdate(
47 | deviceId: deviceId,
48 | connectionState: DeviceConnectionState.disconnected,
49 | failure: null,
50 | ),
51 | );
52 | }
53 | }
54 |
55 | Future dispose() async {
56 | await _deviceConnectionController.close();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/example/lib/src/ble/ble_device_interactor.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
4 |
5 | class BleDeviceInteractor {
6 | BleDeviceInteractor({
7 | required Future> Function(String deviceId) bleDiscoverServices,
8 | required void Function(String message) logMessage,
9 | required this.readRssi,
10 | }) : _bleDiscoverServices = bleDiscoverServices,
11 | _logMessage = logMessage;
12 |
13 | final Future> Function(String deviceId) _bleDiscoverServices;
14 |
15 | final Future Function(String deviceId) readRssi;
16 |
17 | final void Function(String message) _logMessage;
18 |
19 | Future> discoverServices(String deviceId) async {
20 | try {
21 | _logMessage('Start discovering services for: $deviceId');
22 | final result = await _bleDiscoverServices(deviceId);
23 | _logMessage('Discovering services finished');
24 | return result;
25 | } on Exception catch (e) {
26 | _logMessage('Error occurred when discovering services: $e');
27 | rethrow;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example/lib/src/ble/ble_logger.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
2 | import 'package:intl/intl.dart';
3 |
4 | class BleLogger {
5 | BleLogger({
6 | required FlutterReactiveBle ble,
7 | }) : _ble = ble;
8 |
9 | final FlutterReactiveBle _ble;
10 | final List _logMessages = [];
11 | final DateFormat formatter = DateFormat('HH:mm:ss.SSS');
12 |
13 | List get messages => _logMessages;
14 |
15 | void addToLog(String message) {
16 | final now = DateTime.now();
17 | _logMessages.add('${formatter.format(now)} - $message');
18 | }
19 |
20 | void clearLogs() => _logMessages.clear();
21 |
22 | bool get verboseLogging => _ble.logLevel == LogLevel.verbose;
23 |
24 | void toggleVerboseLogging() =>
25 | _ble.logLevel = verboseLogging ? LogLevel.none : LogLevel.verbose;
26 | }
27 |
--------------------------------------------------------------------------------
/example/lib/src/ble/ble_scanner.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
4 | import 'package:flutter_reactive_ble_example/src/ble/reactive_state.dart';
5 | import 'package:meta/meta.dart';
6 |
7 | class BleScanner implements ReactiveState {
8 | BleScanner({
9 | required FlutterReactiveBle ble,
10 | required void Function(String message) logMessage,
11 | }) : _ble = ble,
12 | _logMessage = logMessage;
13 |
14 | final FlutterReactiveBle _ble;
15 | final void Function(String message) _logMessage;
16 | final StreamController _stateStreamController =
17 | StreamController();
18 |
19 | final _devices = [];
20 |
21 | @override
22 | Stream get state => _stateStreamController.stream;
23 |
24 | void startScan(List serviceIds) {
25 | _logMessage('Start ble discovery');
26 | _devices.clear();
27 | _subscription?.cancel();
28 | _subscription =
29 | _ble.scanForDevices(withServices: serviceIds).listen((device) {
30 | final knownDeviceIndex = _devices.indexWhere((d) => d.id == device.id);
31 | if (knownDeviceIndex >= 0) {
32 | _devices[knownDeviceIndex] = device;
33 | } else {
34 | _devices.add(device);
35 | }
36 | _pushState();
37 | }, onError: (Object e) => _logMessage('Device scan fails with error: $e'));
38 | _pushState();
39 | }
40 |
41 | void _pushState() {
42 | _stateStreamController.add(
43 | BleScannerState(
44 | discoveredDevices: _devices,
45 | scanIsInProgress: _subscription != null,
46 | ),
47 | );
48 | }
49 |
50 | Future stopScan() async {
51 | _logMessage('Stop ble discovery');
52 |
53 | await _subscription?.cancel();
54 | _subscription = null;
55 | _pushState();
56 | }
57 |
58 | Future dispose() async {
59 | await _stateStreamController.close();
60 | }
61 |
62 | StreamSubscription? _subscription;
63 | }
64 |
65 | @immutable
66 | class BleScannerState {
67 | const BleScannerState({
68 | required this.discoveredDevices,
69 | required this.scanIsInProgress,
70 | });
71 |
72 | final List discoveredDevices;
73 | final bool scanIsInProgress;
74 | }
75 |
--------------------------------------------------------------------------------
/example/lib/src/ble/ble_status_monitor.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
2 | import 'package:flutter_reactive_ble_example/src/ble/reactive_state.dart';
3 |
4 | class BleStatusMonitor implements ReactiveState {
5 | const BleStatusMonitor(this._ble);
6 |
7 | final FlutterReactiveBle _ble;
8 |
9 | @override
10 | Stream get state => _ble.statusStream;
11 | }
12 |
--------------------------------------------------------------------------------
/example/lib/src/ble/reactive_state.dart:
--------------------------------------------------------------------------------
1 | abstract class ReactiveState {
2 | Stream get state;
3 | }
4 |
--------------------------------------------------------------------------------
/example/lib/src/ui/ble_status_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
3 |
4 | class BleStatusScreen extends StatelessWidget {
5 | const BleStatusScreen({required this.status, Key? key}) : super(key: key);
6 |
7 | final BleStatus status;
8 |
9 | String determineText(BleStatus status) {
10 | switch (status) {
11 | case BleStatus.unsupported:
12 | return "This device does not support Bluetooth";
13 | case BleStatus.unauthorized:
14 | return "Authorize the FlutterReactiveBle example app to use Bluetooth and location";
15 | case BleStatus.poweredOff:
16 | return "Bluetooth is powered off on your device turn it on";
17 | case BleStatus.locationServicesDisabled:
18 | return "Enable location services";
19 | case BleStatus.ready:
20 | return "Bluetooth is up and running";
21 | default:
22 | return "Waiting to fetch Bluetooth status $status";
23 | }
24 | }
25 |
26 | @override
27 | Widget build(BuildContext context) => Scaffold(
28 | body: Center(
29 | child: Text(determineText(status)),
30 | ),
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/example/lib/src/ui/device_detail/device_detail_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
3 | import 'package:flutter_reactive_ble_example/src/ble/ble_device_connector.dart';
4 | import 'package:flutter_reactive_ble_example/src/ui/device_detail/device_log_tab.dart';
5 | import 'package:provider/provider.dart';
6 |
7 | import 'device_interaction_tab.dart';
8 |
9 | class DeviceDetailScreen extends StatelessWidget {
10 | final DiscoveredDevice device;
11 |
12 | const DeviceDetailScreen({required this.device, Key? key}) : super(key: key);
13 |
14 | @override
15 | Widget build(BuildContext context) => Consumer(
16 | builder: (_, deviceConnector, __) => _DeviceDetail(
17 | device: device,
18 | disconnect: deviceConnector.disconnect,
19 | ),
20 | );
21 | }
22 |
23 | class _DeviceDetail extends StatelessWidget {
24 | const _DeviceDetail({
25 | required this.device,
26 | required this.disconnect,
27 | Key? key,
28 | }) : super(key: key);
29 |
30 | final DiscoveredDevice device;
31 | final void Function(String deviceId) disconnect;
32 | @override
33 | Widget build(BuildContext context) => PopScope(
34 | canPop: true,
35 | onPopInvoked: (_) async {
36 | disconnect(device.id);
37 | },
38 | child: DefaultTabController(
39 | length: 2,
40 | child: Scaffold(
41 | appBar: AppBar(
42 | title: Text(device.name.isNotEmpty ? device.name : "Unnamed"),
43 | bottom: const TabBar(
44 | tabs: [
45 | Tab(
46 | icon: Icon(
47 | Icons.bluetooth_connected,
48 | ),
49 | ),
50 | Tab(
51 | icon: Icon(
52 | Icons.find_in_page_sharp,
53 | ),
54 | ),
55 | ],
56 | ),
57 | ),
58 | body: TabBarView(
59 | children: [
60 | DeviceInteractionTab(
61 | device: device,
62 | ),
63 | const DeviceLogTab(),
64 | ],
65 | ),
66 | ),
67 | ),
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/example/lib/src/ui/device_detail/device_log_tab.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:flutter_reactive_ble_example/src/ble/ble_logger.dart';
3 | import 'package:provider/provider.dart';
4 |
5 | class DeviceLogTab extends StatelessWidget {
6 | const DeviceLogTab({Key? key}) : super(key: key);
7 |
8 | @override
9 | Widget build(BuildContext context) => Consumer(
10 | builder: (context, logger, _) => _DeviceLogTab(
11 | messages: logger.messages,
12 | ),
13 | );
14 | }
15 |
16 | class _DeviceLogTab extends StatelessWidget {
17 | const _DeviceLogTab({
18 | required this.messages,
19 | Key? key,
20 | }) : super(key: key);
21 |
22 | final List messages;
23 |
24 | @override
25 | Widget build(BuildContext context) => Padding(
26 | padding: const EdgeInsets.all(16.0),
27 | child: ListView.builder(
28 | itemBuilder: (context, index) => Text(messages[index]),
29 | itemCount: messages.length,
30 | ),
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/example/lib/src/utils.dart:
--------------------------------------------------------------------------------
1 | // ignore: avoid_print
2 | void log(String text) => print("[FlutterReactiveBLEApp] $text");
3 |
--------------------------------------------------------------------------------
/example/lib/src/widgets.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class BluetoothIcon extends StatelessWidget {
4 | const BluetoothIcon({Key? key}) : super(key: key);
5 |
6 | @override
7 | Widget build(BuildContext context) => const SizedBox(
8 | width: 32,
9 | height: 32,
10 | child: Align(alignment: Alignment.center, child: Icon(Icons.bluetooth)),
11 | );
12 | }
13 |
14 | class StatusMessage extends StatelessWidget {
15 | const StatusMessage({
16 | required this.text,
17 | Key? key,
18 | }) : super(key: key);
19 |
20 | final String text;
21 |
22 | @override
23 | Widget build(BuildContext context) => Padding(
24 | padding: const EdgeInsets.symmetric(vertical: 20),
25 | child: Text(text, style: const TextStyle(fontWeight: FontWeight.bold)),
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/example/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/example/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import reactive_ble_mobile
9 |
10 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
11 | ReactiveBlePlugin.register(with: registry.registrar(forPlugin: "ReactiveBlePlugin"))
12 | }
13 |
--------------------------------------------------------------------------------
/example/macos/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.14'
2 |
3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
5 |
6 | project 'Runner', {
7 | 'Debug' => :debug,
8 | 'Profile' => :release,
9 | 'Release' => :release,
10 | }
11 |
12 | def flutter_root
13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
14 | unless File.exist?(generated_xcode_build_settings_path)
15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
16 | end
17 |
18 | File.foreach(generated_xcode_build_settings_path) do |line|
19 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
20 | return matches[1].strip if matches
21 | end
22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
23 | end
24 |
25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
26 |
27 | flutter_macos_podfile_setup
28 |
29 | target 'Runner' do
30 | use_frameworks!
31 | use_modular_headers!
32 |
33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
34 | target 'RunnerTests' do
35 | inherit! :search_paths
36 | end
37 | end
38 |
39 | post_install do |installer|
40 | installer.pods_project.targets.each do |target|
41 | flutter_additional_macos_build_settings(target)
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/example/macos/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - FlutterMacOS (1.0.0)
3 | - Protobuf (3.24.3)
4 | - reactive_ble_mobile (0.0.1):
5 | - Flutter
6 | - FlutterMacOS
7 | - Protobuf (~> 3.5)
8 | - SwiftProtobuf (~> 1.0)
9 | - SwiftProtobuf (1.23.0)
10 |
11 | DEPENDENCIES:
12 | - FlutterMacOS (from `Flutter/ephemeral`)
13 | - reactive_ble_mobile (from `Flutter/ephemeral/.symlinks/plugins/reactive_ble_mobile/darwin`)
14 |
15 | SPEC REPOS:
16 | trunk:
17 | - Protobuf
18 | - SwiftProtobuf
19 |
20 | EXTERNAL SOURCES:
21 | FlutterMacOS:
22 | :path: Flutter/ephemeral
23 | reactive_ble_mobile:
24 | :path: Flutter/ephemeral/.symlinks/plugins/reactive_ble_mobile/darwin
25 |
26 | SPEC CHECKSUMS:
27 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
28 | Protobuf: 970f7ee93a3a08e3cf64859b8efd95ee32b4f87f
29 | reactive_ble_mobile: 856183aeda09f2a676bbaecbea8915c3cb1d33df
30 | SwiftProtobuf: b70d65f419fbfe61a2d58003456ca5da58e337d6
31 |
32 | PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
33 |
34 | COCOAPODS: 1.15.2
35 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @NSApplicationMain
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "app_icon_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "app_icon_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "app_icon_32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "app_icon_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "app_icon_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "app_icon_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "app_icon_256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "app_icon_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "app_icon_512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "app_icon_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = example
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.signify.hue.example
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2023 com.signify.hue. All rights reserved.
15 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/example/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.device.bluetooth
10 |
11 | com.apple.security.network.server
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 | NSBluetoothAlwaysUsageDescription
32 | The app uses bluetooth to find, connect and transfer data between different devices
33 | NSBluetoothPeripheralUsageDescription
34 | The app uses bluetooth to find, connect and transfer data between different devices
35 | UIApplicationSupportsIndirectInputEvents
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/example/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/example/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.device.bluetooth
8 |
9 | com.apple.security.network.server
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/macos/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import FlutterMacOS
2 | import Cocoa
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_reactive_ble_example
2 | description: Demonstrates how to use the flutter_reactive_ble plugin.
3 | version: 5.4.0
4 | publish_to: 'none'
5 |
6 | environment:
7 | sdk: '>=2.17.0 <3.0.0'
8 | flutter: ">=2.0.0"
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 | flutter_reactive_ble: ^5.4.0
14 | functional_data: ^1.0.0
15 | intl: ^0.17.0
16 |
17 | provider: ^6.0.1
18 |
19 | dev_dependencies:
20 | build_runner: ^2.3.3
21 | dependency_validator: ^3.1.0
22 | flutter_lints: ^1.0.4
23 | flutter_test:
24 | sdk: flutter
25 | functional_data_generator: ^1.1.2
26 |
27 | dependency_overrides:
28 | flutter_reactive_ble:
29 | path: ../packages/flutter_reactive_ble
30 | reactive_ble_mobile:
31 | path: ../packages/reactive_ble_mobile
32 | reactive_ble_platform_interface:
33 | path: ../packages/reactive_ble_platform_interface
34 |
35 | flutter:
36 | uses-material-design: true
37 |
--------------------------------------------------------------------------------
/example/test/example_test.dart:
--------------------------------------------------------------------------------
1 | // needed to make CI pass
2 |
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | test('Some test to make CI pass', () {
7 | expect(1, 1);
8 | });
9 | }
10 |
--------------------------------------------------------------------------------
/melos.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_reactive_ble
2 | packages:
3 | - packages/*
4 | - example
5 | scripts:
6 | analyze: melos exec -- flutter analyze
7 |
8 | format: melos exec -- flutter format .
9 |
10 | get: melos exec -- flutter pub get
11 |
12 | upgrade: melos exec -- flutter packages upgrade
13 |
14 | unittest:
15 | run: melos exec -- flutter test
16 |
17 | generate:
18 | run: melos run generate:dart && melos run generate:flutter
19 | description: Build all generated files for Dart & Flutter packages in this project.
20 |
21 | generate:dart:
22 | run: melos exec -c 1 --depends-on="build_runner" --no-flutter -- "dart run build_runner build --delete-conflicting-outputs"
23 | description: Build all generated files for Dart packages in this project.
24 |
25 | generate:flutter:
26 | run: melos exec -c 1 --depends-on="build_runner" --flutter -- "flutter pub run build_runner build --delete-conflicting-outputs"
27 | description: Build all generated files for Flutter packages in this project.
28 |
--------------------------------------------------------------------------------
/packages/flutter_reactive_ble/LICENSE:
--------------------------------------------------------------------------------
1 | BSD license
2 |
3 | ©2019 Signify Holding.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
6 | following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions
9 | and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
12 | disclaimer in the documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
15 | derived from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 |
--------------------------------------------------------------------------------
/packages/flutter_reactive_ble/lib/flutter_reactive_ble.dart:
--------------------------------------------------------------------------------
1 | library flutter_reactive_ble;
2 |
3 | export 'package:reactive_ble_platform_interface/reactive_ble_platform_interface.dart' hide CharacteristicInstance;
4 |
5 | export 'src/reactive_ble.dart';
6 | export 'src/rx_ext/repeater.dart';
7 | export 'src/rx_ext/serial_disposable.dart';
8 |
--------------------------------------------------------------------------------
/packages/flutter_reactive_ble/lib/src/debug_logger.dart:
--------------------------------------------------------------------------------
1 | import 'package:reactive_ble_platform_interface/reactive_ble_platform_interface.dart';
2 |
3 | class DebugLogger implements Logger {
4 | DebugLogger(
5 | String tag,
6 | void Function(Object object) print,
7 | ) : _tag = tag,
8 | _print = print;
9 |
10 | @override
11 | set logLevel(LogLevel logLevel) => _logLevel = logLevel;
12 |
13 | @override
14 | LogLevel get logLevel => _logLevel;
15 |
16 | @override
17 | void log(Object message) {
18 | if (_logLevel == LogLevel.verbose) {
19 | _print('$_tag: $message');
20 | }
21 | }
22 |
23 | final void Function(Object object) _print;
24 | final String _tag;
25 | LogLevel _logLevel = LogLevel.none;
26 | }
27 |
--------------------------------------------------------------------------------
/packages/flutter_reactive_ble/lib/src/device_scanner.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
4 |
5 | abstract class DeviceScanner {
6 | ScanSession? get currentScan;
7 |
8 | Stream scanForDevices({
9 | required List withServices,
10 | ScanMode scanMode = ScanMode.balanced,
11 | bool requireLocationServicesEnabled = true,
12 | });
13 | }
14 |
15 | class DeviceScannerImpl implements DeviceScanner {
16 | DeviceScannerImpl({
17 | required ReactiveBlePlatform blePlatform,
18 | required bool Function() platformIsAndroid,
19 | required Future delayAfterScanCompletion,
20 | required this.addToScanRegistry,
21 | }) : _blePlatform = blePlatform,
22 | _platformIsAndroid = platformIsAndroid,
23 | _delayAfterScanCompletion = delayAfterScanCompletion;
24 |
25 | ScanSession? _currentScanSession;
26 |
27 | final ReactiveBlePlatform _blePlatform;
28 | final bool Function() _platformIsAndroid;
29 | final Future _delayAfterScanCompletion;
30 | final void Function(String deviceId) addToScanRegistry;
31 |
32 | Stream get _scanStream => _blePlatform.scanStream;
33 |
34 | final SerialDisposable> _scanStreamDisposable =
35 | SerialDisposable(
36 | (Repeater repeater) => repeater.dispose());
37 |
38 | @override
39 | ScanSession? get currentScan => _currentScanSession;
40 |
41 | @override
42 | Stream scanForDevices({
43 | required List withServices,
44 | ScanMode scanMode = ScanMode.balanced,
45 | bool requireLocationServicesEnabled = true,
46 | }) {
47 | final completer = Completer();
48 | _currentScanSession =
49 | ScanSession(withServices: withServices, future: completer.future);
50 | // Make sure completing a future with an error does not lead to an unhandled exception.
51 | completer.future.catchError((Object e, StackTrace s) {});
52 |
53 | final scanRepeater = Repeater(
54 | onListenEmitFrom: () =>
55 | _scanStream.map((scan) => scan.result.dematerialize()).handleError(
56 | (Object e, StackTrace s) {
57 | if (!completer.isCompleted) {
58 | completer.completeError(e, s);
59 | if (e is Exception) {
60 | throw e;
61 | } else if (e is Error) {
62 | throw e;
63 | } else {
64 | throw Exception(e);
65 | }
66 | }
67 | },
68 | ),
69 | onCancel: () async {
70 | if (_platformIsAndroid()) {
71 | // Some devices have issues with establishing connection directly after scan is stopped so we should wait here.
72 | // See https://github.com/googlesamples/android-BluetoothLeGatt/issues/44
73 | await _delayAfterScanCompletion;
74 | }
75 | _currentScanSession = null;
76 | if (!completer.isCompleted) {
77 | completer.complete();
78 | }
79 | },
80 | );
81 |
82 | _scanStreamDisposable.set(scanRepeater);
83 |
84 | return _blePlatform
85 | .scanForDevices(
86 | withServices: withServices,
87 | scanMode: scanMode,
88 | requireLocationServicesEnabled: requireLocationServicesEnabled,
89 | )
90 | .asyncExpand((_) => scanRepeater.stream.map((discoveredDevice) {
91 | addToScanRegistry(discoveredDevice.id);
92 | return discoveredDevice;
93 | }));
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/packages/flutter_reactive_ble/lib/src/discovered_devices_registry.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | abstract class DiscoveredDevicesRegistry {
4 | DiscoveredDevicesRegistry({required this.getTimestamp});
5 |
6 | final DateTime Function() getTimestamp;
7 |
8 | void add(String deviceId);
9 |
10 | void remove(String deviceId);
11 |
12 | bool isEmpty();
13 |
14 | bool deviceIsDiscoveredRecently(
15 | {required String deviceId, required Duration cacheValidity});
16 | }
17 |
18 | class DiscoveredDevicesRegistryImpl implements DiscoveredDevicesRegistry {
19 | DiscoveredDevicesRegistryImpl({required this.getTimestamp});
20 |
21 | DiscoveredDevicesRegistryImpl.standard() : this(getTimestamp: DateTime.now);
22 |
23 | @override
24 | final DateTime Function() getTimestamp;
25 |
26 | @visibleForTesting
27 | final discoveredDevices = {};
28 |
29 | @override
30 | void add(String deviceId) {
31 | discoveredDevices[deviceId] = getTimestamp();
32 | }
33 |
34 | @override
35 | void remove(String deviceId) {
36 | discoveredDevices.remove(deviceId);
37 | }
38 |
39 | @override
40 | bool isEmpty() => discoveredDevices.isEmpty;
41 |
42 | @override
43 | bool deviceIsDiscoveredRecently({
44 | required String deviceId,
45 | required Duration cacheValidity,
46 | }) =>
47 | discoveredDevices.containsKey(deviceId) &&
48 | (discoveredDevices[deviceId]
49 | ?.isAfter(getTimestamp().subtract(cacheValidity)) ??
50 | false);
51 | }
52 |
--------------------------------------------------------------------------------
/packages/flutter_reactive_ble/lib/src/rx_ext/serial_disposable.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:reactive_ble_platform_interface/reactive_ble_platform_interface.dart';
4 |
5 | /// A disposable resource whose underlying resource can be replaced by another resource
6 | /// causing automatic disposal of the previous underlying resource.
7 | class SerialDisposable {
8 | SerialDisposable(this._dispose);
9 |
10 | Future set(T newValue) async {
11 | if (_isDisposed) {
12 | throw _SerialAlreadyDisposed(runtimeType);
13 | }
14 | if (_value != null) await _dispose(_value!);
15 | _value = newValue;
16 | }
17 |
18 | /// Dispose underlying resource
19 | Future dispose() async {
20 | _isDisposed = true;
21 | if (_value != null) {
22 | await _dispose(_value!);
23 | }
24 | }
25 |
26 | /// Returns whether or not the underlying resource is disposed
27 | bool get isDisposed => _isDisposed;
28 |
29 | final Future Function(T) _dispose;
30 | bool _isDisposed = false;
31 | T? _value;
32 | }
33 |
34 | class _SerialAlreadyDisposed extends Error {
35 | final Type _type;
36 |
37 | _SerialAlreadyDisposed(this._type);
38 |
39 | @override
40 | String toString() => "An instance of $_type has already been disposed";
41 | }
42 |
43 | /// A [SerialDisposable] that contains an underlying stream subscription.
44 | class StreamSubscriptionSerialDisposable
45 | extends SerialDisposable> {
46 | StreamSubscriptionSerialDisposable()
47 | : super((StreamSubscription subscription) async {
48 | await subscription.cancel();
49 | return const Unit();
50 | });
51 | }
52 |
--------------------------------------------------------------------------------
/packages/flutter_reactive_ble/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_reactive_ble
2 | description: Reactive Bluetooth Low Energy (BLE) plugin that can communicate with multiple devices
3 | version: 5.4.0
4 | homepage: https://github.com/PhilipsHue/flutter_reactive_ble
5 |
6 | environment:
7 | sdk: '>=2.17.0 <3.0.0'
8 | flutter: ">=2.0.0"
9 |
10 | flutter:
11 | plugin:
12 | platforms:
13 | android:
14 | default_package: reactive_ble_mobile
15 | ios:
16 | default_package: reactive_ble_mobile
17 |
18 | dependencies:
19 | collection: ^1.15.0
20 | flutter:
21 | sdk: flutter
22 | functional_data: ^1.0.0
23 | meta: ^1.3.0
24 | reactive_ble_mobile: ^5.4.0
25 | reactive_ble_platform_interface: ^5.4.0
26 |
27 | dependency_overrides:
28 | reactive_ble_mobile:
29 | path: ../reactive_ble_mobile
30 | reactive_ble_platform_interface:
31 | path: ../reactive_ble_platform_interface
32 |
33 | dev_dependencies:
34 | build_runner: ^2.3.3
35 | flutter_lints: ^1.0.4
36 | flutter_test:
37 | sdk: flutter
38 | functional_data_generator: ^1.1.2
39 | mockito: ^5.0.14
40 |
41 |
--------------------------------------------------------------------------------
/packages/flutter_reactive_ble/test/rx_ext/serial_disposable_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | void main() {
5 | group('Serialdisposable', () {
6 | late SerialDisposable sut;
7 | late _Disposer disposer;
8 |
9 | setUp(() {
10 | disposer = _Disposer();
11 | sut = SerialDisposable(disposer.dispose);
12 | });
13 | test('It emits last set value on dispose', () async {
14 | await sut.set(5);
15 | await sut.dispose();
16 | expect(disposer.disposedValues, [5]);
17 | });
18 |
19 | test('It disposes previous value when a new value is set', () async {
20 | await sut.set(4);
21 | await sut.set(5);
22 | expect(disposer.disposedValues, [4]);
23 | });
24 |
25 | test('It never calls dispose method in case no value is set', () async {
26 | await sut.dispose();
27 | expect(disposer.disposedValues, isEmpty);
28 | });
29 |
30 | test('It returns false in case it is not disposed', () async {
31 | await sut.set(4);
32 | expect(sut.isDisposed, false);
33 | });
34 |
35 | test('It returns true in case it is disposed', () async {
36 | await sut.set(4);
37 | await sut.dispose();
38 | expect(sut.isDisposed, true);
39 | });
40 |
41 | test('It throws in case setting a value on already disposed disposable',
42 | () async {
43 | await sut.dispose();
44 | expect(sut.set(4), throwsA(anything));
45 | });
46 | });
47 | }
48 |
49 | class _Disposer {
50 | final _values = [];
51 | Future dispose(int value) async {
52 | _values.add(value);
53 | return const Unit();
54 | }
55 |
56 | List get disposedValues => _values;
57 | }
58 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03
8 | channel: stable
9 |
10 | project_type: plugin
11 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 5.4.0
2 |
3 | * Support for MacOS #889
4 | * Fixes subscription to _deviceConnector.deviceConnectionStateUpdateStream leaking #876
5 | * Workspace upgrade (dependencies) & gitignore sync #857
6 | * Run ktlint -F #766
7 |
8 | ## 5.3.1
9 |
10 | * Use proper platform_interface dependency version #837
11 |
12 | ## 5.3.0
13 |
14 | * Readd accidentally removed version constraint #789
15 | * Add a placeholder implementation for non-mobile platforms #688
16 | * Add Github actions and Migrate to latest Android SDK, AGP and protobuf plugin #830
17 | * Include packages lock-files #797
18 | * Update Kotlin version in README #831
19 | * Migrate away from deprecated strong mode analysis options #832
20 | * Read RSSI #796
21 |
22 | ## 5.2.0
23 |
24 | * Bump the minimum requirement to Dart 2.17 and upgrade melos to 3.1.0 in #762
25 | * swiftlint config and inital formatting pass in #765
26 | * Cancel subscription when a disconnect event has been thrown in #769
27 | * Fix typos in #778
28 | * Update CI config to use Xcode 14 in #786
29 | * Support multiple services or characteristics with the same id in #776
30 | * Breaking change: If a device has multiple characteristics with the same ID, `readCharacteristic`, `writeCharacteristic` and `subscribeToCharacteristic` used to select the first of those characteristics. Now they will fail in this case. Use `resolve` or `getDiscoveredServices` instead.
31 |
32 | ## 5.1.1
33 |
34 | * Make Connectable backwards compatible #757, #750
35 |
36 | ## 5.1.0
37 |
38 | * Add IsConnectable to discovery data. #750
39 | * Upgraded build_runner. #750
40 |
41 | ## 5.0.3
42 |
43 | * Enable extended advertising on android. Fix #571
44 | * Add additional null checks to scanRecord. Fix #521
45 | * Restore support for Xcode 12
46 | * Add verbose debug logging Fix #583
47 |
48 | ## 5.0.2
49 |
50 | * Revert Queue up messages on iOS until event channel is ready. Fix #439
51 |
52 | ## 5.0.1
53 |
54 | * Bump protobuf so it includes binaries for Mac M1 #396.
55 |
56 | ## 5.0.0
57 |
58 | * `DiscoveredService` has now a new required property called `DiscoveredCharacteristic` which provides properties of the BLE characteristic.
59 | * Android 12 support.
60 | * Queue up messages on iOS until event channel is ready. Fixes #137, #251, #307, #385, #387.
61 |
62 | ## 4.1.0
63 |
64 | * Add support Android 12
65 |
66 | ## 4.0.1
67 |
68 | * Add support for iOS 15
69 |
70 | ## 4.0.0
71 |
72 | * Initial Open Source release.
73 |
74 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/LICENSE:
--------------------------------------------------------------------------------
1 | BSD license
2 |
3 | ©2019 Signify Holding.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
6 | following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions
9 | and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
12 | disclaimer in the documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
15 | derived from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/README.md:
--------------------------------------------------------------------------------
1 | # Reactive_ble_mobile
2 |
3 | Official Android and iOS implementation for the flutter_reactive_ble plugin.
4 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/build.gradle:
--------------------------------------------------------------------------------
1 | group 'com.signify.hue.flutterreactiveblelib'
2 | version '1.0-SNAPSHOT'
3 |
4 | buildscript {
5 | ext.detekt_version = '1.23.0'
6 | ext.kotlin_version = '1.8.21'
7 | repositories {
8 | google()
9 | mavenCentral()
10 | maven {
11 | url "https://plugins.gradle.org/m2/"
12 | }
13 | }
14 |
15 | dependencies {
16 | classpath 'com.android.tools.build:gradle:8.0.2'
17 | classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.4'
18 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
19 | classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detekt_version"
20 | classpath "de.mannodermaus.gradle.plugins:android-junit5:1.7.1.1"
21 | }
22 | }
23 |
24 | rootProject.allprojects {
25 | repositories {
26 | google()
27 | mavenCentral()
28 | maven {
29 | url "https://plugins.gradle.org/m2/"
30 | }
31 | }
32 | }
33 |
34 | apply plugin: 'com.android.library'
35 | apply plugin: 'com.google.protobuf'
36 | apply plugin: 'kotlin-android'
37 | apply plugin: "io.gitlab.arturbosch.detekt"
38 | apply plugin: "de.mannodermaus.android-junit5"
39 |
40 | android {
41 | compileSdkVersion 33
42 | sourceSets {
43 | main.java.srcDirs += 'src/main/kotlin'
44 | test.java.srcDirs += 'src/test/kotlin'
45 | main {
46 | proto {
47 | srcDir '../protos/'
48 | }
49 | }
50 | }
51 |
52 | defaultConfig {
53 | minSdkVersion 21
54 | targetSdkVersion 33
55 | consumerProguardFiles 'proguard-rules.txt'
56 | }
57 |
58 | namespace 'com.signify.hue.flutterreactiveble'
59 |
60 | lint {
61 | disable 'InvalidPackage'
62 | }
63 |
64 | compileOptions {
65 | sourceCompatibility JavaVersion.VERSION_1_8
66 | targetCompatibility JavaVersion.VERSION_1_8
67 | }
68 |
69 | kotlinOptions {
70 | jvmTarget = JavaVersion.VERSION_1_8.toString()
71 | }
72 | }
73 |
74 | detekt {
75 | version = detekt_version
76 | input = files("src/main/kotlin")
77 | dependencies {
78 | detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:$detekt_version"
79 | detektPlugins "io.gitlab.arturbosch.detekt:detekt-rules-libraries:$detekt_version"
80 | }
81 | }
82 |
83 | protobuf {
84 | protoc {
85 | artifact = 'com.google.protobuf:protoc:3.25.3'
86 | }
87 |
88 | generateProtoTasks {
89 | all().each { task ->
90 | task.builtins {
91 | java {
92 | option "lite"
93 | }
94 | }
95 | }
96 | }
97 | }
98 |
99 | dependencies {
100 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
101 | implementation 'com.polidea.rxandroidble2:rxandroidble:1.16.0'
102 | implementation 'com.google.protobuf:protobuf-javalite:3.25.3'
103 | implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
104 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
105 |
106 | testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.0"
107 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.7.0"
108 | testImplementation "io.mockk:mockk:1.11.0"
109 | testImplementation "com.google.truth:truth:1.1.4"
110 | }
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/proguard-rules.txt:
--------------------------------------------------------------------------------
1 | -keep class com.signify.hue.** { *; }
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'flutter_reactive_ble'
2 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ReactiveBlePlugin.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble
2 |
3 | import android.content.Context
4 | import io.flutter.embedding.engine.plugins.FlutterPlugin
5 | import io.flutter.plugin.common.BinaryMessenger
6 | import io.flutter.plugin.common.MethodCall
7 | import io.flutter.plugin.common.MethodChannel
8 | import io.flutter.plugin.common.MethodChannel.Result
9 |
10 | class ReactiveBlePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
11 | override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
12 | initializePlugin(binding.binaryMessenger, binding.applicationContext, this)
13 | }
14 |
15 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
16 | deinitializePlugin()
17 | }
18 |
19 | companion object {
20 | lateinit var pluginController: PluginController
21 |
22 | @JvmStatic
23 | private fun initializePlugin(
24 | messenger: BinaryMessenger,
25 | context: Context,
26 | plugin: ReactiveBlePlugin,
27 | ) {
28 | val channel = MethodChannel(messenger, "flutter_reactive_ble_method")
29 | channel.setMethodCallHandler(plugin)
30 | pluginController = PluginController()
31 | pluginController.initialize(messenger, context)
32 | }
33 |
34 | @JvmStatic
35 | private fun deinitializePlugin() {
36 | pluginController.deinitialize()
37 | }
38 | }
39 |
40 | override fun onMethodCall(
41 | call: MethodCall,
42 | result: Result,
43 | ) {
44 | pluginController.execute(call, result)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/BleClient.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.ble
2 |
3 | import android.os.ParcelUuid
4 | import com.polidea.rxandroidble2.RxBleDeviceServices
5 | import com.signify.hue.flutterreactiveble.model.ScanMode
6 | import com.signify.hue.flutterreactiveble.utils.Duration
7 | import io.reactivex.Completable
8 | import io.reactivex.Observable
9 | import io.reactivex.Single
10 | import io.reactivex.subjects.BehaviorSubject
11 | import java.util.UUID
12 |
13 | @Suppress("TooManyFunctions")
14 | interface BleClient {
15 | val connectionUpdateSubject: BehaviorSubject
16 |
17 | fun initializeClient()
18 |
19 | fun scanForDevices(
20 | services: List,
21 | scanMode: ScanMode,
22 | requireLocationServicesEnabled: Boolean,
23 | ): Observable
24 |
25 | fun connectToDevice(
26 | deviceId: String,
27 | timeout: Duration,
28 | )
29 |
30 | fun disconnectDevice(deviceId: String)
31 |
32 | fun disconnectAllDevices()
33 |
34 | fun discoverServices(deviceId: String): Single
35 |
36 | fun clearGattCache(deviceId: String): Completable
37 |
38 | fun readCharacteristic(
39 | deviceId: String,
40 | characteristicId: UUID,
41 | characteristicInstanceId: Int,
42 | ): Single
43 |
44 | fun setupNotification(
45 | deviceId: String,
46 | characteristicId: UUID,
47 | characteristicInstanceId: Int,
48 | ): Observable
49 |
50 | fun writeCharacteristicWithResponse(
51 | deviceId: String,
52 | characteristicId: UUID,
53 | characteristicInstanceId: Int,
54 | value: ByteArray,
55 | ): Single
56 |
57 | fun writeCharacteristicWithoutResponse(
58 | deviceId: String,
59 | characteristicId: UUID,
60 | characteristicInstanceId: Int,
61 | value: ByteArray,
62 | ): Single
63 |
64 | fun negotiateMtuSize(
65 | deviceId: String,
66 | size: Int,
67 | ): Single
68 |
69 | fun observeBleStatus(): Observable
70 |
71 | fun requestConnectionPriority(
72 | deviceId: String,
73 | priority: ConnectionPriority,
74 | ): Single
75 |
76 | fun readRssi(deviceId: String): Single
77 | }
78 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/BleWrapper.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.ble
2 |
3 | import com.polidea.rxandroidble2.RxBleConnection
4 | import java.util.UUID
5 |
6 | data class ScanInfo(
7 | val deviceId: String,
8 | val name: String,
9 | val rssi: Int,
10 | val connectable: Connectable,
11 | val serviceData: Map,
12 | val serviceUuids: List,
13 | val manufacturerData: ByteArray,
14 | ) {
15 | override fun equals(other: Any?): Boolean {
16 | if (this === other) return true
17 | if (javaClass != other?.javaClass) return false
18 |
19 | other as ScanInfo
20 |
21 | if (deviceId != other.deviceId) return false
22 | if (name != other.name) return false
23 | if (rssi != other.rssi) return false
24 | if (connectable != other.connectable) return false
25 | if (serviceData != other.serviceData) return false
26 | if (serviceUuids != other.serviceUuids) return false
27 | if (!manufacturerData.contentEquals(other.manufacturerData)) return false
28 |
29 | return true
30 | }
31 |
32 | override fun hashCode(): Int {
33 | var result = deviceId.hashCode()
34 | result = 31 * result + name.hashCode()
35 | result = 31 * result + rssi
36 | result = 31 * result + connectable.hashCode()
37 | result = 31 * result + serviceData.hashCode()
38 | result = 31 * result + serviceUuids.hashCode()
39 | result = 31 * result + manufacturerData.contentHashCode()
40 | return result
41 | }
42 | }
43 |
44 | sealed class ConnectionUpdate
45 |
46 | data class ConnectionUpdateSuccess(val deviceId: String, val connectionState: Int) : ConnectionUpdate()
47 |
48 | data class ConnectionUpdateError(val deviceId: String, val errorMessage: String) : ConnectionUpdate()
49 |
50 | sealed class EstablishConnectionResult
51 |
52 | data class EstablishedConnection(val deviceId: String, val rxConnection: RxBleConnection) : EstablishConnectionResult()
53 |
54 | data class EstablishConnectionFailure(val deviceId: String, val errorMessage: String) : EstablishConnectionResult()
55 |
56 | sealed class MtuNegotiateResult
57 |
58 | data class MtuNegotiateSuccessful(val deviceId: String, val size: Int) : MtuNegotiateResult()
59 |
60 | data class MtuNegotiateFailed(val deviceId: String, val errorMessage: String) : MtuNegotiateResult()
61 |
62 | sealed class CharOperationResult
63 |
64 | data class CharOperationSuccessful(val deviceId: String, val value: List) : CharOperationResult()
65 |
66 | data class CharOperationFailed(val deviceId: String, val errorMessage: String) : CharOperationResult()
67 |
68 | sealed class RequestConnectionPriorityResult
69 |
70 | data class RequestConnectionPrioritySuccess(val deviceId: String) : RequestConnectionPriorityResult()
71 |
72 | data class RequestConnectionPriorityFailed(val deviceId: String, val errorMessage: String) : RequestConnectionPriorityResult()
73 |
74 | enum class BleStatus(val code: Int) {
75 | UNKNOWN(code = 0),
76 | UNSUPPORTED(code = 1),
77 | UNAUTHORIZED(code = 2),
78 | POWERED_OFF(code = 3),
79 | LOCATION_SERVICES_DISABLED(code = 4),
80 | READY(code = 5),
81 | }
82 |
83 | enum class ConnectionPriority(val code: Int) {
84 | BALANCED(code = 0),
85 | HIGH_PERFORMACE(code = 1),
86 | LOW_POWER(code = 2),
87 | }
88 |
89 | enum class Connectable(val code: Int) {
90 | UNKNOWN(code = 0),
91 | NOT_CONNECTABLE(code = 1),
92 | CONNECTABLE(code = 2),
93 | }
94 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/ConnectionQueue.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.ble
2 |
3 | import androidx.annotation.VisibleForTesting
4 | import io.reactivex.subjects.BehaviorSubject
5 |
6 | internal class ConnectionQueue {
7 | private val queueSubject = BehaviorSubject.createDefault(listOf())
8 |
9 | fun observeQueue() = queueSubject
10 |
11 | fun addToQueue(deviceId: String) {
12 | if (queueSubject.value?.find { it == deviceId } == null) {
13 | queueSubject.value?.let { currentQueue ->
14 | val newQueue = currentQueue.toMutableList()
15 | newQueue.add(deviceId)
16 | queueSubject.onNext(newQueue)
17 | }
18 | }
19 | }
20 |
21 | @VisibleForTesting
22 | internal fun getCurrentQueue() = queueSubject.value
23 |
24 | fun removeFromQueue(deviceId: String) {
25 | queueSubject.value?.let { currentQueue ->
26 | val newQueue = currentQueue.toMutableList()
27 | newQueue.remove(deviceId)
28 | queueSubject.onNext(newQueue)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/ble/extensions/RxBleConnectionExtension.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.ble.extensions
2 |
3 | import android.bluetooth.BluetoothGattCharacteristic
4 | import com.polidea.rxandroidble2.RxBleConnection
5 | import io.reactivex.Single
6 | import java.util.UUID
7 |
8 | fun RxBleConnection.resolveCharacteristic(
9 | uuid: UUID,
10 | instanceId: Int,
11 | ): Single =
12 | discoverServices().flatMap { services ->
13 | Single.just(
14 | services.bluetoothGattServices.flatMap { service ->
15 | service.characteristics.filter {
16 | it.uuid == uuid && it.instanceId == instanceId
17 | }
18 | }.single(),
19 | )
20 | }
21 |
22 | fun RxBleConnection.writeCharWithResponse(
23 | characteristic: BluetoothGattCharacteristic,
24 | value: ByteArray,
25 | ): Single {
26 | characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
27 | return writeCharacteristic(characteristic, value)
28 | }
29 |
30 | fun RxBleConnection.writeCharWithoutResponse(
31 | characteristic: BluetoothGattCharacteristic,
32 | value: ByteArray,
33 | ): Single {
34 | characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
35 | return writeCharacteristic(characteristic, value)
36 | }
37 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/channelhandlers/BleStatusHandler.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.channelhandlers
2 |
3 | import com.signify.hue.flutterreactiveble.ble.BleClient
4 | import io.flutter.plugin.common.EventChannel
5 | import io.reactivex.Observable
6 | import io.reactivex.android.schedulers.AndroidSchedulers
7 | import io.reactivex.disposables.Disposable
8 | import io.reactivex.disposables.SerialDisposable
9 | import java.util.concurrent.TimeUnit
10 | import com.signify.hue.flutterreactiveble.ProtobufModel as pb
11 |
12 | class BleStatusHandler(private val bleClient: BleClient) : EventChannel.StreamHandler {
13 | companion object {
14 | private const val delayListenBleStatus = 500L
15 | }
16 |
17 | private val subscriptionDisposable = SerialDisposable()
18 |
19 | override fun onListen(
20 | arg: Any?,
21 | eventSink: EventChannel.EventSink?,
22 | ) {
23 | subscriptionDisposable.set(eventSink?.let(::listenToBleStatus))
24 | }
25 |
26 | override fun onCancel(arg: Any?) {
27 | subscriptionDisposable.set(null)
28 | }
29 |
30 | private fun listenToBleStatus(eventSink: EventChannel.EventSink): Disposable =
31 | Observable.timer(delayListenBleStatus, TimeUnit.MILLISECONDS)
32 | .switchMap { bleClient.observeBleStatus() }
33 | .observeOn(AndroidSchedulers.mainThread())
34 | .subscribe({ bleStatus ->
35 | val message =
36 | pb.BleStatusInfo.newBuilder()
37 | .setStatus(bleStatus.code)
38 | .build()
39 | eventSink.success(message.toByteArray())
40 | }, { throwable ->
41 | eventSink.error("ObserveBleStatusFailure", throwable.message, null)
42 | })
43 | }
44 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/channelhandlers/DeviceConnectionHandler.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.channelhandlers
2 |
3 | import com.signify.hue.flutterreactiveble.converters.ProtobufMessageConverter
4 | import com.signify.hue.flutterreactiveble.utils.Duration
5 | import io.flutter.plugin.common.EventChannel
6 | import io.reactivex.android.schedulers.AndroidSchedulers
7 | import io.reactivex.disposables.Disposable
8 | import java.util.concurrent.TimeUnit
9 | import com.signify.hue.flutterreactiveble.ProtobufModel as pb
10 |
11 | class DeviceConnectionHandler(private val bleClient: com.signify.hue.flutterreactiveble.ble.BleClient) : EventChannel.StreamHandler {
12 | private var connectDeviceSink: EventChannel.EventSink? = null
13 | private val converter = ProtobufMessageConverter()
14 |
15 | private lateinit var connectionUpdatesDisposable: Disposable
16 |
17 | override fun onListen(
18 | objectSink: Any?,
19 | eventSink: EventChannel.EventSink?,
20 | ) {
21 | eventSink?.let {
22 | connectDeviceSink = eventSink
23 | connectionUpdatesDisposable = listenToConnectionChanges()
24 | }
25 | }
26 |
27 | override fun onCancel(objectSink: Any?) {
28 | disconnectAll()
29 | connectionUpdatesDisposable.dispose()
30 | }
31 |
32 | fun connectToDevice(connectToDeviceMessage: pb.ConnectToDeviceRequest) {
33 | bleClient.connectToDevice(
34 | connectToDeviceMessage.deviceId,
35 | Duration(connectToDeviceMessage.timeoutInMs.toLong(), TimeUnit.MILLISECONDS),
36 | )
37 | }
38 |
39 | fun disconnectDevice(deviceId: String) {
40 | bleClient.disconnectDevice(deviceId)
41 | }
42 |
43 | fun disconnectAll() {
44 | connectDeviceSink = null
45 | bleClient.disconnectAllDevices()
46 | }
47 |
48 | private fun listenToConnectionChanges() =
49 | bleClient.connectionUpdateSubject
50 | .observeOn(AndroidSchedulers.mainThread())
51 | .subscribe { update ->
52 | when (update) {
53 | is com.signify.hue.flutterreactiveble.ble.ConnectionUpdateSuccess -> {
54 | handleDeviceConnectionUpdateResult(converter.convertToDeviceInfo(update))
55 | }
56 | is com.signify.hue.flutterreactiveble.ble.ConnectionUpdateError -> {
57 | handleDeviceConnectionUpdateResult(
58 | converter.convertConnectionErrorToDeviceInfo(update.deviceId, update.errorMessage),
59 | )
60 | }
61 | }
62 | }
63 |
64 | private fun handleDeviceConnectionUpdateResult(connectionUpdateMessage: pb.DeviceInfo) {
65 | connectDeviceSink?.success(connectionUpdateMessage.toByteArray())
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/channelhandlers/ScanDevicesHandler.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.channelhandlers
2 |
3 | import android.os.ParcelUuid
4 | import com.signify.hue.flutterreactiveble.converters.ProtobufMessageConverter
5 | import com.signify.hue.flutterreactiveble.converters.UuidConverter
6 | import com.signify.hue.flutterreactiveble.model.ScanMode
7 | import com.signify.hue.flutterreactiveble.model.createScanMode
8 | import io.flutter.plugin.common.EventChannel
9 | import io.reactivex.android.schedulers.AndroidSchedulers
10 | import io.reactivex.disposables.Disposable
11 | import com.signify.hue.flutterreactiveble.ProtobufModel as pb
12 |
13 | class ScanDevicesHandler(
14 | private val bleClient: com.signify.hue.flutterreactiveble.ble.BleClient,
15 | ) : EventChannel.StreamHandler {
16 | private var scanDevicesSink: EventChannel.EventSink? = null
17 | private lateinit var scanForDevicesDisposable: Disposable
18 | private val converter = ProtobufMessageConverter()
19 |
20 | companion object {
21 | private var scanParameters: ScanParameters? = null
22 | }
23 |
24 | override fun onListen(
25 | objectSink: Any?,
26 | eventSink: EventChannel.EventSink?,
27 | ) {
28 | eventSink?.let {
29 | scanDevicesSink = eventSink
30 | startDeviceScan()
31 | }
32 | }
33 |
34 | override fun onCancel(objectSink: Any?) {
35 | stopDeviceScan()
36 | scanDevicesSink = null
37 | }
38 |
39 | private fun startDeviceScan() {
40 | scanParameters?.let { params ->
41 | scanForDevicesDisposable =
42 | bleClient.scanForDevices(params.filter, params.mode, params.locationServiceIsMandatory)
43 | .observeOn(AndroidSchedulers.mainThread())
44 | .subscribe(
45 | { scanResult ->
46 | handleDeviceScanResult(converter.convertScanInfo(scanResult))
47 | },
48 | { throwable ->
49 | handleDeviceScanResult(converter.convertScanErrorInfo(throwable.message))
50 | },
51 | )
52 | }
53 | ?: handleDeviceScanResult(converter.convertScanErrorInfo("Scanning parameters are not set"))
54 | }
55 |
56 | fun stopDeviceScan() {
57 | if (this::scanForDevicesDisposable.isInitialized) {
58 | scanForDevicesDisposable.let {
59 | if (!it.isDisposed) {
60 | it.dispose()
61 | scanParameters = null
62 | }
63 | }
64 | }
65 | }
66 |
67 | fun prepareScan(scanMessage: pb.ScanForDevicesRequest) {
68 | stopDeviceScan()
69 | val filter =
70 | scanMessage.serviceUuidsList
71 | .map { ParcelUuid(UuidConverter().uuidFromByteArray(it.data.toByteArray())) }
72 | val scanMode = createScanMode(scanMessage.scanMode)
73 | scanParameters = ScanParameters(filter, scanMode, scanMessage.requireLocationServicesEnabled)
74 | }
75 |
76 | private fun handleDeviceScanResult(discoveryMessage: pb.DeviceScanInfo) {
77 | scanDevicesSink?.success(discoveryMessage.toByteArray())
78 | }
79 | }
80 |
81 | private data class ScanParameters(
82 | val filter: List,
83 | val mode: ScanMode,
84 | val locationServiceIsMandatory: Boolean,
85 | )
86 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/converters/ManufacturerDataConverter.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.converters
2 |
3 | import android.util.SparseArray
4 |
5 | fun extractManufacturerData(manufacturerData: SparseArray?): ByteArray {
6 | val rawData = mutableListOf()
7 |
8 | if (manufacturerData != null && manufacturerData.size() > 0) {
9 | val companyId = manufacturerData.keyAt(0)
10 | val payload = manufacturerData.get(companyId)
11 |
12 | rawData.add((companyId.toByte()))
13 | rawData.add(((companyId.shr(Byte.SIZE_BITS)).toByte()))
14 | rawData.addAll(2, payload.asList())
15 | }
16 |
17 | return rawData.toByteArray()
18 | }
19 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/converters/UuidConverter.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.converters
2 |
3 | import java.nio.ByteBuffer
4 | import java.util.UUID
5 |
6 | class UuidConverter {
7 | companion object {
8 | private const val byteSize16Bit = 2
9 | private const val byteSize32Bit = 4
10 | private const val byteBufferSize = 16
11 | }
12 |
13 | fun uuidFromByteArray(bytes: ByteArray): UUID {
14 | return when (bytes.size) {
15 | byteSize16Bit -> convert16BitToUuid(bytes)
16 | byteSize32Bit -> convert32BitToUuid(bytes)
17 | else -> convert128BitNotationToUuid(bytes)
18 | }
19 | }
20 |
21 | @Suppress("Detekt.MagicNumber")
22 | private fun convert16BitToUuid(bytes: ByteArray): UUID {
23 | // UUID construction is retrieved from BLE corespec v5.0 page 1917
24 | val uuidConstruct =
25 | byteArrayOf(
26 | 0x00,
27 | 0x00,
28 | bytes[0],
29 | bytes[1],
30 | 0x00,
31 | 0x00,
32 | 0x10,
33 | 0x00,
34 | 0x80.toByte(),
35 | 0x00,
36 | 0x00,
37 | 0x80.toByte(),
38 | 0x5F,
39 | 0x9B.toByte(),
40 | 0x34,
41 | 0xFB.toByte(),
42 | )
43 |
44 | return convert128BitNotationToUuid(uuidConstruct)
45 | }
46 |
47 | @Suppress("Detekt.MagicNumber")
48 | private fun convert32BitToUuid(bytes: ByteArray): UUID {
49 | val uuidConstruct =
50 | byteArrayOf(
51 | bytes[0],
52 | bytes[1],
53 | bytes[2],
54 | bytes[3],
55 | 0x00,
56 | 0x00,
57 | 0x10,
58 | 0x00,
59 | 0x80.toByte(),
60 | 0x00,
61 | 0x00,
62 | 0x80.toByte(),
63 | 0x5F,
64 | 0x9B.toByte(),
65 | 0x34,
66 | 0xFB.toByte(),
67 | )
68 |
69 | return convert128BitNotationToUuid(uuidConstruct)
70 | }
71 |
72 | private fun convert128BitNotationToUuid(bytes: ByteArray): UUID {
73 | val bb = ByteBuffer.wrap(bytes)
74 | val most = bb.long
75 | val least = bb.long
76 | return UUID(most, least)
77 | }
78 |
79 | fun byteArrayFromUuid(uuid: UUID): ByteArray {
80 | val bb = ByteBuffer.wrap(ByteArray(byteBufferSize))
81 | bb.putLong(uuid.mostSignificantBits)
82 | bb.putLong(uuid.leastSignificantBits)
83 |
84 | return bb.array()
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/debugutils/HexStringConversion.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.debugutils
2 |
3 | fun ByteArray.toHexString() = joinToString("") { String.format("%02x", it) }
4 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/debugutils/PerformanceAnalyzer.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.debugutils
2 |
3 | object PerformanceAnalyzer {
4 | var timer = Pair(0, 0)
5 |
6 | fun start(startTime: Long) {
7 | timer = timer.copy(first = startTime)
8 | }
9 |
10 | fun end(endTime: Long) {
11 | timer = timer.copy(second = endTime)
12 | }
13 |
14 | fun timeElapsed(): Long = timer.second - timer.first
15 | }
16 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/model/ConnectionState.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.model
2 |
3 | import com.polidea.rxandroidble2.RxBleConnection
4 |
5 | @Suppress("Detekt.MagicNumber")
6 | enum class ConnectionState(val code: Int) {
7 | CONNECTING(0),
8 | CONNECTED(1),
9 | DISCONNECTING(2),
10 | DISCONNECTED(3),
11 | UNKNOWN(4),
12 | }
13 |
14 | fun RxBleConnection.RxBleConnectionState.toConnectionState(): ConnectionState =
15 | when (this.name) {
16 | "DISCONNECTED" -> ConnectionState.DISCONNECTED
17 | "CONNECTING" -> ConnectionState.CONNECTING
18 | "CONNECTED" -> ConnectionState.CONNECTED
19 | "DISCONNECTING" -> ConnectionState.DISCONNECTING
20 | else -> ConnectionState.UNKNOWN
21 | }
22 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/model/ErrorType.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.model
2 |
3 | enum class ConnectionErrorType(val code: Int) {
4 | UNKNOWN(0),
5 | FAILEDTOCONNECT(1),
6 | }
7 |
8 | enum class ClearGattCacheErrorType(val code: Int) {
9 | UNKNOWN(0),
10 | }
11 |
12 | enum class CharacteristicErrorType(val code: Int) {
13 | UNKNOWN(0),
14 | }
15 |
16 | enum class NegotiateMtuErrorType(val code: Int) {
17 | UNKNOWN(0),
18 | }
19 |
20 | enum class ScanErrorType(val code: Int) {
21 | UNKNOWN(0),
22 | }
23 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/model/ScanMode.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.model
2 |
3 | import android.bluetooth.le.ScanSettings
4 |
5 | enum class ScanMode(val code: Int) {
6 | OPPORTUNISTIC(-1),
7 | LOW_POWER(0),
8 | BALANCED(1),
9 | LOW_LATENCY(2),
10 | }
11 |
12 | internal fun ScanMode.toScanSettings(): Int =
13 | when (this) {
14 | ScanMode.OPPORTUNISTIC -> ScanSettings.SCAN_MODE_OPPORTUNISTIC
15 | ScanMode.LOW_POWER -> ScanSettings.SCAN_MODE_LOW_POWER
16 | ScanMode.BALANCED -> ScanSettings.SCAN_MODE_BALANCED
17 | ScanMode.LOW_LATENCY -> ScanSettings.SCAN_MODE_LOW_LATENCY
18 | }
19 |
20 | internal fun createScanMode(mode: Int): ScanMode =
21 | when (mode) {
22 | -1 -> ScanMode.OPPORTUNISTIC
23 | 0 -> ScanMode.LOW_POWER
24 | 1 -> ScanMode.BALANCED
25 | 2 -> ScanMode.LOW_LATENCY
26 | else -> ScanMode.LOW_POWER
27 | }
28 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/utils/BleWrapperExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.utils
2 |
3 | import com.polidea.rxandroidble2.RxBleClient
4 | import com.polidea.rxandroidble2.RxBleClient.State.BLUETOOTH_NOT_AVAILABLE
5 | import com.polidea.rxandroidble2.RxBleClient.State.BLUETOOTH_NOT_ENABLED
6 | import com.polidea.rxandroidble2.RxBleClient.State.LOCATION_PERMISSION_NOT_GRANTED
7 | import com.polidea.rxandroidble2.RxBleClient.State.LOCATION_SERVICES_NOT_ENABLED
8 | import com.polidea.rxandroidble2.RxBleClient.State.READY
9 | import com.signify.hue.flutterreactiveble.ble.BleStatus
10 | import com.signify.hue.flutterreactiveble.ble.ConnectionPriority
11 |
12 | fun RxBleClient.State.toBleState(): BleStatus =
13 | when (this) {
14 | BLUETOOTH_NOT_AVAILABLE -> BleStatus.UNSUPPORTED
15 | LOCATION_PERMISSION_NOT_GRANTED -> BleStatus.UNAUTHORIZED
16 | BLUETOOTH_NOT_ENABLED -> BleStatus.POWERED_OFF
17 | LOCATION_SERVICES_NOT_ENABLED -> BleStatus.LOCATION_SERVICES_DISABLED
18 | READY -> BleStatus.READY
19 | }
20 |
21 | fun Int.toConnectionPriority() =
22 | when (this) {
23 | 0 -> ConnectionPriority.BALANCED
24 | 1 -> ConnectionPriority.HIGH_PERFORMACE
25 | 2 -> ConnectionPriority.LOW_POWER
26 | else -> ConnectionPriority.BALANCED
27 | }
28 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/utils/Discard.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.utils
2 |
3 | @Suppress("unused")
4 | fun Any?.discard() = Unit
5 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/main/kotlin/com/signify/hue/flutterreactiveble/utils/Duration.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.utils
2 |
3 | import java.util.concurrent.TimeUnit
4 |
5 | data class Duration(val value: Long, val unit: TimeUnit)
6 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/test/kotlin/com/signify/hue/flutterreactiveble/channelhandlers/ConnectionQueueTest.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.channelhandlers
2 | import com.google.common.truth.Truth.assertThat
3 | import io.reactivex.subjects.BehaviorSubject
4 | import org.junit.jupiter.api.BeforeEach
5 | import org.junit.jupiter.api.Test
6 |
7 | class ConnectionQueueTest {
8 | private lateinit var sut: com.signify.hue.flutterreactiveble.ble.ConnectionQueue
9 |
10 | @BeforeEach
11 | fun setup() {
12 | sut = com.signify.hue.flutterreactiveble.ble.ConnectionQueue()
13 | }
14 |
15 | @Test
16 | fun `should be able to add an item to the queue`() {
17 | sut.addToQueue("test")
18 | assertThat(sut.getCurrentQueue()?.size).isEqualTo(1)
19 | }
20 |
21 | @Test
22 | fun `should not be able to add an item to the queue twice`() {
23 | sut.addToQueue("test")
24 | sut.addToQueue("test")
25 | assertThat(sut.getCurrentQueue()?.size).isEqualTo(1)
26 | }
27 |
28 | @Test
29 | fun `should be able to see all devices in the queue`() {
30 | sut.addToQueue("device1")
31 | sut.addToQueue("device2")
32 | sut.addToQueue("device3")
33 | assertThat(sut.getCurrentQueue()?.size).isEqualTo(3)
34 | }
35 |
36 | @Test
37 | fun `should be able to remove an item to the queue`() {
38 | sut.addToQueue("test")
39 | sut.removeFromQueue("test")
40 | assertThat(sut.getCurrentQueue()?.size).isEqualTo(0)
41 | }
42 |
43 | @Test
44 | fun `should provide behavior subject for observing latest queue state`() {
45 | assertThat(sut.observeQueue()).isInstanceOf(BehaviorSubject::class.java)
46 | }
47 |
48 | @Test
49 | fun `should return an emptylist when queue is created`() {
50 | assertThat(sut.observeQueue().test().values().first().size).isEqualTo(0)
51 | }
52 |
53 | @Test
54 | fun `should provide me new queue each time something changed`() {
55 | val observable = sut.observeQueue().test()
56 | sut.addToQueue("test")
57 |
58 | assertThat(observable.valueCount()).isEqualTo(2)
59 | }
60 |
61 | @Test
62 | fun `should be ordened in the sequence of adding`() {
63 | val expectedQueue = listOf("test1", "test2")
64 | val observable = sut.observeQueue().test()
65 | sut.addToQueue("test1")
66 | sut.addToQueue("test2")
67 |
68 | val lastQueue = observable.values().last()
69 | assertThat(lastQueue).isEqualTo(expectedQueue)
70 | }
71 |
72 | @Test
73 | fun `should update queue after removal`() {
74 | val observable = sut.observeQueue().test()
75 | sut.addToQueue("test1")
76 | sut.addToQueue("test2")
77 | sut.removeFromQueue("test2")
78 |
79 | assertThat(observable.valueCount()).isEqualTo(4)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/test/kotlin/com/signify/hue/flutterreactiveble/converters/ServicesWithCharacteristicsConverterTest.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.converters
2 |
3 | import android.bluetooth.BluetoothGattCharacteristic
4 | import android.bluetooth.BluetoothGattService
5 | import com.google.common.truth.Truth.assertThat
6 | import com.polidea.rxandroidble2.RxBleDeviceServices
7 | import io.mockk.MockKAnnotations
8 | import io.mockk.every
9 | import io.mockk.impl.annotations.MockK
10 | import org.junit.Before
11 | import org.junit.Test
12 | import java.util.UUID
13 | import com.signify.hue.flutterreactiveble.ProtobufModel as pb
14 |
15 | class ServicesWithCharacteristicsConverterTest {
16 | private val serviceUuid = UUID.randomUUID()
17 | private val characteristicUuid = UUID.randomUUID()
18 | private val internalCharacteristicUuid = UUID.randomUUID()
19 | private val internalServiceUuid = UUID.randomUUID()
20 | private val internalCharacteristicUuidLevel2 = UUID.randomUUID()
21 | private val internalServiceUuidLevel2 = UUID.randomUUID()
22 | private val sut = ProtobufMessageConverter()
23 | private lateinit var conversionResult: pb.DiscoverServicesInfo
24 |
25 | @MockK
26 | lateinit var service: BluetoothGattService
27 |
28 | @MockK
29 | lateinit var characteristic: BluetoothGattCharacteristic
30 |
31 | @MockK
32 | lateinit var internalService: BluetoothGattService
33 |
34 | @MockK
35 | lateinit var internalCharacteristic: BluetoothGattCharacteristic
36 |
37 | @MockK
38 | lateinit var internalServiceLevel2: BluetoothGattService
39 |
40 | @MockK
41 | lateinit var internalCharacteristicLevel2: BluetoothGattCharacteristic
42 |
43 | @Before
44 | fun setUp() {
45 | MockKAnnotations.init(this)
46 | every { service.uuid }.returns(serviceUuid)
47 | every { characteristic.uuid }.returns(characteristicUuid)
48 | every { service.includedServices }.returns(listOf(internalService))
49 | every { service.characteristics }.returns(listOf(characteristic))
50 |
51 | every { internalCharacteristic.uuid }.returns(internalCharacteristicUuid)
52 | every { internalService.uuid }.returns(internalServiceUuid)
53 | every { internalService.includedServices }.returns(listOf(internalServiceLevel2, internalServiceLevel2))
54 | every { internalService.characteristics }.returns(listOf(internalCharacteristic))
55 |
56 | every { internalCharacteristicLevel2.uuid }.returns(internalCharacteristicUuidLevel2)
57 | every { internalServiceLevel2.uuid }.returns(internalServiceUuidLevel2)
58 | every { internalServiceLevel2.includedServices }.returns(listOf())
59 | every { internalServiceLevel2.characteristics }.returns(listOf(internalCharacteristicLevel2))
60 |
61 | conversionResult =
62 | sut.convertDiscoverServicesInfo(
63 | "test",
64 | RxBleDeviceServices(listOf(service)),
65 | )
66 | }
67 |
68 | @Test
69 | fun `It converts total services`() {
70 | assertThat(conversionResult.servicesCount).isEqualTo(1)
71 | }
72 |
73 | @Test
74 | fun `It converts characteristic uuid`() {
75 | assertThat(conversionResult.getServices(0).characteristicUuidsCount).isEqualTo(1)
76 | }
77 |
78 | @Test
79 | fun `It converts nested internal services correctly`() {
80 | assertThat(
81 | conversionResult.getServices(0).getIncludedServices(0)
82 | .includedServicesCount,
83 | ).isEqualTo(2)
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/android/src/test/kotlin/com/signify/hue/flutterreactiveble/converters/UuidConverterTest.kt:
--------------------------------------------------------------------------------
1 | package com.signify.hue.flutterreactiveble.converters
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import org.junit.jupiter.api.Test
5 | import java.util.UUID
6 |
7 | class UuidConverterTest {
8 | val converter = UuidConverter()
9 |
10 | @Test
11 | fun `should convert uuid into bytearray`() {
12 | val uuid = UUID.randomUUID()
13 | val byteArray = converter.byteArrayFromUuid(uuid)
14 | val uuid2 = converter.uuidFromByteArray(byteArray)
15 |
16 | assertThat(uuid2).isEqualTo(uuid)
17 | }
18 |
19 | @Test
20 | fun `should be able to convert 16bit uuid`() {
21 | val array = byteArrayOf(0xFE.toByte(), 0x0F.toByte())
22 | val uuid = converter.uuidFromByteArray(array)
23 | assertThat(uuid.toString().toUpperCase()).isEqualTo("0000FE0F-0000-1000-8000-00805F9B34FB")
24 | }
25 |
26 | @Test
27 | fun `should be able to convert 32bit uuid`() {
28 | val array = byteArrayOf(0xFE.toByte(), 0x0F.toByte(), 0x0F.toByte(), 0xFE.toByte())
29 | val uuid = converter.uuidFromByteArray(array)
30 | assertThat(uuid.toString().toUpperCase()).isEqualTo("FE0F0FFE-0000-1000-8000-00805F9B34FB")
31 | }
32 |
33 | @Test
34 | fun `should convert bytearray into uuid`() {
35 | val uuid = UUID.randomUUID()
36 |
37 | val byteArray = converter.byteArrayFromUuid(uuid)
38 | assertThat(converter.uuidFromByteArray(byteArray).toString()).isEqualTo(uuid.toString())
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | .generated/
16 |
17 | *.pbxuser
18 | *.mode1v3
19 | *.mode2v3
20 | *.perspectivev3
21 |
22 | !default.pbxuser
23 | !default.mode1v3
24 | !default.mode2v3
25 | !default.perspectivev3
26 |
27 | xcuserdata
28 |
29 | *.moved-aside
30 |
31 | *.pyc
32 | *sync/
33 | Icon?
34 | .tags*
35 |
36 | /Flutter/Generated.xcconfig
37 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilipsHue/flutter_reactive_ble/fab66c21ef8bc28402cf9715e630fd88c910ad97/packages/reactive_ble_mobile/darwin/Assets/.gitkeep
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/BleData extras/BLEStatus.swift:
--------------------------------------------------------------------------------
1 | import enum CoreBluetooth.CBManagerState
2 |
3 | func encode(_ centralState: CBManagerState) -> Int32 {
4 | switch centralState {
5 | case .unknown, .resetting:
6 | return 0
7 | case .unsupported:
8 | return 1
9 | case .unauthorized:
10 | return 2
11 | case .poweredOff:
12 | return 3
13 | case .poweredOn:
14 | return 5
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/BleData extras/ConnectionState.swift:
--------------------------------------------------------------------------------
1 | import enum CoreBluetooth.CBPeripheralState
2 |
3 | func encode(_ connectionState: CBPeripheralState) -> Int32 {
4 | switch connectionState {
5 | case .disconnected:
6 | return 3
7 | case .connecting:
8 | return 0
9 | case .connected:
10 | return 1
11 | case .disconnecting:
12 | return 2
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/BleData extras/FailureCodes.swift:
--------------------------------------------------------------------------------
1 | enum ConnectionFailure: Int {
2 |
3 | case unknown
4 | case failedToConnect
5 | }
6 |
7 | enum ClearGattCacheFailure: Int {
8 |
9 | case operationNotSupported = 1
10 | }
11 |
12 | enum CharacteristicValueUpdateFailure: Int {
13 |
14 | case unknown
15 | }
16 |
17 | enum WriteCharacteristicFailure: Int {
18 |
19 | case unknown
20 | }
21 |
22 | enum MaximumWriteValueLengthRetrieval: Int {
23 |
24 | case unknown
25 | }
26 |
27 | enum RequestConnectionPriorityFailure: Int {
28 |
29 | case operationNotSupported = 1
30 | }
31 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/Plugin/Common/EventSink.swift:
--------------------------------------------------------------------------------
1 | import enum SwiftProtobuf.BinaryEncodingError
2 |
3 | struct EventSink {
4 |
5 | private let name: String
6 | private let sink: FlutterEventSink
7 |
8 | init(name: String, _ sink: @escaping FlutterEventSink) {
9 | self.name = name
10 | self.sink = sink
11 | }
12 |
13 | func add(_ event: PlatformMethodResult) {
14 | switch event {
15 | case .success(let message):
16 | if let message = message {
17 | do {
18 | sink(FlutterStandardTypedData(bytes: try message.serializedData()))
19 | } catch let error as BinaryEncodingError {
20 | sink(PluginError.messageSerializationFailure(type: type(of: message), underlyingError: error).asFlutterError)
21 | } catch {
22 | sink(PluginError.unknown(error).asFlutterError)
23 | }
24 | } else {
25 | sink(nil)
26 | }
27 | case .failure(let error):
28 | sink(error)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/Plugin/Common/Failable.swift:
--------------------------------------------------------------------------------
1 | enum Failable {
2 |
3 | case success(T)
4 | case failure(Error)
5 |
6 | var value: T? { return iif(success: id, failure: const(nil)) }
7 | var error: Error? { return iif(success: const(nil), failure: id) }
8 |
9 | func iif(success: (T) -> U, failure: (Error) -> U) -> U {
10 | switch self {
11 | case .success(let value):
12 | return success(value)
13 | case .failure(let error):
14 | return failure(error)
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/Plugin/Common/MethodHandler.swift:
--------------------------------------------------------------------------------
1 | final class MethodHandler {
2 |
3 | private let methods: [String: AnyPlatformMethod]
4 |
5 | init(_ methods: [AnyPlatformMethod]) {
6 | self.methods = methods.reduce(into: [:], { $0[$1.name] = $1 })
7 | }
8 |
9 | func handle(in context: Context, _ call: FlutterMethodCall, completion: @escaping FlutterResult) {
10 | guard let method = methods[call.method]
11 | else {
12 | completion(PluginError.unsupportedMethodCall(method: call.method).asFlutterError)
13 | return
14 | }
15 |
16 | method.call(in: context, arguments: call.arguments, completion: completion)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/Plugin/Common/StreamHandler.swift:
--------------------------------------------------------------------------------
1 | final class StreamHandler: NSObject, FlutterStreamHandler {
2 |
3 | typealias OnListenHandler = (Context, EventSink) -> FlutterError?
4 | typealias OnCancelHandler = (Context) -> FlutterError?
5 |
6 | private let name: String
7 | private weak var context: Context?
8 | private let onListenBody: OnListenHandler
9 | private let onCancelBody: OnCancelHandler
10 |
11 | init(name: String, context: Context, onListen: @escaping OnListenHandler, onCancel: @escaping OnCancelHandler) {
12 | self.name = name
13 | self.context = context
14 | self.onListenBody = onListen
15 | self.onCancelBody = onCancel
16 | super.init()
17 | }
18 |
19 | func onListen(withArguments args: Any?, eventSink: @escaping FlutterEventSink) -> FlutterError? {
20 | assert(args == nil)
21 |
22 | guard let context = context
23 | else {
24 | return PluginError.internalInconcictency(
25 | details: "\(StreamHandler.self)(name: \"\(name)\").\(#function) is called when the context is destroyed"
26 | )
27 | .asFlutterError
28 | }
29 |
30 | return onListenBody(context, EventSink(name: name, eventSink))
31 | }
32 |
33 | func onCancel(withArguments args: Any?) -> FlutterError? {
34 | assert(args == nil)
35 |
36 | guard let context = context
37 | else {
38 | return PluginError.internalInconcictency(
39 | details: "\(StreamHandler.self)(name: \"\(name)\").\(#function) is called when the context is destroyed"
40 | )
41 | .asFlutterError
42 | }
43 |
44 | return onCancelBody(context)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/Plugin/PluginError.swift:
--------------------------------------------------------------------------------
1 | import protocol SwiftProtobuf.Message
2 |
3 | enum PluginError: Error {
4 |
5 | case unknown(Error)
6 |
7 | case internalInconcictency(details: String?)
8 |
9 | case unsupportedMethodCall(method: String)
10 | case invalidMethodCall(method: String, details: String?)
11 |
12 | case messageDeserializationFailure(type: Message.Type, underlyingError: Error)
13 | case messageSerializationFailure(type: Message.Type, underlyingError: Error)
14 |
15 | case notInitialized
16 |
17 | case connectionLost
18 |
19 | var asFlutterError: FlutterError {
20 | switch self {
21 | case .unknown(let error as NSError):
22 | return makeFlutterError(code: "\(error.domain):\(error.code)", message: error.localizedDescription, details: error.userInfo)
23 | case .internalInconcictency(let details):
24 | let extra = details.map { " (\($0))" } ?? ""
25 | return makeFlutterError(message: "internal inconsistency" + extra)
26 | case .unsupportedMethodCall(let method):
27 | return makeFlutterError(message: "the method \"\(method)\" is not supported")
28 | case .invalidMethodCall(let method, let details):
29 | let extra = details.map { " (\($0))" } ?? ""
30 | return makeFlutterError(message: "invalid \"\(method)\" method call" + extra)
31 | case .messageDeserializationFailure(let type, let underlyingError):
32 | return makeFlutterError(message: "failed to deserialize a message of type \(type) (\(underlyingError))")
33 | case .messageSerializationFailure(let type, let underlyingError):
34 | return makeFlutterError(message: "failed to serialize a message of type \(type) (\(underlyingError))")
35 | case .notInitialized:
36 | return makeFlutterError(message: "not initialized")
37 | case .connectionLost:
38 | return makeFlutterError(message: "connection lost")
39 | }
40 | }
41 |
42 | private func makeFlutterError(code: String? = nil, message: String?, details: Any? = nil) -> FlutterError {
43 | return FlutterError(code: code ?? "\(self)", message: message, details: details)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/Plugin/StreamingTask.swift:
--------------------------------------------------------------------------------
1 | struct StreamingTask {
2 | let parameters: T
3 | let sink: EventSink?
4 |
5 | init(parameters: T) {
6 | self.parameters = parameters
7 | self.sink = nil
8 | }
9 |
10 | private init(parameters: T, sink: EventSink?) {
11 | self.parameters = parameters
12 | self.sink = sink
13 | }
14 |
15 | func with(sink: EventSink?) -> StreamingTask {
16 | return StreamingTask(parameters: parameters, sink: sink)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/Prelude/CoreBluetooth+Extensions.swift:
--------------------------------------------------------------------------------
1 | #if compiler(<5.5)
2 | import class CoreBluetooth.CBCharacteristic
3 | import class CoreBluetooth.CBService
4 | import class CoreBluetooth.CBPeripheral
5 |
6 | // Extensions below are supposed to backport API breaking changes
7 | // in CoreBluetooth framework which Apple introduced with Xcode 13:
8 | //
9 | // ```
10 | // + weak var peripheral: CBPeripheral? { get }
11 | // - unowned(unsafe) var peripheral: CBPeripheral { get }
12 | // ```
13 | //
14 | // Thus this code shadows original property declarations in CoreBluetooth
15 | // and changes their semantics from `unsafe non-optional` to `weak optional`
16 | // to mimic Xcode 13 behavior.
17 | //
18 | // - Note: This code compiles only when using Xcode 12 and below.
19 | // - SeeAlso: https://forums.swift.org/t/is-unowned-unsafe-t-weak-t-a-breaking-change/49917
20 |
21 | extension CBCharacteristic {
22 | @nonobjc
23 | weak var service: CBService? {
24 | return value(forKey: #function) as? CBService
25 | }
26 | }
27 |
28 | extension CBService {
29 | @nonobjc
30 | weak var peripheral: CBPeripheral? {
31 | return value(forKey: #function) as? CBPeripheral
32 | }
33 | }
34 | #endif
35 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/Prelude/base.swift:
--------------------------------------------------------------------------------
1 | func id(_ some: T) -> T {
2 | return some
3 | }
4 |
5 | func const(_ value: U) -> (T) -> U {
6 | return { (_: T) -> U in value }
7 | }
8 |
9 | func const(_ value: U) -> () -> U {
10 | return { () -> U in value }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/Prelude/papply.swift:
--------------------------------------------------------------------------------
1 | func papply(weak arg1: T1, _ f: @escaping (T1, T2, T3, T4, T5) -> Void) -> (T2, T3, T4, T5) -> Void {
2 | return { [weak arg1] (arg2: T2, arg3: T3, arg4: T4, arg5: T5) -> Void in
3 | guard let arg1 = arg1
4 | else { return }
5 |
6 | f(arg1, arg2, arg3, arg4, arg5)
7 | }
8 | }
9 |
10 | func papply(weak arg1: T1, _ f: @escaping (T1, T2, T3, T4) -> Void) -> (T2, T3, T4) -> Void {
11 | return { [weak arg1] (arg2: T2, arg3: T3, arg4: T4) -> Void in
12 | guard let arg1 = arg1
13 | else { return }
14 |
15 | f(arg1, arg2, arg3, arg4)
16 | }
17 | }
18 |
19 | func papply(weak arg1: T1, _ f: @escaping (T1, T2, T3) -> Void) -> (T2, T3) -> Void {
20 | return { [weak arg1] (arg2: T2, arg3: T3) -> Void in
21 | guard let arg1 = arg1
22 | else { return }
23 |
24 | f(arg1, arg2, arg3)
25 | }
26 | }
27 |
28 | func papply(weak arg1: T1, _ f: @escaping (T1, T2) -> Void) -> (T2) -> Void {
29 | return { [weak arg1] (arg2: T2) -> Void in
30 | guard let arg1 = arg1
31 | else { return }
32 |
33 | f(arg1, arg2)
34 | }
35 | }
36 |
37 | func papply(weak arg1: T1, _ f: @escaping (T1) -> Void) -> () -> Void {
38 | return { [weak arg1] () -> Void in
39 | guard let arg1 = arg1
40 | else { return }
41 |
42 | f(arg1)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/CentralManagerDelegate.swift:
--------------------------------------------------------------------------------
1 | import CoreBluetooth
2 |
3 | enum ConnectionChange {
4 | case connected
5 | case failedToConnect(Error?)
6 | case disconnected(Error?)
7 | }
8 |
9 | final class CentralManagerDelegate: NSObject, CBCentralManagerDelegate {
10 |
11 | typealias StateChangeHandler = (CBManagerState) -> Void
12 | typealias DiscoveryHandler = (CBPeripheral, AdvertisementData, RSSI) -> Void
13 | typealias ConnectionChangeHandler = (CBPeripheral, ConnectionChange) -> Void
14 |
15 | private let onStateChange: StateChangeHandler
16 | private let onDiscovery: DiscoveryHandler
17 | private let onConnectionChange: ConnectionChangeHandler
18 |
19 | init(
20 | onStateChange: @escaping StateChangeHandler,
21 | onDiscovery: @escaping DiscoveryHandler,
22 | onConnectionChange: @escaping ConnectionChangeHandler
23 | ) {
24 | self.onStateChange = onStateChange
25 | self.onDiscovery = onDiscovery
26 | self.onConnectionChange = onConnectionChange
27 | }
28 |
29 | func centralManagerDidUpdateState(_ central: CBCentralManager) {
30 | onStateChange(central.state)
31 | }
32 |
33 | func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi: NSNumber) {
34 | onDiscovery(peripheral, advertisementData, rssi.intValue)
35 | }
36 |
37 | func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
38 | onConnectionChange(peripheral, .connected)
39 | }
40 |
41 | func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
42 | onConnectionChange(peripheral, .failedToConnect(error))
43 | }
44 |
45 | func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
46 | onConnectionChange(peripheral, .disconnected(error))
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/OnOff.swift:
--------------------------------------------------------------------------------
1 | enum OnOff {
2 |
3 | case on
4 | case off
5 |
6 | var isOn: Bool {
7 | switch self {
8 | case .on:
9 | return true
10 | case .off:
11 | return false
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/PeripheralDelegate.swift:
--------------------------------------------------------------------------------
1 | import CoreBluetooth
2 |
3 | final class PeripheralDelegate: NSObject, CBPeripheralDelegate {
4 |
5 | typealias ServicesDiscoveryHandler = (CBPeripheral, Error?) -> Void
6 | typealias CharacteristicsDiscoverHandler = (CBService, Error?) -> Void
7 | typealias CharacteristicNotificationStateUpdateHandler = (CBCharacteristic, Error?) -> Void
8 | typealias CharacteristicValueUpdateHandler = (CBCharacteristic, Error?) -> Void
9 | typealias CharacteristicValueWriteHandler = (CBCharacteristic, Error?) -> Void
10 | typealias ReadRssiHandler = (CBPeripheral, Int, Error?) -> Void
11 |
12 | private let onServicesDiscovery: ServicesDiscoveryHandler
13 | private let onCharacteristicsDiscovery: CharacteristicsDiscoverHandler
14 | private let onCharacteristicNotificationStateUpdate: CharacteristicNotificationStateUpdateHandler
15 | private let onCharacteristicValueUpdate: CharacteristicValueUpdateHandler
16 | private let onCharacteristicValueWrite: CharacteristicValueWriteHandler
17 | private let onReadRssi: ReadRssiHandler
18 |
19 | init(
20 | onServicesDiscovery: @escaping ServicesDiscoveryHandler,
21 | onCharacteristicsDiscovery: @escaping CharacteristicsDiscoverHandler,
22 | onCharacteristicNotificationStateUpdate: @escaping CharacteristicNotificationStateUpdateHandler,
23 | onCharacteristicValueUpdate: @escaping CharacteristicValueUpdateHandler,
24 | onCharacteristicValueWrite: @escaping CharacteristicValueWriteHandler,
25 | onReadRssi: @escaping ReadRssiHandler
26 | ) {
27 | self.onServicesDiscovery = onServicesDiscovery
28 | self.onCharacteristicsDiscovery = onCharacteristicsDiscovery
29 | self.onCharacteristicNotificationStateUpdate = onCharacteristicNotificationStateUpdate
30 | self.onCharacteristicValueUpdate = onCharacteristicValueUpdate
31 | self.onCharacteristicValueWrite = onCharacteristicValueWrite
32 | self.onReadRssi = onReadRssi
33 | }
34 |
35 | func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
36 | onServicesDiscovery(peripheral, error)
37 | }
38 |
39 | func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
40 | onCharacteristicsDiscovery(service, error)
41 | }
42 |
43 | func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
44 | onCharacteristicNotificationStateUpdate(characteristic, error)
45 | }
46 |
47 | func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
48 | onCharacteristicValueUpdate(characteristic, error)
49 | }
50 |
51 | func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
52 | onCharacteristicValueWrite(characteristic, error)
53 | }
54 |
55 | func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
56 | onReadRssi(peripheral, RSSI.intValue, error)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/CharacteristicNotify/CharacteristicNotifyTaskController.swift:
--------------------------------------------------------------------------------
1 | import CoreBluetooth
2 |
3 | struct CharacteristicNotifyTaskController: PeripheralTaskController {
4 |
5 | typealias TaskSpec = CharacteristicNotifyTaskSpec
6 |
7 | private let task: SubjectTask
8 |
9 | init(_ task: SubjectTask) {
10 | self.task = task
11 | }
12 |
13 | func start(characteristic: CBCharacteristic) -> SubjectTask {
14 | guard let peripheral = characteristic.service?.peripheral
15 | else { return task.with(state: task.state.finished(CharacteristicNotifyError.unExpected)) }
16 |
17 | peripheral.setNotifyValue(task.params.state.isOn, for: characteristic)
18 | return task.with(state: task.state.processing(.applying))
19 | }
20 |
21 | func cancel(error: Error) -> SubjectTask {
22 | return task.with(state: task.state.finished(error))
23 | }
24 |
25 | func complete(error: Error?) -> SubjectTask {
26 | return task.with(state: task.state.finished(error))
27 | }
28 |
29 | private enum CharacteristicNotifyError: Error {
30 | case unExpected
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/CharacteristicNotify/CharacteristicNotifyTaskSpec.swift:
--------------------------------------------------------------------------------
1 | struct CharacteristicNotifyTaskSpec: PeripheralTaskSpec {
2 |
3 | typealias Key = CharacteristicInstance
4 |
5 | struct Params {
6 |
7 | let state: OnOff
8 | }
9 |
10 | enum Stage {
11 |
12 | case applying
13 | }
14 |
15 | typealias Result = Error?
16 |
17 | static let tag = "NOTIFY"
18 |
19 | static func isMember(_ key: Key, of group: PeripheralID) -> Bool {
20 | return key.peripheralID == group
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/CharacteristicWrite/CharacteristicWriteTaskController.swift:
--------------------------------------------------------------------------------
1 | import CoreBluetooth
2 |
3 | struct CharacteristicWriteTaskController: PeripheralTaskController {
4 |
5 | typealias TaskSpec = CharacteristicWriteTaskSpec
6 |
7 | private let task: SubjectTask
8 |
9 | init(_ task: SubjectTask) {
10 | self.task = task
11 | }
12 |
13 | func start(peripheral: CBPeripheral) -> SubjectTask {
14 | guard
15 | peripheral.state == .connected,
16 | let service = peripheral.services?.filter({ $0.uuid == task.key.serviceID })[Int(task.key.serviceInstanceID) ?? 0],
17 | let characteristic = service.characteristics?.filter({ $0.uuid == task.key.id })[Int(task.key.instanceID) ?? 0],
18 | characteristic.properties.contains(.write)
19 | else {
20 | return task.with(state: task.state.finished(PluginError.internalInconcictency(details: nil)))
21 | }
22 |
23 | peripheral.writeValue(task.params.value, for: characteristic, type: .withResponse)
24 |
25 | return task.with(state: task.state.processing(.writing))
26 | }
27 |
28 | func cancel(error: Error) -> SubjectTask {
29 | return task.with(state: task.state.finished(error))
30 | }
31 |
32 | func handleWrite(error: Error?) -> SubjectTask {
33 | return task.with(state: task.state.finished(error))
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/CharacteristicWrite/CharacteristicWriteTaskSpec.swift:
--------------------------------------------------------------------------------
1 | struct CharacteristicWriteTaskSpec: PeripheralTaskSpec {
2 |
3 | typealias Key = CharacteristicInstance
4 |
5 | struct Params {
6 |
7 | let value: Data
8 | }
9 |
10 | enum Stage {
11 |
12 | case writing
13 | }
14 |
15 | typealias Result = Error?
16 |
17 | static let tag = "WRITE"
18 |
19 | static func isMember(_ key: Key, of group: PeripheralID) -> Bool {
20 | return key.peripheralID == group
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/Connect/ConnectTaskController.swift:
--------------------------------------------------------------------------------
1 | import CoreBluetooth
2 |
3 | struct ConnectTaskController: PeripheralTaskController {
4 |
5 | typealias TaskSpec = ConnectTaskSpec
6 |
7 | private let task: SubjectTask
8 |
9 | init(_ task: SubjectTask) {
10 | self.task = task
11 | }
12 |
13 | func connect(centralManager: CBCentralManager, peripheral: CBPeripheral) -> SubjectTask {
14 | guard case .pending = task.state
15 | else {
16 | assert(false)
17 | return task
18 | }
19 |
20 | centralManager.connect(peripheral)
21 |
22 | return task.with(state: task.state.processing(.connecting))
23 | }
24 |
25 | func handleConnectionChange(_ connectionChange: ConnectionChange) -> SubjectTask {
26 | guard case .processing(since: _, .connecting) = task.state
27 | else {
28 | assert(false)
29 | return task
30 | }
31 |
32 | return task.with(state: task.state.finished(connectionChange))
33 | }
34 |
35 | func cancel(centralManager: CBCentralManager, peripheral: CBPeripheral, error: Error?) -> SubjectTask {
36 | switch task.state {
37 | case .pending:
38 | return task.with(state: task.state.finished(.failedToConnect(error)))
39 | case .processing(since: _, .connecting):
40 | centralManager.cancelPeripheralConnection(peripheral)
41 | return task.with(state: task.state.finished(.failedToConnect(error)))
42 | case .finished:
43 | assert(false)
44 | return task
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/Connect/ConnectTaskSpec.swift:
--------------------------------------------------------------------------------
1 | struct ConnectTaskSpec: PeripheralTaskSpec {
2 |
3 | typealias Key = PeripheralID
4 |
5 | struct Params {}
6 |
7 | enum Stage {
8 |
9 | case connecting
10 | }
11 |
12 | typealias Result = ConnectionChange
13 |
14 | static let tag = "CONNECT"
15 |
16 | static func isMember(_ key: Key, of group: PeripheralID) -> Bool {
17 | return key == group
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/PeripheralTaskRegistry/PeripheralTask.swift:
--------------------------------------------------------------------------------
1 | protocol PeripheralTaskSpec {
2 |
3 | associatedtype Key: Equatable
4 | associatedtype Group = PeripheralID
5 | associatedtype Params
6 | associatedtype Stage
7 | associatedtype Result
8 |
9 | static var tag: String { get }
10 |
11 | static func isMember(_ key: Key, of group: Group) -> Bool
12 | }
13 |
14 | enum PeripheralTaskState {
15 |
16 | case pending
17 | case processing(since: Date, Stage)
18 | case finished(in: TimeInterval, Result)
19 |
20 | func processing(_ stage: Stage) -> PeripheralTaskState {
21 | switch self {
22 | case .pending:
23 | return .processing(since: Date(), stage)
24 | case .processing(let start, _):
25 | return .processing(since: start, stage)
26 | case .finished:
27 | assert(false, "Invalid transition: \(self) -> .processing")
28 | return .processing(since: .distantPast, stage)
29 | }
30 | }
31 |
32 | func finished(_ result: Result) -> PeripheralTaskState {
33 | switch self {
34 | case .pending:
35 | return .finished(in: 0, result)
36 | case .processing(let start, _):
37 | return .finished(in: -start.timeIntervalSinceNow, result)
38 | case .finished:
39 | assert(false, "Invalid transition: \(self) -> .finished")
40 | return .finished(in: 0, result)
41 | }
42 | }
43 | }
44 |
45 | struct PeripheralTask {
46 |
47 | typealias Key = Spec.Key
48 | typealias Group = Spec.Group
49 | typealias Params = Spec.Params
50 | typealias Stage = Spec.Stage
51 | typealias Result = Spec.Result
52 |
53 | typealias State = PeripheralTaskState
54 | typealias Timeout = (duration: TimeInterval, handler: () -> Void)
55 | typealias CompletionHandler = (Result) -> Void
56 |
57 | let key: Key
58 | let params: Params
59 | let state: State
60 | let timeout: Timeout?
61 | let completion: CompletionHandler
62 |
63 | init(key: Key, params: Params, timeout: Timeout?, completion: @escaping CompletionHandler) {
64 | self.init(key: key, params: params, state: .pending, timeout: timeout, completion: completion)
65 | }
66 |
67 | private init(key: Key, params: Params, state: State, timeout: Timeout?, completion: @escaping CompletionHandler) {
68 | self.key = key
69 | self.params = params
70 | self.state = state
71 | self.timeout = timeout
72 | self.completion = completion
73 | }
74 |
75 | func with(state newState: State) -> PeripheralTask {
76 | return PeripheralTask(
77 | key: key,
78 | params: params,
79 | state: newState,
80 | timeout: timeout,
81 | completion: completion
82 | )
83 | }
84 |
85 | func isMember(of group: Group) -> Bool {
86 | return Spec.isMember(key, of: group)
87 | }
88 |
89 | func iif(finished: (TimeInterval, Result) -> T, otherwise: () -> T) -> T {
90 | switch state {
91 | case .pending, .processing:
92 | return otherwise()
93 | case .finished(let duration, let result):
94 | return finished(duration, result)
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/PeripheralTaskRegistry/PeripheralTaskController.swift:
--------------------------------------------------------------------------------
1 | protocol PeripheralTaskController {
2 |
3 | associatedtype TaskSpec: PeripheralTaskSpec
4 |
5 | typealias SubjectTask = PeripheralTask
6 |
7 | init(_ task: SubjectTask)
8 | }
9 |
10 | extension PeripheralTaskController {
11 |
12 | func improperHandling(currentState: SubjectTask.State, handler: String = #function) -> Error {
13 | return PluginError.internalInconcictency(details: "for a task in state \(currentState) a forbidden handler \(handler) is called")
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/ReadRssi/ReadRssiTaskController.swift:
--------------------------------------------------------------------------------
1 | import CoreBluetooth
2 |
3 | struct ReadRssiTaskController: PeripheralTaskController {
4 | typealias TaskSpec = ReadRssiTaskSpec
5 |
6 | private let task: SubjectTask
7 |
8 | init(_ task: SubjectTask) {
9 | self.task = task
10 | }
11 |
12 | func start(peripheral: CBPeripheral) -> SubjectTask {
13 | // error if not connected
14 | guard peripheral.state == .connected
15 | else {
16 | return task.with(
17 | state: task.state.finished(
18 | .failure(PluginError.internalInconcictency(details: nil))
19 | )
20 | )
21 | }
22 |
23 | peripheral.readRSSI()
24 |
25 | return task.with(state: task.state.processing(.readingRssi))
26 | }
27 |
28 | func cancel(error: Error) -> SubjectTask {
29 | return task
30 | .with(state: task.state.finished(.failure(error)))
31 | }
32 |
33 | func handleReadRssi(rssi: Int, error: Error?) -> SubjectTask {
34 | if let error = error {
35 | return task
36 | .with(state: task.state.finished(.failure(error)))
37 | } else {
38 | return task
39 | .with(state: task.state.finished(.success(rssi)))
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/ReadRssi/ReadRssiTaskSpec.swift:
--------------------------------------------------------------------------------
1 | struct ReadRssiTaskSpec: PeripheralTaskSpec {
2 | typealias Key = PeripheralID
3 |
4 | struct Params {}
5 |
6 | enum Stage {
7 | case readingRssi
8 | }
9 |
10 | typealias Result = Failable
11 |
12 | static let tag = "READ RSSI"
13 |
14 | static func isMember(_ key: Key, of group: PeripheralID) -> Bool {
15 | return key == group
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/ServicesWithCharacteristicsDiscovery/ServicesWithCharacteristicsDiscoveryTaskSpec.swift:
--------------------------------------------------------------------------------
1 | import CoreBluetooth
2 |
3 | struct ServicesWithCharacteristicsDiscoveryTaskSpec: PeripheralTaskSpec {
4 |
5 | typealias Key = PeripheralID
6 |
7 | struct Params {
8 |
9 | let servicesWithCharacteristicsToDiscover: ServicesWithCharacteristicsToDiscover
10 | }
11 |
12 | enum Stage {
13 |
14 | case discoveringServices
15 | case discoveringCharacteristics(servicesLeft: Int, errors: [Error])
16 | }
17 |
18 | typealias Result = [Error]
19 |
20 | static let tag = "DISCOVER S&C"
21 |
22 | static func isMember(_ key: Key, of group: PeripheralID) -> Bool {
23 | return key == group
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBle/Tasks/ServicesWithCharacteristicsDiscovery/ServicesWithCharacteristicsToDiscover.swift:
--------------------------------------------------------------------------------
1 | enum ServicesWithCharacteristicsToDiscover: Equatable {
2 |
3 | case all
4 | case some([ServiceID: CharacteristicsToDiscover])
5 |
6 | var isEmpty: Bool {
7 | switch self {
8 | case .all:
9 | return false
10 | case .some(let servicesWithCharacteristicsToDiscover):
11 | return servicesWithCharacteristicsToDiscover.isEmpty
12 | }
13 | }
14 |
15 | var services: [ServiceID]? {
16 | switch self {
17 | case .all:
18 | return nil
19 | case .some(let servicesWithCharacteristicsToDiscover):
20 | return Array(servicesWithCharacteristicsToDiscover.keys)
21 | }
22 | }
23 | }
24 |
25 | enum CharacteristicsToDiscover: Equatable {
26 |
27 | case all
28 | case some([CharacteristicID])
29 |
30 | var characteristics: [CharacteristicID]? {
31 | switch self {
32 | case .all:
33 | return nil
34 | case .some(let characteristicsToDiscover):
35 | return characteristicsToDiscover
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBlePlugin.h:
--------------------------------------------------------------------------------
1 | #if TARGET_OS_OSX
2 | #import
3 | #else
4 | #import
5 | #endif
6 |
7 | @interface ReactiveBlePlugin : NSObject
8 | @end
9 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/Classes/ReactiveBlePlugin.m:
--------------------------------------------------------------------------------
1 | #import "ReactiveBlePlugin.h"
2 | #import
3 |
4 | @implementation ReactiveBlePlugin
5 | + (void)registerWithRegistrar:(NSObject*)registrar {
6 | [SwiftReactiveBlePlugin registerWithRegistrar:registrar];
7 | }
8 | @end
9 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/darwin/reactive_ble_mobile.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'reactive_ble_mobile'
3 | s.version = '0.0.1'
4 | s.summary = 'Bluetooth Low Energy (BLE) Flutter plug-in'
5 | s.description = <<-DESC
6 | Bluetooth Low Energy (BLE) Flutter plug-in
7 | DESC
8 | s.homepage = 'https://github.com/PhilipsHue/flutter_reactive_ble'
9 | s.license = { :file => '../LICENSE' }
10 | s.author = { 'Your Company' => 'email@example.com' }
11 | s.source = { :path => '.' }
12 | s.source_files = 'Classes/**/*'
13 | s.public_header_files = 'Classes/**/*.h'
14 | s.dependency 'Protobuf', '~> 3.5'
15 | s.dependency 'SwiftProtobuf', '~> 1.0'
16 | s.ios.dependency 'Flutter'
17 | s.osx.dependency 'FlutterMacOS'
18 | s.ios.deployment_target = '11.0'
19 | s.osx.deployment_target = '10.13'
20 | s.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1' }
21 | s.swift_version = '4.2'
22 | end
23 |
24 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/lib/reactive_ble_mobile.dart:
--------------------------------------------------------------------------------
1 | library reactive_ble_mobile;
2 |
3 | export 'src/reactive_ble_mobile_platform.dart';
4 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/lib/src/generated/bledata.pbenum.dart:
--------------------------------------------------------------------------------
1 | //
2 | // Generated code. Do not modify.
3 | // source: bledata.proto
4 | //
5 | // @dart = 2.12
6 |
7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references
8 | // ignore_for_file: constant_identifier_names, library_prefixes
9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields
10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import
11 |
12 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/lib/src/generated/bledata.pbserver.dart:
--------------------------------------------------------------------------------
1 | //
2 | // Generated code. Do not modify.
3 | // source: bledata.proto
4 | //
5 | // @dart = 2.12
6 |
7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references
8 | // ignore_for_file: constant_identifier_names
9 | // ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
10 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields
11 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import
12 |
13 | export 'bledata.pb.dart';
14 |
15 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/lib/src/select_from.dart:
--------------------------------------------------------------------------------
1 | T selectFrom(List values,
2 | {required int? index, required T Function(int? index) fallback}) {
3 | if (index != null && index >= 0 && index < values.length) {
4 | return values[index];
5 | }
6 | return fallback(index);
7 | }
8 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/protos/README.md:
--------------------------------------------------------------------------------
1 | # Protobuf code generation
2 |
3 | 1. Install the newest `protoc`. It can be done via `brew`:
4 |
5 | ```sh
6 | brew install protobuf
7 | ```
8 |
9 | 2. For Swift code generation install :
10 |
11 | ```sh
12 | brew install swift-protobuf
13 | ```
14 |
15 | 3. If you don't have Dart SDK on your computer please install it.
16 |
17 | ```sh
18 | brew install dart
19 | ```
20 |
21 | 4. Run `dart pub global activate protoc_plugin`
22 | 5. OPTIONAL Add plugin path to `PATH` environment variable
23 | 6. Run the following command from the "protos" directory
24 |
25 | ```sh
26 | protoc --dart_out=../lib/src/generated ./bledata.proto
27 | protoc --swift_out=../darwin/Classes/BleData ./bledata.proto
28 | ```
29 |
30 | NOTE: If directory `../lib/generated` or `./darwin/Classes/BleData` does not exist please create it.
31 |
--------------------------------------------------------------------------------
/packages/reactive_ble_mobile/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: reactive_ble_mobile
2 | description: Official Android and iOS implementation for the flutter_reactive_ble plugin.
3 | version: 5.4.0
4 | homepage: https://github.com/PhilipsHue/flutter_reactive_ble
5 |
6 | environment:
7 | sdk: ">=2.12.0 <3.0.0"
8 | flutter: ">=2.0.0"
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 | protobuf: ^3.0.0
14 | reactive_ble_platform_interface: ^5.4.0
15 |
16 | dev_dependencies:
17 | build_runner: ^2.3.3
18 | flutter_lints: ^1.0.4
19 | flutter_test:
20 | sdk: flutter
21 | mockito: ^5.0.14
22 |
23 | dependency_overrides:
24 | reactive_ble_platform_interface:
25 | path: ../reactive_ble_platform_interface
26 |
27 | flutter:
28 | plugin:
29 | platforms:
30 | android:
31 | package: com.signify.hue.flutterreactiveble
32 | pluginClass: ReactiveBlePlugin
33 | ios:
34 | pluginClass: ReactiveBlePlugin
35 | sharedDarwinSource: true
36 | macos:
37 | pluginClass: ReactiveBlePlugin
38 | sharedDarwinSource: true
39 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 5.4.0
2 |
3 | * Support for MacOS #889
4 | * Fixes subscription to _deviceConnector.deviceConnectionStateUpdateStream leaking #876
5 | * Workspace upgrade (dependencies) & gitignore sync #857
6 | * Run ktlint -F #766
7 |
8 | ## 5.3.1
9 |
10 | * Use proper platform_interface dependency version #837
11 |
12 | ## 5.3.0
13 |
14 | * Readd accidentally removed version constraint #789
15 | * Add a placeholder implementation for non-mobile platforms #688
16 | * Add Github actions and Migrate to latest Android SDK, AGP and protobuf plugin #830
17 | * Include packages lock-files #797
18 | * Update Kotlin version in README #831
19 | * Migrate away from deprecated strong mode analysis options #832
20 | * Read RSSI #796
21 |
22 | ## 5.2.0
23 |
24 | * Bump the minimum requirement to Dart 2.17 and upgrade melos to 3.1.0 in #762
25 | * swiftlint config and inital formatting pass in #765
26 | * Cancel subscription when a disconnect event has been thrown in #769
27 | * Fix typos in #778
28 | * Update CI config to use Xcode 14 in #786
29 | * Support multiple services or characteristics with the same id in #776
30 | * Breaking change: If a device has multiple characteristics with the same ID, `readCharacteristic`, `writeCharacteristic` and `subscribeToCharacteristic` used to select the first of those characteristics. Now they will fail in this case. Use `resolve` or `getDiscoveredServices` instead.
31 |
32 | ## 5.1.1
33 |
34 | * Make Connectable backwards compatible #757, #750
35 |
36 | ## 5.1.0
37 |
38 | * Add IsConnectable to discovery data. #750
39 | * Upgraded build_runner. #750
40 |
41 | ## 5.0.3
42 |
43 | * Enable extended advertising on android. Fix #571
44 | * Add additional null checks to scanRecord. Fix #521
45 | * Restore support for Xcode 12
46 | * Add verbose debug logging Fix #583
47 |
48 | ## 5.0.2
49 |
50 | * Revert Queue up messages on iOS until event channel is ready. Fix #439
51 |
52 | ## 5.0.1
53 |
54 | * Bump protobuf so it includes binaries for Mac M1 #396.
55 |
56 | ## 5.0.0
57 |
58 | ** Breaking change **
59 | * DiscoveredService has now a new required property called `DiscoveredCharacteristic` which provides properties of the BLE characteristic.
60 |
61 | Other changes
62 | * Android 12 support.
63 | * Queue up messages on iOS until event channel is ready. Fixes #137, #251, #307, #385, #387.
64 |
65 | ## 4.0.1
66 |
67 | * Add support for iOS 15
68 |
69 | ## 4.0.0
70 |
71 | * Initial Open Source release.
72 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/LICENSE:
--------------------------------------------------------------------------------
1 | BSD license
2 |
3 | ©2019 Signify Holding.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
6 | following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions
9 | and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
12 | disclaimer in the documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
15 | derived from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/README.md:
--------------------------------------------------------------------------------
1 | # Reactive_ble_platform_interface
2 |
3 | A common platform interface for the [reactive ble](https://github.com/PhilipsHue/flutter_reactive_ble/) plugin. This package ensures every platform specific implementation uses the same interface.
4 |
5 | ## Usage
6 |
7 | To implement a new platform specific implementation extend the `ReactiveBlePlatform` with an implementation that performs the platform specific behavior.
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/reactive_ble_platform_interface.dart:
--------------------------------------------------------------------------------
1 | library reactive_ble_platform_interface;
2 |
3 | export 'src/logger.dart';
4 | export 'src/models.dart';
5 | export 'src/reactive_ble_platform_interface.dart';
6 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/logger.dart:
--------------------------------------------------------------------------------
1 | import 'model/log_level.dart';
2 |
3 | abstract class Logger {
4 | set logLevel(LogLevel logLevel);
5 | LogLevel get logLevel;
6 |
7 | void log(Object message);
8 | }
9 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/ble_status.dart:
--------------------------------------------------------------------------------
1 | /// The status of the BLE subsystem as reported by the platform API.
2 | enum BleStatus {
3 | /// Status is not (yet) determined.
4 | unknown,
5 |
6 | /// BLE is not supported on this device.
7 | unsupported,
8 |
9 | /// BLE usage is not authorized for this app.
10 | unauthorized,
11 |
12 | /// BLE is turned off.
13 | poweredOff,
14 |
15 | /// Android only: Location services are disabled.
16 | locationServicesDisabled,
17 |
18 | /// BLE is fully operating for this app.
19 | ready
20 | }
21 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/characteristic_instance.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | import 'uuid.dart';
4 |
5 | @immutable
6 | class CharacteristicInstance {
7 | /// Unique uuid of the specific characteristic
8 | final Uuid characteristicId;
9 |
10 | /// The id that identifies the specific instance of all characteristics with [characteristicId] in a given service.
11 | /// This should no be exposed to user of the plugin as it may be different each time a device is connected to, so it
12 | /// should not be used to identify characteristics across sessions.
13 | final String characteristicInstanceId;
14 |
15 | /// Service uuid of the characteristic
16 | final Uuid serviceId;
17 |
18 | /// The id that identifies the specific instance of all services with [serviceId] in a given device.
19 | /// This should no be exposed to user of the plugin as it may be different each time a device is connected to, so it
20 | /// should not be used to identify characteristics across sessions.
21 | final String serviceInstanceId;
22 |
23 | /// Device id of the BLE device
24 | final String deviceId;
25 |
26 | const CharacteristicInstance({
27 | required this.characteristicId,
28 | required this.characteristicInstanceId,
29 | required this.serviceId,
30 | required this.serviceInstanceId,
31 | required this.deviceId,
32 | });
33 |
34 | @override
35 | String toString() => "$runtimeType(characteristicId: $characteristicId($characteristicInstanceId), "
36 | "serviceId: $serviceId($serviceInstanceId), deviceId: $deviceId)";
37 |
38 | @override
39 | int get hashCode => Object.hash(
40 | characteristicId.expanded,
41 | characteristicInstanceId,
42 | serviceId.expanded,
43 | serviceInstanceId,
44 | deviceId,
45 | );
46 |
47 | @override
48 | bool operator ==(Object other) =>
49 | other is CharacteristicInstance &&
50 | runtimeType == other.runtimeType &&
51 | characteristicId.expanded == other.characteristicId.expanded &&
52 | characteristicInstanceId == other.characteristicInstanceId &&
53 | serviceId.expanded == other.serviceId.expanded &&
54 | serviceInstanceId == other.serviceInstanceId &&
55 | deviceId == other.deviceId;
56 | }
57 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/characteristic_value.dart:
--------------------------------------------------------------------------------
1 | import 'characteristic_instance.dart';
2 | import 'generic_failure.dart';
3 | import 'result.dart';
4 |
5 | /// Value update for specific [CharacteristicInstance].
6 | class CharacteristicValue {
7 | final CharacteristicInstance characteristic;
8 | final Result, GenericFailure?>
9 | result;
10 |
11 | const CharacteristicValue(
12 | {required this.characteristic, required this.result});
13 |
14 | @override
15 | String toString() =>
16 | "$runtimeType(characteristic: $characteristic, value: $result)";
17 | }
18 |
19 | /// Error type for characteristic value update.
20 | enum CharacteristicValueUpdateError { unknown }
21 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/clear_gatt_cache_error.dart:
--------------------------------------------------------------------------------
1 | /// Error type of Clear Gatt cache operation.
2 | enum ClearGattCacheError {
3 | /// Reason of failure is unknown.
4 | unknown,
5 |
6 | /// Clear gatt cache is not supported on this OS.
7 | operationNotSupported
8 | }
9 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/connection_priority.dart:
--------------------------------------------------------------------------------
1 | import '../model/unit.dart';
2 | import 'generic_failure.dart';
3 | import 'result.dart';
4 |
5 | /// The priority that can be requested to update the connection parameter.
6 | enum ConnectionPriority {
7 | /// connection with recommended parameters.
8 | balanced,
9 |
10 | /// high priority, low latency connection.
11 | highPerformance,
12 | // reduced power, low data rate connection.
13 | lowPower,
14 | }
15 |
16 | ///util function to convert priority to a integer.
17 | int convertPriorityToInt(ConnectionPriority priority) {
18 | switch (priority) {
19 | case ConnectionPriority.balanced:
20 | return 0;
21 | case ConnectionPriority.highPerformance:
22 | return 1;
23 | case ConnectionPriority.lowPower:
24 | return 2;
25 | default:
26 | assert(false);
27 | return -1000;
28 | }
29 | }
30 |
31 | /// Result of the connection priority request
32 | class ConnectionPriorityInfo {
33 | const ConnectionPriorityInfo({required this.result});
34 |
35 | final Result?> result;
36 | }
37 |
38 | /// Error type for connection priority.
39 | enum ConnectionPriorityFailure { unknown }
40 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/connection_state_update.dart:
--------------------------------------------------------------------------------
1 | import 'package:functional_data/functional_data.dart';
2 | import 'package:meta/meta.dart';
3 |
4 | import 'generic_failure.dart';
5 |
6 | part 'connection_state_update.g.dart';
7 | //ignore_for_file: annotate_overrides
8 |
9 | ///Status update for a specific BLE device.
10 | @immutable
11 | @FunctionalData()
12 | class ConnectionStateUpdate extends $ConnectionStateUpdate {
13 | final String deviceId;
14 | final DeviceConnectionState connectionState;
15 |
16 | /// Field `error` is null if there is no error reported.
17 | final GenericFailure? failure;
18 |
19 | const ConnectionStateUpdate({
20 | required this.deviceId,
21 | required this.connectionState,
22 | required this.failure,
23 | });
24 | }
25 |
26 | /// Connection status.
27 | enum DeviceConnectionState {
28 | /// Currently establishing a connection.
29 | connecting,
30 |
31 | /// Connection is established.
32 | connected,
33 |
34 | /// Terminating the connection.
35 | disconnecting,
36 |
37 | /// Device is disconnected.
38 | disconnected
39 | }
40 |
41 | /// Type of connection error.
42 | enum ConnectionError {
43 | /// Connection failed for an unknown reason.
44 | unknown,
45 |
46 | /// An attempt to connect was made but it failed.
47 | failedToConnect,
48 | }
49 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/connection_state_update.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'connection_state_update.dart';
4 |
5 | // **************************************************************************
6 | // FunctionalDataGenerator
7 | // **************************************************************************
8 |
9 | abstract class $ConnectionStateUpdate {
10 | const $ConnectionStateUpdate();
11 |
12 | String get deviceId;
13 | DeviceConnectionState get connectionState;
14 | GenericFailure? get failure;
15 |
16 | ConnectionStateUpdate copyWith({
17 | String? deviceId,
18 | DeviceConnectionState? connectionState,
19 | GenericFailure? failure,
20 | }) =>
21 | ConnectionStateUpdate(
22 | deviceId: deviceId ?? this.deviceId,
23 | connectionState: connectionState ?? this.connectionState,
24 | failure: failure ?? this.failure,
25 | );
26 |
27 | ConnectionStateUpdate copyUsing(
28 | void Function(ConnectionStateUpdate$Change change) mutator) {
29 | final change = ConnectionStateUpdate$Change._(
30 | this.deviceId,
31 | this.connectionState,
32 | this.failure,
33 | );
34 | mutator(change);
35 | return ConnectionStateUpdate(
36 | deviceId: change.deviceId,
37 | connectionState: change.connectionState,
38 | failure: change.failure,
39 | );
40 | }
41 |
42 | @override
43 | String toString() =>
44 | "ConnectionStateUpdate(deviceId: $deviceId, connectionState: $connectionState, failure: $failure)";
45 |
46 | @override
47 | // ignore: avoid_equals_and_hash_code_on_mutable_classes
48 | bool operator ==(Object other) =>
49 | other is ConnectionStateUpdate &&
50 | other.runtimeType == runtimeType &&
51 | deviceId == other.deviceId &&
52 | connectionState == other.connectionState &&
53 | failure == other.failure;
54 |
55 | @override
56 | // ignore: avoid_equals_and_hash_code_on_mutable_classes
57 | int get hashCode {
58 | var result = 17;
59 | result = 37 * result + deviceId.hashCode;
60 | result = 37 * result + connectionState.hashCode;
61 | result = 37 * result + failure.hashCode;
62 | return result;
63 | }
64 | }
65 |
66 | class ConnectionStateUpdate$Change {
67 | ConnectionStateUpdate$Change._(
68 | this.deviceId,
69 | this.connectionState,
70 | this.failure,
71 | );
72 |
73 | String deviceId;
74 | DeviceConnectionState connectionState;
75 | GenericFailure? failure;
76 | }
77 |
78 | // ignore: avoid_classes_with_only_static_members
79 | class ConnectionStateUpdate$ {
80 | static final deviceId = Lens(
81 | (deviceIdContainer) => deviceIdContainer.deviceId,
82 | (deviceIdContainer, deviceId) =>
83 | deviceIdContainer.copyWith(deviceId: deviceId),
84 | );
85 |
86 | static final connectionState =
87 | Lens(
88 | (connectionStateContainer) => connectionStateContainer.connectionState,
89 | (connectionStateContainer, connectionState) =>
90 | connectionStateContainer.copyWith(connectionState: connectionState),
91 | );
92 |
93 | static final failure =
94 | Lens?>(
95 | (failureContainer) => failureContainer.failure,
96 | (failureContainer, failure) => failureContainer.copyWith(failure: failure),
97 | );
98 | }
99 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/discovered_characteristic.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | import 'uuid.dart';
4 |
5 | /// Specific BLE characteristic for a BLE device characterized by [deviceId], [serviceId] and
6 | /// [characteristicId].
7 | @immutable
8 | class DiscoveredCharacteristic {
9 | /// Unique uuid of the specific characteristic
10 | final Uuid characteristicId;
11 |
12 | final String characteristicInstanceId;
13 |
14 | /// Service uuid of the characteristic
15 | final Uuid serviceId;
16 |
17 | /// Properties
18 | final bool isReadable;
19 | final bool isWritableWithResponse;
20 | final bool isWritableWithoutResponse;
21 | final bool isNotifiable;
22 | final bool isIndicatable;
23 |
24 | const DiscoveredCharacteristic({
25 | required this.characteristicId,
26 | required this.characteristicInstanceId,
27 | required this.serviceId,
28 | required this.isReadable,
29 | required this.isWritableWithResponse,
30 | required this.isWritableWithoutResponse,
31 | required this.isNotifiable,
32 | required this.isIndicatable,
33 | });
34 |
35 | @override
36 | String toString() =>
37 | "$runtimeType(characteristicId: $characteristicId, serviceId: $serviceId)";
38 |
39 | @override
40 | int get hashCode =>
41 | (((17 * 37) + characteristicId.hashCode) * 37 + serviceId.hashCode) * 37;
42 |
43 | @override
44 | bool operator ==(Object other) =>
45 | other is DiscoveredCharacteristic &&
46 | runtimeType == other.runtimeType &&
47 | characteristicId == other.characteristicId &&
48 | serviceId == other.serviceId;
49 | }
50 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/discovered_device.dart:
--------------------------------------------------------------------------------
1 | import 'dart:typed_data';
2 |
3 | import 'package:collection/collection.dart';
4 | import 'package:functional_data/functional_data.dart';
5 | import 'package:meta/meta.dart';
6 |
7 | import '../../reactive_ble_platform_interface.dart';
8 |
9 | part 'discovered_device.g.dart';
10 |
11 | // ignore_for_file: annotate_overrides, avoid_classes_with_only_static_members, non_constant_identifier_names
12 |
13 | /// Result of a scan interval.
14 | @immutable
15 | @FunctionalData()
16 | class ScanResult extends $ScanResult {
17 | final Result?> result;
18 |
19 | const ScanResult({required this.result});
20 |
21 | @override
22 | String toString() => "$ScanResult(result: $result)";
23 | }
24 |
25 | ///Ble device that is discovered during scanning.
26 | @immutable
27 | @FunctionalData()
28 | class DiscoveredDevice extends $DiscoveredDevice {
29 | /// The unique identifier of the device.
30 | final String id;
31 | final String name;
32 | @CustomEquality(DeepCollectionEquality())
33 | final Map serviceData;
34 |
35 | /// Advertised services
36 | @CustomEquality(DeepCollectionEquality())
37 | final List serviceUuids;
38 |
39 | /// Manufacturer specific data. The first 2 bytes are the Company Identifier Codes.
40 | @CustomEquality(DeepCollectionEquality())
41 | final Uint8List manufacturerData;
42 |
43 | final int rssi;
44 |
45 | final Connectable connectable;
46 |
47 | const DiscoveredDevice({
48 | required this.id,
49 | required this.name,
50 | required this.serviceData,
51 | required this.manufacturerData,
52 | required this.rssi,
53 | required this.serviceUuids,
54 | this.connectable = Connectable.unknown,
55 | });
56 | }
57 |
58 | ///Connection status of the BLE device.
59 | enum ConnectionStatus {
60 | /// Device is disconnected.
61 | disconnected,
62 |
63 | /// A connection is being established.
64 | connecting,
65 |
66 | /// Connected with Device.
67 | connected,
68 |
69 | /// Device is being disconnected.
70 | disconnecting,
71 | }
72 |
73 | /// Failure type of device discovery.
74 | enum ScanFailure { unknown }
75 |
76 | /// Shows if the device is ready to be connected to from a discovery perspective
77 | enum Connectable { unknown, unavailable, available }
78 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/discovered_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:collection/collection.dart';
2 | import 'package:functional_data/functional_data.dart';
3 | import 'package:reactive_ble_platform_interface/src/model/discovered_characteristic.dart';
4 |
5 | import 'uuid.dart';
6 |
7 | part 'discovered_service.g.dart';
8 |
9 | //ignore_for_file: annotate_overrides
10 |
11 | @FunctionalData()
12 | class DiscoveredService extends $DiscoveredService {
13 | const DiscoveredService({
14 | required this.serviceId,
15 | required this.serviceInstanceId,
16 | required this.characteristicIds,
17 | required this.characteristics,
18 | this.includedServices = const [],
19 | });
20 |
21 | final Uuid serviceId;
22 |
23 | final String serviceInstanceId;
24 |
25 | @CustomEquality(DeepCollectionEquality())
26 | final List characteristicIds;
27 |
28 | @CustomEquality(DeepCollectionEquality())
29 | final List characteristics;
30 |
31 | @CustomEquality(DeepCollectionEquality())
32 | final List includedServices;
33 | }
34 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/generic_failure.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | /// Error result of a BLE operation.
4 | @immutable
5 | class GenericFailure {
6 | /// Code that classifies the failure.
7 | final T code;
8 |
9 | /// String that provides additional context of the failure.
10 | final String message;
11 |
12 | const GenericFailure({required this.code, required this.message});
13 |
14 | @override
15 | String toString() => "$runtimeType(code: $code, message: \"$message\")";
16 |
17 | @override
18 | bool operator ==(Object other) =>
19 | identical(this, other) ||
20 | (other is GenericFailure &&
21 | code == other.code &&
22 | message == other.message);
23 |
24 | @override
25 | int get hashCode {
26 | var result = 17;
27 | result = 37 * result + code.hashCode;
28 | result = 37 * result + message.hashCode;
29 | return result;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/log_level.dart:
--------------------------------------------------------------------------------
1 | /// States the level of debug logging within this library
2 | enum LogLevel {
3 | /// No debugLogging at all. This is the default level within the library.
4 | none,
5 |
6 | /// Verbose logging, this can be useful when debugging issues.
7 | verbose,
8 | }
9 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/qualified_characteristic.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | import 'uuid.dart';
4 |
5 | /// Specific BLE characteristic for a BLE device characterized by [deviceId], [serviceId] and
6 | /// [characteristicId].
7 | @immutable
8 | class QualifiedCharacteristic {
9 | /// Unique uuid of the specific characteristic
10 | final Uuid characteristicId;
11 |
12 | /// Service uuid of the characteristic
13 | final Uuid serviceId;
14 |
15 | /// Device id of the BLE device
16 | final String deviceId;
17 |
18 | const QualifiedCharacteristic({
19 | required this.characteristicId,
20 | required this.serviceId,
21 | required this.deviceId,
22 | });
23 |
24 | @override
25 | String toString() =>
26 | "$runtimeType(characteristicId: $characteristicId, serviceId: $serviceId, deviceId: $deviceId)";
27 |
28 | @override
29 | int get hashCode =>
30 | (((17 * 37) + characteristicId.hashCode) * 37 + serviceId.hashCode) * 37 +
31 | deviceId.hashCode;
32 |
33 | @override
34 | bool operator ==(Object other) =>
35 | other is QualifiedCharacteristic &&
36 | runtimeType == other.runtimeType &&
37 | characteristicId == other.characteristicId &&
38 | serviceId == other.serviceId &&
39 | deviceId == other.deviceId;
40 | }
41 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/result.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | ///Result of a ble operation.
4 | ///
5 | /// In case the result is successful [Failure] is null.
6 | @immutable
7 | class Result {
8 | const Result.success(this._value)
9 | : _failure = null; // ignore: avoid_field_initializers_in_const_classes
10 | const Result.failure(this._failure)
11 | : assert(_failure != null),
12 | _value = null; // ignore: avoid_field_initializers_in_const_classes
13 |
14 | /// Provides the value in case of success or throws [Exception] in case of failure.
15 | Value dematerialize() => iif(
16 | success: (value) => value!,
17 | failure: (failure) {
18 | if (failure is Exception) {
19 | // ignore: only_throw_errors
20 | throw failure;
21 | } else {
22 | throw Exception(failure);
23 | }
24 | },
25 | );
26 |
27 | /// Execute specific actions on success and on failure.
28 | T iif(
29 | {required T Function(Value value) success,
30 | required T Function(Failure failure) failure}) {
31 | assert(_value == null || _failure == null);
32 |
33 | if (_failure != null) {
34 | return failure(_failure!);
35 | } else if (_value != null) {
36 | return success(_value!);
37 | } else {
38 | throw Exception('Both value and failure cannot be null');
39 | }
40 | }
41 |
42 | @override
43 | String toString() {
44 | if (_value != null && _failure == null) {
45 | return "$runtimeType.success($_value)";
46 | } else if (_failure != null && _value == null) {
47 | return "$runtimeType.failure($_failure)";
48 | } else {
49 | return "$runtimeType._unsafe(value: $_value, failure: $_failure)";
50 | }
51 | }
52 |
53 | @override
54 | int get hashCode =>
55 | ((17 * 37) + (_value?.hashCode ?? 0)) * 37 + (_failure?.hashCode ?? 0);
56 |
57 | @override
58 | bool operator ==(Object other) =>
59 | other is Result &&
60 | runtimeType == other.runtimeType &&
61 | _value == other._value &&
62 | _failure == other._failure;
63 |
64 | final Value? _value;
65 | final Failure? _failure;
66 | }
67 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/scan_mode.dart:
--------------------------------------------------------------------------------
1 | /// Android only: mode in which BLE discovery is executed.
2 | enum ScanMode {
3 | /// passively listen for other scan results without starting BLE scan itself.
4 | opportunistic,
5 |
6 | /// Scan mode which has the lowest battery consumption.
7 | lowPower,
8 |
9 | /// Scan mode that is a good compromise between battery consumption and latency.
10 | balanced,
11 |
12 | /// Scan mode with highest battery consumption and lowest latency.
13 | /// Should not be used when scanning for a long time.
14 | lowLatency,
15 | }
16 |
17 | /// Converts [ScanMode] to integer representation.
18 | int convertScanModeToArgs(ScanMode scanMode) {
19 | switch (scanMode) {
20 | case ScanMode.opportunistic:
21 | return -1;
22 | case ScanMode.lowPower:
23 | return 0;
24 | case ScanMode.balanced:
25 | return 1;
26 | case ScanMode.lowLatency:
27 | return 2;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/scan_session.dart:
--------------------------------------------------------------------------------
1 | import 'uuid.dart';
2 |
3 | class ScanSession {
4 | final List withServices;
5 | final Future future;
6 |
7 | const ScanSession({required this.withServices, required this.future});
8 | }
9 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/unit.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | @immutable
4 | class Unit {
5 | const Unit();
6 |
7 | @override
8 | bool operator ==(Object other) => other.runtimeType == runtimeType;
9 |
10 | @override
11 | int get hashCode => 1;
12 | }
13 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/uuid.dart:
--------------------------------------------------------------------------------
1 | import 'dart:typed_data';
2 |
3 | import 'package:collection/collection.dart';
4 | import 'package:meta/meta.dart';
5 |
6 | /// Representation of a 16, 32 or 128-bit number used to identify BLE services and characteristics.
7 | class Uuid {
8 | final Uint8List data;
9 |
10 | Uuid(List data) : data = Uint8List.fromList(data);
11 |
12 | factory Uuid.parse(String string) {
13 | final data = Uint8List(16);
14 |
15 | var byteOffset = 0;
16 | for (var substringStart = 0; substringStart < string.length;) {
17 | if (string[substringStart] == "-") {
18 | substringStart += 1;
19 | continue;
20 | }
21 |
22 | if (byteOffset >= 16 || substringStart + 2 > string.length) {
23 | throw _UuidParseFailure(string);
24 | }
25 |
26 | final byte = int.tryParse(
27 | string.substring(substringStart, substringStart + 2),
28 | radix: 16,
29 | );
30 | if (byte == null) throw _UuidParseFailure(string);
31 |
32 | data[byteOffset] = byte;
33 |
34 | byteOffset += 1;
35 | substringStart += 2;
36 | }
37 | if (byteOffset == 2 || byteOffset == 4 || byteOffset == 16) {
38 | return Uuid(data.buffer.asUint8List(0, byteOffset));
39 | } else {
40 | throw _UuidParseFailure(string);
41 | }
42 | }
43 |
44 | /// Expands a 16 bit uuid to a 128 bit one
45 | Uuid get expanded {
46 | if (data.length == 2) {
47 | return Uuid.parse("0000$this-0000-1000-8000-00805f9b34fb");
48 | } else {
49 | return this;
50 | }
51 | }
52 |
53 | @override
54 | String toString() {
55 | String paddedHex(int num) {
56 | final text = num.toRadixString(16);
57 | return text.length < 2 ? "0$text" : text;
58 | }
59 |
60 | final buffer = StringBuffer();
61 | final groupLength = [4, 2, 2, 2, 6];
62 | var group = 0, inGroupOffset = 0;
63 | for (final octet in data) {
64 | if (group < groupLength.length && inGroupOffset >= groupLength[group]) {
65 | buffer.write("-");
66 | group += 1;
67 | inGroupOffset = 0;
68 | }
69 | buffer.write(paddedHex(octet));
70 | inGroupOffset += 1;
71 | }
72 | return buffer.toString();
73 | }
74 |
75 | @override
76 | int get hashCode =>
77 | data.fold(17, (hash, octet) => 37 * hash + octet.hashCode);
78 |
79 | @override
80 | bool operator ==(Object other) =>
81 | other is Uuid &&
82 | other.runtimeType == runtimeType &&
83 | const DeepCollectionEquality().equals(other.data, data);
84 | }
85 |
86 | @immutable
87 | class _UuidParseFailure implements Exception {
88 | final String string;
89 |
90 | const _UuidParseFailure(this.string);
91 |
92 | @override
93 | String toString() => "$runtimeType(\"$string\")";
94 | }
95 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/model/write_characteristic_info.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | import 'characteristic_instance.dart';
4 | import 'generic_failure.dart';
5 | import 'result.dart';
6 | import 'unit.dart';
7 |
8 | @immutable
9 | class WriteCharacteristicInfo {
10 | final CharacteristicInstance characteristic;
11 | final Result?> result;
12 |
13 | const WriteCharacteristicInfo({
14 | required this.characteristic,
15 | required this.result,
16 | });
17 |
18 | @override
19 | String toString() =>
20 | "$runtimeType(characteristic: $characteristic, result: $result)";
21 |
22 | @override
23 | int get hashCode =>
24 | ((17 * 37) + characteristic.hashCode) * 37 + result.hashCode;
25 |
26 | @override
27 | bool operator ==(Object other) =>
28 | runtimeType == other.runtimeType &&
29 | other is WriteCharacteristicInfo &&
30 | characteristic == other.characteristic &&
31 | result == other.result;
32 | }
33 |
34 | enum WriteCharacteristicFailure { unknown }
35 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/models.dart:
--------------------------------------------------------------------------------
1 | export './model/ble_status.dart';
2 | export './model/characteristic_instance.dart';
3 | export './model/characteristic_value.dart';
4 | export './model/clear_gatt_cache_error.dart';
5 | export './model/connection_priority.dart';
6 | export './model/connection_state_update.dart';
7 | export './model/discovered_characteristic.dart';
8 | export './model/discovered_device.dart';
9 | export './model/discovered_service.dart';
10 | export './model/generic_failure.dart';
11 | export './model/log_level.dart';
12 | export './model/qualified_characteristic.dart';
13 | export './model/result.dart';
14 | export './model/scan_mode.dart';
15 | export './model/scan_session.dart';
16 | export './model/unit.dart';
17 | export './model/uuid.dart';
18 | export './model/write_characteristic_info.dart';
19 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/lib/src/select_from.dart:
--------------------------------------------------------------------------------
1 | T selectFrom(List values,
2 | {required int? index, required T Function(int? index) fallback}) {
3 | if (index != null && index >= 0 && index < values.length) {
4 | return values[index];
5 | }
6 | return fallback(index);
7 | }
8 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: reactive_ble_platform_interface
2 | description: Platform interface for the flutter_reactive_ble_project
3 | version: 5.4.0
4 | homepage: https://github.com/PhilipsHue/flutter_reactive_ble
5 |
6 | environment:
7 | sdk: '>=2.17.0 <3.0.0'
8 | flutter: ">=2.0.0"
9 |
10 | dependencies:
11 | collection: ^1.15.0
12 | flutter:
13 | sdk: flutter
14 | functional_data: ^1.0.0
15 | meta: ^1.7.0
16 |
17 | plugin_platform_interface: ^2.0.1
18 |
19 | dev_dependencies:
20 | build_runner: ^2.3.3
21 | flutter_lints: ^1.0.3
22 | flutter_test:
23 | sdk: flutter
24 | functional_data_generator: ^1.1.2
25 | mockito: ^5.0.2
26 |
27 | flutter:
28 |
--------------------------------------------------------------------------------
/packages/reactive_ble_platform_interface/test/select_from_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:reactive_ble_platform_interface/src/select_from.dart';
3 |
4 | void main() {
5 | group("selectFrom", () {
6 | _Enum sut(int? raw) =>
7 | selectFrom(_Enum.values, index: raw, fallback: (raw) => _Enum.unknown);
8 |
9 | test("selects a value by index", () {
10 | expect(sut(1), _Enum.a);
11 | });
12 |
13 | test("uses the fallback given a negative index", () {
14 | expect(sut(-1), _Enum.unknown);
15 | });
16 |
17 | test("uses the fallback given an out of range index", () {
18 | expect(sut(3), _Enum.unknown);
19 | });
20 |
21 | test("uses the fallback given a null index", () {
22 | expect(sut(null), _Enum.unknown);
23 | });
24 | });
25 | }
26 |
27 | enum _Enum { unknown, a }
28 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_reactive_ble_workspace
2 |
3 | environment:
4 | sdk: '>=2.17.0 <3.0.0'
5 | dev_dependencies:
6 | flutter_lints: ^1.0.4
7 | melos: ^3.1.0
8 |
--------------------------------------------------------------------------------