6.0.0 (#74)
* 调整接口 * 临时提交 * 重构 Android 平台代码 * 临时提交 * 临时提交 * Android 6.0.0-dev.0 * 临时提交 * 实现 Windows 接口 * windows-6.0.0-dev.0 * Darwin 6.0.0-dev.0 * 临时提交 * 1 * 临时提交 * 调整接口 * windows-6.0.0-dev.1 * 临时提交 * interface-6.0.0-dev.7 * interface-6.0.0-dev.8 * 临时提交 * windows-6.0.0-dev.2 * 删除多余脚本 * interface-6.0.0-dev.9 * 临时提交 * 临时提交 * interface-6.0.0-dev.10 * android-6.0.0-dev.1 * windows-6.0.0-dev.3 * 临时提交 * interface-6.0.0-dev.11 * windows-6.0.0-dev.4 * 更新 pubspec.lock * 1 * interface-6.0.0-dev.12 * interface-6.0.0-dev.13 * interface-6.0.0-dev.14 * 临时提交 * interface-6.0.0-dev.15 * 临时提交 * interface-6.0.0-dev.16 * android-6.0.0-dev.2 * 临时提交 * windows-6.0.0-dev.5 * 临时提交 * 临时提交 * windows-6.0.0-dev.6 * 优化注释和代码样式 * 优化代码 * 临时提交 * 实现 Dart 接口 * darwin-6.0.0-dev.0 * linux-6.0.0-dev.0 * 修复已知问题 * 修复问题 * 6.0.0-dev.0 * 修改包名 * 更新版本 * 移除原生部分 * 临时提交 * 修复问题 * 更新 pigeon 19.0.0 * 更新 README,添加迁移文档 * linux-6.0.0-dev.1 * 解析扫描回复和扩展广播 * 修复 googletest 版本警告问题 * Use centralArgs instead of addressArgs * interface-6.0.0-dev.18 * android-6.0.0-dev.4 * linux-6.0.0-dev.2 * windows-6.0.0-dev.8 * darwin-6.0.0-dev.2 * 6.0.0-dev.1 * Update LICENSE * clang-format * Combine ADV_IND and SCAN_RES * TEMP commit: update exampe * Adjust advertisement combine logic * Implement `MyPeripheralMananger` on Windows * Added NuGet auto download and scan for names on peripheral (#67) * fetch nuget using other technique * move FetchContent to right location in CMakeLists.txt * also added hash for googletest --------- Co-authored-by: Kevin De Keyser <kevin@dekeyser.ch> * Fix errors. * Check BluetoothAdapter role supported state and implement PeripheralManager on Flutter side. * Sort code * Fix known errors * interface-6.0.0-dev.19 * windows-6.0.0-dev.9 * Optimize example * android-6.0.0-dev.5 * Optimize the Adverrtisement BottomSheet. * linux-6.0.0-dev.3 * Update dependency * Fix example errors. * Temp commit. * darwin-6.0.0-dev.3 * 6.0.0-dev.2 * Update README.md * 6.0.0 * darwin-6.0.0-dev.4 * android-6.0.0-dev.6 * 6.0.0-dev.3 * Update docs. * interface-6.0.0 * android-6.0.0 * darwin-6.0.0 * linux-6.0.0 * windows-6.0.0 * 6.0.0 * Update dependency --------- Co-authored-by: Kevin De Keyser <dekeyser.kevin97@gmail.com> Co-authored-by: Kevin De Keyser <kevin@dekeyser.ch>
This commit is contained in:
1
bluetooth_low_energy_android/.gitignore
vendored
1
bluetooth_low_energy_android/.gitignore
vendored
@ -26,5 +26,4 @@ migrate_working_dir/
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
@ -4,7 +4,7 @@
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "efbf63d9c66b9f6ec30e9ad4611189aa80003d31"
|
||||
revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1"
|
||||
channel: "stable"
|
||||
|
||||
project_type: plugin
|
||||
@ -13,11 +13,11 @@ project_type: plugin
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
- platform: android
|
||||
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||
|
||||
# User provided section
|
||||
|
||||
|
@ -1,3 +1,60 @@
|
||||
## 6.0.0
|
||||
|
||||
* Add `serviceUUIDs` argument to `CentralManager#startDiscovery` method.
|
||||
* Add `CentralManager#requestMTU` method.
|
||||
* Add `CentralManager#retrieveConnectedPeripherals` method.
|
||||
* Move `BluetoothLowEnergyManager#getState` to `BluetoothLowEnergyManager#state`.
|
||||
* Move `CentralManger.instance` to factory constructor.
|
||||
* Move `PeripheralManager.instance` to factory constructor.
|
||||
* Remove `BluetoothLowEnergyManager#setUp` method.
|
||||
* Add `CentralManager#mtuChanged` event.
|
||||
* Add `PeripheralManager#mtuChanged` event.
|
||||
* Add `BluetoothLowEnergyManager.authorize()` method.
|
||||
* Add `BluetoothLowEnergyManager.showAppSettings()` method.
|
||||
* Rewrite example with MVVM.
|
||||
* Fix known issues.
|
||||
|
||||
## 6.0.0-dev.6
|
||||
|
||||
* Fix known issues.
|
||||
|
||||
## 6.0.0-dev.5
|
||||
|
||||
* Rewrite example with MVVM.
|
||||
* Fix known issues.
|
||||
|
||||
## 6.0.0-dev.4
|
||||
|
||||
* Implement new APIs.
|
||||
|
||||
## 6.0.0-dev.3
|
||||
|
||||
* Move organizaioin.
|
||||
|
||||
## 6.0.0-dev.2
|
||||
|
||||
* Implement new APIs.
|
||||
|
||||
## 6.0.0-dev.1
|
||||
|
||||
* Add `CentralManager#mtuChanged` event.
|
||||
* Add `PeripheralManager#mtuChanged` event.
|
||||
* Add `BluetoothLowEnergyManager.authorize()` method.
|
||||
* Add `BluetoothLowEnergyManager.showAppSettings()` method.
|
||||
* Add modifiers to all classes.
|
||||
* Use new capitalization rules.
|
||||
* Make it possible to change the `logLevel` before `initialize()`.
|
||||
|
||||
## 6.0.0-dev.0
|
||||
|
||||
* Add `serviceUUIDs` argument to `CentralManager#startDiscovery` method.
|
||||
* Add `CentralManager#requestMTU` method.
|
||||
* Add `CentralManager#retrieveConnectedPeripherals` method.
|
||||
* Move `BluetoothLowEnergyManager#getState` to `BluetoothLowEnergyManager#state`.
|
||||
* Move `CentralManger.instance` to factory constructor.
|
||||
* Move `PeripheralManager.instance` to factory constructor.
|
||||
* Remove `BluetoothLowEnergyManager#setUp` method.
|
||||
|
||||
## 5.0.5
|
||||
|
||||
* Fix the issue that [Advertisement resolve failed with `NullPointerException`](https://github.com/yanshouwang/bluetooth_low_energy/issues/59)
|
||||
|
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 yanshouwang
|
||||
Copyright (c) 2024 hebei.dev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,16 +1,16 @@
|
||||
group 'dev.yanshouwang.bluetooth_low_energy_android'
|
||||
version '1.0-SNAPSHOT'
|
||||
group = "dev.hebei.bluetooth_low_energy_android"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
ext.kotlin_version = "1.7.10"
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath("com.android.tools.build:gradle:7.3.0")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,37 +21,37 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: "com.android.library"
|
||||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
if (project.android.hasProperty("namespace")) {
|
||||
namespace 'dev.yanshouwang.bluetooth_low_energy_android'
|
||||
namespace = "dev.hebei.bluetooth_low_energy_android"
|
||||
}
|
||||
|
||||
compileSdkVersion 33
|
||||
compileSdk = 34
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
test.java.srcDirs += 'src/test/kotlin'
|
||||
main.java.srcDirs += "src/main/kotlin"
|
||||
test.java.srcDirs += "src/test/kotlin"
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'org.jetbrains.kotlin:kotlin-test'
|
||||
testImplementation 'org.mockito:mockito-core:5.0.0'
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.mockito:mockito-core:5.0.0")
|
||||
}
|
||||
|
||||
testOptions {
|
||||
|
@ -1,15 +1,15 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="dev.yanshouwang.bluetooth_low_energy_android">
|
||||
package="dev.hebei.bluetooth_low_energy_android">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
</manifest>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
</manifest>
|
||||
|
@ -1,11 +1,11 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
|
||||
/** BluetoothLowEnergyAndroid */
|
||||
class BluetoothLowEnergyAndroid : FlutterPlugin, ActivityAware {
|
||||
/** BluetoothLowEnergyAndroidPlugin */
|
||||
class BluetoothLowEnergyAndroidPlugin : FlutterPlugin, ActivityAware {
|
||||
private lateinit var mCentralManager: MyCentralManager
|
||||
private lateinit var mPeripheralManager: MyPeripheralManager
|
||||
|
||||
@ -14,14 +14,14 @@ class BluetoothLowEnergyAndroid : FlutterPlugin, ActivityAware {
|
||||
val binaryMessenger = binding.binaryMessenger
|
||||
mCentralManager = MyCentralManager(context, binaryMessenger)
|
||||
mPeripheralManager = MyPeripheralManager(context, binaryMessenger)
|
||||
MyCentralManagerHostApi.setUp(binaryMessenger, mCentralManager)
|
||||
MyPeripheralManagerHostApi.setUp(binaryMessenger, mPeripheralManager)
|
||||
MyCentralManagerHostAPI.setUp(binaryMessenger, mCentralManager)
|
||||
MyPeripheralManagerHostAPI.setUp(binaryMessenger, mPeripheralManager)
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
val binaryMessenger = binding.binaryMessenger
|
||||
MyCentralManagerHostApi.setUp(binaryMessenger, null)
|
||||
MyPeripheralManagerHostApi.setUp(binaryMessenger, null)
|
||||
MyCentralManagerHostAPI.setUp(binaryMessenger, null)
|
||||
MyPeripheralManagerHostAPI.setUp(binaryMessenger, null)
|
||||
}
|
||||
|
||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,258 @@
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattDescriptor
|
||||
import android.bluetooth.BluetoothGattService
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.bluetooth.le.AdvertiseData
|
||||
import android.bluetooth.le.ScanResult
|
||||
import android.os.Build
|
||||
import android.os.ParcelUuid
|
||||
import android.util.SparseArray
|
||||
import java.util.UUID
|
||||
|
||||
//region ToObject
|
||||
fun MyGATTCharacteristicWriteTypeArgs.toType(): Int {
|
||||
return when (this) {
|
||||
MyGATTCharacteristicWriteTypeArgs.WITH_RESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
||||
MyGATTCharacteristicWriteTypeArgs.WITHOUT_RESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||
}
|
||||
}
|
||||
|
||||
fun MyGATTStatusArgs.toStatus(): Int {
|
||||
return when (this) {
|
||||
MyGATTStatusArgs.SUCCESS -> BluetoothGatt.GATT_SUCCESS
|
||||
MyGATTStatusArgs.READ_NOT_PERMITTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
|
||||
MyGATTStatusArgs.WRITE_NOT_PERMITTED -> BluetoothGatt.GATT_WRITE_NOT_PERMITTED
|
||||
MyGATTStatusArgs.INSUFFICIENT_AUTHENTICATION -> BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION
|
||||
MyGATTStatusArgs.REQUEST_NOT_SUPPORTED -> BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED
|
||||
MyGATTStatusArgs.INSUFFICIENT_ENCRYPTION -> BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION
|
||||
MyGATTStatusArgs.INVALID_OFFSET -> BluetoothGatt.GATT_INVALID_OFFSET
|
||||
MyGATTStatusArgs.INSUFFICIENT_AUTHORIZATION -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION
|
||||
else BluetoothGatt.GATT_FAILURE
|
||||
MyGATTStatusArgs.INVALID_ATTRIBUTE_LENGTH -> BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH
|
||||
MyGATTStatusArgs.CONNECTION_CONGESTED -> BluetoothGatt.GATT_CONNECTION_CONGESTED
|
||||
MyGATTStatusArgs.FAILURE -> BluetoothGatt.GATT_FAILURE
|
||||
}
|
||||
}
|
||||
|
||||
fun MyAdvertisementArgs.toAdvertiseData(): AdvertiseData {
|
||||
val advertiseDataBuilder = AdvertiseData.Builder()
|
||||
for (serviceUuidArgs in serviceUUIDsArgs) {
|
||||
val serviceUUID = ParcelUuid.fromString(serviceUuidArgs)
|
||||
advertiseDataBuilder.addServiceUuid(serviceUUID)
|
||||
}
|
||||
for (entry in serviceDataArgs) {
|
||||
val serviceDataUUID = ParcelUuid.fromString(entry.key as String)
|
||||
val serviceData = entry.value as ByteArray
|
||||
advertiseDataBuilder.addServiceData(serviceDataUUID, serviceData)
|
||||
}
|
||||
for (args in manufacturerSpecificDataArgs) {
|
||||
val itemArgs = args as MyManufacturerSpecificDataArgs
|
||||
val manufacturerId = itemArgs.idArgs.toInt()
|
||||
val manufacturerSpecificData = itemArgs.dataArgs
|
||||
advertiseDataBuilder.addManufacturerData(manufacturerId, manufacturerSpecificData)
|
||||
}
|
||||
return advertiseDataBuilder.build()
|
||||
}
|
||||
|
||||
fun MyAdvertisementArgs.toScanResponse(): AdvertiseData {
|
||||
val advertiseDataBuilder = AdvertiseData.Builder()
|
||||
val includeDeviceName = nameArgs != null
|
||||
advertiseDataBuilder.setIncludeDeviceName(includeDeviceName)
|
||||
return advertiseDataBuilder.build()
|
||||
}
|
||||
|
||||
fun MyMutableGATTDescriptorArgs.toDescriptor(): BluetoothGattDescriptor {
|
||||
val uuid = UUID.fromString(uuidArgs)
|
||||
val permissions = BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE
|
||||
return BluetoothGattDescriptor(uuid, permissions)
|
||||
}
|
||||
|
||||
fun MyMutableGATTCharacteristicArgs.toCharacteristic(): BluetoothGattCharacteristic {
|
||||
val uuid = UUID.fromString(uuidArgs)
|
||||
val properties = getProperties()
|
||||
val permissions = getPermissions()
|
||||
return BluetoothGattCharacteristic(uuid, properties, permissions)
|
||||
}
|
||||
|
||||
fun MyMutableGATTCharacteristicArgs.getProperties(): Int {
|
||||
val propertiesArgs = propertyNumbersArgs.requireNoNulls().map { args ->
|
||||
val raw = args.toInt()
|
||||
MyGATTCharacteristicPropertyArgs.ofRaw(raw)
|
||||
}
|
||||
val read = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.READ)
|
||||
val write = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.WRITE)
|
||||
val writeWithoutResponse = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.WRITE_WITHOUT_RESPONSE)
|
||||
val notify = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.NOTIFY)
|
||||
val indicate = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.INDICATE)
|
||||
var properties = 0
|
||||
if (read) properties = properties or BluetoothGattCharacteristic.PROPERTY_READ
|
||||
if (write) properties = properties or BluetoothGattCharacteristic.PROPERTY_WRITE
|
||||
if (writeWithoutResponse) properties = properties or BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE
|
||||
if (notify) properties = properties or BluetoothGattCharacteristic.PROPERTY_NOTIFY
|
||||
if (indicate) properties = properties or BluetoothGattCharacteristic.PROPERTY_INDICATE
|
||||
return properties
|
||||
}
|
||||
|
||||
fun MyMutableGATTCharacteristicArgs.getPermissions(): Int {
|
||||
val propertiesArgs = propertyNumbersArgs.requireNoNulls().map { args ->
|
||||
val raw = args.toInt()
|
||||
MyGATTCharacteristicPropertyArgs.ofRaw(raw)
|
||||
}
|
||||
val read = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.READ)
|
||||
val write = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.WRITE)
|
||||
val writeWithoutResponse = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.WRITE_WITHOUT_RESPONSE)
|
||||
var permissions = 0
|
||||
if (read) permissions = permissions or BluetoothGattCharacteristic.PERMISSION_READ
|
||||
if (write || writeWithoutResponse) permissions = permissions or BluetoothGattCharacteristic.PERMISSION_WRITE
|
||||
return permissions
|
||||
}
|
||||
|
||||
fun MyMutableGATTServiceArgs.toService(): BluetoothGattService {
|
||||
val uuid = UUID.fromString(uuidArgs)
|
||||
val serviceType = if (isPrimaryArgs) BluetoothGattService.SERVICE_TYPE_PRIMARY
|
||||
else BluetoothGattService.SERVICE_TYPE_SECONDARY
|
||||
return BluetoothGattService(uuid, serviceType)
|
||||
} //endregion
|
||||
|
||||
//region ToArgs
|
||||
fun Int.toBluetoothLowEnergyStateArgs(): MyBluetoothLowEnergyStateArgs {
|
||||
return when (this) {
|
||||
BluetoothAdapter.STATE_OFF -> MyBluetoothLowEnergyStateArgs.OFF
|
||||
BluetoothAdapter.STATE_TURNING_ON -> MyBluetoothLowEnergyStateArgs.TURNING_ON
|
||||
BluetoothAdapter.STATE_ON -> MyBluetoothLowEnergyStateArgs.ON
|
||||
BluetoothAdapter.STATE_TURNING_OFF -> MyBluetoothLowEnergyStateArgs.TURNING_OFF
|
||||
else -> MyBluetoothLowEnergyStateArgs.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.toConnectionStateArgs(): MyConnectionStateArgs {
|
||||
return when (this) {
|
||||
BluetoothProfile.STATE_DISCONNECTED -> MyConnectionStateArgs.DISCONNECTED
|
||||
BluetoothProfile.STATE_CONNECTING -> MyConnectionStateArgs.CONNECTING
|
||||
BluetoothProfile.STATE_CONNECTED -> MyConnectionStateArgs.CONNECTED
|
||||
BluetoothProfile.STATE_DISCONNECTING -> MyConnectionStateArgs.DISCONNECTING
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
fun SparseArray<ByteArray>.toManufacturerSpecificDataArgs(): List<MyManufacturerSpecificDataArgs> {
|
||||
var index = 0
|
||||
val size = size()
|
||||
val itemsArgs = mutableListOf<MyManufacturerSpecificDataArgs>()
|
||||
while (index < size) {
|
||||
val idArgs = keyAt(index).toLong()
|
||||
val dataArgs = valueAt(index)
|
||||
val itemArgs = MyManufacturerSpecificDataArgs(idArgs, dataArgs)
|
||||
itemsArgs.add(itemArgs)
|
||||
index++
|
||||
}
|
||||
return itemsArgs
|
||||
}
|
||||
|
||||
fun ScanResult.toAdvertisementArgs(): MyAdvertisementArgs {
|
||||
val record = scanRecord
|
||||
return if (record == null) {
|
||||
val nameArgs = null
|
||||
val serviceUUIDsArgs = emptyList<String?>()
|
||||
val serviceDataArgs = emptyMap<String?, ByteArray>()
|
||||
val manufacturerSpecificDataArgs = emptyList<MyManufacturerSpecificDataArgs>()
|
||||
MyAdvertisementArgs(nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs)
|
||||
} else {
|
||||
val nameArgs = record.deviceName
|
||||
val serviceUUIDsArgs = record.serviceUuids?.map { uuid -> uuid.toString() } ?: emptyList()
|
||||
val pairs = record.serviceData?.map { (uuid, value) ->
|
||||
val key = uuid.toString()
|
||||
return@map Pair(key, value)
|
||||
}?.toTypedArray() ?: emptyArray()
|
||||
val serviceDataArgs = mapOf<String?, ByteArray?>(*pairs)
|
||||
val manufacturerSpecificDataArgs = record.manufacturerSpecificData?.toManufacturerSpecificDataArgs() ?: emptyList()
|
||||
MyAdvertisementArgs(nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs)
|
||||
}
|
||||
}
|
||||
|
||||
fun BluetoothDevice.toCentralArgs(): MyCentralArgs {
|
||||
val addressArgs = address
|
||||
return MyCentralArgs(addressArgs)
|
||||
}
|
||||
|
||||
fun BluetoothDevice.toPeripheralArgs(): MyPeripheralArgs {
|
||||
val addressArgs = address
|
||||
return MyPeripheralArgs(addressArgs)
|
||||
}
|
||||
|
||||
fun BluetoothGattDescriptor.toArgs(): MyGATTDescriptorArgs {
|
||||
val hashCodeArgs = hashCode().toLong()
|
||||
val uuidArgs = this.uuid.toString()
|
||||
return MyGATTDescriptorArgs(hashCodeArgs, uuidArgs)
|
||||
}
|
||||
|
||||
fun BluetoothGattCharacteristic.getPropertyNumbersArgs(): List<Long> {
|
||||
val numbersArgs = mutableListOf<Long>()
|
||||
if (properties and BluetoothGattCharacteristic.PROPERTY_READ != 0) {
|
||||
val number = MyGATTCharacteristicPropertyArgs.READ.raw.toLong()
|
||||
numbersArgs.add(number)
|
||||
}
|
||||
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) {
|
||||
val number = MyGATTCharacteristicPropertyArgs.WRITE.raw.toLong()
|
||||
numbersArgs.add(number)
|
||||
}
|
||||
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) {
|
||||
val number = MyGATTCharacteristicPropertyArgs.WRITE_WITHOUT_RESPONSE.raw.toLong()
|
||||
numbersArgs.add(number)
|
||||
}
|
||||
if (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {
|
||||
val number = MyGATTCharacteristicPropertyArgs.NOTIFY.raw.toLong()
|
||||
numbersArgs.add(number)
|
||||
}
|
||||
if (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) {
|
||||
val number = MyGATTCharacteristicPropertyArgs.INDICATE.raw.toLong()
|
||||
numbersArgs.add(number)
|
||||
}
|
||||
return numbersArgs
|
||||
}
|
||||
|
||||
fun BluetoothGattCharacteristic.toArgs(): MyGATTCharacteristicArgs {
|
||||
val hashCodeArgs = hashCode().toLong()
|
||||
val uuidArgs = this.uuid.toString()
|
||||
val propertyNumbersArgs = getPropertyNumbersArgs()
|
||||
val descriptorsArgs = descriptors.map { it.toArgs() }
|
||||
return MyGATTCharacteristicArgs(hashCodeArgs, uuidArgs, propertyNumbersArgs, descriptorsArgs)
|
||||
}
|
||||
|
||||
fun BluetoothGattService.toArgs(): MyGATTServiceArgs {
|
||||
val hashCodeArgs = hashCode().toLong()
|
||||
val uuidArgs = uuid.toString()
|
||||
val isPrimaryArgs = type == BluetoothGattService.SERVICE_TYPE_PRIMARY
|
||||
val includedServicesArgs = includedServices.map { it.toArgs() }
|
||||
val characteristicsArgs = characteristics.map { it.toArgs() }
|
||||
return MyGATTServiceArgs(hashCodeArgs, uuidArgs, isPrimaryArgs, includedServicesArgs, characteristicsArgs)
|
||||
} //endregion
|
||||
|
||||
val Any.hashCode get() = this.hashCode()
|
||||
val Int.args get() = this.toLong()
|
||||
|
||||
//val Any.TAG get() = this::class.java.simpleName as String
|
||||
//
|
||||
//val ScanRecord.rawValues: Map<Byte, ByteArray>
|
||||
// get() {
|
||||
// val rawValues = mutableMapOf<Byte, ByteArray>()
|
||||
// var index = 0
|
||||
// val size = bytes.size
|
||||
// while (index < size) {
|
||||
// val length = bytes[index++].toInt() and 0xff
|
||||
// if (length == 0) {
|
||||
// break
|
||||
// }
|
||||
// val end = index + length
|
||||
// val type = bytes[index++]
|
||||
// val value = bytes.slice(index until end).toByteArray()
|
||||
// rawValues[type] = value
|
||||
// index = end
|
||||
// }
|
||||
// return rawValues.toMap()
|
||||
// }
|
@ -0,0 +1,16 @@
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import android.content.Intent
|
||||
import io.flutter.plugin.common.PluginRegistry
|
||||
|
||||
class MyActivityResultListener(manager: MyBluetoothLowEnergyManager) : PluginRegistry.ActivityResultListener {
|
||||
private val mManager: MyBluetoothLowEnergyManager
|
||||
|
||||
init {
|
||||
mManager = manager
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
||||
return mManager.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import android.bluetooth.le.AdvertiseCallback
|
||||
import android.bluetooth.le.AdvertiseSettings
|
@ -1,4 +1,4 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCallback
|
||||
@ -7,8 +7,7 @@ import android.bluetooth.BluetoothGattDescriptor
|
||||
import android.os.Build
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
|
||||
BluetoothGattCallback() {
|
||||
class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) : BluetoothGattCallback() {
|
||||
private val mManager: MyCentralManager
|
||||
private val mExecutor: Executor
|
||||
|
||||
@ -45,12 +44,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCharacteristicRead(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
value: ByteArray,
|
||||
status: Int
|
||||
) {
|
||||
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
|
||||
super.onCharacteristicRead(gatt, characteristic, value, status)
|
||||
mExecutor.execute {
|
||||
mManager.onCharacteristicRead(gatt, characteristic, status, value)
|
||||
@ -58,9 +52,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
|
||||
}
|
||||
|
||||
// TODO: remove this override when minSdkVersion >= 33
|
||||
override fun onCharacteristicRead(
|
||||
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
|
||||
) {
|
||||
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
|
||||
super.onCharacteristicRead(gatt, characteristic, status)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
return
|
||||
@ -71,18 +63,14 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCharacteristicWrite(
|
||||
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
|
||||
) {
|
||||
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
|
||||
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||
mExecutor.execute {
|
||||
mManager.onCharacteristicWrite(gatt, characteristic, status)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCharacteristicChanged(
|
||||
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray
|
||||
) {
|
||||
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
|
||||
super.onCharacteristicChanged(gatt, characteristic, value)
|
||||
mExecutor.execute {
|
||||
mManager.onCharacteristicChanged(gatt, characteristic, value)
|
||||
@ -90,9 +78,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
|
||||
}
|
||||
|
||||
// TODO: remove this override when minSdkVersion >= 33
|
||||
override fun onCharacteristicChanged(
|
||||
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic
|
||||
) {
|
||||
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
||||
super.onCharacteristicChanged(gatt, characteristic)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
return
|
||||
@ -103,9 +89,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDescriptorRead(
|
||||
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray
|
||||
) {
|
||||
override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
|
||||
super.onDescriptorRead(gatt, descriptor, status, value)
|
||||
mExecutor.execute {
|
||||
mManager.onDescriptorRead(gatt, descriptor, status, value)
|
||||
@ -113,9 +97,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
|
||||
}
|
||||
|
||||
// TODO: remove this override when minSdkVersion >= 33
|
||||
override fun onDescriptorRead(
|
||||
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
|
||||
) {
|
||||
override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
|
||||
super.onDescriptorRead(gatt, descriptor, status)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
return
|
||||
@ -126,9 +108,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDescriptorWrite(
|
||||
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
|
||||
) {
|
||||
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
|
||||
super.onDescriptorWrite(gatt, descriptor, status)
|
||||
mExecutor.execute {
|
||||
mManager.onDescriptorWrite(gatt, descriptor, status)
|
@ -1,4 +1,4 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
@ -7,14 +7,13 @@ import android.bluetooth.BluetoothGattServerCallback
|
||||
import android.bluetooth.BluetoothGattService
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Executor) :
|
||||
BluetoothGattServerCallback() {
|
||||
class MyBluetoothGattServerCallback(manager: MyPeripheralManager, mExecutor: Executor) : BluetoothGattServerCallback() {
|
||||
private val mManager: MyPeripheralManager
|
||||
private val mExecutor: Executor
|
||||
|
||||
init {
|
||||
mManager = manager
|
||||
mExecutor = executor
|
||||
this.mExecutor = mExecutor
|
||||
}
|
||||
|
||||
override fun onServiceAdded(status: Int, service: BluetoothGattService) {
|
||||
@ -38,41 +37,17 @@ class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Exec
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCharacteristicReadRequest(
|
||||
device: BluetoothDevice,
|
||||
requestId: Int,
|
||||
offset: Int,
|
||||
characteristic: BluetoothGattCharacteristic
|
||||
) {
|
||||
override fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) {
|
||||
super.onCharacteristicReadRequest(device, requestId, offset, characteristic)
|
||||
mExecutor.execute {
|
||||
mManager.onCharacteristicReadRequest(device, requestId, offset, characteristic)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCharacteristicWriteRequest(
|
||||
device: BluetoothDevice,
|
||||
requestId: Int,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
preparedWrite: Boolean,
|
||||
responseNeeded: Boolean,
|
||||
offset: Int,
|
||||
value: ByteArray
|
||||
) {
|
||||
super.onCharacteristicWriteRequest(
|
||||
device, requestId, characteristic, preparedWrite, responseNeeded, offset, value
|
||||
)
|
||||
override fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
|
||||
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value)
|
||||
mExecutor.execute {
|
||||
mManager.onCharacteristicWriteRequest(
|
||||
device, requestId, characteristic, preparedWrite, responseNeeded, offset, value
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
|
||||
super.onExecuteWrite(device, requestId, execute)
|
||||
mExecutor.execute {
|
||||
mManager.onExecuteWrite(device, requestId, execute)
|
||||
mManager.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,31 +58,24 @@ class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Exec
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDescriptorReadRequest(
|
||||
device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor
|
||||
) {
|
||||
override fun onDescriptorReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor) {
|
||||
super.onDescriptorReadRequest(device, requestId, offset, descriptor)
|
||||
mExecutor.execute {
|
||||
mManager.onDescriptorReadRequest(device, requestId, offset, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDescriptorWriteRequest(
|
||||
device: BluetoothDevice,
|
||||
requestId: Int,
|
||||
descriptor: BluetoothGattDescriptor,
|
||||
preparedWrite: Boolean,
|
||||
responseNeeded: Boolean,
|
||||
offset: Int,
|
||||
value: ByteArray
|
||||
) {
|
||||
super.onDescriptorWriteRequest(
|
||||
device, requestId, descriptor, preparedWrite, responseNeeded, offset, value
|
||||
)
|
||||
override fun onDescriptorWriteRequest(device: BluetoothDevice, requestId: Int, descriptor: BluetoothGattDescriptor, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
|
||||
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value)
|
||||
mExecutor.execute {
|
||||
mManager.onDescriptorWriteRequest(
|
||||
device, requestId, descriptor, preparedWrite, responseNeeded, offset, value
|
||||
)
|
||||
mManager.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
|
||||
super.onExecuteWrite(device, requestId, execute)
|
||||
mExecutor.execute {
|
||||
mManager.onExecuteWrite(device, requestId, execute)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import android.app.Activity
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
import io.flutter.plugin.common.PluginRegistry
|
||||
|
||||
abstract class MyBluetoothLowEnergyManager(val context: Context) {
|
||||
companion object {
|
||||
const val AUTHORIZE_CODE = 0x00
|
||||
const val SHOW_APP_SETTINGS_CODE = 0x01
|
||||
}
|
||||
|
||||
private val mBroadcastReceiver: BroadcastReceiver by lazy { MyBroadcastReceiver(this) }
|
||||
private val mRequestPermissionsResultListener: PluginRegistry.RequestPermissionsResultListener by lazy { MyRequestPermissionResultListener(this) }
|
||||
private val mActivityResultListener: PluginRegistry.ActivityResultListener by lazy { MyActivityResultListener(this) }
|
||||
|
||||
private lateinit var mBinding: ActivityPluginBinding
|
||||
|
||||
init {
|
||||
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
|
||||
context.registerReceiver(mBroadcastReceiver, filter)
|
||||
}
|
||||
|
||||
val activity: Activity get() = mBinding.activity
|
||||
|
||||
fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||
binding.addRequestPermissionsResultListener(mRequestPermissionsResultListener)
|
||||
binding.addActivityResultListener(mActivityResultListener)
|
||||
mBinding = binding
|
||||
}
|
||||
|
||||
fun onDetachedFromActivity() {
|
||||
mBinding.removeRequestPermissionsResultListener(mRequestPermissionsResultListener)
|
||||
mBinding.removeActivityResultListener(mActivityResultListener)
|
||||
}
|
||||
|
||||
abstract fun onReceive(context: Context, intent: Intent)
|
||||
abstract fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean
|
||||
abstract fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
@ -1,10 +1,13 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCallback
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattDescriptor
|
||||
import android.bluetooth.BluetoothGattService
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.bluetooth.BluetoothStatusCodes
|
||||
import android.bluetooth.le.BluetoothLeScanner
|
||||
@ -13,47 +16,46 @@ import android.bluetooth.le.ScanFilter
|
||||
import android.bluetooth.le.ScanResult
|
||||
import android.bluetooth.le.ScanSettings
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.ParcelUuid
|
||||
import android.provider.Settings
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
MyBluetoothLowEnergyManager(context), MyCentralManagerHostApi {
|
||||
companion object {
|
||||
const val REQUEST_CODE = 443
|
||||
}
|
||||
class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) : MyBluetoothLowEnergyManager(context), MyCentralManagerHostAPI {
|
||||
private val mAPI: MyCentralManagerFlutterAPI
|
||||
|
||||
private val mContext: Context
|
||||
private val mApi: MyCentralManagerFlutterApi
|
||||
|
||||
private val mScanCallback: ScanCallback by lazy {
|
||||
MyScanCallback(this)
|
||||
}
|
||||
private val mBluetoothGattCallback: BluetoothGattCallback by lazy {
|
||||
MyBluetoothGattCallback(this, executor)
|
||||
}
|
||||
private val mScanCallback: ScanCallback by lazy { MyScanCallback(this) }
|
||||
private val mBluetoothGattCallback: BluetoothGattCallback by lazy { MyBluetoothGattCallback(this, executor) }
|
||||
|
||||
private var mDiscovering: Boolean
|
||||
|
||||
private val mDevices: MutableMap<String, BluetoothDevice>
|
||||
private val mGATTs: MutableMap<String, BluetoothGatt>
|
||||
private val mCharacteristics: MutableMap<String, Map<Long, BluetoothGattCharacteristic>>
|
||||
private val mDescriptors: MutableMap<String, Map<Long, BluetoothGattDescriptor>>
|
||||
private val mCharacteristics: MutableMap<String, MutableMap<Long, BluetoothGattCharacteristic>>
|
||||
private val mDescriptors: MutableMap<String, MutableMap<Long, BluetoothGattDescriptor>>
|
||||
|
||||
private var mSetUpCallback: ((Result<Unit>) -> Unit)?
|
||||
private var mAuthorizeCallback: ((Result<Boolean>) -> Unit)?
|
||||
private var mShowAppSettingsCallback: ((Result<Unit>) -> Unit)?
|
||||
private var mStartDiscoveryCallback: ((Result<Unit>) -> Unit)?
|
||||
private val mConnectCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
|
||||
private val mDisconnectCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
|
||||
private val mRequestMtuCallbacks: MutableMap<String, (Result<Long>) -> Unit>
|
||||
private val mReadRssiCallbacks: MutableMap<String, (Result<Long>) -> Unit>
|
||||
private val mDiscoverServicesCallbacks: MutableMap<String, (Result<List<MyGattServiceArgs>>) -> Unit>
|
||||
private val mDiscoverServicesCallbacks: MutableMap<String, (Result<List<MyGATTServiceArgs>>) -> Unit>
|
||||
private val mReadCharacteristicCallbacks: MutableMap<String, MutableMap<Long, (Result<ByteArray>) -> Unit>>
|
||||
private val mWriteCharacteristicCallbacks: MutableMap<String, MutableMap<Long, (Result<Unit>) -> Unit>>
|
||||
private val mReadDescriptorCallbacks: MutableMap<String, MutableMap<Long, (Result<ByteArray>) -> Unit>>
|
||||
private val mWriteDescriptorCallbacks: MutableMap<String, MutableMap<Long, (Result<Unit>) -> Unit>>
|
||||
|
||||
init {
|
||||
mContext = context
|
||||
mApi = MyCentralManagerFlutterApi(binaryMessenger)
|
||||
mAPI = MyCentralManagerFlutterAPI(binaryMessenger)
|
||||
|
||||
mDiscovering = false
|
||||
|
||||
@ -62,7 +64,8 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
mCharacteristics = mutableMapOf()
|
||||
mDescriptors = mutableMapOf()
|
||||
|
||||
mSetUpCallback = null
|
||||
mAuthorizeCallback = null
|
||||
mShowAppSettingsCallback = null
|
||||
mStartDiscoveryCallback = null
|
||||
mConnectCallbacks = mutableMapOf()
|
||||
mDisconnectCallbacks = mutableMapOf()
|
||||
@ -75,56 +78,91 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
mWriteDescriptorCallbacks = mutableMapOf()
|
||||
}
|
||||
|
||||
private val mScanner: BluetoothLeScanner get() = adapter.bluetoothLeScanner
|
||||
|
||||
override val permissions: Array<String>
|
||||
private val permissions: Array<String>
|
||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
arrayOf(
|
||||
android.Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||
android.Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
android.Manifest.permission.BLUETOOTH_SCAN,
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT
|
||||
)
|
||||
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
} else {
|
||||
arrayOf(
|
||||
android.Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||
android.Manifest.permission.ACCESS_FINE_LOCATION
|
||||
)
|
||||
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
}
|
||||
private val manager get() = ContextCompat.getSystemService(context, BluetoothManager::class.java) as BluetoothManager
|
||||
private val adapter get() = manager.adapter as BluetoothAdapter
|
||||
private val scanner: BluetoothLeScanner get() = adapter.bluetoothLeScanner
|
||||
private val executor get() = ContextCompat.getMainExecutor(context) as Executor
|
||||
|
||||
override fun initialize(): MyCentralManagerArgs {
|
||||
if (mDiscovering) {
|
||||
stopDiscovery()
|
||||
}
|
||||
|
||||
override val requestCode: Int
|
||||
get() = REQUEST_CODE
|
||||
for (gatt in mGATTs.values) {
|
||||
gatt.disconnect()
|
||||
}
|
||||
|
||||
override fun setUp(callback: (Result<Unit>) -> Unit) {
|
||||
mDevices.clear()
|
||||
mGATTs.clear()
|
||||
mCharacteristics.clear()
|
||||
mDescriptors.clear()
|
||||
|
||||
mAuthorizeCallback = null
|
||||
mShowAppSettingsCallback = null
|
||||
mStartDiscoveryCallback = null
|
||||
mConnectCallbacks.clear()
|
||||
mDisconnectCallbacks.clear()
|
||||
mRequestMtuCallbacks.clear()
|
||||
mReadRssiCallbacks.clear()
|
||||
mDiscoverServicesCallbacks.clear()
|
||||
mReadCharacteristicCallbacks.clear()
|
||||
mWriteCharacteristicCallbacks.clear()
|
||||
mReadDescriptorCallbacks.clear()
|
||||
mWriteDescriptorCallbacks.clear()
|
||||
|
||||
val enableNotificationValue = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||
val enableIndicationValue = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
||||
val disableNotificationValue = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
|
||||
return MyCentralManagerArgs(enableNotificationValue, enableIndicationValue, disableNotificationValue)
|
||||
}
|
||||
|
||||
override fun getState(): MyBluetoothLowEnergyStateArgs {
|
||||
val supported = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
|
||||
return if (supported) {
|
||||
val authorized = permissions.all { permission -> ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED }
|
||||
return if (authorized) adapter.state.toBluetoothLowEnergyStateArgs()
|
||||
else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
|
||||
} else MyBluetoothLowEnergyStateArgs.UNSUPPORTED
|
||||
}
|
||||
|
||||
override fun authorize(callback: (Result<Boolean>) -> Unit) {
|
||||
try {
|
||||
mClearState()
|
||||
val stateArgs = if (hasFeature) {
|
||||
val granted = checkPermissions()
|
||||
if (granted) {
|
||||
registerReceiver()
|
||||
adapter.state.toBluetoothLowEnergyStateArgs()
|
||||
} else {
|
||||
requestPermissions()
|
||||
mSetUpCallback = callback
|
||||
return
|
||||
}
|
||||
} else MyBluetoothLowEnergyStateArgs.UNSUPPORTED
|
||||
mOnStateChanged(stateArgs)
|
||||
callback(Result.success(Unit))
|
||||
ActivityCompat.requestPermissions(activity, permissions, AUTHORIZE_CODE)
|
||||
mAuthorizeCallback = callback
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun startDiscovery(callback: (Result<Unit>) -> Unit) {
|
||||
override fun showAppSettings(callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
val filters = emptyList<ScanFilter>()
|
||||
val settings =
|
||||
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
|
||||
mScanner.startScan(filters, settings, mScanCallback)
|
||||
executor.execute {
|
||||
onScanSucceed()
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
intent.data = Uri.fromParts("package", activity.packageName, null)
|
||||
val options = ActivityOptionsCompat.makeBasic().toBundle()
|
||||
ActivityCompat.startActivityForResult(activity, intent, SHOW_APP_SETTINGS_CODE, options)
|
||||
mShowAppSettingsCallback = callback
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun startDiscovery(serviceUUIDsArgs: List<String>, callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
val filters = mutableListOf<ScanFilter>()
|
||||
for (serviceUuidArgs in serviceUUIDsArgs) {
|
||||
val serviceUUID = ParcelUuid.fromString(serviceUuidArgs)
|
||||
val filter = ScanFilter.Builder().setServiceUuid(serviceUUID).build()
|
||||
filters.add(filter)
|
||||
}
|
||||
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
|
||||
scanner.startScan(filters, settings, mScanCallback)
|
||||
executor.execute { onScanSucceeded() }
|
||||
mStartDiscoveryCallback = callback
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
@ -132,20 +170,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
|
||||
override fun stopDiscovery() {
|
||||
mScanner.stopScan(mScanCallback)
|
||||
scanner.stopScan(mScanCallback)
|
||||
mDiscovering = false
|
||||
}
|
||||
|
||||
override fun connect(addressArgs: String, callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
val device = mDevices[addressArgs] as BluetoothDevice
|
||||
val autoConnect = false
|
||||
// Add to bluetoothGATTs cache.
|
||||
val device = mDevices[addressArgs] ?: throw IllegalArgumentException()
|
||||
val autoConnect = false // Add to bluetoothGATTs cache.
|
||||
mGATTs[addressArgs] = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val transport = BluetoothDevice.TRANSPORT_LE
|
||||
device.connectGatt(mContext, autoConnect, mBluetoothGattCallback, transport)
|
||||
device.connectGatt(context, autoConnect, mBluetoothGattCallback, transport)
|
||||
} else {
|
||||
device.connectGatt(mContext, autoConnect, mBluetoothGattCallback)
|
||||
device.connectGatt(context, autoConnect, mBluetoothGattCallback)
|
||||
}
|
||||
mConnectCallbacks[addressArgs] = callback
|
||||
} catch (e: Throwable) {
|
||||
@ -155,7 +192,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
|
||||
override fun disconnect(addressArgs: String, callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
val gatt = mGATTs[addressArgs] as BluetoothGatt
|
||||
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
|
||||
gatt.disconnect()
|
||||
mDisconnectCallbacks[addressArgs] = callback
|
||||
} catch (e: Throwable) {
|
||||
@ -163,9 +200,21 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun retrieveConnectedPeripherals(): List<MyPeripheralArgs> {
|
||||
// The `BluetoothProfile.GATT` and `BluetoothProfile.GATT_SERVER` return same devices.
|
||||
val devices = manager.getConnectedDevices(BluetoothProfile.GATT)
|
||||
val peripheralsArgs = devices.map { device ->
|
||||
val peripheralArgs = device.toPeripheralArgs()
|
||||
val addressArgs = peripheralArgs.addressArgs
|
||||
mDevices[addressArgs] = device
|
||||
return@map peripheralArgs
|
||||
}
|
||||
return peripheralsArgs
|
||||
}
|
||||
|
||||
override fun requestMTU(addressArgs: String, mtuArgs: Long, callback: (Result<Long>) -> Unit) {
|
||||
try {
|
||||
val gatt = mGATTs[addressArgs] as BluetoothGatt
|
||||
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
|
||||
val mtu = mtuArgs.toInt()
|
||||
val requesting = gatt.requestMtu(mtu)
|
||||
if (!requesting) {
|
||||
@ -179,7 +228,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
|
||||
override fun readRSSI(addressArgs: String, callback: (Result<Long>) -> Unit) {
|
||||
try {
|
||||
val gatt = mGATTs[addressArgs] as BluetoothGatt
|
||||
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
|
||||
val reading = gatt.readRemoteRssi()
|
||||
if (!reading) {
|
||||
throw IllegalStateException()
|
||||
@ -190,11 +239,9 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun discoverServices(
|
||||
addressArgs: String, callback: (Result<List<MyGattServiceArgs>>) -> Unit
|
||||
) {
|
||||
override fun discoverGATT(addressArgs: String, callback: (Result<List<MyGATTServiceArgs>>) -> Unit) {
|
||||
try {
|
||||
val gatt = mGATTs[addressArgs] as BluetoothGatt
|
||||
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
|
||||
val discovering = gatt.discoverServices()
|
||||
if (!discovering) {
|
||||
throw IllegalStateException()
|
||||
@ -205,13 +252,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun readCharacteristic(
|
||||
addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit
|
||||
) {
|
||||
override fun readCharacteristic(addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit) {
|
||||
try {
|
||||
val gatt = mGATTs[addressArgs] as BluetoothGatt
|
||||
val characteristic =
|
||||
mRetrieveCharacteristic(addressArgs, hashCodeArgs) as BluetoothGattCharacteristic
|
||||
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
|
||||
val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
|
||||
val reading = gatt.readCharacteristic(characteristic)
|
||||
if (!reading) {
|
||||
throw IllegalStateException()
|
||||
@ -223,26 +267,15 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeCharacteristic(
|
||||
addressArgs: String,
|
||||
hashCodeArgs: Long,
|
||||
valueArgs: ByteArray,
|
||||
typeNumberArgs: Long,
|
||||
callback: (Result<Unit>) -> Unit
|
||||
) {
|
||||
override fun writeCharacteristic(addressArgs: String, hashCodeArgs: Long, valueArgs: ByteArray, typeArgs: MyGATTCharacteristicWriteTypeArgs, callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
val gatt = mGATTs[addressArgs] as BluetoothGatt
|
||||
val characteristic =
|
||||
mRetrieveCharacteristic(addressArgs, hashCodeArgs) as BluetoothGattCharacteristic
|
||||
val typeRawArgs = typeNumberArgs.toInt()
|
||||
val typeArgs = MyGattCharacteristicWriteTypeArgs.ofRaw(typeRawArgs)
|
||||
?: throw IllegalArgumentException()
|
||||
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
|
||||
val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
|
||||
val type = typeArgs.toType()
|
||||
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val code = gatt.writeCharacteristic(characteristic, valueArgs, type)
|
||||
code == BluetoothStatusCodes.SUCCESS
|
||||
} else {
|
||||
// TODO: remove this when minSdkVersion >= 33
|
||||
} else { // TODO: remove this when minSdkVersion >= 33
|
||||
characteristic.value = valueArgs
|
||||
characteristic.writeType = type
|
||||
gatt.writeCharacteristic(characteristic)
|
||||
@ -257,47 +290,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCharacteristicNotifyState(
|
||||
addressArgs: String,
|
||||
hashCodeArgs: Long,
|
||||
stateNumberArgs: Long,
|
||||
callback: (Result<Unit>) -> Unit
|
||||
) {
|
||||
try {
|
||||
val gatt = mGATTs[addressArgs] as BluetoothGatt
|
||||
val characteristic =
|
||||
mRetrieveCharacteristic(addressArgs, hashCodeArgs) as BluetoothGattCharacteristic
|
||||
val stateRawArgs = stateNumberArgs.toInt()
|
||||
val stateArgs = MyGattCharacteristicNotifyStateArgs.ofRaw(stateRawArgs)
|
||||
?: throw IllegalArgumentException()
|
||||
val enable = stateArgs != MyGattCharacteristicNotifyStateArgs.NONE
|
||||
val notifying = gatt.setCharacteristicNotification(characteristic, enable)
|
||||
if (!notifying) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
// TODO: Seems the docs is not correct, this operation is necessary for all characteristics.
|
||||
// https://developer.android.com/guide/topics/connectivity/bluetooth/transfer-ble-data#notification
|
||||
// This is specific to Heart Rate Measurement.
|
||||
//if (characteristic.uuid == UUID_HEART_RATE_MEASUREMENT) {
|
||||
val cccDescriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID)
|
||||
val cccHashCodeArgs = cccDescriptor.hashCode().toLong()
|
||||
val valueArgs = stateArgs.toValue()
|
||||
writeDescriptor(addressArgs, cccHashCodeArgs, valueArgs, callback)
|
||||
//} else {
|
||||
// callback(Result.success(Unit))
|
||||
//}
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
override fun setCharacteristicNotification(addressArgs: String, hashCodeArgs: Long, enableArgs: Boolean) {
|
||||
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
|
||||
val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
|
||||
val notifying = gatt.setCharacteristicNotification(characteristic, enableArgs)
|
||||
if (!notifying) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun readDescriptor(
|
||||
addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit
|
||||
) {
|
||||
override fun readDescriptor(addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit) {
|
||||
try {
|
||||
val gatt = mGATTs[addressArgs] as BluetoothGatt
|
||||
val descriptor =
|
||||
mRetrieveDescriptor(addressArgs, hashCodeArgs) as BluetoothGattDescriptor
|
||||
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
|
||||
val descriptor = retrieveDescriptor(addressArgs, hashCodeArgs)
|
||||
val reading = gatt.readDescriptor(descriptor)
|
||||
if (!reading) {
|
||||
throw IllegalStateException()
|
||||
@ -309,21 +314,14 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeDescriptor(
|
||||
addressArgs: String,
|
||||
hashCodeArgs: Long,
|
||||
valueArgs: ByteArray,
|
||||
callback: (Result<Unit>) -> Unit
|
||||
) {
|
||||
override fun writeDescriptor(addressArgs: String, hashCodeArgs: Long, valueArgs: ByteArray, callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
val gatt = mGATTs[addressArgs] as BluetoothGatt
|
||||
val descriptor =
|
||||
mRetrieveDescriptor(addressArgs, hashCodeArgs) as BluetoothGattDescriptor
|
||||
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
|
||||
val descriptor = retrieveDescriptor(addressArgs, hashCodeArgs)
|
||||
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val code = gatt.writeDescriptor(descriptor, valueArgs)
|
||||
code == BluetoothStatusCodes.SUCCESS
|
||||
} else {
|
||||
// TODO: remove this when minSdkVersion >= 33
|
||||
} else { // TODO: remove this when minSdkVersion >= 33
|
||||
descriptor.value = valueArgs
|
||||
gatt.writeDescriptor(descriptor)
|
||||
}
|
||||
@ -337,22 +335,37 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPermissionsRequested(granted: Boolean) {
|
||||
val callback = mSetUpCallback ?: return
|
||||
val stateArgs = if (granted) {
|
||||
registerReceiver()
|
||||
adapter.state.toBluetoothLowEnergyStateArgs()
|
||||
} else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
|
||||
mOnStateChanged(stateArgs)
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
|
||||
override fun onAdapterStateChanged(state: Int) {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != BluetoothAdapter.ACTION_STATE_CHANGED) {
|
||||
return
|
||||
}
|
||||
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
|
||||
val stateArgs = state.toBluetoothLowEnergyStateArgs()
|
||||
mOnStateChanged(stateArgs)
|
||||
mAPI.onStateChanged(stateArgs) {}
|
||||
}
|
||||
|
||||
private fun onScanSucceed() {
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean {
|
||||
if (requestCode != AUTHORIZE_CODE) {
|
||||
return false
|
||||
}
|
||||
val callback = mAuthorizeCallback ?: return false
|
||||
mAuthorizeCallback = null
|
||||
val authorized = permissions.contentEquals(this.permissions) && results.all { r -> r == PackageManager.PERMISSION_GRANTED }
|
||||
callback(Result.success(authorized))
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
||||
if (requestCode != SHOW_APP_SETTINGS_CODE) {
|
||||
return false
|
||||
}
|
||||
val callback = mShowAppSettingsCallback ?: return false
|
||||
mShowAppSettingsCallback = null
|
||||
callback(Result.success(Unit))
|
||||
return true
|
||||
}
|
||||
|
||||
private fun onScanSucceeded() {
|
||||
mDiscovering = true
|
||||
val callback = mStartDiscoveryCallback ?: return
|
||||
mStartDiscoveryCallback = null
|
||||
@ -370,16 +383,15 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
val device = result.device
|
||||
val peripheralArgs = device.toPeripheralArgs()
|
||||
val addressArgs = peripheralArgs.addressArgs
|
||||
val rssiArgs = result.rssi.toLong()
|
||||
val rssiArgs = result.rssi.args
|
||||
val advertisementArgs = result.toAdvertisementArgs()
|
||||
mDevices[addressArgs] = device
|
||||
mApi.onDiscovered(peripheralArgs, rssiArgs, advertisementArgs) {}
|
||||
mAPI.onDiscovered(peripheralArgs, rssiArgs, advertisementArgs) {}
|
||||
}
|
||||
|
||||
fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
||||
val device = gatt.device
|
||||
val addressArgs = device.address
|
||||
// check connection state.
|
||||
val addressArgs = device.address // check connection state.
|
||||
if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||
gatt.close()
|
||||
mGATTs.remove(addressArgs)
|
||||
@ -427,9 +439,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
}
|
||||
val stateArgs = newState == BluetoothProfile.STATE_CONNECTED
|
||||
mApi.onConnectionStateChanged(addressArgs, stateArgs) {}
|
||||
// check connect & disconnect callbacks.
|
||||
// check connect callback.
|
||||
val connectCallback = mConnectCallbacks.remove(addressArgs)
|
||||
if (connectCallback != null) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
@ -439,6 +449,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
connectCallback(Result.failure(error))
|
||||
}
|
||||
}
|
||||
// check disconnect callback.
|
||||
val disconnectCallback = mDisconnectCallbacks.remove(addressArgs)
|
||||
if (disconnectCallback != null) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
@ -448,14 +459,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
disconnectCallback(Result.failure(error))
|
||||
}
|
||||
}
|
||||
// invoke connection state changed event.
|
||||
val peripheralArgs = device.toPeripheralArgs()
|
||||
val stateArgs = newState.toConnectionStateArgs()
|
||||
mAPI.onConnectionStateChanged(peripheralArgs, stateArgs) {}
|
||||
}
|
||||
|
||||
fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
|
||||
val device = gatt.device
|
||||
val addressArgs = device.address
|
||||
val result = if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
val mtuArgs = mtu.toLong()
|
||||
mApi.onMtuChanged(addressArgs, mtuArgs) {}
|
||||
val peripheralArgs = device.toPeripheralArgs()
|
||||
val mtuArgs = mtu.args
|
||||
mAPI.onMTUChanged(peripheralArgs, mtuArgs) {}
|
||||
Result.success(mtuArgs)
|
||||
} else {
|
||||
val error = IllegalStateException("Read RSSI failed with status: $status")
|
||||
@ -470,7 +486,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
val addressArgs = device.address
|
||||
val callback = mReadRssiCallbacks.remove(addressArgs) ?: return
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
val rssiArgs = rssi.toLong()
|
||||
val rssiArgs = rssi.args
|
||||
callback(Result.success(rssiArgs))
|
||||
} else {
|
||||
val error = IllegalStateException("Read RSSI failed with status: $status")
|
||||
@ -484,10 +500,9 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
val callback = mDiscoverServicesCallbacks.remove(addressArgs) ?: return
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
val services = gatt.services
|
||||
val characteristics = services.flatMap { it.characteristics }
|
||||
val descriptors = characteristics.flatMap { it.descriptors }
|
||||
mCharacteristics[addressArgs] = characteristics.associateBy { it.hashCode().toLong() }
|
||||
mDescriptors[addressArgs] = descriptors.associateBy { it.hashCode().toLong() }
|
||||
for (service in services) {
|
||||
addService(addressArgs, service)
|
||||
}
|
||||
val servicesArgs = services.map { it.toArgs() }
|
||||
callback(Result.success(servicesArgs))
|
||||
} else {
|
||||
@ -496,15 +511,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
|
||||
fun onCharacteristicRead(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
status: Int,
|
||||
value: ByteArray
|
||||
) {
|
||||
fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int, value: ByteArray) {
|
||||
val device = gatt.device
|
||||
val addressArgs = device.address
|
||||
val hashCodeArgs = characteristic.hashCode().toLong()
|
||||
val hashCodeArgs = characteristic.hashCode.args
|
||||
val callbacks = mReadCharacteristicCallbacks[addressArgs] ?: return
|
||||
val callback = callbacks.remove(hashCodeArgs) ?: return
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
@ -515,12 +525,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
|
||||
fun onCharacteristicWrite(
|
||||
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
|
||||
) {
|
||||
fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
|
||||
val device = gatt.device
|
||||
val addressArgs = device.address
|
||||
val hashCodeArgs = characteristic.hashCode().toLong()
|
||||
val hashCodeArgs = characteristic.hashCode.args
|
||||
val callbacks = mWriteCharacteristicCallbacks[addressArgs] ?: return
|
||||
val callback = callbacks.remove(hashCodeArgs) ?: return
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
@ -531,21 +539,17 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
|
||||
fun onCharacteristicChanged(
|
||||
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray
|
||||
) {
|
||||
fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
|
||||
val device = gatt.device
|
||||
val addressArgs = device.address
|
||||
val hashCodeArgs = characteristic.hashCode().toLong()
|
||||
mApi.onCharacteristicNotified(addressArgs, hashCodeArgs, value) {}
|
||||
val peripheralArgs = device.toPeripheralArgs()
|
||||
val characteristicArgs = characteristic.toArgs()
|
||||
mAPI.onCharacteristicNotified(peripheralArgs, characteristicArgs, value) {}
|
||||
}
|
||||
|
||||
fun onDescriptorRead(
|
||||
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray
|
||||
) {
|
||||
fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
|
||||
val device = gatt.device
|
||||
val addressArgs = device.address
|
||||
val hashCodeArgs = descriptor.hashCode().toLong()
|
||||
val hashCodeArgs = descriptor.hashCode.args
|
||||
val callbacks = mReadDescriptorCallbacks[addressArgs] ?: return
|
||||
val callback = callbacks.remove(hashCodeArgs) ?: return
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
@ -559,7 +563,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
|
||||
val device = gatt.device
|
||||
val addressArgs = device.address
|
||||
val hashCodeArgs = descriptor.hashCode().toLong()
|
||||
val hashCodeArgs = descriptor.hashCode.args
|
||||
val callbacks = mWriteDescriptorCallbacks[addressArgs] ?: return
|
||||
val callback = callbacks.remove(hashCodeArgs) ?: return
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
@ -570,47 +574,28 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun mClearState() {
|
||||
if (mDiscovering) {
|
||||
stopDiscovery()
|
||||
private fun addService(addressArgs: String, service: BluetoothGattService) {
|
||||
val includedServices = service.includedServices
|
||||
for (includedService in includedServices) {
|
||||
addService(addressArgs, includedService)
|
||||
}
|
||||
for (gatt in mGATTs.values) {
|
||||
gatt.disconnect()
|
||||
for (characteristic in service.characteristics) {
|
||||
for (descriptor in characteristic.descriptors) {
|
||||
val descriptors = mDescriptors.getOrPut(addressArgs) { mutableMapOf() }
|
||||
descriptors[descriptor.hashCode.args] = descriptor
|
||||
}
|
||||
val characteristics = mCharacteristics.getOrPut(addressArgs) { mutableMapOf() }
|
||||
characteristics[characteristic.hashCode.args] = characteristic
|
||||
}
|
||||
mDevices.clear()
|
||||
mGATTs.clear()
|
||||
mCharacteristics.clear()
|
||||
mDescriptors.clear()
|
||||
|
||||
mSetUpCallback = null
|
||||
mStartDiscoveryCallback = null
|
||||
mConnectCallbacks.clear()
|
||||
mDisconnectCallbacks.clear()
|
||||
mRequestMtuCallbacks.clear()
|
||||
mReadRssiCallbacks.clear()
|
||||
mDiscoverServicesCallbacks.clear()
|
||||
mReadCharacteristicCallbacks.clear()
|
||||
mWriteCharacteristicCallbacks.clear()
|
||||
mReadDescriptorCallbacks.clear()
|
||||
mWriteDescriptorCallbacks.clear()
|
||||
}
|
||||
|
||||
private fun mOnStateChanged(stateArgs: MyBluetoothLowEnergyStateArgs) {
|
||||
val stateNumberArgs = stateArgs.raw.toLong()
|
||||
mApi.onStateChanged(stateNumberArgs) {}
|
||||
private fun retrieveCharacteristic(addressArgs: String, hashCodeArgs: Long): BluetoothGattCharacteristic {
|
||||
val characteristics = mCharacteristics[addressArgs] ?: throw IllegalArgumentException()
|
||||
return characteristics[hashCodeArgs] ?: throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
private fun mRetrieveCharacteristic(
|
||||
addressArgs: String, hashCodeArgs: Long
|
||||
): BluetoothGattCharacteristic? {
|
||||
val characteristics = mCharacteristics[addressArgs] ?: return null
|
||||
return characteristics[hashCodeArgs]
|
||||
}
|
||||
|
||||
private fun mRetrieveDescriptor(
|
||||
addressArgs: String, hashCodeArgs: Long
|
||||
): BluetoothGattDescriptor? {
|
||||
val descriptors = mDescriptors[addressArgs] ?: return null
|
||||
return descriptors[hashCodeArgs]
|
||||
private fun retrieveDescriptor(addressArgs: String, hashCodeArgs: Long): BluetoothGattDescriptor {
|
||||
val descriptors = mDescriptors[addressArgs] ?: throw IllegalArgumentException()
|
||||
return descriptors[hashCodeArgs] ?: throw IllegalArgumentException()
|
||||
}
|
||||
}
|
@ -0,0 +1,446 @@
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattDescriptor
|
||||
import android.bluetooth.BluetoothGattServer
|
||||
import android.bluetooth.BluetoothGattServerCallback
|
||||
import android.bluetooth.BluetoothGattService
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothStatusCodes
|
||||
import android.bluetooth.le.AdvertiseCallback
|
||||
import android.bluetooth.le.AdvertiseSettings
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) : MyBluetoothLowEnergyManager(context), MyPeripheralManagerHostAPI {
|
||||
private val mAPI: MyPeripheralManagerFlutterAPI
|
||||
|
||||
private val mBluetoothGattServerCallback: BluetoothGattServerCallback by lazy { MyBluetoothGattServerCallback(this, executor) }
|
||||
private val mAdvertiseCallback: AdvertiseCallback by lazy { MyAdvertiseCallback(this) }
|
||||
|
||||
private var mServer: BluetoothGattServer?
|
||||
private var mAdvertising: Boolean
|
||||
|
||||
private val mServicesArgs: MutableMap<Int, MyMutableGATTServiceArgs>
|
||||
private val mCharacteristicsArgs: MutableMap<Int, MyMutableGATTCharacteristicArgs>
|
||||
private val mDescriptorsArgs: MutableMap<Int, MyMutableGATTDescriptorArgs>
|
||||
|
||||
private val mDevices: MutableMap<String, BluetoothDevice>
|
||||
private val mServices: MutableMap<Long, BluetoothGattService>
|
||||
private val mCharacteristics: MutableMap<Long, BluetoothGattCharacteristic>
|
||||
private val mDescriptors: MutableMap<Long, BluetoothGattDescriptor>
|
||||
|
||||
private var mAuthorizeCallback: ((Result<Boolean>) -> Unit)?
|
||||
private var mShowAppSettingsCallback: ((Result<Unit>) -> Unit)?
|
||||
private var mAddServiceCallback: ((Result<Unit>) -> Unit)?
|
||||
private var mStartAdvertisingCallback: ((Result<Unit>) -> Unit)?
|
||||
private val mNotifyCharacteristicValueChangedCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
|
||||
|
||||
init {
|
||||
mAPI = MyPeripheralManagerFlutterAPI(binaryMessenger)
|
||||
|
||||
mServer = null
|
||||
mAdvertising = false
|
||||
|
||||
mServicesArgs = mutableMapOf()
|
||||
mCharacteristicsArgs = mutableMapOf()
|
||||
mDescriptorsArgs = mutableMapOf()
|
||||
|
||||
mDevices = mutableMapOf()
|
||||
mServices = mutableMapOf()
|
||||
mCharacteristics = mutableMapOf()
|
||||
mDescriptors = mutableMapOf()
|
||||
|
||||
mAuthorizeCallback = null
|
||||
mShowAppSettingsCallback = null
|
||||
mAddServiceCallback = null
|
||||
mStartAdvertisingCallback = null
|
||||
mNotifyCharacteristicValueChangedCallbacks = mutableMapOf()
|
||||
}
|
||||
|
||||
private val permissions: Array<String>
|
||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
} else {
|
||||
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
}
|
||||
private val manager get() = ContextCompat.getSystemService(context, BluetoothManager::class.java) as BluetoothManager
|
||||
private val adapter get() = manager.adapter as BluetoothAdapter
|
||||
private val advertiser get() = adapter.bluetoothLeAdvertiser
|
||||
private val server get() = mServer ?: throw IllegalStateException()
|
||||
private val executor get() = ContextCompat.getMainExecutor(context) as Executor
|
||||
|
||||
override fun initialize(): MyPeripheralManagerArgs {
|
||||
if (mAdvertising) {
|
||||
stopAdvertising()
|
||||
}
|
||||
|
||||
mServer?.close()
|
||||
|
||||
mServicesArgs.clear()
|
||||
mCharacteristicsArgs.clear()
|
||||
mDescriptorsArgs.clear()
|
||||
|
||||
mDevices.clear()
|
||||
mServices.clear()
|
||||
mCharacteristics.clear()
|
||||
mDescriptors.clear()
|
||||
|
||||
mAuthorizeCallback = null
|
||||
mShowAppSettingsCallback = null
|
||||
mAddServiceCallback = null
|
||||
mStartAdvertisingCallback = null
|
||||
mNotifyCharacteristicValueChangedCallbacks.clear()
|
||||
|
||||
val enableNotificationValue = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||
val enableIndicationValue = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
||||
val disableNotificationValue = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
|
||||
return MyPeripheralManagerArgs(enableNotificationValue, enableIndicationValue, disableNotificationValue)
|
||||
}
|
||||
|
||||
override fun getState(): MyBluetoothLowEnergyStateArgs {
|
||||
val supported = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
|
||||
return if (supported) {
|
||||
val authorized = permissions.all { permission -> ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED }
|
||||
return if (authorized) adapter.state.toBluetoothLowEnergyStateArgs()
|
||||
else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
|
||||
} else MyBluetoothLowEnergyStateArgs.UNSUPPORTED
|
||||
}
|
||||
|
||||
override fun authorize(callback: (Result<Boolean>) -> Unit) {
|
||||
try {
|
||||
ActivityCompat.requestPermissions(activity, permissions, AUTHORIZE_CODE)
|
||||
mAuthorizeCallback = callback
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun showAppSettings(callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
intent.data = Uri.fromParts("package", activity.packageName, null)
|
||||
val options = ActivityOptionsCompat.makeBasic().toBundle()
|
||||
ActivityCompat.startActivityForResult(activity, intent, SHOW_APP_SETTINGS_CODE, options)
|
||||
mShowAppSettingsCallback = callback
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun openGATTServer() {
|
||||
mServer = manager.openGattServer(context, mBluetoothGattServerCallback)
|
||||
}
|
||||
|
||||
override fun closeGATTServer() {
|
||||
server.close()
|
||||
}
|
||||
|
||||
override fun addService(serviceArgs: MyMutableGATTServiceArgs, callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
val service = addServiceArgs(serviceArgs)
|
||||
val adding = server.addService(service)
|
||||
if (!adding) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
mAddServiceCallback = callback
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeService(hashCodeArgs: Long) {
|
||||
val service = mServices[hashCodeArgs] ?: throw IllegalArgumentException()
|
||||
val removed = server.removeService(service)
|
||||
if (!removed) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
val hashCode = service.hashCode()
|
||||
val serviceArgs = mServicesArgs[hashCode] ?: throw IllegalArgumentException()
|
||||
removeServiceArgs(serviceArgs)
|
||||
}
|
||||
|
||||
override fun removeAllServices() {
|
||||
server.clearServices()
|
||||
mServices.clear()
|
||||
mCharacteristics.clear()
|
||||
mDescriptors.clear()
|
||||
|
||||
mServicesArgs.clear()
|
||||
mCharacteristicsArgs.clear()
|
||||
mDescriptorsArgs.clear()
|
||||
}
|
||||
|
||||
override fun startAdvertising(advertisementArgs: MyAdvertisementArgs, callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
val settings = AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).setConnectable(true).build()
|
||||
// TODO: There is an issue that Android will use the cached name before setName takes effect.
|
||||
// see https://stackoverflow.com/questions/8377558/change-the-android-bluetooth-device-name
|
||||
val nameArgs = advertisementArgs.nameArgs
|
||||
if (nameArgs != null) {
|
||||
adapter.name = nameArgs
|
||||
}
|
||||
val advertiseData = advertisementArgs.toAdvertiseData()
|
||||
val scanResponse = advertisementArgs.toScanResponse()
|
||||
advertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback)
|
||||
mStartAdvertisingCallback = callback
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopAdvertising() {
|
||||
advertiser.stopAdvertising(mAdvertiseCallback)
|
||||
mAdvertising = false
|
||||
}
|
||||
|
||||
override fun sendResponse(addressArgs: String, idArgs: Long, statusArgs: MyGATTStatusArgs, offsetArgs: Long, valueArgs: ByteArray?) {
|
||||
val device = mDevices[addressArgs] ?: throw IllegalArgumentException()
|
||||
val requestId = idArgs.toInt()
|
||||
val status = statusArgs.toStatus()
|
||||
val offset = offsetArgs.toInt()
|
||||
val sent = server.sendResponse(device, requestId, status, offset, valueArgs)
|
||||
if (!sent) {
|
||||
throw IllegalStateException("Send response failed.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun notifyCharacteristicChanged(addressArgs: String, hashCodeArgs: Long, confirmArgs: Boolean, valueArgs: ByteArray, callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
val device = mDevices[addressArgs] ?: throw IllegalArgumentException()
|
||||
val characteristic = mCharacteristics[hashCodeArgs] ?: throw IllegalArgumentException()
|
||||
val notifying = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val statusCode = server.notifyCharacteristicChanged(device, characteristic, confirmArgs, valueArgs)
|
||||
statusCode == BluetoothStatusCodes.SUCCESS
|
||||
} else { // TODO: remove this when minSdkVersion >= 33
|
||||
characteristic.value = valueArgs
|
||||
server.notifyCharacteristicChanged(device, characteristic, confirmArgs)
|
||||
}
|
||||
if (!notifying) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
mNotifyCharacteristicValueChangedCallbacks[addressArgs] = callback
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != BluetoothAdapter.ACTION_STATE_CHANGED) {
|
||||
return
|
||||
}
|
||||
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
|
||||
val stateArgs = state.toBluetoothLowEnergyStateArgs()
|
||||
mAPI.onStateChanged(stateArgs) {}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean {
|
||||
if (requestCode != AUTHORIZE_CODE) {
|
||||
return false
|
||||
}
|
||||
val callback = mAuthorizeCallback ?: return false
|
||||
mAuthorizeCallback = null
|
||||
val authorized = permissions.contentEquals(this.permissions) && results.all { r -> r == PackageManager.PERMISSION_GRANTED }
|
||||
callback(Result.success(authorized))
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
||||
if (requestCode != SHOW_APP_SETTINGS_CODE) {
|
||||
return false
|
||||
}
|
||||
val callback = mShowAppSettingsCallback ?: return false
|
||||
mShowAppSettingsCallback = null
|
||||
callback(Result.success(Unit))
|
||||
return true
|
||||
}
|
||||
|
||||
fun onServiceAdded(status: Int, service: BluetoothGattService) {
|
||||
val callback = mAddServiceCallback ?: return
|
||||
mAddServiceCallback = null
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
callback(Result.success(Unit))
|
||||
} else {
|
||||
val error = IllegalStateException("Read rssi failed with status: $status")
|
||||
callback(Result.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
|
||||
mAdvertising = true
|
||||
val callback = mStartAdvertisingCallback ?: return
|
||||
mStartAdvertisingCallback = null
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
|
||||
fun onStartFailure(errorCode: Int) {
|
||||
val callback = mStartAdvertisingCallback ?: return
|
||||
mStartAdvertisingCallback = null
|
||||
val error = IllegalStateException("Start advertising failed with error code: $errorCode")
|
||||
callback(Result.failure(error))
|
||||
}
|
||||
|
||||
fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
|
||||
val centralArgs = device.toCentralArgs()
|
||||
val addressArgs = centralArgs.addressArgs
|
||||
val statusArgs = status.args
|
||||
val stateArgs = newState.toConnectionStateArgs()
|
||||
mDevices[addressArgs] = device
|
||||
mAPI.onConnectionStateChanged(centralArgs, statusArgs, stateArgs) {}
|
||||
}
|
||||
|
||||
fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
|
||||
val centralArgs = device.toCentralArgs()
|
||||
val mtuArgs = mtu.args
|
||||
mAPI.onMTUChanged(centralArgs, mtuArgs) {}
|
||||
}
|
||||
|
||||
fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) {
|
||||
val centralArgs = device.toCentralArgs()
|
||||
val idArgs = requestId.args
|
||||
val offsetArgs = offset.args
|
||||
val hashCode = characteristic.hashCode()
|
||||
val characteristicArgs = mCharacteristicsArgs[hashCode]
|
||||
if (characteristicArgs == null) {
|
||||
val status = BluetoothGatt.GATT_FAILURE
|
||||
server.sendResponse(device, requestId, status, offset, null)
|
||||
} else {
|
||||
val hashCodeArgs = characteristicArgs.hashCodeArgs
|
||||
mAPI.onCharacteristicReadRequest(centralArgs, idArgs, offsetArgs, hashCodeArgs) {}
|
||||
}
|
||||
}
|
||||
|
||||
fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
|
||||
val centralArgs = device.toCentralArgs()
|
||||
val idArgs = requestId.args
|
||||
val hashCode = characteristic.hashCode()
|
||||
val characteristicArgs = mCharacteristicsArgs[hashCode]
|
||||
if (characteristicArgs == null) {
|
||||
if (!responseNeeded) {
|
||||
return
|
||||
}
|
||||
val status = BluetoothGatt.GATT_FAILURE
|
||||
server.sendResponse(device, requestId, status, offset, null)
|
||||
} else {
|
||||
val hashCodeArgs = characteristicArgs.hashCodeArgs
|
||||
val offsetArgs = offset.args
|
||||
mAPI.onCharacteristicWriteRequest(centralArgs, idArgs, hashCodeArgs, preparedWrite, responseNeeded, offsetArgs, value) {}
|
||||
}
|
||||
}
|
||||
|
||||
fun onNotificationSent(device: BluetoothDevice, status: Int) {
|
||||
val addressArgs = device.address
|
||||
val callback = mNotifyCharacteristicValueChangedCallbacks.remove(addressArgs) ?: return
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
callback(Result.success(Unit))
|
||||
} else {
|
||||
val error = IllegalStateException("Notify characteristic value changed failed with status: $status")
|
||||
callback(Result.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
fun onDescriptorReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor) {
|
||||
val centralArgs = device.toCentralArgs()
|
||||
val idArgs = requestId.args
|
||||
val offsetArgs = offset.args
|
||||
val hashCode = descriptor.hashCode()
|
||||
val descriptorArgs = mDescriptorsArgs[hashCode]
|
||||
if (descriptorArgs == null) {
|
||||
val status = BluetoothGatt.GATT_FAILURE
|
||||
server.sendResponse(device, requestId, status, offset, null)
|
||||
} else {
|
||||
val hashCodeArgs = descriptorArgs.hashCodeArgs
|
||||
mAPI.onDescriptorReadRequest(centralArgs, idArgs, offsetArgs, hashCodeArgs) {}
|
||||
}
|
||||
}
|
||||
|
||||
fun onDescriptorWriteRequest(device: BluetoothDevice, requestId: Int, descriptor: BluetoothGattDescriptor, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
|
||||
val centralArgs = device.toCentralArgs()
|
||||
val idArgs = requestId.args
|
||||
val hashCode = descriptor.hashCode()
|
||||
val descriptorArgs = mDescriptorsArgs[hashCode]
|
||||
if (descriptorArgs == null) {
|
||||
if (!responseNeeded) {
|
||||
return
|
||||
}
|
||||
val status = BluetoothGatt.GATT_FAILURE
|
||||
server.sendResponse(device, requestId, status, offset, null)
|
||||
} else {
|
||||
val hashCodeArgs = descriptorArgs.hashCodeArgs
|
||||
val offsetArgs = offset.args
|
||||
mAPI.onDescriptorWriteRequest(centralArgs, idArgs, hashCodeArgs, preparedWrite, responseNeeded, offsetArgs, value) {}
|
||||
}
|
||||
}
|
||||
|
||||
fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
|
||||
val centralArgs = device.toCentralArgs()
|
||||
val idArgs = requestId.args
|
||||
mAPI.onExecuteWrite(centralArgs, idArgs, execute) {}
|
||||
}
|
||||
|
||||
private fun addServiceArgs(serviceArgs: MyMutableGATTServiceArgs): BluetoothGattService {
|
||||
val service = serviceArgs.toService()
|
||||
this.mServicesArgs[service.hashCode] = serviceArgs
|
||||
this.mServices[serviceArgs.hashCodeArgs] = service
|
||||
val includedServicesArgs = serviceArgs.includedServicesArgs.requireNoNulls()
|
||||
for (includedServiceArgs in includedServicesArgs) {
|
||||
val includedService = addServiceArgs(includedServiceArgs)
|
||||
val adding = service.addService(includedService)
|
||||
if (!adding) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
val characteristicsArgs = serviceArgs.characteristicsArgs.requireNoNulls()
|
||||
for (characteristicArgs in characteristicsArgs) {
|
||||
val characteristic = characteristicArgs.toCharacteristic()
|
||||
this.mCharacteristicsArgs[characteristic.hashCode] = characteristicArgs
|
||||
this.mCharacteristics[characteristicArgs.hashCodeArgs] = characteristic
|
||||
val descriptorsArgs = characteristicArgs.descriptorsArgs.requireNoNulls()
|
||||
for (descriptorArgs in descriptorsArgs) {
|
||||
val descriptor = descriptorArgs.toDescriptor()
|
||||
this.mDescriptorsArgs[descriptor.hashCode] = descriptorArgs
|
||||
this.mDescriptors[descriptorArgs.hashCodeArgs] = descriptor
|
||||
val descriptorAdded = characteristic.addDescriptor(descriptor)
|
||||
if (!descriptorAdded) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
val characteristicAdded = service.addCharacteristic(characteristic)
|
||||
if (!characteristicAdded) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
return service
|
||||
}
|
||||
|
||||
private fun removeServiceArgs(serviceArgs: MyMutableGATTServiceArgs) {
|
||||
val includedServicesArgs = serviceArgs.includedServicesArgs.requireNoNulls()
|
||||
for (includedServiceArgs in includedServicesArgs) {
|
||||
removeServiceArgs(includedServiceArgs)
|
||||
}
|
||||
val characteristicsArgs = serviceArgs.characteristicsArgs.requireNoNulls()
|
||||
for (characteristicArgs in characteristicsArgs) {
|
||||
val descriptorsArgs = characteristicArgs.descriptorsArgs.requireNoNulls()
|
||||
for (descriptorArgs in descriptorsArgs) {
|
||||
val descriptor = mDescriptors.remove(descriptorArgs.hashCodeArgs) ?: throw IllegalArgumentException()
|
||||
this.mDescriptorsArgs.remove(descriptor.hashCode)
|
||||
}
|
||||
val characteristic = mCharacteristics.remove(characteristicArgs.hashCodeArgs) ?: throw IllegalArgumentException()
|
||||
this.mCharacteristicsArgs.remove(characteristic.hashCode)
|
||||
}
|
||||
val service = mServices.remove(serviceArgs.hashCodeArgs) ?: throw IllegalArgumentException()
|
||||
mServicesArgs.remove(service.hashCode)
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import io.flutter.plugin.common.PluginRegistry
|
||||
|
||||
class MyRequestPermissionResultListener(manager: MyBluetoothLowEnergyManager) : PluginRegistry.RequestPermissionsResultListener {
|
||||
private val mManager: MyBluetoothLowEnergyManager
|
||||
|
||||
init {
|
||||
mManager = manager
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean {
|
||||
return mManager.onRequestPermissionsResult(requestCode, permissions, results)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
import android.bluetooth.le.ScanCallback
|
||||
import android.bluetooth.le.ScanResult
|
@ -1,270 +0,0 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattDescriptor
|
||||
import android.bluetooth.BluetoothGattService
|
||||
import android.bluetooth.le.AdvertiseData
|
||||
import android.bluetooth.le.ScanResult
|
||||
import android.os.ParcelUuid
|
||||
import android.util.SparseArray
|
||||
import java.util.UUID
|
||||
|
||||
//region ToObj
|
||||
fun MyGattCharacteristicWriteTypeArgs.toType(): Int {
|
||||
return when (this) {
|
||||
MyGattCharacteristicWriteTypeArgs.WITHRESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
||||
MyGattCharacteristicWriteTypeArgs.WITHOUTRESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||
}
|
||||
}
|
||||
|
||||
fun MyGattCharacteristicNotifyStateArgs.toValue(): ByteArray {
|
||||
return when (this) {
|
||||
MyGattCharacteristicNotifyStateArgs.NONE -> BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
|
||||
MyGattCharacteristicNotifyStateArgs.NOTIFY -> BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||
MyGattCharacteristicNotifyStateArgs.INDICATE -> BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
fun MyGattStatusArgs.toStatus(): Int {
|
||||
return when (this) {
|
||||
MyGattStatusArgs.SUCCESS -> BluetoothGatt.GATT_SUCCESS
|
||||
MyGattStatusArgs.READNOTPERMITTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
|
||||
MyGattStatusArgs.WRITENOTPERMITTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
|
||||
MyGattStatusArgs.REQUESTNOTSUPPORTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
|
||||
MyGattStatusArgs.INVALIDOFFSET -> BluetoothGatt.GATT_INVALID_OFFSET
|
||||
MyGattStatusArgs.INSUFFICIENTAUTHENTICATION -> BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION
|
||||
MyGattStatusArgs.INSUFFICIENTENCRYPTION -> BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION
|
||||
MyGattStatusArgs.INVALIDATTRIBUTELENGTH -> BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH
|
||||
MyGattStatusArgs.CONNECTIONCONGESTED -> BluetoothGatt.GATT_CONNECTION_CONGESTED
|
||||
MyGattStatusArgs.FAILURE -> BluetoothGatt.GATT_FAILURE
|
||||
}
|
||||
}
|
||||
|
||||
fun MyAdvertisementArgs.toAdvertiseData(adapter: BluetoothAdapter): AdvertiseData {
|
||||
val advertiseDataBuilder = AdvertiseData.Builder()
|
||||
if (nameArgs == null) {
|
||||
advertiseDataBuilder.setIncludeDeviceName(false)
|
||||
} else {
|
||||
// TODO: There is an issue that Android will use the cached name before setName takes effect.
|
||||
// see https://stackoverflow.com/questions/8377558/change-the-android-bluetooth-device-name
|
||||
adapter.name = nameArgs
|
||||
advertiseDataBuilder.setIncludeDeviceName(true)
|
||||
}
|
||||
for (serviceUuidArgs in serviceUUIDsArgs) {
|
||||
val serviceUUID = ParcelUuid.fromString(serviceUuidArgs)
|
||||
advertiseDataBuilder.addServiceUuid(serviceUUID)
|
||||
}
|
||||
for (entry in serviceDataArgs) {
|
||||
val serviceDataUUID = ParcelUuid.fromString(entry.key as String)
|
||||
val serviceData = entry.value as ByteArray
|
||||
advertiseDataBuilder.addServiceData(serviceDataUUID, serviceData)
|
||||
}
|
||||
if (manufacturerSpecificDataArgs != null) {
|
||||
val manufacturerId = manufacturerSpecificDataArgs.idArgs.toInt()
|
||||
val manufacturerSpecificData = manufacturerSpecificDataArgs.dataArgs
|
||||
advertiseDataBuilder.addManufacturerData(manufacturerId, manufacturerSpecificData)
|
||||
}
|
||||
return advertiseDataBuilder.build()
|
||||
}
|
||||
|
||||
fun MyGattDescriptorArgs.toDescriptor(): BluetoothGattDescriptor {
|
||||
val uuid = UUID.fromString(uuidArgs)
|
||||
val permissions =
|
||||
BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE
|
||||
return BluetoothGattDescriptor(uuid, permissions)
|
||||
}
|
||||
|
||||
fun MyGattCharacteristicArgs.toCharacteristic(): BluetoothGattCharacteristic {
|
||||
val uuid = UUID.fromString(uuidArgs)
|
||||
val properties = getProperties()
|
||||
val permissions = getPermissions()
|
||||
return BluetoothGattCharacteristic(uuid, properties, permissions)
|
||||
}
|
||||
|
||||
fun MyGattCharacteristicArgs.getProperties(): Int {
|
||||
val propertiesArgs = propertyNumbersArgs.filterNotNull().map { args ->
|
||||
val raw = args.toInt()
|
||||
MyGattCharacteristicPropertyArgs.ofRaw(raw)
|
||||
}
|
||||
val read = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.READ)
|
||||
val write = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITE)
|
||||
val writeWithoutResponse =
|
||||
propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITEWITHOUTRESPONSE)
|
||||
val notify = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.NOTIFY)
|
||||
val indicate = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.INDICATE)
|
||||
var properties = 0
|
||||
if (read) properties = properties or BluetoothGattCharacteristic.PROPERTY_READ
|
||||
if (write) properties = properties or BluetoothGattCharacteristic.PROPERTY_WRITE
|
||||
if (writeWithoutResponse) properties =
|
||||
properties or BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE
|
||||
if (notify) properties = properties or BluetoothGattCharacteristic.PROPERTY_NOTIFY
|
||||
if (indicate) properties = properties or BluetoothGattCharacteristic.PROPERTY_INDICATE
|
||||
return properties
|
||||
}
|
||||
|
||||
fun MyGattCharacteristicArgs.getPermissions(): Int {
|
||||
val propertiesArgs = propertyNumbersArgs.filterNotNull().map { args ->
|
||||
val raw = args.toInt()
|
||||
MyGattCharacteristicPropertyArgs.ofRaw(raw)
|
||||
}
|
||||
val read = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.READ)
|
||||
val write = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITE)
|
||||
val writeWithoutResponse =
|
||||
propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITEWITHOUTRESPONSE)
|
||||
var permissions = 0
|
||||
if (read) permissions = permissions or BluetoothGattCharacteristic.PERMISSION_READ
|
||||
if (write || writeWithoutResponse) permissions =
|
||||
permissions or BluetoothGattCharacteristic.PERMISSION_WRITE
|
||||
return permissions
|
||||
}
|
||||
|
||||
fun MyGattServiceArgs.toService(): BluetoothGattService {
|
||||
val uuid = UUID.fromString(uuidArgs)
|
||||
val serviceType = BluetoothGattService.SERVICE_TYPE_PRIMARY
|
||||
return BluetoothGattService(uuid, serviceType)
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region ToArgs
|
||||
fun Int.toBluetoothLowEnergyStateArgs(): MyBluetoothLowEnergyStateArgs {
|
||||
return when (this) {
|
||||
BluetoothAdapter.STATE_OFF -> MyBluetoothLowEnergyStateArgs.OFF
|
||||
BluetoothAdapter.STATE_TURNING_ON -> MyBluetoothLowEnergyStateArgs.TURNINGON
|
||||
BluetoothAdapter.STATE_ON -> MyBluetoothLowEnergyStateArgs.ON
|
||||
BluetoothAdapter.STATE_TURNING_OFF -> MyBluetoothLowEnergyStateArgs.TURNINGOFF
|
||||
else -> MyBluetoothLowEnergyStateArgs.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
fun SparseArray<ByteArray>.toManufacturerSpecificDataArgs(): MyManufacturerSpecificDataArgs? {
|
||||
var index = 0
|
||||
val size = size()
|
||||
val itemsArgs = mutableListOf<MyManufacturerSpecificDataArgs>()
|
||||
while (index < size) {
|
||||
val idArgs = keyAt(index).toLong()
|
||||
val dataArgs = valueAt(index)
|
||||
val itemArgs = MyManufacturerSpecificDataArgs(idArgs, dataArgs)
|
||||
itemsArgs.add(itemArgs)
|
||||
index++
|
||||
}
|
||||
return itemsArgs.lastOrNull()
|
||||
}
|
||||
|
||||
fun ScanResult.toAdvertisementArgs(): MyAdvertisementArgs {
|
||||
val record = scanRecord
|
||||
return if (record == null) {
|
||||
val nameArgs = null
|
||||
val serviceUUIDsArgs = emptyList<String?>()
|
||||
val serviceDataArgs = emptyMap<String?, ByteArray>()
|
||||
val manufacturerSpecificDataArgs = null
|
||||
MyAdvertisementArgs(
|
||||
nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs
|
||||
)
|
||||
} else {
|
||||
val nameArgs = record.deviceName
|
||||
val serviceUUIDsArgs = record.serviceUuids?.map { uuid -> uuid.toString() } ?: emptyList()
|
||||
val pairs = record.serviceData?.map { (uuid, value) ->
|
||||
val key = uuid.toString()
|
||||
return@map Pair(key, value)
|
||||
}?.toTypedArray() ?: emptyArray()
|
||||
val serviceDataArgs = mapOf<String?, ByteArray?>(*pairs)
|
||||
val manufacturerSpecificDataArgs =
|
||||
record.manufacturerSpecificData?.toManufacturerSpecificDataArgs()
|
||||
MyAdvertisementArgs(
|
||||
nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun BluetoothDevice.toCentralArgs(): MyCentralArgs {
|
||||
val addressArgs = address
|
||||
return MyCentralArgs(addressArgs)
|
||||
}
|
||||
|
||||
fun BluetoothDevice.toPeripheralArgs(): MyPeripheralArgs {
|
||||
val addressArgs = address
|
||||
return MyPeripheralArgs(addressArgs)
|
||||
}
|
||||
|
||||
fun BluetoothGattService.toArgs(): MyGattServiceArgs {
|
||||
val hashCodeArgs = hashCode().toLong()
|
||||
val uuidArgs = this.uuid.toString()
|
||||
val characteristicsArgs = characteristics.map { it.toArgs() }
|
||||
return MyGattServiceArgs(hashCodeArgs, uuidArgs, characteristicsArgs)
|
||||
}
|
||||
|
||||
fun BluetoothGattCharacteristic.toArgs(): MyGattCharacteristicArgs {
|
||||
val hashCodeArgs = hashCode().toLong()
|
||||
val uuidArgs = this.uuid.toString()
|
||||
val propertyNumbersArgs = getPropertyNumbersArgs()
|
||||
val descriptorsArgs = descriptors.map { it.toArgs() }
|
||||
return MyGattCharacteristicArgs(hashCodeArgs, uuidArgs, propertyNumbersArgs, descriptorsArgs)
|
||||
}
|
||||
|
||||
fun BluetoothGattCharacteristic.getPropertyNumbersArgs(): List<Long> {
|
||||
val numbersArgs = mutableListOf<Long>()
|
||||
if (properties and BluetoothGattCharacteristic.PROPERTY_READ != 0) {
|
||||
val number = MyGattCharacteristicPropertyArgs.READ.raw.toLong()
|
||||
numbersArgs.add(number)
|
||||
}
|
||||
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) {
|
||||
val number = MyGattCharacteristicPropertyArgs.WRITE.raw.toLong()
|
||||
numbersArgs.add(number)
|
||||
}
|
||||
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) {
|
||||
val number = MyGattCharacteristicPropertyArgs.WRITEWITHOUTRESPONSE.raw.toLong()
|
||||
numbersArgs.add(number)
|
||||
}
|
||||
if (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {
|
||||
val number = MyGattCharacteristicPropertyArgs.NOTIFY.raw.toLong()
|
||||
numbersArgs.add(number)
|
||||
}
|
||||
if (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) {
|
||||
val number = MyGattCharacteristicPropertyArgs.INDICATE.raw.toLong()
|
||||
numbersArgs.add(number)
|
||||
}
|
||||
return numbersArgs
|
||||
}
|
||||
|
||||
fun BluetoothGattDescriptor.toArgs(): MyGattDescriptorArgs {
|
||||
val hashCodeArgs = hashCode().toLong()
|
||||
val uuidArgs = this.uuid.toString()
|
||||
return MyGattDescriptorArgs(hashCodeArgs, uuidArgs, null)
|
||||
}
|
||||
|
||||
fun ByteArray.toNotifyStateArgs(): MyGattCharacteristicNotifyStateArgs {
|
||||
return if (this contentEquals BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) {
|
||||
MyGattCharacteristicNotifyStateArgs.NOTIFY
|
||||
} else if (this contentEquals BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) {
|
||||
MyGattCharacteristicNotifyStateArgs.INDICATE
|
||||
} else if (this contentEquals BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE) {
|
||||
MyGattCharacteristicNotifyStateArgs.NONE
|
||||
} else {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
val Any.TAG get() = this::class.java.simpleName as String
|
||||
|
||||
//val ScanRecord.rawValues: Map<Byte, ByteArray>
|
||||
// get() {
|
||||
// val rawValues = mutableMapOf<Byte, ByteArray>()
|
||||
// var index = 0
|
||||
// val size = bytes.size
|
||||
// while (index < size) {
|
||||
// val length = bytes[index++].toInt() and 0xff
|
||||
// if (length == 0) {
|
||||
// break
|
||||
// }
|
||||
// val end = index + length
|
||||
// val type = bytes[index++]
|
||||
// val value = bytes.slice(index until end).toByteArray()
|
||||
// rawValues[type] = value
|
||||
// index = end
|
||||
// }
|
||||
// return rawValues.toMap()
|
||||
// }
|
@ -1,106 +0,0 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
abstract class MyBluetoothLowEnergyManager(context: Context) {
|
||||
companion object {
|
||||
val CLIENT_CHARACTERISTIC_CONFIG_UUID =
|
||||
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") as UUID
|
||||
}
|
||||
|
||||
private val mContext: Context
|
||||
|
||||
private val mRequestPermissionsResultListener: RequestPermissionsResultListener by lazy {
|
||||
MyRequestPermissionResultListener(this)
|
||||
}
|
||||
private val mBroadcastReceiver: BroadcastReceiver by lazy {
|
||||
MyBroadcastReceiver(this)
|
||||
}
|
||||
|
||||
private var mRegistered: Boolean
|
||||
private lateinit var mBinding: ActivityPluginBinding
|
||||
|
||||
init {
|
||||
mContext = context
|
||||
mRegistered = false
|
||||
}
|
||||
|
||||
protected val executor get() = ContextCompat.getMainExecutor(mContext) as Executor
|
||||
protected val hasFeature get() = mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
|
||||
protected val manager
|
||||
get() = ContextCompat.getSystemService(
|
||||
mContext, BluetoothManager::class.java
|
||||
) as BluetoothManager
|
||||
protected val adapter get() = manager.adapter as BluetoothAdapter
|
||||
|
||||
protected fun checkPermissions(): Boolean {
|
||||
return permissions.all { permission ->
|
||||
ActivityCompat.checkSelfPermission(
|
||||
mContext, permission
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
|
||||
protected fun requestPermissions() {
|
||||
val activity = mBinding.activity
|
||||
ActivityCompat.requestPermissions(activity, permissions, requestCode)
|
||||
}
|
||||
|
||||
fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||
binding.addRequestPermissionsResultListener(mRequestPermissionsResultListener)
|
||||
mBinding = binding
|
||||
}
|
||||
|
||||
fun onDetachedFromActivity() {
|
||||
mBinding.removeRequestPermissionsResultListener(mRequestPermissionsResultListener)
|
||||
}
|
||||
|
||||
fun onRequestPermissionsResult(
|
||||
requestCode: Int, permissions: Array<out String>, results: IntArray
|
||||
): Boolean {
|
||||
if (this.requestCode != requestCode) {
|
||||
return false
|
||||
}
|
||||
val granted =
|
||||
permissions.contentEquals(this.permissions) && results.all { r -> r == PackageManager.PERMISSION_GRANTED }
|
||||
onPermissionsRequested(granted)
|
||||
return true
|
||||
}
|
||||
|
||||
fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action
|
||||
if (action != BluetoothAdapter.ACTION_STATE_CHANGED) {
|
||||
return
|
||||
}
|
||||
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
|
||||
onAdapterStateChanged(state)
|
||||
}
|
||||
|
||||
protected fun registerReceiver() {
|
||||
if (mRegistered) {
|
||||
return
|
||||
}
|
||||
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
|
||||
mContext.registerReceiver(mBroadcastReceiver, filter)
|
||||
mRegistered = true
|
||||
}
|
||||
|
||||
abstract val permissions: Array<String>
|
||||
abstract val requestCode: Int
|
||||
|
||||
abstract fun onPermissionsRequested(granted: Boolean)
|
||||
abstract fun onAdapterStateChanged(state: Int)
|
||||
}
|
||||
|
@ -1,461 +0,0 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattDescriptor
|
||||
import android.bluetooth.BluetoothGattServer
|
||||
import android.bluetooth.BluetoothGattServerCallback
|
||||
import android.bluetooth.BluetoothGattService
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.bluetooth.BluetoothStatusCodes
|
||||
import android.bluetooth.le.AdvertiseCallback
|
||||
import android.bluetooth.le.AdvertiseSettings
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
|
||||
class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) :
|
||||
MyBluetoothLowEnergyManager(context), MyPeripheralManagerHostApi {
|
||||
companion object {
|
||||
const val REQUEST_CODE = 445
|
||||
}
|
||||
|
||||
private val advertiser get() = adapter.bluetoothLeAdvertiser
|
||||
|
||||
private val mContext: Context
|
||||
private val mApi: MyPeripheralManagerFlutterApi
|
||||
|
||||
private val bluetoothGattServerCallback: BluetoothGattServerCallback by lazy {
|
||||
MyBluetoothGattServerCallback(this, executor)
|
||||
}
|
||||
private val advertiseCallback: AdvertiseCallback by lazy {
|
||||
MyAdvertiseCallback(this)
|
||||
}
|
||||
|
||||
private lateinit var mServer: BluetoothGattServer
|
||||
private var mOpening = false
|
||||
private var mAdvertising = false
|
||||
|
||||
private val mServicesArgs: MutableMap<Int, MyGattServiceArgs>
|
||||
private val mCharacteristicsArgs: MutableMap<Int, MyGattCharacteristicArgs>
|
||||
private val mDescriptorsArgs: MutableMap<Int, MyGattDescriptorArgs>
|
||||
|
||||
private val mDevices: MutableMap<String, BluetoothDevice>
|
||||
private val mServices: MutableMap<Long, BluetoothGattService>
|
||||
private val mCharacteristics: MutableMap<Long, BluetoothGattCharacteristic>
|
||||
private val mDescriptors: MutableMap<Long, BluetoothGattDescriptor>
|
||||
|
||||
private var mSetUpCallback: ((Result<Unit>) -> Unit)?
|
||||
private var mAddServiceCallback: ((Result<Unit>) -> Unit)?
|
||||
private var mStartAdvertisingCallback: ((Result<Unit>) -> Unit)?
|
||||
private val mNotifyCharacteristicValueChangedCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
|
||||
|
||||
init {
|
||||
mContext = context
|
||||
mApi = MyPeripheralManagerFlutterApi(binaryMessenger)
|
||||
|
||||
mServicesArgs = mutableMapOf()
|
||||
mCharacteristicsArgs = mutableMapOf()
|
||||
mDescriptorsArgs = mutableMapOf()
|
||||
|
||||
mDevices = mutableMapOf()
|
||||
mServices = mutableMapOf()
|
||||
mCharacteristics = mutableMapOf()
|
||||
mDescriptors = mutableMapOf()
|
||||
|
||||
mSetUpCallback = null
|
||||
mAddServiceCallback = null
|
||||
mStartAdvertisingCallback = null
|
||||
mNotifyCharacteristicValueChangedCallbacks = mutableMapOf()
|
||||
}
|
||||
|
||||
override val permissions: Array<String>
|
||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
arrayOf(
|
||||
android.Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||
android.Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
android.Manifest.permission.BLUETOOTH_ADVERTISE,
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT
|
||||
)
|
||||
} else {
|
||||
arrayOf(
|
||||
android.Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||
android.Manifest.permission.ACCESS_FINE_LOCATION
|
||||
)
|
||||
}
|
||||
|
||||
override val requestCode: Int
|
||||
get() = REQUEST_CODE
|
||||
|
||||
override fun setUp(callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
mClearState()
|
||||
val stateArgs = if (hasFeature) {
|
||||
val granted = checkPermissions()
|
||||
if (granted) {
|
||||
registerReceiver()
|
||||
adapter.state.toBluetoothLowEnergyStateArgs()
|
||||
} else {
|
||||
requestPermissions()
|
||||
mSetUpCallback = callback
|
||||
return
|
||||
}
|
||||
} else MyBluetoothLowEnergyStateArgs.UNSUPPORTED
|
||||
mOnStateChanged(stateArgs)
|
||||
callback(Result.success(Unit))
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun addService(serviceArgs: MyGattServiceArgs, callback: (Result<Unit>) -> Unit) {
|
||||
try {
|
||||
val service = serviceArgs.toService()
|
||||
val characteristicsArgs = serviceArgs.characteristicsArgs.filterNotNull()
|
||||
for (characteristicArgs in characteristicsArgs) {
|
||||
val characteristic = characteristicArgs.toCharacteristic()
|
||||
val descriptorsArgs = characteristicArgs.descriptorsArgs.filterNotNull()
|
||||
for (descriptorArgs in descriptorsArgs) {
|
||||
val descriptor = descriptorArgs.toDescriptor()
|
||||
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
|
||||
val descriptorHashCode = descriptor.hashCode()
|
||||
this.mDescriptorsArgs[descriptorHashCode] = descriptorArgs
|
||||
this.mDescriptors[descriptorHashCodeArgs] = descriptor
|
||||
val descriptorAdded = characteristic.addDescriptor(descriptor)
|
||||
if (!descriptorAdded) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
|
||||
val characteristicHashCode = characteristic.hashCode()
|
||||
this.mCharacteristicsArgs[characteristicHashCode] = characteristicArgs
|
||||
this.mCharacteristics[characteristicHashCodeArgs] = characteristic
|
||||
val characteristicAdded = service.addCharacteristic(characteristic)
|
||||
if (!characteristicAdded) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
val serviceHashCodeArgs = serviceArgs.hashCodeArgs
|
||||
val serviceHashCode = service.hashCode()
|
||||
this.mServicesArgs[serviceHashCode] = serviceArgs
|
||||
this.mServices[serviceHashCodeArgs] = service
|
||||
val adding = mServer.addService(service)
|
||||
if (!adding) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
mAddServiceCallback = callback
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeService(hashCodeArgs: Long) {
|
||||
val service = mServices.remove(hashCodeArgs) as BluetoothGattService
|
||||
val removed = mServer.removeService(service)
|
||||
if (!removed) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
val hashCode = service.hashCode()
|
||||
val serviceArgs = mServicesArgs.remove(hashCode) as MyGattServiceArgs
|
||||
val characteristicsArgs = serviceArgs.characteristicsArgs.filterNotNull()
|
||||
for (characteristicArgs in characteristicsArgs) {
|
||||
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
|
||||
val characteristic =
|
||||
mCharacteristics.remove(characteristicHashCodeArgs) as BluetoothGattCharacteristic
|
||||
val characteristicHashCode = characteristic.hashCode()
|
||||
mCharacteristicsArgs.remove(characteristicHashCode)
|
||||
val descriptorsArgs = characteristicArgs.descriptorsArgs.filterNotNull()
|
||||
for (descriptorArgs in descriptorsArgs) {
|
||||
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
|
||||
val descriptor =
|
||||
mDescriptors.remove(descriptorHashCodeArgs) as BluetoothGattDescriptor
|
||||
val descriptorHashCode = descriptor.hashCode()
|
||||
mDescriptorsArgs.remove(descriptorHashCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearServices() {
|
||||
mServer.clearServices()
|
||||
mServices.clear()
|
||||
mCharacteristics.clear()
|
||||
mDescriptors.clear()
|
||||
|
||||
mServicesArgs.clear()
|
||||
mCharacteristicsArgs.clear()
|
||||
mDescriptorsArgs.clear()
|
||||
}
|
||||
|
||||
override fun startAdvertising(
|
||||
advertisementArgs: MyAdvertisementArgs, callback: (Result<Unit>) -> Unit
|
||||
) {
|
||||
try {
|
||||
val settings = AdvertiseSettings.Builder()
|
||||
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).setConnectable(true)
|
||||
.build()
|
||||
val advertiseData = advertisementArgs.toAdvertiseData(adapter)
|
||||
advertiser.startAdvertising(settings, advertiseData, advertiseCallback)
|
||||
mStartAdvertisingCallback = callback
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopAdvertising() {
|
||||
advertiser.stopAdvertising(advertiseCallback)
|
||||
mAdvertising = false
|
||||
}
|
||||
|
||||
override fun sendResponse(
|
||||
addressArgs: String,
|
||||
idArgs: Long,
|
||||
statusNumberArgs: Long,
|
||||
offsetArgs: Long,
|
||||
valueArgs: ByteArray?
|
||||
) {
|
||||
val device = mDevices[addressArgs] as BluetoothDevice
|
||||
val requestId = idArgs.toInt()
|
||||
val statusRawArgs = statusNumberArgs.toInt()
|
||||
val statusArgs = MyGattStatusArgs.ofRaw(statusRawArgs) ?: throw IllegalArgumentException()
|
||||
val status = statusArgs.toStatus()
|
||||
val offset = offsetArgs.toInt()
|
||||
val sent = mServer.sendResponse(device, requestId, status, offset, valueArgs)
|
||||
if (!sent) {
|
||||
throw IllegalStateException("Send response failed.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun notifyCharacteristicChanged(
|
||||
hashCodeArgs: Long,
|
||||
valueArgs: ByteArray,
|
||||
confirmArgs: Boolean,
|
||||
addressArgs: String,
|
||||
callback: (Result<Unit>) -> Unit
|
||||
) {
|
||||
try {
|
||||
val device = mDevices[addressArgs] as BluetoothDevice
|
||||
val characteristic = mCharacteristics[hashCodeArgs] as BluetoothGattCharacteristic
|
||||
val notifying = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val statusCode = mServer.notifyCharacteristicChanged(
|
||||
device, characteristic, confirmArgs, valueArgs
|
||||
)
|
||||
statusCode == BluetoothStatusCodes.SUCCESS
|
||||
} else {
|
||||
// TODO: remove this when minSdkVersion >= 33
|
||||
characteristic.value = valueArgs
|
||||
mServer.notifyCharacteristicChanged(device, characteristic, confirmArgs)
|
||||
}
|
||||
if (!notifying) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
mNotifyCharacteristicValueChangedCallbacks[addressArgs] = callback
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPermissionsRequested(granted: Boolean) {
|
||||
val callback = mSetUpCallback ?: return
|
||||
val stateArgs = if (granted) {
|
||||
registerReceiver()
|
||||
adapter.state.toBluetoothLowEnergyStateArgs()
|
||||
} else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
|
||||
mOnStateChanged(stateArgs)
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
|
||||
override fun onAdapterStateChanged(state: Int) {
|
||||
val stateArgs = state.toBluetoothLowEnergyStateArgs()
|
||||
mOnStateChanged(stateArgs)
|
||||
}
|
||||
|
||||
fun onServiceAdded(status: Int, service: BluetoothGattService) {
|
||||
val callback = mAddServiceCallback ?: return
|
||||
mAddServiceCallback = null
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
callback(Result.success(Unit))
|
||||
} else {
|
||||
val error = IllegalStateException("Read rssi failed with status: $status")
|
||||
callback(Result.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
|
||||
mAdvertising = true
|
||||
val callback = mStartAdvertisingCallback ?: return
|
||||
mStartAdvertisingCallback = null
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
|
||||
fun onStartFailure(errorCode: Int) {
|
||||
val callback = mStartAdvertisingCallback ?: return
|
||||
mStartAdvertisingCallback = null
|
||||
val error = IllegalStateException("Start advertising failed with error code: $errorCode")
|
||||
callback(Result.failure(error))
|
||||
}
|
||||
|
||||
fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
|
||||
val centralArgs = device.toCentralArgs()
|
||||
val addressArgs = centralArgs.addressArgs
|
||||
val stateArgs = newState == BluetoothProfile.STATE_CONNECTED
|
||||
mDevices[addressArgs] = device
|
||||
mApi.onConnectionStateChanged(centralArgs, stateArgs) {}
|
||||
}
|
||||
|
||||
fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
|
||||
val addressArgs = device.address
|
||||
val mtuArgs = mtu.toLong()
|
||||
mApi.onMtuChanged(addressArgs, mtuArgs) {}
|
||||
}
|
||||
|
||||
fun onCharacteristicReadRequest(
|
||||
device: BluetoothDevice,
|
||||
requestId: Int,
|
||||
offset: Int,
|
||||
characteristic: BluetoothGattCharacteristic
|
||||
) {
|
||||
val addressArgs = device.address
|
||||
val hashCode = characteristic.hashCode()
|
||||
val characteristicArgs = mCharacteristicsArgs[hashCode] ?: return
|
||||
val hashCodeArgs = characteristicArgs.hashCodeArgs
|
||||
val idArgs = requestId.toLong()
|
||||
val offsetArgs = offset.toLong()
|
||||
mApi.onCharacteristicReadRequest(addressArgs, hashCodeArgs, idArgs, offsetArgs) {}
|
||||
}
|
||||
|
||||
fun onCharacteristicWriteRequest(
|
||||
device: BluetoothDevice,
|
||||
requestId: Int,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
preparedWrite: Boolean,
|
||||
responseNeeded: Boolean,
|
||||
offset: Int,
|
||||
value: ByteArray
|
||||
) {
|
||||
val addressArgs = device.address
|
||||
val hashCode = characteristic.hashCode()
|
||||
val characteristicArgs = mCharacteristicsArgs[hashCode] ?: return
|
||||
val hashCodeArgs = characteristicArgs.hashCodeArgs
|
||||
val idArgs = requestId.toLong()
|
||||
val offsetArgs = offset.toLong()
|
||||
mApi.onCharacteristicWriteRequest(
|
||||
addressArgs, hashCodeArgs, idArgs, offsetArgs, value, preparedWrite, responseNeeded
|
||||
) {}
|
||||
}
|
||||
|
||||
fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
|
||||
val addressArgs = device.address
|
||||
val idArgs = requestId.toLong()
|
||||
mApi.onExecuteWrite(addressArgs, idArgs, execute) {}
|
||||
}
|
||||
|
||||
fun onDescriptorReadRequest(
|
||||
device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor
|
||||
) {
|
||||
val addressArgs = device.address
|
||||
val hashCode = descriptor.hashCode()
|
||||
val descriptorArgs = mDescriptorsArgs[hashCode] ?: return
|
||||
val hashCodeArgs = descriptorArgs.hashCodeArgs
|
||||
val idArgs = requestId.toLong()
|
||||
val offsetArgs = offset.toLong()
|
||||
mApi.onDescriptorReadRequest(addressArgs, hashCodeArgs, idArgs, offsetArgs) {}
|
||||
}
|
||||
|
||||
fun onDescriptorWriteRequest(
|
||||
device: BluetoothDevice,
|
||||
requestId: Int,
|
||||
descriptor: BluetoothGattDescriptor,
|
||||
preparedWrite: Boolean,
|
||||
responseNeeded: Boolean,
|
||||
offset: Int,
|
||||
value: ByteArray
|
||||
) {
|
||||
val addressArgs = device.address
|
||||
val hashCode = descriptor.hashCode()
|
||||
val descriptorArgs = mDescriptorsArgs[hashCode] ?: return
|
||||
val hashCodeArgs = descriptorArgs.hashCodeArgs
|
||||
val idArgs = requestId.toLong()
|
||||
val offsetArgs = offset.toLong()
|
||||
mApi.onDescriptorWriteRequest(
|
||||
addressArgs, hashCodeArgs, idArgs, offsetArgs, value, preparedWrite, responseNeeded
|
||||
) {}
|
||||
if (descriptor.uuid == CLIENT_CHARACTERISTIC_CONFIG_UUID) {
|
||||
val characteristic = descriptor.characteristic
|
||||
mOnCharacteristicNotifyStateChanged(device, characteristic, value)
|
||||
}
|
||||
}
|
||||
|
||||
fun onNotificationSent(device: BluetoothDevice, status: Int) {
|
||||
val addressArgs = device.address
|
||||
val callback = mNotifyCharacteristicValueChangedCallbacks.remove(addressArgs) ?: return
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
callback(Result.success(Unit))
|
||||
} else {
|
||||
val error =
|
||||
IllegalStateException("Notify characteristic value changed failed with status: $status")
|
||||
callback(Result.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
private fun mClearState() {
|
||||
if (mAdvertising) {
|
||||
stopAdvertising()
|
||||
}
|
||||
|
||||
mServicesArgs.clear()
|
||||
mCharacteristicsArgs.clear()
|
||||
mDescriptorsArgs.clear()
|
||||
|
||||
mDevices.clear()
|
||||
mServices.clear()
|
||||
mCharacteristics.clear()
|
||||
mDescriptors.clear()
|
||||
|
||||
mSetUpCallback = null
|
||||
mAddServiceCallback = null
|
||||
mStartAdvertisingCallback = null
|
||||
mNotifyCharacteristicValueChangedCallbacks.clear()
|
||||
}
|
||||
|
||||
private fun mOnStateChanged(stateArgs: MyBluetoothLowEnergyStateArgs) {
|
||||
val stateNumberArgs = stateArgs.raw.toLong()
|
||||
mApi.onStateChanged(stateNumberArgs) {}
|
||||
// Renew GATT server when bluetooth adapter state changed.
|
||||
when (stateArgs) {
|
||||
MyBluetoothLowEnergyStateArgs.OFF -> mCloseServer()
|
||||
MyBluetoothLowEnergyStateArgs.ON -> mOpenServer()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mOpenServer() {
|
||||
if (mOpening) {
|
||||
return
|
||||
}
|
||||
mServer = manager.openGattServer(mContext, bluetoothGattServerCallback)
|
||||
mOpening = true
|
||||
}
|
||||
|
||||
private fun mCloseServer() {
|
||||
if (!mOpening) {
|
||||
return
|
||||
}
|
||||
mServer.close()
|
||||
mOpening = false
|
||||
}
|
||||
|
||||
private fun mOnCharacteristicNotifyStateChanged(
|
||||
device: BluetoothDevice,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
value: ByteArray
|
||||
) {
|
||||
val addressArgs = device.address
|
||||
val hashCode = characteristic.hashCode()
|
||||
val characteristicArgs = mCharacteristicsArgs[hashCode] ?: return
|
||||
val hashCodeArgs = characteristicArgs.hashCodeArgs
|
||||
val stateArgs = value.toNotifyStateArgs()
|
||||
val stateNumberArgs = stateArgs.raw.toLong()
|
||||
mApi.onCharacteristicNotifyStateChanged(addressArgs, hashCodeArgs, stateNumberArgs) {}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
|
||||
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
|
||||
|
||||
class MyRequestPermissionResultListener(manager: MyBluetoothLowEnergyManager) :
|
||||
RequestPermissionsResultListener {
|
||||
private val mManager: MyBluetoothLowEnergyManager
|
||||
|
||||
init {
|
||||
mManager = manager
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int, permissions: Array<out String>, results: IntArray
|
||||
): Boolean {
|
||||
return mManager.onRequestPermissionsResult(requestCode, permissions, results)
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package dev.hebei.bluetooth_low_energy_android
|
||||
|
||||
/*
|
||||
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
|
||||
*
|
||||
* Once you have built the plugin's example app, you can run these tests from the command
|
||||
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
|
||||
* you can run them directly from IDEs that support JUnit such as Android Studio.
|
||||
*/
|
||||
|
@ -1,27 +0,0 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_android
|
||||
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import kotlin.test.Test
|
||||
import org.mockito.Mockito
|
||||
|
||||
/*
|
||||
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
|
||||
*
|
||||
* Once you have built the plugin's example app, you can run these tests from the command
|
||||
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
|
||||
* you can run them directly from IDEs that support JUnit such as Android Studio.
|
||||
*/
|
||||
|
||||
internal class BluetoothLowEnergyAndroidPluginTest {
|
||||
@Test
|
||||
fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
|
||||
val plugin = BluetoothLowEnergyAndroidPlugin()
|
||||
|
||||
val call = MethodCall("getPlatformVersion", null)
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
|
||||
}
|
||||
}
|
@ -27,7 +27,6 @@ migrate_working_dir/
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
@ -1,6 +1,6 @@
|
||||
# bluetooth_low_energy_example
|
||||
# bluetooth_low_energy_android_example
|
||||
|
||||
Demonstrates how to use the bluetooth_low_energy plugin.
|
||||
Demonstrates how to use the bluetooth_low_energy_android plugin.
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
@ -1,68 +1,58 @@
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
def localPropertiesFile = rootProject.file("local.properties")
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localPropertiesFile.withReader("UTF-8") { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
flutterVersionCode = "1"
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
def flutterVersionName = localProperties.getProperty("flutter.versionName")
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
flutterVersionName = "1.0"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "dev.yanshouwang.bluetooth_low_energy_example"
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
namespace = "dev.hebei.bluetooth_low_energy_android_example"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "dev.yanshouwang.bluetooth_low_energy_example"
|
||||
applicationId = "dev.hebei.bluetooth_low_energy_android_example"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
// minSdkVersion flutter.minSdkVersion
|
||||
minSdkVersion 21
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutterVersionCode.toInteger()
|
||||
versionName = flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
signingConfig = signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {}
|
||||
|
@ -1,12 +1,13 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="bluetooth_low_energy_example"
|
||||
android:label="bluetooth_low_energy_android_example"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
@ -30,4 +31,15 @@
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
@ -0,0 +1,5 @@
|
||||
package dev.hebei.bluetooth_low_energy_android_example
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity()
|
@ -1,6 +0,0 @@
|
||||
package dev.yanshouwang.bluetooth_low_energy_example
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
@ -1,16 +1,3 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
@ -18,12 +5,12 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
|
@ -1,3 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
||||
|
@ -5,16 +5,21 @@ pluginManagement {
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}
|
||||
settings.ext.flutterSdkPath = flutterSdkPath()
|
||||
}()
|
||||
|
||||
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
include ":app"
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.3.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
|
||||
}
|
||||
|
||||
apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||
include ":app"
|
||||
|
@ -6,17 +6,8 @@
|
||||
// For more information about Flutter integration tests, please see
|
||||
// https://docs.flutter.dev/cookbook/testing/integration/introduction
|
||||
|
||||
// import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// testWidgets('getPlatformVersion test', (WidgetTester tester) async {
|
||||
// final BluetoothLowEnergy plugin = BluetoothLowEnergy();
|
||||
// final String? version = await plugin.getPlatformVersion();
|
||||
// // The version string depends on the host platform running the test, so
|
||||
// // just assert that some non-empty string is returned.
|
||||
// expect(version?.isNotEmpty, true);
|
||||
// });
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
1
bluetooth_low_energy_android/example/lib/models.dart
Normal file
1
bluetooth_low_energy_android/example/lib/models.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'models/log.dart';
|
10
bluetooth_low_energy_android/example/lib/models/log.dart
Normal file
10
bluetooth_low_energy_android/example/lib/models/log.dart
Normal file
@ -0,0 +1,10 @@
|
||||
class Log {
|
||||
final DateTime time;
|
||||
final String type;
|
||||
final String message;
|
||||
|
||||
Log({
|
||||
required this.type,
|
||||
required this.message,
|
||||
}) : time = DateTime.now();
|
||||
}
|
85
bluetooth_low_energy_android/example/lib/router_config.dart
Normal file
85
bluetooth_low_energy_android/example/lib/router_config.dart
Normal file
@ -0,0 +1,85 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'view_models.dart';
|
||||
import 'views.dart';
|
||||
|
||||
final routerConfig = GoRouter(
|
||||
redirect: (context, state) {
|
||||
if (state.matchedLocation == '/') {
|
||||
return '/central';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
routes: [
|
||||
StatefulShellRoute(
|
||||
// builder: (context, state, navigationShell) {
|
||||
// return HomeView(navigationShell: navigationShell);
|
||||
// },
|
||||
builder: (context, state, navigationShell) => navigationShell,
|
||||
navigatorContainerBuilder: (context, navigationShell, children) {
|
||||
final navigators = children.mapIndexed(
|
||||
(index, element) {
|
||||
if (index == 0) {
|
||||
return ViewModelBinding(
|
||||
viewBuilder: (context) => element,
|
||||
viewModelBuilder: (context) => CentralManagerViewModel(),
|
||||
);
|
||||
} else {
|
||||
return element;
|
||||
}
|
||||
},
|
||||
).toList();
|
||||
return HomeView(
|
||||
navigationShell: navigationShell,
|
||||
navigators: navigators,
|
||||
);
|
||||
},
|
||||
branches: [
|
||||
StatefulShellBranch(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/central',
|
||||
builder: (context, state) {
|
||||
return const CentralManagerView();
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':uuid',
|
||||
builder: (context, state) {
|
||||
final uuidValue = state.pathParameters['uuid']!;
|
||||
final uuid = UUID.fromString(uuidValue);
|
||||
final viewModel =
|
||||
ViewModel.of<CentralManagerViewModel>(context);
|
||||
final eventArgs = viewModel.discoveries.firstWhere(
|
||||
(discovery) => discovery.peripheral.uuid == uuid);
|
||||
return ViewModelBinding(
|
||||
viewBuilder: (context) => PeripheralView(),
|
||||
viewModelBuilder: (context) =>
|
||||
PeripheralViewModel(eventArgs),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/peripheral',
|
||||
builder: (context, state) {
|
||||
return ViewModelBinding(
|
||||
viewBuilder: (context) => const PeripheralManagerView(),
|
||||
viewModelBuilder: (context) => PeripheralManagerViewModel(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
@ -0,0 +1,6 @@
|
||||
export 'view_models/central_manager_view_model.dart';
|
||||
export 'view_models/peripheral_view_model.dart';
|
||||
export 'view_models/service_view_model.dart';
|
||||
export 'view_models/characteristic_view_model.dart';
|
||||
export 'view_models/descriptor_view_model.dart';
|
||||
export 'view_models/peripheral_manager_view_model.dart';
|
@ -0,0 +1,74 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class CentralManagerViewModel extends ViewModel {
|
||||
final CentralManager _manager;
|
||||
final List<DiscoveredEventArgs> _discoveries;
|
||||
bool _discovering;
|
||||
|
||||
late final StreamSubscription _stateChangedSubscription;
|
||||
late final StreamSubscription _discoveredSubscription;
|
||||
|
||||
CentralManagerViewModel()
|
||||
: _manager = CentralManager()..logLevel = Level.INFO,
|
||||
_discoveries = [],
|
||||
_discovering = false {
|
||||
_stateChangedSubscription = _manager.stateChanged.listen((eventArgs) async {
|
||||
if (eventArgs.state == BluetoothLowEnergyState.unauthorized) {
|
||||
await _manager.authorize();
|
||||
}
|
||||
notifyListeners();
|
||||
});
|
||||
_discoveredSubscription = _manager.discovered.listen((eventArgs) {
|
||||
final peripheral = eventArgs.peripheral;
|
||||
final index = _discoveries.indexWhere((i) => i.peripheral == peripheral);
|
||||
if (index < 0) {
|
||||
_discoveries.add(eventArgs);
|
||||
} else {
|
||||
_discoveries[index] = eventArgs;
|
||||
}
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
BluetoothLowEnergyState get state => _manager.state;
|
||||
bool get discovering => _discovering;
|
||||
List<DiscoveredEventArgs> get discoveries => _discoveries;
|
||||
|
||||
Future<void> showAppSettings() async {
|
||||
await _manager.showAppSettings();
|
||||
}
|
||||
|
||||
Future<void> startDiscovery({
|
||||
List<UUID>? serviceUUIDs,
|
||||
}) async {
|
||||
if (_discovering) {
|
||||
return;
|
||||
}
|
||||
_discoveries.clear();
|
||||
await _manager.startDiscovery(
|
||||
serviceUUIDs: serviceUUIDs,
|
||||
);
|
||||
_discovering = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> stopDiscovery() async {
|
||||
if (!_discovering) {
|
||||
return;
|
||||
}
|
||||
await _manager.stopDiscovery();
|
||||
_discovering = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_stateChangedSubscription.cancel();
|
||||
_discoveredSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:bluetooth_low_energy_android_example/models.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
|
||||
import 'descriptor_view_model.dart';
|
||||
|
||||
class CharacteristicViewModel extends ViewModel {
|
||||
final CentralManager _manager;
|
||||
final Peripheral _peripheral;
|
||||
final GATTCharacteristic _characteristic;
|
||||
final List<DescriptorViewModel> _descriptorViewModels;
|
||||
final List<Log> _logs;
|
||||
|
||||
GATTCharacteristicWriteType _writeType;
|
||||
bool _notifyState;
|
||||
|
||||
late final StreamSubscription _characteristicNotifiedSubscription;
|
||||
|
||||
CharacteristicViewModel({
|
||||
required CentralManager manager,
|
||||
required Peripheral peripheral,
|
||||
required GATTCharacteristic characteristic,
|
||||
}) : _manager = manager,
|
||||
_peripheral = peripheral,
|
||||
_characteristic = characteristic,
|
||||
_descriptorViewModels = characteristic.descriptors
|
||||
.map((descriptor) => DescriptorViewModel(descriptor))
|
||||
.toList(),
|
||||
_logs = [],
|
||||
_writeType = GATTCharacteristicWriteType.withResponse,
|
||||
_notifyState = false {
|
||||
if (!canWrite && canWriteWithoutResponse) {
|
||||
_writeType = GATTCharacteristicWriteType.withoutResponse;
|
||||
}
|
||||
_characteristicNotifiedSubscription =
|
||||
_manager.characteristicNotified.listen((eventArgs) {
|
||||
if (eventArgs.characteristic != _characteristic) {
|
||||
return;
|
||||
}
|
||||
final log = Log(
|
||||
type: 'Notified',
|
||||
message: '[${eventArgs.value.length}] ${eventArgs.value}',
|
||||
);
|
||||
_logs.add(log);
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
UUID get uuid => _characteristic.uuid;
|
||||
bool get canRead =>
|
||||
_characteristic.properties.contains(GATTCharacteristicProperty.read);
|
||||
bool get canWrite =>
|
||||
_characteristic.properties.contains(GATTCharacteristicProperty.write);
|
||||
bool get canWriteWithoutResponse => _characteristic.properties
|
||||
.contains(GATTCharacteristicProperty.writeWithoutResponse);
|
||||
bool get canNotify =>
|
||||
_characteristic.properties.contains(GATTCharacteristicProperty.notify) ||
|
||||
_characteristic.properties.contains(GATTCharacteristicProperty.indicate);
|
||||
List<DescriptorViewModel> get descriptorViewModels => _descriptorViewModels;
|
||||
List<Log> get logs => _logs;
|
||||
GATTCharacteristicWriteType get writeType => _writeType;
|
||||
bool get notifyState => _notifyState;
|
||||
|
||||
Future<void> read() async {
|
||||
final value = await _manager.readCharacteristic(
|
||||
_peripheral,
|
||||
_characteristic,
|
||||
);
|
||||
final log = Log(
|
||||
type: 'Read',
|
||||
message: '[${value.length}] $value',
|
||||
);
|
||||
_logs.add(log);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setWriteType(GATTCharacteristicWriteType type) {
|
||||
if (type == GATTCharacteristicWriteType.withResponse && !canWrite) {
|
||||
throw ArgumentError.value(type);
|
||||
}
|
||||
if (type == GATTCharacteristicWriteType.withoutResponse &&
|
||||
!canWriteWithoutResponse) {
|
||||
throw ArgumentError.value(type);
|
||||
}
|
||||
_writeType = type;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> write(Uint8List value) async {
|
||||
// Fragments the value by maximumWriteLength.
|
||||
final fragmentSize = await _manager.getMaximumWriteLength(
|
||||
_peripheral,
|
||||
type: writeType,
|
||||
);
|
||||
var start = 0;
|
||||
while (start < value.length) {
|
||||
final end = start + fragmentSize;
|
||||
final fragmentedValue =
|
||||
end < value.length ? value.sublist(start, end) : value.sublist(start);
|
||||
final type = writeType;
|
||||
await _manager.writeCharacteristic(
|
||||
_peripheral,
|
||||
_characteristic,
|
||||
value: fragmentedValue,
|
||||
type: type,
|
||||
);
|
||||
final log = Log(
|
||||
type: type == GATTCharacteristicWriteType.withResponse
|
||||
? 'Write'
|
||||
: 'Write without response',
|
||||
message: '[${value.length}] $value',
|
||||
);
|
||||
_logs.add(log);
|
||||
notifyListeners();
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setNotifyState(bool state) async {
|
||||
await _manager.setCharacteristicNotifyState(
|
||||
_peripheral,
|
||||
_characteristic,
|
||||
state: state,
|
||||
);
|
||||
_notifyState = state;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clearLogs() {
|
||||
_logs.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_characteristicNotifiedSubscription.cancel();
|
||||
for (var descriptorViewModel in descriptorViewModels) {
|
||||
descriptorViewModel.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
|
||||
class DescriptorViewModel extends ViewModel {
|
||||
final GATTDescriptor _descriptor;
|
||||
|
||||
DescriptorViewModel(this._descriptor);
|
||||
|
||||
UUID get uuid => _descriptor.uuid;
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:bluetooth_low_energy_android_example/models.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class PeripheralManagerViewModel extends ViewModel {
|
||||
final PeripheralManager _manager;
|
||||
final List<Log> _logs;
|
||||
bool _advertising;
|
||||
|
||||
late final StreamSubscription _stateChangedSubscription;
|
||||
late final StreamSubscription _characteristicReadRequestedSubscription;
|
||||
late final StreamSubscription _characteristicWriteRequestedSubscription;
|
||||
late final StreamSubscription _characteristicNotifyStateChangedSubscription;
|
||||
|
||||
PeripheralManagerViewModel()
|
||||
: _manager = PeripheralManager()..logLevel = Level.INFO,
|
||||
_logs = [],
|
||||
_advertising = false {
|
||||
_stateChangedSubscription = _manager.stateChanged.listen((eventArgs) async {
|
||||
if (eventArgs.state == BluetoothLowEnergyState.unauthorized) {
|
||||
await _manager.authorize();
|
||||
}
|
||||
notifyListeners();
|
||||
});
|
||||
_characteristicReadRequestedSubscription =
|
||||
_manager.characteristicReadRequested.listen((eventArgs) async {
|
||||
final central = eventArgs.central;
|
||||
final characteristic = eventArgs.characteristic;
|
||||
final request = eventArgs.request;
|
||||
final offset = request.offset;
|
||||
final log = Log(
|
||||
type: 'Characteristic read requested',
|
||||
message: '${central.uuid}, ${characteristic.uuid}, $offset',
|
||||
);
|
||||
_logs.add(log);
|
||||
notifyListeners();
|
||||
final elements = List.generate(100, (i) => i % 256);
|
||||
final value = Uint8List.fromList(elements);
|
||||
final trimmedValue = value.sublist(offset);
|
||||
await _manager.respondReadRequestWithValue(
|
||||
request,
|
||||
value: trimmedValue,
|
||||
);
|
||||
});
|
||||
_characteristicWriteRequestedSubscription =
|
||||
_manager.characteristicWriteRequested.listen((eventArgs) async {
|
||||
final central = eventArgs.central;
|
||||
final characteristic = eventArgs.characteristic;
|
||||
final request = eventArgs.request;
|
||||
final offset = request.offset;
|
||||
final value = request.value;
|
||||
final log = Log(
|
||||
type: 'Characteristic write requested',
|
||||
message:
|
||||
'[${value.length}] ${central.uuid}, ${characteristic.uuid}, $offset, $value',
|
||||
);
|
||||
_logs.add(log);
|
||||
notifyListeners();
|
||||
await _manager.respondWriteRequest(request);
|
||||
});
|
||||
_characteristicNotifyStateChangedSubscription =
|
||||
_manager.characteristicNotifyStateChanged.listen((eventArgs) async {
|
||||
final central = eventArgs.central;
|
||||
final characteristic = eventArgs.characteristic;
|
||||
final state = eventArgs.state;
|
||||
final log = Log(
|
||||
type: 'Characteristic notify state changed',
|
||||
message: '${central.uuid}, ${characteristic.uuid}, $state',
|
||||
);
|
||||
_logs.add(log);
|
||||
notifyListeners();
|
||||
// Write someting to the central when notify started.
|
||||
if (state) {
|
||||
final maximumNotifyLength =
|
||||
await _manager.getMaximumNotifyLength(central);
|
||||
final elements = List.generate(maximumNotifyLength, (i) => i % 256);
|
||||
final value = Uint8List.fromList(elements);
|
||||
await _manager.notifyCharacteristic(
|
||||
central,
|
||||
characteristic,
|
||||
value: value,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BluetoothLowEnergyState get state => _manager.state;
|
||||
bool get advertising => _advertising;
|
||||
List<Log> get logs => _logs;
|
||||
|
||||
Future<void> showAppSettings() async {
|
||||
await _manager.showAppSettings();
|
||||
}
|
||||
|
||||
Future<void> startAdvertising() async {
|
||||
if (_advertising) {
|
||||
return;
|
||||
}
|
||||
await _manager.removeAllServices();
|
||||
final elements = List.generate(100, (i) => i % 256);
|
||||
final value = Uint8List.fromList(elements);
|
||||
final service = GATTService(
|
||||
uuid: UUID.short(100),
|
||||
isPrimary: true,
|
||||
includedServices: [],
|
||||
characteristics: [
|
||||
GATTCharacteristic.immutable(
|
||||
uuid: UUID.short(200),
|
||||
value: value,
|
||||
descriptors: [],
|
||||
),
|
||||
GATTCharacteristic.mutable(
|
||||
uuid: UUID.short(201),
|
||||
properties: [
|
||||
GATTCharacteristicProperty.read,
|
||||
GATTCharacteristicProperty.write,
|
||||
GATTCharacteristicProperty.writeWithoutResponse,
|
||||
GATTCharacteristicProperty.notify,
|
||||
GATTCharacteristicProperty.indicate,
|
||||
],
|
||||
permissions: [
|
||||
GATTCharacteristicPermission.read,
|
||||
GATTCharacteristicPermission.write,
|
||||
],
|
||||
descriptors: [],
|
||||
),
|
||||
],
|
||||
);
|
||||
await _manager.addService(service);
|
||||
final advertisement = Advertisement(
|
||||
name: 'BLE-12138',
|
||||
manufacturerSpecificData: [
|
||||
ManufacturerSpecificData(
|
||||
id: 0x2e19,
|
||||
data: Uint8List.fromList([0x01, 0x02, 0x03]),
|
||||
)
|
||||
],
|
||||
);
|
||||
await _manager.startAdvertising(advertisement);
|
||||
_advertising = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> stopAdvertising() async {
|
||||
if (!_advertising) {
|
||||
return;
|
||||
}
|
||||
await _manager.stopAdvertising();
|
||||
_advertising = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clearLogs() {
|
||||
_logs.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_stateChangedSubscription.cancel();
|
||||
_characteristicReadRequestedSubscription.cancel();
|
||||
_characteristicWriteRequestedSubscription.cancel();
|
||||
_characteristicNotifyStateChangedSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
import 'package:hybrid_logging/hybrid_logging.dart';
|
||||
|
||||
import 'service_view_model.dart';
|
||||
|
||||
class PeripheralViewModel extends ViewModel with TypeLogger {
|
||||
final CentralManager _manager;
|
||||
final Peripheral _peripheral;
|
||||
final String? _name;
|
||||
bool _connected;
|
||||
List<ServiceViewModel> _serviceViewModels;
|
||||
|
||||
late final StreamSubscription _connectionStateChangedSubscription;
|
||||
late final StreamSubscription _mtuChangedChangedSubscription;
|
||||
|
||||
PeripheralViewModel(DiscoveredEventArgs eventArgs)
|
||||
: _manager = CentralManager(),
|
||||
_peripheral = eventArgs.peripheral,
|
||||
_name = eventArgs.advertisement.name,
|
||||
_connected = false,
|
||||
_serviceViewModels = [] {
|
||||
_connectionStateChangedSubscription =
|
||||
_manager.connectionStateChanged.listen((eventArgs) {
|
||||
if (eventArgs.peripheral != _peripheral) {
|
||||
return;
|
||||
}
|
||||
if (eventArgs.state == ConnectionState.connected) {
|
||||
_connected = true;
|
||||
} else {
|
||||
_connected = false;
|
||||
_serviceViewModels = [];
|
||||
}
|
||||
notifyListeners();
|
||||
});
|
||||
_mtuChangedChangedSubscription = _manager.mtuChanged.listen((eventArgs) {
|
||||
if (eventArgs.peripheral != _peripheral) {
|
||||
return;
|
||||
}
|
||||
logger.info('MTU chanaged: ${eventArgs.mtu}');
|
||||
});
|
||||
}
|
||||
|
||||
UUID get uuid => _peripheral.uuid;
|
||||
String? get name => _name;
|
||||
bool get connected => _connected;
|
||||
List<ServiceViewModel> get serviceViewModels => _serviceViewModels;
|
||||
|
||||
Future<void> connect() async {
|
||||
await _manager.connect(_peripheral);
|
||||
}
|
||||
|
||||
Future<void> disconnect() async {
|
||||
await _manager.disconnect(_peripheral);
|
||||
}
|
||||
|
||||
Future<void> discoverGATT() async {
|
||||
final services = await _manager.discoverGATT(_peripheral);
|
||||
_serviceViewModels = services
|
||||
.map((service) => ServiceViewModel(
|
||||
manager: _manager,
|
||||
peripheral: _peripheral,
|
||||
service: service,
|
||||
))
|
||||
.toList();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (connected) {
|
||||
disconnect();
|
||||
}
|
||||
_connectionStateChangedSubscription.cancel();
|
||||
_mtuChangedChangedSubscription.cancel();
|
||||
for (var serviceViewModel in serviceViewModels) {
|
||||
serviceViewModel.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
|
||||
import 'characteristic_view_model.dart';
|
||||
|
||||
class ServiceViewModel extends ViewModel {
|
||||
final GATTService _service;
|
||||
|
||||
final List<ServiceViewModel> _includedServiceViewModels;
|
||||
final List<CharacteristicViewModel> _characteristicViewModels;
|
||||
|
||||
ServiceViewModel({
|
||||
required CentralManager manager,
|
||||
required Peripheral peripheral,
|
||||
required GATTService service,
|
||||
}) : _service = service,
|
||||
_includedServiceViewModels = service.includedServices
|
||||
.map((service) => ServiceViewModel(
|
||||
manager: manager,
|
||||
peripheral: peripheral,
|
||||
service: service,
|
||||
))
|
||||
.toList(),
|
||||
_characteristicViewModels = service.characteristics
|
||||
.map((characteristic) => CharacteristicViewModel(
|
||||
manager: manager,
|
||||
peripheral: peripheral,
|
||||
characteristic: characteristic,
|
||||
))
|
||||
.toList();
|
||||
|
||||
UUID get uuid => _service.uuid;
|
||||
bool get isPrimary => _service.isPrimary;
|
||||
List<ServiceViewModel> get includedServiceViewModels =>
|
||||
_includedServiceViewModels;
|
||||
List<CharacteristicViewModel> get characteristicViewModels =>
|
||||
_characteristicViewModels;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (var characteristicViewModel in characteristicViewModels) {
|
||||
characteristicViewModel.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
4
bluetooth_low_energy_android/example/lib/views.dart
Normal file
4
bluetooth_low_energy_android/example/lib/views.dart
Normal file
@ -0,0 +1,4 @@
|
||||
export 'views/home_view.dart';
|
||||
export 'views/central_manager_view.dart';
|
||||
export 'views/peripheral_manager_view.dart';
|
||||
export 'views/peripheral_view.dart';
|
@ -0,0 +1,69 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AdvertisementView extends StatelessWidget {
|
||||
final Advertisement advertisement;
|
||||
|
||||
const AdvertisementView({
|
||||
super.key,
|
||||
required this.advertisement,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final manufacturerSpecificData = advertisement.manufacturerSpecificData;
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
const idWidth = 100.0;
|
||||
final valueWidth = constraints.maxWidth - idWidth - 16.0 * 2.0;
|
||||
return DataTable(
|
||||
columnSpacing: 0.0,
|
||||
horizontalMargin: 16.0,
|
||||
columns: [
|
||||
DataColumn(
|
||||
label: Container(
|
||||
width: idWidth,
|
||||
alignment: Alignment.center,
|
||||
child: const Text('Id'),
|
||||
),
|
||||
),
|
||||
DataColumn(
|
||||
label: Container(
|
||||
width: valueWidth,
|
||||
alignment: Alignment.center,
|
||||
child: const Text('Value'),
|
||||
),
|
||||
),
|
||||
],
|
||||
rows: manufacturerSpecificData.map((item) {
|
||||
final id = '0x${item.id.toRadixString(16).padLeft(4, '0')}';
|
||||
final value = hex.encode(item.data);
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(
|
||||
Container(
|
||||
width: idWidth,
|
||||
alignment: Alignment.center,
|
||||
child: Text(id),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Container(
|
||||
width: valueWidth,
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
value,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:bluetooth_low_energy_android_example/view_models.dart';
|
||||
import 'package:bluetooth_low_energy_android_example/widgets.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'advertisement_view.dart';
|
||||
|
||||
class CentralManagerView extends StatelessWidget {
|
||||
const CentralManagerView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final viewModel = ViewModel.of<CentralManagerViewModel>(context);
|
||||
final state = viewModel.state;
|
||||
final discovering = viewModel.discovering;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Central Manager'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: state == BluetoothLowEnergyState.poweredOn
|
||||
? () async {
|
||||
if (discovering) {
|
||||
await viewModel.stopDiscovery();
|
||||
} else {
|
||||
await viewModel.startDiscovery();
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Text(discovering ? 'END' : 'BEGIN'),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: buildBody(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBody(BuildContext context) {
|
||||
final viewModel = ViewModel.of<CentralManagerViewModel>(context);
|
||||
final state = viewModel.state;
|
||||
if (state == BluetoothLowEnergyState.unauthorized) {
|
||||
return Center(
|
||||
child: TextButton(
|
||||
onPressed: () => viewModel.showAppSettings(),
|
||||
child: const Text('Go to settings'),
|
||||
),
|
||||
);
|
||||
} else if (state == BluetoothLowEnergyState.poweredOn) {
|
||||
final discoveries = viewModel.discoveries;
|
||||
return ListView.separated(
|
||||
itemBuilder: (context, index) {
|
||||
final theme = Theme.of(context);
|
||||
final discovery = discoveries[index];
|
||||
final uuid = discovery.peripheral.uuid;
|
||||
final name = discovery.advertisement.name;
|
||||
final rssi = discovery.rssi;
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
onTapDissovery(context, discovery);
|
||||
},
|
||||
onLongPress: () {
|
||||
onLongPressDiscovery(context, discovery);
|
||||
},
|
||||
title: Text(name ?? ''),
|
||||
subtitle: Text(
|
||||
'$uuid',
|
||||
style: theme.textTheme.bodySmall,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
RSSIIndicator(rssi),
|
||||
Text('$rssi'),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, i) {
|
||||
return const Divider(
|
||||
height: 0.0,
|
||||
);
|
||||
},
|
||||
itemCount: discoveries.length,
|
||||
);
|
||||
} else {
|
||||
return Center(
|
||||
child: Text(
|
||||
'$state',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void onTapDissovery(
|
||||
BuildContext context, DiscoveredEventArgs discovery) async {
|
||||
final viewModel = ViewModel.of<CentralManagerViewModel>(context);
|
||||
if (viewModel.discovering) {
|
||||
await viewModel.stopDiscovery();
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final uuid = discovery.peripheral.uuid;
|
||||
context.go('/central/$uuid');
|
||||
}
|
||||
|
||||
void onLongPressDiscovery(
|
||||
BuildContext context, DiscoveredEventArgs discovery) async {
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AdvertisementView(
|
||||
advertisement: discovery.advertisement,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import 'package:bluetooth_low_energy_android_example/view_models.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CharacteristicTreeNodeView extends StatelessWidget {
|
||||
const CharacteristicTreeNodeView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final viewModel = ViewModel.of<CharacteristicViewModel>(context);
|
||||
return Text(
|
||||
'${viewModel.uuid}',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:bluetooth_low_energy_android_example/view_models.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
|
||||
import 'log_view.dart';
|
||||
|
||||
class CharacteristicView extends StatefulWidget {
|
||||
const CharacteristicView({super.key});
|
||||
|
||||
@override
|
||||
State<CharacteristicView> createState() => _CharacteristicViewState();
|
||||
}
|
||||
|
||||
class _CharacteristicViewState extends State<CharacteristicView> {
|
||||
late final TextEditingController _textController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textController = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final viewModel = ViewModel.of<CharacteristicViewModel>(context);
|
||||
final logs = viewModel.logs;
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return OverflowBox(
|
||||
alignment: Alignment.topCenter,
|
||||
maxHeight: constraints.maxHeight + 1.0,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InputDecorator(
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(12.0),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: const Radius.circular(12.0),
|
||||
bottom: viewModel.canWrite ||
|
||||
viewModel.canWriteWithoutResponse
|
||||
? Radius.zero
|
||||
: const Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, i) {
|
||||
final log = logs[i];
|
||||
return LogView(
|
||||
log: log,
|
||||
);
|
||||
},
|
||||
itemCount: logs.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: viewModel.canNotify,
|
||||
child: viewModel.notifyState
|
||||
? IconButton.filled(
|
||||
onPressed: () async {
|
||||
await viewModel.setNotifyState(false);
|
||||
},
|
||||
icon:
|
||||
const Icon(Symbols.notifications_active),
|
||||
)
|
||||
: IconButton.filledTonal(
|
||||
onPressed: () async {
|
||||
await viewModel.setNotifyState(true);
|
||||
},
|
||||
icon: const Icon(Symbols.notifications_off),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: viewModel.canRead,
|
||||
child: IconButton.filled(
|
||||
onPressed: () async {
|
||||
await viewModel.read();
|
||||
},
|
||||
icon: const Icon(Symbols.arrow_downward),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: viewModel.canWrite ||
|
||||
viewModel.canWriteWithoutResponse,
|
||||
child: viewModel.writeType ==
|
||||
GATTCharacteristicWriteType.withResponse
|
||||
? IconButton.filled(
|
||||
onPressed: viewModel.canWriteWithoutResponse
|
||||
? () {
|
||||
viewModel.setWriteType(
|
||||
GATTCharacteristicWriteType
|
||||
.withoutResponse);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Symbols.swap_vert),
|
||||
)
|
||||
: IconButton.filledTonal(
|
||||
onPressed: viewModel.canWrite
|
||||
? () {
|
||||
viewModel.setWriteType(
|
||||
GATTCharacteristicWriteType
|
||||
.withResponse);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Symbols.arrow_upward),
|
||||
),
|
||||
),
|
||||
IconButton.filled(
|
||||
onPressed: () => viewModel.clearLogs(),
|
||||
icon: const Icon(Symbols.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: viewModel.canWrite || viewModel.canWriteWithoutResponse,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _textController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _textController,
|
||||
builder: (context, tev, child) {
|
||||
final text = tev.text;
|
||||
return IconButton.filled(
|
||||
onPressed: text.isEmpty
|
||||
? null
|
||||
: () async {
|
||||
final value = utf8.encode(text);
|
||||
await viewModel.write(value);
|
||||
},
|
||||
icon: const Icon(Symbols.pets),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import 'package:bluetooth_low_energy_android_example/view_models.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DescriptorTreeNodeView extends StatelessWidget {
|
||||
const DescriptorTreeNodeView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final viewModel = ViewModel.of<DescriptorViewModel>(context);
|
||||
return Text(
|
||||
'${viewModel.uuid}',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class HomeView extends StatefulWidget {
|
||||
final StatefulNavigationShell navigationShell;
|
||||
final List<Widget> navigators;
|
||||
|
||||
const HomeView({
|
||||
super.key,
|
||||
required this.navigationShell,
|
||||
required this.navigators,
|
||||
});
|
||||
|
||||
@override
|
||||
State<HomeView> createState() => _HomeViewState();
|
||||
}
|
||||
|
||||
class _HomeViewState extends State<HomeView> {
|
||||
late final PageController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = PageController(
|
||||
initialPage: widget.navigationShell.currentIndex,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final navigationShell = widget.navigationShell;
|
||||
final navigators = widget.navigators;
|
||||
return Scaffold(
|
||||
// body: navigationShell,
|
||||
body: PageView.builder(
|
||||
controller: _controller,
|
||||
onPageChanged: (index) {
|
||||
// Ignore tap events.
|
||||
if (index == navigationShell.currentIndex) {
|
||||
return;
|
||||
}
|
||||
navigationShell.goBranch(
|
||||
index,
|
||||
initialLocation: false,
|
||||
);
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
return navigators[index];
|
||||
},
|
||||
itemCount: navigators.length,
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
onTap: (index) {
|
||||
navigationShell.goBranch(
|
||||
index,
|
||||
initialLocation: index == navigationShell.currentIndex,
|
||||
);
|
||||
},
|
||||
items: const [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.radar),
|
||||
label: 'Central',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.sensors),
|
||||
label: 'Peripheral',
|
||||
),
|
||||
],
|
||||
currentIndex: widget.navigationShell.currentIndex,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant HomeView oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
final navigationShell = widget.navigationShell;
|
||||
final page = _controller.page ?? _controller.initialPage;
|
||||
final index = page.round();
|
||||
// Ignore swipe events.
|
||||
if (index == navigationShell.currentIndex) {
|
||||
return;
|
||||
}
|
||||
_controller.animateToPage(
|
||||
navigationShell.currentIndex,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.linear,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
43
bluetooth_low_energy_android/example/lib/views/log_view.dart
Normal file
43
bluetooth_low_energy_android/example/lib/views/log_view.dart
Normal file
@ -0,0 +1,43 @@
|
||||
import 'package:bluetooth_low_energy_android_example/models.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class LogView extends StatelessWidget {
|
||||
final Log log;
|
||||
|
||||
const LogView({
|
||||
super.key,
|
||||
required this.log,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final formatter = DateFormat.Hms();
|
||||
final time = formatter.format(log.time);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
text: '[$time] ',
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: log.type,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
log.message,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:bluetooth_low_energy_android_example/view_models.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
|
||||
import 'log_view.dart';
|
||||
|
||||
class PeripheralManagerView extends StatelessWidget {
|
||||
const PeripheralManagerView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final viewModel = ViewModel.of<PeripheralManagerViewModel>(context);
|
||||
final state = viewModel.state;
|
||||
final advertising = viewModel.advertising;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Peripheral Manager'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: state == BluetoothLowEnergyState.poweredOn
|
||||
? () async {
|
||||
if (advertising) {
|
||||
await viewModel.stopAdvertising();
|
||||
} else {
|
||||
await viewModel.startAdvertising();
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Text(advertising ? 'END' : 'BEGIN'),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: buildBody(context),
|
||||
floatingActionButton: state == BluetoothLowEnergyState.poweredOn
|
||||
? FloatingActionButton(
|
||||
onPressed: () => viewModel.clearLogs(),
|
||||
child: const Icon(Symbols.delete),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBody(BuildContext context) {
|
||||
final viewModel = ViewModel.of<PeripheralManagerViewModel>(context);
|
||||
final state = viewModel.state;
|
||||
if (state == BluetoothLowEnergyState.unauthorized) {
|
||||
return Center(
|
||||
child: TextButton(
|
||||
onPressed: () => viewModel.showAppSettings(),
|
||||
child: const Text('Go to settings'),
|
||||
),
|
||||
);
|
||||
} else if (state == BluetoothLowEnergyState.poweredOn) {
|
||||
final logs = viewModel.logs;
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
itemBuilder: (context, i) {
|
||||
final log = logs[i];
|
||||
return LogView(
|
||||
log: log,
|
||||
);
|
||||
},
|
||||
itemCount: logs.length,
|
||||
);
|
||||
} else {
|
||||
return Center(
|
||||
child: Text(
|
||||
'$state',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
import 'package:bluetooth_low_energy_android_example/view_models.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_simple_treeview/flutter_simple_treeview.dart';
|
||||
|
||||
import 'characteristic_tree_node_view.dart';
|
||||
import 'characteristic_view.dart';
|
||||
import 'descriptor_tree_node_view.dart';
|
||||
import 'service_tree_node_view.dart';
|
||||
|
||||
class PeripheralView extends StatelessWidget {
|
||||
final TreeController _treeController;
|
||||
|
||||
PeripheralView({super.key})
|
||||
: _treeController = TreeController(
|
||||
allNodesExpanded: false,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final viewModel = ViewModel.of<PeripheralViewModel>(context);
|
||||
final connected = viewModel.connected;
|
||||
final serviceViewModels = viewModel.serviceViewModels;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(viewModel.name ?? '${viewModel.uuid}'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (connected) {
|
||||
await viewModel.disconnect();
|
||||
} else {
|
||||
await viewModel.connect();
|
||||
await viewModel.discoverGATT();
|
||||
}
|
||||
},
|
||||
child: Text(connected ? 'DISCONNECT' : 'CONNECT'),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: TreeView(
|
||||
treeController: _treeController,
|
||||
indent: 0.0,
|
||||
nodes: _buildServiceTreeNodes(serviceViewModels),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<TreeNode> _buildServiceTreeNodes(List<ServiceViewModel> viewModels) {
|
||||
return viewModels.map((viewModel) {
|
||||
final includedServiceViewModels = viewModel.includedServiceViewModels;
|
||||
final characteristicViewModels = viewModel.characteristicViewModels;
|
||||
return TreeNode(
|
||||
children: [
|
||||
..._buildServiceTreeNodes(includedServiceViewModels),
|
||||
..._buildCharacteristicTreeNodes(characteristicViewModels),
|
||||
],
|
||||
content: InheritedViewModel(
|
||||
view: const ServiceTreeNodeView(),
|
||||
viewModel: viewModel,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<TreeNode> _buildCharacteristicTreeNodes(
|
||||
List<CharacteristicViewModel> viewModels) {
|
||||
return viewModels.map((viewModel) {
|
||||
final descriptorViewModels = viewModel.descriptorViewModels;
|
||||
return TreeNode(
|
||||
children: [
|
||||
TreeNode(
|
||||
content: Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(right: 40.0),
|
||||
height: 360.0,
|
||||
child: InheritedViewModel(
|
||||
view: const CharacteristicView(),
|
||||
viewModel: viewModel,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
..._buildDescriptorTreeNodes(descriptorViewModels),
|
||||
],
|
||||
content: InheritedViewModel(
|
||||
view: const CharacteristicTreeNodeView(),
|
||||
viewModel: viewModel,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<TreeNode> _buildDescriptorTreeNodes(
|
||||
List<DescriptorViewModel> viewModels) {
|
||||
return viewModels.map((viewModel) {
|
||||
return TreeNode(
|
||||
content: InheritedViewModel(
|
||||
view: const DescriptorTreeNodeView(),
|
||||
viewModel: viewModel,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import 'package:bluetooth_low_energy_android_example/view_models.dart';
|
||||
import 'package:clover/clover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ServiceTreeNodeView extends StatelessWidget {
|
||||
const ServiceTreeNodeView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final viewModel = ViewModel.of<ServiceViewModel>(context);
|
||||
return Text(
|
||||
'${viewModel.uuid}',
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
1
bluetooth_low_energy_android/example/lib/widgets.dart
Normal file
1
bluetooth_low_energy_android/example/lib/widgets.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'widgets/rssi_indicator.dart';
|
@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RSSIIndicator extends StatelessWidget {
|
||||
final int rssi;
|
||||
|
||||
const RSSIIndicator(
|
||||
this.rssi, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final IconData icon;
|
||||
if (rssi > -70) {
|
||||
icon = Icons.wifi_rounded;
|
||||
} else if (rssi > -100) {
|
||||
icon = Icons.wifi_2_bar_rounded;
|
||||
} else {
|
||||
icon = Icons.wifi_1_bar_rounded;
|
||||
}
|
||||
return Icon(icon);
|
||||
}
|
||||
}
|
@ -15,15 +15,15 @@ packages:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "5.0.4"
|
||||
version: "6.0.0"
|
||||
bluetooth_low_energy_platform_interface:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: bluetooth_low_energy_platform_interface
|
||||
sha256: "5af74eb8f97a896dfdbcff2da284d4fe5b4e2e49ebb2f46f826ac1810f66e4d7"
|
||||
sha256: bc2e8d97c141653e5747bcb3cdc9fe956541b6ecc6e5f158b99a2f3abc2d946a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.2"
|
||||
version: "6.0.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -48,8 +48,16 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
clover:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: clover
|
||||
sha256: eba28e69b32f174a51c093eef098cf5ae646470322727081d5d3d8f66c786487
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
@ -68,10 +76,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
version: "1.0.8"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -102,7 +110,15 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
||||
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
flutter_simple_treeview:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_simple_treeview
|
||||
sha256: ad4978d2668dd078d3a09966832da111bef9102dd636e572c50c80133b7ff4d9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
@ -111,11 +127,32 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fuchsia_remote_debug_protocol:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
go_router:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: "6ad5662b014c06c20fa46ab78715c96b2222a7fe4f87bf77e0289592c2539e86"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.1.3"
|
||||
hybrid_logging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hybrid_logging
|
||||
sha256: "54248d52ce68c14702a42fbc4083bac5c6be30f6afad8a41be4bbadd197b8af5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
integration_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -133,44 +170,36 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.0"
|
||||
version: "10.0.4"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "3.0.3"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
log_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: log_service
|
||||
sha256: "21124936899e227d1779268077921d46d57456e2592d1562e455be273594e2e4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "4.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logging
|
||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||
@ -193,14 +222,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
material_symbols_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: material_symbols_icons
|
||||
sha256: b2d3cbc3c42b8a217715b0d97ff03aebb14b2b4592875736e5599c603fb2db7e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2758.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
version: "1.12.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -290,10 +327,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
version: "0.7.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -314,10 +351,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.0"
|
||||
version: "14.2.1"
|
||||
webdriver:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -327,5 +364,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
sdks:
|
||||
dart: ">=3.2.0-0 <4.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: bluetooth_low_energy_example
|
||||
description: Demonstrates how to use the bluetooth_low_energy plugin.
|
||||
name: bluetooth_low_energy_android_example
|
||||
description: "Demonstrates how to use the bluetooth_low_energy_android plugin."
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
@ -17,10 +17,10 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
bluetooth_low_energy_platform_interface: ^5.0.0
|
||||
bluetooth_low_energy_platform_interface: ^6.0.0
|
||||
bluetooth_low_energy_android:
|
||||
# When depending on this package from a real application you should use:
|
||||
# bluetooth_low_energy: ^x.y.z
|
||||
# bluetooth_low_energy_android: ^x.y.z
|
||||
# See https://dart.dev/tools/pub/dependencies#version-constraints
|
||||
# The example app is bundled with the plugin so we use a path dependency on
|
||||
# the parent directory to use the current plugin's version.
|
||||
@ -28,9 +28,16 @@ dependencies:
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
convert: ^3.1.1
|
||||
cupertino_icons: ^1.0.6
|
||||
clover: ^3.0.0
|
||||
go_router: ^14.1.3
|
||||
logging: ^1.2.0
|
||||
hybrid_logging: ^1.0.0
|
||||
intl: ^0.19.0
|
||||
collection: ^1.18.0
|
||||
convert: ^3.1.1
|
||||
flutter_simple_treeview: ^3.0.2
|
||||
material_symbols_icons: ^4.2744.0
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
@ -43,7 +50,7 @@ dev_dependencies:
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^3.0.0
|
||||
flutter_lints: ^4.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
@ -5,23 +5,4 @@
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:bluetooth_low_energy_example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that platform version is retrieved.
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(Widget widget) => widget is Text &&
|
||||
widget.data!.startsWith('Running on:'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
||||
void main() {}
|
||||
|
@ -3,9 +3,9 @@ import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_pla
|
||||
import 'src/my_central_manager.dart';
|
||||
import 'src/my_peripheral_manager.dart';
|
||||
|
||||
abstract class BluetoothLowEnergyAndroid {
|
||||
abstract class BluetoothLowEnergyAndroidPlugin {
|
||||
static void registerWith() {
|
||||
CentralManager.instance = MyCentralManager();
|
||||
PeripheralManager.instance = MyPeripheralManager();
|
||||
PlatformCentralManager.instance = MyCentralManager();
|
||||
PlatformPeripheralManager.instance = MyPeripheralManager();
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,11 @@ import 'dart:typed_data';
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
|
||||
import 'my_api.g.dart';
|
||||
import 'my_central2.dart';
|
||||
import 'my_gatt_characteristic2.dart';
|
||||
import 'my_gatt_descriptor2.dart';
|
||||
import 'my_gatt_service2.dart';
|
||||
import 'my_peripheral2.dart';
|
||||
import 'my_central.dart';
|
||||
import 'my_gatt.dart';
|
||||
import 'my_peripheral.dart';
|
||||
|
||||
export 'my_api.g.dart';
|
||||
|
||||
// ToObject
|
||||
// ToObj
|
||||
extension MyBluetoothLowEnergyStateArgsX on MyBluetoothLowEnergyStateArgs {
|
||||
BluetoothLowEnergyState toState() {
|
||||
switch (this) {
|
||||
@ -31,209 +27,251 @@ extension MyBluetoothLowEnergyStateArgsX on MyBluetoothLowEnergyStateArgs {
|
||||
}
|
||||
}
|
||||
|
||||
extension MyGattCharacteristicPropertyArgsX
|
||||
on MyGattCharacteristicPropertyArgs {
|
||||
GattCharacteristicProperty toProperty() {
|
||||
return GattCharacteristicProperty.values[index];
|
||||
extension MyConnectionStateArgsX on MyConnectionStateArgs {
|
||||
ConnectionState toState() {
|
||||
switch (this) {
|
||||
case MyConnectionStateArgs.disconnected:
|
||||
case MyConnectionStateArgs.connecting:
|
||||
return ConnectionState.disconnected;
|
||||
case MyConnectionStateArgs.connected:
|
||||
case MyConnectionStateArgs.disconnecting:
|
||||
return ConnectionState.connected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MyGATTCharacteristicPropertyArgsX
|
||||
on MyGATTCharacteristicPropertyArgs {
|
||||
GATTCharacteristicProperty toProperty() {
|
||||
return GATTCharacteristicProperty.values[index];
|
||||
}
|
||||
}
|
||||
|
||||
extension MyManufacturerSpecificDataArgsX on MyManufacturerSpecificDataArgs {
|
||||
ManufacturerSpecificData toManufacturerSpecificData() {
|
||||
final id = idArgs;
|
||||
final data = dataArgs;
|
||||
return ManufacturerSpecificData(
|
||||
id: id,
|
||||
data: data,
|
||||
id: idArgs,
|
||||
data: dataArgs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyAdvertisementArgsX on MyAdvertisementArgs {
|
||||
Advertisement toAdvertisement() {
|
||||
final name = nameArgs;
|
||||
final serviceUUIDs =
|
||||
serviceUUIDsArgs.cast<String>().map((args) => args.toUUID()).toList();
|
||||
final serviceData = serviceDataArgs.cast<String, Uint8List>().map(
|
||||
(uuidArgs, dataArgs) {
|
||||
final uuid = uuidArgs.toUUID();
|
||||
final data = dataArgs;
|
||||
return MapEntry(uuid, data);
|
||||
},
|
||||
);
|
||||
final manufacturerSpecificData =
|
||||
manufacturerSpecificDataArgs?.toManufacturerSpecificData();
|
||||
return Advertisement(
|
||||
name: name,
|
||||
serviceUUIDs: serviceUUIDs,
|
||||
serviceData: serviceData,
|
||||
manufacturerSpecificData: manufacturerSpecificData,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyCentralArgsX on MyCentralArgs {
|
||||
MyCentral2 toCentral() {
|
||||
return MyCentral2(
|
||||
address: addressArgs,
|
||||
name: nameArgs,
|
||||
serviceUUIDs: serviceUUIDsArgs
|
||||
.cast<String>()
|
||||
.map((args) => UUID.fromString(args))
|
||||
.toList(),
|
||||
serviceData: serviceDataArgs.cast<String, Uint8List>().map(
|
||||
(uuidArgs, dataArgs) {
|
||||
final uuid = UUID.fromString(uuidArgs);
|
||||
return MapEntry(uuid, dataArgs);
|
||||
},
|
||||
),
|
||||
manufacturerSpecificData: manufacturerSpecificDataArgs
|
||||
.cast<MyManufacturerSpecificDataArgs>()
|
||||
.map((args) => args.toManufacturerSpecificData())
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyPeripheralArgsX on MyPeripheralArgs {
|
||||
MyPeripheral2 toPeripheral() {
|
||||
return MyPeripheral2(
|
||||
MyPeripheral toPeripheral() {
|
||||
return MyPeripheral(
|
||||
addressArgs: addressArgs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyGATTDescriptorArgsX on MyGATTDescriptorArgs {
|
||||
MyGATTDescriptor toDescriptor() {
|
||||
return MyGATTDescriptor(
|
||||
hashCodeArgs: hashCodeArgs,
|
||||
uuid: UUID.fromString(uuidArgs),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyGATTCharacteristicArgsX on MyGATTCharacteristicArgs {
|
||||
MyGATTCharacteristic toCharacteristic() {
|
||||
return MyGATTCharacteristic(
|
||||
hashCodeArgs: hashCodeArgs,
|
||||
uuid: UUID.fromString(uuidArgs),
|
||||
properties: propertyNumbersArgs.cast<int>().map((args) {
|
||||
final propertyArgs = MyGATTCharacteristicPropertyArgs.values[args];
|
||||
return propertyArgs.toProperty();
|
||||
}).toList(),
|
||||
descriptors: descriptorsArgs
|
||||
.cast<MyGATTDescriptorArgs>()
|
||||
.map((args) => args.toDescriptor())
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyGATTServiceArgsX on MyGATTServiceArgs {
|
||||
MyGATTService toService() {
|
||||
return MyGATTService(
|
||||
hashCodeArgs: hashCodeArgs,
|
||||
uuid: UUID.fromString(uuidArgs),
|
||||
isPrimary: isPrimaryArgs,
|
||||
includedServices: includedServicesArgs
|
||||
.cast<MyGATTServiceArgs>()
|
||||
.map((args) => args.toService())
|
||||
.toList(),
|
||||
characteristics: characteristicsArgs
|
||||
.cast<MyGATTCharacteristicArgs>()
|
||||
.map((args) => args.toCharacteristic())
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyCentralArgsX on MyCentralArgs {
|
||||
MyCentral toCentral() {
|
||||
return MyCentral(
|
||||
address: addressArgs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyGattDescriptorArgsX on MyGattDescriptorArgs {
|
||||
MyGattDescriptor2 toDescriptor2(MyPeripheral2 peripheral) {
|
||||
final hashCode = hashCodeArgs;
|
||||
final uuid = uuidArgs.toUUID();
|
||||
return MyGattDescriptor2(
|
||||
peripheral: peripheral,
|
||||
hashCode: hashCode,
|
||||
uuid: uuid,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyGattCharacteristicArgsX on MyGattCharacteristicArgs {
|
||||
MyGattCharacteristic2 toCharacteristic2(MyPeripheral2 peripheral) {
|
||||
final hashCode = hashCodeArgs;
|
||||
final uuid = uuidArgs.toUUID();
|
||||
final properties = propertyNumbersArgs.cast<int>().map(
|
||||
(args) {
|
||||
final propertyArgs = MyGattCharacteristicPropertyArgs.values[args];
|
||||
return propertyArgs.toProperty();
|
||||
},
|
||||
).toList();
|
||||
final descriptors = descriptorsArgs
|
||||
.cast<MyGattDescriptorArgs>()
|
||||
.map((args) => args.toDescriptor2(peripheral))
|
||||
.toList();
|
||||
return MyGattCharacteristic2(
|
||||
peripheral: peripheral,
|
||||
hashCode: hashCode,
|
||||
uuid: uuid,
|
||||
properties: properties,
|
||||
descriptors: descriptors,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyGattServiceArgsX on MyGattServiceArgs {
|
||||
MyGattService2 toService2(MyPeripheral2 peripheral) {
|
||||
final hashCode = hashCodeArgs;
|
||||
final uuid = uuidArgs.toUUID();
|
||||
final characteristics = characteristicsArgs
|
||||
.cast<MyGattCharacteristicArgs>()
|
||||
.map((args) => args.toCharacteristic2(peripheral))
|
||||
.toList();
|
||||
return MyGattService2(
|
||||
peripheral: peripheral,
|
||||
hashCode: hashCode,
|
||||
uuid: uuid,
|
||||
characteristics: characteristics,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyUuidArgsX on String {
|
||||
UUID toUUID() {
|
||||
return UUID.fromString(this);
|
||||
}
|
||||
}
|
||||
|
||||
// ToArgs
|
||||
extension GattCharacteristicPropertyX on GattCharacteristicProperty {
|
||||
MyGattCharacteristicPropertyArgs toArgs() {
|
||||
return MyGattCharacteristicPropertyArgs.values[index];
|
||||
extension UUIDX on UUID {
|
||||
String toArgs() {
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
|
||||
extension GattCharacteristicWriteTypeX on GattCharacteristicWriteType {
|
||||
MyGattCharacteristicWriteTypeArgs toArgs() {
|
||||
return MyGattCharacteristicWriteTypeArgs.values[index];
|
||||
extension GATTCharacteristicWriteTypeX on GATTCharacteristicWriteType {
|
||||
MyGATTCharacteristicWriteTypeArgs toArgs() {
|
||||
return MyGATTCharacteristicWriteTypeArgs.values[index];
|
||||
}
|
||||
}
|
||||
|
||||
extension GATTCharacteristicPropertyX on GATTCharacteristicProperty {
|
||||
MyGATTCharacteristicPropertyArgs toArgs() {
|
||||
return MyGATTCharacteristicPropertyArgs.values[index];
|
||||
}
|
||||
}
|
||||
|
||||
extension GATTCharacteristicPermissionX on GATTCharacteristicPermission {
|
||||
MyGATTCharacteristicPermissionArgs toArgs() {
|
||||
return MyGATTCharacteristicPermissionArgs.values[index];
|
||||
}
|
||||
}
|
||||
|
||||
extension GATTErrorX on GATTError {
|
||||
MyGATTStatusArgs toArgs() {
|
||||
switch (this) {
|
||||
case GATTError.readNotPermitted:
|
||||
return MyGATTStatusArgs.readNotPermitted;
|
||||
case GATTError.writeNotPermitted:
|
||||
return MyGATTStatusArgs.writeNotPermitted;
|
||||
case GATTError.insufficientAuthentication:
|
||||
return MyGATTStatusArgs.insufficientAuthentication;
|
||||
case GATTError.requestNotSupported:
|
||||
return MyGATTStatusArgs.requestNotSupported;
|
||||
case GATTError.invalidOffset:
|
||||
return MyGATTStatusArgs.invalidOffset;
|
||||
case GATTError.insufficientAuthorization:
|
||||
return MyGATTStatusArgs.insufficientAuthorization;
|
||||
case GATTError.invalidAttributeValueLength:
|
||||
return MyGATTStatusArgs.invalidAttributeLength;
|
||||
case GATTError.insufficientEncryption:
|
||||
return MyGATTStatusArgs.insufficientEncryption;
|
||||
default:
|
||||
return MyGATTStatusArgs.failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ManufacturerSpecificDataX on ManufacturerSpecificData {
|
||||
MyManufacturerSpecificDataArgs toArgs() {
|
||||
final idArgs = id;
|
||||
final dataArgs = data;
|
||||
return MyManufacturerSpecificDataArgs(
|
||||
idArgs: idArgs,
|
||||
dataArgs: dataArgs,
|
||||
idArgs: id,
|
||||
dataArgs: data,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension AdvertisementX on Advertisement {
|
||||
MyAdvertisementArgs toArgs() {
|
||||
final nameArgs = name;
|
||||
final serviceUUIDsArgs = serviceUUIDs.map((uuid) => uuid.toArgs()).toList();
|
||||
final serviceDataArgs = serviceData.map((uuid, data) {
|
||||
final uuidArgs = uuid.toArgs();
|
||||
final dataArgs = data;
|
||||
return MapEntry(uuidArgs, dataArgs);
|
||||
});
|
||||
final manufacturerSpecificDataArgs = manufacturerSpecificData?.toArgs();
|
||||
return MyAdvertisementArgs(
|
||||
nameArgs: nameArgs,
|
||||
serviceUUIDsArgs: serviceUUIDsArgs,
|
||||
serviceDataArgs: serviceDataArgs,
|
||||
manufacturerSpecificDataArgs: manufacturerSpecificDataArgs,
|
||||
nameArgs: name,
|
||||
serviceUUIDsArgs: serviceUUIDs.map((uuid) => uuid.toArgs()).toList(),
|
||||
serviceDataArgs: serviceData.map((uuid, data) {
|
||||
final uuidArgs = uuid.toArgs();
|
||||
final dataArgs = data;
|
||||
return MapEntry(uuidArgs, dataArgs);
|
||||
}),
|
||||
manufacturerSpecificDataArgs:
|
||||
manufacturerSpecificData.map((data) => data.toArgs()).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyGattDescriptorX on MyGattDescriptor {
|
||||
MyGattDescriptorArgs toArgs() {
|
||||
final hashCodeArgs = hashCode;
|
||||
final uuidArgs = uuid.toArgs();
|
||||
final valueArgs = value;
|
||||
return MyGattDescriptorArgs(
|
||||
hashCodeArgs: hashCodeArgs,
|
||||
uuidArgs: uuidArgs,
|
||||
valueArgs: valueArgs,
|
||||
extension MutableGATTDescriptorX on MutableGATTDescriptor {
|
||||
MyMutableGATTDescriptorArgs toArgs() {
|
||||
return MyMutableGATTDescriptorArgs(
|
||||
hashCodeArgs: hashCode,
|
||||
uuidArgs: uuid.toArgs(),
|
||||
permissionNumbersArgs: permissions.map((permission) {
|
||||
final permissionArgs = permission.toArgs();
|
||||
return permissionArgs.index;
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyGattCharacteristicX on MyGattCharacteristic {
|
||||
MyGattCharacteristicArgs toArgs(List<MyGattDescriptorArgs> descriptorsArgs) {
|
||||
final hashCodeArgs = hashCode;
|
||||
final uuidArgs = uuid.toArgs();
|
||||
final propertyNumbersArgs = properties.map((property) {
|
||||
final propertyArgs = property.toArgs();
|
||||
return propertyArgs.index;
|
||||
}).toList();
|
||||
return MyGattCharacteristicArgs(
|
||||
hashCodeArgs: hashCodeArgs,
|
||||
uuidArgs: uuidArgs,
|
||||
propertyNumbersArgs: propertyNumbersArgs,
|
||||
extension MutableGATTCharacteristicX on MutableGATTCharacteristic {
|
||||
MyMutableGATTCharacteristicArgs toArgs() {
|
||||
// Add CCC descriptor.
|
||||
final cccUUID = UUID.short(0x2902);
|
||||
final descriptorsArgs = descriptors
|
||||
.cast<MutableGATTDescriptor>()
|
||||
.where((descriptor) => descriptor.uuid != cccUUID)
|
||||
.map((descriptor) => descriptor.toArgs())
|
||||
.toList();
|
||||
final cccDescriptor = MutableGATTDescriptor(
|
||||
uuid: cccUUID,
|
||||
permissions: [
|
||||
GATTCharacteristicPermission.read,
|
||||
GATTCharacteristicPermission.write,
|
||||
],
|
||||
);
|
||||
final cccDescriptorArgs = cccDescriptor.toArgs();
|
||||
descriptorsArgs.add(cccDescriptorArgs);
|
||||
return MyMutableGATTCharacteristicArgs(
|
||||
hashCodeArgs: hashCode,
|
||||
uuidArgs: uuid.toArgs(),
|
||||
permissionNumbersArgs: permissions.map((permission) {
|
||||
final permissionArgs = permission.toArgs();
|
||||
return permissionArgs.index;
|
||||
}).toList(),
|
||||
propertyNumbersArgs: properties.map((property) {
|
||||
final propertyArgs = property.toArgs();
|
||||
return propertyArgs.index;
|
||||
}).toList(),
|
||||
descriptorsArgs: descriptorsArgs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension MyGattServiceX on MyGattService {
|
||||
MyGattServiceArgs toArgs(List<MyGattCharacteristicArgs> characteristicsArgs) {
|
||||
final hashCodeArgs = hashCode;
|
||||
final uuidArgs = uuid.toArgs();
|
||||
return MyGattServiceArgs(
|
||||
hashCodeArgs: hashCodeArgs,
|
||||
uuidArgs: uuidArgs,
|
||||
characteristicsArgs: characteristicsArgs,
|
||||
extension GATTServiceX on GATTService {
|
||||
MyMutableGATTServiceArgs toArgs() {
|
||||
return MyMutableGATTServiceArgs(
|
||||
hashCodeArgs: hashCode,
|
||||
uuidArgs: uuid.toArgs(),
|
||||
isPrimaryArgs: isPrimary,
|
||||
includedServicesArgs:
|
||||
includedServices.map((service) => service.toArgs()).toList(),
|
||||
characteristicsArgs: characteristics
|
||||
.cast<MutableGATTCharacteristic>()
|
||||
.map((characteristic) => characteristic.toArgs())
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension UuidX on UUID {
|
||||
String toArgs() {
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,9 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
|
||||
class MyCentral2 extends MyCentral {
|
||||
final class MyCentral extends Central {
|
||||
final String address;
|
||||
|
||||
MyCentral2({
|
||||
MyCentral({
|
||||
required this.address,
|
||||
}) : super(
|
||||
uuid: UUID.fromAddress(address),
|
@ -1,70 +1,100 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart' hide ConnectionState;
|
||||
|
||||
import 'my_api.dart';
|
||||
import 'my_gatt_characteristic2.dart';
|
||||
import 'my_gatt_descriptor2.dart';
|
||||
import 'my_peripheral2.dart';
|
||||
import 'my_api.g.dart';
|
||||
import 'my_gatt.dart';
|
||||
import 'my_peripheral.dart';
|
||||
|
||||
class MyCentralManager extends CentralManager
|
||||
implements MyCentralManagerFlutterApi {
|
||||
final MyCentralManagerHostApi _api;
|
||||
final class MyCentralManager extends PlatformCentralManager
|
||||
with WidgetsBindingObserver
|
||||
implements MyCentralManagerFlutterAPI {
|
||||
final MyCentralManagerHostAPI _api;
|
||||
final StreamController<BluetoothLowEnergyStateChangedEventArgs>
|
||||
_stateChangedController;
|
||||
final StreamController<DiscoveredEventArgs> _discoveredController;
|
||||
final StreamController<ConnectionStateChangedEventArgs>
|
||||
final StreamController<PeripheralConnectionStateChangedEventArgs>
|
||||
_connectionStateChangedController;
|
||||
final StreamController<GattCharacteristicNotifiedEventArgs>
|
||||
final StreamController<PeripheralMTUChangedEventArgs> _mtuChangedController;
|
||||
final StreamController<GATTCharacteristicNotifiedEventArgs>
|
||||
_characteristicNotifiedController;
|
||||
|
||||
final Map<String, MyPeripheral2> _peripherals;
|
||||
final Map<String, Map<int, MyGattCharacteristic2>> _characteristics;
|
||||
final Map<String, int> _mtus;
|
||||
|
||||
late final MyCentralManagerArgs _args;
|
||||
|
||||
BluetoothLowEnergyState _state;
|
||||
|
||||
MyCentralManager()
|
||||
: _api = MyCentralManagerHostApi(),
|
||||
: _api = MyCentralManagerHostAPI(),
|
||||
_stateChangedController = StreamController.broadcast(),
|
||||
_discoveredController = StreamController.broadcast(),
|
||||
_connectionStateChangedController = StreamController.broadcast(),
|
||||
_mtuChangedController = StreamController.broadcast(),
|
||||
_characteristicNotifiedController = StreamController.broadcast(),
|
||||
_peripherals = {},
|
||||
_characteristics = {},
|
||||
_mtus = {},
|
||||
_state = BluetoothLowEnergyState.unknown;
|
||||
|
||||
UUID get cccUUID => UUID.short(0x2902);
|
||||
@override
|
||||
BluetoothLowEnergyState get state => _state;
|
||||
@override
|
||||
Stream<BluetoothLowEnergyStateChangedEventArgs> get stateChanged =>
|
||||
_stateChangedController.stream;
|
||||
@override
|
||||
Stream<DiscoveredEventArgs> get discovered => _discoveredController.stream;
|
||||
@override
|
||||
Stream<ConnectionStateChangedEventArgs> get connectionStateChanged =>
|
||||
_connectionStateChangedController.stream;
|
||||
Stream<PeripheralConnectionStateChangedEventArgs>
|
||||
get connectionStateChanged => _connectionStateChangedController.stream;
|
||||
@override
|
||||
Stream<GattCharacteristicNotifiedEventArgs> get characteristicNotified =>
|
||||
Stream<PeripheralMTUChangedEventArgs> get mtuChanged =>
|
||||
_mtuChangedController.stream;
|
||||
@override
|
||||
Stream<GATTCharacteristicNotifiedEventArgs> get characteristicNotified =>
|
||||
_characteristicNotifiedController.stream;
|
||||
|
||||
@override
|
||||
Future<void> setUp() async {
|
||||
logger.info('setUp');
|
||||
await _api.setUp();
|
||||
MyCentralManagerFlutterApi.setup(this);
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
logger.info('didChangeAppLifecycleState: $state');
|
||||
if (state != AppLifecycleState.resumed) {
|
||||
return;
|
||||
}
|
||||
_getState();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BluetoothLowEnergyState> getState() {
|
||||
logger.info('getState');
|
||||
return Future.value(_state);
|
||||
void initialize() {
|
||||
MyCentralManagerFlutterAPI.setUp(this);
|
||||
final binding = WidgetsFlutterBinding.ensureInitialized();
|
||||
binding.addObserver(this);
|
||||
_initialize();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> startDiscovery() async {
|
||||
logger.info('startDiscovery');
|
||||
await _api.startDiscovery();
|
||||
Future<bool> authorize() async {
|
||||
logger.info('authorize');
|
||||
final authorized = await _api.authorize();
|
||||
return authorized;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> showAppSettings() async {
|
||||
logger.info('showAppSettings');
|
||||
await _api.showAppSettings();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> startDiscovery({
|
||||
List<UUID>? serviceUUIDs,
|
||||
}) async {
|
||||
final serviceUUIDsArgs =
|
||||
serviceUUIDs?.map((uuid) => uuid.toArgs()).toList() ?? [];
|
||||
logger.info('startDiscovery: $serviceUUIDsArgs');
|
||||
await _api.startDiscovery(serviceUUIDsArgs);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -73,74 +103,103 @@ class MyCentralManager extends CentralManager
|
||||
await _api.stopDiscovery();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Peripheral>> retrieveConnectedPeripherals() async {
|
||||
logger.info('retrieveConnectedPeripherals');
|
||||
final peripheralsArgs = await _api.retrieveConnectedPeripherals();
|
||||
final peripherals = peripheralsArgs
|
||||
.cast<MyPeripheralArgs>()
|
||||
.map((args) => args.toPeripheral())
|
||||
.toList();
|
||||
return peripherals;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> connect(Peripheral peripheral) async {
|
||||
if (peripheral is! MyPeripheral2) {
|
||||
if (peripheral is! MyPeripheral) {
|
||||
throw TypeError();
|
||||
}
|
||||
final addressArgs = peripheral.address;
|
||||
final addressArgs = peripheral.addressArgs;
|
||||
logger.info('connect: $addressArgs');
|
||||
await _api.connect(addressArgs);
|
||||
try {
|
||||
await _api.requestMTU(addressArgs, 517);
|
||||
} catch (error, stackTrace) {
|
||||
// 忽略协商 MTU 错误
|
||||
logger.warning('requstMTU failed.', error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> disconnect(Peripheral peripheral) async {
|
||||
if (peripheral is! MyPeripheral2) {
|
||||
if (peripheral is! MyPeripheral) {
|
||||
throw TypeError();
|
||||
}
|
||||
final addressArgs = peripheral.address;
|
||||
final addressArgs = peripheral.addressArgs;
|
||||
logger.info('disconnect: $addressArgs');
|
||||
await _api.disconnect(addressArgs);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> readRSSI(Peripheral peripheral) async {
|
||||
if (peripheral is! MyPeripheral2) {
|
||||
Future<int> requestMTU(
|
||||
Peripheral peripheral, {
|
||||
required int mtu,
|
||||
}) async {
|
||||
if (peripheral is! MyPeripheral) {
|
||||
throw TypeError();
|
||||
}
|
||||
final addressArgs = peripheral.address;
|
||||
final addressArgs = peripheral.addressArgs;
|
||||
final mtuArgs = mtu;
|
||||
logger.info('requestMTU: $addressArgs - $mtuArgs');
|
||||
final size = await _api.requestMTU(addressArgs, mtuArgs);
|
||||
return size;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getMaximumWriteLength(
|
||||
Peripheral peripheral, {
|
||||
required GATTCharacteristicWriteType type,
|
||||
}) {
|
||||
if (peripheral is! MyPeripheral) {
|
||||
throw TypeError();
|
||||
}
|
||||
final addressArgs = peripheral.addressArgs;
|
||||
final mtu = _mtus[addressArgs] ?? 23;
|
||||
final maximumWriteLength = (mtu - 3).clamp(20, 512);
|
||||
return Future.value(maximumWriteLength);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> readRSSI(Peripheral peripheral) async {
|
||||
if (peripheral is! MyPeripheral) {
|
||||
throw TypeError();
|
||||
}
|
||||
final addressArgs = peripheral.addressArgs;
|
||||
logger.info('readRSSI: $addressArgs');
|
||||
final rssi = await _api.readRSSI(addressArgs);
|
||||
return rssi;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<GattService>> discoverGATT(Peripheral peripheral) async {
|
||||
if (peripheral is! MyPeripheral2) {
|
||||
Future<List<GATTService>> discoverGATT(Peripheral peripheral) async {
|
||||
if (peripheral is! MyPeripheral) {
|
||||
throw TypeError();
|
||||
}
|
||||
final addressArgs = peripheral.address;
|
||||
logger.info('discoverServices: $addressArgs');
|
||||
final servicesArgs = await _api.discoverServices(addressArgs);
|
||||
final addressArgs = peripheral.addressArgs;
|
||||
logger.info('discoverGATT: $addressArgs');
|
||||
final servicesArgs = await _api.discoverGATT(addressArgs);
|
||||
final services = servicesArgs
|
||||
.cast<MyGattServiceArgs>()
|
||||
.map((args) => args.toService2(peripheral))
|
||||
.cast<MyGATTServiceArgs>()
|
||||
.map((args) => args.toService())
|
||||
.toList();
|
||||
final characteristics =
|
||||
services.expand((service) => service.characteristics).toList();
|
||||
_characteristics[addressArgs] = {
|
||||
for (var characteristic in characteristics)
|
||||
characteristic.hashCode: characteristic
|
||||
};
|
||||
return services;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> readCharacteristic(
|
||||
GattCharacteristic characteristic,
|
||||
Peripheral peripheral,
|
||||
GATTCharacteristic characteristic,
|
||||
) async {
|
||||
if (characteristic is! MyGattCharacteristic2) {
|
||||
if (peripheral is! MyPeripheral ||
|
||||
characteristic is! MyGATTCharacteristic) {
|
||||
throw TypeError();
|
||||
}
|
||||
final peripheral = characteristic.peripheral;
|
||||
final addressArgs = peripheral.address;
|
||||
final hashCodeArgs = characteristic.hashCode;
|
||||
final addressArgs = peripheral.addressArgs;
|
||||
final hashCodeArgs = characteristic.hashCodeArgs;
|
||||
logger.info('readCharacteristic: $addressArgs.$hashCodeArgs');
|
||||
final value = await _api.readCharacteristic(addressArgs, hashCodeArgs);
|
||||
return value;
|
||||
@ -148,86 +207,75 @@ class MyCentralManager extends CentralManager
|
||||
|
||||
@override
|
||||
Future<void> writeCharacteristic(
|
||||
GattCharacteristic characteristic, {
|
||||
Peripheral peripheral,
|
||||
GATTCharacteristic characteristic, {
|
||||
required Uint8List value,
|
||||
required GattCharacteristicWriteType type,
|
||||
required GATTCharacteristicWriteType type,
|
||||
}) async {
|
||||
if (characteristic is! MyGattCharacteristic2) {
|
||||
if (peripheral is! MyPeripheral ||
|
||||
characteristic is! MyGATTCharacteristic) {
|
||||
throw TypeError();
|
||||
}
|
||||
final peripheral = characteristic.peripheral;
|
||||
final addressArgs = peripheral.address;
|
||||
final hashCodeArgs = characteristic.hashCode;
|
||||
final trimmedValueArgs = value.trimGATT();
|
||||
final addressArgs = peripheral.addressArgs;
|
||||
final hashCodeArgs = characteristic.hashCodeArgs;
|
||||
final valueArgs = value;
|
||||
final typeArgs = type.toArgs();
|
||||
final typeNumberArgs = typeArgs.index;
|
||||
// When write without response, fragments the value by MTU - 3 size.
|
||||
// If mtu is null, use 23 as default MTU size.
|
||||
if (type == GattCharacteristicWriteType.withResponse) {
|
||||
logger.info(
|
||||
'writeCharacteristic: $addressArgs.$hashCodeArgs - $trimmedValueArgs, $typeArgs');
|
||||
await _api.writeCharacteristic(
|
||||
addressArgs,
|
||||
hashCodeArgs,
|
||||
trimmedValueArgs,
|
||||
typeNumberArgs,
|
||||
);
|
||||
} else {
|
||||
final mtu = _mtus[addressArgs] ?? 23;
|
||||
final fragmentSize = (mtu - 3).clamp(20, 512);
|
||||
var start = 0;
|
||||
while (start < trimmedValueArgs.length) {
|
||||
final end = start + fragmentSize;
|
||||
final fragmentedValueArgs = end < trimmedValueArgs.length
|
||||
? trimmedValueArgs.sublist(start, end)
|
||||
: trimmedValueArgs.sublist(start);
|
||||
logger.info(
|
||||
'writeCharacteristic: $addressArgs.$hashCodeArgs - $fragmentedValueArgs, $typeArgs');
|
||||
await _api.writeCharacteristic(
|
||||
addressArgs,
|
||||
hashCodeArgs,
|
||||
fragmentedValueArgs,
|
||||
typeNumberArgs,
|
||||
);
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setCharacteristicNotifyState(
|
||||
GattCharacteristic characteristic, {
|
||||
required bool state,
|
||||
}) async {
|
||||
if (characteristic is! MyGattCharacteristic2) {
|
||||
throw TypeError();
|
||||
}
|
||||
final peripheral = characteristic.peripheral;
|
||||
final addressArgs = peripheral.address;
|
||||
final hashCodeArgs = characteristic.hashCode;
|
||||
final stateArgs = state
|
||||
? characteristic.properties.contains(GattCharacteristicProperty.notify)
|
||||
? MyGattCharacteristicNotifyStateArgs.notify
|
||||
: MyGattCharacteristicNotifyStateArgs.indicate
|
||||
: MyGattCharacteristicNotifyStateArgs.none;
|
||||
final stateNumberArgs = stateArgs.index;
|
||||
logger.info(
|
||||
'setCharacteristicNotifyState: $addressArgs.$hashCodeArgs - $stateArgs');
|
||||
await _api.setCharacteristicNotifyState(
|
||||
'writeCharacteristic: $addressArgs.$hashCodeArgs - $valueArgs, $typeArgs');
|
||||
await _api.writeCharacteristic(
|
||||
addressArgs,
|
||||
hashCodeArgs,
|
||||
stateNumberArgs,
|
||||
valueArgs,
|
||||
typeArgs,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> readDescriptor(GattDescriptor descriptor) async {
|
||||
if (descriptor is! MyGattDescriptor2) {
|
||||
Future<void> setCharacteristicNotifyState(
|
||||
Peripheral peripheral,
|
||||
GATTCharacteristic characteristic, {
|
||||
required bool state,
|
||||
}) async {
|
||||
if (peripheral is! MyPeripheral ||
|
||||
characteristic is! MyGATTCharacteristic) {
|
||||
throw TypeError();
|
||||
}
|
||||
final peripheral = descriptor.peripheral;
|
||||
final addressArgs = peripheral.address;
|
||||
final hashCodeArgs = descriptor.hashCode;
|
||||
final addressArgs = peripheral.addressArgs;
|
||||
final hashCodeArgs = characteristic.hashCodeArgs;
|
||||
final enableArgs = state;
|
||||
logger.info(
|
||||
'setCharacteristicNotification: $addressArgs.$hashCodeArgs - $enableArgs');
|
||||
await _api.setCharacteristicNotification(
|
||||
addressArgs,
|
||||
hashCodeArgs,
|
||||
enableArgs,
|
||||
);
|
||||
// Seems the docs is not correct, this operation is necessary for all characteristics.
|
||||
// https://developer.android.com/guide/topics/connectivity/bluetooth/transfer-ble-data#notification
|
||||
final descriptor = characteristic.descriptors
|
||||
.firstWhere((descriptor) => descriptor.uuid == cccUUID);
|
||||
final value = state
|
||||
? characteristic.properties.contains(GATTCharacteristicProperty.notify)
|
||||
? _args.enableNotificationValue
|
||||
: _args.enableIndicationValue
|
||||
: _args.disableNotificationValue;
|
||||
await writeDescriptor(
|
||||
peripheral,
|
||||
descriptor,
|
||||
value: value,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> readDescriptor(
|
||||
Peripheral peripheral,
|
||||
GATTDescriptor descriptor,
|
||||
) async {
|
||||
if (peripheral is! MyPeripheral || descriptor is! MyGATTDescriptor) {
|
||||
throw TypeError();
|
||||
}
|
||||
final addressArgs = peripheral.addressArgs;
|
||||
final hashCodeArgs = descriptor.hashCodeArgs;
|
||||
logger.info('readDescriptor: $addressArgs.$hashCodeArgs');
|
||||
final value = await _api.readDescriptor(addressArgs, hashCodeArgs);
|
||||
return value;
|
||||
@ -235,24 +283,22 @@ class MyCentralManager extends CentralManager
|
||||
|
||||
@override
|
||||
Future<void> writeDescriptor(
|
||||
GattDescriptor descriptor, {
|
||||
Peripheral peripheral,
|
||||
GATTDescriptor descriptor, {
|
||||
required Uint8List value,
|
||||
}) async {
|
||||
if (descriptor is! MyGattDescriptor2) {
|
||||
if (peripheral is! MyPeripheral || descriptor is! MyGATTDescriptor) {
|
||||
throw TypeError();
|
||||
}
|
||||
final peripheral = descriptor.peripheral;
|
||||
final addressArgs = peripheral.address;
|
||||
final hashCodeArgs = descriptor.hashCode;
|
||||
final trimmedValueArgs = value.trimGATT();
|
||||
logger.info(
|
||||
'writeDescriptor: $addressArgs.$hashCodeArgs - $trimmedValueArgs');
|
||||
await _api.writeDescriptor(addressArgs, hashCodeArgs, trimmedValueArgs);
|
||||
final addressArgs = peripheral.addressArgs;
|
||||
final hashCodeArgs = descriptor.hashCodeArgs;
|
||||
final valueArgs = value;
|
||||
logger.info('writeDescriptor: $addressArgs.$hashCodeArgs - $valueArgs');
|
||||
await _api.writeDescriptor(addressArgs, hashCodeArgs, valueArgs);
|
||||
}
|
||||
|
||||
@override
|
||||
void onStateChanged(int stateNumberArgs) {
|
||||
final stateArgs = MyBluetoothLowEnergyStateArgs.values[stateNumberArgs];
|
||||
void onStateChanged(MyBluetoothLowEnergyStateArgs stateArgs) {
|
||||
logger.info('onStateChanged: $stateArgs');
|
||||
final state = stateArgs.toState();
|
||||
if (_state == state) {
|
||||
@ -271,10 +317,7 @@ class MyCentralManager extends CentralManager
|
||||
) {
|
||||
final addressArgs = peripheralArgs.addressArgs;
|
||||
logger.info('onDiscovered: $addressArgs - $rssiArgs, $advertisementArgs');
|
||||
final peripheral = _peripherals.putIfAbsent(
|
||||
peripheralArgs.addressArgs,
|
||||
() => peripheralArgs.toPeripheral(),
|
||||
);
|
||||
final peripheral = peripheralArgs.toPeripheral();
|
||||
final rssi = rssiArgs;
|
||||
final advertisement = advertisementArgs.toAdvertisement();
|
||||
final eventArgs = DiscoveredEventArgs(
|
||||
@ -286,56 +329,76 @@ class MyCentralManager extends CentralManager
|
||||
}
|
||||
|
||||
@override
|
||||
void onConnectionStateChanged(String addressArgs, bool stateArgs) {
|
||||
void onConnectionStateChanged(
|
||||
MyPeripheralArgs peripheralArgs,
|
||||
MyConnectionStateArgs stateArgs,
|
||||
) {
|
||||
final addressArgs = peripheralArgs.addressArgs;
|
||||
logger.info('onConnectionStateChanged: $addressArgs - $stateArgs');
|
||||
final peripheral = _peripherals[addressArgs];
|
||||
if (peripheral == null) {
|
||||
return;
|
||||
}
|
||||
final state = stateArgs;
|
||||
final eventArgs = ConnectionStateChangedEventArgs(peripheral, state);
|
||||
_connectionStateChangedController.add(eventArgs);
|
||||
if (!state) {
|
||||
_characteristics.remove(addressArgs);
|
||||
final peripheral = peripheralArgs.toPeripheral();
|
||||
final state = stateArgs.toState();
|
||||
if (state == ConnectionState.disconnected) {
|
||||
_mtus.remove(addressArgs);
|
||||
}
|
||||
final eventArgs = PeripheralConnectionStateChangedEventArgs(
|
||||
peripheral,
|
||||
state,
|
||||
);
|
||||
_connectionStateChangedController.add(eventArgs);
|
||||
}
|
||||
|
||||
@override
|
||||
void onMtuChanged(String addressArgs, int mtuArgs) {
|
||||
logger.info('onMtuChanged: $addressArgs - $mtuArgs');
|
||||
void onMTUChanged(MyPeripheralArgs peripheralArgs, int mtuArgs) {
|
||||
final addressArgs = peripheralArgs.addressArgs;
|
||||
logger.info('onMTUChanged: $addressArgs - $mtuArgs');
|
||||
final peripheral = peripheralArgs.toPeripheral();
|
||||
final mtu = mtuArgs;
|
||||
_mtus[addressArgs] = mtu;
|
||||
final eventArgs = PeripheralMTUChangedEventArgs(peripheral, mtu);
|
||||
_mtuChangedController.add(eventArgs);
|
||||
}
|
||||
|
||||
@override
|
||||
void onCharacteristicNotified(
|
||||
String addressArgs,
|
||||
int hashCodeArgs,
|
||||
MyPeripheralArgs peripheralArgs,
|
||||
MyGATTCharacteristicArgs characteristicArgs,
|
||||
Uint8List valueArgs,
|
||||
) {
|
||||
final addressArgs = peripheralArgs.addressArgs;
|
||||
final hashCodeArgs = characteristicArgs.hashCodeArgs;
|
||||
logger.info(
|
||||
'onCharacteristicNotified: $addressArgs.$hashCodeArgs - $valueArgs');
|
||||
final characteristic = _retrieveCharacteristic(addressArgs, hashCodeArgs);
|
||||
if (characteristic == null) {
|
||||
return;
|
||||
}
|
||||
final peripheral = peripheralArgs.toPeripheral();
|
||||
final characteristic = characteristicArgs.toCharacteristic();
|
||||
final value = valueArgs;
|
||||
final eventArgs = GattCharacteristicNotifiedEventArgs(
|
||||
final eventArgs = GATTCharacteristicNotifiedEventArgs(
|
||||
peripheral,
|
||||
characteristic,
|
||||
value,
|
||||
);
|
||||
_characteristicNotifiedController.add(eventArgs);
|
||||
}
|
||||
|
||||
MyGattCharacteristic2? _retrieveCharacteristic(
|
||||
String addressArgs,
|
||||
int hashCodeArgs,
|
||||
) {
|
||||
final characteristics = _characteristics[addressArgs];
|
||||
if (characteristics == null) {
|
||||
return null;
|
||||
Future<void> _initialize() async {
|
||||
// Here we use `Future()` to make it possible to change the `logLevel` before `initialize()`.
|
||||
await Future(() async {
|
||||
try {
|
||||
logger.info('initialize');
|
||||
_args = await _api.initialize();
|
||||
_getState();
|
||||
} catch (e) {
|
||||
logger.severe('initialize failed.', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _getState() async {
|
||||
try {
|
||||
logger.info('getState');
|
||||
final stateArgs = await _api.getState();
|
||||
onStateChanged(stateArgs);
|
||||
} catch (e) {
|
||||
logger.severe('getState failed.', e);
|
||||
}
|
||||
return characteristics[hashCodeArgs];
|
||||
}
|
||||
}
|
||||
|
95
bluetooth_low_energy_android/lib/src/my_gatt.dart
Normal file
95
bluetooth_low_energy_android/lib/src/my_gatt.dart
Normal file
@ -0,0 +1,95 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
|
||||
final class MyGATTDescriptor extends GATTDescriptor {
|
||||
final int hashCodeArgs;
|
||||
|
||||
MyGATTDescriptor({
|
||||
required this.hashCodeArgs,
|
||||
required super.uuid,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode => hashCodeArgs;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MyGATTDescriptor && other.hashCodeArgs == hashCodeArgs;
|
||||
}
|
||||
}
|
||||
|
||||
final class MyGATTCharacteristic extends GATTCharacteristic {
|
||||
final int hashCodeArgs;
|
||||
|
||||
MyGATTCharacteristic({
|
||||
required this.hashCodeArgs,
|
||||
required super.uuid,
|
||||
required super.properties,
|
||||
required List<MyGATTDescriptor> descriptors,
|
||||
}) : super(
|
||||
descriptors: descriptors,
|
||||
);
|
||||
|
||||
@override
|
||||
List<MyGATTDescriptor> get descriptors =>
|
||||
super.descriptors.cast<MyGATTDescriptor>();
|
||||
|
||||
@override
|
||||
int get hashCode => hashCodeArgs;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MyGATTCharacteristic && other.hashCodeArgs == hashCodeArgs;
|
||||
}
|
||||
}
|
||||
|
||||
final class MyGATTService extends GATTService {
|
||||
final int hashCodeArgs;
|
||||
|
||||
MyGATTService({
|
||||
required this.hashCodeArgs,
|
||||
required super.uuid,
|
||||
required super.isPrimary,
|
||||
required List<MyGATTService> includedServices,
|
||||
required List<MyGATTCharacteristic> characteristics,
|
||||
}) : super(
|
||||
includedServices: includedServices,
|
||||
characteristics: characteristics,
|
||||
);
|
||||
|
||||
@override
|
||||
List<MyGATTCharacteristic> get characteristics =>
|
||||
super.characteristics.cast<MyGATTCharacteristic>();
|
||||
|
||||
@override
|
||||
int get hashCode => hashCodeArgs;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MyGATTService && other.hashCodeArgs == hashCodeArgs;
|
||||
}
|
||||
}
|
||||
|
||||
final class MyGATTReadRequest extends GATTReadRequest {
|
||||
final String addressArgs;
|
||||
final int idArgs;
|
||||
|
||||
MyGATTReadRequest({
|
||||
required this.addressArgs,
|
||||
required this.idArgs,
|
||||
required super.offset,
|
||||
});
|
||||
}
|
||||
|
||||
final class MyGATTWriteRequest extends GATTWriteRequest {
|
||||
final String addressArgs;
|
||||
final int idArgs;
|
||||
final bool responseNeededArgs;
|
||||
|
||||
MyGATTWriteRequest({
|
||||
required this.addressArgs,
|
||||
required this.idArgs,
|
||||
required this.responseNeededArgs,
|
||||
required super.offset,
|
||||
required super.value,
|
||||
});
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
|
||||
import 'my_gatt_descriptor2.dart';
|
||||
import 'my_peripheral2.dart';
|
||||
|
||||
class MyGattCharacteristic2 extends MyGattCharacteristic {
|
||||
final MyPeripheral2 peripheral;
|
||||
@override
|
||||
final int hashCode;
|
||||
|
||||
MyGattCharacteristic2({
|
||||
required this.peripheral,
|
||||
required this.hashCode,
|
||||
required super.uuid,
|
||||
required super.properties,
|
||||
required List<MyGattDescriptor2> descriptors,
|
||||
}) : super(descriptors: descriptors);
|
||||
|
||||
@override
|
||||
List<MyGattDescriptor2> get descriptors =>
|
||||
super.descriptors.cast<MyGattDescriptor2>();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MyGattCharacteristic2 &&
|
||||
other.peripheral == peripheral &&
|
||||
other.hashCode == hashCode;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
|
||||
import 'my_peripheral2.dart';
|
||||
|
||||
class MyGattDescriptor2 extends MyGattDescriptor {
|
||||
final MyPeripheral2 peripheral;
|
||||
@override
|
||||
final int hashCode;
|
||||
|
||||
MyGattDescriptor2({
|
||||
required this.peripheral,
|
||||
required this.hashCode,
|
||||
required super.uuid,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MyGattDescriptor2 &&
|
||||
other.peripheral == peripheral &&
|
||||
other.hashCode == hashCode;
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
|
||||
import 'my_gatt_characteristic2.dart';
|
||||
import 'my_peripheral2.dart';
|
||||
|
||||
class MyGattService2 extends MyGattService {
|
||||
final MyPeripheral2 peripheral;
|
||||
@override
|
||||
final int hashCode;
|
||||
|
||||
MyGattService2({
|
||||
required this.peripheral,
|
||||
required this.hashCode,
|
||||
required super.uuid,
|
||||
required List<MyGattCharacteristic2> characteristics,
|
||||
}) : super(characteristics: characteristics);
|
||||
|
||||
@override
|
||||
List<MyGattCharacteristic2> get characteristics =>
|
||||
super.characteristics.cast<MyGattCharacteristic2>();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MyGattService2 &&
|
||||
other.peripheral == peripheral &&
|
||||
other.hashCode == hashCode;
|
||||
}
|
||||
}
|
11
bluetooth_low_energy_android/lib/src/my_peripheral.dart
Normal file
11
bluetooth_low_energy_android/lib/src/my_peripheral.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
|
||||
final class MyPeripheral extends Peripheral {
|
||||
final String addressArgs;
|
||||
|
||||
MyPeripheral({
|
||||
required this.addressArgs,
|
||||
}) : super(
|
||||
uuid: UUID.fromAddress(addressArgs),
|
||||
);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
|
||||
|
||||
class MyPeripheral2 extends MyPeripheral {
|
||||
final String address;
|
||||
|
||||
MyPeripheral2({
|
||||
required this.address,
|
||||
}) : super(
|
||||
uuid: UUID.fromAddress(address),
|
||||
);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,16 @@
|
||||
// Run with `dart run pigeon --input my_api.dart`.
|
||||
import 'package:pigeon/pigeon.dart';
|
||||
|
||||
// TODO: Use `@ProxyApi` to manage instancs when this feature released:
|
||||
// https://github.com/flutter/flutter/issues/147486
|
||||
@ConfigurePigeon(
|
||||
PigeonOptions(
|
||||
dartOut: 'lib/src/my_api.g.dart',
|
||||
dartOptions: DartOptions(),
|
||||
kotlinOut:
|
||||
'android/src/main/kotlin/dev/yanshouwang/bluetooth_low_energy_android/MyApi.g.kt',
|
||||
'android/src/main/kotlin/dev/hebei/bluetooth_low_energy_android/MyAPI.g.kt',
|
||||
kotlinOptions: KotlinOptions(
|
||||
package: 'dev.yanshouwang.bluetooth_low_energy_android',
|
||||
package: 'dev.hebei.bluetooth_low_energy_android',
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -21,7 +24,14 @@ enum MyBluetoothLowEnergyStateArgs {
|
||||
turningOff,
|
||||
}
|
||||
|
||||
enum MyGattCharacteristicPropertyArgs {
|
||||
enum MyConnectionStateArgs {
|
||||
disconnected,
|
||||
connecting,
|
||||
connected,
|
||||
disconnecting,
|
||||
}
|
||||
|
||||
enum MyGATTCharacteristicPropertyArgs {
|
||||
read,
|
||||
write,
|
||||
writeWithoutResponse,
|
||||
@ -29,30 +39,56 @@ enum MyGattCharacteristicPropertyArgs {
|
||||
indicate,
|
||||
}
|
||||
|
||||
enum MyGattCharacteristicWriteTypeArgs {
|
||||
enum MyGATTCharacteristicPermissionArgs {
|
||||
read,
|
||||
readEncrypted,
|
||||
write,
|
||||
writeEncrypted,
|
||||
}
|
||||
|
||||
enum MyGATTCharacteristicWriteTypeArgs {
|
||||
withResponse,
|
||||
withoutResponse,
|
||||
}
|
||||
|
||||
enum MyGattCharacteristicNotifyStateArgs {
|
||||
none,
|
||||
notify,
|
||||
indicate,
|
||||
}
|
||||
|
||||
enum MyGattStatusArgs {
|
||||
enum MyGATTStatusArgs {
|
||||
success,
|
||||
readNotPermitted,
|
||||
writeNotPermitted,
|
||||
requestNotSupported,
|
||||
invalidOffset,
|
||||
insufficientAuthentication,
|
||||
requestNotSupported,
|
||||
insufficientEncryption,
|
||||
invalidOffset,
|
||||
insufficientAuthorization,
|
||||
invalidAttributeLength,
|
||||
connectionCongested,
|
||||
failure,
|
||||
}
|
||||
|
||||
class MyCentralManagerArgs {
|
||||
final Uint8List enableNotificationValue;
|
||||
final Uint8List enableIndicationValue;
|
||||
final Uint8List disableNotificationValue;
|
||||
|
||||
MyCentralManagerArgs(
|
||||
this.enableNotificationValue,
|
||||
this.enableIndicationValue,
|
||||
this.disableNotificationValue,
|
||||
);
|
||||
}
|
||||
|
||||
class MyPeripheralManagerArgs {
|
||||
final Uint8List enableNotificationValue;
|
||||
final Uint8List enableIndicationValue;
|
||||
final Uint8List disableNotificationValue;
|
||||
|
||||
MyPeripheralManagerArgs(
|
||||
this.enableNotificationValue,
|
||||
this.enableIndicationValue,
|
||||
this.disableNotificationValue,
|
||||
);
|
||||
}
|
||||
|
||||
class MyManufacturerSpecificDataArgs {
|
||||
final int idArgs;
|
||||
final Uint8List dataArgs;
|
||||
@ -64,7 +100,7 @@ class MyAdvertisementArgs {
|
||||
final String? nameArgs;
|
||||
final List<String?> serviceUUIDsArgs;
|
||||
final Map<String?, Uint8List?> serviceDataArgs;
|
||||
final MyManufacturerSpecificDataArgs? manufacturerSpecificDataArgs;
|
||||
final List<MyManufacturerSpecificDataArgs?> manufacturerSpecificDataArgs;
|
||||
|
||||
MyAdvertisementArgs(
|
||||
this.nameArgs,
|
||||
@ -86,25 +122,23 @@ class MyPeripheralArgs {
|
||||
MyPeripheralArgs(this.addressArgs);
|
||||
}
|
||||
|
||||
class MyGattDescriptorArgs {
|
||||
class MyGATTDescriptorArgs {
|
||||
final int hashCodeArgs;
|
||||
final String uuidArgs;
|
||||
final Uint8List? valueArgs;
|
||||
|
||||
MyGattDescriptorArgs(
|
||||
MyGATTDescriptorArgs(
|
||||
this.hashCodeArgs,
|
||||
this.uuidArgs,
|
||||
this.valueArgs,
|
||||
);
|
||||
}
|
||||
|
||||
class MyGattCharacteristicArgs {
|
||||
class MyGATTCharacteristicArgs {
|
||||
final int hashCodeArgs;
|
||||
final String uuidArgs;
|
||||
final List<int?> propertyNumbersArgs;
|
||||
final List<MyGattDescriptorArgs?> descriptorsArgs;
|
||||
final List<MyGATTDescriptorArgs?> descriptorsArgs;
|
||||
|
||||
MyGattCharacteristicArgs(
|
||||
MyGATTCharacteristicArgs(
|
||||
this.hashCodeArgs,
|
||||
this.uuidArgs,
|
||||
this.propertyNumbersArgs,
|
||||
@ -112,35 +146,88 @@ class MyGattCharacteristicArgs {
|
||||
);
|
||||
}
|
||||
|
||||
class MyGattServiceArgs {
|
||||
class MyGATTServiceArgs {
|
||||
final int hashCodeArgs;
|
||||
final String uuidArgs;
|
||||
final List<MyGattCharacteristicArgs?> characteristicsArgs;
|
||||
final bool isPrimaryArgs;
|
||||
final List<MyGATTServiceArgs?> includedServicesArgs;
|
||||
final List<MyGATTCharacteristicArgs?> characteristicsArgs;
|
||||
|
||||
MyGattServiceArgs(
|
||||
MyGATTServiceArgs(
|
||||
this.hashCodeArgs,
|
||||
this.uuidArgs,
|
||||
this.isPrimaryArgs,
|
||||
this.includedServicesArgs,
|
||||
this.characteristicsArgs,
|
||||
);
|
||||
}
|
||||
|
||||
class MyMutableGATTDescriptorArgs {
|
||||
final int hashCodeArgs;
|
||||
final String uuidArgs;
|
||||
final List<int?> permissionNumbersArgs;
|
||||
|
||||
MyMutableGATTDescriptorArgs(
|
||||
this.hashCodeArgs,
|
||||
this.uuidArgs,
|
||||
this.permissionNumbersArgs,
|
||||
);
|
||||
}
|
||||
|
||||
class MyMutableGATTCharacteristicArgs {
|
||||
final int hashCodeArgs;
|
||||
final String uuidArgs;
|
||||
final List<int?> permissionNumbersArgs;
|
||||
final List<int?> propertyNumbersArgs;
|
||||
final List<MyMutableGATTDescriptorArgs?> descriptorsArgs;
|
||||
|
||||
MyMutableGATTCharacteristicArgs(
|
||||
this.hashCodeArgs,
|
||||
this.uuidArgs,
|
||||
this.permissionNumbersArgs,
|
||||
this.propertyNumbersArgs,
|
||||
this.descriptorsArgs,
|
||||
);
|
||||
}
|
||||
|
||||
class MyMutableGATTServiceArgs {
|
||||
final int hashCodeArgs;
|
||||
final String uuidArgs;
|
||||
final bool isPrimaryArgs;
|
||||
final List<MyMutableGATTServiceArgs?> includedServicesArgs;
|
||||
final List<MyMutableGATTCharacteristicArgs?> characteristicsArgs;
|
||||
|
||||
MyMutableGATTServiceArgs(
|
||||
this.hashCodeArgs,
|
||||
this.uuidArgs,
|
||||
this.isPrimaryArgs,
|
||||
this.includedServicesArgs,
|
||||
this.characteristicsArgs,
|
||||
);
|
||||
}
|
||||
|
||||
@HostApi()
|
||||
abstract class MyCentralManagerHostApi {
|
||||
abstract class MyCentralManagerHostAPI {
|
||||
MyCentralManagerArgs initialize();
|
||||
MyBluetoothLowEnergyStateArgs getState();
|
||||
@async
|
||||
void setUp();
|
||||
bool authorize();
|
||||
@async
|
||||
void startDiscovery();
|
||||
void showAppSettings();
|
||||
@async
|
||||
void startDiscovery(List<String> serviceUUIDsArgs);
|
||||
void stopDiscovery();
|
||||
@async
|
||||
void connect(String addressArgs);
|
||||
@async
|
||||
void disconnect(String addressArgs);
|
||||
List<MyPeripheralArgs> retrieveConnectedPeripherals();
|
||||
@async
|
||||
int requestMTU(String addressArgs, int mtuArgs);
|
||||
@async
|
||||
int readRSSI(String addressArgs);
|
||||
@async
|
||||
List<MyGattServiceArgs> discoverServices(String addressArgs);
|
||||
List<MyGATTServiceArgs> discoverGATT(String addressArgs);
|
||||
@async
|
||||
Uint8List readCharacteristic(String addressArgs, int hashCodeArgs);
|
||||
@async
|
||||
@ -148,13 +235,12 @@ abstract class MyCentralManagerHostApi {
|
||||
String addressArgs,
|
||||
int hashCodeArgs,
|
||||
Uint8List valueArgs,
|
||||
int typeNumberArgs,
|
||||
MyGATTCharacteristicWriteTypeArgs typeArgs,
|
||||
);
|
||||
@async
|
||||
void setCharacteristicNotifyState(
|
||||
void setCharacteristicNotification(
|
||||
String addressArgs,
|
||||
int hashCodeArgs,
|
||||
int stateNumberArgs,
|
||||
bool enableArgs,
|
||||
);
|
||||
@async
|
||||
Uint8List readDescriptor(String addressArgs, int hashCodeArgs);
|
||||
@ -167,91 +253,99 @@ abstract class MyCentralManagerHostApi {
|
||||
}
|
||||
|
||||
@FlutterApi()
|
||||
abstract class MyCentralManagerFlutterApi {
|
||||
void onStateChanged(int stateNumberArgs);
|
||||
abstract class MyCentralManagerFlutterAPI {
|
||||
void onStateChanged(MyBluetoothLowEnergyStateArgs stateArgs);
|
||||
void onDiscovered(
|
||||
MyPeripheralArgs peripheralArgs,
|
||||
int rssiArgs,
|
||||
MyAdvertisementArgs advertisementArgs,
|
||||
);
|
||||
void onConnectionStateChanged(String addressArgs, bool stateArgs);
|
||||
void onMtuChanged(String addressArgs, int mtuArgs);
|
||||
void onConnectionStateChanged(
|
||||
MyPeripheralArgs peripheralArgs,
|
||||
MyConnectionStateArgs stateArgs,
|
||||
);
|
||||
void onMTUChanged(MyPeripheralArgs peripheralArgs, int mtuArgs);
|
||||
void onCharacteristicNotified(
|
||||
String addressArgs,
|
||||
int hashCodeArgs,
|
||||
MyPeripheralArgs peripheralArgs,
|
||||
MyGATTCharacteristicArgs characteristicArgs,
|
||||
Uint8List valueArgs,
|
||||
);
|
||||
}
|
||||
|
||||
@HostApi()
|
||||
abstract class MyPeripheralManagerHostApi {
|
||||
abstract class MyPeripheralManagerHostAPI {
|
||||
MyPeripheralManagerArgs initialize();
|
||||
MyBluetoothLowEnergyStateArgs getState();
|
||||
@async
|
||||
void setUp();
|
||||
bool authorize();
|
||||
@async
|
||||
void addService(MyGattServiceArgs serviceArgs);
|
||||
void showAppSettings();
|
||||
void openGATTServer();
|
||||
void closeGATTServer();
|
||||
@async
|
||||
void addService(MyMutableGATTServiceArgs serviceArgs);
|
||||
void removeService(int hashCodeArgs);
|
||||
void clearServices();
|
||||
void removeAllServices();
|
||||
@async
|
||||
void startAdvertising(MyAdvertisementArgs advertisementArgs);
|
||||
void stopAdvertising();
|
||||
void sendResponse(
|
||||
String addressArgs,
|
||||
int idArgs,
|
||||
int statusNumberArgs,
|
||||
MyGATTStatusArgs statusArgs,
|
||||
int offsetArgs,
|
||||
Uint8List? valueArgs,
|
||||
);
|
||||
@async
|
||||
void notifyCharacteristicChanged(
|
||||
int hashCodeArgs,
|
||||
Uint8List valueArgs,
|
||||
bool confirmArgs,
|
||||
String addressArgs,
|
||||
int hashCodeArgs,
|
||||
bool confirmArgs,
|
||||
Uint8List valueArgs,
|
||||
);
|
||||
}
|
||||
|
||||
@FlutterApi()
|
||||
abstract class MyPeripheralManagerFlutterApi {
|
||||
void onStateChanged(int stateNumberArgs);
|
||||
void onConnectionStateChanged(MyCentralArgs centralArgs, bool stateArgs);
|
||||
void onMtuChanged(String addressArgs, int mtuArgs);
|
||||
abstract class MyPeripheralManagerFlutterAPI {
|
||||
void onStateChanged(MyBluetoothLowEnergyStateArgs stateArgs);
|
||||
void onConnectionStateChanged(
|
||||
MyCentralArgs centralArgs,
|
||||
int statusArgs,
|
||||
MyConnectionStateArgs stateArgs,
|
||||
);
|
||||
void onMTUChanged(MyCentralArgs centralArgs, int mtuArgs);
|
||||
void onCharacteristicReadRequest(
|
||||
String addressArgs,
|
||||
int hashCodeArgs,
|
||||
MyCentralArgs centralArgs,
|
||||
int idArgs,
|
||||
int offsetArgs,
|
||||
int hashCodeArgs,
|
||||
);
|
||||
void onCharacteristicWriteRequest(
|
||||
String addressArgs,
|
||||
int hashCodeArgs,
|
||||
MyCentralArgs centralArgs,
|
||||
int idArgs,
|
||||
int offsetArgs,
|
||||
Uint8List valueArgs,
|
||||
int hashCodeArgs,
|
||||
bool preparedWriteArgs,
|
||||
bool responseNeededArgs,
|
||||
);
|
||||
void onCharacteristicNotifyStateChanged(
|
||||
String addressArgs,
|
||||
int hashCodeArgs,
|
||||
int stateNumberArgs,
|
||||
int offsetArgs,
|
||||
Uint8List valueArgs,
|
||||
);
|
||||
void onDescriptorReadRequest(
|
||||
String addressArgs,
|
||||
int hashCodeArgs,
|
||||
MyCentralArgs centralArgs,
|
||||
int idArgs,
|
||||
int offsetArgs,
|
||||
int hashCodeArgs,
|
||||
);
|
||||
void onDescriptorWriteRequest(
|
||||
String addressArgs,
|
||||
int hashCodeArgs,
|
||||
MyCentralArgs centralArgs,
|
||||
int idArgs,
|
||||
int offsetArgs,
|
||||
Uint8List valueArgs,
|
||||
int hashCodeArgs,
|
||||
bool preparedWriteArgs,
|
||||
bool responseNeededArgs,
|
||||
int offsetArgs,
|
||||
Uint8List valueArgs,
|
||||
);
|
||||
void onExecuteWrite(
|
||||
String addressArgs,
|
||||
MyCentralArgs centralArgs,
|
||||
int idArgs,
|
||||
bool executeArgs,
|
||||
);
|
||||
|
@ -1,28 +1,38 @@
|
||||
name: bluetooth_low_energy_android
|
||||
description: Android implementation of the bluetooth_low_energy plugin.
|
||||
version: 5.0.5
|
||||
description: "Android implementation of the bluetooth_low_energy plugin."
|
||||
version: 6.0.0
|
||||
homepage: https://github.com/yanshouwang/bluetooth_low_energy
|
||||
repository: https://github.com/yanshouwang/bluetooth_low_energy
|
||||
issue_tracker: https://github.com/yanshouwang/bluetooth_low_energy/issues
|
||||
topics:
|
||||
- bluetooth
|
||||
- bluetooth-low-energy
|
||||
- ble
|
||||
funding:
|
||||
- https://paypal.me/yanshouwang5112
|
||||
- https://afdian.net/a/yanshouwang
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
flutter: '>=3.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
bluetooth_low_energy_platform_interface: ^5.0.2
|
||||
bluetooth_low_energy_platform_interface: ^6.0.0
|
||||
collection: ^1.18.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
pigeon: ^15.0.2
|
||||
flutter_lints: ^4.0.0
|
||||
pigeon: ^19.0.0
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
implements: bluetooth_low_energy
|
||||
platforms:
|
||||
android:
|
||||
package: dev.yanshouwang.bluetooth_low_energy_android
|
||||
pluginClass: BluetoothLowEnergyAndroid
|
||||
dartPluginClass: BluetoothLowEnergyAndroid
|
||||
package: dev.hebei.bluetooth_low_energy_android
|
||||
pluginClass: BluetoothLowEnergyAndroidPlugin
|
||||
dartPluginClass: BluetoothLowEnergyAndroidPlugin
|
||||
|
@ -0,0 +1 @@
|
||||
void main() {}
|
Reference in New Issue
Block a user