Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## 1.2.0
* Add `autoConnect` parameter to `connect()` method for automatic reconnection support on Android and iOS/macOS
* Add `serviceData` in `BleDevice`

## 1.1.0
* Add readRssi method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ data class UniversalBleScanResult (
val isPaired: Boolean? = null,
val rssi: Long? = null,
val manufacturerDataList: List<UniversalManufacturerData>? = null,
val serviceData: Map<String, ByteArray>? = null,
val services: List<String>? = null,
val timestamp: Long? = null
)
Expand All @@ -185,9 +186,10 @@ data class UniversalBleScanResult (
val isPaired = pigeonVar_list[2] as Boolean?
val rssi = pigeonVar_list[3] as Long?
val manufacturerDataList = pigeonVar_list[4] as List<UniversalManufacturerData>?
val services = pigeonVar_list[5] as List<String>?
val timestamp = pigeonVar_list[6] as Long?
return UniversalBleScanResult(deviceId, name, isPaired, rssi, manufacturerDataList, services, timestamp)
val serviceData = pigeonVar_list[5] as Map<String, ByteArray>?
val services = pigeonVar_list[6] as List<String>?
val timestamp = pigeonVar_list[7] as Long?
return UniversalBleScanResult(deviceId, name, isPaired, rssi, manufacturerDataList, serviceData, services, timestamp)
}
}
fun toList(): List<Any?> {
Expand All @@ -197,6 +199,7 @@ data class UniversalBleScanResult (
isPaired,
rssi,
manufacturerDataList,
serviceData,
services,
timestamp,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ val ScanResult.manufacturerDataList: List<UniversalManufacturerData>
} ?: emptyList()
}

val ScanResult.serviceData: Map<String, ByteArray>
get() {
return scanRecord?.serviceData?.mapKeys { it.key.uuid.toString() } ?: emptyMap()
}

fun <T> SparseArray<T>.toList(): List<Pair<Int, T>> {
return (0 until size).map { index ->
keyAt(index) to valueAt(index)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
deviceId = it.address,
isPaired = it.bondState == BOND_BONDED,
manufacturerDataList = null,
serviceData = null,
rssi = null,
timestamp = System.currentTimeMillis()
)
Expand Down Expand Up @@ -1100,6 +1101,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),

val name = result.device.name
val manufacturerDataList = result.manufacturerDataList
val serviceData = result.serviceData

if (!universalBleFilterUtil.filterDevice(
name,
Expand All @@ -1116,6 +1118,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
deviceId = result.device.address,
isPaired = result.device.bondState == BOND_BONDED,
manufacturerDataList = manufacturerDataList,
serviceData = serviceData,
rssi = result.rssi.toLong(),
services = serviceUuids.map { it.toString() }.toList(),
timestamp = System.currentTimeMillis()
Expand Down
8 changes: 6 additions & 2 deletions darwin/Classes/UniversalBle.g.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ struct UniversalBleScanResult: Hashable {
var isPaired: Bool? = nil
var rssi: Int64? = nil
var manufacturerDataList: [UniversalManufacturerData]? = nil
var serviceData: [String: FlutterStandardTypedData]? = nil
var services: [String]? = nil
var timestamp: Int64? = nil

Expand All @@ -224,15 +225,17 @@ struct UniversalBleScanResult: Hashable {
let isPaired: Bool? = nilOrValue(pigeonVar_list[2])
let rssi: Int64? = nilOrValue(pigeonVar_list[3])
let manufacturerDataList: [UniversalManufacturerData]? = nilOrValue(pigeonVar_list[4])
let services: [String]? = nilOrValue(pigeonVar_list[5])
let timestamp: Int64? = nilOrValue(pigeonVar_list[6])
let serviceData: [String: FlutterStandardTypedData]? = nilOrValue(pigeonVar_list[5])
let services: [String]? = nilOrValue(pigeonVar_list[6])
let timestamp: Int64? = nilOrValue(pigeonVar_list[7])

return UniversalBleScanResult(
deviceId: deviceId,
name: name,
isPaired: isPaired,
rssi: rssi,
manufacturerDataList: manufacturerDataList,
serviceData: serviceData,
services: services,
timestamp: timestamp
)
Expand All @@ -244,6 +247,7 @@ struct UniversalBleScanResult: Hashable {
isPaired,
rssi,
manufacturerDataList,
serviceData,
services,
timestamp,
]
Expand Down
24 changes: 17 additions & 7 deletions darwin/Classes/UniversalBlePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
let peripheral = try deviceId.getPeripheral(manager: manager)
peripheral.delegate = self
let shouldAutoConnect = autoConnect ?? false

if shouldAutoConnect {
autoConnectDevices.insert(deviceId)
if #available(iOS 17.0, macOS 14.0, watchOS 10.0, tvOS 17.0, *) {
Expand All @@ -148,9 +148,9 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
// (e.g., in central manager delegate callbacks).
UniversalBleLogger.shared.logInfo(
"autoConnect requested for device \(deviceId), " +
"but automatic reconnection via CBConnectPeripheralOptionEnableAutoReconnect " +
"is only available on iOS 17+/macOS 14+/watchOS 10+/tvOS 17+. " +
"On this OS version, reconnections must be handled manually."
"but automatic reconnection via CBConnectPeripheralOptionEnableAutoReconnect " +
"is only available on iOS 17+/macOS 14+/watchOS 10+/tvOS 17+. " +
"On this OS version, reconnections must be handled manually."
)
manager.connect(peripheral)
}
Expand Down Expand Up @@ -410,6 +410,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
return UniversalBleScanResult(
deviceId: id,
name: name,
serviceData: nil,
timestamp: Int64(Date().timeIntervalSince1970 * 1000)
)
}))
Expand Down Expand Up @@ -437,6 +438,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
// Extract manufacturer data and service UUIDs from the advertisement data
let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
let services = (advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID])
let serviceDataDict = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]

