diff --git a/packages/quick_blue/.metadata b/packages/quick_blue/.metadata
index 8c15ad7..99f5d2e 100644
--- a/packages/quick_blue/.metadata
+++ b/packages/quick_blue/.metadata
@@ -1,10 +1,42 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
-# This file should be version controlled and should not be manually edited.
+# This file should be version controlled.
version:
- revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b
+ revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
channel: stable
project_type: plugin
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ - platform: android
+ create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ - platform: ios
+ create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ - platform: linux
+ create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ - platform: macos
+ create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ - platform: windows
+ create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/packages/quick_blue/android/build.gradle b/packages/quick_blue/android/build.gradle
index 429b831..575a638 100644
--- a/packages/quick_blue/android/build.gradle
+++ b/packages/quick_blue/android/build.gradle
@@ -2,19 +2,19 @@ group 'com.example.quick_blue'
version '1.0-SNAPSHOT'
buildscript {
- ext.kotlin_version = '1.5.30'
+ ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.0'
+ classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
-rootProject.allprojects {
+allprojects {
repositories {
google()
mavenCentral()
@@ -25,7 +25,7 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
- compileSdkVersion 30
+ compileSdkVersion 31
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@@ -38,13 +38,27 @@ android {
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
+ test.java.srcDirs += 'src/test/kotlin'
}
defaultConfig {
minSdkVersion 21
}
-}
-dependencies {
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ dependencies {
+ testImplementation 'org.jetbrains.kotlin:kotlin-test'
+ testImplementation 'org.mockito:mockito-core:5.0.0'
+ }
+
+ testOptions {
+ unitTests.all {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed", "standardOut", "standardError"
+ outputs.upToDateWhen {false}
+ showStandardStreams = true
+ }
+ }
+ }
}
diff --git a/packages/quick_blue/android/gradle/wrapper/gradle-wrapper.jar b/packages/quick_blue/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..41d9927
Binary files /dev/null and b/packages/quick_blue/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/packages/quick_blue/android/gradle/wrapper/gradle-wrapper.properties b/packages/quick_blue/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..41dfb87
--- /dev/null
+++ b/packages/quick_blue/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/packages/quick_blue/android/gradlew b/packages/quick_blue/android/gradlew
new file mode 100644
index 0000000..1b6c787
--- /dev/null
+++ b/packages/quick_blue/android/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/packages/quick_blue/android/gradlew.bat b/packages/quick_blue/android/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/packages/quick_blue/android/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/packages/quick_blue/android/src/main/AndroidManifest.xml b/packages/quick_blue/android/src/main/AndroidManifest.xml
index 05d674f..6dbadaa 100644
--- a/packages/quick_blue/android/src/main/AndroidManifest.xml
+++ b/packages/quick_blue/android/src/main/AndroidManifest.xml
@@ -1,17 +1,13 @@
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.quick_blue">
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt b/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt
index 624c046..b0a1761 100644
--- a/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt
+++ b/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt
@@ -3,14 +3,17 @@ package com.example.quick_blue
import android.annotation.SuppressLint
import android.bluetooth.*
import android.bluetooth.le.ScanCallback
+import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
-import android.content.Context
+import android.bluetooth.le.ScanSettings
import android.content.BroadcastReceiver
+import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.Handler
import android.os.Looper
+import android.os.ParcelUuid
import android.util.Log
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
@@ -52,6 +55,7 @@ class QuickBluePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHand
broadcastReceiver,
IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
)
+
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
@@ -73,107 +77,138 @@ class QuickBluePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHand
mainThreadHandler.post { messageChannel.send(message) }
}
+ fun trace() = Arrays.toString(Throwable().stackTrace)
+
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
- when (call.method) {
- "isBluetoothAvailable" -> {
- result.success(bluetoothManager.adapter.isEnabled)
- }
- "startScan" -> {
- bluetoothManager.adapter.bluetoothLeScanner?.startScan(scanCallback)
- result.success(null)
- }
- "stopScan" -> {
- bluetoothManager.adapter.bluetoothLeScanner?.stopScan(scanCallback)
- result.success(null)
- }
- "connect" -> {
- val deviceId = call.argument("deviceId")!!
- if (knownGatts.find { it.device.address == deviceId } != null) {
- return result.success(null)
+ try {
+ when (call.method) {
+ "isBluetoothAvailable" -> {
+ result.success(bluetoothManager.adapter.isEnabled)
}
- val remoteDevice = bluetoothManager.adapter.getRemoteDevice(deviceId)
- val gatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- remoteDevice.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
- } else {
- remoteDevice.connectGatt(context, false, gattCallback)
+ "startScan" -> {
+ val serviceUUIDs = call.argument>("serviceUUIDs")
+ if (serviceUUIDs != null && serviceUUIDs.size > 0) {
+ val filters: ArrayList = ArrayList()
+ for (serviceUUID in serviceUUIDs) {
+ val filter = ScanFilter.Builder()
+ .setServiceUuid(parseToParcelUuid(serviceUUID))
+ .build()
+ filters.add(filter)
+ }
+
+ val settings = ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+ .build()
+
+ bluetoothManager.adapter.bluetoothLeScanner?.startScan(filters, settings, scanCallback)
+ } else {
+ bluetoothManager.adapter.bluetoothLeScanner?.startScan(scanCallback)
+ }
+ result.success(null)
}
- knownGatts.add(gatt)
- result.success(null)
- // TODO connecting
- }
- "disconnect" -> {
- val deviceId = call.argument("deviceId")!!
- val gatt = knownGatts.find { it.device.address == deviceId }
- ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
- cleanConnection(gatt)
- result.success(null)
- //FIXME If `disconnect` is called before BluetoothGatt.STATE_CONNECTED
- // there will be no `disconnected` message any more
- }
- "discoverServices" -> {
- val deviceId = call.argument("deviceId")!!
- val gatt = knownGatts.find { it.device.address == deviceId }
- ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
- gatt.discoverServices()
- result.success(null)
- }
- "setNotifiable" -> {
- val deviceId = call.argument("deviceId")!!
- val service = call.argument("service")!!
- val characteristic = call.argument("characteristic")!!
- val bleInputProperty = call.argument("bleInputProperty")!!
- val gatt = knownGatts.find { it.device.address == deviceId }
- ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
- val c = gatt.getCharacteristic(service, characteristic)
- ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", null)
- gatt.setNotifiable(c, bleInputProperty)
- result.success(null)
- }
- "readValue" -> {
- val deviceId = call.argument("deviceId")!!
- val service = call.argument("service")!!
- val characteristic = call.argument("characteristic")!!
- val gatt = knownGatts.find { it.device.address == deviceId }
- ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
- val c = gatt.getCharacteristic(service, characteristic)
- ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", null)
- if (gatt.readCharacteristic(c))
+ "stopScan" -> {
+ bluetoothManager.adapter.bluetoothLeScanner?.stopScan(scanCallback)
result.success(null)
- else
- result.error("Characteristic unavailable", null, null)
- }
- "writeValue" -> {
- val deviceId = call.argument("deviceId")!!
- val service = call.argument("service")!!
- val characteristic = call.argument("characteristic")!!
- val value = call.argument("value")!!
- val gatt = knownGatts.find { it.device.address == deviceId }
- ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
- val c = gatt.getCharacteristic(service, characteristic)
- ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", null)
- c.value = value
- if (gatt.writeCharacteristic(c))
+ }
+ "connect" -> {
+ val deviceId = call.argument("deviceId")!!
+ if (knownGatts.find { it.device.address == deviceId } != null) {
+ return result.success(null)
+ }
+ val remoteDevice = bluetoothManager.adapter.getRemoteDevice(deviceId)
+ val gatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ remoteDevice.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
+ } else {
+ remoteDevice.connectGatt(context, false, gattCallback)
+ }
+ knownGatts.add(gatt)
result.success(null)
- else
- result.error("Characteristic unavailable", null, null)
- }
- "requestMtu" -> {
- val deviceId = call.argument("deviceId")!!
- val expectedMtu = call.argument("expectedMtu")!!
- val gatt = knownGatts.find { it.device.address == deviceId }
- ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
- gatt.requestMtu(expectedMtu)
- result.success(null)
- }
- else -> {
- result.notImplemented()
+ // TODO connecting
+ }
+ "disconnect" -> {
+ val deviceId = call.argument("deviceId")!!
+ val gatt = knownGatts.find { it.device.address == deviceId }
+ ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace())
+ cleanConnection(gatt)
+ result.success(null)
+ //FIXME If `disconnect` is called before BluetoothGatt.STATE_CONNECTED
+ // there will be no `disconnected` message any more
+ }
+ "discoverServices" -> {
+ val deviceId = call.argument("deviceId")!!
+ val gatt = knownGatts.find { it.device.address == deviceId }
+ ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace())
+ gatt.discoverServices()
+ result.success(null)
+ }
+ "setNotifiable" -> {
+ val deviceId = call.argument("deviceId")!!
+ val service = call.argument("service")!!
+ val characteristic = call.argument("characteristic")!!
+ val bleInputProperty = call.argument("bleInputProperty")!!
+ val gatt = knownGatts.find { it.device.address == deviceId }
+ ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace())
+ val s = gatt.getService(UUID.fromString(service))
+ ?: return result.error("IllegalArgument", "Unknown service: $service", trace())
+ val c = s.getCharacteristic(UUID.fromString(characteristic))
+ ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", trace())
+ gatt.setNotifiable(c, bleInputProperty)
+ result.success(null)
+ }
+ "readValue" -> {
+ val deviceId = call.argument("deviceId")!!
+ val service = call.argument("service")!!
+ val characteristic = call.argument("characteristic")!!
+ val gatt = knownGatts.find { it.device.address == deviceId }
+ ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace())
+ val c = gatt.getCharacteristic(service, characteristic)
+ ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", trace())
+ if (gatt.readCharacteristic(c))
+ result.success(null)
+ else
+ result.error("Characteristic unavailable", null, trace())
+ }
+ "writeValue" -> {
+ val deviceId = call.argument("deviceId")!!
+ val service = call.argument("service")!!
+ val characteristic = call.argument("characteristic")!!
+ val value = call.argument("value")!!
+ val gatt = knownGatts.find { it.device.address == deviceId }
+ ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace())
+ val c = gatt.getCharacteristic(service, characteristic)
+ ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", trace())
+ c.value = value
+ if (gatt.writeCharacteristic(c)) {
+ result.success(null)
+ } else {
+ result.error("Characteristic unavailable", null, trace())
+ }
+ }
+ "requestMtu" -> {
+ val deviceId = call.argument("deviceId")!!
+ val expectedMtu = call.argument("expectedMtu")!!
+ val gatt = knownGatts.find { it.device.address == deviceId }
+ ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace())
+ val success = gatt.requestMtu(expectedMtu)
+ if (success)
+ result.success(null)
+ else
+ result.error("Unable to set MTU", null, trace())
+ }
+ else -> {
+ result.notImplemented()
+ }
}
+ } catch (e: Throwable) {
+ e.printStackTrace()
+ result.error("Error", "Error", trace())
}
}
private fun cleanConnection(gatt: BluetoothGatt) {
- knownGatts.remove(gatt)
+ gatt.close()
gatt.disconnect()
+ knownGatts.remove(gatt)
}
enum class AvailabilityState(val value: Int) {
@@ -206,11 +241,10 @@ class QuickBluePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHand
private val scanCallback = object : ScanCallback() {
override fun onScanFailed(errorCode: Int) {
- Log.v(TAG, "onScanFailed: $errorCode")
+ //Log.v(TAG, "onScanFailed: $errorCode")
}
override fun onScanResult(callbackType: Int, result: ScanResult) {
- Log.v(TAG, "onScanResult: $callbackType + $result")
scanResultSink?.success(mapOf(
"name" to (result.device.name ?: ""),
"deviceId" to result.device.address,
@@ -220,7 +254,7 @@ class QuickBluePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHand
}
override fun onBatchScanResults(results: MutableList?) {
- Log.v(TAG, "onBatchScanResults: $results")
+ //Log.v(TAG, "onBatchScanResults: $results")
}
}
@@ -248,7 +282,7 @@ class QuickBluePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHand
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
- Log.v(TAG, "onConnectionStateChange: device(${gatt.device.address}) status($status), newState($newState)")
+ //Log.v(TAG, "onConnectionStateChange: device(${gatt.device.address}) status($status), newState($newState)")
if (newState == BluetoothGatt.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
sendMessage(messageConnector, mapOf(
"deviceId" to gatt.device.address,
@@ -264,15 +298,15 @@ class QuickBluePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHand
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
- Log.v(TAG, "onServicesDiscovered ${gatt.device.address} $status")
+ //Log.v(TAG, "onServicesDiscovered ${gatt.device.address} $status")
if (status != BluetoothGatt.GATT_SUCCESS) return
gatt.services?.forEach { service ->
- Log.v(TAG, "Service " + service.uuid)
+ //Log.v(TAG, "Service " + service.uuid)
service.characteristics.forEach { characteristic ->
- Log.v(TAG, " Characteristic ${characteristic.uuid}")
+ //Log.v(TAG, " Characteristic ${characteristic.uuid}")
characteristic.descriptors.forEach {
- Log.v(TAG, " Descriptor ${it.uuid}")
+ //Log.v(TAG, " Descriptor ${it.uuid}")
}
}
@@ -290,11 +324,15 @@ class QuickBluePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHand
sendMessage(messageConnector, mapOf(
"mtuConfig" to mtu
))
+ } else {
+ sendMessage(messageConnector, mapOf(
+ "mtuConfig" to -1
+ ))
}
}
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
- Log.v(TAG, "onCharacteristicRead ${characteristic.uuid}, ${characteristic.value.contentToString()}")
+ //Log.v(TAG, "onCharacteristicRead ${characteristic.uuid}, ${characteristic.value.contentToString()}")
sendMessage(messageConnector, mapOf(
"deviceId" to gatt.device.address,
"characteristicValue" to mapOf(
@@ -305,11 +343,11 @@ class QuickBluePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHand
}
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic, status: Int) {
- Log.v(TAG, "onCharacteristicWrite ${characteristic.uuid}, ${characteristic.value.contentToString()} $status")
+ //Log.v(TAG, "onCharacteristicWrite ${characteristic.uuid}, ${characteristic.value.contentToString()} $status")
}
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
- Log.v(TAG, "onCharacteristicChanged ${characteristic.uuid}, ${characteristic.value.contentToString()}")
+ //Log.v(TAG, "onCharacteristicChanged ${characteristic.uuid}, ${characteristic.value.contentToString()}")
sendMessage(messageConnector, mapOf(
"deviceId" to gatt.device.address,
"characteristicValue" to mapOf(
@@ -333,7 +371,7 @@ fun Short.toByteArray(byteOrder: ByteOrder = ByteOrder.LITTLE_ENDIAN): ByteArray
ByteBuffer.allocate(2 /*Short.SIZE_BYTES*/).order(byteOrder).putShort(this).array()
fun BluetoothGatt.getCharacteristic(service: String, characteristic: String): BluetoothGattCharacteristic? =
- getService(UUID.fromString(service)).getCharacteristic(UUID.fromString(characteristic))
+ getService(UUID.fromString(service))?.getCharacteristic(UUID.fromString(characteristic))
private val DESC__CLIENT_CHAR_CONFIGURATION = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
@@ -344,6 +382,23 @@ fun BluetoothGatt.setNotifiable(gattCharacteristic: BluetoothGattCharacteristic,
"indication" -> BluetoothGattDescriptor.ENABLE_INDICATION_VALUE to true
else -> BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE to false
}
- descriptor.value = value
- setCharacteristicNotification(descriptor.characteristic, enable) && writeDescriptor(descriptor)
+ if (setCharacteristicNotification(gattCharacteristic, enable) && descriptor != null) {
+ descriptor.value = value
+ writeDescriptor(descriptor)
+ }
+}
+const val baseBluetoothUuidPostfix = "0000-1000-8000-00805F9B34FB"
+
+fun parseToParcelUuid(uuid: String): ParcelUuid {
+ return when (uuid.length) {
+ 4 -> {
+ ParcelUuid(UUID.fromString("0000$uuid-$baseBluetoothUuidPostfix"))
+ }
+ 8 -> {
+ ParcelUuid(UUID.fromString("$uuid-$baseBluetoothUuidPostfix"))
+ }
+ else -> {
+ ParcelUuid.fromString(uuid)
+ }
+ }
}
\ No newline at end of file
diff --git a/packages/quick_blue/darwin/QuickBlueDarwin.swift b/packages/quick_blue/darwin/QuickBlueDarwin.swift
index 3ff685b..2fb9782 100644
--- a/packages/quick_blue/darwin/QuickBlueDarwin.swift
+++ b/packages/quick_blue/darwin/QuickBlueDarwin.swift
@@ -13,269 +13,280 @@ let GATT_HEADER_LENGTH = 3
let GSS_SUFFIX = "0000-1000-8000-00805f9b34fb"
extension CBUUID {
- public var uuidStr: String {
- get {
- uuidString.lowercased()
+ public var uuidStr: String {
+ get {
+ uuidString.lowercased()
+ }
}
- }
}
extension CBPeripheral {
- // FIXME https://forums.developer.apple.com/thread/84375
- public var uuid: UUID {
- get {
- value(forKey: "identifier") as! NSUUID as UUID
+ // FIXME https://forums.developer.apple.com/thread/84375
+ public var uuid: UUID {
+ get {
+ value(forKey: "identifier") as! NSUUID as UUID
+ }
}
- }
- public func getCharacteristic(_ characteristic: String, of service: String) -> CBCharacteristic? {
- let s = self.services?.first {
- $0.uuid.uuidStr == service || "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)" == service
+ public func getCharacteristic(_ characteristic: String, of service: String) -> CBCharacteristic? {
+ let s = self.services?.first {
+ $0.uuid.uuidStr == service || "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)" == service
+ }
+ let c = s?.characteristics?.first {
+ $0.uuid.uuidStr == characteristic || "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)" == characteristic
+ }
+ return c
}
- let c = s?.characteristics?.first {
- $0.uuid.uuidStr == characteristic || "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)" == characteristic
- }
- return c
- }
- public func setNotifiable(_ bleInputProperty: String, for characteristic: String, of service: String) {
- guard let characteristic = getCharacteristic(characteristic, of: service) else{
- return
+ public func setNotifiable(_ bleInputProperty: String, for characteristic: String, of service: String) {
+ guard let characteristic = getCharacteristic(characteristic, of: service) else{
+ return
+ }
+ setNotifyValue(bleInputProperty != "disabled", for: characteristic)
}
- setNotifyValue(bleInputProperty != "disabled", for: characteristic)
- }
}
public class QuickBlueDarwin: NSObject, FlutterPlugin {
- public static func register(with registrar: FlutterPluginRegistrar) {
- #if os(iOS)
- let messenger = registrar.messenger()
- #elseif os(OSX)
- let messenger = registrar.messenger
- #endif
- let method = FlutterMethodChannel(name: "quick_blue/method", binaryMessenger: messenger)
- let eventAvailabilityChange = FlutterEventChannel(name: "quick_blue/event.availabilityChange", binaryMessenger: messenger)
- let eventScanResult = FlutterEventChannel(name: "quick_blue/event.scanResult", binaryMessenger: messenger)
- let messageConnector = FlutterBasicMessageChannel(name: "quick_blue/message.connector", binaryMessenger: messenger)
+ public static func register(with registrar: FlutterPluginRegistrar) {
+#if os(iOS)
+ let messenger = registrar.messenger()
+#elseif os(OSX)
+ let messenger = registrar.messenger
+#endif
+ let method = FlutterMethodChannel(name: "quick_blue/method", binaryMessenger: messenger)
+ let eventAvailabilityChange = FlutterEventChannel(name: "quick_blue/event.availabilityChange", binaryMessenger: messenger)
+ let eventScanResult = FlutterEventChannel(name: "quick_blue/event.scanResult", binaryMessenger: messenger)
+ let messageConnector = FlutterBasicMessageChannel(name: "quick_blue/message.connector", binaryMessenger: messenger)
- let instance = QuickBlueDarwin()
- registrar.addMethodCallDelegate(instance, channel: method)
- eventAvailabilityChange.setStreamHandler(instance)
- eventScanResult.setStreamHandler(instance)
- instance.messageConnector = messageConnector
- }
+ let instance = QuickBlueDarwin()
+ registrar.addMethodCallDelegate(instance, channel: method)
+ eventAvailabilityChange.setStreamHandler(instance)
+ eventScanResult.setStreamHandler(instance)
+ instance.messageConnector = messageConnector
+ }
- private lazy var manager: CBCentralManager = { CBCentralManager(delegate: self, queue: nil) }()
- private var discoveredPeripherals = Dictionary()
+ private lazy var manager: CBCentralManager = { CBCentralManager(delegate: self, queue: nil) }()
+ private var discoveredPeripherals = Dictionary()
- private var availabilityChangeSink: FlutterEventSink?
- private var scanResultSink: FlutterEventSink?
- private var messageConnector: FlutterBasicMessageChannel!
+ private var availabilityChangeSink: FlutterEventSink?
+ private var scanResultSink: FlutterEventSink?
+ private var messageConnector: FlutterBasicMessageChannel!
- public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
- switch call.method {
- case "isBluetoothAvailable":
- result(manager.state == .poweredOn)
- case "startScan":
- manager.scanForPeripherals(withServices: nil)
- result(nil)
- case "stopScan":
- manager.stopScan()
- result(nil)
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ switch call.method {
+ case "isBluetoothAvailable":
+ result(manager.state == .poweredOn)
+ case "startScan":
+ let arguments = call.arguments as! Dictionary
+ let serviceUUIDs = arguments["serviceUUIDs"] as? [String]
+ if serviceUUIDs != nil && serviceUUIDs!.count > 0 {
+ let serviceCBUUID = serviceUUIDs!.map({ uuid in
+ CBUUID(string: uuid)
+ })
+ manager.scanForPeripherals(withServices: serviceCBUUID)
+ } else {
+ manager.scanForPeripherals(withServices: nil)
+ }
+ result(nil)
+ case "stopScan":
+ manager.stopScan()
+ result(nil)
case "connect":
- let arguments = call.arguments as! Dictionary
- let deviceId = arguments["deviceId"] as! String
- guard let peripheral = discoveredPeripherals[deviceId] else {
- result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
- return
- }
- peripheral.delegate = self
- manager.connect(peripheral)
- result(nil)
- case "disconnect":
- let arguments = call.arguments as! Dictionary
- let deviceId = arguments["deviceId"] as! String
- guard let peripheral = discoveredPeripherals[deviceId] else {
- result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
- return
- }
- if (peripheral.state != .disconnected) {
- manager.cancelPeripheralConnection(peripheral)
- }
- result(nil)
- case "discoverServices":
- let arguments = call.arguments as! Dictionary
- let deviceId = arguments["deviceId"] as! String
- guard let peripheral = discoveredPeripherals[deviceId] else {
- result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
- return
- }
- peripheral.discoverServices(nil)
- result(nil)
- case "setNotifiable":
- let arguments = call.arguments as! Dictionary
- let deviceId = arguments["deviceId"] as! String
- let service = arguments["service"] as! String
- let characteristic = arguments["characteristic"] as! String
- let bleInputProperty = arguments["bleInputProperty"] as! String
- guard let peripheral = discoveredPeripherals[deviceId] else {
- result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
- return
- }
- guard let c = peripheral.getCharacteristic(characteristic, of: service) else {
- result(FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil))
- return
- }
- peripheral.setNotifyValue(bleInputProperty != "disabled", for: c)
- result(nil)
- case "readValue":
- let arguments = call.arguments as! Dictionary
- let deviceId = arguments["deviceId"] as! String
- let service = arguments["service"] as! String
- let characteristic = arguments["characteristic"] as! String
- guard let peripheral = discoveredPeripherals[deviceId] else {
- result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
- return
- }
- guard let c = peripheral.getCharacteristic(characteristic, of: service) else {
- result(FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil))
- return
- }
- peripheral.readValue(for: c)
- result(nil)
- case "writeValue":
- let arguments = call.arguments as! Dictionary
- let deviceId = arguments["deviceId"] as! String
- let service = arguments["service"] as! String
- let characteristic = arguments["characteristic"] as! String
- let value = arguments["value"] as! FlutterStandardTypedData
- let bleOutputProperty = arguments["bleOutputProperty"] as! String
- guard let peripheral = discoveredPeripherals[deviceId] else {
- result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
- return
- }
- let type = bleOutputProperty == "withoutResponse" ? CBCharacteristicWriteType.withoutResponse : CBCharacteristicWriteType.withResponse
- guard let c = peripheral.getCharacteristic(characteristic, of: service) else {
- result(FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil))
- return
- }
- peripheral.writeValue(value.data, for: c, type: type)
- result(nil)
- case "requestMtu":
- let arguments = call.arguments as! Dictionary
- let deviceId = arguments["deviceId"] as! String
- guard let peripheral = discoveredPeripherals[deviceId] else {
- result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
- return
- }
- result(nil)
- let mtu = peripheral.maximumWriteValueLength(for: .withoutResponse)
- print("peripheral.maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse \(mtu)")
- messageConnector.sendMessage(["mtuConfig": mtu + GATT_HEADER_LENGTH])
- default:
- result(FlutterMethodNotImplemented)
+ let arguments = call.arguments as! Dictionary
+ let deviceId = arguments["deviceId"] as! String
+ guard let peripheral = discoveredPeripherals[deviceId] else {
+ result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
+ return
+ }
+ peripheral.delegate = self
+ manager.connect(peripheral)
+ result(nil)
+ case "disconnect":
+ let arguments = call.arguments as! Dictionary
+ let deviceId = arguments["deviceId"] as! String
+ guard let peripheral = discoveredPeripherals[deviceId] else {
+ result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
+ return
+ }
+ if (peripheral.state != .disconnected) {
+ manager.cancelPeripheralConnection(peripheral)
+ }
+ result(nil)
+ case "discoverServices":
+ let arguments = call.arguments as! Dictionary
+ let deviceId = arguments["deviceId"] as! String
+ guard let peripheral = discoveredPeripherals[deviceId] else {
+ result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
+ return
+ }
+ peripheral.discoverServices(nil)
+ result(nil)
+ case "setNotifiable":
+ let arguments = call.arguments as! Dictionary
+ let deviceId = arguments["deviceId"] as! String
+ let service = arguments["service"] as! String
+ let characteristic = arguments["characteristic"] as! String
+ let bleInputProperty = arguments["bleInputProperty"] as! String
+ guard let peripheral = discoveredPeripherals[deviceId] else {
+ result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
+ return
+ }
+ guard let c = peripheral.getCharacteristic(characteristic, of: service) else {
+ result(FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil))
+ return
+ }
+ peripheral.setNotifyValue(bleInputProperty != "disabled", for: c)
+ result(nil)
+ case "readValue":
+ let arguments = call.arguments as! Dictionary
+ let deviceId = arguments["deviceId"] as! String
+ let service = arguments["service"] as! String
+ let characteristic = arguments["characteristic"] as! String
+ guard let peripheral = discoveredPeripherals[deviceId] else {
+ result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
+ return
+ }
+ guard let c = peripheral.getCharacteristic(characteristic, of: service) else {
+ result(FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil))
+ return
+ }
+ peripheral.readValue(for: c)
+ result(nil)
+ case "writeValue":
+ let arguments = call.arguments as! Dictionary
+ let deviceId = arguments["deviceId"] as! String
+ let service = arguments["service"] as! String
+ let characteristic = arguments["characteristic"] as! String
+ let value = arguments["value"] as! FlutterStandardTypedData
+ let bleOutputProperty = arguments["bleOutputProperty"] as! String
+ guard let peripheral = discoveredPeripherals[deviceId] else {
+ result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
+ return
+ }
+ let type = bleOutputProperty == "withoutResponse" ? CBCharacteristicWriteType.withoutResponse : CBCharacteristicWriteType.withResponse
+ guard let c = peripheral.getCharacteristic(characteristic, of: service) else {
+ result(FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil))
+ return
+ }
+ peripheral.writeValue(value.data, for: c, type: type)
+ result(nil)
+ case "requestMtu":
+ let arguments = call.arguments as! Dictionary
+ let deviceId = arguments["deviceId"] as! String
+ guard let peripheral = discoveredPeripherals[deviceId] else {
+ result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
+ return
+ }
+ result(nil)
+ let mtu = peripheral.maximumWriteValueLength(for: .withoutResponse)
+ // print("peripheral.maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse \(mtu)")
+ messageConnector.sendMessage(["mtuConfig": mtu + GATT_HEADER_LENGTH])
+ default:
+ result(FlutterMethodNotImplemented)
+ }
}
- }
}
extension QuickBlueDarwin: CBCentralManagerDelegate {
- public func centralManagerDidUpdateState(_ central: CBCentralManager) {
- availabilityChangeSink?(central.state.rawValue)
- }
+ public func centralManagerDidUpdateState(_ central: CBCentralManager) {
+ availabilityChangeSink?(central.state.rawValue)
+ }
- public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
- print("centralManager:didDiscoverPeripheral \(peripheral.name ?? "nil") \(peripheral.uuid.uuidString)")
- discoveredPeripherals[peripheral.uuid.uuidString] = peripheral
+ public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
+ // print("centralManager:didDiscoverPeripheral \(peripheral.name ?? "nil") \(peripheral.uuid.uuidString)")
+ discoveredPeripherals[peripheral.uuid.uuidString] = peripheral
- let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
- scanResultSink?([
- "name": peripheral.name ?? "",
- "deviceId": peripheral.uuid.uuidString,
- "manufacturerData": FlutterStandardTypedData(bytes: manufacturerData ?? Data()),
- "rssi": RSSI,
- ])
- }
+ let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
+ scanResultSink?([
+ "name": peripheral.name ?? "",
+ "deviceId": peripheral.uuid.uuidString,
+ "manufacturerData": FlutterStandardTypedData(bytes: manufacturerData ?? Data()),
+ "rssi": RSSI,
+ ])
+ }
- public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
- print("centralManager:didConnect \(peripheral.uuid.uuidString)")
- messageConnector.sendMessage([
- "deviceId": peripheral.uuid.uuidString,
- "ConnectionState": "connected",
- ])
- }
-
- public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
- print("centralManager:didDisconnectPeripheral: \(peripheral.uuid.uuidString) error: \(String(describing: error))")
- messageConnector.sendMessage([
- "deviceId": peripheral.uuid.uuidString,
- "ConnectionState": "disconnected",
- ])
- }
+ public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
+ // print("centralManager:didConnect \(peripheral.uuid.uuidString)")
+ messageConnector.sendMessage([
+ "deviceId": peripheral.uuid.uuidString,
+ "ConnectionState": "connected",
+ ])
+ }
+
+ public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
+ // print("centralManager:didDisconnectPeripheral: \(peripheral.uuid.uuidString) error: \(String(describing: error))")
+ messageConnector.sendMessage([
+ "deviceId": peripheral.uuid.uuidString,
+ "ConnectionState": "disconnected",
+ ])
+ }
}
extension QuickBlueDarwin: FlutterStreamHandler {
- open func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
- guard let args = arguments as? Dictionary, let name = args["name"] as? String else {
- return nil
+ open func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
+ guard let args = arguments as? Dictionary, let name = args["name"] as? String else {
+ return nil
+ }
+ print("QuickBlueDarwin onListenWithArguments: \(name)")
+ if name == "availabilityChange" {
+ availabilityChangeSink = events
+ availabilityChangeSink?(manager.state.rawValue) // Initializes CBCentralManager and returns the current state when hot restarting
+ } else if name == "scanResult" {
+ scanResultSink = events
+ }
+ return nil
}
- print("QuickBlueDarwin onListenWithArguments: \(name)")
- if name == "availabilityChange" {
- availabilityChangeSink = events
- availabilityChangeSink?(manager.state.rawValue) // Initializes CBCentralManager and returns the current state when hot restarting
- } else if name == "scanResult" {
- scanResultSink = events
- }
- return nil
- }
- open func onCancel(withArguments arguments: Any?) -> FlutterError? {
- guard let args = arguments as? Dictionary, let name = args["name"] as? String else {
- return nil
- }
- print("QuickBlueDarwin onCancelWithArguments: \(name)")
- if name == "availabilityChange" {
- availabilityChangeSink = nil
- } else if name == "scanResult" {
- scanResultSink = nil
+ open func onCancel(withArguments arguments: Any?) -> FlutterError? {
+ guard let args = arguments as? Dictionary, let name = args["name"] as? String else {
+ return nil
+ }
+ print("QuickBlueDarwin onCancelWithArguments: \(name)")
+ if name == "availabilityChange" {
+ availabilityChangeSink = nil
+ } else if name == "scanResult" {
+ scanResultSink = nil
+ }
+ return nil
}
- return nil
- }
}
extension QuickBlueDarwin: CBPeripheralDelegate {
- public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
- print("peripheral: \(peripheral.uuid.uuidString) didDiscoverServices error: \(String(describing: error))")
- for service in peripheral.services! {
- peripheral.discoverCharacteristics(nil, for: service)
+ public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
+ // print("peripheral: \(peripheral.uuid.uuidString) didDiscoverServices error: \(String(describing: error))")
+ for service in peripheral.services! {
+ // if service.uuid == CBUUID(string: "FF00") {
+ peripheral.discoverCharacteristics(nil, for: service)
+ // }
+ }
}
- }
-
- public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
- for characteristic in service.characteristics! {
- print("peripheral:didDiscoverCharacteristicsForService (\(service.uuid.uuidStr), \(characteristic.uuid.uuidStr)")
+
+ public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
+ // for characteristic in service.characteristics! {
+ // print("peripheral:didDiscoverCharacteristicsForService (\(service.uuid.uuidStr), \(characteristic.uuid.uuidStr)")
+ // }
+ self.messageConnector.sendMessage([
+ "deviceId": peripheral.uuid.uuidString,
+ "ServiceState": "discovered",
+ "service": service.uuid.uuidStr,
+ "characteristics": service.characteristics!.map { $0.uuid.uuidStr }
+ ])
}
- self.messageConnector.sendMessage([
- "deviceId": peripheral.uuid.uuidString,
- "ServiceState": "discovered",
- "service": service.uuid.uuidStr,
- "characteristics": service.characteristics!.map { $0.uuid.uuidStr }
- ])
- }
- public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
- let data = characteristic.value as NSData?
- print("peripheral:didWriteValueForCharacteristic \(characteristic.uuid.uuidStr) \(String(describing: data)) error: \(String(describing: error))")
- }
+ public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
+ let data = characteristic.value as NSData?
+ // print("peripheral:didWriteValueForCharacteristic \(characteristic.uuid.uuidStr) \(String(describing: data)) error: \(String(describing: error))")
+ }
- public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
- let data = characteristic.value as NSData?
- print("peripheral:didUpdateValueForCharacteristic \(characteristic.uuid) \(String(describing: data)) error: \(String(describing: error))")
- self.messageConnector.sendMessage([
- "deviceId": peripheral.uuid.uuidString,
- "characteristicValue": [
- "characteristic": characteristic.uuid.uuidStr,
- "value": FlutterStandardTypedData(bytes: characteristic.value!)
- ]
- ])
- }
+ public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
+ let data = characteristic.value as NSData?
+ // print("peripheral:didUpdateValueForCharacteristic \(characteristic.uuid) \(String(describing: data)) error: \(String(describing: error))")
+ self.messageConnector.sendMessage([
+ "deviceId": peripheral.uuid.uuidString,
+ "characteristicValue": [
+ "characteristic": characteristic.uuid.uuidStr,
+ "value": FlutterStandardTypedData(bytes: characteristic.value!)
+ ]
+ ])
+ }
}
diff --git a/packages/quick_blue/example/.metadata b/packages/quick_blue/example/.metadata
index fd70cab..732ba6d 100644
--- a/packages/quick_blue/example/.metadata
+++ b/packages/quick_blue/example/.metadata
@@ -1,10 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
-# This file should be version controlled and should not be manually edited.
+# This file should be version controlled.
version:
- revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b
+ revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
channel: stable
project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ - platform: android
+ create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+ base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/packages/quick_blue/example/android/build.gradle b/packages/quick_blue/example/android/build.gradle
index 0cb14a5..f7eb7f6 100644
--- a/packages/quick_blue/example/android/build.gradle
+++ b/packages/quick_blue/example/android/build.gradle
@@ -1,12 +1,12 @@
buildscript {
- ext.kotlin_version = '1.5.30'
+ ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.0'
+ classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}
-task clean(type: Delete) {
+tasks.register("clean", Delete) {
delete rootProject.buildDir
}
diff --git a/packages/quick_blue/example/lib/main.dart b/packages/quick_blue/example/lib/main.dart
index d4e5708..13ea21b 100644
--- a/packages/quick_blue/example/lib/main.dart
+++ b/packages/quick_blue/example/lib/main.dart
@@ -87,7 +87,7 @@ class _MyAppState extends State {
ElevatedButton(
child: const Text('startScan'),
onPressed: () {
- QuickBlue.startScan();
+ QuickBlue.startScan(serviceUUIDs: ['FF02']);
},
),
ElevatedButton(
diff --git a/packages/quick_blue/example/linux/flutter/generated_plugins.cmake b/packages/quick_blue/example/linux/flutter/generated_plugins.cmake
index 51436ae..2e1de87 100644
--- a/packages/quick_blue/example/linux/flutter/generated_plugins.cmake
+++ b/packages/quick_blue/example/linux/flutter/generated_plugins.cmake
@@ -5,6 +5,9 @@
list(APPEND FLUTTER_PLUGIN_LIST
)
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
@@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/packages/quick_blue/example/windows/flutter/generated_plugins.cmake b/packages/quick_blue/example/windows/flutter/generated_plugins.cmake
index 3994668..55a705b 100644
--- a/packages/quick_blue/example/windows/flutter/generated_plugins.cmake
+++ b/packages/quick_blue/example/windows/flutter/generated_plugins.cmake
@@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
quick_blue
)
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
@@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/packages/quick_blue/lib/quick_blue.dart b/packages/quick_blue/lib/quick_blue.dart
index 99a7a29..8ab461d 100644
--- a/packages/quick_blue/lib/quick_blue.dart
+++ b/packages/quick_blue/lib/quick_blue.dart
@@ -16,52 +16,65 @@ class QuickBlue {
static void setInstance(QuickBluePlatform instance) => _platform = instance;
- static void setLogger(QuickLogger logger) =>
- _platform.setLogger(logger);
+ static void setLogger(QuickLogger logger) => _platform.setLogger(logger);
static Future isBluetoothAvailable() =>
_platform.isBluetoothAvailable();
- static Stream get availabilityChangeStream => _platform.availabilityChangeStream.map(AvailabilityState.parse);
+ static Stream get availabilityChangeStream =>
+ _platform.availabilityChangeStream.map(AvailabilityState.parse);
- static Future startScan() => _platform.startScan();
+ static Future startScan({List? serviceUUIDs}) =>
+ _platform.startScan(serviceUUIDs);
- static void stopScan() => _platform.stopScan();
+ static Future stopScan() => _platform.stopScan();
static Stream get scanResultStream {
return _platform.scanResultStream
- .map((item) => BlueScanResult.fromMap(item));
+ .map((item) => BlueScanResult.fromMap(item));
}
- static void connect(String deviceId) => _platform.connect(deviceId);
+ static Future connect(String deviceId) => _platform.connect(deviceId);
- static void disconnect(String deviceId) => _platform.disconnect(deviceId);
+ static Future disconnect(String deviceId) =>
+ _platform.disconnect(deviceId);
static void setConnectionHandler(OnConnectionChanged? onConnectionChanged) {
_platform.onConnectionChanged = onConnectionChanged;
}
- static void discoverServices(String deviceId) => _platform.discoverServices(deviceId);
+ static Future discoverServices(String deviceId) =>
+ _platform.discoverServices(deviceId);
static void setServiceHandler(OnServiceDiscovered? onServiceDiscovered) {
_platform.onServiceDiscovered = onServiceDiscovered;
}
- static Future setNotifiable(String deviceId, String service, String characteristic, BleInputProperty bleInputProperty) {
- return _platform.setNotifiable(deviceId, service, characteristic, bleInputProperty);
+ static Future setNotifiable(String deviceId, String service,
+ String characteristic, BleInputProperty bleInputProperty) {
+ return _platform.setNotifiable(
+ deviceId, service, characteristic, bleInputProperty);
}
static void setValueHandler(OnValueChanged? onValueChanged) {
_platform.onValueChanged = onValueChanged;
}
- static Future readValue(String deviceId, String service, String characteristic) {
+ static Future readValue(
+ String deviceId, String service, String characteristic) {
return _platform.readValue(deviceId, service, characteristic);
}
- static Future writeValue(String deviceId, String service, String characteristic, Uint8List value, BleOutputProperty bleOutputProperty) {
- return _platform.writeValue(deviceId, service, characteristic, value, bleOutputProperty);
+ static Future writeValue(
+ String deviceId,
+ String service,
+ String characteristic,
+ Uint8List value,
+ BleOutputProperty bleOutputProperty) {
+ return _platform.writeValue(
+ deviceId, service, characteristic, value, bleOutputProperty);
}
- static Future requestMtu(String deviceId, int expectedMtu) => _platform.requestMtu(deviceId, expectedMtu);
+ static Future requestMtu(String deviceId, int expectedMtu) =>
+ _platform.requestMtu(deviceId, expectedMtu);
}
diff --git a/packages/quick_blue/lib/src/method_channel_quick_blue.dart b/packages/quick_blue/lib/src/method_channel_quick_blue.dart
index 47064b7..8c57a77 100644
--- a/packages/quick_blue/lib/src/method_channel_quick_blue.dart
+++ b/packages/quick_blue/lib/src/method_channel_quick_blue.dart
@@ -1,5 +1,4 @@
import 'dart:async';
-import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
@@ -9,8 +8,10 @@ import 'quick_blue_platform_interface.dart';
class MethodChannelQuickBlue extends QuickBluePlatform {
static const _method = MethodChannel('quick_blue/method');
static const _eventScanResult = EventChannel('quick_blue/event.scanResult');
- static const _eventAvailabilityChange = EventChannel('quick_blue/event.availabilityChange');
- static const _messageConnector = BasicMessageChannel('quick_blue/message.connector', StandardMessageCodec());
+ static const _eventAvailabilityChange =
+ EventChannel('quick_blue/event.availabilityChange');
+ static const _messageConnector = BasicMessageChannel(
+ 'quick_blue/message.connector', StandardMessageCodec());
MethodChannelQuickBlue() {
_messageConnector.setMessageHandler(_handleConnectorMessage);
@@ -33,14 +34,17 @@ class MethodChannelQuickBlue extends QuickBluePlatform {
return result;
}
- final Stream _availabilityChangeStream = _eventAvailabilityChange.receiveBroadcastStream({'name': 'availabilityChange'}).cast();
+ final Stream _availabilityChangeStream = _eventAvailabilityChange
+ .receiveBroadcastStream({'name': 'availabilityChange'}).cast();
@override
Stream get availabilityChangeStream => _availabilityChangeStream;
@override
- Future startScan() async {
- await _method.invokeMethod('startScan');
+ Future startScan(List? serviceUUIDs) async {
+ await _method.invokeMethod('startScan', {
+ 'serviceUUIDs': serviceUUIDs,
+ });
_log('startScan invokeMethod success');
}
@@ -50,28 +54,29 @@ class MethodChannelQuickBlue extends QuickBluePlatform {
_log('stopScan invokeMethod success');
}
- final Stream _scanResultStream = _eventScanResult.receiveBroadcastStream({'name': 'scanResult'});
+ final Stream _scanResultStream =
+ _eventScanResult.receiveBroadcastStream({'name': 'scanResult'});
@override
Stream get scanResultStream => _scanResultStream;
@override
- void connect(String deviceId) {
- _method.invokeMethod('connect', {
+ Future connect(String deviceId) async {
+ await _method.invokeMethod('connect', {
'deviceId': deviceId,
}).then((_) => _log('connect invokeMethod success'));
}
@override
- void disconnect(String deviceId) {
- _method.invokeMethod('disconnect', {
+ Future disconnect(String deviceId) async {
+ await _method.invokeMethod('disconnect', {
'deviceId': deviceId,
}).then((_) => _log('disconnect invokeMethod success'));
}
@override
- void discoverServices(String deviceId) {
- _method.invokeMethod('discoverServices', {
+ Future discoverServices(String deviceId) async {
+ await _method.invokeMethod('discoverServices', {
'deviceId': deviceId,
}).then((_) => _log('discoverServices invokeMethod success'));
}
@@ -80,29 +85,34 @@ class MethodChannelQuickBlue extends QuickBluePlatform {
_log('_handleConnectorMessage $message', logLevel: Level.ALL);
if (message['ConnectionState'] != null) {
String deviceId = message['deviceId'];
- BlueConnectionState connectionState = BlueConnectionState.parse(message['ConnectionState']);
+ BlueConnectionState connectionState =
+ BlueConnectionState.parse(message['ConnectionState']);
onConnectionChanged?.call(deviceId, connectionState);
} else if (message['ServiceState'] != null) {
if (message['ServiceState'] == 'discovered') {
String deviceId = message['deviceId'];
String service = message['service'];
- List characteristics = (message['characteristics'] as List).cast();
+ List characteristics = ((message['characteristics'] ?? []) as List).cast();
onServiceDiscovered?.call(deviceId, service, characteristics);
}
} else if (message['characteristicValue'] != null) {
String deviceId = message['deviceId'];
var characteristicValue = message['characteristicValue'];
String characteristic = characteristicValue['characteristic'];
- Uint8List value = Uint8List.fromList(characteristicValue['value']); // In case of _Uint8ArrayView
+ Uint8List value = Uint8List.fromList(
+ characteristicValue['value']); // In case of _Uint8ArrayView
onValueChanged?.call(deviceId, characteristic, value);
} else if (message['mtuConfig'] != null) {
_mtuConfigController.add(message['mtuConfig']);
+ } else {
+ _log('_handleConnectorMessage $message is not implented.');
}
}
@override
- Future setNotifiable(String deviceId, String service, String characteristic, BleInputProperty bleInputProperty) async {
- _method.invokeMethod('setNotifiable', {
+ Future setNotifiable(String deviceId, String service,
+ String characteristic, BleInputProperty bleInputProperty) async {
+ await _method.invokeMethod('setNotifiable', {
'deviceId': deviceId,
'service': service,
'characteristic': characteristic,
@@ -111,8 +121,9 @@ class MethodChannelQuickBlue extends QuickBluePlatform {
}
@override
- Future readValue(String deviceId, String service, String characteristic) async {
- _method.invokeMethod('readValue', {
+ Future readValue(
+ String deviceId, String service, String characteristic) async {
+ await _method.invokeMethod('readValue', {
'deviceId': deviceId,
'service': service,
'characteristic': characteristic,
@@ -120,8 +131,14 @@ class MethodChannelQuickBlue extends QuickBluePlatform {
}
@override
- Future writeValue(String deviceId, String service, String characteristic, Uint8List value, BleOutputProperty bleOutputProperty) async {
- _method.invokeMethod('writeValue', {
+ Future writeValue(
+ String deviceId,
+ String service,
+ String characteristic,
+ Uint8List value,
+ BleOutputProperty bleOutputProperty,
+ ) async {
+ await _method.invokeMethod('writeValue', {
'deviceId': deviceId,
'service': service,
'characteristic': characteristic,
@@ -140,10 +157,16 @@ class MethodChannelQuickBlue extends QuickBluePlatform {
@override
Future requestMtu(String deviceId, int expectedMtu) async {
- _method.invokeMethod('requestMtu', {
+ await _method.invokeMethod('requestMtu', {
'deviceId': deviceId,
'expectedMtu': expectedMtu,
}).then((_) => _log('requestMtu invokeMethod success'));
- return await _mtuConfigController.stream.first;
+ int mtu = await _mtuConfigController.stream.first
+ .timeout(const Duration(milliseconds: 1500));
+ if (mtu == -1) {
+ throw Exception('Unable to set MTU size');
+ } else {
+ return mtu;
+ }
}
}
diff --git a/packages/quick_blue/lib/src/quick_blue_linux.dart b/packages/quick_blue/lib/src/quick_blue_linux.dart
index 5952e35..632ebb8 100644
--- a/packages/quick_blue/lib/src/quick_blue_linux.dart
+++ b/packages/quick_blue/lib/src/quick_blue_linux.dart
@@ -24,13 +24,14 @@ class QuickBlueLinux extends QuickBluePlatform {
if (!isInitialized) {
await _client.connect();
- _activeAdapter ??= _client.adapters.firstWhereOrNull((adapter) => adapter.powered);
+ _activeAdapter ??=
+ _client.adapters.firstWhereOrNull((adapter) => adapter.powered);
if (_activeAdapter == null) {
- if (_client.adapters.isEmpty) {
- throw Exception('Bluetooth adapter unavailable');
- }
- await _client.adapters.first.setPowered(true);
- _activeAdapter = _client.adapters.first;
+ if (_client.adapters.isEmpty) {
+ throw Exception('Bluetooth adapter unavailable');
+ }
+ await _client.adapters.first.setPowered(true);
+ _activeAdapter = _client.adapters.first;
}
_client.deviceAdded.listen(_onDeviceAdd);
@@ -63,23 +64,29 @@ class QuickBlueLinux extends QuickBluePlatform {
}
// FIXME Close
- final StreamController _availabilityStateController = StreamController.broadcast();
+ final StreamController _availabilityStateController =
+ StreamController.broadcast();
@override
- Stream get availabilityChangeStream =>_availabilityStateController.stream.map((state) => state.value);
+ Stream get availabilityChangeStream =>
+ _availabilityStateController.stream.map((state) => state.value);
AvailabilityState get availabilityState {
- return _activeAdapter!.powered ? AvailabilityState.poweredOn : AvailabilityState.poweredOff;
+ return _activeAdapter!.powered
+ ? AvailabilityState.poweredOn
+ : AvailabilityState.poweredOff;
}
@override
- Future startScan() async {
+ Future startScan(List? serviceUUIDs) async {
await _ensureInitialized();
_log('startScan invoke success');
if (!_activeAdapter!.discovering) {
- _activeAdapter!.startDiscovery();
+ _activeAdapter!.startDiscovery(); //TODO This is async. Should it be awaited, or should it have error handlers attached?
_client.devices.forEach(_onDeviceAdd);
+ } else {
+ _log('startScan not triggered because we are already scanning');
}
}
@@ -94,7 +101,8 @@ class QuickBlueLinux extends QuickBluePlatform {
}
// FIXME Close
- final StreamController _scanResultController = StreamController.broadcast();
+ final StreamController _scanResultController =
+ StreamController.broadcast();
@override
Stream get scanResultStream => _scanResultController.stream;
@@ -109,7 +117,8 @@ class QuickBlueLinux extends QuickBluePlatform {
}
BlueZDevice _findDeviceById(String deviceId) {
- var device = _client.devices.firstWhereOrNull((device) => device.address == deviceId);
+ var device = _client.devices
+ .firstWhereOrNull((device) => device.address == deviceId);
if (device == null) {
throw Exception('Unknown deviceId:$deviceId');
}
@@ -117,21 +126,21 @@ class QuickBlueLinux extends QuickBluePlatform {
}
@override
- void connect(String deviceId) {
- _findDeviceById(deviceId).connect().then((_) {
+ Future connect(String deviceId) async {
+ await _findDeviceById(deviceId).connect().then((_) {
onConnectionChanged?.call(deviceId, BlueConnectionState.connected);
});
}
@override
- void disconnect(String deviceId) {
- _findDeviceById(deviceId).disconnect().then((_) {
+ Future disconnect(String deviceId) async {
+ await _findDeviceById(deviceId).disconnect().then((_) {
onConnectionChanged?.call(deviceId, BlueConnectionState.disconnected);
});
}
@override
- void discoverServices(String deviceId) {
+ Future discoverServices(String deviceId) async {
var device = _findDeviceById(deviceId);
for (var service in device.gattServices) {
@@ -140,15 +149,20 @@ class QuickBlueLinux extends QuickBluePlatform {
_log(" Characteristic ${characteristic.uuid}");
}
- var characteristics = service.characteristics.map((e) => e.uuid.toString()).toList();
- onServiceDiscovered?.call(deviceId, service.uuid.toString(), characteristics);
+ var characteristics =
+ service.characteristics.map((e) => e.uuid.toString()).toList();
+ onServiceDiscovered?.call(
+ deviceId, service.uuid.toString(), characteristics);
}
}
- BlueZGattCharacteristic _getCharacteristic(String deviceId, String service, String characteristic) {
+ BlueZGattCharacteristic _getCharacteristic(
+ String deviceId, String service, String characteristic) {
var device = _findDeviceById(deviceId);
- var s = device.gattServices.firstWhereOrNull((s) => s.uuid.toString() == service);
- var c = s?.characteristics.firstWhereOrNull((c) => c.uuid.toString() == characteristic);
+ var s = device.gattServices
+ .firstWhereOrNull((s) => s.uuid.toString() == service);
+ var c = s?.characteristics
+ .firstWhereOrNull((c) => c.uuid.toString() == characteristic);
if (c == null) {
throw Exception('Unknown characteristic:$characteristic');
@@ -156,29 +170,36 @@ class QuickBlueLinux extends QuickBluePlatform {
return c;
}
- final Map>> _characteristicPropertiesSubscriptions = {};
+ final Map>>
+ _characteristicPropertiesSubscriptions = {};
@override
- Future setNotifiable(String deviceId, String service, String characteristic, BleInputProperty bleInputProperty) async {
+ Future setNotifiable(String deviceId, String service,
+ String characteristic, BleInputProperty bleInputProperty) async {
var c = _getCharacteristic(deviceId, service, characteristic);
-
+
if (bleInputProperty != BleInputProperty.disabled) {
- c.startNotify();
+ c.startNotify(); //TODO This is async. Should it be awaited, or should it have error handlers attached?
void onPropertiesChanged(properties) {
if (properties.contains('Value')) {
- _log('onCharacteristicPropertiesChanged $characteristic, ${hex.encode(c.value)}');
- onValueChanged?.call(deviceId, characteristic, Uint8List.fromList(c.value));
+ _log(
+ 'onCharacteristicPropertiesChanged $characteristic, ${hex.encode(c.value)}');
+ onValueChanged?.call(
+ deviceId, characteristic, Uint8List.fromList(c.value));
}
}
- _characteristicPropertiesSubscriptions[characteristic] ??= c.propertiesChanged.listen(onPropertiesChanged);
+
+ _characteristicPropertiesSubscriptions[characteristic] ??=
+ c.propertiesChanged.listen(onPropertiesChanged);
} else {
- c.stopNotify();
- _characteristicPropertiesSubscriptions.remove(characteristic)?.cancel();
+ c.stopNotify(); //TODO This is async. Should it be awaited, or should it have error handlers attached?
+ _characteristicPropertiesSubscriptions.remove(characteristic)?.cancel(); //TODO This is async. Should it be awaited, or should it have error handlers attached?
}
}
@override
- Future readValue(String deviceId, String service, String characteristic) async {
+ Future readValue(
+ String deviceId, String service, String characteristic) async {
var c = _getCharacteristic(deviceId, service, characteristic);
var data = await c.readValue();
@@ -187,7 +208,12 @@ class QuickBlueLinux extends QuickBluePlatform {
}
@override
- Future writeValue(String deviceId, String service, String characteristic, Uint8List value, BleOutputProperty bleOutputProperty) async {
+ Future writeValue(
+ String deviceId,
+ String service,
+ String characteristic,
+ Uint8List value,
+ BleOutputProperty bleOutputProperty) async {
var c = _getCharacteristic(deviceId, service, characteristic);
if (bleOutputProperty == BleOutputProperty.withResponse) {
diff --git a/packages/quick_blue/lib/src/quick_blue_platform_interface.dart b/packages/quick_blue/lib/src/quick_blue_platform_interface.dart
index 65d9940..632df05 100644
--- a/packages/quick_blue/lib/src/quick_blue_platform_interface.dart
+++ b/packages/quick_blue/lib/src/quick_blue_platform_interface.dart
@@ -11,11 +11,14 @@ export 'models.dart';
typedef QuickLogger = Logger;
-typedef OnConnectionChanged = void Function(String deviceId, BlueConnectionState state);
+typedef OnConnectionChanged = void Function(
+ String deviceId, BlueConnectionState state);
-typedef OnServiceDiscovered = void Function(String deviceId, String serviceId, List characteristicIds);
+typedef OnServiceDiscovered = void Function(
+ String deviceId, String serviceId, List characteristicIds);
-typedef OnValueChanged = void Function(String deviceId, String characteristicId, Uint8List value);
+typedef OnValueChanged = void Function(
+ String deviceId, String characteristicId, Uint8List value);
abstract class QuickBluePlatform extends PlatformInterface {
QuickBluePlatform() : super(token: _token);
@@ -37,29 +40,36 @@ abstract class QuickBluePlatform extends PlatformInterface {
Stream get availabilityChangeStream;
- Future startScan();
+ Future startScan(List? serviceUUIDs);
Future stopScan();
Stream get scanResultStream;
- void connect(String deviceId);
+ Future connect(String deviceId);
- void disconnect(String deviceId);
+ Future disconnect(String deviceId);
OnConnectionChanged? onConnectionChanged;
- void discoverServices(String deviceId);
+ Future discoverServices(String deviceId);
OnServiceDiscovered? onServiceDiscovered;
- Future setNotifiable(String deviceId, String service, String characteristic, BleInputProperty bleInputProperty);
+ Future setNotifiable(String deviceId, String service,
+ String characteristic, BleInputProperty bleInputProperty);
OnValueChanged? onValueChanged;
- Future readValue(String deviceId, String service, String characteristic);
+ Future readValue(
+ String deviceId, String service, String characteristic);
- Future writeValue(String deviceId, String service, String characteristic, Uint8List value, BleOutputProperty bleOutputProperty);
+ Future writeValue(
+ String deviceId,
+ String service,
+ String characteristic,
+ Uint8List value,
+ BleOutputProperty bleOutputProperty);
Future requestMtu(String deviceId, int expectedMtu);
}
diff --git a/packages/quick_blue/pubspec.yaml b/packages/quick_blue/pubspec.yaml
index 5e1ef52..e49a6b9 100644
--- a/packages/quick_blue/pubspec.yaml
+++ b/packages/quick_blue/pubspec.yaml
@@ -13,13 +13,14 @@ dependencies:
sdk: flutter
logging: ^1.0.2
plugin_platform_interface: ^2.1.2
- bluez: ^0.7.9
+ bluez: ^0.8.1
convert: ^3.0.1
+ collection: ^1.17.0
dev_dependencies:
flutter_test:
sdk: flutter
- flutter_lints: ^1.0.0
+ flutter_lints: ^3.0.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
diff --git a/packages/quick_blue/windows/quick_blue_plugin.cpp b/packages/quick_blue/windows/quick_blue_plugin.cpp
index 9ec7456..e4331bd 100644
--- a/packages/quick_blue/windows/quick_blue_plugin.cpp
+++ b/packages/quick_blue/windows/quick_blue_plugin.cpp
@@ -24,310 +24,431 @@
#include
#include
+#include
+#include
+#include
+
#define GUID_FORMAT "%08x-%04hx-%04hx-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
#define GUID_ARG(guid) guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]
-namespace {
-
-using namespace winrt::Windows::Foundation;
-using namespace winrt::Windows::Foundation::Collections;
-using namespace winrt::Windows::Storage::Streams;
-using namespace winrt::Windows::Devices::Radios;
-using namespace winrt::Windows::Devices::Bluetooth;
-using namespace winrt::Windows::Devices::Bluetooth::Advertisement;
-using namespace winrt::Windows::Devices::Bluetooth::GenericAttributeProfile;
-
-using flutter::EncodableValue;
-using flutter::EncodableMap;
-using flutter::EncodableList;
-
-union uint16_t_union {
- uint16_t uint16;
- byte bytes[sizeof(uint16_t)];
-};
-
-std::vector to_bytevc(IBuffer buffer) {
- auto reader = DataReader::FromBuffer(buffer);
- auto result = std::vector(reader.UnconsumedBufferLength());
- reader.ReadBytes(result);
- return result;
-}
-
-IBuffer from_bytevc(std::vector bytes) {
- auto writer = DataWriter();
- writer.WriteBytes(bytes);
- return writer.DetachBuffer();
-}
-
-std::string to_hexstring(std::vector bytes) {
- auto ss = std::stringstream();
- for (auto b : bytes)
+namespace
+{
+
+ using namespace winrt::Windows::Foundation;
+ using namespace winrt::Windows::Foundation::Collections;
+ using namespace winrt::Windows::Storage::Streams;
+ using namespace winrt::Windows::Devices::Radios;
+ using namespace winrt::Windows::Devices::Bluetooth;
+ using namespace winrt::Windows::Devices::Bluetooth::Advertisement;
+ using namespace winrt::Windows::Devices::Bluetooth::GenericAttributeProfile;
+
+ using flutter::EncodableList;
+ using flutter::EncodableMap;
+ using flutter::EncodableValue;
+
+ union uint16_t_union
+ {
+ uint16_t uint16;
+ byte bytes[sizeof(uint16_t)];
+ };
+
+ std::vector to_bytevc(IBuffer buffer)
+ {
+ auto reader = DataReader::FromBuffer(buffer);
+ auto result = std::vector(reader.UnconsumedBufferLength());
+ reader.ReadBytes(result);
+ return result;
+ }
+
+ IBuffer from_bytevc(std::vector bytes)
+ {
+ auto writer = DataWriter();
+ writer.WriteBytes(bytes);
+ return writer.DetachBuffer();
+ }
+
+ std::string to_hexstring(std::vector bytes)
+ {
+ auto ss = std::stringstream();
+ for (auto b : bytes)
ss << std::setw(2) << std::setfill('0') << std::hex << static_cast(b);
- return ss.str();
-}
-
-std::string to_uuidstr(winrt::guid guid) {
- char chars[36 + 1];
- sprintf_s(chars, GUID_FORMAT, GUID_ARG(guid));
- return std::string{ chars };
-}
-
-struct BluetoothDeviceAgent {
- BluetoothLEDevice device;
- winrt::event_token connnectionStatusChangedToken;
- std::map gattServices;
- std::map gattCharacteristics;
- std::map valueChangedTokens;
-
- BluetoothDeviceAgent(BluetoothLEDevice device, winrt::event_token connnectionStatusChangedToken)
- : device(device),
- connnectionStatusChangedToken(connnectionStatusChangedToken) {}
-
- ~BluetoothDeviceAgent() {
- device = nullptr;
+ return ss.str();
}
- IAsyncOperation GetServiceAsync(std::string service) {
- if (gattServices.count(service) == 0) {
- auto serviceResult = co_await device.GetGattServicesAsync();
- if (serviceResult.Status() != GattCommunicationStatus::Success)
- co_return nullptr;
+ union to_guid
+ {
+ uint8_t buf[16];
+ winrt::guid guid;
+ };
+
+ const uint8_t BYTE_ORDER[] = {3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15};
+
+ winrt::guid make_guid(const char *value)
+ {
+ to_guid to_guid;
+ memset(&to_guid, 0, sizeof(to_guid));
+ int offset = 0;
+ for (int i = 0; i < (int)strlen(value); i++)
+ {
+ if (value[i] >= '0' && value[i] <= '9')
+ {
+ uint8_t digit = value[i] - '0';
+ to_guid.buf[BYTE_ORDER[offset / 2]] += offset % 2 == 0 ? digit << 4 : digit;
+ offset++;
+ }
+ else if (value[i] >= 'A' && value[i] <= 'F')
+ {
+ uint8_t digit = 10 + value[i] - 'A';
+ to_guid.buf[BYTE_ORDER[offset / 2]] += offset % 2 == 0 ? digit << 4 : digit;
+ offset++;
+ }
+ else if (value[i] >= 'a' && value[i] <= 'f')
+ {
+ uint8_t digit = 10 + value[i] - 'a';
+ to_guid.buf[BYTE_ORDER[offset / 2]] += offset % 2 == 0 ? digit << 4 : digit;
+ offset++;
+ }
+ else
+ {
+ // skip char
+ }
+ }
+
+ return to_guid.guid;
+ }
- for (auto s : serviceResult.Services())
- if (to_uuidstr(s.Uuid()) == service)
- gattServices.insert(std::make_pair(service, s));
+ std::string LongUUID(std::string str)
+ {
+ std::string baseBluetoothUuidPostfix("0000-1000-8000-00805F9B34FB");
+
+ switch (str.length())
+ {
+ case 4:
+ return "0000" + str + "-" + baseBluetoothUuidPostfix;
+ case 8:
+ return str + "-" + baseBluetoothUuidPostfix;
+ default:
+ return str;
}
- co_return gattServices.at(service);
}
- IAsyncOperation GetCharacteristicAsync(std::string service, std::string characteristic) {
- if (gattCharacteristics.count(characteristic) == 0) {
- auto gattService = co_await GetServiceAsync(service);
+ std::string to_uuidstr(winrt::guid guid)
+ {
+ char chars[36 + 1];
+ sprintf_s(chars, GUID_FORMAT, GUID_ARG(guid));
+ return std::string{chars};
+ }
- auto characteristicResult = co_await gattService.GetCharacteristicsAsync();
- if (characteristicResult.Status() != GattCommunicationStatus::Success)
- co_return nullptr;
+ struct BluetoothDeviceAgent
+ {
+ BluetoothLEDevice device;
+ winrt::event_token connnectionStatusChangedToken;
+ std::map gattServices;
+ std::map gattCharacteristics;
+ std::map valueChangedTokens;
+
+ BluetoothDeviceAgent(BluetoothLEDevice device, winrt::event_token connnectionStatusChangedToken)
+ : device(device),
+ connnectionStatusChangedToken(connnectionStatusChangedToken) {}
+
+ ~BluetoothDeviceAgent()
+ {
+ device = nullptr;
+ }
- for (auto c : characteristicResult.Characteristics())
- if (to_uuidstr(c.Uuid()) == characteristic)
- gattCharacteristics.insert(std::make_pair(characteristic, c));
+ IAsyncOperation GetServiceAsync(std::string service)
+ {
+ if (gattServices.count(service) == 0)
+ {
+ auto serviceResult = co_await device.GetGattServicesAsync();
+ if (serviceResult.Status() != GattCommunicationStatus::Success)
+ co_return nullptr;
+
+ for (auto s : serviceResult.Services())
+ if (to_uuidstr(s.Uuid()) == service)
+ gattServices.insert(std::make_pair(service, s));
+ }
+ co_return gattServices.at(service);
}
- co_return gattCharacteristics.at(characteristic);
- }
-};
-class QuickBluePlugin : public flutter::Plugin, public flutter::StreamHandler {
- public:
- static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
+ IAsyncOperation GetCharacteristicAsync(std::string service, std::string characteristic)
+ {
+ if (gattCharacteristics.count(characteristic) == 0)
+ {
+ auto gattService = co_await GetServiceAsync(service);
- QuickBluePlugin();
+ auto characteristicResult = co_await gattService.GetCharacteristicsAsync();
+ if (characteristicResult.Status() != GattCommunicationStatus::Success)
+ co_return nullptr;
- virtual ~QuickBluePlugin();
+ for (auto c : characteristicResult.Characteristics())
+ if (to_uuidstr(c.Uuid()) == characteristic)
+ gattCharacteristics.insert(std::make_pair(characteristic, c));
+ }
+ co_return gattCharacteristics.at(characteristic);
+ }
+ };
+
+ class QuickBluePlugin : public flutter::Plugin, public flutter::StreamHandler
+ {
+ public:
+ static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
+
+ QuickBluePlugin();
+
+ virtual ~QuickBluePlugin();
+
+ private:
+ winrt::fire_and_forget InitializeAsync();
+
+ // Called when a method is called on this plugin's channel from Dart.
+ void HandleMethodCall(
+ const flutter::MethodCall &method_call,
+ std::unique_ptr> result);
+
+ std::unique_ptr> OnListenInternal(
+ const EncodableValue *arguments,
+ std::unique_ptr> &&events) override;
+ std::unique_ptr> OnCancelInternal(
+ const EncodableValue *arguments) override;
+
+ std::unique_ptr> message_connector_;
+
+ std::unique_ptr> availability_change_sink_;
+ std::unique_ptr> scan_result_sink_;
+
+ Radio bluetoothRadio{nullptr};
+ void Radio_StateChanged(Radio sender, IInspectable args);
+ RadioState oldRadioState = RadioState::Unknown;
+
+ BluetoothLEAdvertisementWatcher bluetoothLEWatcher{nullptr};
+ winrt::event_token bluetoothLEWatcherReceivedToken;
+ winrt::fire_and_forget BluetoothLEWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args);
+
+ std::map> connectedDevices{};
+ winrt::event_revoker radioStateChangedRevoker;
+
+ winrt::fire_and_forget ConnectAsync(uint64_t bluetoothAddress);
+ void BluetoothLEDevice_ConnectionStatusChanged(BluetoothLEDevice sender, IInspectable args);
+ void CleanConnection(uint64_t bluetoothAddress);
+ winrt::fire_and_forget DiscoverServicesAsync(BluetoothDeviceAgent &bluetoothDeviceAgent);
+
+ winrt::fire_and_forget SetNotifiableAsync(BluetoothDeviceAgent &bluetoothDeviceAgent, std::string service, std::string characteristic, std::string bleInputProperty);
+ winrt::fire_and_forget RequestMtuAsync(BluetoothDeviceAgent &bluetoothDeviceAgent, uint64_t expectedMtu);
+ winrt::fire_and_forget ReadValueAsync(GattCharacteristic &gattCharacteristic);
+ winrt::fire_and_forget WriteValueAsync(BluetoothDeviceAgent &bluetoothDeviceAgent, std::string service, std::string characteristic, std::vector value, std::string bleOutputProperty);
+ void QuickBluePlugin::GattCharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args);
+ };
+
+ // static
+ void QuickBluePlugin::RegisterWithRegistrar(
+ flutter::PluginRegistrarWindows *registrar)
+ {
+ auto method =
+ std::make_unique>(
+ registrar->messenger(), "quick_blue/method",
+ &flutter::StandardMethodCodec::GetInstance());
+ auto event_availability_change =
+ std::make_unique>(
+ registrar->messenger(), "quick_blue/event.availabilityChange",
+ &flutter::StandardMethodCodec::GetInstance());
+ auto event_scan_result =
+ std::make_unique>(
+ registrar->messenger(), "quick_blue/event.scanResult",
+ &flutter::StandardMethodCodec::GetInstance());
+ auto message_connector_ =
+ std::make_unique>(
+ registrar->messenger(), "quick_blue/message.connector",
+ &flutter::StandardMessageCodec::GetInstance());
+
+ auto plugin = std::make_unique();
+
+ method->SetMethodCallHandler(
+ [plugin_pointer = plugin.get()](const auto &call, auto result)
+ {
+ plugin_pointer->HandleMethodCall(call, std::move(result));
+ });
- private:
- winrt::fire_and_forget InitializeAsync();
+ auto availability_handler = std::make_unique<
+ flutter::StreamHandlerFunctions<>>(
+ [plugin_pointer = plugin.get()](
+ const EncodableValue *arguments,
+ std::unique_ptr> &&events)
+ -> std::unique_ptr>
+ {
+ return plugin_pointer->OnListen(arguments, std::move(events));
+ },
+ [plugin_pointer = plugin.get()](const EncodableValue *arguments)
+ -> std::unique_ptr>
+ {
+ return plugin_pointer->OnCancel(arguments);
+ });
+ auto scan_result_handler = std::make_unique<
+ flutter::StreamHandlerFunctions<>>(
+ [plugin_pointer = plugin.get()](
+ const EncodableValue *arguments,
+ std::unique_ptr> &&events)
+ -> std::unique_ptr>
+ {
+ return plugin_pointer->OnListen(arguments, std::move(events));
+ },
+ [plugin_pointer = plugin.get()](const EncodableValue *arguments)
+ -> std::unique_ptr>
+ {
+ return plugin_pointer->OnCancel(arguments);
+ });
+ event_availability_change->SetStreamHandler(std::move(availability_handler));
+ event_scan_result->SetStreamHandler(std::move(scan_result_handler));
+ plugin->message_connector_ = std::move(message_connector_);
- // Called when a method is called on this plugin's channel from Dart.
- void HandleMethodCall(
- const flutter::MethodCall &method_call,
- std::unique_ptr> result);
-
- std::unique_ptr> OnListenInternal(
- const EncodableValue* arguments,
- std::unique_ptr>&& events) override;
- std::unique_ptr> OnCancelInternal(
- const EncodableValue* arguments) override;
-
- std::unique_ptr> message_connector_;
-
- std::unique_ptr> availability_change_sink_;
- std::unique_ptr> scan_result_sink_;
-
- Radio bluetoothRadio{ nullptr };
- void Radio_StateChanged(Radio sender, IInspectable args);
- RadioState oldRadioState = RadioState::Unknown;
-
- BluetoothLEAdvertisementWatcher bluetoothLEWatcher{ nullptr };
- winrt::event_token bluetoothLEWatcherReceivedToken;
- winrt::fire_and_forget BluetoothLEWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args);
-
- std::map> connectedDevices{};
- winrt::event_revoker radioStateChangedRevoker;
-
- winrt::fire_and_forget ConnectAsync(uint64_t bluetoothAddress);
- void BluetoothLEDevice_ConnectionStatusChanged(BluetoothLEDevice sender, IInspectable args);
- void CleanConnection(uint64_t bluetoothAddress);
- winrt::fire_and_forget DiscoverServicesAsync(BluetoothDeviceAgent &bluetoothDeviceAgent);
-
- winrt::fire_and_forget SetNotifiableAsync(BluetoothDeviceAgent& bluetoothDeviceAgent, GattCharacteristic& gattCharacteristic, std::string bleInputProperty);
- winrt::fire_and_forget RequestMtuAsync(BluetoothDeviceAgent& bluetoothDeviceAgent, uint64_t expectedMtu);
- winrt::fire_and_forget ReadValueAsync(GattCharacteristic& gattCharacteristic);
- winrt::fire_and_forget WriteValueAsync(GattCharacteristic& gattCharacteristic, std::vector value, std::string bleOutputProperty);
- void QuickBluePlugin::GattCharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args);
-};
-
-// static
-void QuickBluePlugin::RegisterWithRegistrar(
- flutter::PluginRegistrarWindows *registrar) {
- auto method =
- std::make_unique>(
- registrar->messenger(), "quick_blue/method",
- &flutter::StandardMethodCodec::GetInstance());
- auto event_availability_change =
- std::make_unique>(
- registrar->messenger(), "quick_blue/event.availabilityChange",
- &flutter::StandardMethodCodec::GetInstance());
- auto event_scan_result =
- std::make_unique>(
- registrar->messenger(), "quick_blue/event.scanResult",
- &flutter::StandardMethodCodec::GetInstance());
- auto message_connector_ =
- std::make_unique>(
- registrar->messenger(), "quick_blue/message.connector",
- &flutter::StandardMessageCodec::GetInstance());
-
- auto plugin = std::make_unique();
-
- method->SetMethodCallHandler(
- [plugin_pointer = plugin.get()](const auto &call, auto result) {
- plugin_pointer->HandleMethodCall(call, std::move(result));
- });
+ registrar->AddPlugin(std::move(plugin));
+ }
- auto availability_handler = std::make_unique<
- flutter::StreamHandlerFunctions<>>(
- [plugin_pointer = plugin.get()](
- const EncodableValue* arguments,
- std::unique_ptr>&& events)
- -> std::unique_ptr> {
- return plugin_pointer->OnListen(arguments, std::move(events));
- },
- [plugin_pointer = plugin.get()](const EncodableValue* arguments)
- -> std::unique_ptr> {
- return plugin_pointer->OnCancel(arguments);
- });
- auto scan_result_handler = std::make_unique<
- flutter::StreamHandlerFunctions<>>(
- [plugin_pointer = plugin.get()](
- const EncodableValue* arguments,
- std::unique_ptr>&& events)
- -> std::unique_ptr> {
- return plugin_pointer->OnListen(arguments, std::move(events));
- },
- [plugin_pointer = plugin.get()](const EncodableValue* arguments)
- -> std::unique_ptr> {
- return plugin_pointer->OnCancel(arguments);
- });
- event_availability_change->SetStreamHandler(std::move(availability_handler));
- event_scan_result->SetStreamHandler(std::move(scan_result_handler));
- plugin->message_connector_ = std::move(message_connector_);
-
- registrar->AddPlugin(std::move(plugin));
-}
-
-QuickBluePlugin::QuickBluePlugin() {
- InitializeAsync();
-}
-
-QuickBluePlugin::~QuickBluePlugin() {}
-
-winrt::fire_and_forget QuickBluePlugin::InitializeAsync() {
- auto bluetoothAdapter = co_await BluetoothAdapter::GetDefaultAsync();
- bluetoothRadio = co_await bluetoothAdapter.GetRadioAsync();
- if (bluetoothRadio) {
- radioStateChangedRevoker = bluetoothRadio.StateChanged(winrt::auto_revoke, { this, &QuickBluePlugin::Radio_StateChanged });
+ QuickBluePlugin::QuickBluePlugin()
+ {
+ InitializeAsync();
}
-}
-
-void QuickBluePlugin::HandleMethodCall(
- const flutter::MethodCall &method_call,
- std::unique_ptr> result) {
- auto method_name = method_call.method_name();
- OutputDebugString((L"HandleMethodCall " + winrt::to_hstring(method_name) + L"\n").c_str());
- if (method_name.compare("isBluetoothAvailable") == 0) {
- result->Success(EncodableValue(bluetoothRadio && bluetoothRadio.State() == RadioState::On));
- } else if (method_name.compare("startScan") == 0) {
- if (bluetoothRadio && bluetoothRadio.State() == RadioState::On) {
- if (!bluetoothLEWatcher) {
- bluetoothLEWatcher = BluetoothLEAdvertisementWatcher();
- bluetoothLEWatcherReceivedToken = bluetoothLEWatcher.Received({ this, &QuickBluePlugin::BluetoothLEWatcher_Received });
+
+ QuickBluePlugin::~QuickBluePlugin() {}
+
+ winrt::fire_and_forget QuickBluePlugin::InitializeAsync()
+ {
+ auto bluetoothAdapter = co_await BluetoothAdapter::GetDefaultAsync();
+ bluetoothRadio = co_await bluetoothAdapter.GetRadioAsync();
+ if (bluetoothRadio)
+ {
+ radioStateChangedRevoker = bluetoothRadio.StateChanged(winrt::auto_revoke, {this, &QuickBluePlugin::Radio_StateChanged});
+ }
+ }
+
+ void QuickBluePlugin::HandleMethodCall(
+ const flutter::MethodCall &method_call,
+ std::unique_ptr> result)
+ {
+ auto method_name = method_call.method_name();
+ OutputDebugString((L"HandleMethodCall " + winrt::to_hstring(method_name) + L"\n").c_str());
+ if (method_name.compare("isBluetoothAvailable") == 0)
+ {
+ result->Success(EncodableValue(bluetoothRadio && bluetoothRadio.State() == RadioState::On));
+ }
+ else if (method_name.compare("startScan") == 0)
+ {
+ if (bluetoothRadio && bluetoothRadio.State() == RadioState::On)
+ {
+ if (!bluetoothLEWatcher)
+ {
+ bluetoothLEWatcher = BluetoothLEAdvertisementWatcher();
+ bluetoothLEWatcherReceivedToken = bluetoothLEWatcher.Received({this, &QuickBluePlugin::BluetoothLEWatcher_Received});
+ }
+
+ auto args = std::get(*method_call.arguments());
+ auto serviceUUIDs = std::get(args[EncodableValue("serviceUUIDs")]);
+
+ if (serviceUUIDs.size() > 0)
+ {
+ auto filter = BluetoothLEAdvertisementFilter();
+ auto advert = BluetoothLEAdvertisement();
+ for (size_t i = 0; i < serviceUUIDs.size(); i++)
+ {
+ std::string serviceString = std::get(serviceUUIDs[i]);
+ auto serviceGuid = make_guid(LongUUID(serviceString).c_str());
+ advert.ServiceUuids().Append(serviceGuid);
+ OutputDebugStringA(to_uuidstr(serviceGuid).c_str());
+ }
+ filter.Advertisement(advert);
+ bluetoothLEWatcher.AdvertisementFilter(filter);
+ }
+ else
+ {
+ }
+
+ bluetoothLEWatcher.Start();
+ result->Success(nullptr);
}
- bluetoothLEWatcher.Start();
- result->Success(nullptr);
- } else {
- result->Error("IllegalState", "Bluetooth unavailable");
- }
- } else if (method_name.compare("stopScan") == 0) {
- if (bluetoothRadio && bluetoothRadio.State() == RadioState::On) {
- if (bluetoothLEWatcher) {
- bluetoothLEWatcher.Stop();
- bluetoothLEWatcher.Received(bluetoothLEWatcherReceivedToken);
+ else
+ {
+ result->Error("IllegalState", "Bluetooth unavailable");
+ }
+ }
+ else if (method_name.compare("stopScan") == 0)
+ {
+ if (bluetoothRadio && bluetoothRadio.State() == RadioState::On)
+ {
+ if (bluetoothLEWatcher)
+ {
+ bluetoothLEWatcher.Stop();
+ bluetoothLEWatcher.Received(bluetoothLEWatcherReceivedToken);
+ }
+ bluetoothLEWatcher = nullptr;
+ result->Success(nullptr);
}
- bluetoothLEWatcher = nullptr;
+ else
+ {
+ result->Error("IllegalState", "Bluetooth unavailable");
+ }
+ }
+ else if (method_name.compare("connect") == 0)
+ {
+ auto args = std::get(*method_call.arguments());
+ auto deviceId = std::get(args[EncodableValue("deviceId")]);
+ ConnectAsync(std::stoull(deviceId));
result->Success(nullptr);
- } else {
- result->Error("IllegalState", "Bluetooth unavailable");
- }
- } else if (method_name.compare("connect") == 0) {
- auto args = std::get(*method_call.arguments());
- auto deviceId = std::get(args[EncodableValue("deviceId")]);
- ConnectAsync(std::stoull(deviceId));
- result->Success(nullptr);
- } else if (method_name.compare("disconnect") == 0) {
- auto args = std::get(*method_call.arguments());
- auto deviceId = std::get(args[EncodableValue("deviceId")]);
- CleanConnection(std::stoull(deviceId));
- // TODO send `disconnected` message
- result->Success(nullptr);
- } else if (method_name.compare("discoverServices") == 0) {
- auto args = std::get(*method_call.arguments());
- auto deviceId = std::get(args[EncodableValue("deviceId")]);
- auto it = connectedDevices.find(std::stoull(deviceId));
- if (it == connectedDevices.end()) {
- result->Error("IllegalArgument", "Unknown devicesId:" + deviceId);
- return;
}
- DiscoverServicesAsync(*it->second);
- result->Success(nullptr);
- } else if (method_name.compare("setNotifiable") == 0) {
- auto args = std::get(*method_call.arguments());
- auto deviceId = std::get(args[EncodableValue("deviceId")]);
- auto service = std::get(args[EncodableValue("service")]);
- auto characteristic = std::get(args[EncodableValue("characteristic")]);
- auto bleInputProperty = std::get(args[EncodableValue("bleInputProperty")]);
- auto it = connectedDevices.find(std::stoull(deviceId));
- if (it == connectedDevices.end()) {
- result->Error("IllegalArgument", "Unknown devicesId:" + deviceId);
- return;
+ else if (method_name.compare("disconnect") == 0)
+ {
+ auto args = std::get(*method_call.arguments());
+ auto deviceId = std::get(args[EncodableValue("deviceId")]);
+ CleanConnection(std::stoull(deviceId));
+ // TODO send `disconnected` message
+ result->Success(nullptr);
}
+ else if (method_name.compare("discoverServices") == 0)
+ {
+ auto args = std::get(*method_call.arguments());
+ auto deviceId = std::get(args[EncodableValue("deviceId")]);
+ auto it = connectedDevices.find(std::stoull(deviceId));
+ if (it == connectedDevices.end())
+ {
+ result->Error("IllegalArgument", "Unknown devicesId:" + deviceId);
+ return;
+ }
+ DiscoverServicesAsync(*it->second);
+ result->Success(nullptr);
+ }
+ else if (method_name.compare("setNotifiable") == 0)
+ {
+ auto args = std::get(*method_call.arguments());
+ auto deviceId = std::get(args[EncodableValue("deviceId")]);
+ auto service = std::get(args[EncodableValue("service")]);
+ auto characteristic = std::get(args[EncodableValue("characteristic")]);
+ auto bleInputProperty = std::get(args[EncodableValue("bleInputProperty")]);
+ auto it = connectedDevices.find(std::stoull(deviceId));
+ if (it == connectedDevices.end())
+ {
+ result->Error("IllegalArgument", "Unknown devicesId:" + deviceId);
+ return;
+ }
- auto bluetoothAgent = *it->second;
- auto async_c = bluetoothAgent.GetCharacteristicAsync(service, characteristic);
- async_c.Completed([&, result_pointer = result.get()]
- (IAsyncOperation const& sender, AsyncStatus const args) {
- // FIXME https://github.com/woodemi/quick.flutter/pull/31#issuecomment-1159213902
- auto c = sender.GetResults();
- if (c == nullptr) {
- result_pointer->Error("IllegalArgument", "Unknown characteristic:" + characteristic);
- return;
- }
- SetNotifiableAsync(bluetoothAgent, c, bleInputProperty);
- result_pointer->Success(nullptr);
- });
- } else if (method_name.compare("readValue") == 0) {
- auto args = std::get(*method_call.arguments());
- auto deviceId = std::get(args[EncodableValue("deviceId")]);
- auto service = std::get(args[EncodableValue("service")]);
- auto characteristic = std::get(args[EncodableValue("characteristic")]);
- auto it = connectedDevices.find(std::stoull(deviceId));
- if (it == connectedDevices.end()) {
- result->Error("IllegalArgument", "Unknown devicesId:" + deviceId);
- return;
+ SetNotifiableAsync(*it->second, service, characteristic, bleInputProperty);
+ result->Success(nullptr);
}
+ else if (method_name.compare("readValue") == 0)
+ {
+ auto args = std::get(*method_call.arguments());
+ auto deviceId = std::get(args[EncodableValue("deviceId")]);
+ auto service = std::get(args[EncodableValue("service")]);
+ auto characteristic = std::get(args[EncodableValue("characteristic")]);
+ auto it = connectedDevices.find(std::stoull(deviceId));
+ if (it == connectedDevices.end())
+ {
+ result->Error("IllegalArgument", "Unknown devicesId:" + deviceId);
+ return;
+ }
- auto bluetoothAgent = *it->second;
- auto async_c = bluetoothAgent.GetCharacteristicAsync(service, characteristic);
- async_c.Completed([&, result_pointer = result.get()]
- (IAsyncOperation const& sender, AsyncStatus const args) {
+ auto bluetoothAgent = *it->second;
+ auto async_c = bluetoothAgent.GetCharacteristicAsync(service, characteristic);
+ async_c.Completed([&, result_pointer = result.get()](IAsyncOperation const &sender, AsyncStatus const args)
+ {
// FIXME https://github.com/woodemi/quick.flutter/pull/31#issuecomment-1159213902
auto c = sender.GetResults();
if (c == nullptr) {
@@ -335,289 +456,330 @@ void QuickBluePlugin::HandleMethodCall(
return;
}
ReadValueAsync(c);
- result_pointer->Success(nullptr);
- });
- } else if (method_name.compare("writeValue") == 0) {
- auto args = std::get(*method_call.arguments());
- auto deviceId = std::get(args[EncodableValue("deviceId")]);
- auto service = std::get(args[EncodableValue("service")]);
- auto characteristic = std::get(args[EncodableValue("characteristic")]);
- auto value = std::get>(args[EncodableValue("value")]);
- auto bleOutputProperty = std::get(args[EncodableValue("bleOutputProperty")]);
- auto it = connectedDevices.find(std::stoull(deviceId));
- if (it == connectedDevices.end()) {
- result->Error("IllegalArgument", "Unknown devicesId:" + deviceId);
- return;
+ result_pointer->Success(nullptr); });
}
+ else if (method_name.compare("writeValue") == 0)
+ {
+ auto args = std::get