Implement Logging (#199)

* Implement Logging

* Add timestamp in OnValueChanged

* improve web and linux logging

* Implement windows and format pigeon

* Fix windows build and unity log level

* Log commands with timestamp

* Fix code doc

* Remove unnecessary import

* Update docs

* Fix crash on windows 11 because of failed pairing

* Update changelog

* Fix native code formatting
This commit is contained in:
Rohit Sangwan
2025-12-12 18:56:43 +05:30
committed by GitHub
parent d85cbbe725
commit 89abb3bf03
32 changed files with 1016 additions and 346 deletions
+3
View File
@@ -1,4 +1,6 @@
## 1.0.0
* BREAKING CHANGE: `OnValueChange` callback also includes timestamp
* Fix Windows 11 crash on cancelling pairing
* Unified error codes for all platforms
* Add `isScanning` api
* Add `requestPermissions` api and auto ask permission on `startScan`
@@ -6,6 +8,7 @@
* Improve Windows disconnection event handling and cleanup
* Add `withDescriptors` parameter in `discoverServices` API
* Add `timestamp` in `BleDevice`
* Add `setLogLevel` api for configuring logs
## 0.21.1
* Fix device name resolution on Windows
+17
View File
@@ -745,6 +745,23 @@ class UniversalBleMock extends UniversalBlePlatform {
UniversalBle.setInstance(UniversalBleMock());
```
## Logging
Configure logging to help debug Ble operations
### Usage
Set the log level during app initialization, default level is `none`
```dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Enable verbose logging to see all BLE operations
await UniversalBle.setLogLevel(BleLogLevel.verbose);
runApp(MyApp());
}
```
## Low level API
For more granular control, you can use the [Low-Level API](README.low_level.md). This API is "Device ID"-based, offering greater flexibility by enabling direct calls without the need for object instances.
@@ -18,13 +18,13 @@ private const val TAG = "UniversalBlePlugin"
/**
* A safe wrapper for Bluetooth LE scanning operations that prevents excessive scanning.
*
*
* This class manages BLE scanning while adhering to Android's scanning frequency limits by:
* - Tracking scan start times over a 30-second window
* - Limiting to 5 scan operations within this window
* - Automatically scheduling delayed scans when frequency limits are exceeded
* - Providing safe start/stop scan operations with error handling
*
*
* The scanner will automatically delay new scan requests if the frequency limit is reached,
* and will retry once sufficient time has passed. This helps prevent scan failure errors
* and ensures compliance with Android's scanning restrictions.
@@ -81,6 +81,21 @@ class FlutterError (
val details: Any? = null
) : Throwable()
enum class UniversalBleLogLevel(val raw: Int) {
NONE(0),
ERROR(1),
WARNING(2),
INFO(3),
DEBUG(4),
VERBOSE(5);
companion object {
fun ofRaw(raw: Int): UniversalBleLogLevel? {
return values().firstOrNull { it.raw == raw }
}
}
}
/** Unified error codes for all platforms */
enum class UniversalBleErrorCode(val raw: Int) {
UNKNOWN_ERROR(0),
@@ -398,40 +413,45 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as Long?)?.let {
UniversalBleErrorCode.ofRaw(it.toInt())
UniversalBleLogLevel.ofRaw(it.toInt())
}
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBleScanResult.fromList(it)
return (readValue(buffer) as Long?)?.let {
UniversalBleErrorCode.ofRaw(it.toInt())
}
}
131.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBleService.fromList(it)
UniversalBleScanResult.fromList(it)
}
}
132.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBleCharacteristic.fromList(it)
UniversalBleService.fromList(it)
}
}
133.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBleDescriptor.fromList(it)
UniversalBleCharacteristic.fromList(it)
}
}
134.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalScanFilter.fromList(it)
UniversalBleDescriptor.fromList(it)
}
}
135.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalManufacturerDataFilter.fromList(it)
UniversalScanFilter.fromList(it)
}
}
136.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalManufacturerDataFilter.fromList(it)
}
}
137.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalManufacturerData.fromList(it)
}
@@ -441,38 +461,42 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() {
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is UniversalBleErrorCode -> {
is UniversalBleLogLevel -> {
stream.write(129)
writeValue(stream, value.raw.toLong())
}
is UniversalBleScanResult -> {
is UniversalBleErrorCode -> {
stream.write(130)
writeValue(stream, value.toList())
writeValue(stream, value.raw.toLong())
}
is UniversalBleService -> {
is UniversalBleScanResult -> {
stream.write(131)
writeValue(stream, value.toList())
}
is UniversalBleCharacteristic -> {
is UniversalBleService -> {
stream.write(132)
writeValue(stream, value.toList())
}
is UniversalBleDescriptor -> {
is UniversalBleCharacteristic -> {
stream.write(133)
writeValue(stream, value.toList())
}
is UniversalScanFilter -> {
is UniversalBleDescriptor -> {
stream.write(134)
writeValue(stream, value.toList())
}
is UniversalManufacturerDataFilter -> {
is UniversalScanFilter -> {
stream.write(135)
writeValue(stream, value.toList())
}
is UniversalManufacturerData -> {
is UniversalManufacturerDataFilter -> {
stream.write(136)
writeValue(stream, value.toList())
}
is UniversalManufacturerData -> {
stream.write(137)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
@@ -504,6 +528,7 @@ interface UniversalBlePlatformChannel {
fun unPair(deviceId: String)
fun getSystemDevices(withServices: List<String>, callback: (Result<List<UniversalBleScanResult>>) -> Unit)
fun getConnectionState(deviceId: String): Long
fun setLogLevel(logLevel: UniversalBleLogLevel)
companion object {
/** The codec used by UniversalBlePlatformChannel. */
@@ -876,6 +901,24 @@ interface UniversalBlePlatformChannel {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val logLevelArg = args[0] as UniversalBleLogLevel
val wrapped: List<Any?> = try {
api.setLogLevel(logLevelArg)
listOf(null)
} catch (exception: Throwable) {
UniversalBlePigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@@ -942,12 +985,12 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger,
}
}
}
fun onValueChanged(deviceIdArg: String, characteristicIdArg: String, valueArg: ByteArray, callback: (Result<Unit>) -> Unit)
fun onValueChanged(deviceIdArg: String, characteristicIdArg: String, valueArg: ByteArray, timestampArg: Long?, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(deviceIdArg, characteristicIdArg, valueArg)) {
channel.send(listOf(deviceIdArg, characteristicIdArg, valueArg, timestampArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
@@ -0,0 +1,49 @@
package com.navideck.universal_ble
import android.util.Log
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object UniversalBleLogger {
private const val TAG = "UniversalBle"
private var currentLogLevel: UniversalBleLogLevel = UniversalBleLogLevel.NONE
private val timeFormatter = SimpleDateFormat("HH:mm:ss.SSS", Locale.US)
fun setLogLevel(logLevel: UniversalBleLogLevel) {
currentLogLevel = logLevel
}
fun logError(message: String) {
if (!allows(UniversalBleLogLevel.ERROR)) return
Log.e(TAG, withTimestamp(message))
}
fun logWarning(message: String) {
if (!allows(UniversalBleLogLevel.WARNING)) return
Log.w(TAG, withTimestamp(message))
}
fun logInfo(message: String) {
if (!allows(UniversalBleLogLevel.INFO)) return
Log.i(TAG, withTimestamp(message))
}
fun logDebug(message: String) {
if (!allows(UniversalBleLogLevel.DEBUG)) return
Log.d(TAG, withTimestamp(message))
}
fun logVerbose(message: String) {
if (!allows(UniversalBleLogLevel.VERBOSE)) return
Log.v(TAG, withTimestamp(message))
}
private fun allows(level: UniversalBleLogLevel): Boolean {
return currentLogLevel != UniversalBleLogLevel.NONE && level.ordinal <= currentLogLevel.ordinal
}
private fun withTimestamp(message: String): String {
return "[${timeFormatter.format(Date())}] $message"
}
}
@@ -24,7 +24,6 @@ import android.content.IntentFilter
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
@@ -35,8 +34,6 @@ import java.util.concurrent.TimeUnit
import androidx.core.content.edit
private const val TAG = "UniversalBlePlugin"
@SuppressLint("MissingPermission")
class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), FlutterPlugin,
ActivityAware, PluginRegistry.ActivityResultListener,
@@ -178,7 +175,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
// Set custom scan filter only if required
if (usesCustomFilters) {
Log.e(TAG, "Using Custom Filters")
UniversalBleLogger.logError("Using Custom Filters")
universalBleFilterUtil.scanFilter = filter
universalBleFilterUtil.serviceFilterUUIDS = filterServices
} else {
@@ -217,7 +214,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
deviceId.findGatt()?.let {
val currentState = bluetoothManager.getConnectionState(it.device, BluetoothProfile.GATT)
if (currentState == BluetoothGatt.STATE_CONNECTED) {
Log.e(TAG, "$deviceId Already connected")
UniversalBleLogger.logError("$deviceId Already connected")
mainThreadHandler?.post {
callbackChannel?.onConnectionChanged(deviceId, true, null) {}
}
@@ -266,7 +263,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
connectionState.toBleConnectionState().value
} else {
// Might be connected with device, but not with app
Log.e(TAG, "Device might be connected but not known to this app")
UniversalBleLogger.logError("Device might be connected but not known to this app")
BleConnectionState.Disconnected.value
}
} catch (e: Exception) {
@@ -274,6 +271,10 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
}
}
override fun setLogLevel(logLevel: UniversalBleLogLevel) {
UniversalBleLogger.setLogLevel(logLevel)
}
override fun discoverServices(
deviceId: String,
withDescriptors: Boolean,
@@ -348,6 +349,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
callback: (Result<Unit>) -> Unit,
) {
try {
UniversalBleLogger.logDebug("SET_NOTIFY -> $deviceId $service $characteristic input=$bleInputProperty")
val gatt = deviceId.toBluetoothGatt()
val gattCharacteristic: BluetoothGattCharacteristic? =
gatt.getCharacteristic(service, characteristic)
@@ -408,7 +410,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
}
}
} else {
Log.d("UniversalBle", "CCCD Descriptor not found")
UniversalBleLogger.logDebug("CCCD Descriptor not found")
}
if (gatt.setCharacteristicNotification(gattCharacteristic, enable)) {
@@ -456,6 +458,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
callback: (Result<ByteArray>) -> Unit,
) {
try {
UniversalBleLogger.logDebug("READ -> $deviceId $service $characteristic")
val gatt = deviceId.toBluetoothGatt()
val gattCharacteristic = gatt.getCharacteristic(service, characteristic)
if (gattCharacteristic == null) {
@@ -519,6 +522,9 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
if (status == BluetoothGatt.GATT_SUCCESS) {
it.result(Result.success(value))
} else {
UniversalBleLogger.logError(
"READ_FAILED <- ${gatt.device.address} ${characteristic.uuid} status=$status"
)
it.result(
Result.failure(
createFlutterError(
@@ -542,6 +548,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
callback: (Result<Unit>) -> Unit,
) {
try {
UniversalBleLogger.logDebug("WRITE -> $deviceId $service $characteristic len=${value.size} property=$bleOutputProperty")
val gatt = deviceId.toBluetoothGatt()
val gattCharacteristic = gatt.getCharacteristic(service, characteristic)
if (gattCharacteristic == null) {
@@ -638,6 +645,9 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
if (status == BluetoothGatt.GATT_SUCCESS) {
it.result(Result.success(Unit))
} else {
UniversalBleLogger.logError(
"WRITE_FAILED <- ${gatt?.device?.address} ${characteristic.uuid} status=$status"
)
it.result(
Result.failure(
createFlutterError(
@@ -653,6 +663,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
override fun requestMtu(deviceId: String, expectedMtu: Long, callback: (Result<Long>) -> Unit) {
UniversalBleLogger.logDebug("REQUEST_MTU -> $deviceId expected=$expectedMtu")
try {
val gatt = deviceId.toBluetoothGatt()
gatt.requestMtu(expectedMtu.toInt())
@@ -957,13 +968,13 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
if (device == null) {
Log.e(TAG, "No device found in ACTION_BOND_STATE_CHANGED intent")
UniversalBleLogger.logError("No device found in ACTION_BOND_STATE_CHANGED intent")
return
}
// get pairing failed error
when (intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR)) {
BluetoothDevice.BOND_BONDING -> {
Log.v(TAG, "${device.address} BOND_BONDING")
UniversalBleLogger.logVerbose("${device.address} BOND_BONDING")
}
BOND_BONDED -> {
@@ -975,7 +986,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
}
BluetoothDevice.BOND_NONE -> {
Log.e(TAG, "${device.address} BOND_NONE")
UniversalBleLogger.logError("${device.address} BOND_NONE")
onBondStateUpdate(device.address, false)
}
}
@@ -986,12 +997,12 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
private val scanCallback = object : ScanCallback() {
override fun onScanFailed(errorCode: Int) {
Log.e(TAG, "OnScanFailed: ${errorCode.parseScanErrorMessage()}")
UniversalBleLogger.logError("OnScanFailed: ${errorCode.parseScanErrorMessage()}")
}
override fun onScanResult(callbackType: Int, result: ScanResult) {
// Log.v(TAG, "onScanResult: $result")
// UniversalBleLogger.logVerbose("onScanResult: $result")
var serviceUuids: Array<UUID> = arrayOf()
result.device.uuids?.forEach {
serviceUuids += it.uuid
@@ -1029,14 +1040,17 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
}
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
Log.v(TAG, "onBatchScanResults: $results")
UniversalBleLogger.logVerbose("onBatchScanResults: $results")
}
}
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
Log.d(
TAG,
override fun onConnectionStateChange(
gatt: BluetoothGatt,
status: Int,
newState: Int,
) {
UniversalBleLogger.logDebug(
"onConnectionStateChange-> Status: $status ${status.parseHciErrorCode()}, NewState: $newState"
)
@@ -1053,7 +1067,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
gatt.device.address, false, status.parseHciErrorCode()
) {}
}
Log.d(TAG, "Closing gatt for ${gatt.device.name}")
UniversalBleLogger.logDebug("Closing gatt for ${gatt.device.name}")
gatt.close()
}
}
@@ -1063,11 +1077,15 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
) {
UniversalBleLogger.logVerbose(
"NOTIFY <- ${gatt.device.address} ${characteristic.uuid} len=${value.size}"
)
mainThreadHandler?.post {
callbackChannel?.onValueChanged(
deviceIdArg = gatt.device.address,
characteristicIdArg = characteristic.uuid.toString(),
valueArg = value
valueArg = value,
timestampArg = System.currentTimeMillis()
) {}
}
}
+53 -19
View File
@@ -132,6 +132,15 @@ func deepHashUniversalBle(value: Any?, hasher: inout Hasher) {
enum UniversalBleLogLevel: Int {
case none = 0
case error = 1
case warning = 2
case info = 3
case debug = 4
case verbose = 5
}
/// Unified error codes for all platforms
enum UniversalBleErrorCode: Int {
case unknownError = 0
@@ -436,22 +445,28 @@ private class UniversalBlePigeonCodecReader: FlutterStandardReader {
case 129:
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
if let enumResultAsInt = enumResultAsInt {
return UniversalBleErrorCode(rawValue: enumResultAsInt)
return UniversalBleLogLevel(rawValue: enumResultAsInt)
}
return nil
case 130:
return UniversalBleScanResult.fromList(self.readValue() as! [Any?])
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
if let enumResultAsInt = enumResultAsInt {
return UniversalBleErrorCode(rawValue: enumResultAsInt)
}
return nil
case 131:
return UniversalBleService.fromList(self.readValue() as! [Any?])
return UniversalBleScanResult.fromList(self.readValue() as! [Any?])
case 132:
return UniversalBleCharacteristic.fromList(self.readValue() as! [Any?])
return UniversalBleService.fromList(self.readValue() as! [Any?])
case 133:
return UniversalBleDescriptor.fromList(self.readValue() as! [Any?])
return UniversalBleCharacteristic.fromList(self.readValue() as! [Any?])
case 134:
return UniversalScanFilter.fromList(self.readValue() as! [Any?])
return UniversalBleDescriptor.fromList(self.readValue() as! [Any?])
case 135:
return UniversalManufacturerDataFilter.fromList(self.readValue() as! [Any?])
return UniversalScanFilter.fromList(self.readValue() as! [Any?])
case 136:
return UniversalManufacturerDataFilter.fromList(self.readValue() as! [Any?])
case 137:
return UniversalManufacturerData.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
@@ -461,30 +476,33 @@ private class UniversalBlePigeonCodecReader: FlutterStandardReader {
private class UniversalBlePigeonCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? UniversalBleErrorCode {
if let value = value as? UniversalBleLogLevel {
super.writeByte(129)
super.writeValue(value.rawValue)
} else if let value = value as? UniversalBleScanResult {
} else if let value = value as? UniversalBleErrorCode {
super.writeByte(130)
super.writeValue(value.toList())
} else if let value = value as? UniversalBleService {
super.writeValue(value.rawValue)
} else if let value = value as? UniversalBleScanResult {
super.writeByte(131)
super.writeValue(value.toList())
} else if let value = value as? UniversalBleCharacteristic {
} else if let value = value as? UniversalBleService {
super.writeByte(132)
super.writeValue(value.toList())
} else if let value = value as? UniversalBleDescriptor {
} else if let value = value as? UniversalBleCharacteristic {
super.writeByte(133)
super.writeValue(value.toList())
} else if let value = value as? UniversalScanFilter {
} else if let value = value as? UniversalBleDescriptor {
super.writeByte(134)
super.writeValue(value.toList())
} else if let value = value as? UniversalManufacturerDataFilter {
} else if let value = value as? UniversalScanFilter {
super.writeByte(135)
super.writeValue(value.toList())
} else if let value = value as? UniversalManufacturerData {
} else if let value = value as? UniversalManufacturerDataFilter {
super.writeByte(136)
super.writeValue(value.toList())
} else if let value = value as? UniversalManufacturerData {
super.writeByte(137)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
@@ -529,6 +547,7 @@ protocol UniversalBlePlatformChannel {
func unPair(deviceId: String) throws
func getSystemDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void)
func getConnectionState(deviceId: String) throws -> Int64
func setLogLevel(logLevel: UniversalBleLogLevel) throws
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@@ -847,6 +866,21 @@ class UniversalBlePlatformChannelSetup {
} else {
getConnectionStateChannel.setMessageHandler(nil)
}
let setLogLevelChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
setLogLevelChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let logLevelArg = args[0] as! UniversalBleLogLevel
do {
try api.setLogLevel(logLevel: logLevelArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
setLogLevelChannel.setMessageHandler(nil)
}
}
}
/// Native -> Flutter
@@ -856,7 +890,7 @@ protocol UniversalBleCallbackChannelProtocol {
func onAvailabilityChanged(state stateArg: Int64, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onPairStateChange(deviceId deviceIdArg: String, isPaired isPairedArg: Bool, error errorArg: String?, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onScanResult(result resultArg: UniversalBleScanResult, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onValueChanged(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, value valueArg: FlutterStandardTypedData, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onValueChanged(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, value valueArg: FlutterStandardTypedData, timestamp timestampArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onConnectionChanged(deviceId deviceIdArg: String, connected connectedArg: Bool, error errorArg: String?, completion: @escaping (Result<Void, PigeonError>) -> Void)
}
class UniversalBleCallbackChannel: UniversalBleCallbackChannelProtocol {
@@ -923,10 +957,10 @@ class UniversalBleCallbackChannel: UniversalBleCallbackChannelProtocol {
}
}
}
func onValueChanged(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, value valueArg: FlutterStandardTypedData, completion: @escaping (Result<Void, PigeonError>) -> Void) {
func onValueChanged(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, value valueArg: FlutterStandardTypedData, timestamp timestampArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([deviceIdArg, characteristicIdArg, valueArg] as [Any?]) { response in
channel.sendMessage([deviceIdArg, characteristicIdArg, valueArg, timestampArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
@@ -30,7 +30,7 @@ class UniversalBleAsyncServiceDiscovery: NSObject {
/// Starts the service discovery process
func startDiscovery() {
guard !isDiscoveryInProgress else {
print("Service discovery already in progress for device: \(deviceId)")
UniversalBleLogger.shared.logWarning("Service discovery already in progress for device: \(deviceId)")
return
}
isDiscoveryInProgress = true
+53
View File
@@ -0,0 +1,53 @@
import Foundation
final class UniversalBleLogger {
static let shared = UniversalBleLogger()
private init() {}
private lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss.SSS"
return formatter
}()
private var currentLogLevel: UniversalBleLogLevel = .none
func setLogLevel(_ logLevel: UniversalBleLogLevel) {
currentLogLevel = logLevel
}
func logError(_ message: String) {
guard allows(.error) else { return }
print("UniversalBle:ERROR \(withTimestamp(message))")
}
func logWarning(_ message: String) {
guard allows(.warning) else { return }
print("UniversalBle:WARN \(withTimestamp(message))")
}
func logInfo(_ message: String) {
guard allows(.info) else { return }
print("UniversalBle:INFO \(withTimestamp(message))")
}
func logDebug(_ message: String) {
guard allows(.debug) else { return }
print("UniversalBle:DEBUG \(withTimestamp(message))")
}
func logVerbose(_ message: String) {
guard allows(.verbose) else { return }
print("UniversalBle:VERBOSE \(withTimestamp(message))")
}
private func allows(_ level: UniversalBleLogLevel) -> Bool {
return currentLogLevel != .none && level.rawValue <= currentLogLevel.rawValue
}
private func withTimestamp(_ message: String) -> String {
let time = dateFormatter.string(from: Date())
return "[\(time)] \(message)"
}
}
+29 -4
View File
@@ -93,7 +93,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
var withServices: [CBUUID] = try filter?.withServices.compactMap { $0 }.toCBUUID() ?? []
if usesCustomFilters {
print("Using Custom Filters")
UniversalBleLogger.shared.logInfo("Using Custom Filters")
universalBleFilterUtil.scanFilter = filter
universalBleFilterUtil.scanFilterServicesUUID = withServices
withServices = []
@@ -132,6 +132,10 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
return isManageScanning
}
func setLogLevel(logLevel: UniversalBleLogLevel) throws {
UniversalBleLogger.shared.setLogLevel(logLevel)
}
func connect(deviceId: String) throws {
let peripheral = try deviceId.getPeripheral(manager: manager)
peripheral.delegate = self
@@ -218,7 +222,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
// Check if discovery is already in progress
if activeServiceDiscoveries[deviceId] != nil {
print("Services discovery already in progress for :\(deviceId), waiting for completion.")
UniversalBleLogger.shared.logWarning("Services discovery already in progress for :\(deviceId), waiting for completion.")
discoverServicesFutures.append(DiscoverServicesFuture(deviceId: deviceId, result: completion))
return
}
@@ -247,6 +251,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
}
func setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Int64, completion: @escaping (Result<Void, any Error>) -> Void) {
UniversalBleLogger.shared.logDebug("SET_NOTIFY -> \(deviceId) \(service) \(characteristic) input=\(bleInputProperty)")
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
completion(Result.failure(createFlutterError(code: .deviceNotFound, message: "Unknown deviceId:\(deviceId)")))
return
@@ -273,6 +278,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
}
func readValue(deviceId: String, service: String, characteristic: String, completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void) {
UniversalBleLogger.shared.logDebug("READ -> \(deviceId) \(service) \(characteristic)")
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
completion(Result.failure(createFlutterError(code: .deviceNotFound, message: "Unknown deviceId:\(self)")))
return
@@ -290,6 +296,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
}
func writeValue(deviceId: String, service: String, characteristic: String, value: FlutterStandardTypedData, bleOutputProperty: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
UniversalBleLogger.shared.logDebug("WRITE -> \(deviceId) \(service) \(characteristic) len=\(value.data.count) property=\(bleOutputProperty)")
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
completion(Result.failure(createFlutterError(code: .deviceNotFound, message: "Unknown deviceId:\(self)")))
return
@@ -324,6 +331,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
}
func requestMtu(deviceId: String, expectedMtu _: Int64, completion: @escaping (Result<Int64, Error>) -> Void) {
UniversalBleLogger.shared.logDebug("REQUEST_MTU -> \(deviceId)")
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
completion(Result.failure(createFlutterError(code: .deviceNotFound, message: "Unknown deviceId:\(self)")))
return
@@ -349,7 +357,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
func getSystemDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void) {
var servicesFilter = withServices
if servicesFilter.isEmpty {
print("No services filter was set for getting system connected devices. Using default services...")
UniversalBleLogger.shared.logInfo("No services filter was set for getting system connected devices. Using default services...")
// Add several generic services
servicesFilter = ["1800", "1801", "180A", "180D", "1810", "181B", "1808", "181D", "1816", "1814", "181A", "1802", "1803", "1804", "1815", "1805", "1807", "1806", "1848", "185E", "180F", "1812", "180E", "1813"]
@@ -461,6 +469,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
characteristicWriteFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr {
if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("WRITE_FAILED <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else {
future.result(Result.success({}()))
@@ -475,6 +484,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
characteristicNotifyFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr {
if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("SET_NOTIFY_FAILED <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else {
future.result(Result.success({}()))
@@ -486,10 +496,24 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
}
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let error {
UniversalBleLogger.shared.logError("NOTIFY_ERROR <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(error.localizedDescription)")
}
if characteristic.isNotifying, let characteristicValue = characteristic.value {
let preview = characteristicValue.prefix(8).map { String(format: "%02X", $0) }.joined()
UniversalBleLogger.shared.logVerbose("NOTIFY <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr) len=\(characteristicValue.count) data=\(preview)")
}
// Update callbackChannel if notifying
if characteristic.isNotifying {
if let characteristicValue = characteristic.value {
callbackChannel.onValueChanged(deviceId: peripheral.uuid.uuidString, characteristicId: characteristic.uuid.uuidStr, value: FlutterStandardTypedData(bytes: characteristicValue)) { _ in }
callbackChannel.onValueChanged(
deviceId: peripheral.uuid.uuidString,
characteristicId: characteristic.uuid.uuidStr,
value: FlutterStandardTypedData(bytes: characteristicValue),
timestamp: Int64(Date().timeIntervalSince1970 * 1000)
) { _ in }
}
}
@@ -501,6 +525,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
characteristicReadFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr {
if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("READ_FAILED <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else {
if let characteristicValue = characteristic.value {
+4 -1
View File
@@ -1,7 +1,10 @@
import 'package:flutter/material.dart';
import 'package:universal_ble/universal_ble.dart';
import 'package:universal_ble_example/home/home.dart';
void main() {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await UniversalBle.setLogLevel(BleLogLevel.verbose);
runApp(
MaterialApp(
title: 'Universal BLE',
@@ -81,11 +81,14 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
}
}
void _handleValueChange(
String deviceId, String characteristicId, Uint8List value) {
void _handleValueChange(String deviceId, String characteristicId,
Uint8List value, int? timestamp) {
String s = String.fromCharCodes(value);
String data = '$s\nraw : ${value.toString()}';
debugPrint('_handleValueChange $characteristicId, $s');
DateTime? timestampDateTime = timestamp != null
? DateTime.fromMillisecondsSinceEpoch(timestamp)
: null;
debugPrint('_handleValueChange ($timestampDateTime) $characteristicId, $s');
_addLog("Value", data);
}
+4 -4
View File
@@ -214,10 +214,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.17.0"
version: "1.16.0"
path:
dependency: transitive
description:
@@ -315,10 +315,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.dev"
source: hosted
version: "0.7.7"
version: "0.7.6"
typed_data:
dependency: transitive
description:
+2 -2
View File
@@ -93,7 +93,7 @@ extension BleDeviceExtension on BleDevice {
///
/// [service] is the UUID of the service.
/// [preferCached] indicates whether to use cached services. If cache is empty, discoverServices() will be called.
/// might throw [NotFoundException]
/// might throw [UniversalBleException]
Future<BleService> getService(
String service, {
bool preferCached = true,
@@ -128,7 +128,7 @@ extension BleDeviceExtension on BleDevice {
/// [service] is the UUID of the service.
/// [characteristic] is the UUID of the characteristic.
/// [preferCached] indicates whether to use cached services. If cache is empty, discoverServices() will be called.
/// might throw [NotFoundException]
/// might throw [UniversalBleException]
Future<BleCharacteristic> getCharacteristic(
String characteristic, {
required String service,
+8
View File
@@ -0,0 +1,8 @@
enum BleLogLevel {
none,
error,
warning,
info,
debug,
verbose;
}
+1
View File
@@ -1,3 +1,4 @@
export 'package:universal_ble/src/models/ble_log_level.dart';
export 'package:universal_ble/src/models/manufacturer_data.dart';
export 'package:universal_ble/src/models/platform_config.dart';
export 'package:universal_ble/src/models/queue_type.dart';
+8
View File
@@ -24,6 +24,14 @@ class UniversalBle {
_bleCommandQueue.timeout = duration;
}
/// Set log level for both Dart and native implementations.
/// Only effective in debug builds.
static Future<void> setLogLevel(BleLogLevel logLevel) async {
if (!kDebugMode) return;
UniversalLogger.setLogLevel(logLevel);
await _platform.setLogLevel(logLevel);
}
/// Set how commands will be executed. By default, all commands are executed in a global queue (`QueueType.global`),
/// with each command waiting for the previous one to finish.
///
@@ -244,6 +244,10 @@ class UniversalBleLinux extends UniversalBlePlatform {
@override
Future<void> setNotifiable(String deviceId, String service,
String characteristic, BleInputProperty bleInputProperty) async {
UniversalLogger.logDebug(
"SET_NOTIFY -> $deviceId $service $characteristic input=${bleInputProperty.name}",
withTimestamp: true,
);
final char = _getCharacteristic(deviceId, service, characteristic);
String characteristicKey = "${deviceId}_${service}_$characteristic";
@@ -265,10 +269,15 @@ class UniversalBleLinux extends UniversalBlePlatform {
for (String property in properties) {
switch (property) {
case BluezProperty.value:
UniversalLogger.logVerbose(
"NOTIFY <- $deviceId $service $characteristic len=${char.value.length} data=${char.value}",
withTimestamp: true,
);
updateCharacteristicValue(
deviceId,
characteristic,
Uint8List.fromList(char.value),
DateTime.now().millisecondsSinceEpoch,
);
break;
default:
@@ -293,11 +302,19 @@ class UniversalBleLinux extends UniversalBlePlatform {
String characteristic, {
final Duration? timeout,
}) async {
UniversalLogger.logDebug(
"READ -> $deviceId $service $characteristic",
withTimestamp: true,
);
try {
final c = _getCharacteristic(deviceId, service, characteristic);
final data = await c.readValue();
return Uint8List.fromList(data);
} on BlueZFailedException catch (e) {
UniversalLogger.logError(
"READ_FAILED <- $deviceId $service $characteristic ${e.message}",
withTimestamp: true,
);
throw e.toUniversalBleException(
defaultCode: UniversalBleErrorCode.readFailed,
);
@@ -311,6 +328,10 @@ class UniversalBleLinux extends UniversalBlePlatform {
String characteristic,
Uint8List value,
BleOutputProperty bleOutputProperty) async {
UniversalLogger.logDebug(
"WRITE -> $deviceId $service $characteristic len=${value.length} property=${bleOutputProperty.name}",
withTimestamp: true,
);
try {
final c = _getCharacteristic(deviceId, service, characteristic);
if (bleOutputProperty == BleOutputProperty.withResponse) {
@@ -325,6 +346,10 @@ class UniversalBleLinux extends UniversalBlePlatform {
);
}
} on BlueZFailedException catch (e) {
UniversalLogger.logError(
"WRITE_FAILED <- $deviceId $service $characteristic ${e.message}",
withTimestamp: true,
);
throw e.toUniversalBleException(
defaultCode: UniversalBleErrorCode.writeFailed,
);
@@ -41,6 +41,15 @@ bool _deepEquals(Object? a, Object? b) {
return a == b;
}
enum UniversalBleLogLevel {
none,
error,
warning,
info,
debug,
verbose,
}
/// Unified error codes for all platforms
enum UniversalBleErrorCode {
unknownError,
@@ -478,30 +487,33 @@ class _PigeonCodec extends StandardMessageCodec {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else if (value is UniversalBleErrorCode) {
} else if (value is UniversalBleLogLevel) {
buffer.putUint8(129);
writeValue(buffer, value.index);
} else if (value is UniversalBleScanResult) {
} else if (value is UniversalBleErrorCode) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else if (value is UniversalBleService) {
writeValue(buffer, value.index);
} else if (value is UniversalBleScanResult) {
buffer.putUint8(131);
writeValue(buffer, value.encode());
} else if (value is UniversalBleCharacteristic) {
} else if (value is UniversalBleService) {
buffer.putUint8(132);
writeValue(buffer, value.encode());
} else if (value is UniversalBleDescriptor) {
} else if (value is UniversalBleCharacteristic) {
buffer.putUint8(133);
writeValue(buffer, value.encode());
} else if (value is UniversalScanFilter) {
} else if (value is UniversalBleDescriptor) {
buffer.putUint8(134);
writeValue(buffer, value.encode());
} else if (value is UniversalManufacturerDataFilter) {
} else if (value is UniversalScanFilter) {
buffer.putUint8(135);
writeValue(buffer, value.encode());
} else if (value is UniversalManufacturerData) {
} else if (value is UniversalManufacturerDataFilter) {
buffer.putUint8(136);
writeValue(buffer, value.encode());
} else if (value is UniversalManufacturerData) {
buffer.putUint8(137);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@@ -512,20 +524,23 @@ class _PigeonCodec extends StandardMessageCodec {
switch (type) {
case 129:
final value = readValue(buffer) as int?;
return value == null ? null : UniversalBleErrorCode.values[value];
return value == null ? null : UniversalBleLogLevel.values[value];
case 130:
return UniversalBleScanResult.decode(readValue(buffer)!);
final value = readValue(buffer) as int?;
return value == null ? null : UniversalBleErrorCode.values[value];
case 131:
return UniversalBleService.decode(readValue(buffer)!);
return UniversalBleScanResult.decode(readValue(buffer)!);
case 132:
return UniversalBleCharacteristic.decode(readValue(buffer)!);
return UniversalBleService.decode(readValue(buffer)!);
case 133:
return UniversalBleDescriptor.decode(readValue(buffer)!);
return UniversalBleCharacteristic.decode(readValue(buffer)!);
case 134:
return UniversalScanFilter.decode(readValue(buffer)!);
return UniversalBleDescriptor.decode(readValue(buffer)!);
case 135:
return UniversalManufacturerDataFilter.decode(readValue(buffer)!);
return UniversalScanFilter.decode(readValue(buffer)!);
case 136:
return UniversalManufacturerDataFilter.decode(readValue(buffer)!);
case 137:
return UniversalManufacturerData.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
@@ -1061,6 +1076,30 @@ class UniversalBlePlatformChannel {
return (pigeonVar_replyList[0] as int?)!;
}
}
Future<void> setLogLevel(UniversalBleLogLevel logLevel) async {
final pigeonVar_channelName =
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[logLevel]);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
}
/// Native -> Flutter
@@ -1073,8 +1112,8 @@ abstract class UniversalBleCallbackChannel {
void onScanResult(UniversalBleScanResult result);
void onValueChanged(
String deviceId, String characteristicId, Uint8List value);
void onValueChanged(String deviceId, String characteristicId, Uint8List value,
int? timestamp);
void onConnectionChanged(String deviceId, bool connected, String? error);
@@ -1192,9 +1231,10 @@ abstract class UniversalBleCallbackChannel {
final Uint8List? arg_value = (args[2] as Uint8List?);
assert(arg_value != null,
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged was null, expected non-null Uint8List.');
final int? arg_timestamp = (args[3] as int?);
try {
api.onValueChanged(
arg_deviceId!, arg_characteristicId!, arg_value!);
api.onValueChanged(arg_deviceId!, arg_characteristicId!, arg_value!,
arg_timestamp);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
@@ -164,6 +164,10 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
);
}
@override
Future<void> setLogLevel(BleLogLevel logLevel) => _executeWithErrorHandling(
() => _channel.setLogLevel(logLevel.toUniversalBleLogLevel()));
/// To set listeners
void _setupListeners() {
UniversalBleCallbackChannel.setUp(_UniversalBleCallbackHandler(
@@ -253,8 +257,12 @@ class _UniversalBleCallbackHandler extends UniversalBleCallbackChannel {
@override
void onValueChanged(
String deviceId, String characteristicId, Uint8List value) =>
valueChanged(deviceId, characteristicId, value);
String deviceId,
String characteristicId,
Uint8List value,
int? timestamp,
) =>
valueChanged(deviceId, characteristicId, value, timestamp);
@override
void onPairStateChange(String deviceId, bool isPaired, String? error) =>
@@ -298,3 +306,14 @@ extension _ScanFilterExtension on ScanFilter? {
);
}
}
extension _BleLogLevelExtension on BleLogLevel {
UniversalBleLogLevel toUniversalBleLogLevel() => switch (this) {
BleLogLevel.none => UniversalBleLogLevel.none,
BleLogLevel.error => UniversalBleLogLevel.error,
BleLogLevel.warning => UniversalBleLogLevel.warning,
BleLogLevel.info => UniversalBleLogLevel.info,
BleLogLevel.debug => UniversalBleLogLevel.debug,
BleLogLevel.verbose => UniversalBleLogLevel.verbose,
};
}
+15 -3
View File
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:universal_ble/src/utils/cache_handler.dart';
import 'package:universal_ble/src/utils/universal_ble_stream_controller.dart';
import 'package:universal_ble/src/utils/universal_logger.dart';
import 'package:universal_ble/universal_ble.dart';
abstract class UniversalBlePlatform {
@@ -85,6 +86,9 @@ abstract class UniversalBlePlatform {
List<String>? withServices,
);
Future<void> setLogLevel(BleLogLevel logLevel) async =>
UniversalLogger.setLogLevel(logLevel);
bool receivesAdvertisements(String deviceId) => true;
/// Streams
@@ -142,6 +146,7 @@ abstract class UniversalBlePlatform {
String deviceId,
String characteristicId,
Uint8List value,
int? timestamp,
) {
characteristicId = BleUuidParser.string(characteristicId);
_valueStreamController.add((
@@ -150,7 +155,7 @@ abstract class UniversalBlePlatform {
value: value,
));
try {
onValueChange?.call(deviceId, characteristicId, value);
onValueChange?.call(deviceId, characteristicId, value, timestamp);
} catch (_) {}
}
@@ -176,10 +181,17 @@ abstract class UniversalBlePlatform {
// Callback types
typedef OnConnectionChange = void Function(
String deviceId, bool isConnected, String? error);
String deviceId,
bool isConnected,
String? error,
);
typedef OnValueChange = void Function(
String deviceId, String characteristicId, Uint8List value);
String deviceId,
String characteristicId,
Uint8List value,
int? timestamp,
);
typedef OnScanResult = void Function(BleDevice scanResult);
@@ -162,6 +162,10 @@ class UniversalBleWeb extends UniversalBlePlatform {
String characteristic,
BleInputProperty bleInputProperty,
) async {
UniversalLogger.logDebug(
"SET_NOTIFY -> $deviceId $service $characteristic input=${bleInputProperty.name}",
withTimestamp: true,
);
final bleCharacteristic = await _getBleCharacteristic(
deviceId: deviceId,
serviceId: service,
@@ -185,10 +189,20 @@ class UniversalBleWeb extends UniversalBlePlatform {
await bleCharacteristic.startNotifications();
_characteristicStreamList[characteristicKey] =
bleCharacteristic.value.listen((ByteData event) {
final preview = event.buffer
.asUint8List()
.take(8)
.map((e) => e.toRadixString(16).padLeft(2, '0'))
.join();
UniversalLogger.logVerbose(
"NOTIFY <- $deviceId $characteristic len=${event.lengthInBytes} data=$preview",
withTimestamp: true,
);
updateCharacteristicValue(
deviceId,
characteristic,
event.buffer.asUint8List(),
DateTime.now().millisecondsSinceEpoch,
);
});
} else {
@@ -205,6 +219,10 @@ class UniversalBleWeb extends UniversalBlePlatform {
Uint8List value,
BleOutputProperty bleOutputProperty,
) async {
UniversalLogger.logDebug(
"WRITE -> $deviceId $service $characteristic len=${value.length} property=${bleOutputProperty.name}",
withTimestamp: true,
);
final bleCharacteristic = await _getBleCharacteristic(
deviceId: deviceId,
serviceId: service,
@@ -234,6 +252,10 @@ class UniversalBleWeb extends UniversalBlePlatform {
String characteristic, {
final Duration? timeout,
}) async {
UniversalLogger.logDebug(
"READ -> $deviceId $service $characteristic",
withTimestamp: true,
);
var bleCharacteristic = await _getBleCharacteristic(
deviceId: deviceId,
serviceId: service,
+63 -7
View File
@@ -1,24 +1,80 @@
import 'dart:developer';
import 'package:universal_ble/src/models/ble_log_level.dart';
class UniversalLogger {
static void logInfo(String message) {
static BleLogLevel _currentLogLevel = BleLogLevel.none;
static BleLogLevel get currentLogLevel => _currentLogLevel;
static void setLogLevel(BleLogLevel logLevel) {
_currentLogLevel = logLevel;
}
static void logError(
String message, {
bool withTimestamp = false,
}) {
if (!_allows(BleLogLevel.error)) return;
if (withTimestamp) {
final ts = DateTime.now().toIso8601String();
message = "[$ts] $message";
}
log(
'\x1B[31m$message\x1B[0m',
name: 'UniversalBle:ERROR',
);
}
static void logWarning(String message, {bool withTimestamp = false}) {
if (!_allows(BleLogLevel.warning)) return;
if (withTimestamp) {
final ts = DateTime.now().toIso8601String();
message = "[$ts] $message";
}
log(
'\x1B[33m$message\x1B[0m',
name: 'UniversalBle:WARN',
);
}
static void logInfo(String message, {bool withTimestamp = false}) {
if (!_allows(BleLogLevel.info)) return;
if (withTimestamp) {
final ts = DateTime.now().toIso8601String();
message = "[$ts] $message";
}
log(
message.toString(),
name: 'UniversalBle:INFO',
);
}
static void logError(String message) {
static void logDebug(String message, {bool withTimestamp = false}) {
if (!_allows(BleLogLevel.debug)) return;
if (withTimestamp) {
final ts = DateTime.now().toIso8601String();
message = "[$ts] $message";
}
log(
'\x1B[31m$message\x1B[31m',
name: 'UniversalBle:ERROR',
message.toString(),
name: 'UniversalBle:DEBUG',
);
}
static void logWarning(String message) {
static void logVerbose(String message, {bool withTimestamp = false}) {
if (!_allows(BleLogLevel.verbose)) return;
if (withTimestamp) {
final ts = DateTime.now().toIso8601String();
message = "[$ts] $message";
}
log(
'\x1B[33m$message\x1B[33m',
name: 'UniversalBle:WARN',
message.toString(),
name: 'UniversalBle:VERBOSE',
);
}
static bool _allows(BleLogLevel level) {
return level.index <= _currentLogLevel.index &&
_currentLogLevel != BleLogLevel.none;
}
}
+12
View File
@@ -90,6 +90,8 @@ abstract class UniversalBlePlatformChannel {
);
int getConnectionState(String deviceId);
void setLogLevel(UniversalBleLogLevel logLevel);
}
/// Native -> Flutter
@@ -105,6 +107,7 @@ abstract class UniversalBleCallbackChannel {
String deviceId,
String characteristicId,
Uint8List value,
int? timestamp,
);
void onConnectionChanged(
@@ -134,6 +137,15 @@ class UniversalBleScanResult {
});
}
enum UniversalBleLogLevel {
none,
error,
warning,
info,
debug,
verbose,
}
class UniversalBleService {
String uuid;
List<UniversalBleCharacteristic>? characteristics;
+1
View File
@@ -106,6 +106,7 @@ class _UniversalBleMock extends UniversalBlePlatformMock {
deviceId,
characteristic,
Uint8List.fromList([1, 2, 3]),
DateTime.now().millisecondsSinceEpoch,
);
});
}
+2
View File
@@ -46,6 +46,8 @@ list(APPEND PLUGIN_SOURCES
"src/universal_ble_filter_util.h"
"src/universal_ble_thread_safe.h"
"src/enum_parser.h"
"src/helper/universal_ble_logger.cpp"
"src/helper/universal_ble_logger.h"
)
add_library(${PLUGIN_NAME} SHARED
+55 -15
View File
@@ -487,27 +487,32 @@ EncodableValue PigeonInternalCodecSerializer::ReadValueOfType(
case 129: {
const auto& encodable_enum_arg = ReadValue(stream);
const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue();
return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast<UniversalBleErrorCode>(enum_arg_value));
return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast<UniversalBleLogLevel>(enum_arg_value));
}
case 130: {
return CustomEncodableValue(UniversalBleScanResult::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
const auto& encodable_enum_arg = ReadValue(stream);
const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue();
return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast<UniversalBleErrorCode>(enum_arg_value));
}
case 131: {
return CustomEncodableValue(UniversalBleService::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalBleScanResult::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 132: {
return CustomEncodableValue(UniversalBleCharacteristic::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalBleService::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 133: {
return CustomEncodableValue(UniversalBleDescriptor::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalBleCharacteristic::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 134: {
return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalBleDescriptor::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 135: {
return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 136: {
return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 137: {
return CustomEncodableValue(UniversalManufacturerData::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
default:
@@ -519,43 +524,48 @@ void PigeonInternalCodecSerializer::WriteValue(
const EncodableValue& value,
flutter::ByteStreamWriter* stream) const {
if (const CustomEncodableValue* custom_value = std::get_if<CustomEncodableValue>(&value)) {
if (custom_value->type() == typeid(UniversalBleErrorCode)) {
if (custom_value->type() == typeid(UniversalBleLogLevel)) {
stream->WriteByte(129);
WriteValue(EncodableValue(static_cast<int>(std::any_cast<UniversalBleLogLevel>(*custom_value))), stream);
return;
}
if (custom_value->type() == typeid(UniversalBleErrorCode)) {
stream->WriteByte(130);
WriteValue(EncodableValue(static_cast<int>(std::any_cast<UniversalBleErrorCode>(*custom_value))), stream);
return;
}
if (custom_value->type() == typeid(UniversalBleScanResult)) {
stream->WriteByte(130);
stream->WriteByte(131);
WriteValue(EncodableValue(std::any_cast<UniversalBleScanResult>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalBleService)) {
stream->WriteByte(131);
stream->WriteByte(132);
WriteValue(EncodableValue(std::any_cast<UniversalBleService>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalBleCharacteristic)) {
stream->WriteByte(132);
stream->WriteByte(133);
WriteValue(EncodableValue(std::any_cast<UniversalBleCharacteristic>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalBleDescriptor)) {
stream->WriteByte(133);
stream->WriteByte(134);
WriteValue(EncodableValue(std::any_cast<UniversalBleDescriptor>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalScanFilter)) {
stream->WriteByte(134);
stream->WriteByte(135);
WriteValue(EncodableValue(std::any_cast<UniversalScanFilter>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalManufacturerDataFilter)) {
stream->WriteByte(135);
stream->WriteByte(136);
WriteValue(EncodableValue(std::any_cast<UniversalManufacturerDataFilter>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalManufacturerData)) {
stream->WriteByte(136);
stream->WriteByte(137);
WriteValue(EncodableValue(std::any_cast<UniversalManufacturerData>(*custom_value).ToEncodableList()), stream);
return;
}
@@ -1151,6 +1161,34 @@ void UniversalBlePlatformChannel::SetUp(
channel.SetMessageHandler(nullptr);
}
}
{
BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel" + prepended_suffix, &GetCodec());
if (api != nullptr) {
channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply<EncodableValue>& reply) {
try {
const auto& args = std::get<EncodableList>(message);
const auto& encodable_log_level_arg = args.at(0);
if (encodable_log_level_arg.IsNull()) {
reply(WrapError("log_level_arg unexpectedly null."));
return;
}
const auto& log_level_arg = std::any_cast<const UniversalBleLogLevel&>(std::get<CustomEncodableValue>(encodable_log_level_arg));
std::optional<FlutterError> output = api->SetLogLevel(log_level_arg);
if (output.has_value()) {
reply(WrapError(output.value()));
return;
}
EncodableList wrapped;
wrapped.push_back(EncodableValue());
reply(EncodableValue(std::move(wrapped)));
} catch (const std::exception& exception) {
reply(WrapError(exception.what()));
}
});
} else {
channel.SetMessageHandler(nullptr);
}
}
}
EncodableValue UniversalBlePlatformChannel::WrapError(std::string_view error_message) {
@@ -1267,6 +1305,7 @@ void UniversalBleCallbackChannel::OnValueChanged(
const std::string& device_id_arg,
const std::string& characteristic_id_arg,
const std::vector<uint8_t>& value_arg,
const int64_t* timestamp_arg,
std::function<void(void)>&& on_success,
std::function<void(const FlutterError&)>&& on_error) {
const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged" + message_channel_suffix_;
@@ -1275,6 +1314,7 @@ void UniversalBleCallbackChannel::OnValueChanged(
EncodableValue(device_id_arg),
EncodableValue(characteristic_id_arg),
EncodableValue(value_arg),
timestamp_arg ? EncodableValue(*timestamp_arg) : EncodableValue(),
});
channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) {
std::unique_ptr<EncodableValue> response = GetCodec().DecodeMessage(reply, reply_size);
+11
View File
@@ -57,6 +57,15 @@ template<class T> class ErrorOr {
};
enum class UniversalBleLogLevel {
kNone = 0,
kError = 1,
kWarning = 2,
kInfo = 3,
kDebug = 4,
kVerbose = 5
};
// Unified error codes for all platforms
enum class UniversalBleErrorCode {
kUnknownError = 0,
@@ -425,6 +434,7 @@ class UniversalBlePlatformChannel {
const flutter::EncodableList& with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) = 0;
virtual ErrorOr<int64_t> GetConnectionState(const std::string& device_id) = 0;
virtual std::optional<FlutterError> SetLogLevel(const UniversalBleLogLevel& log_level) = 0;
// The codec used by UniversalBlePlatformChannel.
static const flutter::StandardMessageCodec& GetCodec();
@@ -469,6 +479,7 @@ class UniversalBleCallbackChannel {
const std::string& device_id,
const std::string& characteristic_id,
const std::vector<uint8_t>& value,
const int64_t* timestamp,
std::function<void(void)>&& on_success,
std::function<void(const FlutterError&)>&& on_error);
void OnConnectionChanged(
@@ -0,0 +1,84 @@
#include "universal_ble_logger.h"
#include <chrono>
#include <iomanip>
#include <sstream>
namespace universal_ble {
UniversalBleLogLevel UniversalBleLogger::current_level_ =
UniversalBleLogLevel::kNone;
static std::string GetCurrentTimestampString() {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) %
1000;
std::tm timeinfo;
localtime_s(&timeinfo, &time_t);
std::ostringstream oss;
oss << std::put_time(&timeinfo, "%H:%M:%S");
oss << "." << std::setfill('0') << std::setw(3) << ms.count();
return "[" + oss.str() + "]";
}
void UniversalBleLogger::SetLogLevel(UniversalBleLogLevel level) {
current_level_ = level;
}
UniversalBleLogLevel UniversalBleLogger::current_log_level() {
return current_level_;
}
void UniversalBleLogger::LogError(const std::string &message) {
if (!Allows(UniversalBleLogLevel::kError))
return;
std::cout << "UniversalBle:ERROR " << message << std::endl;
}
void UniversalBleLogger::LogWarning(const std::string &message) {
if (!Allows(UniversalBleLogLevel::kWarning))
return;
std::cout << "UniversalBle:WARN " << message << std::endl;
}
void UniversalBleLogger::LogInfo(const std::string &message) {
if (!Allows(UniversalBleLogLevel::kInfo))
return;
std::cout << "UniversalBle:INFO " << message << std::endl;
}
void UniversalBleLogger::LogDebug(const std::string &message) {
if (!Allows(UniversalBleLogLevel::kDebug))
return;
std::cout << "UniversalBle:DEBUG " << message << std::endl;
}
void UniversalBleLogger::LogVerbose(const std::string &message) {
if (!Allows(UniversalBleLogLevel::kVerbose))
return;
std::cout << "UniversalBle:VERBOSE " << message << std::endl;
}
void UniversalBleLogger::LogDebugWithTimestamp(const std::string &message) {
if (!Allows(UniversalBleLogLevel::kDebug))
return;
std::cout << "UniversalBle:DEBUG " << GetCurrentTimestampString() << " " << message
<< std::endl;
}
void UniversalBleLogger::LogVerboseWithTimestamp(const std::string &message) {
if (!Allows(UniversalBleLogLevel::kVerbose))
return;
std::cout << "UniversalBle:VERBOSE " << GetCurrentTimestampString() << " "
<< message << std::endl;
}
bool UniversalBleLogger::Allows(UniversalBleLogLevel level) {
return current_level_ != UniversalBleLogLevel::kNone &&
static_cast<int>(level) <= static_cast<int>(current_level_);
}
} // namespace universal_ble
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <iostream>
#include <string>
#include "../generated/universal_ble.g.h"
namespace universal_ble {
class UniversalBleLogger {
public:
static void SetLogLevel(UniversalBleLogLevel level);
static UniversalBleLogLevel current_log_level();
static void LogError(const std::string &message);
static void LogWarning(const std::string &message);
static void LogInfo(const std::string &message);
static void LogDebug(const std::string &message);
static void LogVerbose(const std::string &message);
static void LogDebugWithTimestamp(const std::string &message);
static void LogVerboseWithTimestamp(const std::string &message);
private:
static UniversalBleLogLevel current_level_;
static bool Allows(UniversalBleLogLevel level);
};
} // namespace universal_ble
+102 -54
View File
@@ -14,6 +14,7 @@
#include "enum_parser.h"
#include "generated/universal_ble.g.h"
#include "helper/universal_ble_logger.h"
#include "helper/universal_enum.h"
#include "helper/utils.h"
#include "pin_entry.h"
@@ -165,7 +166,7 @@ UniversalBlePlugin::StartScan(const UniversalScanFilter *filter) {
filter->with_name_prefix().size() > 0;
if (uses_custom_filters) {
std::cout << "Using Custom Scan Filter" << std::endl;
UniversalBleLogger::LogInfo("Using Custom Scan Filter");
setScanFilter(*filter);
} else {
// Apply Services filter
@@ -186,7 +187,7 @@ UniversalBlePlugin::StartScan(const UniversalScanFilter *filter) {
bluetooth_le_watcher_.Start();
return std::nullopt;
} catch (...) {
std::cout << "Unknown error StartScan" << std::endl;
UniversalBleLogger::LogError("Unknown error StartScan");
return create_flutter_error(UniversalBleErrorCode::kUnknownError,
"Unknown error");
}
@@ -205,8 +206,8 @@ std::optional<FlutterError> UniversalBlePlugin::StopScan() {
return std::nullopt;
} catch (const hresult_error &err) {
const int error_code = err.code();
std::cout << "StopScanLog: " << to_string(err.message())
<< " ErrorCode: " << std::to_string(error_code) << std::endl;
UniversalBleLogger::LogError("StopScanLog: " + to_string(err.message()) +
" ErrorCode: " + std::to_string(error_code));
return create_flutter_error(UniversalBleErrorCode::kFailed,
to_string(err.message()),
std::to_string(error_code));
@@ -248,6 +249,12 @@ UniversalBlePlugin::GetConnectionState(const std::string &device_id) {
}
}
std::optional<FlutterError>
UniversalBlePlugin::SetLogLevel(const UniversalBleLogLevel &log_level) {
UniversalBleLogger::SetLogLevel(log_level);
return std::nullopt;
}
std::optional<FlutterError>
UniversalBlePlugin::Connect(const std::string &device_id) {
ConnectAsync(str_to_mac_address(device_id));
@@ -288,6 +295,8 @@ void UniversalBlePlugin::ReadValue(
const std::string &device_id, const std::string &service,
const std::string &characteristic,
std::function<void(ErrorOr<std::vector<uint8_t>> reply)> result) {
UniversalBleLogger::LogDebugWithTimestamp("READ -> " + device_id + " " +
service + " " + characteristic);
try {
const auto it = connected_devices_.find(str_to_mac_address(device_id));
if (it == connected_devices_.end()) {
@@ -317,6 +326,10 @@ void UniversalBlePlugin::ReadValue(
const auto read_value_result = sender.GetResults();
const auto status = read_value_result.Status();
if (status != GattCommunicationStatus::Success) {
UniversalBleLogger::LogError(
"READ_FAILED <- " + device_id + " " + service + " " +
characteristic +
" status=" + std::to_string(static_cast<int>(status)));
result(create_flutter_error_from_gatt_communication_status(status));
} else {
result(to_bytevc(read_value_result.Value()));
@@ -325,7 +338,7 @@ void UniversalBlePlugin::ReadValue(
} catch (const FlutterError &err) {
return result(err);
} catch (...) {
std::cout << "ReadValueLog: Unknown error" << std::endl;
UniversalBleLogger::LogError("ReadValueLog: Unknown error");
return result(create_flutter_unknown_error());
}
}
@@ -335,6 +348,10 @@ void UniversalBlePlugin::WriteValue(
const std::string &characteristic, const std::vector<uint8_t> &value,
int64_t ble_output_property,
std::function<void(std::optional<FlutterError> reply)> result) {
UniversalBleLogger::LogDebugWithTimestamp(
"WRITE -> " + device_id + " " + service + " " + characteristic +
" len=" + std::to_string(value.size()) +
" property=" + std::to_string(ble_output_property));
try {
const auto it = connected_devices_.find(str_to_mac_address(device_id));
if (it == connected_devices_.end()) {
@@ -383,6 +400,10 @@ void UniversalBlePlugin::WriteValue(
const auto status = sender.GetResults();
if (status != GattCommunicationStatus::Success) {
UniversalBleLogger::LogError(
"WRITE_FAILED <- " + device_id + " " + service + " " +
characteristic +
" status=" + std::to_string(static_cast<int>(status)));
result(create_flutter_error_from_gatt_communication_status(status));
} else {
result(std::nullopt);
@@ -391,7 +412,7 @@ void UniversalBlePlugin::WriteValue(
} catch (const FlutterError &err) {
result(err);
} catch (...) {
std::cout << "WriteValue: Unknown error" << std::endl;
UniversalBleLogger::LogError("WriteValue: Unknown error");
result(create_flutter_unknown_error());
}
}
@@ -399,6 +420,9 @@ void UniversalBlePlugin::WriteValue(
void UniversalBlePlugin::RequestMtu(
const std::string &device_id, int64_t expected_mtu,
std::function<void(ErrorOr<int64_t> reply)> result) {
UniversalBleLogger::LogDebugWithTimestamp(
"REQUEST_MTU -> " + device_id +
" expected=" + std::to_string(expected_mtu));
try {
const auto it = connected_devices_.find(str_to_mac_address(device_id));
if (it == connected_devices_.end()) {
@@ -495,7 +519,7 @@ fire_and_forget UniversalBlePlugin::InitializeAsync() {
}
}
if (!bluetooth_radio_) {
std::cout << "Bluetooth is not available" << std::endl;
UniversalBleLogger::LogError("Bluetooth is not available");
ui_thread_handler_.Post([] {
callback_channel->OnAvailabilityChanged(
static_cast<int>(AvailabilityState::unsupported), SuccessCallback,
@@ -509,7 +533,7 @@ fire_and_forget UniversalBlePlugin::PairAsync(
const std::string &device_id,
const std::function<void(ErrorOr<bool> reply)> result) {
try {
std::cout << "Trying to pair" << std::endl;
UniversalBleLogger::LogInfo("Trying to pair");
const auto device = co_await BluetoothLEDevice::FromBluetoothAddressAsync(
str_to_mac_address(device_id));
@@ -519,7 +543,7 @@ fire_and_forget UniversalBlePlugin::PairAsync(
co_return;
}
std::cout << "Got device" << std::endl;
UniversalBleLogger::LogInfo("Got device");
const auto device_information = device.DeviceInformation();
if (device_information.Pairing().IsPaired())
@@ -530,25 +554,31 @@ fire_and_forget UniversalBlePlugin::PairAsync(
else {
const auto pair_result =
co_await device_information.Pairing().PairAsync();
std::cout << "PairLog: Received pairing status" << std::endl;
UniversalBleLogger::LogInfo("PairLog: Received pairing status");
bool is_paired =
pair_result.Status() == DevicePairingResultStatus::Paired;
result(is_paired);
const std::string *error_msg = nullptr;
const auto error_str =
device_pairing_result_to_string(pair_result.Status());
std::optional<std::string> captured_error;
if (error_str.has_value()) {
error_msg = &error_str.value();
captured_error = error_str.value();
}
ui_thread_handler_.Post([device_id, is_paired, error_msg] {
ui_thread_handler_.Post([device_id, is_paired, captured_error] {
const std::string *error_msg = nullptr;
std::string error_string;
if (captured_error.has_value()) {
error_string = captured_error.value();
error_msg = &error_string;
}
callback_channel->OnPairStateChange(device_id, is_paired, error_msg,
SuccessCallback, ErrorCallback);
});
}
} catch (...) {
result(false);
std::cout << "PairLog: Unknown error" << std::endl;
UniversalBleLogger::LogError("PairLog: Unknown error");
}
}
@@ -573,7 +603,7 @@ fire_and_forget UniversalBlePlugin::CustomPairAsync(
const auto custom_pairing = device_information.Pairing().Custom();
const event_token token = custom_pairing.PairingRequested(
{this, &UniversalBlePlugin::PairingRequestedHandler});
std::cout << "PairLog: Trying to pair" << std::endl;
UniversalBleLogger::LogInfo("PairLog: Trying to pair");
const DevicePairingProtectionLevel protection_level =
device_information.Pairing().ProtectionLevel();
// DevicePairingKinds => None, ConfirmOnly, DisplayPin, ProvidePin,
@@ -581,25 +611,31 @@ fire_and_forget UniversalBlePlugin::CustomPairAsync(
const auto pair_result = co_await custom_pairing.PairAsync(
DevicePairingKinds::ConfirmOnly | DevicePairingKinds::ProvidePin,
protection_level);
std::cout << "PairLog: Got Pair Result" << std::endl;
UniversalBleLogger::LogInfo("PairLog: Got Pair Result");
const DevicePairingResultStatus status = pair_result.Status();
custom_pairing.PairingRequested(token);
bool is_paired = status == DevicePairingResultStatus::Paired;
result(is_paired);
const std::string *error_msg = nullptr;
const auto error_str = device_pairing_result_to_string(status);
std::optional<std::string> captured_error;
if (error_str.has_value()) {
error_msg = &error_str.value();
captured_error = error_str.value();
}
ui_thread_handler_.Post([device_id, is_paired, error_msg] {
ui_thread_handler_.Post([device_id, is_paired, captured_error] {
const std::string *error_msg = nullptr;
std::string error_string;
if (captured_error.has_value()) {
error_string = captured_error.value();
error_msg = &error_string;
}
callback_channel->OnPairStateChange(device_id, is_paired, error_msg,
SuccessCallback, ErrorCallback);
});
}
} catch (...) {
result(false);
std::cout << "PairLog Error: Pairing Failed" << std::endl;
UniversalBleLogger::LogError("PairLog Error: Pairing Failed");
}
}
@@ -607,16 +643,16 @@ fire_and_forget UniversalBlePlugin::CustomPairAsync(
void UniversalBlePlugin::PairingRequestedHandler(
DeviceInformationCustomPairing sender,
const DevicePairingRequestedEventArgs &event_args) {
std::cout << "PairLog: Got PairingRequest" << std::endl;
UniversalBleLogger::LogInfo("PairLog: Got PairingRequest");
const DevicePairingKinds kind = event_args.PairingKind();
if (kind != DevicePairingKinds::ProvidePin) {
event_args.Accept();
return;
}
std::cout << "PairLog: Trying to get pin from user" << std::endl;
UniversalBleLogger::LogInfo("PairLog: Trying to get pin from user");
const hstring pin = askForPairingPin();
std::wcout << "PairLog: Got Pin: " << pin.c_str() << std::endl;
UniversalBleLogger::LogInfo("PairLog: Got Pin: " + to_string(pin));
event_args.Accept(pin);
}
@@ -678,10 +714,7 @@ void UniversalBlePlugin::PushUniversalScanResult(
// Filter final result before sending to Flutter
if (is_connectable && filterDevice(scan_result)) {
scan_result.set_timestamp(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count());
scan_result.set_timestamp(GetCurrentTimestampMillis());
ui_thread_handler_.Post([scan_result] {
callback_channel->OnScanResult(scan_result, SuccessCallback,
ErrorCallback);
@@ -762,7 +795,7 @@ void UniversalBlePlugin::SetupDeviceWatcher() {
device_watcher_enumeration_completed_token_ =
device_watcher_.EnumerationCompleted([this](DeviceWatcher sender,
IInspectable args) {
std::cout << "DeviceWatcherEvent: EnumerationCompleted" << std::endl;
UniversalBleLogger::LogInfo("DeviceWatcherEvent: EnumerationCompleted");
DisposeDeviceWatcher();
// EnumerationCompleted
});
@@ -913,7 +946,7 @@ void UniversalBlePlugin::BluetoothLeWatcherReceived(
// Filter Device
PushUniversalScanResult(universal_scan_result, args.IsConnectable());
} catch (...) {
std::cout << "ScanResultErrorInParsing" << std::endl;
UniversalBleLogger::LogError("ScanResultErrorInParsing");
}
}
@@ -936,8 +969,8 @@ fire_and_forget UniversalBlePlugin::ConnectAsync(uint64_t bluetooth_address) {
BluetoothLEDevice device =
co_await BluetoothLEDevice::FromBluetoothAddressAsync(bluetooth_address);
if (!device) {
std::cout << "ConnectionLog: ConnectionFailed: Failed to get device"
<< std::endl;
UniversalBleLogger::LogError(
"ConnectionLog: ConnectionFailed: Failed to get device");
ui_thread_handler_.Post([bluetooth_address] {
callback_channel->OnConnectionChanged(
mac_address_to_str(bluetooth_address), false,
@@ -947,14 +980,14 @@ fire_and_forget UniversalBlePlugin::ConnectAsync(uint64_t bluetooth_address) {
co_return;
}
std::cout << "ConnectionLog: Device found" << std::endl;
UniversalBleLogger::LogInfo("ConnectionLog: Device found");
auto services_result =
co_await device.GetGattServicesAsync((BluetoothCacheMode::Uncached));
auto services_result_error =
gatt_communication_status_to_error(services_result.Status());
if (services_result_error.has_value()) {
std::cout << "ConnectionFailed: Failed to get services: "
<< services_result_error.value() << std::endl;
UniversalBleLogger::LogError("ConnectionFailed: Failed to get services: " +
services_result_error.value());
ui_thread_handler_.Post([bluetooth_address, services_result_error] {
callback_channel->OnConnectionChanged(
mac_address_to_str(bluetooth_address), false,
@@ -963,7 +996,7 @@ fire_and_forget UniversalBlePlugin::ConnectAsync(uint64_t bluetooth_address) {
co_return;
}
std::cout << "ConnectionLog: Services discovered" << std::endl;
UniversalBleLogger::LogInfo("ConnectionLog: Services discovered");
std::unordered_map<std::string, GattServiceObject> gatt_map;
auto gatt_services = services_result.Services();
for (GattDeviceService &&service : gatt_services) {
@@ -976,9 +1009,9 @@ fire_and_forget UniversalBlePlugin::ConnectAsync(uint64_t bluetooth_address) {
gatt_communication_status_to_error(characteristics_result.Status());
if (characteristics_result_error.has_value()) {
std::cout << "Failed to get characteristics for service: " << service_uuid
<< ", With Status: " << characteristics_result_error.value()
<< std::endl;
UniversalBleLogger::LogError(
"Failed to get characteristics for service: " + service_uuid +
", With Status: " + characteristics_result_error.value());
continue;
// PostConnectionUpdate(bluetoothAddress, ConnectionState::disconnected);
// co_return;
@@ -1001,7 +1034,7 @@ fire_and_forget UniversalBlePlugin::ConnectAsync(uint64_t bluetooth_address) {
device, connection_status_changed_token, gatt_map);
auto pair = std::make_pair(bluetooth_address, std::move(device_agent));
connected_devices_.insert(std::move(pair));
std::cout << "ConnectionLog: Connected" << std::endl;
UniversalBleLogger::LogInfo("ConnectionLog: Connected");
ui_thread_handler_.Post([bluetooth_address] {
callback_channel->OnConnectionChanged(mac_address_to_str(bluetooth_address),
true, nullptr, SuccessCallback,
@@ -1089,13 +1122,14 @@ fire_and_forget UniversalBlePlugin::GetSystemDevicesAsync(
result(results);
} catch (const hresult_error &err) {
int error_code = err.code();
std::cout << "GetConnectedDeviceLog: " << to_string(err.message())
<< " ErrorCode: " << std::to_string(error_code) << std::endl;
UniversalBleLogger::LogError(
"GetConnectedDeviceLog: " + to_string(err.message()) +
" ErrorCode: " + std::to_string(error_code));
result(create_flutter_error(UniversalBleErrorCode::kFailed,
to_string(err.message()),
std::to_string(error_code)));
} catch (...) {
std::cout << "Unknown error GetSystemDevicesAsyncAsync" << std::endl;
UniversalBleLogger::LogError("Unknown error GetSystemDevicesAsyncAsync");
result(create_flutter_error(UniversalBleErrorCode::kUnknownError,
"Unknown error"));
}
@@ -1136,9 +1170,8 @@ fire_and_forget UniversalBlePlugin::DiscoverServicesAsync(
}
}
} catch (...) {
std::cout << "DiscoverServicesAsync: failed to get descriptors for "
"characteristic: "
<< std::endl;
UniversalBleLogger::LogError("DiscoverServicesAsync: failed to get "
"descriptors for characteristic");
}
}
universal_characteristics.push_back(
@@ -1180,7 +1213,7 @@ fire_and_forget UniversalBlePlugin::IsPairedAsync(
const bool is_paired = device.DeviceInformation().Pairing().IsPaired();
result(is_paired);
} catch (...) {
std::cout << "IsPairedAsync: Error " << std::endl;
UniversalBleLogger::LogError("IsPairedAsync: Error");
result(create_flutter_error(UniversalBleErrorCode::kUnknownError,
"Unknown error"));
}
@@ -1190,6 +1223,9 @@ fire_and_forget UniversalBlePlugin::SetNotifiableAsync(
const std::string &device_id, const std::string &service,
const std::string &characteristic, const int64_t ble_input_property,
const std::function<void(std::optional<FlutterError> reply)> result) {
UniversalBleLogger::LogDebugWithTimestamp(
"SET_NOTIFY -> " + device_id + " " + service + " " + characteristic +
" input=" + std::to_string(ble_input_property));
try {
const auto it = connected_devices_.find(str_to_mac_address(device_id));
if (it == connected_devices_.end()) {
@@ -1236,6 +1272,9 @@ fire_and_forget UniversalBlePlugin::SetNotifiableAsync(
.WriteClientCharacteristicConfigurationDescriptorAsync(
descriptor_value);
if (status != GattCommunicationStatus::Success) {
UniversalBleLogger::LogError("SET_NOTIFY_FAILED <- " + device_id + " " +
service + " " + characteristic + " status=" +
std::to_string(static_cast<int>(status)));
result(create_flutter_error_from_gatt_communication_status(status));
co_return;
}
@@ -1246,16 +1285,16 @@ fire_and_forget UniversalBlePlugin::SetNotifiableAsync(
if (gatt_char.subscription_token.has_value()) {
gatt_characteristic.ValueChanged(gatt_char.subscription_token.value());
gatt_char.subscription_token = std::nullopt;
std::cout << "Unsubscribed " << to_uuidstr(gatt_characteristic.Uuid())
<< std::endl;
UniversalBleLogger::LogInfo("Unsubscribed " +
to_uuidstr(gatt_characteristic.Uuid()));
}
} else {
// If a notification for the given characteristic is already in progress,
// swap the callbacks.
if (gatt_char.subscription_token.has_value()) {
std::cout << "A notification for the given characteristic is already "
"in progress. Swapping callbacks."
<< std::endl;
UniversalBleLogger::LogWarning(
"A notification for the given characteristic is already in "
"progress. Swapping callbacks.");
gatt_characteristic.ValueChanged(gatt_char.subscription_token.value());
gatt_char.subscription_token = std::nullopt;
}
@@ -1269,7 +1308,7 @@ fire_and_forget UniversalBlePlugin::SetNotifiableAsync(
} catch (const FlutterError &err) {
result(err);
} catch (...) {
std::cout << "SetNotifiableLog: Unknown error" << std::endl;
UniversalBleLogger::LogError("SetNotifiableLog: Unknown error");
result(create_flutter_unknown_error());
}
}
@@ -1278,10 +1317,19 @@ void UniversalBlePlugin::GattCharacteristicValueChanged(
const GattCharacteristic &sender, const GattValueChangedEventArgs &args) {
auto uuid = to_uuidstr(sender.Uuid());
auto bytes = to_bytevc(args.CharacteristicValue());
ui_thread_handler_.Post([sender, uuid, bytes] {
auto device_id =
mac_address_to_str(sender.Service().Device().BluetoothAddress());
UniversalBleLogger::LogVerboseWithTimestamp(
"NOTIFY <- " + device_id + " " + uuid +
" len=" + std::to_string(bytes.size()));
auto timestamp = GetCurrentTimestampMillis();
ui_thread_handler_.Post([sender, bytes, timestamp] {
auto uuid = to_uuidstr(sender.Uuid());
callback_channel->OnValueChanged(
mac_address_to_str(sender.Service().Device().BluetoothAddress()), uuid,
bytes, SuccessCallback, ErrorCallback);
bytes, &timestamp, SuccessCallback, ErrorCallback);
});
}
+177 -172
View File
@@ -5,205 +5,210 @@
#include <flutter/plugin_registrar_windows.h>
#include <windows.h>
#include <winrt/base.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Devices.Radios.h>
#include <winrt/Windows.Devices.Bluetooth.h>
#include <winrt/Windows.Devices.Bluetooth.Advertisement.h>
#include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h>
#include <winrt/Windows.Devices.Bluetooth.h>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Devices.Radios.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/base.h>
#include <memory>
#include "helper/utils.h"
#include "helper/universal_enum.h"
#include "helper/universal_ble_base.h"
#include "generated/universal_ble.g.h"
#include "helper/universal_ble_base.h"
#include "helper/universal_enum.h"
#include "helper/utils.h"
#include "ui_thread_handler.hpp"
#include "universal_ble_thread_safe.h"
#include <memory>
namespace universal_ble
{
struct GattCharacteristicObject
{
GattCharacteristic obj = nullptr;
std::optional<event_token> subscription_token;
};
namespace universal_ble {
struct GattCharacteristicObject {
GattCharacteristic obj = nullptr;
std::optional<event_token> subscription_token;
};
struct GattServiceObject
{
GattDeviceService obj = nullptr;
std::unordered_map<std::string, GattCharacteristicObject> characteristics;
};
struct GattServiceObject {
GattDeviceService obj = nullptr;
std::unordered_map<std::string, GattCharacteristicObject> characteristics;
};
struct BluetoothDeviceAgent
{
BluetoothLEDevice device;
event_token connection_status_changed_token;
std::unordered_map<std::string, GattServiceObject> gatt_map;
struct BluetoothDeviceAgent {
BluetoothLEDevice device;
event_token connection_status_changed_token;
std::unordered_map<std::string, GattServiceObject> gatt_map;
BluetoothDeviceAgent(const BluetoothLEDevice &device, const event_token connection_status_changed_token,
const std::unordered_map<std::string, GattServiceObject> &gatt_map)
: device(device),
connection_status_changed_token(connection_status_changed_token),
gatt_map(gatt_map)
{
}
BluetoothDeviceAgent(
const BluetoothLEDevice &device,
const event_token connection_status_changed_token,
const std::unordered_map<std::string, GattServiceObject> &gatt_map)
: device(device),
connection_status_changed_token(connection_status_changed_token),
gatt_map(gatt_map) {}
~BluetoothDeviceAgent()
{
device = nullptr;
}
~BluetoothDeviceAgent() { device = nullptr; }
GattCharacteristicObject &FetchCharacteristic(const std::string &service_uuid,
const std::string &characteristic_uuid)
{
if (gatt_map.count(service_uuid) == 0)
{
throw create_flutter_error(UniversalBleErrorCode::kServiceNotFound, "Service not found");
}
if (gatt_map[service_uuid].characteristics.count(characteristic_uuid) == 0)
{
throw create_flutter_error(UniversalBleErrorCode::kCharacteristicNotFound, "Characteristic not found");
}
return gatt_map[service_uuid].characteristics.at(characteristic_uuid);
}
};
GattCharacteristicObject &
FetchCharacteristic(const std::string &service_uuid,
const std::string &characteristic_uuid) {
if (gatt_map.count(service_uuid) == 0) {
throw create_flutter_error(UniversalBleErrorCode::kServiceNotFound,
"Service not found");
}
if (gatt_map[service_uuid].characteristics.count(characteristic_uuid) ==
0) {
throw create_flutter_error(UniversalBleErrorCode::kCharacteristicNotFound,
"Characteristic not found");
}
return gatt_map[service_uuid].characteristics.at(characteristic_uuid);
}
};
class UniversalBlePlugin : public flutter::Plugin, public UniversalBlePlatformChannel
{
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
class UniversalBlePlugin : public flutter::Plugin,
public UniversalBlePlatformChannel {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
UniversalBlePlugin(flutter::PluginRegistrarWindows *registrar);
UniversalBlePlugin(flutter::PluginRegistrarWindows *registrar);
~UniversalBlePlugin();
~UniversalBlePlugin();
// Disallow copy and assign.
UniversalBlePlugin(const UniversalBlePlugin&) = delete;
UniversalBlePlugin& operator=(const UniversalBlePlugin&) = delete;
// Disallow copy and assign.
UniversalBlePlugin(const UniversalBlePlugin &) = delete;
UniversalBlePlugin &operator=(const UniversalBlePlugin &) = delete;
private:
static void SuccessCallback() {}
static void ErrorCallback(const FlutterError &error) {
// Ignore ChannelConnection Error, This might occur because of HotReload
if (error.code() != "channel-error") {
std::cout << "ErrorCode: " << error.code()
<< " Message: " << error.message() << std::endl;
}
}
static int64_t GetCurrentTimestampMillis() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
private:
static void SuccessCallback() {}
static void ErrorCallback(const FlutterError &error)
{
// Ignore ChannelConnection Error, This might occur because of HotReload
if (error.code() != "channel-error")
{
std::cout << "ErrorCode: " << error.code() << " Message: " << error.message() << std::endl;
}
}
flutter::PluginRegistrarWindows *registrar_;
bool initialized_ = false;
flutter::PluginRegistrarWindows *registrar_;
bool initialized_ = false;
UniversalBleUiThreadHandler ui_thread_handler_;
Radio bluetooth_radio_{nullptr};
RadioState old_radio_state_ = RadioState::Unknown;
BluetoothLEAdvertisementWatcher bluetooth_le_watcher_{nullptr};
DeviceWatcher device_watcher_{nullptr};
UniversalBleUiThreadHandler ui_thread_handler_;
Radio bluetooth_radio_{nullptr};
RadioState old_radio_state_ = RadioState::Unknown;
BluetoothLEAdvertisementWatcher bluetooth_le_watcher_{nullptr};
DeviceWatcher device_watcher_{nullptr};
std::unordered_map<uint64_t, std::unique_ptr<BluetoothDeviceAgent>>
connected_devices_{};
ThreadSafeMap<std::string, DeviceInformation> device_watcher_devices_{};
ThreadSafeMap<std::string, UniversalBleScanResult> scan_results_{};
// Maps DeviceInformation.Id() -> MAC address string used as key in
// device_watcher_devices_
ThreadSafeMap<std::string, std::string> device_watcher_id_to_mac_{};
std::unordered_map<uint64_t, std::unique_ptr<BluetoothDeviceAgent>> connected_devices_{};
ThreadSafeMap<std::string, DeviceInformation> device_watcher_devices_{};
ThreadSafeMap<std::string, UniversalBleScanResult> scan_results_{};
// Maps DeviceInformation.Id() -> MAC address string used as key in device_watcher_devices_
ThreadSafeMap<std::string, std::string> device_watcher_id_to_mac_{};
event_token bluetooth_le_watcher_received_token_;
event_token device_watcher_added_token_;
event_token device_watcher_updated_token_;
event_token device_watcher_removed_token_;
event_token device_watcher_enumeration_completed_token_;
event_token device_watcher_stopped_token_;
event_revoker<IRadio> radio_state_changed_revoker_;
event_token bluetooth_le_watcher_received_token_;
event_token device_watcher_added_token_;
event_token device_watcher_updated_token_;
event_token device_watcher_removed_token_;
event_token device_watcher_enumeration_completed_token_;
event_token device_watcher_stopped_token_;
event_revoker<IRadio> radio_state_changed_revoker_;
fire_and_forget InitializeAsync();
fire_and_forget ConnectAsync(uint64_t bluetooth_address);
fire_and_forget SetNotifiableAsync(
const std::string &device_id, const std::string &service,
const std::string &characteristic, int64_t ble_input_property,
std::function<void(std::optional<FlutterError> reply)> result);
fire_and_forget PairAsync(const std::string &device_id,
std::function<void(ErrorOr<bool> reply)> result);
fire_and_forget
CustomPairAsync(const std::string &device_id,
std::function<void(ErrorOr<bool> reply)> result);
static fire_and_forget GetSystemDevicesAsync(
std::vector<std::string> with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result);
static fire_and_forget
IsPairedAsync(const std::string &device_id,
std::function<void(ErrorOr<bool> reply)> result);
fire_and_forget DiscoverServicesAsync(
const std::string &device_id, bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result);
void
PairingRequestedHandler(DeviceInformationCustomPairing sender,
const DevicePairingRequestedEventArgs &event_args);
fire_and_forget InitializeAsync();
fire_and_forget ConnectAsync(uint64_t bluetooth_address);
fire_and_forget SetNotifiableAsync(
const std::string& device_id,
const std::string& service,
const std::string& characteristic,
int64_t ble_input_property,
std::function<void(std::optional<FlutterError> reply)> result);
fire_and_forget PairAsync(const std::string& device_id, std::function<void(ErrorOr<bool> reply)> result);
fire_and_forget CustomPairAsync(const std::string& device_id, std::function<void(ErrorOr<bool> reply)> result);
static fire_and_forget GetSystemDevicesAsync(
std::vector<std::string> with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result);
static fire_and_forget IsPairedAsync(const std::string& device_id, std::function<void(ErrorOr<bool> reply)> result);
fire_and_forget DiscoverServicesAsync(const std::string &device_id,
bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result);
void RadioStateChanged(const Radio &sender, const IInspectable &);
void SetupDeviceWatcher();
void DisposeDeviceWatcher();
void PushUniversalScanResult(UniversalBleScanResult scan_result,
bool is_connectable);
void BluetoothLeWatcherReceived(
const BluetoothLEAdvertisementWatcher &sender,
const BluetoothLEAdvertisementReceivedEventArgs &args);
void OnDeviceInfoReceived(const DeviceInformation &device_info);
void BluetoothLeDeviceConnectionStatusChanged(const BluetoothLEDevice &sender,
const IInspectable &args);
void CleanConnection(uint64_t bluetooth_address);
void
DisposeServices(const std::unique_ptr<BluetoothDeviceAgent> &device_agent);
void PairingRequestedHandler(DeviceInformationCustomPairing sender, const DevicePairingRequestedEventArgs& event_args);
void GattCharacteristicValueChanged(const GattCharacteristic &sender,
const GattValueChangedEventArgs &args);
void RadioStateChanged(const Radio& sender, const IInspectable&);
void SetupDeviceWatcher();
void DisposeDeviceWatcher();
void PushUniversalScanResult(UniversalBleScanResult scan_result, bool is_connectable);
void BluetoothLeWatcherReceived(const BluetoothLEAdvertisementWatcher& sender, const
BluetoothLEAdvertisementReceivedEventArgs& args);
void OnDeviceInfoReceived(const DeviceInformation& device_info);
void BluetoothLeDeviceConnectionStatusChanged(const BluetoothLEDevice& sender, const IInspectable& args);
void CleanConnection(uint64_t bluetooth_address);
void DisposeServices(const std::unique_ptr<BluetoothDeviceAgent> &device_agent);
void GattCharacteristicValueChanged(const GattCharacteristic& sender, const GattValueChangedEventArgs& args);
// UniversalBlePlatformChannel implementation.
void GetBluetoothAvailabilityState(std::function<void(ErrorOr<int64_t> reply)> result) override;
void EnableBluetooth(std::function<void(ErrorOr<bool> reply)> result) override;
void DisableBluetooth(std::function<void(ErrorOr<bool> reply)> result) override;
ErrorOr<int64_t> GetConnectionState(const std::string &device_id) override;
std::optional<FlutterError> StartScan(const UniversalScanFilter *filter) override;
std::optional<FlutterError> StopScan() override;
ErrorOr<bool> IsScanning() override;
std::optional<FlutterError> Connect(const std::string &device_id) override;
std::optional<FlutterError> Disconnect(const std::string &device_id) override;
void RequestPermissions(
bool with_android_fine_location,
std::function<void(std::optional<FlutterError> reply)> result) override;
void DiscoverServices(
const std::string &device_id,
bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) override;
void SetNotifiable(
const std::string &device_id,
const std::string &service,
const std::string &characteristic,
int64_t ble_input_property,
std::function<void(std::optional<FlutterError> reply)> result) override;
void ReadValue(
const std::string &device_id,
const std::string &service,
const std::string &characteristic,
std::function<void(ErrorOr<std::vector<uint8_t>> reply)> result) override;
void WriteValue(
const std::string &device_id,
const std::string &service,
const std::string &characteristic,
const std::vector<uint8_t> &value,
int64_t ble_output_property,
std::function<void(std::optional<FlutterError> reply)> result) override;
void RequestMtu(
const std::string &device_id,
int64_t expected_mtu,
std::function<void(ErrorOr<int64_t> reply)> result) override;
void IsPaired(
const std::string &device_id,
// UniversalBlePlatformChannel implementation.
void GetBluetoothAvailabilityState(
std::function<void(ErrorOr<int64_t> reply)> result) override;
void
EnableBluetooth(std::function<void(ErrorOr<bool> reply)> result) override;
void
DisableBluetooth(std::function<void(ErrorOr<bool> reply)> result) override;
ErrorOr<int64_t> GetConnectionState(const std::string &device_id) override;
std::optional<FlutterError>
SetLogLevel(const UniversalBleLogLevel &log_level) override;
std::optional<FlutterError>
StartScan(const UniversalScanFilter *filter) override;
std::optional<FlutterError> StopScan() override;
ErrorOr<bool> IsScanning() override;
std::optional<FlutterError> Connect(const std::string &device_id) override;
std::optional<FlutterError> Disconnect(const std::string &device_id) override;
void RequestPermissions(
bool with_android_fine_location,
std::function<void(std::optional<FlutterError> reply)> result) override;
void
DiscoverServices(const std::string &device_id, bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)>
result) override;
void SetNotifiable(
const std::string &device_id, const std::string &service,
const std::string &characteristic, int64_t ble_input_property,
std::function<void(std::optional<FlutterError> reply)> result) override;
void ReadValue(
const std::string &device_id, const std::string &service,
const std::string &characteristic,
std::function<void(ErrorOr<std::vector<uint8_t>> reply)> result) override;
void WriteValue(
const std::string &device_id, const std::string &service,
const std::string &characteristic, const std::vector<uint8_t> &value,
int64_t ble_output_property,
std::function<void(std::optional<FlutterError> reply)> result) override;
void RequestMtu(const std::string &device_id, int64_t expected_mtu,
std::function<void(ErrorOr<int64_t> reply)> result) override;
void IsPaired(const std::string &device_id,
std::function<void(ErrorOr<bool> reply)> result) override;
void Pair(const std::string &device_id,
std::function<void(ErrorOr<bool> reply)> result) override;
void Pair(
const std::string &device_id,
std::function<void(ErrorOr<bool> reply)> result) override;
std::optional<FlutterError> UnPair(const std::string &device_id) override;
void GetSystemDevices(
const flutter::EncodableList &with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) override;
};
std::optional<FlutterError> UnPair(const std::string &device_id) override;
void
GetSystemDevices(const flutter::EncodableList &with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)>
result) override;
};
} // namespace universal_ble