var manufacturerDataList: [UniversalManufacturerData] = []
var universalManufacturerData: UniversalManufacturerData? = nil
Expand All @@ -448,6 +450,13 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
manufacturerDataList.append(universalManufacturerData!)
}

var serviceData: [String: FlutterStandardTypedData]? = nil
if let serviceDataDict = serviceDataDict {
serviceData = Dictionary(uniqueKeysWithValues: serviceDataDict.map { uuid, data in
(uuid.uuidStr, FlutterStandardTypedData(bytes: data))
})
}

let advertisedName = advertisementData[CBAdvertisementDataLocalNameKey] as? String
let displayName = advertisedName ?? peripheral.name
advertisementNameCache[peripheral.uuid.uuidString] = displayName
Expand All @@ -463,6 +472,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
isPaired: nil,
rssi: RSSI as? Int64,
manufacturerDataList: manufacturerDataList,
serviceData: serviceData,
services: services?.map { $0.uuidStr },
timestamp: Int64(Date().timeIntervalSince1970 * 1000)
)) { _ in }
Expand All @@ -481,18 +491,18 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
public func centralManager(
_: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
timestamp: CFAbsoluteTime,
timestamp _: CFAbsoluteTime,
isReconnecting: Bool,
error: Error?
) {
let deviceId = peripheral.uuid.uuidString

if #available(iOS 17.0, macOS 14.0, watchOS 10.0, tvOS 17.0, *) {
if isReconnecting {
return
}
}

handlePeripheralDisconnection(deviceId: deviceId, error: error)
}

Expand Down
10 changes: 5 additions & 5 deletions example/macos/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
universal_ble: 45519b2aeafe62761e2c6309f8927edb5288b914
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
universal_ble: 65e1257dffc557cc7991a93d253beeddc7c1dc92
url_launcher_macos: 175a54c831f4375a6cf895875f716ee5af3888ce

PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3

Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ packages:
path: ".."
relative: true
source: path
version: "1.1.0"
version: "1.2.0"
url_launcher:
dependency: "direct main"
description:
Expand Down
24 changes: 19 additions & 5 deletions lib/src/models/ble_device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class BleDevice {
List<String> services;
bool? isSystemDevice;
List<ManufacturerData> manufacturerDataList;
Map<String, Uint8List> serviceData;

@Deprecated("Use `manufacturerDataList` instead")
Uint8List? get manufacturerData => manufacturerDataList.isEmpty
Expand Down Expand Up @@ -52,16 +53,28 @@ class BleDevice {
this.services = const [],
this.isSystemDevice,
this.manufacturerDataList = const [],
Map<String, Uint8List> serviceData = const {},
this.timestamp,
}) {
rawName = name;
this.name = name?.replaceAll(RegExp(r'[^ -~]'), '').trim();
}
}) : serviceData = _validateServiceData(serviceData),
rawName = name,
name = name?.replaceAll(RegExp(r'[^ -~]'), '').trim();

DateTime? get timestampDateTime => timestamp != null
? DateTime.fromMillisecondsSinceEpoch(timestamp!)
: null;

static Map<String, Uint8List> _validateServiceData(
Map<String, Uint8List> data,
) {
if (data.isEmpty) return const {};
return data.map(
(key, value) => MapEntry(
BleUuidParser.stringOrNull(key) ?? key,
Uint8List.fromList(value),
),
);
}

