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:
@@ -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,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
enum BleLogLevel {
|
||||
none,
|
||||
error,
|
||||
warning,
|
||||
info,
|
||||
debug,
|
||||
verbose;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user