├── .github └── workflows │ └── dart-flutter-package-analyzer.yml ├── .gitignore ├── .gitmodules ├── .metadata ├── .travis.yml ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── REFERENCE.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── polidea │ └── flutter_ble_lib │ ├── BleErrorFactory.java │ ├── CharacteristicsResponse.java │ ├── ConnectionStateChange.java │ ├── FlutterBleLibPlugin.java │ ├── MultiCharacteristicsResponse.java │ ├── SafeMainThreadResolver.java │ ├── SingleCharacteristicResponse.java │ ├── constant │ ├── ArgumentKey.java │ ├── ChannelName.java │ └── MethodName.java │ ├── converter │ ├── BleErrorJsonConverter.java │ ├── CharacteristicJsonConverter.java │ ├── ConnectionStateChangeJsonConverter.java │ ├── DescriptorJsonConverter.java │ ├── DevicesResultJsonConverter.java │ ├── JsonConverter.java │ ├── MultiCharacteristicsResponseJsonConverter.java │ ├── MultiDescriptorsResponseJsonConverter.java │ ├── ScanResultJsonConverter.java │ ├── ServiceJsonConverter.java │ └── SingleCharacteristicResponseJsonConverter.java │ ├── delegate │ ├── BluetoothStateDelegate.java │ ├── CallDelegate.java │ ├── CharacteristicsDelegate.java │ ├── DescriptorsDelegate.java │ ├── DeviceConnectionDelegate.java │ ├── DevicesDelegate.java │ ├── DiscoveryDelegate.java │ ├── LogLevelDelegate.java │ ├── MtuDelegate.java │ └── RssiDelegate.java │ └── event │ ├── AdapterStateStreamHandler.java │ ├── CharacteristicsMonitorStreamHandler.java │ ├── ConnectionStateStreamHandler.java │ ├── RestoreStateStreamHandler.java │ └── ScanningStreamHandler.java ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── polidea │ │ │ │ │ └── flutter_ble_lib_example │ │ │ │ │ └── MainActivity.java │ │ │ └── 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 │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ └── ti_logo.png ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── 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 │ │ ├── SwiftBridging.swift │ │ └── main.m ├── lib │ ├── device_details │ │ ├── device_detail_view.dart │ │ ├── device_details_bloc.dart │ │ ├── devices_details_bloc_provider.dart │ │ └── view │ │ │ ├── auto_test_view.dart │ │ │ ├── button_view.dart │ │ │ ├── logs_container_view.dart │ │ │ └── manual_test_view.dart │ ├── devices_list │ │ ├── devices_bloc.dart │ │ ├── devices_bloc_provider.dart │ │ ├── devices_list_view.dart │ │ └── hex_painter.dart │ ├── main.dart │ ├── model │ │ └── ble_device.dart │ ├── repository │ │ └── device_repository.dart │ ├── sensor_tag_config.dart │ ├── test_scenarios │ │ ├── base.dart │ │ ├── bluetooth_state_toggle_scenario.dart │ │ ├── peripheral_test_operations.dart │ │ ├── sensor_tag_test_scenario.dart │ │ ├── sensor_tag_test_with_scan_and_connection_scenario.dart │ │ └── test_scenarios.dart │ └── util │ │ └── pair.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── Constants │ │ ├── ArgumentKey.h │ │ ├── ArgumentKey.m │ │ ├── ChannelName.h │ │ ├── ChannelName.m │ │ ├── MethodName.h │ │ └── MethodName.m │ ├── Event │ │ ├── AdapterStateStreamHandler.h │ │ ├── AdapterStateStreamHandler.m │ │ ├── ConnectionStateStreamHandler.h │ │ ├── ConnectionStateStreamHandler.m │ │ ├── MonitorCharacteristicStreamHandler.h │ │ ├── MonitorCharacteristicStreamHandler.m │ │ ├── RestoreStateStreamHandler.h │ │ ├── RestoreStateStreamHandler.m │ │ ├── ScanningStreamHandler.h │ │ └── ScanningStreamHandler.m │ ├── FlutterBleLibPlugin.h │ ├── FlutterBleLibPlugin.m │ ├── Response │ │ ├── CharacteristicResponse.h │ │ ├── CharacteristicResponse.m │ │ ├── DescriptorResponse.h │ │ ├── DescriptorResponse.m │ │ ├── PeripheralResponse.h │ │ ├── PeripheralResponse.m │ │ ├── ServiceResponse.h │ │ └── ServiceResponse.m │ ├── ResponseConverter │ │ ├── CharacteristicResponseConverter.h │ │ ├── CharacteristicResponseConverter.m │ │ ├── DescriptorResponseConverter.h │ │ ├── DescriptorResponseConverter.m │ │ ├── PeripheralResponseConverter.h │ │ ├── PeripheralResponseConverter.m │ │ ├── ServiceResponseConverter.h │ │ └── ServiceResponseConverter.m │ ├── SwiftBridging.swift │ ├── Util │ │ ├── ArgumentHandler.h │ │ ├── ArgumentHandler.m │ │ ├── CommonTypes.h │ │ ├── FlutterErrorFactory.h │ │ ├── FlutterErrorFactory.m │ │ ├── JSONStringifier.h │ │ └── JSONStringifier.m │ └── flutter_ble_lib-Bridging-Header.h └── flutter_ble_lib.podspec ├── lib ├── ble_manager.dart ├── characteristic.dart ├── descriptor.dart ├── error │ └── ble_error.dart ├── flutter_ble_lib.dart ├── peripheral.dart ├── scan_result.dart ├── service.dart └── src │ ├── _constants.dart │ ├── _containers.dart │ ├── _internal.dart │ ├── _managers_for_classes.dart │ ├── base_entities.dart │ ├── bridge │ ├── bluetooth_state_mixin.dart │ ├── characteristics_mixin.dart │ ├── descriptors_mixin.dart │ ├── device_connection_mixin.dart │ ├── device_rssi_mixin.dart │ ├── devices_mixin.dart │ ├── discovery_mixin.dart │ ├── lib_core.dart │ ├── log_level_mixin.dart │ ├── mtu_mixin.dart │ └── scanning_mixin.dart │ ├── internal_ble_manager.dart │ └── util │ ├── _transaction_id_generator.dart │ └── _transformers.dart ├── pubspec.yaml ├── site ├── flutter-ble-lib-logo-small.png ├── flutter-ble-lib-logo.png └── logo_Blemulator.png └── test ├── analysis_options.yaml ├── ble_manager_test.dart ├── characteristic_test.dart ├── characteristic_test.mocks.dart ├── descriptor_test.dart ├── descriptor_test.mocks.dart ├── json └── ble_error_jsons.dart ├── mock └── mocks.dart ├── scan_result.dart ├── service_test.dart ├── service_test.mocks.dart ├── src ├── bridge │ ├── lib_core_test.dart │ └── lib_core_test.mocks.dart └── util │ └── transcation_id_generator_test.dart └── test_util ├── characteristic_generator.dart ├── characteristic_generator.mocks.dart └── descriptor_generator.dart /.github/workflows/dart-flutter-package-analyzer.yml: -------------------------------------------------------------------------------- 1 | name: Dart/Flutter Package Analyzer 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | 6 | package-analysis: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - uses: axel-op/dart-package-analyzer@v3 14 | # set an id for the current step 15 | id: analysis 16 | with: 17 | githubToken: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | # You can then use this id to retrieve the outputs in the next steps. 20 | # The following step shows how to exit the workflow with an error if the total score in percentage is below 50: 21 | - name: Check scores 22 | env: 23 | # NB: "analysis" is the id set above. Replace it with the one you used if different. 24 | TOTAL: ${{ steps.analysis.outputs.total }} 25 | TOTAL_MAX: ${{ steps.analysis.outputs.total_max }} 26 | ANALYSIS: ${{ steps.analysis.outputs.analysis }} 27 | ANALYSIS_MAX: ${{ steps.analysis.outputs.analysis_max }} 28 | run: | 29 | if (( $ANALYSIS < $ANALYSIS_MAX )) 30 | then 31 | echo Unacceptable analysis score! Needs to be maxed out! 32 | exit 1 33 | elif (( $TOTAL < ($TOTAL_MAX - 10) )) 34 | then 35 | echo Unacceptable total score! Check dependencies and other factors! 36 | exit 2 37 | fi 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .atom/ 3 | .idea 4 | .packages 5 | .pub/ 6 | build/ 7 | ios/.generated/ 8 | packages 9 | pubspec.lock 10 | *.iml 11 | example/android/res/values/strings_en.arb 12 | res/values/strings_en.arb 13 | lib/generated/ 14 | example/lib/generated/ 15 | example/.flutter-plugins-dependencies 16 | .dart_tool/ 17 | example/ios/Podfile.lock 18 | .vscode/settings.json 19 | org.eclipse.buildship.core.prefs 20 | .project 21 | .classpath 22 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/.gitmodules -------------------------------------------------------------------------------- /.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: c55f1e93d2fc569e557fbc395da54e2c034980b1 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | git checlanguage: generic 2 | 3 | stages: 4 | - name: self-test 5 | if: type = push 6 | - name: flutter 7 | if: branch = master OR type = pull_request 8 | - name: native 9 | if: branch = master OR type = pull_request 10 | 11 | _flutter_job_template: &_flutter_job_template 12 | before_script: 13 | - git clone https://github.com/flutter/flutter.git -b stable 14 | - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH 15 | script: 16 | - flutter packages get 17 | - flutter test 18 | 19 | _android_job_template: &android_job_template 20 | language: android 21 | android: 22 | components: 23 | - tools 24 | - android-28 25 | - android-29 26 | - build-tools-28.0.3 27 | - build-tools-29.0.5 28 | - extra-android-m2repository 29 | env: 30 | global: 31 | - GRADLE_OPTS="-Xms128m" 32 | - ABI="default;armeabi-v7a" 33 | before_cache: 34 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 35 | cache: 36 | directories: 37 | - $HOME/.m2 38 | - $HOME/.gradle/caches/ 39 | - $HOME/.gradle/wrapper/ 40 | before_script: 41 | - git clone https://github.com/flutter/flutter.git -b stable 42 | - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH 43 | script: 44 | - cd example 45 | - flutter build apk 46 | 47 | _ios_job_template: &ios_job_template 48 | language: objective-c 49 | os: osx 50 | osx_image: xcode12.2 51 | xcode_workspave: example/ios/Runner.xcworkspace 52 | xcode_scheme: Runner 53 | before_script: 54 | - git clone https://github.com/flutter/flutter.git -b stable 55 | - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH 56 | script: 57 | - cd example 58 | - flutter build ios --no-codesign 59 | 60 | matrix: 61 | include: 62 | - <<: *android_job_template 63 | name: Build Android 64 | stage: native 65 | - <<: *ios_job_template 66 | name: Build iOS 67 | stage: native 68 | - <<: *_flutter_job_template 69 | name: Build Flutter 70 | stage: flutter 71 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Example (debug)", 6 | "request": "launch", 7 | "type": "dart", 8 | "flutterMode": "debug" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.4.0 2 | 3 | * Add `BleManager#isClientCreated()` for checking whether native client exists 4 | 5 | 6 | ## 2.3.2 7 | 8 | * Fix lack of disconnection event on iOS if connection failed to be established 9 | 10 | ## 2.3.1 11 | 12 | * Fix connection timeout on iOS 13 | * Fix emitting current connection state on iOS 14 | * Update MBA to 0.1.7: 15 | * Fix lack of disconnection event on iOS when connection fails to be established 16 | * Fix not all errors being passed through onError callbacks on Android (thanks, @eliaslecomte) 17 | 18 | ## 2.3.0 19 | 20 | * add `BleManager.createUnsafePeripheral()` to allow for connecting to known peripheral without launching scan first 21 | **NOTE:** this change will not work with BLEmulator below 1.2.0 22 | 23 | ## 2.2.9 24 | 25 | * Fixed issue with incorrectly typed Stream 26 | 27 | ## 2.2.8 28 | 29 | * Formatted all sources according to dartfmt for consistency 30 | 31 | ## 2.2.7 32 | 33 | * Minor code style fixes. Adjusted device connection state monitoring. 34 | 35 | ## 2.2.6 36 | 37 | * Fixed scan quick failures not being reported to the listener (race condition in scanning_mixin.dart) 38 | 39 | ## 2.2.5 40 | 41 | * add missing handling of destroyClient call on iOS 42 | * fix race condition in ConnectionStateStreamHandler (thanks, @cbreezier!) 43 | * fix issue with picking incorrect characteristic on Android when there are multiple characteristics with the same UUID on the peripheral 44 | * update pubspec format to properly display supported platforms 45 | * fix messages in IllegalStateExceptions on Android 46 | 47 | ## 2.2.4 48 | 49 | * Fix issue where `withResponse` argument was always true when writing to a characteristic on iOS 50 | * Remove unnecessary file that was interfering with analyzer 51 | 52 | ## 2.2.3 53 | 54 | * Fix issue with duplicated or malformed notification values 55 | 56 | ## 2.2.2 57 | 58 | * Fix issue with invalid characteristic value base64 coding when performing characteristic operations on iOS 59 | * Improve documentation 60 | 61 | ## 2.2.1 62 | 63 | * Hide private APIs 64 | * Add setup instructions to README 65 | * Add missing descriptor operations information to README 66 | * Moved permission_handler dependency to its place in example 67 | 68 | ## 2.2.0 69 | 70 | * **NEW** operations on descriptors 71 | * **BREAKING FOR BLEMULATOR** BLEmulator versions lower than 1.1.0 are not supported from this release 72 | * Support for AndroidX 73 | * Support for Swift 5 74 | * Lower iOS deployment target to 8.0 75 | 76 | ## 2.1.0 77 | 78 | * **BREAKING** ScanResult.AdvertisementData.ManufacturerData has changed type from Int8List to Uint8List 79 | * Fix issue with invalid value of manufacturer data in scan results 80 | * Fix issue with initialising plugin from a service without an active activity 81 | * Update README with information about permissions 82 | * Fix issue with default value of MTU when connecting to a peripheral on Android 83 | * Fix issue where first few notifications right after start of monitoring a characteristic could get lost 84 | 85 | ## 2.0.1 86 | 87 | * Fix issue with automatically generated transaction IDs. 88 | 89 | ## 2.0.0 90 | 91 | * Dart 2.0 support 92 | * **BREAKING** Completely new API. Consult [README](https://github.com/Polidea/FlutterBleLib/blob/master/README.md) for instructions on how to use the new version. 93 | * Added [BLEmulator](https://github.com/Polidea/blemulator_flutter) support 94 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | 3 | analyzer: 4 | exclude: [test/**] 5 | 6 | linter: 7 | rules: 8 | prefer_single_quotes: false 9 | omit_local_variable_types: false 10 | unawaited_futures: false 11 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.polidea.flutter_ble_lib' 2 | version '2.3.2' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.3' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | maven { url "https://jitpack.io" } 20 | } 21 | } 22 | 23 | apply plugin: 'com.android.library' 24 | 25 | android { 26 | compileSdkVersion 28 27 | 28 | defaultConfig { 29 | minSdkVersion 18 30 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 31 | } 32 | lintOptions { 33 | disable 'InvalidPackage' 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation 'androidx.annotation:annotation:1.1.0' 39 | implementation 'com.github.Polidea:MultiPlatformBleAdapter:0.1.8' 40 | } 41 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_ble_lib' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/BleErrorFactory.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib; 2 | 3 | import com.polidea.multiplatformbleadapter.errors.BleError; 4 | import com.polidea.multiplatformbleadapter.errors.BleErrorCode; 5 | 6 | 7 | public class BleErrorFactory { 8 | 9 | public static BleError fromThrowable(Throwable exception) { 10 | return new BleError(BleErrorCode.UnknownError, exception.toString(), null); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/CharacteristicsResponse.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib; 2 | 3 | import com.polidea.multiplatformbleadapter.Characteristic; 4 | import com.polidea.multiplatformbleadapter.Service; 5 | 6 | import java.util.List; 7 | 8 | public class CharacteristicsResponse { 9 | private final List characteristics; 10 | private final Service service; 11 | 12 | public CharacteristicsResponse(List characteristics, Service service) { 13 | this.characteristics = characteristics; 14 | this.service = service; 15 | } 16 | 17 | public List getCharacteristics() { 18 | return characteristics; 19 | } 20 | 21 | public Service getService() { 22 | return service; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/ConnectionStateChange.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib; 2 | 3 | import com.polidea.multiplatformbleadapter.ConnectionState; 4 | 5 | public class ConnectionStateChange { 6 | private String peripheralIdentifier; 7 | private ConnectionState connectionState; 8 | 9 | public ConnectionStateChange(String peripheralIdentifier, ConnectionState connectionState) { 10 | this.peripheralIdentifier = peripheralIdentifier; 11 | this.connectionState = connectionState; 12 | } 13 | 14 | public String getPeripheralIdentifier() { 15 | return peripheralIdentifier; 16 | } 17 | 18 | public ConnectionState getConnectionState() { 19 | return connectionState; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/MultiCharacteristicsResponse.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib; 2 | 3 | import com.polidea.multiplatformbleadapter.Characteristic; 4 | import com.polidea.multiplatformbleadapter.Service; 5 | 6 | import java.util.List; 7 | import java.util.UUID; 8 | 9 | public class MultiCharacteristicsResponse { 10 | private final List characteristics; 11 | private int serviceId; 12 | private UUID serviceUuid; 13 | 14 | public MultiCharacteristicsResponse(List characteristics, Service service) { 15 | this.characteristics = characteristics; 16 | this.serviceId = service.getId(); 17 | this.serviceUuid = service.getUuid(); 18 | } 19 | 20 | public MultiCharacteristicsResponse(List characteristics, int serviceId, UUID serviceUuid) { 21 | this.characteristics = characteristics; 22 | this.serviceId = serviceId; 23 | this.serviceUuid = serviceUuid; 24 | } 25 | 26 | public List getCharacteristics() { 27 | return characteristics; 28 | } 29 | 30 | public int getServiceId() { 31 | return serviceId; 32 | } 33 | 34 | public UUID getServiceUuid() { 35 | return serviceUuid; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/SafeMainThreadResolver.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import com.polidea.multiplatformbleadapter.OnErrorCallback; 7 | import com.polidea.multiplatformbleadapter.OnSuccessCallback; 8 | import com.polidea.multiplatformbleadapter.errors.BleError; 9 | 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | 12 | public class SafeMainThreadResolver implements OnSuccessCallback, OnErrorCallback { 13 | 14 | private OnErrorCallback onErrorCallback = null; 15 | private OnSuccessCallback onSuccessCallback = null; 16 | private AtomicBoolean called = new AtomicBoolean(false); 17 | 18 | public SafeMainThreadResolver(OnSuccessCallback onSuccessCallback, OnErrorCallback onErrorCallback) { 19 | this.onErrorCallback = onErrorCallback; 20 | this.onSuccessCallback = onSuccessCallback; 21 | } 22 | 23 | public void onSuccess(final T data) { 24 | if (onSuccessCallback != null && called.compareAndSet(false, true)) { 25 | new Handler(Looper.getMainLooper()).post(new Runnable() { 26 | @Override 27 | public void run() { 28 | onSuccessCallback.onSuccess(data); 29 | } 30 | }); 31 | } 32 | } 33 | 34 | public void onError(final BleError error) { 35 | if (onErrorCallback != null && called.compareAndSet(false, true)) { 36 | new Handler(Looper.getMainLooper()).post(new Runnable() { 37 | @Override 38 | public void run() { 39 | onErrorCallback.onError(error); 40 | } 41 | }); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/SingleCharacteristicResponse.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib; 2 | 3 | import com.polidea.multiplatformbleadapter.Characteristic; 4 | 5 | import java.util.UUID; 6 | 7 | public class SingleCharacteristicResponse { 8 | private final Characteristic characteristic; 9 | private int serviceId; 10 | private UUID serviceUuid; 11 | private String transactionId; 12 | 13 | public SingleCharacteristicResponse(Characteristic characteristics, int serviceId, UUID serviceUuid, 14 | String transactionId) { 15 | this.characteristic = characteristics; 16 | this.serviceId = serviceId; 17 | this.serviceUuid = serviceUuid; 18 | this.transactionId = transactionId; 19 | } 20 | 21 | public Characteristic getCharacteristic() { 22 | return characteristic; 23 | } 24 | 25 | public int getServiceId() { 26 | return serviceId; 27 | } 28 | 29 | public UUID getServiceUuid() { 30 | return serviceUuid; 31 | } 32 | 33 | public String getTransactionId() { 34 | return transactionId; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/constant/ArgumentKey.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.constant; 2 | 3 | public interface ArgumentKey { 4 | String RESTORE_STATE_IDENTIFIER = "restoreStateIdentifier"; 5 | String SCAN_MODE = "scanMode"; 6 | String CALLBACK_TYPE = "callbackType"; 7 | String UUIDS = "uuids"; 8 | 9 | String TRANSACTION_ID = "transactionId"; 10 | 11 | String DEVICE_IDENTIFIER = "deviceIdentifier"; 12 | String IS_AUTO_CONNECT = "isAutoConnect"; 13 | String REQUEST_MTU = "requestMtu"; 14 | String REFRESH_GATT = "refreshGatt"; 15 | String TIMEOUT_MILLIS = "timeout"; 16 | String EMIT_CURRENT_VALUE = "emitCurrentValue"; 17 | 18 | String LOG_LEVEL = "logLevel"; 19 | 20 | String SERVICE_UUID = "serviceUuid"; 21 | String SERVICE_IDENTIFIER = "serviceId"; 22 | String CHARACTERISTIC_UUID = "characteristicUuid"; 23 | String CHARACTERISTIC_IDENTIFIER = "characteristicIdentifier"; 24 | String DESCRIPTOR_UUID = "descriptorUuid"; 25 | String DESCRIPTOR_IDENTIFIER = "descriptorIdentifier"; 26 | String VALUE = "value"; 27 | String WITH_RESPONSE = "withResponse"; 28 | 29 | String MTU = "mtu"; 30 | 31 | String DEVICE_IDENTIFIERS = "deviceIdentifiers"; 32 | } -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/constant/ChannelName.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.constant; 2 | 3 | public interface ChannelName { 4 | String FLUTTER_BLE_LIB = "flutter_ble_lib"; 5 | String ADAPTER_STATE_CHANGES = FLUTTER_BLE_LIB + "/stateChanges"; 6 | String STATE_RESTORE_EVENTS = FLUTTER_BLE_LIB + "/stateRestoreEvents"; 7 | String SCANNING_EVENTS = FLUTTER_BLE_LIB + "/scanningEvents"; 8 | String CONNECTION_STATE_CHANGE_EVENTS = FLUTTER_BLE_LIB + "/connectionStateChangeEvents"; 9 | String MONITOR_CHARACTERISTIC = FLUTTER_BLE_LIB + "/monitorCharacteristic"; 10 | } 11 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/constant/MethodName.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.constant; 2 | 3 | public interface MethodName { 4 | String IS_CLIENT_CREATED = "isClientCreated"; 5 | String CREATE_CLIENT = "createClient"; 6 | String DESTROY_CLIENT = "destroyClient"; 7 | 8 | String CANCEL_TRANSACTION = "cancelTransaction"; 9 | 10 | String GET_STATE = "getState"; 11 | 12 | String ENABLE_RADIO = "enableRadio"; 13 | String DISABLE_RADIO = "disableRadio"; 14 | 15 | String START_DEVICE_SCAN = "startDeviceScan"; 16 | String STOP_DEVICE_SCAN = "stopDeviceScan"; 17 | 18 | String CONNECT_TO_DEVICE = "connectToDevice"; 19 | String IS_DEVICE_CONNECTED = "isDeviceConnected"; 20 | String OBSERVE_CONNECTION_STATE = "observeConnectionState"; 21 | String CANCEL_CONNECTION = "cancelConnection"; 22 | 23 | String DISCOVER_ALL_SERVICES_AND_CHARACTERISTICS = "discoverAllServicesAndCharacteristics"; 24 | String GET_SERVICES = "services"; 25 | String GET_CHARACTERISTICS = "characteristics"; 26 | String GET_CHARACTERISTICS_FOR_SERVICE = "characteristicsForService"; 27 | String GET_DESCRIPTORS_FOR_DEVICE = "descriptorsForDevice"; 28 | String GET_DESCRIPTORS_FOR_CHARACTERISTIC = "descriptorsForCharacteristic"; 29 | String GET_DESCRIPTORS_FOR_SERVICE = "descriptorsForService"; 30 | 31 | String LOG_LEVEL = "logLevel"; 32 | String SET_LOG_LEVEL = "setLogLevel"; 33 | 34 | String RSSI = "rssi"; 35 | 36 | String REQUEST_MTU = "requestMtu"; 37 | 38 | String GET_CONNECTED_DEVICES = "getConnectedDevices"; 39 | String GET_KNOWN_DEVICES = "getKnownDevices"; 40 | 41 | String READ_CHARACTERISTIC_FOR_IDENTIFIER = "readCharacteristicForIdentifier"; 42 | String READ_CHARACTERISTIC_FOR_DEVICE = "readCharacteristicForDevice"; 43 | String READ_CHARACTERISTIC_FOR_SERVICE = "readCharacteristicForService"; 44 | 45 | String WRITE_CHARACTERISTIC_FOR_IDENTIFIER = "writeCharacteristicForIdentifier"; 46 | String WRITE_CHARACTERISTIC_FOR_DEVICE = "writeCharacteristicForDevice"; 47 | String WRITE_CHARACTERISTIC_FOR_SERVICE = "writeCharacteristicForService"; 48 | 49 | String MONITOR_CHARACTERISTIC_FOR_IDENTIFIER = "monitorCharacteristicForIdentifier"; 50 | String MONITOR_CHARACTERISTIC_FOR_DEVICE = "monitorCharacteristicForDevice"; 51 | String MONITOR_CHARACTERISTIC_FOR_SERVICE = "monitorCharacteristicForService"; 52 | 53 | String READ_DESCRIPTOR_FOR_IDENTIFIER = "readDescriptorForIdentifier"; 54 | String READ_DESCRIPTOR_FOR_CHARACTERISTIC = "readDescriptorForCharacteristic"; 55 | String READ_DESCRIPTOR_FOR_SERVICE = "readDescriptorForService"; 56 | String READ_DESCRIPTOR_FOR_DEVICE = "readDescriptorForDevice"; 57 | 58 | String WRITE_DESCRIPTOR_FOR_IDENTIFIER = "writeDescriptorForIdentifier"; 59 | String WRITE_DESCRIPTOR_FOR_CHARACTERISTIC = "writeDescriptorForCharacteristic"; 60 | String WRITE_DESCRIPTOR_FOR_SERVICE = "writeDescriptorForService"; 61 | String WRITE_DESCRIPTOR_FOR_DEVICE = "writeDescriptorForDevice"; 62 | } 63 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/converter/BleErrorJsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.converter; 2 | 3 | 4 | import com.polidea.multiplatformbleadapter.errors.BleError; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | public class BleErrorJsonConverter implements JsonConverter { 12 | 13 | public static final int MAX_ATT_ERROR = 0x80; 14 | 15 | private interface Metadata { 16 | String ERROR_CODE = "errorCode"; 17 | String ATT_ERROR_CODE = "attErrorCode"; 18 | String ANDROID_ERROR_CODE = "androidErrorCode"; 19 | String REASON = "reason"; 20 | String DEVICE_ID = "deviceID"; 21 | String SERVICE_UUID = "serviceUUID"; 22 | String CHARACTERISTIC_UUID = "characteristicUUID"; 23 | String DESCRIPTOR_UUID = "descriptorUUID"; 24 | String INTERNAL_MESSAGE = "internalMessage"; 25 | String TRANSACTION_ID = "transactionId"; 26 | } 27 | 28 | @Override 29 | @Nullable 30 | public String toJson(BleError error) { 31 | try { 32 | return toJsonObject(error).toString(); 33 | } catch (JSONException e) { 34 | e.printStackTrace(); 35 | return null; 36 | } 37 | } 38 | 39 | @Nullable 40 | public String toJson(BleError error, String transactionId) { 41 | try { 42 | JSONObject root = toJsonObject(error); 43 | root.put(Metadata.TRANSACTION_ID, transactionId != null ? transactionId : JSONObject.NULL); 44 | return root.toString(); 45 | } catch (JSONException e) { 46 | e.printStackTrace(); 47 | return null; 48 | } 49 | } 50 | 51 | private JSONObject toJsonObject(BleError error) throws JSONException { 52 | JSONObject root = new JSONObject(); 53 | root.put(Metadata.ERROR_CODE, error.errorCode.code); 54 | if (error.androidCode == null || error.androidCode > MAX_ATT_ERROR || error.androidCode < 0) { 55 | root.put(Metadata.ATT_ERROR_CODE, JSONObject.NULL); 56 | } else { 57 | root.put(Metadata.ATT_ERROR_CODE, error.androidCode.intValue()); 58 | } 59 | if (error.androidCode == null || error.androidCode <= MAX_ATT_ERROR) { 60 | root.put(Metadata.ANDROID_ERROR_CODE, JSONObject.NULL); 61 | } else { 62 | root.put(Metadata.ANDROID_ERROR_CODE, error.androidCode.intValue()); 63 | } 64 | root.put(Metadata.REASON, error.reason); 65 | root.put(Metadata.DEVICE_ID, error.deviceID); 66 | root.put(Metadata.SERVICE_UUID, error.serviceUUID); 67 | root.put(Metadata.CHARACTERISTIC_UUID, error.characteristicUUID); 68 | root.put(Metadata.DESCRIPTOR_UUID, error.descriptorUUID); 69 | root.put(Metadata.INTERNAL_MESSAGE, error.internalMessage); 70 | return root; 71 | } 72 | } -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/converter/CharacteristicJsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.converter; 2 | 3 | import com.polidea.multiplatformbleadapter.Characteristic; 4 | import com.polidea.multiplatformbleadapter.utils.Base64Converter; 5 | 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import java.util.List; 11 | 12 | public class CharacteristicJsonConverter implements JsonConverter { 13 | 14 | private interface Metadata { 15 | String UUID = "characteristicUuid"; 16 | String ID = "id"; 17 | String IS_READABLE = "isReadable"; 18 | String IS_WRITABLE_WITH_RESPONSE = "isWritableWithResponse"; 19 | String IS_WRITABLE_WITHOUT_RESPONSE = "isWritableWithoutResponse"; 20 | String IS_NOTIFIABLE = "isNotifiable"; 21 | String IS_INDICATABLE = "isIndicatable"; 22 | String VALUE = "value"; 23 | } 24 | 25 | @Override 26 | public String toJson(Characteristic characteristic) throws JSONException { 27 | return toJsonObject(characteristic).toString(); 28 | } 29 | 30 | public String toJson(List characteristics) throws JSONException { 31 | return toJsonArray(characteristics).toString(); 32 | } 33 | 34 | public JSONArray toJsonArray(List characteristics) throws JSONException { 35 | JSONArray jsonArray = new JSONArray(); 36 | for (Characteristic characteristic : characteristics) { 37 | jsonArray.put(toJsonObject(characteristic)); 38 | } 39 | return jsonArray; 40 | } 41 | 42 | public JSONObject toJsonObject(Characteristic characteristic) throws JSONException { 43 | JSONObject jsonObject = new JSONObject(); 44 | 45 | jsonObject.put(Metadata.UUID, characteristic.getUuid()); 46 | jsonObject.put(Metadata.ID, characteristic.getId()); 47 | jsonObject.put(Metadata.IS_READABLE, characteristic.isReadable()); 48 | jsonObject.put(Metadata.IS_WRITABLE_WITH_RESPONSE, characteristic.isWritableWithResponse()); 49 | jsonObject.put(Metadata.IS_WRITABLE_WITHOUT_RESPONSE, characteristic.isWritableWithoutResponse()); 50 | jsonObject.put(Metadata.IS_NOTIFIABLE, characteristic.isNotifiable()); 51 | jsonObject.put(Metadata.IS_INDICATABLE, characteristic.isIndicatable()); 52 | jsonObject.put(Metadata.VALUE, characteristic.getValue() != null ? 53 | Base64Converter.encode(characteristic.getValue()) : JSONObject.NULL); 54 | 55 | return jsonObject; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/converter/ConnectionStateChangeJsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.converter; 2 | 3 | 4 | import com.polidea.flutter_ble_lib.ConnectionStateChange; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | public class ConnectionStateChangeJsonConverter implements JsonConverter { 12 | 13 | private interface Metadata { 14 | String PERIPHERAL_IDENTIFIER = "peripheralIdentifier"; 15 | String CONNECTION_STATE = "connectionState"; 16 | } 17 | 18 | @Nullable 19 | @Override 20 | public String toJson(ConnectionStateChange value) throws JSONException { 21 | JSONObject jsonObject = new JSONObject(); 22 | 23 | jsonObject.put(Metadata.PERIPHERAL_IDENTIFIER, value.getPeripheralIdentifier()); 24 | jsonObject.put(Metadata.CONNECTION_STATE, value.getConnectionState().value); 25 | 26 | return jsonObject.toString(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/converter/DescriptorJsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.converter; 2 | 3 | import com.polidea.multiplatformbleadapter.Descriptor; 4 | import com.polidea.multiplatformbleadapter.utils.Base64Converter; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | public class DescriptorJsonConverter implements JsonConverter { 10 | 11 | 12 | private interface Metadata { 13 | String SERVICE_UUID = "serviceUuid"; 14 | String SERVICE_ID = "serviceId"; 15 | String CHARACTERISTIC_UUID = "characteristicUuid"; 16 | String CHARACTERISTIC_ID = "id"; 17 | String DESCRIPTOR_UUID = "descriptorUuid"; 18 | String DESCRIPTOR_ID = "descriptorId"; 19 | String DESCRIPTOR_VALUE = "value"; 20 | } 21 | 22 | @Override 23 | public String toJson(Descriptor descriptor) throws JSONException { 24 | JSONObject jsonObject = toJsonObject(descriptor); 25 | 26 | jsonObject.put(Metadata.SERVICE_ID, descriptor.getServiceId()); 27 | jsonObject.put(Metadata.SERVICE_UUID, descriptor.getServiceUuid()); 28 | jsonObject.put(Metadata.CHARACTERISTIC_ID, descriptor.getCharacteristicId()); 29 | jsonObject.put(Metadata.CHARACTERISTIC_UUID, descriptor.getCharacteristicUuid()); 30 | 31 | return jsonObject.toString(); 32 | } 33 | 34 | public JSONObject toJsonObject(Descriptor descriptor) throws JSONException { 35 | JSONObject jsonObject = new JSONObject(); 36 | 37 | 38 | jsonObject.put(Metadata.DESCRIPTOR_ID, descriptor.getId()); 39 | jsonObject.put(Metadata.DESCRIPTOR_UUID, descriptor.getUuid()); 40 | jsonObject.put(Metadata.DESCRIPTOR_VALUE, 41 | descriptor.getValue() != null ? 42 | Base64Converter.encode(descriptor.getValue()) 43 | : JSONObject.NULL); 44 | 45 | return jsonObject; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/converter/DevicesResultJsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.converter; 2 | 3 | import android.util.Log; 4 | 5 | import com.polidea.multiplatformbleadapter.Device; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | import androidx.annotation.Nullable; 12 | 13 | public class DevicesResultJsonConverter implements JsonConverter { 14 | 15 | public static String TAG = DevicesResultJsonConverter.class.getName(); 16 | 17 | private interface Metadata { 18 | String ID = "id"; 19 | String NAME = "name"; 20 | } 21 | 22 | @Override 23 | @Nullable 24 | public String toJson(Device[] devices) throws JSONException { 25 | JSONArray jsonDevicesArray = new JSONArray(); 26 | 27 | for (Device device : devices) { 28 | Log.d(TAG, "try to parse json " + device.toString()); 29 | JSONObject jsonDevice = new JSONObject(); 30 | jsonDevice.put(Metadata.ID, device.getId()); 31 | jsonDevice.put(Metadata.NAME, device.getName()); 32 | jsonDevicesArray.put(jsonDevice); 33 | } 34 | 35 | return jsonDevicesArray.toString(); 36 | } 37 | } -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/converter/JsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.converter; 2 | 3 | import org.json.JSONException; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | public interface JsonConverter { 8 | 9 | @Nullable 10 | String toJson(T value) throws JSONException; 11 | } 12 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/converter/MultiCharacteristicsResponseJsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.converter; 2 | 3 | import com.polidea.flutter_ble_lib.MultiCharacteristicsResponse; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | public class MultiCharacteristicsResponseJsonConverter implements JsonConverter { 10 | 11 | private interface Metadata { 12 | String UUID = "serviceUuid"; 13 | String ID = "serviceId"; 14 | String CHARACTERISTICS = "characteristics"; 15 | } 16 | 17 | @Override 18 | public String toJson(MultiCharacteristicsResponse characteristicsResponse) throws JSONException { 19 | JSONObject jsonObject = new JSONObject(); 20 | 21 | jsonObject.put(Metadata.UUID, characteristicsResponse.getServiceUuid()); 22 | jsonObject.put(Metadata.ID, characteristicsResponse.getServiceId()); 23 | 24 | JSONArray jsonArray = new CharacteristicJsonConverter().toJsonArray(characteristicsResponse.getCharacteristics()); 25 | 26 | jsonObject.put(Metadata.CHARACTERISTICS, jsonArray); 27 | return jsonObject.toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/converter/MultiDescriptorsResponseJsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.converter; 2 | 3 | import com.polidea.multiplatformbleadapter.Descriptor; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | import java.util.List; 10 | 11 | public class MultiDescriptorsResponseJsonConverter implements JsonConverter> { 12 | 13 | private DescriptorJsonConverter descriptorJsonConverter = new DescriptorJsonConverter(); 14 | 15 | private interface Metadata { 16 | String SERVICE_UUID = "serviceUuid"; 17 | String SERVICE_ID = "serviceId"; 18 | String CHARACTERISTIC_UUID = "characteristicUuid"; 19 | String CHARACTERISTIC_ID = "id"; 20 | String DESCRIPTORS = "descriptors"; 21 | } 22 | 23 | @Override 24 | public String toJson(List descriptors) throws JSONException { 25 | JSONObject root = new JSONObject(); 26 | JSONArray array = new JSONArray(); 27 | if (descriptors.size() > 0) { 28 | root.put(Metadata.SERVICE_ID, descriptors.get(0).getServiceId()); 29 | root.put(Metadata.SERVICE_UUID, descriptors.get(0).getServiceUuid()); 30 | root.put(Metadata.CHARACTERISTIC_ID, descriptors.get(0).getCharacteristicId()); 31 | root.put(Metadata.CHARACTERISTIC_UUID, descriptors.get(0).getCharacteristicUuid()); 32 | 33 | for (Descriptor descriptor : descriptors) { 34 | array.put(descriptorJsonConverter.toJsonObject(descriptor)); 35 | } 36 | 37 | } 38 | root.put(Metadata.DESCRIPTORS, array); 39 | 40 | return root.toString(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/converter/ServiceJsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.converter; 2 | 3 | import com.polidea.multiplatformbleadapter.Service; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | import java.util.List; 10 | 11 | public class ServiceJsonConverter implements JsonConverter { 12 | 13 | private interface Metadata { 14 | String uuid = "serviceUuid"; 15 | String id = "serviceId"; 16 | } 17 | 18 | @Override 19 | public String toJson(Service service) throws JSONException { 20 | return toJsonObject(service).toString(); 21 | } 22 | 23 | public String toJson(List services) throws JSONException { 24 | JSONArray array = new JSONArray(); 25 | for (Service service : services) { 26 | array.put(toJsonObject(service)); 27 | } 28 | 29 | return array.toString(); 30 | } 31 | 32 | private JSONObject toJsonObject(Service service) throws JSONException { 33 | JSONObject jsonObject = new JSONObject(); 34 | 35 | jsonObject.put(Metadata.id, service.getId()); 36 | jsonObject.put(Metadata.uuid, service.getUuid()); 37 | 38 | return jsonObject; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/converter/SingleCharacteristicResponseJsonConverter.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.converter; 2 | 3 | import com.polidea.flutter_ble_lib.SingleCharacteristicResponse; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | public class SingleCharacteristicResponseJsonConverter implements JsonConverter { 11 | 12 | private interface Metadata { 13 | String UUID = "serviceUuid"; 14 | String ID = "serviceId"; 15 | String CHARACTERISTIC = "characteristic"; 16 | String TRANSACTION_ID = "transactionId"; 17 | } 18 | 19 | private CharacteristicJsonConverter characteristicJsonConverter = new CharacteristicJsonConverter(); 20 | 21 | @Nullable 22 | @Override 23 | public String toJson(SingleCharacteristicResponse value) throws JSONException { 24 | JSONObject jsonObject = new JSONObject(); 25 | 26 | jsonObject.put(Metadata.UUID, value.getServiceUuid()); 27 | jsonObject.put(Metadata.ID, value.getServiceId()); 28 | jsonObject.put(Metadata.TRANSACTION_ID, value.getTransactionId()); 29 | 30 | jsonObject.put(Metadata.CHARACTERISTIC, characteristicJsonConverter.toJsonObject(value.getCharacteristic())); 31 | return jsonObject.toString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/delegate/BluetoothStateDelegate.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.delegate; 2 | 3 | import com.polidea.flutter_ble_lib.constant.ArgumentKey; 4 | import com.polidea.flutter_ble_lib.constant.MethodName; 5 | import com.polidea.flutter_ble_lib.converter.BleErrorJsonConverter; 6 | import com.polidea.multiplatformbleadapter.BleAdapter; 7 | import com.polidea.multiplatformbleadapter.OnErrorCallback; 8 | import com.polidea.multiplatformbleadapter.OnSuccessCallback; 9 | import com.polidea.multiplatformbleadapter.errors.BleError; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | import androidx.annotation.NonNull; 15 | import io.flutter.plugin.common.MethodCall; 16 | import io.flutter.plugin.common.MethodChannel; 17 | 18 | public class BluetoothStateDelegate extends CallDelegate { 19 | 20 | private static List supportedMethods = Arrays.asList( 21 | MethodName.ENABLE_RADIO, 22 | MethodName.DISABLE_RADIO, 23 | MethodName.GET_STATE); 24 | 25 | private BleAdapter bleAdapter; 26 | private BleErrorJsonConverter bleErrorJsonConverter = new BleErrorJsonConverter(); 27 | 28 | public BluetoothStateDelegate(BleAdapter bleAdapter) { 29 | super(supportedMethods); 30 | this.bleAdapter = bleAdapter; 31 | } 32 | 33 | @Override 34 | public void onMethodCall(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { 35 | switch (methodCall.method) { 36 | case MethodName.ENABLE_RADIO: 37 | enableRadio(methodCall.argument(ArgumentKey.TRANSACTION_ID), result); 38 | return; 39 | case MethodName.DISABLE_RADIO: 40 | disableRadio(methodCall.argument(ArgumentKey.TRANSACTION_ID), result); 41 | return; 42 | case MethodName.GET_STATE: 43 | getState(result); 44 | return; 45 | default: 46 | throw new IllegalArgumentException(methodCall.method + " cannot be handle by this delegate"); 47 | } 48 | } 49 | 50 | private void enableRadio(String transactionId, @NonNull final MethodChannel.Result result) { 51 | bleAdapter.enable(transactionId, 52 | new OnSuccessCallback() { 53 | @Override 54 | public void onSuccess(Void data) { 55 | result.success(null); 56 | } 57 | }, new OnErrorCallback() { 58 | @Override 59 | public void onError(BleError error) { 60 | result.error(String.valueOf(error.errorCode.code), error.reason, bleErrorJsonConverter.toJson(error)); 61 | } 62 | }); 63 | } 64 | 65 | private void disableRadio(String transactionId, @NonNull final MethodChannel.Result result) { 66 | bleAdapter.disable(transactionId, 67 | new OnSuccessCallback() { 68 | @Override 69 | public void onSuccess(Void data) { 70 | result.success(null); 71 | } 72 | }, new OnErrorCallback() { 73 | @Override 74 | public void onError(BleError error) { 75 | result.error(String.valueOf(error.errorCode.code), error.reason, bleErrorJsonConverter.toJson(error)); 76 | } 77 | }); 78 | } 79 | 80 | private void getState(@NonNull final MethodChannel.Result result) { 81 | result.success(bleAdapter.getCurrentState()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/delegate/CallDelegate.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.delegate; 2 | 3 | import java.util.List; 4 | 5 | import io.flutter.plugin.common.MethodCall; 6 | import io.flutter.plugin.common.MethodChannel; 7 | 8 | abstract public class CallDelegate implements MethodChannel.MethodCallHandler { 9 | 10 | final List supportedMethods; 11 | 12 | CallDelegate(List supportedMethods) { 13 | this.supportedMethods = supportedMethods; 14 | } 15 | 16 | public boolean canHandle(MethodCall call) { 17 | return supportedMethods.contains(call.method); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/delegate/LogLevelDelegate.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.delegate; 2 | 3 | import android.util.Log; 4 | 5 | import com.polidea.flutter_ble_lib.constant.ArgumentKey; 6 | import com.polidea.flutter_ble_lib.constant.MethodName; 7 | import com.polidea.multiplatformbleadapter.BleAdapter; 8 | import com.polidea.multiplatformbleadapter.errors.BleErrorCode; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | import androidx.annotation.NonNull; 14 | import io.flutter.plugin.common.MethodCall; 15 | import io.flutter.plugin.common.MethodChannel; 16 | 17 | public class LogLevelDelegate extends CallDelegate { 18 | 19 | private static final String TAG = LogLevelDelegate.class.getName(); 20 | private static List supportedMethods = Arrays.asList(MethodName.LOG_LEVEL, MethodName.SET_LOG_LEVEL); 21 | 22 | private BleAdapter bleAdapter; 23 | 24 | public LogLevelDelegate(BleAdapter bleAdapter) { 25 | super(supportedMethods); 26 | this.bleAdapter = bleAdapter; 27 | } 28 | 29 | @Override 30 | public void onMethodCall(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { 31 | switch (methodCall.method) { 32 | case MethodName.SET_LOG_LEVEL: 33 | setLogLevel(methodCall.argument(ArgumentKey.LOG_LEVEL), result); 34 | return; 35 | case MethodName.LOG_LEVEL: 36 | logLevel(result); 37 | return; 38 | default: 39 | throw new IllegalArgumentException(methodCall.method + " cannot be handled by this delegate"); 40 | } 41 | } 42 | 43 | private void logLevel(@NonNull final MethodChannel.Result result) { 44 | result.success(bleAdapter.getLogLevel().toUpperCase()); 45 | } 46 | 47 | private void setLogLevel(@NonNull String logLevel, @NonNull final MethodChannel.Result result) { 48 | try { 49 | Log.d(TAG, "set log level to: " + logLevel); 50 | bleAdapter.setLogLevel(capitalizeFirstLetter(logLevel)); 51 | result.success(null); 52 | } catch(Exception e) { 53 | Log.e(TAG, "setLogLevel error", e); 54 | result.error(String.valueOf(BleErrorCode.UnknownError), e.getMessage(), null); 55 | } 56 | } 57 | 58 | private String capitalizeFirstLetter(String text) { 59 | return text.length() == 0 ? text : text.substring(0, 1).toUpperCase() + text.substring(1); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/delegate/MtuDelegate.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.delegate; 2 | 3 | import android.util.Log; 4 | 5 | import com.polidea.flutter_ble_lib.SafeMainThreadResolver; 6 | import com.polidea.flutter_ble_lib.constant.ArgumentKey; 7 | import com.polidea.flutter_ble_lib.constant.MethodName; 8 | import com.polidea.flutter_ble_lib.converter.BleErrorJsonConverter; 9 | import com.polidea.multiplatformbleadapter.BleAdapter; 10 | import com.polidea.multiplatformbleadapter.Device; 11 | import com.polidea.multiplatformbleadapter.OnErrorCallback; 12 | import com.polidea.multiplatformbleadapter.OnSuccessCallback; 13 | import com.polidea.multiplatformbleadapter.errors.BleError; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | import androidx.annotation.NonNull; 19 | import io.flutter.plugin.common.MethodCall; 20 | import io.flutter.plugin.common.MethodChannel; 21 | 22 | public class MtuDelegate extends CallDelegate { 23 | 24 | private static final String TAG = MtuDelegate.class.getName(); 25 | private static List supportedMethods = Arrays.asList(MethodName.REQUEST_MTU); 26 | 27 | private BleAdapter bleAdapter; 28 | private BleErrorJsonConverter bleErrorJsonConverter = new BleErrorJsonConverter(); 29 | 30 | public MtuDelegate(BleAdapter bleAdapter) { 31 | super(supportedMethods); 32 | this.bleAdapter = bleAdapter; 33 | } 34 | 35 | @Override 36 | public void onMethodCall(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { 37 | switch (methodCall.method) { 38 | case MethodName.REQUEST_MTU: 39 | requestMtu( 40 | methodCall.argument(ArgumentKey.DEVICE_IDENTIFIER), 41 | methodCall.argument(ArgumentKey.MTU), 42 | methodCall.argument(ArgumentKey.TRANSACTION_ID), 43 | result); 44 | return; 45 | default: 46 | throw new IllegalArgumentException(methodCall.method + " cannot be handled by this delegate"); 47 | } 48 | } 49 | 50 | private void requestMtu(String deviceIdentifier, @NonNull int mtu, String transactionId, @NonNull final MethodChannel.Result result) { 51 | Log.d(TAG, "Request MTU " + mtu); 52 | 53 | final SafeMainThreadResolver resolver = new SafeMainThreadResolver<>( 54 | new OnSuccessCallback() { 55 | @Override 56 | public void onSuccess(Integer mtu) { 57 | result.success(mtu); 58 | } 59 | }, 60 | new OnErrorCallback() { 61 | @Override 62 | public void onError(BleError error) { 63 | Log.e(TAG, "MTU request error " + error.reason + " " + error.internalMessage); 64 | result.error(String.valueOf(error.errorCode.code), error.reason, bleErrorJsonConverter.toJson(error)); 65 | } 66 | }); 67 | 68 | bleAdapter.requestMTUForDevice(deviceIdentifier, mtu, transactionId, new OnSuccessCallback() { 69 | @Override 70 | public void onSuccess(Device device) { 71 | resolver.onSuccess(device.getMtu()); 72 | } 73 | }, new OnErrorCallback() { 74 | @Override 75 | public void onError(BleError error) { 76 | resolver.onError(error); 77 | } 78 | }); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/delegate/RssiDelegate.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.delegate; 2 | 3 | import android.util.Log; 4 | 5 | import com.polidea.flutter_ble_lib.SafeMainThreadResolver; 6 | import com.polidea.flutter_ble_lib.constant.ArgumentKey; 7 | import com.polidea.flutter_ble_lib.constant.MethodName; 8 | import com.polidea.flutter_ble_lib.converter.BleErrorJsonConverter; 9 | import com.polidea.multiplatformbleadapter.BleAdapter; 10 | import com.polidea.multiplatformbleadapter.Device; 11 | import com.polidea.multiplatformbleadapter.OnErrorCallback; 12 | import com.polidea.multiplatformbleadapter.OnSuccessCallback; 13 | import com.polidea.multiplatformbleadapter.errors.BleError; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | import androidx.annotation.NonNull; 19 | import io.flutter.plugin.common.MethodCall; 20 | import io.flutter.plugin.common.MethodChannel; 21 | 22 | public class RssiDelegate extends CallDelegate { 23 | 24 | private static final String TAG = RssiDelegate.class.getName(); 25 | private static List supportedMethods = Arrays.asList(MethodName.RSSI); 26 | 27 | private BleAdapter bleAdapter; 28 | private BleErrorJsonConverter bleErrorJsonConverter = new BleErrorJsonConverter(); 29 | 30 | public RssiDelegate(BleAdapter bleAdapter) { 31 | super(supportedMethods); 32 | this.bleAdapter = bleAdapter; 33 | } 34 | 35 | @Override 36 | public void onMethodCall(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) { 37 | switch (methodCall.method) { 38 | case MethodName.RSSI: 39 | rssi(methodCall.argument(ArgumentKey.DEVICE_IDENTIFIER), 40 | methodCall.argument(ArgumentKey.TRANSACTION_ID), 41 | result); 42 | return; 43 | default: 44 | throw new IllegalArgumentException(methodCall.method + " cannot be handled by this delegate"); 45 | } 46 | } 47 | 48 | private void rssi(@NonNull final String deviceIdentifier, String transactionId, @NonNull final MethodChannel.Result result) { 49 | Log.d(TAG, "Read rssi for device " + deviceIdentifier + " transactionId: " + transactionId); 50 | 51 | final SafeMainThreadResolver resolver = new SafeMainThreadResolver<>( 52 | new OnSuccessCallback() { 53 | @Override 54 | public void onSuccess(Integer rssi) { 55 | result.success(rssi); 56 | } 57 | }, 58 | new OnErrorCallback() { 59 | @Override 60 | public void onError(BleError error) { 61 | Log.e(TAG, "RSSI error " + error.reason + " " + error.internalMessage); 62 | result.error(String.valueOf(error.errorCode.code), error.reason, bleErrorJsonConverter.toJson(error)); 63 | } 64 | }); 65 | 66 | bleAdapter.readRSSIForDevice(deviceIdentifier, transactionId, new OnSuccessCallback() { 67 | @Override 68 | public void onSuccess(Device device) { 69 | Log.d(TAG, "rssi ready on native side: " + device.getRssi()); 70 | resolver.onSuccess(device.getRssi()); 71 | } 72 | }, new OnErrorCallback() { 73 | @Override 74 | public void onError(BleError error) { 75 | resolver.onError(error); 76 | } 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/event/AdapterStateStreamHandler.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.event; 2 | 3 | 4 | import io.flutter.plugin.common.EventChannel; 5 | 6 | public class AdapterStateStreamHandler implements EventChannel.StreamHandler { 7 | 8 | private EventChannel.EventSink adapterStateSink; 9 | 10 | @Override 11 | public void onListen(Object o, EventChannel.EventSink eventSink) { 12 | adapterStateSink = eventSink; 13 | } 14 | 15 | @Override 16 | public void onCancel(Object o) { 17 | adapterStateSink = null; 18 | } 19 | 20 | public void onNewAdapterState(String bluetoothAdapterState) { 21 | if (adapterStateSink != null) { 22 | adapterStateSink.success(bluetoothAdapterState); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/event/CharacteristicsMonitorStreamHandler.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.event; 2 | 3 | import com.polidea.flutter_ble_lib.SingleCharacteristicResponse; 4 | import com.polidea.flutter_ble_lib.converter.BleErrorJsonConverter; 5 | import com.polidea.flutter_ble_lib.converter.SingleCharacteristicResponseJsonConverter; 6 | import com.polidea.multiplatformbleadapter.errors.BleError; 7 | 8 | import org.json.JSONException; 9 | 10 | import io.flutter.plugin.common.EventChannel; 11 | 12 | public class CharacteristicsMonitorStreamHandler implements EventChannel.StreamHandler { 13 | 14 | private EventChannel.EventSink eventSink; 15 | private SingleCharacteristicResponseJsonConverter characteristicResponseJsonConverter 16 | = new SingleCharacteristicResponseJsonConverter(); 17 | private BleErrorJsonConverter bleErrorJsonConverter = new BleErrorJsonConverter(); 18 | 19 | @Override 20 | synchronized public void onListen(Object o, EventChannel.EventSink eventSink) { 21 | this.eventSink = eventSink; 22 | } 23 | 24 | @Override 25 | synchronized public void onCancel(Object o) { 26 | eventSink = null; 27 | } 28 | 29 | synchronized public void onCharacteristicsUpdate(SingleCharacteristicResponse characteristic) throws JSONException { 30 | if (eventSink != null) { 31 | eventSink.success(characteristicResponseJsonConverter.toJson(characteristic)); 32 | } 33 | } 34 | 35 | synchronized public void onError(BleError error, String transactionId) { 36 | if (eventSink != null) { 37 | eventSink.error(String.valueOf(error.errorCode.code), error.reason, bleErrorJsonConverter.toJson(error, transactionId)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/event/ConnectionStateStreamHandler.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.event; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import com.polidea.flutter_ble_lib.ConnectionStateChange; 7 | import com.polidea.flutter_ble_lib.converter.BleErrorJsonConverter; 8 | import com.polidea.flutter_ble_lib.converter.ConnectionStateChangeJsonConverter; 9 | 10 | import org.json.JSONException; 11 | 12 | import io.flutter.plugin.common.EventChannel; 13 | 14 | public class ConnectionStateStreamHandler implements EventChannel.StreamHandler { 15 | private EventChannel.EventSink eventSink; 16 | private ConnectionStateChangeJsonConverter connectionStateChangeJsonConverter = new ConnectionStateChangeJsonConverter(); 17 | 18 | @Override 19 | synchronized public void onListen(Object o, EventChannel.EventSink eventSink) { 20 | this.eventSink = eventSink; 21 | } 22 | 23 | @Override 24 | synchronized public void onCancel(Object o) { 25 | eventSink = null; 26 | } 27 | 28 | synchronized public void onNewConnectionState(final ConnectionStateChange connectionState) { 29 | if (eventSink != null) { 30 | final ConnectionStateStreamHandler that = this; 31 | new Handler(Looper.getMainLooper()).post(new Runnable() { 32 | @Override 33 | public void run() { 34 | synchronized (that) { 35 | // Check again for null - by the time we get into this async runnable our eventSink 36 | // may have been canceled 37 | if (eventSink != null) { 38 | try { 39 | eventSink.success(connectionStateChangeJsonConverter.toJson(connectionState)); 40 | } catch (JSONException e) { 41 | eventSink.error("-1", e.getMessage(), e.getStackTrace()); 42 | } 43 | } 44 | } 45 | } 46 | }); 47 | } 48 | } 49 | 50 | synchronized public void onComplete() { 51 | if (eventSink != null) eventSink.endOfStream(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/event/RestoreStateStreamHandler.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.event; 2 | 3 | import io.flutter.plugin.common.EventChannel; 4 | 5 | public class RestoreStateStreamHandler implements EventChannel.StreamHandler { 6 | 7 | private EventChannel.EventSink restoreStateSink; 8 | 9 | @Override 10 | public void onListen(Object o, EventChannel.EventSink eventSink) { 11 | restoreStateSink = eventSink; 12 | } 13 | 14 | @Override 15 | public void onCancel(Object o) { 16 | restoreStateSink = null; 17 | } 18 | 19 | public void onRestoreEvent(Integer restoreStateIdentifier) { 20 | if (restoreStateSink != null) { 21 | restoreStateSink.success(restoreStateIdentifier); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/src/main/java/com/polidea/flutter_ble_lib/event/ScanningStreamHandler.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib.event; 2 | 3 | import com.polidea.flutter_ble_lib.converter.BleErrorJsonConverter; 4 | import com.polidea.flutter_ble_lib.converter.ScanResultJsonConverter; 5 | import com.polidea.multiplatformbleadapter.ScanResult; 6 | import com.polidea.multiplatformbleadapter.errors.BleError; 7 | 8 | import io.flutter.plugin.common.EventChannel; 9 | 10 | public class ScanningStreamHandler implements EventChannel.StreamHandler { 11 | 12 | private EventChannel.EventSink scanResultsSink; 13 | private ScanResultJsonConverter scanResultJsonConverter = new ScanResultJsonConverter(); 14 | private BleErrorJsonConverter bleErrorJsonConverter = new BleErrorJsonConverter(); 15 | 16 | @Override 17 | synchronized public void onListen(Object o, EventChannel.EventSink eventSink) { 18 | scanResultsSink = eventSink; 19 | } 20 | 21 | @Override 22 | synchronized public void onCancel(Object o) { 23 | scanResultsSink = null; 24 | } 25 | 26 | synchronized public void onScanResult(ScanResult scanResult) { 27 | if (scanResultsSink != null) { 28 | scanResultsSink.success(scanResultJsonConverter.toJson(scanResult)); 29 | } 30 | } 31 | 32 | synchronized public void onError(BleError error) { 33 | if (scanResultsSink != null) { 34 | scanResultsSink.error( 35 | String.valueOf(error.errorCode.code), 36 | error.reason, 37 | bleErrorJsonConverter.toJson(error)); 38 | scanResultsSink.endOfStream(); 39 | } 40 | } 41 | 42 | synchronized public void onComplete() { 43 | if (scanResultsSink != null) { 44 | scanResultsSink.endOfStream(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/.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 | # Visual Studio Code related 19 | .classpath 20 | .project 21 | .settings/ 22 | .vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | .dart_tool/ 27 | .flutter-plugins 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | -------------------------------------------------------------------------------- /example/.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: c55f1e93d2fc569e557fbc395da54e2c034980b1 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_ble_lib_example 2 | 3 | Demonstrates how to use the flutter_ble_lib plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | applicationId "com.polidea.flutter_ble_lib_example" 36 | minSdkVersion 18 37 | targetSdkVersion 28 38 | versionCode flutterVersionCode.toInteger() 39 | versionName flutterVersionName 40 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 41 | } 42 | 43 | buildTypes { 44 | release { 45 | // TODO: Add your own signing config for the release build. 46 | // Signing with the debug keys for now, so `flutter run --release` works. 47 | signingConfig signingConfigs.debug 48 | matchingFallbacks = ['debug', 'release'] 49 | } 50 | debug {} 51 | profile { 52 | // Specifies a sorted list of fallback build types that the 53 | // plugin should try to use when a dependency does not include a 54 | // "staging" build type. You may specify as many fallbacks as you 55 | // like, and the plugin selects the first build type that's 56 | // available in the dependency. 57 | matchingFallbacks = ['debug', 'release'] 58 | } 59 | } 60 | } 61 | 62 | flutter { 63 | source '../..' 64 | } 65 | 66 | dependencies { 67 | testImplementation 'junit:junit:4.12' 68 | androidTestImplementation 'androidx.test:runner:1.2.0' 69 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 70 | } 71 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 14 | 21 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/polidea/flutter_ble_lib_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.polidea.flutter_ble_lib_example; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.5.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /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-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/assets/ti_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/example/assets/ti_logo.png -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 8.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 | -------------------------------------------------------------------------------- /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, '9.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/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/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 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_ble_lib_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSBluetoothAlwaysUsageDescription 26 | App would like to use bluetooth for communication purposes 27 | UIBackgroundModes 28 | 29 | bluetooth-central 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | NSBonjourServices 49 | 50 | _dartobservatory._tcp 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /example/ios/Runner/SwiftBridging.swift: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/device_details/device_detail_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:fimber/fimber.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_ble_lib_example/device_details/device_details_bloc.dart'; 6 | import 'package:flutter_ble_lib_example/device_details/devices_details_bloc_provider.dart'; 7 | import 'package:flutter_ble_lib_example/device_details/view/auto_test_view.dart'; 8 | import 'package:flutter_ble_lib_example/device_details/view/manual_test_view.dart'; 9 | 10 | class DeviceDetailsView extends StatefulWidget { 11 | @override 12 | State createState() => DeviceDetailsViewState(); 13 | } 14 | 15 | class DeviceDetailsViewState extends State { 16 | DeviceDetailsBloc? _deviceDetailsBloc; 17 | StreamSubscription? _appStateSubscription; 18 | 19 | bool _shouldRunOnResume = true; 20 | 21 | @override 22 | void didChangeDependencies() { 23 | super.didChangeDependencies(); 24 | Fimber.d("didChangeDependencies"); 25 | if (_deviceDetailsBloc == null) { 26 | _deviceDetailsBloc = DeviceDetailsBlocProvider.of(context); 27 | if (_shouldRunOnResume) { 28 | _shouldRunOnResume = false; 29 | _onResume(); 30 | } 31 | } 32 | } 33 | 34 | void _onResume() { 35 | Fimber.d("onResume"); 36 | _deviceDetailsBloc?.init(); 37 | _appStateSubscription = 38 | _deviceDetailsBloc?.disconnectedDevice.listen((bleDevice) async { 39 | Fimber.d("navigate to details"); 40 | _onPause(); 41 | Navigator.pop(context); 42 | _shouldRunOnResume = true; 43 | Fimber.d("back from details"); 44 | }); 45 | } 46 | 47 | void _onPause() { 48 | Fimber.d("onPause"); 49 | _appStateSubscription?.cancel(); 50 | _deviceDetailsBloc?.dispose(); 51 | } 52 | 53 | @override 54 | void dispose() { 55 | Fimber.d("Dispose DeviceListScreenState"); 56 | _onPause(); 57 | super.dispose(); 58 | } 59 | 60 | @override 61 | Widget build(BuildContext context) { 62 | final deviceDetailsBloc = _deviceDetailsBloc; 63 | return WillPopScope( 64 | onWillPop: () { 65 | if (deviceDetailsBloc == null) { 66 | return Future.value(true); 67 | } 68 | return deviceDetailsBloc.disconnect().then((_) { 69 | return false; 70 | }); 71 | }, 72 | child: DefaultTabController( 73 | length: 2, 74 | child: Scaffold( 75 | backgroundColor: Colors.grey[300], 76 | appBar: AppBar( 77 | title: Text('Device Details'), 78 | bottom: TabBar( 79 | tabs: [ 80 | Tab( 81 | icon: Icon(Icons.autorenew), 82 | text: "Automatic", 83 | ), 84 | Tab( 85 | icon: Icon(Icons.settings), 86 | text: "Manual", 87 | ), 88 | ], 89 | ), 90 | ), 91 | body: TabBarView( 92 | children: [ 93 | if (deviceDetailsBloc != null) 94 | AutoTestView(deviceDetailsBloc), 95 | if (deviceDetailsBloc != null) 96 | ManualTestView(deviceDetailsBloc), 97 | ], 98 | )), 99 | ), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /example/lib/device_details/devices_details_bloc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import 'device_details_bloc.dart'; 4 | 5 | class DeviceDetailsBlocProvider extends InheritedWidget { 6 | final DeviceDetailsBloc _deviceDetailsBloc; 7 | 8 | DeviceDetailsBlocProvider({ 9 | Key? key, 10 | DeviceDetailsBloc? deviceDetailsBloc, 11 | required Widget child, 12 | }) 13 | : _deviceDetailsBloc = deviceDetailsBloc ?? DeviceDetailsBloc(), 14 | super(key: key, child: child); 15 | 16 | @override 17 | bool updateShouldNotify(InheritedWidget oldWidget) => true; 18 | 19 | static DeviceDetailsBloc of(BuildContext context) => context 20 | .dependOnInheritedWidgetOfExactType() 21 | !._deviceDetailsBloc; 22 | } 23 | -------------------------------------------------------------------------------- /example/lib/device_details/view/auto_test_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_ble_lib_example/device_details/device_details_bloc.dart'; 3 | import 'package:flutter_ble_lib_example/device_details/view/button_view.dart'; 4 | import 'package:flutter_ble_lib_example/device_details/view/logs_container_view.dart'; 5 | 6 | class AutoTestView extends StatelessWidget { 7 | final DeviceDetailsBloc _deviceDetailsBloc; 8 | 9 | AutoTestView(this._deviceDetailsBloc); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Padding( 14 | padding: const EdgeInsets.all(16.0), 15 | child: Column(children: [ 16 | Expanded( 17 | flex: 1, 18 | child: SingleChildScrollView( 19 | child: _createAutoTestControlPanel(), 20 | ), 21 | ), 22 | Expanded( 23 | flex: 9, 24 | child: LogsContainerView(_deviceDetailsBloc.logs), 25 | ) 26 | ]), 27 | ); 28 | } 29 | 30 | Widget _createAutoTestControlPanel() { 31 | return Row( 32 | children: [ 33 | ButtonView("Start Auto Test", action: _startAutoTest), 34 | ], 35 | ); 36 | } 37 | 38 | void _startAutoTest() { 39 | _deviceDetailsBloc.startAutoTest(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/lib/device_details/view/button_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ButtonView extends StatelessWidget { 4 | final String _text; 5 | final void Function()? action; 6 | 7 | ButtonView(this._text, {this.action}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Expanded( 12 | child: Padding( 13 | padding: const EdgeInsets.all(2.0), 14 | child: RaisedButton( 15 | color: Colors.blue, 16 | textColor: Colors.white, 17 | child: Text(_text), 18 | onPressed: action, 19 | ), 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/lib/device_details/view/logs_container_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_ble_lib_example/device_details/device_details_bloc.dart'; 3 | 4 | class LogsContainerView extends StatelessWidget { 5 | final Stream> _logs; 6 | 7 | LogsContainerView(this._logs); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | margin: EdgeInsets.symmetric(vertical: 8.0), 13 | decoration: BoxDecoration( 14 | color: Colors.white, 15 | borderRadius: BorderRadius.all(Radius.circular(4.0))), 16 | child: SizedBox.expand( 17 | child: Column( 18 | children: [ 19 | Flexible( 20 | child: StreamBuilder>( 21 | initialData: [], 22 | stream: _logs, 23 | builder: (context, snapshot) => _buildLogs(context, snapshot), 24 | ), 25 | ), 26 | ], 27 | ), 28 | ), 29 | ); 30 | } 31 | 32 | Widget _buildLogs(BuildContext context, AsyncSnapshot> logs) { 33 | final data = logs.data; 34 | return ListView.builder( 35 | itemCount: data?.length, 36 | shrinkWrap: true, 37 | itemBuilder: (buildContext, index) => Container( 38 | decoration: BoxDecoration( 39 | border: Border( 40 | top: BorderSide( 41 | color: Colors.grey, 42 | width: 0.5, 43 | ), 44 | bottom: BorderSide( 45 | color: Colors.grey, 46 | width: 0.5, 47 | ), 48 | ), 49 | ), 50 | child: Padding( 51 | padding: const EdgeInsets.only(top: 2.0), 52 | child: Row( 53 | crossAxisAlignment: CrossAxisAlignment.start, 54 | children: [ 55 | Padding( 56 | padding: const EdgeInsets.only(right: 8.0), 57 | child: Text( 58 | data?[index].time ?? "", 59 | style: TextStyle(fontSize: 9), 60 | ), 61 | ), 62 | Flexible( 63 | child: Text( 64 | data?[index].content ?? "", 65 | overflow: TextOverflow.ellipsis, 66 | softWrap: true, 67 | style: TextStyle(fontSize: 13) 68 | ), 69 | ), 70 | ], 71 | ), 72 | ), 73 | ), 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /example/lib/devices_list/devices_bloc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_ble_lib_example/devices_list/devices_bloc.dart'; 3 | 4 | class DevicesBlocProvider extends InheritedWidget { 5 | final DevicesBloc _devicesBloc; 6 | 7 | DevicesBlocProvider({ 8 | Key? key, 9 | DevicesBloc? devicesBloc, 10 | required Widget child, 11 | }) : _devicesBloc = devicesBloc ?? DevicesBloc(), 12 | super(key: key, child: child); 13 | 14 | @override 15 | bool updateShouldNotify(InheritedWidget oldWidget) => true; 16 | 17 | static DevicesBloc of(BuildContext context) => context 18 | .dependOnInheritedWidgetOfExactType() 19 | !._devicesBloc; 20 | } 21 | -------------------------------------------------------------------------------- /example/lib/devices_list/hex_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HexPainter extends CustomPainter { 4 | final Color _foregroundColor; 5 | final Color _backgroundColor; 6 | 7 | HexPainter({ 8 | Color? backgroundColor, 9 | Color? foregroundColor, 10 | }) : _backgroundColor = backgroundColor ?? Colors.white, 11 | _foregroundColor = foregroundColor ?? Colors.black, 12 | super(); 13 | 14 | @override 15 | void paint(Canvas canvas, Size size) { 16 | final paint = Paint(); 17 | drawHexagon(canvas, size, paint); 18 | drawButton(canvas, size, paint); 19 | } 20 | 21 | /// Draws rounded hexagon shape imitating Humon Hex device. 22 | void drawHexagon(Canvas canvas, Size size, Paint paint) { 23 | paint.color = _backgroundColor; 24 | paint.strokeWidth = size.width * 0.5; 25 | paint.strokeJoin = StrokeJoin.round; 26 | paint.style = PaintingStyle.stroke; 27 | var path = Path(); 28 | path.addPolygon([ 29 | Offset(size.width * 0.25, size.height * 0.375), 30 | Offset(size.width * 0.5, size.height * 0.25), 31 | Offset(size.width * 0.75, size.height * 0.375), 32 | Offset(size.width * 0.75, size.height * 0.625), 33 | Offset(size.width * 0.5, size.height * 0.75), 34 | Offset(size.width * 0.25, size.height * 0.625) 35 | ], true); 36 | canvas.drawPath(path, paint); 37 | } 38 | 39 | /// Draws Humon Hex button. 40 | void drawButton(Canvas canvas, Size size, Paint paint) { 41 | paint.color = _foregroundColor; 42 | paint.style = PaintingStyle.fill; 43 | canvas.drawCircle(Offset(size.width * 0.5, size.height * 0.23), 44 | size.height * 0.08, paint); 45 | } 46 | 47 | @override 48 | bool shouldRepaint(CustomPainter oldDelegate) => false; 49 | } 50 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:fimber/fimber.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_ble_lib_example/devices_list/devices_bloc_provider.dart'; 4 | import 'package:flutter_ble_lib_example/devices_list/devices_list_view.dart'; 5 | 6 | import 'device_details/device_detail_view.dart'; 7 | import 'device_details/devices_details_bloc_provider.dart'; 8 | 9 | void main() { 10 | Fimber.plantTree(DebugTree()); 11 | runApp(MyApp()); 12 | } 13 | 14 | final RouteObserver routeObserver = RouteObserver(); 15 | 16 | class MyApp extends StatelessWidget { 17 | @override 18 | Widget build(BuildContext context) { 19 | return MaterialApp( 20 | title: 'FlutterBleLib example', 21 | theme: new ThemeData( 22 | primaryColor: new Color(0xFF0A3D91), 23 | accentColor: new Color(0xFFCC0000), 24 | ), 25 | initialRoute: "/", 26 | routes: { 27 | "/": (context) => DevicesBlocProvider(child: DevicesListScreen()), 28 | "/details": (context) => 29 | DeviceDetailsBlocProvider(child: DeviceDetailsView()), 30 | }, 31 | navigatorObservers: [routeObserver], 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/lib/model/ble_device.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 3 | 4 | class BleDevice { 5 | final Peripheral peripheral; 6 | final String name; 7 | final DeviceCategory category; 8 | 9 | String get id => peripheral.identifier; 10 | 11 | BleDevice(ScanResult scanResult) 12 | : peripheral = scanResult.peripheral, 13 | name = scanResult.name, 14 | category = scanResult.category; 15 | 16 | @override 17 | int get hashCode => id.hashCode; 18 | 19 | @override 20 | bool operator ==(other) => 21 | other is BleDevice && 22 | compareAsciiLowerCase(this.name, other.name) == 0 && 23 | this.id == other.id; 24 | 25 | @override 26 | String toString() { 27 | return 'BleDevice{name: $name}'; 28 | } 29 | } 30 | 31 | enum DeviceCategory { sensorTag, hex, other } 32 | 33 | extension on ScanResult { 34 | String get name => 35 | peripheral.name ?? advertisementData.localName ?? "Unknown"; 36 | 37 | DeviceCategory get category { 38 | if (name == "SensorTag") { 39 | return DeviceCategory.sensorTag; 40 | } else if (name.startsWith("Hex")) { 41 | return DeviceCategory.hex; 42 | } else { 43 | return DeviceCategory.other; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/lib/repository/device_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_ble_lib_example/model/ble_device.dart'; 2 | import 'package:rxdart/rxdart.dart'; 3 | 4 | class MissingPickedDeviceException implements Exception {} 5 | 6 | class DeviceRepository { 7 | static BleDevice? _bleDevice; 8 | BehaviorSubject _deviceController; 9 | 10 | static final DeviceRepository _deviceRepository = 11 | DeviceRepository._internal(); 12 | 13 | factory DeviceRepository() { 14 | return _deviceRepository; 15 | } 16 | 17 | DeviceRepository._internal() 18 | : _deviceController = BehaviorSubject.seeded(_bleDevice); 19 | 20 | 21 | void pickDevice(BleDevice? bleDevice) { 22 | _bleDevice = bleDevice; 23 | _deviceController.add(_bleDevice); 24 | } 25 | 26 | ValueStream get pickedDevice => 27 | _deviceController.stream.shareValueSeeded(_bleDevice); 28 | 29 | bool get hasPickedDevice => _bleDevice != null; 30 | } 31 | -------------------------------------------------------------------------------- /example/lib/sensor_tag_config.dart: -------------------------------------------------------------------------------- 1 | abstract class SensorTagTemperatureUuids { 2 | static const String temperatureService = 3 | "F000AA00-0451-4000-B000-000000000000"; 4 | static const String temperatureDataCharacteristic = 5 | "F000AA01-0451-4000-B000-000000000000"; 6 | static const String temperatureConfigCharacteristic = 7 | "F000AA02-0451-4000-B000-000000000000"; 8 | static const String characteristicUserDescriptionDescriptor = 9 | "00002901-0000-1000-8000-00805f9b34fb"; 10 | static const String clientCharacteristicConfigurationDescriptor = 11 | "00002902-0000-1000-8000-00805f9b34fb"; 12 | } 13 | -------------------------------------------------------------------------------- /example/lib/test_scenarios/base.dart: -------------------------------------------------------------------------------- 1 | part of test_scenarios; 2 | 3 | typedef Logger = Function(String); 4 | 5 | abstract class TestScenario { 6 | Future runTestScenario(Logger log, Logger errorLogger); 7 | } 8 | -------------------------------------------------------------------------------- /example/lib/test_scenarios/bluetooth_state_toggle_scenario.dart: -------------------------------------------------------------------------------- 1 | part of test_scenarios; 2 | 3 | class BluetoothStateTestScenario implements TestScenario { 4 | StreamSubscription? _radioStateSubscription; 5 | 6 | @override 7 | Future runTestScenario(Logger log, Logger errorLogger) async { 8 | BleManager bleManager = BleManager(); 9 | 10 | log("SCENARIO WON'T WORK IF BLUETOOTH IS ENABLED"); 11 | log("Waiting 10 seconds for user to turn BT off"); 12 | await Future.delayed(Duration(seconds: 10)); 13 | 14 | log("Creating client..."); 15 | await bleManager.createClient(); 16 | log("Created client"); 17 | 18 | log("Subscribe for radio state changes"); 19 | _observeRadioState(bleManager, log); 20 | 21 | log("Get radio state: ${await bleManager.bluetoothState()}"); 22 | 23 | log("Enabling radio..."); 24 | try { 25 | await bleManager.enableRadio(); 26 | } catch (e) { 27 | errorLogger(e.toString()); 28 | } 29 | 30 | log("Enabled radio!"); 31 | 32 | log("Get radio state: ${await bleManager.bluetoothState()}"); 33 | 34 | log("Waiting 10 seconds before disabling radio..."); 35 | await Future.delayed(Duration(seconds: 10)); 36 | 37 | log("Disabling radio..."); 38 | await bleManager.disableRadio().catchError((error) => errorLogger(error)); 39 | log("Disabled radio!"); 40 | 41 | log("Destroying client"); 42 | await _radioStateSubscription?.cancel(); 43 | await bleManager.destroyClient(); 44 | log("Destroyed client!"); 45 | } 46 | 47 | void _observeRadioState(BleManager bleManager, Logger log) async { 48 | await _radioStateSubscription?.cancel(); 49 | _radioStateSubscription = 50 | bleManager.observeBluetoothState().listen((newState) { 51 | log("New radio state: $newState"); 52 | }, onError: (error) { 53 | log("Error while observing radio state. Error: $error"); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/lib/test_scenarios/sensor_tag_test_scenario.dart: -------------------------------------------------------------------------------- 1 | part of test_scenarios; 2 | 3 | class SensorTagTestScenario { 4 | PeripheralTestOperations _peripheralTestOperations; 5 | 6 | SensorTagTestScenario(BleManager bleManager, Peripheral peripheral, 7 | Logger log, Logger logError) 8 | : _peripheralTestOperations = 9 | PeripheralTestOperations(bleManager, peripheral, log, logError); 10 | 11 | Future runTestScenario() async { 12 | _peripheralTestOperations 13 | .connect() 14 | .then((_) => _peripheralTestOperations.cancelTransaction()) 15 | .then((_) => _peripheralTestOperations.discovery()) 16 | .then((_) => _peripheralTestOperations.testRequestingMtu()) 17 | .then((_) => _peripheralTestOperations.testReadingRssi()) 18 | .then((_) => _peripheralTestOperations 19 | .readWriteMonitorCharacteristicForPeripheral()) 20 | .then((_) => _peripheralTestOperations 21 | .readWriteMonitorCharacteristicForService()) 22 | .then((_) => _peripheralTestOperations.readWriteMonitorCharacteristic()) 23 | .then((_) => Future.delayed(Duration(milliseconds: 100))) 24 | .then( 25 | (_) => _peripheralTestOperations.readWriteDescriptorForPeripheral()) 26 | .then((_) => _peripheralTestOperations.readWriteDescriptorForService()) 27 | .then((_) => 28 | _peripheralTestOperations.readWriteDescriptorForCharacteristic()) 29 | .then((_) => _peripheralTestOperations.readWriteDescriptor()) 30 | .then((_) => _peripheralTestOperations.fetchConnectedDevice()) 31 | .then((_) => _peripheralTestOperations.fetchKnownDevice()) 32 | .then((_) => _peripheralTestOperations.disconnect()) 33 | .catchError((error) => _peripheralTestOperations.logError(error)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/lib/test_scenarios/sensor_tag_test_with_scan_and_connection_scenario.dart: -------------------------------------------------------------------------------- 1 | part of test_scenarios; 2 | 3 | class SensorTagTestWithScanAndConnectionScenario implements TestScenario { 4 | BleManager bleManager = BleManager(); 5 | bool deviceConnectionAttempted = false; 6 | 7 | Future runTestScenario(Logger log, Logger logError) async { 8 | log("CREATING CLIENT..."); 9 | await bleManager.createClient( 10 | restoreStateIdentifier: "5", 11 | restoreStateAction: (devices) { 12 | log("RESTORED DEVICES: $devices"); 13 | }); 14 | 15 | log("CREATED CLIENT"); 16 | log("STARTING SCAN..."); 17 | log("Looking for Sensor Tag..."); 18 | 19 | bleManager.startPeripheralScan().listen((scanResult) async { 20 | log("RECEIVED SCAN RESULT: " 21 | "\n name: ${scanResult.peripheral.name}" 22 | "\n identifier: ${scanResult.peripheral.identifier}" 23 | "\n rssi: ${scanResult.rssi}"); 24 | 25 | if (scanResult.peripheral.name == "SensorTag" && 26 | !deviceConnectionAttempted) { 27 | log("Sensor Tag found!"); 28 | deviceConnectionAttempted = true; 29 | log("Stopping device scan..."); 30 | await bleManager.stopPeripheralScan(); 31 | return _tryToConnect(scanResult.peripheral, log, logError); 32 | } 33 | }, onError: (error) { 34 | logError(error); 35 | }); 36 | } 37 | 38 | Future _tryToConnect( 39 | Peripheral peripheral, Logger log, Logger logError) async { 40 | log("OBSERVING connection state \nfor ${peripheral.name}," 41 | " ${peripheral.identifier}..."); 42 | 43 | peripheral 44 | .observeConnectionState(emitCurrentValue: true) 45 | .listen((connectionState) { 46 | log("Current connection state is: \n $connectionState"); 47 | if (connectionState == PeripheralConnectionState.disconnected) { 48 | log("${peripheral.name} has DISCONNECTED"); 49 | } 50 | }); 51 | 52 | log("CONNECTING to ${peripheral.name}, ${peripheral.identifier}..."); 53 | await peripheral.connect(); 54 | log("CONNECTED to ${peripheral.name}, ${peripheral.identifier}!"); 55 | deviceConnectionAttempted = false; 56 | 57 | await peripheral 58 | .discoverAllServicesAndCharacteristics() 59 | .then((_) => peripheral.services()) 60 | .then((services) { 61 | log("PRINTING SERVICES for ${peripheral.name}"); 62 | services.forEach((service) => log("Found service ${service.uuid}")); 63 | return services.first; 64 | }) 65 | .then((service) async { 66 | log("PRINTING CHARACTERISTICS FOR SERVICE \n${service.uuid}"); 67 | List characteristics = 68 | await service.characteristics(); 69 | characteristics.forEach((characteristic) { 70 | log("${characteristic.uuid}"); 71 | }); 72 | 73 | log("PRINTING CHARACTERISTICS FROM \nPERIPHERAL for the same service"); 74 | return peripheral.characteristics(service.uuid); 75 | }) 76 | .then((characteristics) => characteristics.forEach((characteristic) => 77 | log("Found characteristic \n ${characteristic.uuid}"))) 78 | .then((_) { 79 | log("WAITING 10 SECOND BEFORE DISCONNECTING"); 80 | return Future.delayed(Duration(seconds: 10)); 81 | }) 82 | .then((_) { 83 | log("DISCONNECTING..."); 84 | return peripheral.disconnectOrCancelConnection(); 85 | }) 86 | .then((_) { 87 | log("Disconnected!"); 88 | log("WAITING 10 SECOND BEFORE DESTROYING CLIENT"); 89 | return Future.delayed(Duration(seconds: 10)); 90 | }) 91 | .then((_) { 92 | log("DESTROYING client..."); 93 | return bleManager.destroyClient(); 94 | }) 95 | .then((_) => log("\BleClient destroyed after a delay")); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /example/lib/test_scenarios/test_scenarios.dart: -------------------------------------------------------------------------------- 1 | library test_scenarios; 2 | 3 | import 'dart:async'; 4 | import 'dart:typed_data'; 5 | 6 | import 'package:flutter/widgets.dart'; 7 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 8 | 9 | import '../sensor_tag_config.dart'; 10 | 11 | part 'base.dart'; 12 | part 'sensor_tag_test_with_scan_and_connection_scenario.dart'; 13 | part 'bluetooth_state_toggle_scenario.dart'; 14 | part 'sensor_tag_test_scenario.dart'; 15 | part 'peripheral_test_operations.dart'; 16 | -------------------------------------------------------------------------------- /example/lib/util/pair.dart: -------------------------------------------------------------------------------- 1 | class Pair { 2 | Pair(this.first, this.second); 3 | 4 | final T first; 5 | final S second; 6 | 7 | @override 8 | String toString() => 'Pair[$first, $second]'; 9 | } 10 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_ble_lib_example 2 | description: Demonstrates how to use the flutter_ble_lib plugin. 3 | version: 0.1.0+2 4 | publish_to: "none" 5 | 6 | environment: 7 | sdk: ">=2.12.0-0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | # The following adds the Cupertino Icons font to your application. 14 | # Use with the CupertinoIcons class for iOS style icons. 15 | cupertino_icons: ^1.0.2 16 | 17 | rxdart: ^0.26.0 18 | fimber: ^0.6.1 19 | 20 | permission_handler: ^6.1.1 21 | 22 | dev_dependencies: 23 | flutter_test: 24 | sdk: flutter 25 | 26 | flutter_ble_lib: 27 | path: ../ 28 | 29 | # For information on the generic Dart part of this file, see the 30 | # following page: https://dart.dev/tools/pub/pubspec 31 | 32 | # The following section is specific to Flutter. 33 | flutter: 34 | # The following line ensures that the Material Icons font is 35 | # included with your application, so that you can use the icons in 36 | # the material Icons class. 37 | uses-material-design: true 38 | 39 | # To add assets to your application, add an assets section, like this: 40 | assets: 41 | - assets/ti_logo.png 42 | # - images/a_dot_ham.jpeg 43 | # An image asset can refer to one or more resolution-specific "variants", see 44 | # https://flutter.dev/assets-and-images/#resolution-aware. 45 | # For details regarding adding assets from package dependencies, see 46 | # https://flutter.dev/assets-and-images/#from-packages 47 | # To add custom fonts to your application, add a fonts section here, 48 | # in this "flutter" section. Each entry in this list should have a 49 | # "family" key with the font family name, and a "fonts" key with a 50 | # list giving the asset and other descriptors for the font. For 51 | # example: 52 | # fonts: 53 | # - family: Schyler 54 | # fonts: 55 | # - asset: fonts/Schyler-Regular.ttf 56 | # - asset: fonts/Schyler-Italic.ttf 57 | # style: italic 58 | # - family: Trajan Pro 59 | # fonts: 60 | # - asset: fonts/TrajanPro.ttf 61 | # - asset: fonts/TrajanPro_Bold.ttf 62 | # weight: 700 63 | # 64 | # For details regarding fonts from package dependencies, 65 | # see https://flutter.dev/custom-fonts/#from-packages 66 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_ble_lib_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => 22 | widget is Text, 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /ios/.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 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/Constants/ArgumentKey.h: -------------------------------------------------------------------------------- 1 | extern NSString * const ARGUMENT_KEY_RESTORE_STATE_IDENTIFIER; 2 | extern NSString * const ARGUMENT_KEY_SCAN_MODE; 3 | extern NSString * const ARGUMENT_KEY_ALLOW_DUPLICATES; 4 | extern NSString * const ARGUMENT_KEY_CALLBACK_TYPE; 5 | extern NSString * const ARGUMENT_KEY_UUIDS; 6 | 7 | extern NSString * const ARGUMENT_KEY_TRANSACTION_ID; 8 | 9 | extern NSString * const ARGUMENT_KEY_DEVICE_IDENTIFIER; 10 | extern NSString * const ARGUMENT_KEY_IS_AUTO_CONNECT; 11 | extern NSString * const ARGUMENT_KEY_REQUEST_MTU; 12 | extern NSString * const ARGUMENT_KEY_REFRESH_GATT; 13 | extern NSString * const ARGUMENT_KEY_TIMEOUT_MILLIS; 14 | extern NSString * const ARGUMENT_KEY_EMIT_CURRENT_VALUE; 15 | 16 | extern NSString * const ARGUMENT_KEY_LOG_LEVEL; 17 | 18 | extern NSString * const ARGUMENT_KEY_SERVICE_UUID; 19 | extern NSString * const ARGUMENT_KEY_SERVICE_ID; 20 | extern NSString * const ARGUMENT_KEY_CHARACTERISTIC_UUID; 21 | extern NSString * const ARGUMENT_KEY_CHARACTERISTIC_IDENTIFIER; 22 | extern NSString * const ARGUMENT_KEY_VALUE; 23 | extern NSString * const ARGUMENT_KEY_WITH_RESPONSE; 24 | 25 | extern NSString * const ARGUMENT_KEY_DESCRIPTOR_UUID; 26 | extern NSString * const ARGUMENT_KEY_DESCRIPTOR_IDENTIFIER; 27 | 28 | extern NSString * const ARGUMENT_KEY_MTU; 29 | 30 | extern NSString * const ARGUMENT_KEY_DEVICE_IDENTIFIERS; 31 | -------------------------------------------------------------------------------- /ios/Classes/Constants/ArgumentKey.m: -------------------------------------------------------------------------------- 1 | NSString * const ARGUMENT_KEY_RESTORE_STATE_IDENTIFIER = @"restoreStateIdentifier"; 2 | NSString * const ARGUMENT_KEY_SCAN_MODE = @"scanMode"; 3 | NSString * const ARGUMENT_KEY_ALLOW_DUPLICATES = @"allowDuplicates"; 4 | NSString * const ARGUMENT_KEY_CALLBACK_TYPE = @"callbackType"; 5 | NSString * const ARGUMENT_KEY_UUIDS = @"uuids"; 6 | 7 | NSString * const ARGUMENT_KEY_TRANSACTION_ID = @"transactionId"; 8 | 9 | NSString * const ARGUMENT_KEY_DEVICE_IDENTIFIER = @"deviceIdentifier"; 10 | NSString * const ARGUMENT_KEY_IS_AUTO_CONNECT = @"isAutoConnect"; 11 | NSString * const ARGUMENT_KEY_REQUEST_MTU = @"requestMtu"; 12 | NSString * const ARGUMENT_KEY_REFRESH_GATT = @"refreshGatt"; 13 | NSString * const ARGUMENT_KEY_TIMEOUT_MILLIS = @"timeout"; 14 | NSString * const ARGUMENT_KEY_EMIT_CURRENT_VALUE = @"emitCurrentValue"; 15 | 16 | NSString * const ARGUMENT_KEY_LOG_LEVEL = @"logLevel"; 17 | 18 | NSString * const ARGUMENT_KEY_SERVICE_UUID = @"serviceUuid"; 19 | NSString * const ARGUMENT_KEY_SERVICE_ID = @"serviceId"; 20 | NSString * const ARGUMENT_KEY_CHARACTERISTIC_UUID = @"characteristicUuid"; 21 | NSString * const ARGUMENT_KEY_CHARACTERISTIC_IDENTIFIER = @"characteristicIdentifier"; 22 | NSString * const ARGUMENT_KEY_VALUE = @"value"; 23 | NSString * const ARGUMENT_KEY_WITH_RESPONSE = @"withResponse"; 24 | 25 | NSString * const ARGUMENT_KEY_DESCRIPTOR_UUID = @"descriptorUuid"; 26 | NSString * const ARGUMENT_KEY_DESCRIPTOR_IDENTIFIER = @"descriptorIdentifier"; 27 | 28 | NSString * const ARGUMENT_KEY_MTU = @"mtu"; 29 | 30 | NSString * const ARGUMENT_KEY_DEVICE_IDENTIFIERS = @"deviceIdentifiers"; 31 | -------------------------------------------------------------------------------- /ios/Classes/Constants/ChannelName.h: -------------------------------------------------------------------------------- 1 | extern NSString * const CHANNEL_NAME_FLUTTER_BLE_LIB; 2 | extern NSString * const CHANNEL_NAME_ADAPTER_STATE_CHANGES; 3 | extern NSString * const CHANNEL_NAME_STATE_RESTORE_EVENTS; 4 | extern NSString * const CHANNEL_NAME_SCANNING_EVENTS; 5 | extern NSString * const CHANNEL_NAME_CONNECTION_STATE_CHANGE_EVENTS; 6 | extern NSString * const CHANNEL_NAME_MONITOR_CHARACTERISTIC; 7 | -------------------------------------------------------------------------------- /ios/Classes/Constants/ChannelName.m: -------------------------------------------------------------------------------- 1 | #define FLUTTER_BLE_LIB @"flutter_ble_lib" 2 | 3 | NSString * const CHANNEL_NAME_FLUTTER_BLE_LIB = FLUTTER_BLE_LIB; 4 | NSString * const CHANNEL_NAME_ADAPTER_STATE_CHANGES = (FLUTTER_BLE_LIB @"/stateChanges"); 5 | NSString * const CHANNEL_NAME_STATE_RESTORE_EVENTS = (FLUTTER_BLE_LIB @"/stateRestoreEvents"); 6 | NSString * const CHANNEL_NAME_SCANNING_EVENTS = (FLUTTER_BLE_LIB @"/scanningEvents"); 7 | NSString * const CHANNEL_NAME_CONNECTION_STATE_CHANGE_EVENTS = (FLUTTER_BLE_LIB @"/connectionStateChangeEvents"); 8 | NSString * const CHANNEL_NAME_MONITOR_CHARACTERISTIC = (FLUTTER_BLE_LIB @"/monitorCharacteristic"); 9 | -------------------------------------------------------------------------------- /ios/Classes/Constants/MethodName.h: -------------------------------------------------------------------------------- 1 | extern NSString * const METHOD_NAME_IS_CLIENT_CREATED; 2 | extern NSString * const METHOD_NAME_CREATE_CLIENT; 3 | extern NSString * const METHOD_NAME_DESTROY_CLIENT; 4 | 5 | extern NSString * const METHOD_NAME_CANCEL_TRANSACTION; 6 | 7 | extern NSString * const METHOD_NAME_GET_STATE; 8 | 9 | extern NSString * const METHOD_NAME_ENABLE_RADIO; 10 | extern NSString * const METHOD_NAME_DISABLE_RADIO; 11 | 12 | extern NSString * const METHOD_NAME_START_DEVICE_SCAN; 13 | extern NSString * const METHOD_NAME_STOP_DEVICE_SCAN; 14 | 15 | extern NSString * const METHOD_NAME_CONNECT_TO_DEVICE; 16 | extern NSString * const METHOD_NAME_IS_DEVICE_CONNECTED; 17 | extern NSString * const METHOD_NAME_OBSERVE_CONNECTION_STATE; 18 | extern NSString * const METHOD_NAME_CANCEL_CONNECTION; 19 | 20 | extern NSString * const METHOD_NAME_DISCOVER_ALL_SERVICES_AND_CHARACTERISTICS; 21 | extern NSString * const METHOD_NAME_GET_SERVICES; 22 | extern NSString * const METHOD_NAME_GET_CHARACTERISTICS; 23 | extern NSString * const METHOD_NAME_GET_CHARACTERISTICS_FOR_SERVICE; 24 | 25 | extern NSString * const METHOD_NAME_GET_DESCRIPTORS_FOR_DEVICE; 26 | extern NSString * const METHOD_NAME_GET_DESCRIPTORS_FOR_SERVICE; 27 | extern NSString * const METHOD_NAME_GET_DESCRIPTORS_FOR_CHARACTERISTIC; 28 | 29 | extern NSString * const METHOD_NAME_LOG_LEVEL; 30 | extern NSString * const METHOD_NAME_SET_LOG_LEVEL; 31 | 32 | extern NSString * const METHOD_NAME_RSSI; 33 | 34 | extern NSString * const METHOD_NAME_REQUEST_MTU; 35 | 36 | extern NSString * const METHOD_NAME_GET_CONNECTED_DEVICES; 37 | extern NSString * const METHOD_NAME_GET_KNOWN_DEVICES; 38 | 39 | extern NSString * const METHOD_NAME_READ_CHARACTERISTIC_FOR_IDENTIFIER; 40 | extern NSString * const METHOD_NAME_READ_CHARACTERISTIC_FOR_DEVICE; 41 | extern NSString * const METHOD_NAME_READ_CHARACTERISTIC_FOR_SERVICE; 42 | 43 | extern NSString * const METHOD_NAME_WRITE_CHARACTERISTIC_FOR_IDENTIFIER; 44 | extern NSString * const METHOD_NAME_WRITE_CHARACTERISTIC_FOR_DEVICE; 45 | extern NSString * const METHOD_NAME_WRITE_CHARACTERISTIC_FOR_SERVICE; 46 | 47 | extern NSString * const METHOD_NAME_MONITOR_CHARACTERISTIC_FOR_IDENTIFIER; 48 | extern NSString * const METHOD_NAME_MONITOR_CHARACTERISTIC_FOR_DEVICE; 49 | extern NSString * const METHOD_NAME_MONITOR_CHARACTERISTIC_FOR_SERVICE; 50 | 51 | extern NSString * const METHOD_NAME_READ_DESCRIPTOR_FOR_IDENTIFIER; 52 | extern NSString * const METHOD_NAME_READ_DESCRIPTOR_FOR_CHARACTERISTIC; 53 | extern NSString * const METHOD_NAME_READ_DESCRIPTOR_FOR_SERVICE; 54 | extern NSString * const METHOD_NAME_READ_DESCRIPTOR_FOR_DEVICE; 55 | 56 | extern NSString * const METHOD_NAME_WRITE_DESCRIPTOR_FOR_IDENTIFIER; 57 | extern NSString * const METHOD_NAME_WRITE_DESCRIPTOR_FOR_CHARACTERISTIC; 58 | extern NSString * const METHOD_NAME_WRITE_DESCRIPTOR_FOR_SERVICE; 59 | extern NSString * const METHOD_NAME_WRITE_DESCRIPTOR_FOR_DEVICE; 60 | -------------------------------------------------------------------------------- /ios/Classes/Constants/MethodName.m: -------------------------------------------------------------------------------- 1 | NSString * const METHOD_NAME_IS_CLIENT_CREATED = @"isClientCreated"; 2 | NSString * const METHOD_NAME_CREATE_CLIENT = @"createClient"; 3 | NSString * const METHOD_NAME_DESTROY_CLIENT = @"destroyClient"; 4 | 5 | NSString * const METHOD_NAME_CANCEL_TRANSACTION = @"cancelTransaction"; 6 | 7 | NSString * const METHOD_NAME_GET_STATE = @"getState"; 8 | 9 | NSString * const METHOD_NAME_ENABLE_RADIO = @"enableRadio"; 10 | NSString * const METHOD_NAME_DISABLE_RADIO = @"disableRadio"; 11 | 12 | NSString * const METHOD_NAME_START_DEVICE_SCAN = @"startDeviceScan"; 13 | NSString * const METHOD_NAME_STOP_DEVICE_SCAN = @"stopDeviceScan"; 14 | 15 | NSString * const METHOD_NAME_CONNECT_TO_DEVICE = @"connectToDevice"; 16 | NSString * const METHOD_NAME_IS_DEVICE_CONNECTED = @"isDeviceConnected"; 17 | NSString * const METHOD_NAME_OBSERVE_CONNECTION_STATE = @"observeConnectionState"; 18 | NSString * const METHOD_NAME_CANCEL_CONNECTION = @"cancelConnection"; 19 | 20 | NSString * const METHOD_NAME_DISCOVER_ALL_SERVICES_AND_CHARACTERISTICS = @"discoverAllServicesAndCharacteristics"; 21 | NSString * const METHOD_NAME_GET_SERVICES = @"services"; 22 | NSString * const METHOD_NAME_GET_CHARACTERISTICS = @"characteristics"; 23 | NSString * const METHOD_NAME_GET_CHARACTERISTICS_FOR_SERVICE = @"characteristicsForService"; 24 | NSString * const METHOD_NAME_GET_DESCRIPTORS_FOR_DEVICE = @"descriptorsForDevice"; 25 | NSString * const METHOD_NAME_GET_DESCRIPTORS_FOR_SERVICE = @"descriptorsForService"; 26 | NSString * const METHOD_NAME_GET_DESCRIPTORS_FOR_CHARACTERISTIC = @"descriptorsForCharacteristic"; 27 | 28 | NSString * const METHOD_NAME_LOG_LEVEL = @"logLevel"; 29 | NSString * const METHOD_NAME_SET_LOG_LEVEL = @"setLogLevel"; 30 | 31 | NSString * const METHOD_NAME_RSSI = @"rssi"; 32 | 33 | NSString * const METHOD_NAME_REQUEST_MTU = @"requestMtu"; 34 | 35 | NSString * const METHOD_NAME_GET_CONNECTED_DEVICES = @"getConnectedDevices"; 36 | NSString * const METHOD_NAME_GET_KNOWN_DEVICES = @"getKnownDevices"; 37 | 38 | NSString * const METHOD_NAME_READ_CHARACTERISTIC_FOR_IDENTIFIER = @"readCharacteristicForIdentifier"; 39 | NSString * const METHOD_NAME_READ_CHARACTERISTIC_FOR_DEVICE = @"readCharacteristicForDevice"; 40 | NSString * const METHOD_NAME_READ_CHARACTERISTIC_FOR_SERVICE = @"readCharacteristicForService"; 41 | 42 | NSString * const METHOD_NAME_WRITE_CHARACTERISTIC_FOR_IDENTIFIER = @"writeCharacteristicForIdentifier"; 43 | NSString * const METHOD_NAME_WRITE_CHARACTERISTIC_FOR_DEVICE = @"writeCharacteristicForDevice"; 44 | NSString * const METHOD_NAME_WRITE_CHARACTERISTIC_FOR_SERVICE = @"writeCharacteristicForService"; 45 | 46 | NSString * const METHOD_NAME_MONITOR_CHARACTERISTIC_FOR_IDENTIFIER = @"monitorCharacteristicForIdentifier"; 47 | NSString * const METHOD_NAME_MONITOR_CHARACTERISTIC_FOR_DEVICE = @"monitorCharacteristicForDevice"; 48 | NSString * const METHOD_NAME_MONITOR_CHARACTERISTIC_FOR_SERVICE = @"monitorCharacteristicForService"; 49 | 50 | NSString * const METHOD_NAME_READ_DESCRIPTOR_FOR_IDENTIFIER = @"readDescriptorForIdentifier"; 51 | NSString * const METHOD_NAME_READ_DESCRIPTOR_FOR_CHARACTERISTIC = @"readDescriptorForCharacteristic"; 52 | NSString * const METHOD_NAME_READ_DESCRIPTOR_FOR_SERVICE = @"readDescriptorForService"; 53 | NSString * const METHOD_NAME_READ_DESCRIPTOR_FOR_DEVICE = @"readDescriptorForDevice"; 54 | 55 | NSString * const METHOD_NAME_WRITE_DESCRIPTOR_FOR_IDENTIFIER = @"writeDescriptorForIdentifier"; 56 | NSString * const METHOD_NAME_WRITE_DESCRIPTOR_FOR_CHARACTERISTIC = @"writeDescriptorForCharacteristic"; 57 | NSString * const METHOD_NAME_WRITE_DESCRIPTOR_FOR_SERVICE = @"writeDescriptorForService"; 58 | NSString * const METHOD_NAME_WRITE_DESCRIPTOR_FOR_DEVICE = @"writeDescriptorForDevice"; 59 | -------------------------------------------------------------------------------- /ios/Classes/Event/AdapterStateStreamHandler.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AdapterStateStreamHandler : NSObject 4 | 5 | - (void)onNewAdapterState:(NSArray *)bluetoothAdapterState; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /ios/Classes/Event/AdapterStateStreamHandler.m: -------------------------------------------------------------------------------- 1 | #import "AdapterStateStreamHandler.h" 2 | #import "FlutterErrorFactory.h" 3 | 4 | @implementation AdapterStateStreamHandler { 5 | FlutterEventSink adapterStateSink; 6 | } 7 | 8 | - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { 9 | @synchronized (self) { 10 | adapterStateSink = nil; 11 | return nil; 12 | } 13 | } 14 | 15 | - (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { 16 | @synchronized (self) { 17 | adapterStateSink = events; 18 | return nil; 19 | } 20 | } 21 | 22 | - (void)onNewAdapterState:(NSString *)bluetoothAdapterState { 23 | @synchronized (self) { 24 | if (adapterStateSink != nil) { 25 | adapterStateSink(bluetoothAdapterState); 26 | } 27 | } 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /ios/Classes/Event/ConnectionStateStreamHandler.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ConnectionStateStreamHandler : NSObject 4 | 5 | - (void)onConnectingEvent:(NSString *)deviceId; 6 | 7 | - (void)onConnectedEvent:(NSString *)deviceId; 8 | 9 | - (void)onDisconnectedEvent:(NSArray *)peripheralResponse; 10 | 11 | - (void)emitDisconnectedEvent:(NSString *)deviceId; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Classes/Event/ConnectionStateStreamHandler.m: -------------------------------------------------------------------------------- 1 | #import "ConnectionStateStreamHandler.h" 2 | #import "FlutterErrorFactory.h" 3 | 4 | @implementation ConnectionStateStreamHandler { 5 | FlutterEventSink eventSink; 6 | } 7 | 8 | - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { 9 | @synchronized (self) { 10 | eventSink = nil; 11 | return nil; 12 | } 13 | } 14 | 15 | - (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { 16 | @synchronized (self) { 17 | eventSink = events; 18 | return nil; 19 | } 20 | } 21 | 22 | - (void)onConnectingEvent:(NSString *)deviceId { 23 | @synchronized (self) { 24 | if (eventSink != nil) { 25 | eventSink([self jsonStringForDeviceId:deviceId connectionState:@"connecting"]); 26 | } 27 | } 28 | } 29 | 30 | - (void)onConnectedEvent:(NSString *)deviceId { 31 | @synchronized (self) { 32 | if (eventSink != nil) { 33 | eventSink([self jsonStringForDeviceId:deviceId connectionState:@"connected"]); 34 | } 35 | } 36 | } 37 | 38 | - (void)onDisconnectedEvent:(NSArray *)peripheralResponse { 39 | @synchronized (self) { 40 | if (eventSink != nil) { 41 | if (peripheralResponse[0] == [NSNull null]) { 42 | NSDictionary *peripheral = peripheralResponse[1]; 43 | eventSink([self jsonStringForDeviceId:[peripheral objectForKey:@"id"] connectionState:@"disconnected"]); 44 | } else { 45 | eventSink([FlutterErrorFactory flutterErrorFromJSONString:peripheralResponse[0]]); 46 | } 47 | } 48 | } 49 | } 50 | 51 | - (void)emitDisconnectedEvent:(NSString *)deviceId { 52 | @synchronized (self) { 53 | if (eventSink != nil) { 54 | eventSink([self jsonStringForDeviceId:deviceId connectionState:@"disconnected"]); 55 | } 56 | } 57 | } 58 | 59 | - (NSString *)jsonStringForDeviceId:(NSString *)deviceId connectionState:(NSString *)connectionState { 60 | NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init]; 61 | [dictionary setValue:deviceId forKey:@"peripheralIdentifier"]; 62 | [dictionary setValue:connectionState forKey:@"connectionState"]; 63 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary 64 | options:NSJSONWritingPrettyPrinted 65 | error:nil]; 66 | return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /ios/Classes/Event/MonitorCharacteristicStreamHandler.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface MonitorCharacteristicStreamHandler : NSObject 4 | 5 | - (void)onReadEvent:(NSArray *)readResult; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /ios/Classes/Event/MonitorCharacteristicStreamHandler.m: -------------------------------------------------------------------------------- 1 | #import "MonitorCharacteristicStreamHandler.h" 2 | #import "CharacteristicResponseConverter.h" 3 | #import "FlutterErrorFactory.h" 4 | 5 | @implementation MonitorCharacteristicStreamHandler { 6 | FlutterEventSink characteristicEventSink; 7 | } 8 | 9 | - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { 10 | @synchronized (self) { 11 | characteristicEventSink = nil; 12 | return nil; 13 | } 14 | } 15 | 16 | - (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { 17 | @synchronized (self) { 18 | characteristicEventSink = events; 19 | return nil; 20 | } 21 | } 22 | 23 | - (void)onReadEvent:(NSArray *)readResult { 24 | @synchronized (self) { 25 | if (characteristicEventSink != nil) { 26 | if (readResult[0] == [NSNull null]) { 27 | characteristicEventSink([CharacteristicResponseConverter jsonStringFromCharacteristicResponse:readResult[1] 28 | transactionId:readResult[2]]); 29 | } else { 30 | characteristicEventSink([FlutterErrorFactory flutterErrorFromJSONString:readResult[0] 31 | withTransactionId:readResult[2]]); 32 | } 33 | } 34 | } 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /ios/Classes/Event/RestoreStateStreamHandler.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RestoreStateStreamHandler : NSObject 4 | 5 | - (void)onRestoreEvent:(id)restoreState; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /ios/Classes/Event/RestoreStateStreamHandler.m: -------------------------------------------------------------------------------- 1 | #import "RestoreStateStreamHandler.h" 2 | #import "JSONStringifier.h" 3 | 4 | @implementation RestoreStateStreamHandler { 5 | FlutterEventSink restoreStateSink; 6 | } 7 | 8 | - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { 9 | @synchronized (self) { 10 | restoreStateSink = nil; 11 | return nil; 12 | } 13 | } 14 | 15 | - (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { 16 | @synchronized (self) { 17 | restoreStateSink = events; 18 | return nil; 19 | } 20 | } 21 | 22 | - (void)onRestoreEvent:(id)restoreState { 23 | @synchronized (self) { 24 | if (restoreStateSink != nil && restoreState != [NSNull null]) { 25 | NSArray *connectedPeripherals = [restoreState objectForKey:@"connectedPeripherals"]; 26 | restoreStateSink([JSONStringifier jsonStringFromJSONObject:connectedPeripherals]); 27 | } 28 | } 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /ios/Classes/Event/ScanningStreamHandler.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ScanningStreamHandler : NSObject 4 | 5 | - (void)onScanResult:(NSArray *)scanResult; 6 | - (void)onComplete; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /ios/Classes/Event/ScanningStreamHandler.m: -------------------------------------------------------------------------------- 1 | #import "ScanningStreamHandler.h" 2 | #import "ArgumentHandler.h" 3 | #import "JSONStringifier.h" 4 | #import "FlutterErrorFactory.h" 5 | 6 | @implementation ScanningStreamHandler { 7 | FlutterEventSink scanResultsSink; 8 | bool ended; 9 | } 10 | 11 | - (ScanningStreamHandler *)init { 12 | if (self = [super init]) { 13 | ended = false; 14 | } 15 | return self; 16 | } 17 | 18 | - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { 19 | @synchronized (self) { 20 | scanResultsSink = nil; 21 | ended = false; 22 | return nil; 23 | } 24 | } 25 | 26 | - (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { 27 | @synchronized (self) { 28 | scanResultsSink = events; 29 | ended = false; 30 | return nil; 31 | } 32 | } 33 | 34 | - (void)onScanResult:(NSArray *)scanResult { 35 | @synchronized (self) { 36 | if (scanResultsSink != nil && !ended) { 37 | if (!(scanResult.count == 2 && 38 | (scanResult[0] == [NSNull null] || (scanResult[1] == [NSNull null] && [scanResult[0] isKindOfClass:NSString.class])))) { 39 | scanResultsSink([FlutterError errorWithCode:@"-1" message:@"Invalid scanResult format." details:nil]); 40 | [self onComplete]; 41 | } else { 42 | if (scanResult[0] == [NSNull null]) { 43 | scanResultsSink([JSONStringifier jsonStringFromJSONObject:scanResult[1]]); 44 | } else { 45 | scanResultsSink([FlutterErrorFactory flutterErrorFromJSONString:scanResult[0]]); 46 | [self onComplete]; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | - (void)onComplete { 54 | @synchronized (self) { 55 | if (scanResultsSink != nil && !ended) { 56 | scanResultsSink(FlutterEndOfEventStream); 57 | ended = true; 58 | } 59 | } 60 | } 61 | 62 | - (nullable NSString *)validStringOrNil:(id)argument { 63 | if (argument != nil && (NSNull *)argument != [NSNull null] && [argument isKindOfClass:[NSString class]]) { 64 | return (NSString *)argument; 65 | } else { 66 | return nil; 67 | } 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /ios/Classes/FlutterBleLibPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FlutterBleLibPlugin : NSObject 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /ios/Classes/Response/CharacteristicResponse.h: -------------------------------------------------------------------------------- 1 | extern NSString * const CHARACTERISTIC_RESPONSE_UUID; 2 | extern NSString * const CHARACTERISTIC_RESPONSE_ID; 3 | extern NSString * const CHARACTERISTIC_RESPONSE_IS_INDICATABLE; 4 | extern NSString * const CHARACTERISTIC_RESPONSE_IS_NOTIFIABLE; 5 | extern NSString * const CHARACTERISTIC_RESPONSE_IS_NOTIFYING; 6 | extern NSString * const CHARACTERISTIC_RESPONSE_IS_READABLE; 7 | extern NSString * const CHARACTERISTIC_RESPONSE_IS_WRITABLE_WITH_RESPONSE; 8 | extern NSString * const CHARACTERISTIC_RESPONSE_IS_WRITABLE_WITHOUT_RESPONSE; 9 | extern NSString * const CHARACTERISTIC_RESPONSE_VALUE; 10 | extern NSString * const CHARACTERISTIC_RESPONSE_SERVICE_ID; 11 | extern NSString * const CHARACTERISTIC_RESPONSE_SERVICE_UUID; 12 | extern NSString * const CHARACTERISTIC_RESPONSE_DEVICE_ID; 13 | -------------------------------------------------------------------------------- /ios/Classes/Response/CharacteristicResponse.m: -------------------------------------------------------------------------------- 1 | NSString * const CHARACTERISTIC_RESPONSE_UUID = @"uuid"; 2 | NSString * const CHARACTERISTIC_RESPONSE_ID = @"id"; 3 | NSString * const CHARACTERISTIC_RESPONSE_IS_INDICATABLE = @"isIndicatable"; 4 | NSString * const CHARACTERISTIC_RESPONSE_IS_NOTIFIABLE = @"isNotifiable"; 5 | NSString * const CHARACTERISTIC_RESPONSE_IS_NOTIFYING = @"isNotifying"; 6 | NSString * const CHARACTERISTIC_RESPONSE_IS_READABLE = @"isReadable"; 7 | NSString * const CHARACTERISTIC_RESPONSE_IS_WRITABLE_WITH_RESPONSE = @"isWritableWithResponse"; 8 | NSString * const CHARACTERISTIC_RESPONSE_IS_WRITABLE_WITHOUT_RESPONSE = @"isWritableWithoutResponse"; 9 | NSString * const CHARACTERISTIC_RESPONSE_VALUE = @"value"; 10 | NSString * const CHARACTERISTIC_RESPONSE_SERVICE_ID = @"serviceID"; 11 | NSString * const CHARACTERISTIC_RESPONSE_SERVICE_UUID = @"serviceUUID"; 12 | NSString * const CHARACTERISTIC_RESPONSE_DEVICE_ID = @"deviceID"; 13 | -------------------------------------------------------------------------------- /ios/Classes/Response/DescriptorResponse.h: -------------------------------------------------------------------------------- 1 | extern NSString * const DESCRIPTOR_RESPONSE_UUID; 2 | extern NSString * const DESCRIPTOR_RESPONSE_ID; 3 | extern NSString * const DESCRIPTOR_RESPONSE_VALUE; 4 | extern NSString * const DESCRIPTOR_RESPONSE_CHARACTERISTIC_UUID; 5 | extern NSString * const DESCRIPTOR_RESPONSE_CHARACTERISTIC_ID; 6 | extern NSString * const DESCRIPTOR_RESPONSE_SERVICE_UUID; 7 | extern NSString * const DESCRIPTOR_RESPONSE_SERVICE_ID; 8 | -------------------------------------------------------------------------------- /ios/Classes/Response/DescriptorResponse.m: -------------------------------------------------------------------------------- 1 | NSString * const DESCRIPTOR_RESPONSE_UUID = @"uuid"; 2 | NSString * const DESCRIPTOR_RESPONSE_ID = @"id"; 3 | NSString * const DESCRIPTOR_RESPONSE_VALUE = @"value"; 4 | NSString * const DESCRIPTOR_RESPONSE_CHARACTERISTIC_UUID = @"characteristicUUID"; 5 | NSString * const DESCRIPTOR_RESPONSE_CHARACTERISTIC_ID = @"characteristicID"; 6 | NSString * const DESCRIPTOR_RESPONSE_SERVICE_UUID = @"serviceUUID"; 7 | NSString * const DESCRIPTOR_RESPONSE_SERVICE_ID = @"serviceID"; 8 | -------------------------------------------------------------------------------- /ios/Classes/Response/PeripheralResponse.h: -------------------------------------------------------------------------------- 1 | extern NSString * const PERIPHERAL_RESPONSE_ID; 2 | extern NSString * const PERIPHERAL_RESPONSE_IS_CONNECTABLE; 3 | extern NSString * const PERIPHERAL_RESPONSE_LOCAL_NAME; 4 | extern NSString * const PERIPHERAL_RESPONSE_MANUFACTURER_DATA; 5 | extern NSString * const PERIPHERAL_RESPONSE_MTU; 6 | extern NSString * const PERIPHERAL_RESPONSE_NAME; 7 | extern NSString * const PERIPHERAL_RESPONSE_OVERFLOW_SERVICE_UUIDS; 8 | extern NSString * const PERIPHERAL_RESPONSE_RSSI; 9 | extern NSString * const PERIPHERAL_RESPONSE_SERVICE_DATA; 10 | extern NSString * const PERIPHERAL_RESPONSE_SERVICE_UUIDS; 11 | extern NSString * const PERIPHERAL_RESPONSE_SOLICITED_SERVICE_UUIDS; 12 | extern NSString * const PERIPHERAL_RESPONSE_TX_POWER_LEVEL; 13 | -------------------------------------------------------------------------------- /ios/Classes/Response/PeripheralResponse.m: -------------------------------------------------------------------------------- 1 | NSString * const PERIPHERAL_RESPONSE_ID = @"id"; 2 | NSString * const PERIPHERAL_RESPONSE_IS_CONNECTABLE = @"isConnectable"; 3 | NSString * const PERIPHERAL_RESPONSE_LOCAL_NAME = @"localName"; 4 | NSString * const PERIPHERAL_RESPONSE_MANUFACTURER_DATA = @"manufacturerData"; 5 | NSString * const PERIPHERAL_RESPONSE_MTU = @"mtu"; 6 | NSString * const PERIPHERAL_RESPONSE_NAME = @"name"; 7 | NSString * const PERIPHERAL_RESPONSE_OVERFLOW_SERVICE_UUIDS = @"overflowServiceUUIDs"; 8 | NSString * const PERIPHERAL_RESPONSE_RSSI = @"rssi"; 9 | NSString * const PERIPHERAL_RESPONSE_SERVICE_DATA = @"serviceData"; 10 | NSString * const PERIPHERAL_RESPONSE_SERVICE_UUIDS = @"serviceUUIDs"; 11 | NSString * const PERIPHERAL_RESPONSE_SOLICITED_SERVICE_UUIDS = @"solicitedServiceUUIDs"; 12 | NSString * const PERIPHERAL_RESPONSE_TX_POWER_LEVEL = @"txPowerLevel"; 13 | -------------------------------------------------------------------------------- /ios/Classes/Response/ServiceResponse.h: -------------------------------------------------------------------------------- 1 | extern NSString * const SERVICE_RESPONSE_ID; 2 | extern NSString * const SERVICE_RESPONSE_UUID; 3 | extern NSString * const SERVICE_RESPONSE_DEVICE_ID; 4 | extern NSString * const SERVICE_RESPONSE_ID_PRIMARY; 5 | -------------------------------------------------------------------------------- /ios/Classes/Response/ServiceResponse.m: -------------------------------------------------------------------------------- 1 | NSString * const SERVICE_RESPONSE_ID = @"id"; 2 | NSString * const SERVICE_RESPONSE_UUID = @"uuid"; 3 | NSString * const SERVICE_RESPONSE_DEVICE_ID = @"deviceId"; 4 | NSString * const SERVICE_RESPONSE_ID_PRIMARY = @"isPrimary"; 5 | -------------------------------------------------------------------------------- /ios/Classes/ResponseConverter/CharacteristicResponseConverter.h: -------------------------------------------------------------------------------- 1 | @interface CharacteristicResponseConverter : NSObject 2 | 3 | + (NSString *)jsonStringFromCharacteristicResponse:(NSDictionary *)response 4 | transactionId:(NSString *)transactionId; 5 | 6 | + (NSString *)jsonStringFromCharacteristicsResponse:(NSArray *)characteristicsResponse; 7 | 8 | + (NSString *)jsonStringWithServiceFromCharacteristicsResponse:(NSArray *)characteristicsResponse; 9 | 10 | + (NSArray *)characteristicsFromCharacteristicResponse:(NSArray *)characteristicsResponse; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /ios/Classes/ResponseConverter/DescriptorResponseConverter.h: -------------------------------------------------------------------------------- 1 | @interface DescriptorResponseConverter : NSObject 2 | 3 | + (NSString *)jsonStringFromDescriptorResponse:(NSDictionary *)response; 4 | 5 | + (NSString *)jsonStringFromMultipleDescriptorsResponse:(NSArray *)descriptorsResponse; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /ios/Classes/ResponseConverter/DescriptorResponseConverter.m: -------------------------------------------------------------------------------- 1 | #import "DescriptorResponseConverter.h" 2 | #import "DescriptorResponse.h" 3 | #import "JSONStringifier.h" 4 | 5 | @implementation DescriptorResponseConverter 6 | 7 | const NSString *keyDescriptorResponseDescriptorId = @"descriptorId"; 8 | const NSString *keyDescriptorResponseDescriptorUuid = @"descriptorUuid"; 9 | const NSString *keyDescriptorResponseValue = @"value"; 10 | const NSString *keyDescriptorResponseServiceId = @"serviceId"; 11 | const NSString *keyDescriptorResponseServiceUuid = @"serviceUuid"; 12 | const NSString *keyDescriptorResponseCharacteristicId = @"id"; 13 | const NSString *keyDescriptorResponseCharacteristicUuid = @"uuid"; 14 | const NSString *keyDescriptorResponseDescriptors = @"descriptors"; 15 | 16 | + (NSString *)jsonStringFromDescriptorResponse:(NSDictionary *)response { 17 | NSDictionary *result = [[NSDictionary alloc] initWithObjectsAndKeys: 18 | [response objectForKey:DESCRIPTOR_RESPONSE_ID], keyDescriptorResponseDescriptorId, 19 | [response objectForKey:DESCRIPTOR_RESPONSE_UUID], keyDescriptorResponseDescriptorUuid, 20 | [response objectForKey:DESCRIPTOR_RESPONSE_VALUE], keyDescriptorResponseValue, 21 | [response objectForKey:DESCRIPTOR_RESPONSE_SERVICE_ID], keyDescriptorResponseServiceId, 22 | [response objectForKey:DESCRIPTOR_RESPONSE_SERVICE_UUID], keyDescriptorResponseServiceUuid, 23 | [response objectForKey:DESCRIPTOR_RESPONSE_CHARACTERISTIC_ID], keyDescriptorResponseCharacteristicId, 24 | [response objectForKey:DESCRIPTOR_RESPONSE_CHARACTERISTIC_UUID], keyDescriptorResponseCharacteristicUuid, 25 | nil]; 26 | return [JSONStringifier jsonStringFromJSONObject:result]; 27 | } 28 | 29 | + (NSString *)jsonStringFromMultipleDescriptorsResponse:(NSArray *)descriptorsResponse { 30 | if ([descriptorsResponse count] > 0) { 31 | NSMutableArray *descriptors = [[NSMutableArray alloc] init]; 32 | for (NSDictionary *singleDescriptor in descriptorsResponse) { 33 | [descriptors addObject:[[NSDictionary alloc] initWithObjectsAndKeys: 34 | [singleDescriptor objectForKey:DESCRIPTOR_RESPONSE_ID], keyDescriptorResponseDescriptorId, 35 | [singleDescriptor objectForKey:DESCRIPTOR_RESPONSE_UUID], keyDescriptorResponseDescriptorUuid, 36 | nil] 37 | ]; 38 | } 39 | 40 | NSDictionary *firstDescriptor = descriptorsResponse[0]; 41 | NSDictionary *result = [[NSDictionary alloc] initWithObjectsAndKeys: 42 | [firstDescriptor objectForKey:DESCRIPTOR_RESPONSE_SERVICE_ID], keyDescriptorResponseServiceId, 43 | [firstDescriptor objectForKey:DESCRIPTOR_RESPONSE_SERVICE_UUID], keyDescriptorResponseServiceUuid, 44 | [firstDescriptor objectForKey:DESCRIPTOR_RESPONSE_CHARACTERISTIC_ID], keyDescriptorResponseCharacteristicId, 45 | [firstDescriptor objectForKey:DESCRIPTOR_RESPONSE_CHARACTERISTIC_UUID], keyDescriptorResponseCharacteristicUuid, 46 | descriptors, keyDescriptorResponseDescriptors, 47 | nil]; 48 | 49 | return [JSONStringifier jsonStringFromJSONObject:result]; 50 | } else { 51 | return [JSONStringifier jsonStringFromJSONObject:descriptorsResponse]; 52 | } 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /ios/Classes/ResponseConverter/PeripheralResponseConverter.h: -------------------------------------------------------------------------------- 1 | @interface PeripheralResponseConverter : NSObject 2 | 3 | + (NSString *)jsonStringFromPeripheralResponse:(NSArray *)response; 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /ios/Classes/ResponseConverter/PeripheralResponseConverter.m: -------------------------------------------------------------------------------- 1 | #import "PeripheralResponseConverter.h" 2 | #import "PeripheralResponse.h" 3 | #import "JsonStringifier.h" 4 | 5 | @implementation PeripheralResponseConverter 6 | 7 | const NSString *keyId = @"id"; 8 | const NSString *keyName = @"name"; 9 | 10 | + (NSString *)jsonStringFromPeripheralResponse:(NSArray *)response { 11 | NSMutableArray *result = [[NSMutableArray alloc] init]; 12 | 13 | for (NSDictionary *peripheral in response) { 14 | NSDictionary *device = [[NSDictionary alloc] initWithObjectsAndKeys: 15 | [peripheral objectForKey:PERIPHERAL_RESPONSE_ID], keyId, 16 | [peripheral objectForKey:PERIPHERAL_RESPONSE_NAME], keyName, 17 | nil]; 18 | [result addObject:device]; 19 | } 20 | 21 | return [JSONStringifier jsonStringFromJSONObject:result]; 22 | }; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /ios/Classes/ResponseConverter/ServiceResponseConverter.h: -------------------------------------------------------------------------------- 1 | @interface ServiceResponseConverter : NSObject 2 | 3 | + (NSString *)jsonStringFromServicesResponse:(NSArray *)servicesResponse; 4 | 5 | + (NSArray *)servicesFromServicesResponse:(NSArray *)servicesResponse; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /ios/Classes/ResponseConverter/ServiceResponseConverter.m: -------------------------------------------------------------------------------- 1 | #import "ServiceResponseConverter.h" 2 | #import "ServiceResponse.h" 3 | #import "JSONStringifier.h" 4 | 5 | @implementation ServiceResponseConverter 6 | 7 | const NSString *keyServiceId = @"serviceId"; 8 | const NSString *keyServiceUuid = @"serviceUuid"; 9 | 10 | + (NSString *)jsonStringFromServicesResponse:(NSArray *)servicesResponse { 11 | NSArray *result = [self servicesFromServicesResponse:servicesResponse]; 12 | return [JSONStringifier jsonStringFromJSONObject:result]; 13 | } 14 | 15 | + (NSArray *)servicesFromServicesResponse:(NSArray *)servicesResponse { 16 | NSMutableArray *result = [[NSMutableArray alloc] init]; 17 | for (NSDictionary *service in servicesResponse) { 18 | [result addObject:[self serviceDictionaryFromServiceResponse:service]]; 19 | } 20 | return result; 21 | } 22 | 23 | + (NSDictionary *)serviceDictionaryFromServiceResponse:(NSDictionary *)serviceResponse { 24 | NSDictionary *service = [[NSDictionary alloc] initWithObjectsAndKeys: 25 | [serviceResponse objectForKey:SERVICE_RESPONSE_ID], keyServiceId, 26 | [serviceResponse objectForKey:SERVICE_RESPONSE_UUID], keyServiceUuid, 27 | nil]; 28 | return service; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /ios/Classes/SwiftBridging.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | -------------------------------------------------------------------------------- /ios/Classes/Util/ArgumentHandler.h: -------------------------------------------------------------------------------- 1 | @interface ArgumentHandler : NSObject 2 | 3 | + (nullable id)argumentOrNil:(id _Nonnull)argument; 4 | 5 | + (nullable NSString *)stringOrNil:(id _Nonnull)argument; 6 | 7 | + (nullable NSArray *)stringArrayOrNil:(id _Nonnull)argument; 8 | 9 | + (nullable NSDictionary *)dictionaryOrNil:(NSArray * _Nonnull)argumentKeys in:(NSDictionary * _Nonnull)dictionary; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /ios/Classes/Util/ArgumentHandler.m: -------------------------------------------------------------------------------- 1 | #import "ArgumentHandler.h" 2 | 3 | @implementation ArgumentHandler 4 | 5 | + (nullable id)argumentOrNil:(id)argument { 6 | if (argument != nil && (NSNull *)argument != [NSNull null]) { 7 | return argument; 8 | } else { 9 | return nil; 10 | } 11 | } 12 | 13 | + (nullable NSString *)stringOrNil:(id)argument { 14 | if (argument != nil && [argument isKindOfClass:[NSString class]]) { 15 | return (NSString*)argument; 16 | } else { 17 | return nil; 18 | } 19 | } 20 | 21 | + (nullable NSArray *)stringArrayOrNil:(id)argument { 22 | if (argument != nil && [argument isKindOfClass:[NSArray class]]) { 23 | return (NSArray*)argument; 24 | } else { 25 | return nil; 26 | } 27 | } 28 | 29 | + (nullable NSDictionary *)dictionaryOrNil:(NSArray *)argumentKeys in:(NSDictionary *)dictionary { 30 | NSMutableDictionary * resultDictionary = [NSMutableDictionary new]; 31 | for (NSString *argumentKey in argumentKeys) { 32 | if ([dictionary objectForKey:argumentKey] != nil) { 33 | [resultDictionary setValue:[self argumentOrNil:[dictionary objectForKey:argumentKey]] forKey:argumentKey]; 34 | } 35 | } 36 | return resultDictionary; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ios/Classes/Util/CommonTypes.h: -------------------------------------------------------------------------------- 1 | typedef void (^Resolve)(id result); 2 | typedef void (^Reject)(NSString *code, NSString *message, NSError *error); 3 | -------------------------------------------------------------------------------- /ios/Classes/Util/FlutterErrorFactory.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FlutterErrorFactory : NSObject 4 | 5 | + (FlutterError *)flutterErrorFromJSONString:(NSString *)jsonString; 6 | 7 | + (FlutterError *)flutterErrorFromJSONString:(NSString *)jsonString 8 | withTransactionId:(NSString *)transactionId; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /ios/Classes/Util/FlutterErrorFactory.m: -------------------------------------------------------------------------------- 1 | #import "FlutterErrorFactory.h" 2 | #import "JSONStringifier.h" 3 | 4 | @implementation FlutterErrorFactory 5 | 6 | + (FlutterError *)flutterErrorFromJSONString:(NSString *)jsonString { 7 | NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] 8 | options:NSJSONReadingMutableContainers 9 | error:nil]; 10 | return [FlutterError errorWithCode:[[dictionary objectForKey:@"errorCode"] stringValue] 11 | message:[dictionary objectForKey:@"reason"] 12 | details:jsonString]; 13 | } 14 | 15 | + (FlutterError *)flutterErrorFromJSONString:(NSString *)jsonString 16 | withTransactionId:(NSString *)transactionId { 17 | NSMutableDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] 18 | options:NSJSONReadingMutableContainers 19 | error:nil]; 20 | [dictionary setObject:transactionId forKey:@"transactionId"]; 21 | return [FlutterError errorWithCode:[[dictionary objectForKey:@"errorCode"] stringValue] 22 | message:[dictionary objectForKey:@"reason"] 23 | details:[JSONStringifier jsonStringFromJSONObject:dictionary]]; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /ios/Classes/Util/JSONStringifier.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface JSONStringifier : NSObject 4 | 5 | + (NSString *)jsonStringFromJSONObject:(id)jsonObject; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /ios/Classes/Util/JSONStringifier.m: -------------------------------------------------------------------------------- 1 | #import "JSONStringifier.h" 2 | 3 | @implementation JSONStringifier 4 | 5 | + (NSString *)jsonStringFromJSONObject:(id)jsonObject { 6 | NSData * jsonData = [NSJSONSerialization dataWithJSONObject:jsonObject 7 | options:NSJSONWritingPrettyPrinted 8 | error:nil]; 9 | return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 10 | } 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /ios/Classes/flutter_ble_lib-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /ios/flutter_ble_lib.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'flutter_ble_lib' 6 | s.version = '2.3.2' 7 | s.summary = 'A new flutter plugin project.' 8 | s.description = <<-DESC 9 | A new flutter plugin project. 10 | DESC 11 | s.homepage = 'http://example.com' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Your Company' => 'email@example.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | s.swift_versions = ['4.0', '4.2', '5.0'] 19 | s.dependency 'MultiplatformBleAdapter', '~> 0.1.8' 20 | 21 | s.ios.deployment_target = '8.0' 22 | end 23 | 24 | -------------------------------------------------------------------------------- /lib/descriptor.dart: -------------------------------------------------------------------------------- 1 | part of flutter_ble_lib; 2 | 3 | abstract class _DescriptorMetadata { 4 | static const String uuid = 'descriptorUuid'; 5 | static const String id = 'descriptorId'; 6 | static const String value = 'value'; 7 | } 8 | 9 | class Descriptor extends InternalDescriptor { 10 | final ManagerForDescriptor _manager; 11 | final Characteristic characteristic; 12 | final String uuid; 13 | 14 | Descriptor.fromJson( 15 | Map jsonObject, 16 | Characteristic characteristic, 17 | ManagerForDescriptor manager, 18 | ) : _manager = manager, 19 | characteristic = characteristic, 20 | uuid = jsonObject[_DescriptorMetadata.uuid], 21 | super(jsonObject[_DescriptorMetadata.id]); 22 | 23 | Future read({String? transactionId}) => 24 | _manager.readDescriptorForIdentifier( 25 | this, 26 | transactionId ?? TransactionIdGenerator.getNextId(), 27 | ); 28 | 29 | Future write(Uint8List value, {String? transactionId}) => 30 | _manager.writeDescriptorForIdentifier( 31 | this, 32 | value, 33 | transactionId ?? TransactionIdGenerator.getNextId(), 34 | ); 35 | 36 | @override 37 | bool operator ==(Object other) => 38 | identical(this, other) || 39 | other is Descriptor && 40 | runtimeType == other.runtimeType && 41 | _manager == other._manager && 42 | characteristic == other.characteristic && 43 | uuid == other.uuid; 44 | 45 | @override 46 | int get hashCode => 47 | _manager.hashCode ^ characteristic.hashCode ^ uuid.hashCode; 48 | } 49 | 50 | class DescriptorWithValue extends Descriptor { 51 | Uint8List value; 52 | 53 | DescriptorWithValue.fromJson( 54 | Map jsonObject, 55 | Characteristic characteristic, 56 | ManagerForDescriptor manager, 57 | ) : value = base64Decode(jsonObject[_DescriptorMetadata.value]), 58 | super.fromJson(jsonObject, characteristic, manager); 59 | } 60 | -------------------------------------------------------------------------------- /lib/flutter_ble_lib.dart: -------------------------------------------------------------------------------- 1 | /// Library for handling Bluetooth Low Energy functionality. 2 | /// 3 | /// The library is organised around a few base entities, which are: 4 | /// - [BleManager] 5 | /// - [Peripheral] 6 | /// - [Service] 7 | /// - [Characteristic] 8 | /// - [Descriptor] 9 | /// 10 | /// You have to create an instance of [BleManager] and initialise underlying 11 | /// native resources. Using that instance you then obtain an instance of 12 | /// [Peripheral], which can be used to run operations on the corresponding 13 | /// peripheral. 14 | /// 15 | /// All operations passing the Dart-native bridge are asynchronous, 16 | /// hence all operations in the plugin return either [Future] or [Stream]. 17 | /// 18 | /// The library handles scanning for peripherals, connecting to peripherals, 19 | /// service discovery process on peripherals, manipulating characteristics 20 | /// and descriptors. 21 | /// 22 | /// Bonding is handled transparently by the platform's operating system. 23 | /// 24 | /// You can also listen to changes of Bluetooth adapter's state. 25 | /// 26 | /// ```dart 27 | /// BleManager bleManager = BleManager(); 28 | /// await bleManager.createClient(); //ready to go! 29 | /// //your BLE logic 30 | /// bleManager.destroyClient(); //remember to release native resources when you're done! 31 | /// ``` 32 | /// 33 | /// For more samples refer to specific classes. 34 | library flutter_ble_lib; 35 | 36 | import 'dart:async'; 37 | import 'dart:convert'; 38 | import 'dart:typed_data'; 39 | 40 | import 'package:flutter_ble_lib/src/_internal.dart'; 41 | import 'package:flutter_ble_lib/src/_managers_for_classes.dart'; 42 | import 'package:flutter_ble_lib/src/util/_transaction_id_generator.dart'; 43 | 44 | import 'src/_managers_for_classes.dart'; 45 | 46 | part 'error/ble_error.dart'; 47 | 48 | part 'ble_manager.dart'; 49 | 50 | part 'characteristic.dart'; 51 | 52 | part 'descriptor.dart'; 53 | 54 | part 'peripheral.dart'; 55 | 56 | part 'scan_result.dart'; 57 | 58 | part 'service.dart'; 59 | -------------------------------------------------------------------------------- /lib/scan_result.dart: -------------------------------------------------------------------------------- 1 | part of flutter_ble_lib; 2 | 3 | abstract class _ScanResultMetadata { 4 | static const String rssi = "rssi"; 5 | static const String manufacturerData = "manufacturerData"; 6 | static const String serviceData = "serviceData"; 7 | static const String serviceUuids = "serviceUUIDs"; 8 | static const String localName = "localName"; 9 | static const String txPowerLevel = "txPowerLevel"; 10 | static const String solicitedServiceUuids = "solicitedServiceUUIDs"; 11 | static const String isConnectable = "isConnectable"; 12 | static const String overflowServiceUuids = "overflowServiceUUIDs"; 13 | } 14 | 15 | /// A scan result emitted by the scanning operation, containing [Peripheral] and [AdvertisementData]. 16 | class ScanResult { 17 | final Peripheral peripheral; 18 | 19 | /// Signal strength of the peripheral in dBm. 20 | final int rssi; 21 | 22 | /// An indicator whether the peripheral is connectable (iOS only). 23 | final bool? isConnectable; 24 | 25 | /// A list of UUIDs found in the overflow area of the advertisement data (iOS only). 26 | final List overflowServiceUuids; 27 | 28 | /// A packet of data advertised by the peripheral. 29 | final AdvertisementData advertisementData; 30 | 31 | ScanResult._( 32 | this.peripheral, 33 | this.rssi, 34 | this.advertisementData, 35 | {this.isConnectable, 36 | List? overflowServiceUuids, 37 | }) : overflowServiceUuids = overflowServiceUuids ?? []; 38 | 39 | 40 | factory ScanResult.fromJson( 41 | Map json, 42 | ManagerForPeripheral manager 43 | ) { 44 | assert(json[_ScanResultMetadata.rssi] is int); 45 | return ScanResult._( 46 | Peripheral.fromJson(json, manager), 47 | json[_ScanResultMetadata.rssi], 48 | AdvertisementData._fromJson(json), 49 | isConnectable: json[_ScanResultMetadata.isConnectable], 50 | overflowServiceUuids: json[_ScanResultMetadata.overflowServiceUuids] 51 | ); 52 | } 53 | } 54 | 55 | /// Data advertised by the [Peripheral]: power level, local name, 56 | /// manufacturer's data, advertised [Service]s 57 | class AdvertisementData { 58 | /// The manufacturer data of the peripheral. 59 | final Uint8List? manufacturerData; 60 | 61 | /// A dictionary that contains service-specific advertisement data. 62 | final Map? serviceData; 63 | 64 | /// A list of service UUIDs. 65 | final List? serviceUuids; 66 | 67 | /// The local name of the [Peripheral]. Might be different than 68 | /// [Peripheral.name]. 69 | final String? localName; 70 | 71 | /// The transmit power of the peripheral. 72 | final int? txPowerLevel; 73 | 74 | /// A list of solicited service UUIDs. 75 | final List? solicitedServiceUuids; 76 | 77 | AdvertisementData._fromJson(Map json) 78 | : manufacturerData = 79 | _decodeBase64OrNull(json[_ScanResultMetadata.manufacturerData]), 80 | serviceData = 81 | _getServiceDataOrNull(json[_ScanResultMetadata.serviceData]), 82 | serviceUuids = 83 | _mapToListOfStringsOrNull(json[_ScanResultMetadata.serviceUuids]), 84 | localName = json[_ScanResultMetadata.localName], 85 | txPowerLevel = json[_ScanResultMetadata.txPowerLevel], 86 | solicitedServiceUuids = 87 | _mapToListOfStringsOrNull( 88 | json[_ScanResultMetadata.solicitedServiceUuids] 89 | ); 90 | 91 | static Map? _getServiceDataOrNull( 92 | Map? serviceData) { 93 | return serviceData?.map( 94 | (key, value) => MapEntry(key, base64Decode(value)), 95 | ); 96 | } 97 | 98 | static Uint8List? _decodeBase64OrNull(String? base64Value) { 99 | if (base64Value != null) { 100 | return base64.decode(base64Value); 101 | } else { 102 | return null; 103 | } 104 | } 105 | 106 | static List? _mapToListOfStringsOrNull(List? values) => 107 | values?.cast(); 108 | } 109 | -------------------------------------------------------------------------------- /lib/src/_containers.dart: -------------------------------------------------------------------------------- 1 | abstract class _ConnectionStateContainerMetadata { 2 | static const String peripheralIdentifier = "peripheralIdentifier"; 3 | static const String connectionState = "connectionState"; 4 | } 5 | 6 | class ConnectionStateContainer { 7 | String peripheralIdentifier; 8 | String connectionState; 9 | 10 | ConnectionStateContainer.fromJson(Map json) 11 | : peripheralIdentifier = 12 | json[_ConnectionStateContainerMetadata.peripheralIdentifier], 13 | connectionState = 14 | json[_ConnectionStateContainerMetadata.connectionState]; 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/_internal.dart: -------------------------------------------------------------------------------- 1 | library _internal; 2 | 3 | import 'dart:async'; 4 | import 'dart:convert'; 5 | import 'dart:typed_data'; 6 | 7 | import 'package:collection/collection.dart'; 8 | import 'package:flutter/foundation.dart'; 9 | import 'package:flutter/services.dart'; 10 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 11 | import 'package:flutter_ble_lib/src/_constants.dart'; 12 | import 'package:flutter_ble_lib/src/_containers.dart'; 13 | import 'package:flutter_ble_lib/src/util/_transaction_id_generator.dart'; 14 | import 'package:flutter_ble_lib/src/util/_transformers.dart'; 15 | 16 | import '_managers_for_classes.dart'; 17 | 18 | part 'base_entities.dart'; 19 | 20 | part 'internal_ble_manager.dart'; 21 | 22 | part 'bridge/bluetooth_state_mixin.dart'; 23 | 24 | part 'bridge/characteristics_mixin.dart'; 25 | 26 | part 'bridge/device_connection_mixin.dart'; 27 | 28 | part 'bridge/descriptors_mixin.dart'; 29 | 30 | part 'bridge/device_rssi_mixin.dart'; 31 | 32 | part 'bridge/devices_mixin.dart'; 33 | 34 | part 'bridge/discovery_mixin.dart'; 35 | 36 | part 'bridge/lib_core.dart'; 37 | 38 | part 'bridge/log_level_mixin.dart'; 39 | 40 | part 'bridge/mtu_mixin.dart'; 41 | 42 | part 'bridge/scanning_mixin.dart'; 43 | -------------------------------------------------------------------------------- /lib/src/base_entities.dart: -------------------------------------------------------------------------------- 1 | part of _internal; 2 | 3 | class InternalService { 4 | final int _id; 5 | 6 | InternalService(this._id); 7 | } 8 | 9 | class InternalCharacteristic { 10 | final int _id; 11 | 12 | InternalCharacteristic(this._id); 13 | } 14 | 15 | class InternalDescriptor { 16 | final int _id; 17 | 18 | InternalDescriptor(this._id); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /lib/src/bridge/bluetooth_state_mixin.dart: -------------------------------------------------------------------------------- 1 | part of _internal; 2 | 3 | mixin BluetoothStateMixin on FlutterBLE { 4 | final Stream _adapterStateChanges = 5 | const EventChannel(ChannelName.adapterStateChanges) 6 | .receiveBroadcastStream(); 7 | 8 | Future enableRadio(String transactionId) async { 9 | await _methodChannel.invokeMethod( 10 | MethodName.enableRadio, 11 | { 12 | ArgumentName.transactionId: transactionId, 13 | }, 14 | ).catchError((errorJson) => 15 | Future.error(BleError.fromJson(jsonDecode(errorJson.details)))); 16 | } 17 | 18 | Future disableRadio(String transactionId) async { 19 | await _methodChannel.invokeMethod( 20 | MethodName.disableRadio, 21 | { 22 | ArgumentName.transactionId: transactionId, 23 | }, 24 | ).catchError((errorJson) => 25 | Future.error(BleError.fromJson(jsonDecode(errorJson.details)))); 26 | } 27 | 28 | Future state() => _methodChannel 29 | .invokeMethod(MethodName.getState) 30 | .then(_mapToBluetoothState); 31 | 32 | Stream observeBluetoothState(bool emitCurrentValue) async* { 33 | if (emitCurrentValue == true) { 34 | BluetoothState currentState = await state(); 35 | yield currentState; 36 | } 37 | yield* _adapterStateChanges.map(_mapToBluetoothState); 38 | } 39 | 40 | BluetoothState _mapToBluetoothState(dynamic rawValue) { 41 | switch (rawValue) { 42 | case "Unknown": 43 | return BluetoothState.UNKNOWN; 44 | case "Unsupported": 45 | return BluetoothState.UNSUPPORTED; 46 | case "Unauthorized": 47 | return BluetoothState.UNAUTHORIZED; 48 | case "Resetting": 49 | return BluetoothState.RESETTING; 50 | case "PoweredOn": 51 | return BluetoothState.POWERED_ON; 52 | case "PoweredOff": 53 | return BluetoothState.POWERED_OFF; 54 | default: 55 | throw "Cannot map $rawValue to known bluetooth state"; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/bridge/device_connection_mixin.dart: -------------------------------------------------------------------------------- 1 | part of _internal; 2 | 3 | mixin DeviceConnectionMixin on FlutterBLE { 4 | final Stream _peripheralConnectionStateChanges = 5 | const EventChannel(ChannelName.connectionStateChangeEvents) 6 | .receiveBroadcastStream(); 7 | 8 | Future connectToPeripheral( 9 | String deviceIdentifier, 10 | bool isAutoConnect, 11 | int requestMtu, 12 | bool refreshGatt, 13 | Duration? timeout 14 | ) async { 15 | return await _methodChannel.invokeMethod( 16 | MethodName.connectToDevice, 17 | { 18 | ArgumentName.deviceIdentifier: deviceIdentifier, 19 | ArgumentName.isAutoConnect: isAutoConnect, 20 | ArgumentName.requestMtu: requestMtu, 21 | ArgumentName.refreshGatt: refreshGatt, 22 | ArgumentName.timeoutMillis: timeout?.inMilliseconds 23 | }, 24 | ).catchError( 25 | (errorJson) => Future.error( 26 | BleError.fromJson(jsonDecode(errorJson.details)), 27 | ), 28 | ); 29 | } 30 | 31 | Stream observePeripheralConnectionState( 32 | String identifier, bool emitCurrentValue) { 33 | final controller = StreamController( 34 | onListen: () => _methodChannel.invokeMethod( 35 | MethodName.observeConnectionState, 36 | { 37 | ArgumentName.deviceIdentifier: identifier, 38 | ArgumentName.emitCurrentValue: emitCurrentValue, 39 | }, 40 | ).catchError( 41 | (errorJson) => throw BleError.fromJson(jsonDecode(errorJson.details)), 42 | ), 43 | ); 44 | 45 | final sourceStream = _peripheralConnectionStateChanges 46 | .map((jsonString) => 47 | ConnectionStateContainer.fromJson(jsonDecode(jsonString))) 48 | .where((connectionStateContainer) => 49 | connectionStateContainer.peripheralIdentifier == identifier) 50 | .map((connectionStateContainer) => 51 | connectionStateContainer.connectionState) 52 | .map((connectionStateString) { 53 | switch (connectionStateString.toLowerCase()) { 54 | case NativeConnectionState.connected: 55 | return PeripheralConnectionState.connected; 56 | case NativeConnectionState.connecting: 57 | return PeripheralConnectionState.connecting; 58 | case NativeConnectionState.disconnected: 59 | return PeripheralConnectionState.disconnected; 60 | case NativeConnectionState.disconnecting: 61 | return PeripheralConnectionState.disconnecting; 62 | default: 63 | throw FormatException( 64 | 'Unrecognized value of device connection state. Value: $connectionStateString', 65 | ); 66 | } 67 | }); 68 | 69 | controller 70 | .addStream( 71 | sourceStream, 72 | cancelOnError: true, 73 | ) 74 | .then((value) => controller.close()); 75 | 76 | return controller.stream; 77 | } 78 | 79 | Future isPeripheralConnected(String peripheralIdentifier) async { 80 | return await _methodChannel 81 | .invokeMethod(MethodName.isDeviceConnected, { 82 | ArgumentName.deviceIdentifier: peripheralIdentifier, 83 | }).catchError( 84 | (errorJson) { 85 | if (errorJson is MissingPluginException) { 86 | return Future.error(errorJson); 87 | } 88 | return Future.error( 89 | BleError.fromJson(jsonDecode(errorJson.details)) 90 | ); 91 | } 92 | ); 93 | } 94 | 95 | Future disconnectOrCancelPeripheralConnection( 96 | String peripheralIdentifier) async { 97 | return await _methodChannel 98 | .invokeMethod(MethodName.cancelConnection, { 99 | ArgumentName.deviceIdentifier: peripheralIdentifier, 100 | }).catchError( 101 | (errorJson) => Future.error( 102 | BleError.fromJson(jsonDecode(errorJson.details)), 103 | ), 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/src/bridge/device_rssi_mixin.dart: -------------------------------------------------------------------------------- 1 | part of _internal; 2 | 3 | mixin RssiMixin on FlutterBLE { 4 | Future rssi(Peripheral peripheral, String transactionId) async { 5 | return await _methodChannel.invokeMethod(MethodName.rssi, { 6 | ArgumentName.deviceIdentifier: peripheral.identifier, 7 | ArgumentName.transactionId: transactionId 8 | }).catchError((errorJson) => 9 | Future.error(BleError.fromJson(jsonDecode(errorJson.details)))); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/bridge/devices_mixin.dart: -------------------------------------------------------------------------------- 1 | part of _internal; 2 | 3 | mixin DevicesMixin on FlutterBLE { 4 | Future> knownDevices( 5 | List peripheralIdentifiers) async { 6 | return _methodChannel 7 | .invokeMethod(MethodName.knownDevices, { 8 | ArgumentName.deviceIdentifiers: peripheralIdentifiers, 9 | }).then((peripheralsJson) { 10 | print("known devices json: $peripheralsJson"); 11 | return _parsePeripheralsJson(peripheralsJson); 12 | }); 13 | } 14 | 15 | Future> connectedDevices(List serviceUuids) async { 16 | return _methodChannel 17 | .invokeMethod(MethodName.connectedDevices, { 18 | ArgumentName.uuids: serviceUuids, 19 | }).then((peripheralsJson) { 20 | print("connected devices json: $peripheralsJson"); 21 | return _parsePeripheralsJson(peripheralsJson); 22 | }); 23 | } 24 | 25 | List _parsePeripheralsJson(String peripheralsJson) { 26 | List list = json 27 | .decode(peripheralsJson) 28 | .map((peripheral) => Peripheral.fromJson(peripheral, _manager)) 29 | .toList(); 30 | return list.cast(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/bridge/lib_core.dart: -------------------------------------------------------------------------------- 1 | part of _internal; 2 | 3 | abstract class FlutterBLE { 4 | final InternalBleManager _manager; 5 | 6 | FlutterBLE._(this._manager); 7 | 8 | final MethodChannel _methodChannel = 9 | const MethodChannel(ChannelName.flutterBleLib); 10 | 11 | Future cancelTransaction(String transactionId) async { 12 | await _methodChannel.invokeMethod(MethodName.cancelTransaction, 13 | {ArgumentName.transactionId: transactionId}); 14 | return; 15 | } 16 | } 17 | 18 | class FlutterBleLib extends FlutterBLE 19 | with 20 | DeviceConnectionMixin, 21 | DiscoveryMixin, 22 | ScanningMixin, 23 | LogLevelMixin, 24 | RssiMixin, 25 | MtuMixin, 26 | BluetoothStateMixin, 27 | DevicesMixin, 28 | CharacteristicsMixin, 29 | DescriptorsMixin { 30 | final Stream _restoreStateEvents = 31 | const EventChannel(ChannelName.stateRestoreEvents) 32 | .receiveBroadcastStream(); 33 | 34 | FlutterBleLib(InternalBleManager manager) : super._(manager); 35 | 36 | Future> restoredState() async { 37 | final peripherals = await _restoreStateEvents 38 | .map( 39 | (jsonString) { 40 | if (jsonString == null || 41 | jsonString is String == false) { 42 | return null; 43 | } 44 | final restoredPeripheralsJson = 45 | (jsonDecode(jsonString) as List) 46 | .cast>(); 47 | return restoredPeripheralsJson 48 | .map((peripheralJson) => 49 | Peripheral.fromJson(peripheralJson, _manager)) 50 | .toList(); 51 | 52 | }, 53 | ) 54 | .take(1) 55 | .single; 56 | return peripherals ?? []; 57 | } 58 | 59 | Future isClientCreated() => 60 | _methodChannel.invokeMethod(MethodName.isClientCreated) 61 | .then((value) => value!); 62 | 63 | Future createClient(String? restoreStateIdentifier) async { 64 | await _methodChannel.invokeMethod(MethodName.createClient, { 65 | ArgumentName.restoreStateIdentifier: restoreStateIdentifier 66 | }); 67 | } 68 | 69 | Future destroyClient() async { 70 | await _methodChannel.invokeMethod(MethodName.destroyClient); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/bridge/log_level_mixin.dart: -------------------------------------------------------------------------------- 1 | part of _internal; 2 | 3 | mixin LogLevelMixin on FlutterBLE { 4 | Future setLogLevel(LogLevel logLevel) async { 5 | print("set log level to ${describeEnum(logLevel)}"); 6 | return await _methodChannel.invokeMethod( 7 | MethodName.setLogLevel, 8 | { 9 | ArgumentName.logLevel: describeEnum(logLevel), 10 | }, 11 | ).catchError((errorJson) => 12 | Future.error(BleError.fromJson(jsonDecode(errorJson.details)))); 13 | } 14 | 15 | Future logLevel() async { 16 | String logLevelName = 17 | await _methodChannel.invokeMethod(MethodName.logLevel); 18 | return _logLevelFromString(logLevelName); 19 | } 20 | 21 | LogLevel _logLevelFromString(String logLevelName) { 22 | print("try to get log level from: $logLevelName"); 23 | return LogLevel.values.firstWhere( 24 | (e) => e.toString() == 'LogLevel.' + logLevelName.toLowerCase()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/bridge/mtu_mixin.dart: -------------------------------------------------------------------------------- 1 | part of _internal; 2 | 3 | mixin MtuMixin on FlutterBLE { 4 | Future requestMtu( 5 | Peripheral peripheral, int mtu, String transactionId) async { 6 | return await _methodChannel 7 | .invokeMethod(MethodName.requestMtu, { 8 | ArgumentName.deviceIdentifier: peripheral.identifier, 9 | ArgumentName.mtu: mtu, 10 | ArgumentName.transactionId: transactionId 11 | }).catchError((errorJson) => 12 | Future.error(BleError.fromJson(jsonDecode(errorJson.details)))); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/bridge/scanning_mixin.dart: -------------------------------------------------------------------------------- 1 | part of _internal; 2 | 3 | mixin ScanningMixin on FlutterBLE { 4 | Stream? _activeScanEvents; 5 | Stream get _scanEvents { 6 | var scanEvents = _activeScanEvents; 7 | if (scanEvents == null) { 8 | scanEvents = 9 | const EventChannel( 10 | ChannelName.scanningEvents 11 | ).receiveBroadcastStream().handleError( 12 | (errorJson) => throw BleError.fromJson( 13 | jsonDecode(errorJson.details) 14 | ), 15 | test: (error) => error is PlatformException, 16 | ).map( 17 | (scanResultJson) => 18 | ScanResult.fromJson(jsonDecode(scanResultJson), _manager), 19 | ); 20 | _activeScanEvents = scanEvents; 21 | } 22 | return scanEvents; 23 | } 24 | void _resetScanEvents() { 25 | _activeScanEvents = null; 26 | } 27 | 28 | Stream startDeviceScan( 29 | int scanMode, 30 | int callbackType, 31 | List uuids, 32 | bool allowDuplicates, 33 | ) { 34 | final streamController = StreamController.broadcast( 35 | onListen: () => _methodChannel.invokeMethod( 36 | MethodName.startDeviceScan, 37 | { 38 | ArgumentName.scanMode: scanMode, 39 | ArgumentName.callbackType: callbackType, 40 | ArgumentName.uuids: uuids, 41 | ArgumentName.allowDuplicates: allowDuplicates, 42 | }, 43 | ), 44 | onCancel: () => stopDeviceScan(), 45 | ); 46 | 47 | streamController 48 | .addStream(_scanEvents, cancelOnError: true) 49 | .then((_) => streamController.close()); 50 | 51 | return streamController.stream; 52 | } 53 | 54 | Future stopDeviceScan() async { 55 | await _methodChannel.invokeMethod(MethodName.stopDeviceScan); 56 | _resetScanEvents(); 57 | return; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/util/_transaction_id_generator.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | abstract class TransactionIdGenerator { 4 | static int _id = 0; 5 | 6 | @visibleForTesting 7 | static int get id => _id; 8 | 9 | static String getNextId() { 10 | _id++; 11 | return _id.toString(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/util/_transformers.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class CancelOnErrorStreamTransformer extends StreamTransformerBase { 4 | @override 5 | Stream bind(Stream stream) => 6 | stream.transform(StreamTransformer.fromHandlers( 7 | handleError: (error, stacktrace, sink) { 8 | sink.addError(error, stacktrace); 9 | sink.close(); 10 | }, 11 | )); 12 | } 13 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_ble_lib 2 | description: FlutterBle Library is a flutter library that supports BLE operations. It uses MultiPlatformBleAdapter as a native backend.. 3 | version: 2.4.0 4 | homepage: https://github.com/Polidea/FlutterBleLib 5 | 6 | environment: 7 | sdk: ">=2.12.0-0 <3.0.0" 8 | # Flutter versions prior to 1.10 did not support the flutter.plugin.platforms map. 9 | flutter: ">=1.10.0 <2.0.0" 10 | 11 | dependencies: 12 | collection: ^1.15.0 13 | async: ^2.5.0 14 | flutter: 15 | sdk: flutter 16 | 17 | dev_dependencies: 18 | mockito: ^5.0.3 19 | build_runner: ^1.12.2 20 | pedantic: ^1.11.0 21 | 22 | flutter_test: 23 | sdk: flutter 24 | 25 | # For information on the generic Dart part of this file, see the 26 | # following page: https://dart.dev/tools/pub/pubspec 27 | 28 | # The following section is specific to Flutter. 29 | flutter: 30 | # This section identifies this Flutter project as a plugin project. 31 | # The androidPackage and pluginClass identifiers should not ordinarily 32 | # be modified. They are used by the tooling to maintain consistency when 33 | # adding or updating assets for this project. 34 | plugin: 35 | platforms: 36 | android: 37 | package: com.polidea.flutter_ble_lib 38 | pluginClass: FlutterBleLibPlugin 39 | ios: 40 | pluginClass: FlutterBleLibPlugin 41 | # To add assets to your plugin package, add an assets section, like this: 42 | # assets: 43 | # - images/a_dot_burr.jpeg 44 | # - images/a_dot_ham.jpeg 45 | # 46 | # For details regarding assets in packages, see 47 | # https://flutter.dev/assets-and-images/#from-packages 48 | # 49 | # An image asset can refer to one or more resolution-specific "variants", see 50 | # https://flutter.dev/assets-and-images/#resolution-aware. 51 | # To add custom fonts to your plugin package, add a fonts section here, 52 | # in this "flutter" section. Each entry in this list should have a 53 | # "family" key with the font family name, and a "fonts" key with a 54 | # list giving the asset and other descriptors for the font. For 55 | # example: 56 | # fonts: 57 | # - family: Schyler 58 | # fonts: 59 | # - asset: fonts/Schyler-Regular.ttf 60 | # - asset: fonts/Schyler-Italic.ttf 61 | # style: italic 62 | # - family: Trajan Pro 63 | # fonts: 64 | # - asset: fonts/TrajanPro.ttf 65 | # - asset: fonts/TrajanPro_Bold.ttf 66 | # weight: 700 67 | # 68 | # For details regarding fonts in packages, see 69 | # https://flutter.dev/custom-fonts/#from-packages 70 | -------------------------------------------------------------------------------- /site/flutter-ble-lib-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/site/flutter-ble-lib-logo-small.png -------------------------------------------------------------------------------- /site/flutter-ble-lib-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/site/flutter-ble-lib-logo.png -------------------------------------------------------------------------------- /site/logo_Blemulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotintent/FlutterBleLib/a4df42eda471f0c53a06b7b9937db8b59585f318/site/logo_Blemulator.png -------------------------------------------------------------------------------- /test/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../analysis_options.yaml 2 | 3 | analyzer: 4 | exclude: [./**] 5 | 6 | linter: 7 | rules: 8 | unawaited_futures: false 9 | -------------------------------------------------------------------------------- /test/ble_manager_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | test('Ble manager must be singleton', () { 6 | //given 7 | final firstInstanceOfBlemanager = BleManager(); 8 | 9 | //when 10 | final secondInstanceOfBleManager = BleManager(); 11 | 12 | //then 13 | expect(secondInstanceOfBleManager, same(firstInstanceOfBlemanager)); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /test/descriptor_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 5 | import 'package:flutter_ble_lib/src/_managers_for_classes.dart'; 6 | import 'package:mockito/mockito.dart'; 7 | import 'package:mockito/annotations.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'test_util/descriptor_generator.dart'; 11 | 12 | import './descriptor_test.mocks.dart'; 13 | 14 | @GenerateMocks([ManagerForDescriptor, Characteristic]) 15 | void main() { 16 | final managerForDescriptor = MockManagerForDescriptor(); 17 | when( 18 | managerForDescriptor.readDescriptorForIdentifier(any, any) 19 | ).thenAnswer( 20 | (_) async => Uint8List.fromList([]) 21 | ); 22 | DescriptorGenerator descriptorGenerator = 23 | DescriptorGenerator(managerForDescriptor); 24 | 25 | DescriptorWithValue createDescriptor(int seed) => 26 | descriptorGenerator.create(seed, MockCharacteristic()); 27 | 28 | Descriptor descriptor = createDescriptor(123); 29 | 30 | tearDown(() { 31 | clearInteractions(managerForDescriptor); 32 | }); 33 | 34 | test("read returns expected value", () async { 35 | //given 36 | when(managerForDescriptor.readDescriptorForIdentifier(descriptor, "456")) 37 | .thenAnswer((_) => Future.value(Uint8List.fromList([1, 2, 3, 4]))); 38 | 39 | //when 40 | var value = await descriptor.read(transactionId: "456"); 41 | 42 | //then 43 | expect(value, equals(Uint8List.fromList([1, 2, 3, 4]))); 44 | }); 45 | 46 | test( 47 | "read invokes manager with expected params when transactionId is specified", 48 | () { 49 | //when 50 | descriptor.read(transactionId: "456"); 51 | 52 | //then 53 | verify( 54 | managerForDescriptor.readDescriptorForIdentifier(descriptor, "456"), 55 | ); 56 | }); 57 | 58 | test( 59 | "read invokes manager with expected params when transactionId is not specified", 60 | () { 61 | //when 62 | descriptor.read(); 63 | 64 | //then 65 | verify( 66 | managerForDescriptor.readDescriptorForIdentifier( 67 | descriptor, argThat(isNotNull)), 68 | ); 69 | }); 70 | 71 | test( 72 | "read invokes manager with unique transactionId when transactionId is not specified", 73 | () { 74 | //when 75 | descriptor.read(); 76 | descriptor.read(); 77 | 78 | //then 79 | var transactionIds = verify( 80 | managerForDescriptor.readDescriptorForIdentifier( 81 | descriptor, captureThat(isNotNull)), 82 | ).captured; 83 | expect(transactionIds[0], isNot(equals(transactionIds[1]))); 84 | }); 85 | 86 | test( 87 | "write invokes manager with expected params when transactionId is specified", 88 | () { 89 | //when 90 | descriptor.write(Uint8List.fromList([1, 2, 3, 4]), transactionId: "456"); 91 | 92 | //then 93 | verify( 94 | managerForDescriptor.writeDescriptorForIdentifier( 95 | descriptor, Uint8List.fromList([1, 2, 3, 4]), "456"), 96 | ); 97 | }); 98 | 99 | test( 100 | "write invokes manager with expected params when transactionId is not specified", 101 | () { 102 | //when 103 | descriptor.write(Uint8List.fromList([1, 2, 3, 4])); 104 | 105 | //then 106 | verify( 107 | managerForDescriptor.writeDescriptorForIdentifier( 108 | descriptor, Uint8List.fromList([1, 2, 3, 4]), argThat(isNotNull)), 109 | ); 110 | }); 111 | 112 | test( 113 | "write invokes manager with unique transactionId when transactionId is not specified", 114 | () { 115 | //when 116 | descriptor.write(Uint8List.fromList([1, 2, 3, 4])); 117 | descriptor.write(Uint8List.fromList([1, 2, 3, 4])); 118 | 119 | //then 120 | var transactionIds = verify( 121 | managerForDescriptor.writeDescriptorForIdentifier(descriptor, 122 | Uint8List.fromList([1, 2, 3, 4]), captureThat(isNotNull))) 123 | .captured; 124 | expect(transactionIds[0], isNot(equals(transactionIds[1]))); 125 | }); 126 | } 127 | -------------------------------------------------------------------------------- /test/json/ble_error_jsons.dart: -------------------------------------------------------------------------------- 1 | String cancellationErrorJson(String transactionId) => """ 2 | { 3 | "errorCode": 2, 4 | "reason": "Operation cancelled", 5 | "transactionId": "$transactionId" 6 | } 7 | """; 8 | -------------------------------------------------------------------------------- /test/mock/mocks.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 2 | import 'package:flutter_ble_lib/src/_managers_for_classes.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | 5 | 6 | class ManagerForCharacteristicMock extends Mock 7 | implements ManagerForCharacteristic {} 8 | 9 | class ManagerForDescriptorMock extends Mock implements ManagerForDescriptor {} 10 | 11 | class ServiceMock extends Mock implements Service {} 12 | 13 | class PeripheralMock extends Mock implements Peripheral {} 14 | 15 | class CharacteristicMock extends Mock implements Characteristic {} 16 | -------------------------------------------------------------------------------- /test/scan_result.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | 7 | void main() { 8 | group("Test manufacturer data deserialization:", () { 9 | void testManufacturerDataDeserialization(Uint8List manufacturerData) { 10 | test("$manufacturerData is deserialized correctly", () { 11 | //given 12 | String serializedScanResult = 13 | _createJsonScanResult(manufacturerData: manufacturerData); 14 | 15 | //when 16 | ScanResult scanResult = 17 | ScanResult.fromJson(jsonDecode(serializedScanResult), null); 18 | 19 | //then 20 | expect(scanResult.advertisementData.manufacturerData, 21 | equals(manufacturerData)); 22 | }); 23 | } 24 | 25 | testManufacturerDataDeserialization(Uint8List.fromList([0, 1, 2])); 26 | testManufacturerDataDeserialization(Uint8List.fromList([255, 255, 255])); 27 | testManufacturerDataDeserialization(null); 28 | }); 29 | 30 | group("Test service data deserialization", () { 31 | void testServiceDataDeserialization(Map serviceData) { 32 | test("$serviceData is deserialized correctly", () { 33 | //given 34 | String serializedScanResult = 35 | _createJsonScanResult(serviceData: serviceData); 36 | 37 | //when 38 | ScanResult scanResult = 39 | ScanResult.fromJson(jsonDecode(serializedScanResult), null); 40 | 41 | //then 42 | expect(scanResult.advertisementData.serviceData, equals(serviceData)); 43 | }); 44 | } 45 | 46 | testServiceDataDeserialization({ 47 | "uuid1": Uint8List.fromList([0, 1, 2]), 48 | "uuid2": Uint8List.fromList([0, 0, 0, 0, 0]), 49 | }); 50 | 51 | testServiceDataDeserialization(null); 52 | testServiceDataDeserialization({}); 53 | }); 54 | } 55 | 56 | String _createJsonScanResult({ 57 | String id = "valid id", 58 | String name = "Valid name", 59 | int rssi = -60, 60 | bool isConnectable = true, 61 | List overflowServiceUuids, 62 | Uint8List manufacturerData, 63 | Map serviceData, 64 | List serviceUuids, 65 | String localName, 66 | int txPowerLevel, 67 | List solicitedServiceUuids, 68 | }) { 69 | String serializedManufacturerData; 70 | if (manufacturerData != null) { 71 | serializedManufacturerData = "\"${base64Encode(manufacturerData)}\""; 72 | } else { 73 | serializedManufacturerData = "null"; 74 | } 75 | 76 | return "{" 77 | "\"id\": \"$id\"," 78 | "\"name\": \"$name\"," 79 | "\"rssi\": $rssi," 80 | "\"isConnectable\": $isConnectable," 81 | "\"overflowServiceUuids\": ${_jsonizeList(overflowServiceUuids)}," 82 | "\"manufacturerData\": $serializedManufacturerData," 83 | "\"serviceData\": ${_jsonizeMap(serviceData)}," 84 | "\"serviceUuids\": ${_jsonizeList(serviceUuids)}," 85 | "\"localName\": \"$localName\"," 86 | "\"txPowerLevel\": $txPowerLevel," 87 | "\"solicitedServiceUuids\": ${_jsonizeList(solicitedServiceUuids)}}"; 88 | } 89 | 90 | String _jsonizeList(List list) { 91 | if (list == null) { 92 | return "null"; 93 | } else { 94 | String result = "["; 95 | for (int i = 0; i < list.length; i++) { 96 | result += list[i]; 97 | if (i < list.length - 1) { 98 | result += ","; 99 | } 100 | } 101 | result += "]"; 102 | return result; 103 | } 104 | } 105 | 106 | String _jsonizeMap(Map map) { 107 | if (map == null) { 108 | return "null"; 109 | } else { 110 | String result = "{"; 111 | List> entries = map.entries.toList(); 112 | for (int i = 0; i < entries.length; i++) { 113 | result += "\"${entries[i].key}\": \"${base64Encode(entries[i].value)}\""; 114 | if (i < entries.length - 1) { 115 | result += ","; 116 | } 117 | } 118 | result += "}"; 119 | return result; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /test/src/util/transcation_id_generator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:flutter_ble_lib/src/util/_transaction_id_generator.dart'; 3 | 4 | void main() { 5 | test("should be able to generate an id", () { 6 | expect(TransactionIdGenerator.getNextId(), isNotNull); 7 | }); 8 | 9 | test("should always return unique values", () { 10 | List generatedIds = []; 11 | for (var i = 0; i < 1000; i++) { 12 | var generatedId = TransactionIdGenerator.getNextId(); 13 | expect(generatedIds, isNot(contains(generatedId))); 14 | generatedIds.add(generatedId); 15 | } 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /test/test_util/characteristic_generator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 4 | import 'package:mockito/annotations.dart'; 5 | import 'package:flutter_ble_lib/src/_managers_for_classes.dart'; 6 | import './characteristic_generator.mocks.dart'; 7 | 8 | export './characteristic_generator.mocks.dart'; 9 | 10 | @GenerateMocks( 11 | [], 12 | customMocks:[ 13 | MockSpec(returnNullOnMissingStub: true), 14 | ]) 15 | class CharacteristicGenerator { 16 | MockManagerForCharacteristic managerForCharacteristic; 17 | 18 | CharacteristicGenerator(this.managerForCharacteristic); 19 | 20 | Map _createRawCharacteristic(int seed) => { 21 | "characteristicUuid": seed.toString(), 22 | "id": seed, 23 | "isReadable": seed % 2 == 0, 24 | "isWritableWithResponse": seed % 2 == 0, 25 | "isWritableWithoutResponse": seed % 2 == 0, 26 | "isNotifiable": seed % 2 == 0, 27 | "isIndicatable": seed % 2 == 0, 28 | "value": base64Encode([seed]) 29 | }; 30 | 31 | CharacteristicWithValue create(int seed, Service service) => 32 | CharacteristicWithValue.fromJson( 33 | _createRawCharacteristic(seed), 34 | service, 35 | managerForCharacteristic, 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /test/test_util/characteristic_generator.mocks.dart: -------------------------------------------------------------------------------- 1 | // Mocks generated by Mockito 5.0.3 from annotations 2 | // in flutter_ble_lib/test/test_util/characteristic_generator.dart. 3 | // Do not manually edit this file. 4 | 5 | import 'dart:async' as _i5; 6 | import 'dart:typed_data' as _i2; 7 | 8 | import 'package:flutter_ble_lib/flutter_ble_lib.dart' as _i3; 9 | import 'package:flutter_ble_lib/src/_internal.dart' as _i6; 10 | import 'package:flutter_ble_lib/src/_managers_for_classes.dart' as _i4; 11 | import 'package:mockito/mockito.dart' as _i1; 12 | 13 | // ignore_for_file: comment_references 14 | // ignore_for_file: unnecessary_parenthesis 15 | 16 | class _FakeUint8List extends _i1.Fake implements _i2.Uint8List {} 17 | 18 | class _FakeDescriptorWithValue extends _i1.Fake 19 | implements _i3.DescriptorWithValue {} 20 | 21 | class _FakeDescriptor extends _i1.Fake implements _i3.Descriptor {} 22 | 23 | /// A class which mocks [ManagerForCharacteristic]. 24 | /// 25 | /// See the documentation for Mockito's code generation for more information. 26 | class MockManagerForCharacteristic extends _i1.Mock 27 | implements _i4.ManagerForCharacteristic { 28 | @override 29 | _i5.Future<_i2.Uint8List> readCharacteristicForIdentifier( 30 | _i3.Peripheral? peripheral, 31 | _i6.InternalCharacteristic? characteristic, 32 | String? transactionId) => 33 | (super.noSuchMethod( 34 | Invocation.method(#readCharacteristicForIdentifier, 35 | [peripheral, characteristic, transactionId]), 36 | returnValue: Future.value(_FakeUint8List())) 37 | as _i5.Future<_i2.Uint8List>); 38 | @override 39 | _i5.Future writeCharacteristicForIdentifier( 40 | _i3.Peripheral? peripheral, 41 | _i6.InternalCharacteristic? characteristic, 42 | _i2.Uint8List? value, 43 | bool? withResponse, 44 | String? transactionId) => 45 | (super.noSuchMethod( 46 | Invocation.method(#writeCharacteristicForIdentifier, 47 | [peripheral, characteristic, value, withResponse, transactionId]), 48 | returnValue: Future.value(null), 49 | returnValueForMissingStub: Future.value()) as _i5.Future); 50 | @override 51 | _i5.Stream<_i2.Uint8List> monitorCharacteristicForIdentifier( 52 | _i3.Peripheral? peripheral, 53 | _i6.InternalCharacteristic? characteristic, 54 | String? transactionId) => 55 | (super.noSuchMethod( 56 | Invocation.method(#monitorCharacteristicForIdentifier, 57 | [peripheral, characteristic, transactionId]), 58 | returnValue: Stream<_i2.Uint8List>.empty()) 59 | as _i5.Stream<_i2.Uint8List>); 60 | @override 61 | _i5.Future> descriptorsForCharacteristic( 62 | _i3.Characteristic? characteristic) => 63 | (super.noSuchMethod( 64 | Invocation.method(#descriptorsForCharacteristic, [characteristic]), 65 | returnValue: 66 | Future.value(<_i3.Descriptor>[])) as _i5 67 | .Future>); 68 | @override 69 | _i5.Future<_i3.DescriptorWithValue> readDescriptorForCharacteristic( 70 | _i3.Characteristic? characteristic, 71 | String? descriptorUuid, 72 | String? transactionId) => 73 | (super.noSuchMethod( 74 | Invocation.method(#readDescriptorForCharacteristic, 75 | [characteristic, descriptorUuid, transactionId]), 76 | returnValue: Future.value(_FakeDescriptorWithValue())) 77 | as _i5.Future<_i3.DescriptorWithValue>); 78 | @override 79 | _i5.Future<_i3.Descriptor> writeDescriptorForCharacteristic( 80 | _i3.Characteristic? characteristic, 81 | String? descriptorUuid, 82 | _i2.Uint8List? value, 83 | String? transactionId) => 84 | (super.noSuchMethod( 85 | Invocation.method(#writeDescriptorForCharacteristic, 86 | [characteristic, descriptorUuid, value, transactionId]), 87 | returnValue: Future.value(_FakeDescriptor())) 88 | as _i5.Future<_i3.Descriptor>); 89 | } 90 | -------------------------------------------------------------------------------- /test/test_util/descriptor_generator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 4 | import 'package:flutter_ble_lib/src/_managers_for_classes.dart'; 5 | 6 | class DescriptorGenerator { 7 | ManagerForDescriptor managerForDescriptor; 8 | 9 | DescriptorGenerator(this.managerForDescriptor); 10 | 11 | Map _createRawDescriptor(int seed) => { 12 | "descriptorId": seed, 13 | "descriptorUuid": seed.toString(), 14 | "value": base64Encode([seed]) 15 | }; 16 | 17 | DescriptorWithValue create(int seed, Characteristic characteristic) => 18 | DescriptorWithValue.fromJson( 19 | _createRawDescriptor(seed), 20 | characteristic, 21 | managerForDescriptor, 22 | ); 23 | } 24 | --------------------------------------------------------------------------------