Initial commit
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
Extension Discovery Cache
|
||||||
|
=========================
|
||||||
|
|
||||||
|
This folder is used by `package:extension_discovery` to cache lists of
|
||||||
|
packages that contains extensions for other packages.
|
||||||
|
|
||||||
|
DO NOT USE THIS FOLDER
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
* Do not read (or rely) the contents of this folder.
|
||||||
|
* Do write to this folder.
|
||||||
|
|
||||||
|
If you're interested in the lists of extensions stored in this folder use the
|
||||||
|
API offered by package `extension_discovery` to get this information.
|
||||||
|
|
||||||
|
If this package doesn't work for your use-case, then don't try to read the
|
||||||
|
contents of this folder. It may change, and will not remain stable.
|
||||||
|
|
||||||
|
Use package `extension_discovery`
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
If you want to access information from this folder.
|
||||||
|
|
||||||
|
Feel free to delete this folder
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Files in this folder act as a cache, and the cache is discarded if the files
|
||||||
|
are older than the modification time of `.dart_tool/package_config.json`.
|
||||||
|
|
||||||
|
Hence, it should never be necessary to clear this cache manually, if you find a
|
||||||
|
need to do please file a bug.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":2,"entries":[{"package":"ble_relay_host","rootUri":"../","packageUri":"lib/"}]}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
{
|
||||||
|
"configVersion": 2,
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "async",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/async-2.13.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "boolean_selector",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "characters",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/characters-1.4.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clock",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/clock-1.1.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collection",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/collection-1.19.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fake_async",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/fake_async-1.3.3",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter",
|
||||||
|
"rootUri": "file:///Users/tonylu/git/flutter/packages/flutter",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_lints",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/flutter_lints-5.0.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_test",
|
||||||
|
"rootUri": "file:///Users/tonylu/git/flutter/packages/flutter_test",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/leak_tracker-11.0.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_flutter_testing",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_testing",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lints",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/lints-5.1.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "matcher",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/matcher-0.12.17",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "material_color_utilities",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "2.17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meta",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/meta-1.16.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "2.12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "path",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/path-1.9.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "plugin_platform_interface",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sky_engine",
|
||||||
|
"rootUri": "file:///Users/tonylu/git/flutter/bin/cache/pkg/sky_engine",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "source_span",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/source_span-1.10.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stack_trace",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/stack_trace-1.12.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stream_channel",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/stream_channel-2.1.4",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "string_scanner",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/string_scanner-1.4.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "term_glyph",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/term_glyph-1.2.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test_api",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/test_api-0.7.6",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vector_math",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/vector_math-2.2.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vm_service",
|
||||||
|
"rootUri": "file:///Users/tonylu/.pub-cache/hosted/pub.dev/vm_service-15.1.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ble_relay_host",
|
||||||
|
"rootUri": "../",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"generator": "pub",
|
||||||
|
"generatorVersion": "3.9.2",
|
||||||
|
"flutterRoot": "file:///Users/tonylu/git/flutter",
|
||||||
|
"flutterVersion": "3.35.4",
|
||||||
|
"pubCache": "file:///Users/tonylu/.pub-cache"
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
{
|
||||||
|
"roots": [
|
||||||
|
"ble_relay_host"
|
||||||
|
],
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "ble_relay_host",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"plugin_platform_interface"
|
||||||
|
],
|
||||||
|
"devDependencies": [
|
||||||
|
"flutter_lints",
|
||||||
|
"flutter_test"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_lints",
|
||||||
|
"version": "5.0.0",
|
||||||
|
"dependencies": [
|
||||||
|
"lints"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_test",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": [
|
||||||
|
"clock",
|
||||||
|
"collection",
|
||||||
|
"fake_async",
|
||||||
|
"flutter",
|
||||||
|
"leak_tracker_flutter_testing",
|
||||||
|
"matcher",
|
||||||
|
"meta",
|
||||||
|
"path",
|
||||||
|
"stack_trace",
|
||||||
|
"stream_channel",
|
||||||
|
"test_api",
|
||||||
|
"vector_math"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": [
|
||||||
|
"characters",
|
||||||
|
"collection",
|
||||||
|
"material_color_utilities",
|
||||||
|
"meta",
|
||||||
|
"sky_engine",
|
||||||
|
"vector_math"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vector_math",
|
||||||
|
"version": "2.2.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test_api",
|
||||||
|
"version": "0.7.6",
|
||||||
|
"dependencies": [
|
||||||
|
"async",
|
||||||
|
"boolean_selector",
|
||||||
|
"collection",
|
||||||
|
"meta",
|
||||||
|
"source_span",
|
||||||
|
"stack_trace",
|
||||||
|
"stream_channel",
|
||||||
|
"string_scanner",
|
||||||
|
"term_glyph"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stream_channel",
|
||||||
|
"version": "2.1.4",
|
||||||
|
"dependencies": [
|
||||||
|
"async"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stack_trace",
|
||||||
|
"version": "1.12.1",
|
||||||
|
"dependencies": [
|
||||||
|
"path"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "path",
|
||||||
|
"version": "1.9.1",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meta",
|
||||||
|
"version": "1.16.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "matcher",
|
||||||
|
"version": "0.12.17",
|
||||||
|
"dependencies": [
|
||||||
|
"async",
|
||||||
|
"meta",
|
||||||
|
"stack_trace",
|
||||||
|
"term_glyph",
|
||||||
|
"test_api"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_flutter_testing",
|
||||||
|
"version": "3.0.10",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"leak_tracker",
|
||||||
|
"leak_tracker_testing",
|
||||||
|
"matcher",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fake_async",
|
||||||
|
"version": "1.3.3",
|
||||||
|
"dependencies": [
|
||||||
|
"clock",
|
||||||
|
"collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collection",
|
||||||
|
"version": "1.19.1",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clock",
|
||||||
|
"version": "1.1.2",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sky_engine",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "material_color_utilities",
|
||||||
|
"version": "0.11.1",
|
||||||
|
"dependencies": [
|
||||||
|
"collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "characters",
|
||||||
|
"version": "1.4.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_testing",
|
||||||
|
"version": "3.0.2",
|
||||||
|
"dependencies": [
|
||||||
|
"leak_tracker",
|
||||||
|
"matcher",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker",
|
||||||
|
"version": "11.0.2",
|
||||||
|
"dependencies": [
|
||||||
|
"clock",
|
||||||
|
"collection",
|
||||||
|
"meta",
|
||||||
|
"path",
|
||||||
|
"vm_service"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lints",
|
||||||
|
"version": "5.1.1",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "term_glyph",
|
||||||
|
"version": "1.2.2",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "boolean_selector",
|
||||||
|
"version": "2.1.2",
|
||||||
|
"dependencies": [
|
||||||
|
"source_span",
|
||||||
|
"string_scanner"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "async",
|
||||||
|
"version": "2.13.1",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "string_scanner",
|
||||||
|
"version": "1.4.1",
|
||||||
|
"dependencies": [
|
||||||
|
"source_span"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "source_span",
|
||||||
|
"version": "1.10.2",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"path",
|
||||||
|
"term_glyph"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "plugin_platform_interface",
|
||||||
|
"version": "2.1.8",
|
||||||
|
"dependencies": [
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vm_service",
|
||||||
|
"version": "15.1.0",
|
||||||
|
"dependencies": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configVersion": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.35.4
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
group 'com.dalimaster.ble_relay_host'
|
||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.9.20'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.1.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace 'com.dalimaster.ble_relay_host'
|
||||||
|
compileSdk 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk 21
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
include ':ble_relay_host'
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
package com.dalimaster.ble_relay_host
|
||||||
|
|
||||||
|
import android.bluetooth.*
|
||||||
|
import android.bluetooth.le.AdvertiseCallback
|
||||||
|
import android.bluetooth.le.AdvertiseData
|
||||||
|
import android.bluetooth.le.AdvertiseSettings
|
||||||
|
import android.bluetooth.le.BluetoothLeAdvertiser
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.ParcelUuid
|
||||||
|
import android.util.Log
|
||||||
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
import io.flutter.plugin.common.MethodCall
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||||
|
import io.flutter.plugin.common.MethodChannel.Result
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
private const val TAG = "BleRelayHostPlugin"
|
||||||
|
private const val CHANNEL = "com.dalimaster.ble_relay_host"
|
||||||
|
private const val EVENT_CHANNEL = "com.dalimaster.ble_relay_host/clientWrites"
|
||||||
|
|
||||||
|
class BleRelayHostPlugin : FlutterPlugin, MethodCallHandler {
|
||||||
|
|
||||||
|
private lateinit var methodChannel: MethodChannel
|
||||||
|
private lateinit var eventChannel: EventChannel
|
||||||
|
private var eventSink: EventChannel.EventSink? = null
|
||||||
|
|
||||||
|
private var context: Context? = null
|
||||||
|
private var bluetoothManager: BluetoothManager? = null
|
||||||
|
private var bluetoothAdapter: BluetoothAdapter? = null
|
||||||
|
private var gattServer: BluetoothGattServer? = null
|
||||||
|
private var advertiser: BluetoothLeAdvertiser? = null
|
||||||
|
private var relayCharacteristic: BluetoothGattCharacteristic? = null
|
||||||
|
private val connectedDevices = mutableSetOf<BluetoothDevice>()
|
||||||
|
private var pendingStartResult: Result? = null
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// FlutterPlugin callbacks
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
|
context = binding.applicationContext
|
||||||
|
bluetoothManager =
|
||||||
|
binding.applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
|
||||||
|
bluetoothAdapter = bluetoothManager?.adapter
|
||||||
|
|
||||||
|
methodChannel = MethodChannel(binding.binaryMessenger, CHANNEL)
|
||||||
|
methodChannel.setMethodCallHandler(this)
|
||||||
|
|
||||||
|
eventChannel = EventChannel(binding.binaryMessenger, EVENT_CHANNEL)
|
||||||
|
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
|
||||||
|
override fun onListen(arguments: Any?, sink: EventChannel.EventSink?) {
|
||||||
|
eventSink = sink
|
||||||
|
}
|
||||||
|
override fun onCancel(arguments: Any?) {
|
||||||
|
eventSink = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
|
methodChannel.setMethodCallHandler(null)
|
||||||
|
stopInternal()
|
||||||
|
context = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// MethodCallHandler
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
override fun onMethodCall(call: MethodCall, result: Result) {
|
||||||
|
when (call.method) {
|
||||||
|
"start" -> {
|
||||||
|
val advertisedName = call.argument<String>("advertisedName") ?: "DALI-Bridge"
|
||||||
|
val serviceUuidStr = call.argument<String>("serviceUuid")
|
||||||
|
?: "0000fff0-0000-1000-8000-00805f9b34fb"
|
||||||
|
val charUuidStr = call.argument<String>("characteristicUuid")
|
||||||
|
?: "0000fff1-0000-1000-8000-00805f9b34fb"
|
||||||
|
startInternal(advertisedName, serviceUuidStr, charUuidStr, result)
|
||||||
|
}
|
||||||
|
"push" -> {
|
||||||
|
val data = call.argument<ByteArray>("data")
|
||||||
|
if (data != null) pushInternal(data)
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
"stop" -> {
|
||||||
|
pendingStartResult?.error("CANCELLED", "BLE relay host start cancelled", null)
|
||||||
|
pendingStartResult = null
|
||||||
|
stopInternal()
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
"isSupported" -> result.success(bluetoothAdapter != null && bluetoothAdapter!!.isMultipleAdvertisementSupported)
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// BLE internals
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private fun startInternal(
|
||||||
|
advertisedName: String,
|
||||||
|
serviceUuidStr: String,
|
||||||
|
charUuidStr: String,
|
||||||
|
result: Result,
|
||||||
|
) {
|
||||||
|
val ctx = context ?: return result.error("NO_CONTEXT", "No context", null)
|
||||||
|
val adapter = bluetoothAdapter
|
||||||
|
?: return result.error("BLE_UNAVAILABLE", "Bluetooth adapter not available", null)
|
||||||
|
|
||||||
|
if (!adapter.isMultipleAdvertisementSupported) {
|
||||||
|
return result.error("NOT_SUPPORTED", "BLE peripheral advertising not supported on this device", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingStartResult != null) {
|
||||||
|
return result.error("START_IN_PROGRESS", "BLE relay host start already in progress", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopInternal()
|
||||||
|
|
||||||
|
val serviceUuid = UUID.fromString(serviceUuidStr)
|
||||||
|
val charUuid = UUID.fromString(charUuidStr)
|
||||||
|
|
||||||
|
val characteristic = BluetoothGattCharacteristic(
|
||||||
|
charUuid,
|
||||||
|
BluetoothGattCharacteristic.PROPERTY_READ or
|
||||||
|
BluetoothGattCharacteristic.PROPERTY_WRITE or
|
||||||
|
BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE or
|
||||||
|
BluetoothGattCharacteristic.PROPERTY_NOTIFY,
|
||||||
|
BluetoothGattCharacteristic.PERMISSION_READ or
|
||||||
|
BluetoothGattCharacteristic.PERMISSION_WRITE,
|
||||||
|
)
|
||||||
|
// Client Characteristic Configuration Descriptor for notifications.
|
||||||
|
val cccd = BluetoothGattDescriptor(
|
||||||
|
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"),
|
||||||
|
BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE,
|
||||||
|
)
|
||||||
|
characteristic.addDescriptor(cccd)
|
||||||
|
relayCharacteristic = characteristic
|
||||||
|
|
||||||
|
val service = BluetoothGattService(serviceUuid, BluetoothGattService.SERVICE_TYPE_PRIMARY)
|
||||||
|
service.addCharacteristic(characteristic)
|
||||||
|
|
||||||
|
val server = bluetoothManager!!.openGattServer(ctx, gattServerCallback)
|
||||||
|
?: return result.error("GATT_SERVER_FAILED", "Unable to open GATT server", null)
|
||||||
|
server.addService(service)
|
||||||
|
gattServer = server
|
||||||
|
|
||||||
|
// Start advertising.
|
||||||
|
try {
|
||||||
|
adapter.name = advertisedName
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
Log.w(TAG, "Cannot set device name: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
advertiser = adapter.bluetoothLeAdvertiser
|
||||||
|
?: return result.error("ADVERTISER_UNAVAILABLE", "BLE advertiser not available", null)
|
||||||
|
val settings = AdvertiseSettings.Builder()
|
||||||
|
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
|
||||||
|
.setConnectable(true)
|
||||||
|
.setTimeout(0)
|
||||||
|
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
|
||||||
|
.build()
|
||||||
|
val data = AdvertiseData.Builder()
|
||||||
|
.setIncludeDeviceName(true)
|
||||||
|
.addServiceUuid(ParcelUuid(serviceUuid))
|
||||||
|
.build()
|
||||||
|
pendingStartResult = result
|
||||||
|
advertiser?.startAdvertising(settings, data, advertiseCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pushInternal(data: ByteArray) {
|
||||||
|
val server = gattServer ?: return
|
||||||
|
val char = relayCharacteristic ?: return
|
||||||
|
char.value = data
|
||||||
|
val devsCopy: Set<BluetoothDevice>
|
||||||
|
synchronized(connectedDevices) { devsCopy = connectedDevices.toSet() }
|
||||||
|
for (device in devsCopy) {
|
||||||
|
try {
|
||||||
|
server.notifyCharacteristicChanged(device, char, false)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "notifyCharacteristicChanged failed: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopInternal() {
|
||||||
|
advertiser?.stopAdvertising(advertiseCallback)
|
||||||
|
advertiser = null
|
||||||
|
synchronized(connectedDevices) { connectedDevices.clear() }
|
||||||
|
gattServer?.close()
|
||||||
|
gattServer = null
|
||||||
|
relayCharacteristic = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// GATT server callback
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private val gattServerCallback = object : BluetoothGattServerCallback() {
|
||||||
|
|
||||||
|
override fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
|
||||||
|
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
Log.d(TAG, "Client connected: ${device.address}")
|
||||||
|
synchronized(connectedDevices) { connectedDevices.add(device) }
|
||||||
|
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||||
|
Log.d(TAG, "Client disconnected: ${device.address}")
|
||||||
|
synchronized(connectedDevices) { connectedDevices.remove(device) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicWriteRequest(
|
||||||
|
device: BluetoothDevice,
|
||||||
|
requestId: Int,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
preparedWrite: Boolean,
|
||||||
|
responseNeeded: Boolean,
|
||||||
|
offset: Int,
|
||||||
|
value: ByteArray,
|
||||||
|
) {
|
||||||
|
if (responseNeeded) {
|
||||||
|
gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null)
|
||||||
|
}
|
||||||
|
// Forward the written frame to Flutter.
|
||||||
|
eventSink?.success(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDescriptorWriteRequest(
|
||||||
|
device: BluetoothDevice,
|
||||||
|
requestId: Int,
|
||||||
|
descriptor: BluetoothGattDescriptor,
|
||||||
|
preparedWrite: Boolean,
|
||||||
|
responseNeeded: Boolean,
|
||||||
|
offset: Int,
|
||||||
|
value: ByteArray,
|
||||||
|
) {
|
||||||
|
if (responseNeeded) {
|
||||||
|
gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicReadRequest(
|
||||||
|
device: BluetoothDevice,
|
||||||
|
requestId: Int,
|
||||||
|
offset: Int,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
) {
|
||||||
|
gattServer?.sendResponse(
|
||||||
|
device, requestId, BluetoothGatt.GATT_SUCCESS, 0,
|
||||||
|
characteristic.value ?: ByteArray(0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Advertise callback
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private val advertiseCallback = object : AdvertiseCallback() {
|
||||||
|
override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
|
||||||
|
Log.d(TAG, "BLE advertising started")
|
||||||
|
pendingStartResult?.success(null)
|
||||||
|
pendingStartResult = null
|
||||||
|
}
|
||||||
|
override fun onStartFailure(errorCode: Int) {
|
||||||
|
Log.e(TAG, "BLE advertising failed: $errorCode")
|
||||||
|
pendingStartResult?.error(
|
||||||
|
"ADVERTISE_FAILED",
|
||||||
|
"BLE advertising failed with code $errorCode",
|
||||||
|
errorCode,
|
||||||
|
)
|
||||||
|
pendingStartResult = null
|
||||||
|
stopInternal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import Foundation
|
||||||
|
import Flutter
|
||||||
|
|
||||||
|
/// iOS stub — BLE peripheral (CoreBluetooth CBPeripheralManager) is available on iOS but
|
||||||
|
/// not yet implemented. `isSupported` returns false; all other methods are no-ops.
|
||||||
|
public class BleRelayHostPlugin: NSObject, FlutterPlugin {
|
||||||
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||||
|
let channel = FlutterMethodChannel(
|
||||||
|
name: "com.dalimaster.ble_relay_host",
|
||||||
|
binaryMessenger: registrar.messenger())
|
||||||
|
let instance = BleRelayHostPlugin()
|
||||||
|
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
switch call.method {
|
||||||
|
case "isSupported":
|
||||||
|
result(false)
|
||||||
|
default:
|
||||||
|
result(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/// BLE GATT-server peripheral host adapter for the DALI Toolkit relay feature.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```dart
|
||||||
|
/// final host = BleRelayHost();
|
||||||
|
/// if (host.isSupported) {
|
||||||
|
/// await host.start(
|
||||||
|
/// advertisedName: 'DALI-Bridge',
|
||||||
|
/// onClientWrite: (data) async { /* forward to DALI upstream */ },
|
||||||
|
/// );
|
||||||
|
/// // Push DALI responses back to connected BLE clients:
|
||||||
|
/// await host.push(responseBytes);
|
||||||
|
/// await host.stop();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
library ble_relay_host;
|
||||||
|
|
||||||
|
export 'src/ble_relay_host.dart';
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
/// GATT service UUID shared between DALI Toolkit BLE central (client) and the
|
||||||
|
/// relay host peripheral. Must match the UUID used in `ble.dart` / `ble_web.dart`.
|
||||||
|
const String kBleRelayServiceUuid = '0000fff0-0000-1000-8000-00805f9b34fb';
|
||||||
|
|
||||||
|
/// GATT characteristic UUID for bidirectional DALI frame data.
|
||||||
|
const String kBleRelayCharacteristicUuid = '0000fff1-0000-1000-8000-00805f9b34fb';
|
||||||
|
|
||||||
|
/// Method-channel name (must match Android/iOS/macOS native implementations).
|
||||||
|
const String _kChannelName = 'com.dalimaster.ble_relay_host';
|
||||||
|
|
||||||
|
/// An abstraction over the platform BLE GATT-server implementation.
|
||||||
|
///
|
||||||
|
/// On Android (API 21+) this wires into [BluetoothGattServer] + [BluetoothLeAdvertiser].
|
||||||
|
/// On all other platforms, [isSupported] returns `false` and every method is a no-op.
|
||||||
|
class BleRelayHost {
|
||||||
|
BleRelayHost() : _channel = const MethodChannel(_kChannelName);
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
BleRelayHost.withChannel(MethodChannel channel) : _channel = channel;
|
||||||
|
|
||||||
|
final MethodChannel _channel;
|
||||||
|
|
||||||
|
StreamSubscription<dynamic>? _eventSub;
|
||||||
|
Future<void> Function(Uint8List data)? _onClientWrite;
|
||||||
|
bool _running = false;
|
||||||
|
|
||||||
|
bool get isRunning => _running;
|
||||||
|
|
||||||
|
/// Returns true if the current platform has a native implementation.
|
||||||
|
/// On Web this is always false; on non-Android platforms this calls the
|
||||||
|
/// native `isSupported` method to let the platform decide.
|
||||||
|
bool get isSupported {
|
||||||
|
if (kIsWeb) return false;
|
||||||
|
// Synchronous check is not possible via MethodChannel; instead we rely on
|
||||||
|
// the static per-platform capability flags defined at the package root.
|
||||||
|
// See [BleRelayHostCapability.isSupported].
|
||||||
|
return defaultTargetPlatform == TargetPlatform.android;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts BLE advertising and opens the GATT server.
|
||||||
|
///
|
||||||
|
/// [advertisedName] — the Bluetooth device name visible to scanning clients.
|
||||||
|
/// [onClientWrite] — callback invoked whenever a connected client writes a
|
||||||
|
/// frame to the relay characteristic. Forward the bytes to
|
||||||
|
/// the upstream DALI transport.
|
||||||
|
Future<void> start({
|
||||||
|
required String advertisedName,
|
||||||
|
required Future<void> Function(Uint8List data) onClientWrite,
|
||||||
|
}) async {
|
||||||
|
if (!isSupported) return;
|
||||||
|
_onClientWrite = onClientWrite;
|
||||||
|
|
||||||
|
// Listen for inbound writes from BLE clients via event channel.
|
||||||
|
const eventChannel = EventChannel('$_kChannelName/clientWrites');
|
||||||
|
_eventSub?.cancel();
|
||||||
|
_eventSub = eventChannel.receiveBroadcastStream().listen((dynamic event) {
|
||||||
|
if (event is Uint8List) {
|
||||||
|
_onClientWrite?.call(event);
|
||||||
|
} else if (event is List) {
|
||||||
|
_onClientWrite?.call(Uint8List.fromList(event.cast<int>()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _channel.invokeMethod<void>('start', {
|
||||||
|
'advertisedName': advertisedName,
|
||||||
|
'serviceUuid': kBleRelayServiceUuid,
|
||||||
|
'characteristicUuid': kBleRelayCharacteristicUuid,
|
||||||
|
});
|
||||||
|
_running = true;
|
||||||
|
} catch (_) {
|
||||||
|
await _eventSub?.cancel();
|
||||||
|
_eventSub = null;
|
||||||
|
_onClientWrite = null;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends [data] as a GATT notification to all connected clients.
|
||||||
|
Future<void> push(Uint8List data) async {
|
||||||
|
if (!_running) return;
|
||||||
|
await _channel.invokeMethod<void>('push', {'data': data});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops advertising, disconnects clients, and closes the GATT server.
|
||||||
|
Future<void> stop() async {
|
||||||
|
await _eventSub?.cancel();
|
||||||
|
_eventSub = null;
|
||||||
|
_onClientWrite = null;
|
||||||
|
if (_running) {
|
||||||
|
await _channel.invokeMethod<void>('stop');
|
||||||
|
}
|
||||||
|
_running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
set(PROJECT_NAME "ble_relay_host")
|
||||||
|
project(${PROJECT_NAME} LANGUAGES CXX)
|
||||||
|
|
||||||
|
cmake_policy(VERSION 3.10...3.24)
|
||||||
|
|
||||||
|
set(PLUGIN_NAME "ble_relay_host_plugin")
|
||||||
|
|
||||||
|
list(APPEND PLUGIN_SOURCES
|
||||||
|
"ble_relay_host_plugin.cc"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(${PLUGIN_NAME} SHARED
|
||||||
|
${PLUGIN_SOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
|
apply_standard_settings(${PLUGIN_NAME})
|
||||||
|
set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden)
|
||||||
|
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
|
||||||
|
target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter)
|
||||||
|
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)
|
||||||
|
|
||||||
|
set(ble_relay_host_bundled_libraries
|
||||||
|
""
|
||||||
|
PARENT_SCOPE
|
||||||
|
)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#include "ble_relay_host_plugin.h"
|
||||||
|
#include <flutter_linux/flutter_linux.h>
|
||||||
|
|
||||||
|
#define BLE_RELAY_HOST_PLUGIN(obj) \
|
||||||
|
(G_TYPE_CHECK_INSTANCE_CAST((obj), ble_relay_host_plugin_get_type(), BleRelayHostPlugin))
|
||||||
|
|
||||||
|
struct _BleRelayHostPlugin {
|
||||||
|
GObject parent_instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE(BleRelayHostPlugin, ble_relay_host_plugin, g_object_get_type())
|
||||||
|
|
||||||
|
static void ble_relay_host_plugin_handle_method_call(
|
||||||
|
BleRelayHostPlugin* self,
|
||||||
|
FlMethodCall* method_call) {
|
||||||
|
g_autoptr(FlMethodResponse) response = nullptr;
|
||||||
|
const gchar* method = fl_method_call_get_name(method_call);
|
||||||
|
if (strcmp(method, "isSupported") == 0) {
|
||||||
|
g_autoptr(FlValue) result = fl_value_new_bool(FALSE);
|
||||||
|
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
|
||||||
|
} else {
|
||||||
|
response = FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_null()));
|
||||||
|
}
|
||||||
|
fl_method_call_respond(method_call, response, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
|
||||||
|
gpointer user_data) {
|
||||||
|
BleRelayHostPlugin* plugin = BLE_RELAY_HOST_PLUGIN(user_data);
|
||||||
|
ble_relay_host_plugin_handle_method_call(plugin, method_call);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ble_relay_host_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
|
||||||
|
BleRelayHostPlugin* plugin = BLE_RELAY_HOST_PLUGIN(
|
||||||
|
g_object_new(ble_relay_host_plugin_get_type(), nullptr));
|
||||||
|
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
|
||||||
|
g_autoptr(FlMethodChannel) channel =
|
||||||
|
fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
|
||||||
|
"com.dalimaster.ble_relay_host",
|
||||||
|
FL_METHOD_CODEC(codec));
|
||||||
|
fl_method_channel_set_method_call_handler(channel, method_call_cb,
|
||||||
|
g_object_ref(plugin),
|
||||||
|
g_object_unref);
|
||||||
|
g_object_unref(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ble_relay_host_plugin_class_init(BleRelayHostPluginClass* klass) {}
|
||||||
|
static void ble_relay_host_plugin_init(BleRelayHostPlugin* self) {}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef FLUTTER_PLUGIN_BLE_RELAY_HOST_PLUGIN_H_
|
||||||
|
#define FLUTTER_PLUGIN_BLE_RELAY_HOST_PLUGIN_H_
|
||||||
|
|
||||||
|
#include <flutter_linux/flutter_linux.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define BLE_RELAY_HOST_PLUGIN_TYPE (ble_relay_host_plugin_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE(BleRelayHostPlugin, ble_relay_host_plugin, BLE_RELAY_HOST, PLUGIN, GObject)
|
||||||
|
|
||||||
|
void ble_relay_host_plugin_register_with_registrar(FlPluginRegistrar* registrar);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif // FLUTTER_PLUGIN_BLE_RELAY_HOST_PLUGIN_H_
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
/// macOS stub — BLE peripheral mode not yet implemented.
|
||||||
|
public class BleRelayHostPlugin: NSObject, FlutterPlugin {
|
||||||
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||||
|
let channel = FlutterMethodChannel(
|
||||||
|
name: "com.dalimaster.ble_relay_host",
|
||||||
|
binaryMessenger: registrar.messenger)
|
||||||
|
let instance = BleRelayHostPlugin()
|
||||||
|
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
switch call.method {
|
||||||
|
case "isSupported":
|
||||||
|
result(false)
|
||||||
|
default:
|
||||||
|
result(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+213
@@ -0,0 +1,213 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.1"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.3"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.0"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.0.2"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.10"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.1"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.17"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.11.1"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.16.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.2"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.6"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "046d3928e16fa4dc46e8350415661755ab759d9fc97fc21b5ab295f71e4f0499"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.1.0"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.8.0-0 <4.0.0"
|
||||||
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: ble_relay_host
|
||||||
|
description: "BLE GATT-server peripheral host adapter for the DALI Toolkit relay feature. Provides a simple start/stop/push API backed by platform-specific BLE advertising."
|
||||||
|
version: 0.1.0
|
||||||
|
publish_to: none
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=3.1.4 <4.0.0'
|
||||||
|
flutter: '>=3.3.0'
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
plugin_platform_interface: ^2.0.2
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_lints: ^5.0.0
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
plugin:
|
||||||
|
platforms:
|
||||||
|
android:
|
||||||
|
package: com.dalimaster.ble_relay_host
|
||||||
|
pluginClass: BleRelayHostPlugin
|
||||||
|
ios:
|
||||||
|
pluginClass: BleRelayHostPlugin
|
||||||
|
macos:
|
||||||
|
pluginClass: BleRelayHostPlugin
|
||||||
|
linux:
|
||||||
|
pluginClass: BleRelayHostPlugin
|
||||||
|
windows:
|
||||||
|
pluginClass: BleRelayHostPluginCApi
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
set(PROJECT_NAME "ble_relay_host")
|
||||||
|
project(${PROJECT_NAME} LANGUAGES CXX)
|
||||||
|
|
||||||
|
cmake_policy(VERSION 3.14...3.25)
|
||||||
|
|
||||||
|
set(PLUGIN_NAME "ble_relay_host_plugin")
|
||||||
|
|
||||||
|
list(APPEND PLUGIN_SOURCES
|
||||||
|
"ble_relay_host_plugin.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(${PLUGIN_NAME} SHARED
|
||||||
|
"include/ble_relay_host/ble_relay_host_plugin_c_api.h"
|
||||||
|
${PLUGIN_SOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
|
apply_standard_settings(${PLUGIN_NAME})
|
||||||
|
set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden)
|
||||||
|
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
|
||||||
|
target_include_directories(${PLUGIN_NAME} INTERFACE
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
|
||||||
|
|
||||||
|
set(ble_relay_host_bundled_libraries
|
||||||
|
""
|
||||||
|
PARENT_SCOPE
|
||||||
|
)
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#include "ble_relay_host_plugin.h"
|
||||||
|
|
||||||
|
// Windows stub — BLE peripheral not yet implemented.
|
||||||
|
|
||||||
|
#include <flutter/method_channel.h>
|
||||||
|
#include <flutter/plugin_registrar_windows.h>
|
||||||
|
#include <flutter/standard_method_codec.h>
|
||||||
|
|
||||||
|
namespace ble_relay_host {
|
||||||
|
|
||||||
|
class BleRelayHostPlugin : public flutter::Plugin {
|
||||||
|
public:
|
||||||
|
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar) {
|
||||||
|
auto channel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
|
||||||
|
registrar->messenger(), "com.dalimaster.ble_relay_host",
|
||||||
|
&flutter::StandardMethodCodec::GetInstance());
|
||||||
|
auto plugin = std::make_unique<BleRelayHostPlugin>();
|
||||||
|
channel->SetMethodCallHandler(
|
||||||
|
[plugin_pointer = plugin.get()](const auto& call, auto result) {
|
||||||
|
plugin_pointer->HandleMethodCall(call, std::move(result));
|
||||||
|
});
|
||||||
|
registrar->AddPlugin(std::move(plugin));
|
||||||
|
}
|
||||||
|
|
||||||
|
BleRelayHostPlugin() {}
|
||||||
|
virtual ~BleRelayHostPlugin() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void HandleMethodCall(
|
||||||
|
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
||||||
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||||
|
if (method_call.method_name().compare("isSupported") == 0) {
|
||||||
|
result->Success(flutter::EncodableValue(false));
|
||||||
|
} else {
|
||||||
|
result->Success();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ble_relay_host
|
||||||
|
|
||||||
|
void BleRelayHostPluginCApiRegisterWithRegistrar(
|
||||||
|
FlutterDesktopPluginRegistrarRef registrar) {
|
||||||
|
ble_relay_host::BleRelayHostPlugin::RegisterWithRegistrar(
|
||||||
|
flutter::PluginRegistrarManager::GetInstance()
|
||||||
|
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef FLUTTER_PLUGIN_BLE_RELAY_HOST_PLUGIN_H_
|
||||||
|
#define FLUTTER_PLUGIN_BLE_RELAY_HOST_PLUGIN_H_
|
||||||
|
|
||||||
|
#include <flutter/plugin_registrar_windows.h>
|
||||||
|
|
||||||
|
#ifdef FLUTTER_PLUGIN_IMPL
|
||||||
|
#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
FLUTTER_PLUGIN_EXPORT void BleRelayHostPluginCApiRegisterWithRegistrar(
|
||||||
|
FlutterDesktopPluginRegistrarRef registrar);
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
} // extern "C"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // FLUTTER_PLUGIN_BLE_RELAY_HOST_PLUGIN_H_
|
||||||
Reference in New Issue
Block a user