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