@override
String toString() {
return 'BleDevice: '
Expand All @@ -72,6 +85,7 @@ class BleDevice {
'services: $services, '
'isSystemDevice: $isSystemDevice, '
'timestamp: $timestamp, '
'manufacturerDataList: $manufacturerDataList';
'manufacturerDataList: $manufacturerDataList, '
'serviceData: $serviceData';
}
}
9 changes: 9 additions & 0 deletions lib/src/models/ble_uuid_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ class BleUuidParser {
return uuid.toLowerCase();
}

/// Parse a string to a valid 128-bit UUID or return null if the string is null or invalid.
static String? stringOrNull(String uuid) {
try {
return string(uuid);
} catch (e) {
return null;
}
}

/// Parse an int number into a 128-bit UUID string.
/// e.g. `0x1800` to `00001800-0000-1000-8000-00805f9b34fb`.
static String number(int short) {
Expand Down
18 changes: 17 additions & 1 deletion lib/src/universal_ble_linux/universal_ble_linux.dart
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,11 @@ class UniversalBleLinux extends UniversalBlePlatform {
}

@override
Future<void> connect(String deviceId, {Duration? connectionTimeout, bool autoConnect = false}) async {
Future<void> connect(
String deviceId, {
Duration? connectionTimeout,
bool autoConnect = false,
}) async {
// Note: autoConnect is not directly supported on Linux platform
final device = _findDeviceById(deviceId);
if (device.connected) {
Expand Down Expand Up @@ -696,6 +700,17 @@ extension BlueZDeviceExtension on BlueZDevice {
ManufacturerData(data.key.id, Uint8List.fromList(data.value)))
.toList();

Map<String, Uint8List> get serviceDataMap {
try {
return {
for (final entry in serviceData.entries)
entry.key.toString(): Uint8List.fromList(entry.value),
};
} catch (e) {
return <String, Uint8List>{};
}
}

BleDevice toBleDevice({bool? isSystemDevice}) {
return BleDevice(
name: name,
Expand All @@ -705,6 +720,7 @@ extension BlueZDeviceExtension on BlueZDevice {
isSystemDevice: isSystemDevice,
services: uuids.map((e) => e.toString()).toList(),
manufacturerDataList: manufacturerDataList,
serviceData: serviceDataMap,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
}
Expand Down
10 changes: 8 additions & 2 deletions lib/src/universal_ble_pigeon/universal_ble.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class UniversalBleScanResult {
this.isPaired,
this.rssi,
this.manufacturerDataList,
this.serviceData,
this.services,
this.timestamp,
});
Expand All @@ -136,6 +137,8 @@ class UniversalBleScanResult {

List<UniversalManufacturerData>? manufacturerDataList;

Map<String, Uint8List>? serviceData;

List<String>? services;

int? timestamp;
Expand All @@ -147,6 +150,7 @@ class UniversalBleScanResult {
isPaired,
rssi,
manufacturerDataList,
serviceData,
services,
timestamp,
];
Expand All @@ -165,8 +169,10 @@ class UniversalBleScanResult {
rssi: result[3] as int?,
manufacturerDataList:
(result[4] as List<Object?>?)?.cast<UniversalManufacturerData>(),
services: (result[5] as List<Object?>?)?.cast<String>(),
timestamp: result[6] as int?,
serviceData:
(result[5] as Map<Object?, Object?>?)?.cast<String, Uint8List>(),
services: (result[6] as List<Object?>?)?.cast<String>(),
timestamp: result[7] as int?,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ extension _UniversalBleScanResultExtension on UniversalBleScanResult {
?.map((e) => ManufacturerData(e.companyIdentifier, e.data))
.toList() ??
[],
serviceData: serviceData ?? {},
);
}
}
Expand Down
6 changes: 6 additions & 0 deletions lib/src/universal_ble_web/universal_ble_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,15 @@ class UniversalBleWeb extends UniversalBlePlatform {

_deviceAdvertisementStreamList[device.id] =
device.advertisements.listen((event) {
final serviceDataMap = event.serviceData.map(
(key, value) => MapEntry(key, value.buffer.asUint8List()),
);
updateScanResult(
device.toBleScanResult(
rssi: event.rssi,
manufacturerDataMap: event.manufacturerData,
services: event.uuids.toSet().toList(),
serviceDataMap: serviceDataMap,
),
);
});
Expand Down Expand Up @@ -519,13 +523,15 @@ extension _BluetoothDeviceExtension on BluetoothDevice {
int? rssi,
UnmodifiableMapView<int, ByteData>? manufacturerDataMap,
List<String> services = const [],
Map<String, Uint8List>? serviceDataMap,
}) {
return BleDevice(
name: name,
deviceId: id,
manufacturerDataList: manufacturerDataMap?.toManufacturerDataList() ?? [],
rssi: rssi,
services: services,
serviceData: serviceDataMap ?? {},
timestamp: DateTime.now().millisecondsSinceEpoch,
);
}
Expand Down
Loading