Add service-data in advertisements (#207)
* Add service-data in advertisements * Fix windows serviceData parsing * Resolve Ai comments * Resolve Ai comments and validate service data * Cleanup and Update changelog
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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?> {
|
||||
@@ -197,6 +199,7 @@ data class UniversalBleScanResult (
|
||||
isPaired,
|
||||
rssi,
|
||||
manufacturerDataList,
|
||||
serviceData,
|
||||
services,
|
||||
timestamp,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
@@ -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,
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -224,8 +225,9 @@ 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,
|
||||
@@ -233,6 +235,7 @@ struct UniversalBleScanResult: Hashable {
|
||||
isPaired: isPaired,
|
||||
rssi: rssi,
|
||||
manufacturerDataList: manufacturerDataList,
|
||||
serviceData: serviceData,
|
||||
services: services,
|
||||
timestamp: timestamp
|
||||
)
|
||||
@@ -244,6 +247,7 @@ struct UniversalBleScanResult: Hashable {
|
||||
isPaired,
|
||||
rssi,
|
||||
manufacturerDataList,
|
||||
serviceData,
|
||||
services,
|
||||
timestamp,
|
||||
]
|
||||
|
||||
@@ -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, *) {
|
||||
@@ -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)
|
||||
}
|
||||
@@ -410,6 +410,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
|
||||
return UniversalBleScanResult(
|
||||
deviceId: id,
|
||||
name: name,
|
||||
serviceData: nil,
|
||||
timestamp: Int64(Date().timeIntervalSince1970 * 1000)
|
||||
)
|
||||
}))
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 }
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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: '
|
||||
@@ -72,6 +85,7 @@ class BleDevice {
|
||||
'services: $services, '
|
||||
'isSystemDevice: $isSystemDevice, '
|
||||
'timestamp: $timestamp, '
|
||||
'manufacturerDataList: $manufacturerDataList';
|
||||
'manufacturerDataList: $manufacturerDataList, '
|
||||
'serviceData: $serviceData';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ class UniversalBleScanResult {
|
||||
this.isPaired,
|
||||
this.rssi,
|
||||
this.manufacturerDataList,
|
||||
this.serviceData,
|
||||
this.services,
|
||||
this.timestamp,
|
||||
});
|
||||
@@ -136,6 +137,8 @@ class UniversalBleScanResult {
|
||||
|
||||
List<UniversalManufacturerData>? manufacturerDataList;
|
||||
|
||||
Map<String, Uint8List>? serviceData;
|
||||
|
||||
List<String>? services;
|
||||
|
||||
int? timestamp;
|
||||
@@ -147,6 +150,7 @@ class UniversalBleScanResult {
|
||||
isPaired,
|
||||
rssi,
|
||||
manufacturerDataList,
|
||||
serviceData,
|
||||
services,
|
||||
timestamp,
|
||||
];
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -294,6 +294,7 @@ extension _UniversalBleScanResultExtension on UniversalBleScanResult {
|
||||
?.map((e) => ManufacturerData(e.companyIdentifier, e.data))
|
||||
.toList() ??
|
||||
[],
|
||||
serviceData: serviceData ?? {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -519,6 +523,7 @@ extension _BluetoothDeviceExtension on BluetoothDevice {
|
||||
int? rssi,
|
||||
UnmodifiableMapView<int, ByteData>? manufacturerDataMap,
|
||||
List<String> services = const [],
|
||||
Map<String, Uint8List>? serviceDataMap,
|
||||
}) {
|
||||
return BleDevice(
|
||||
name: name,
|
||||
@@ -526,6 +531,7 @@ extension _BluetoothDeviceExtension on BluetoothDevice {
|
||||
manufacturerDataList: manufacturerDataMap?.toManufacturerDataList() ?? [],
|
||||
rssi: rssi,
|
||||
services: services,
|
||||
serviceData: serviceDataMap ?? {},
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ class UniversalBleScanResult {
|
||||
final bool? isPaired;
|
||||
final int? rssi;
|
||||
final List<UniversalManufacturerData>? manufacturerDataList;
|
||||
final Map<String, Uint8List>? serviceData;
|
||||
final List<String>? services;
|
||||
final int? timestamp;
|
||||
|
||||
@@ -137,6 +138,7 @@ class UniversalBleScanResult {
|
||||
required this.isPaired,
|
||||
required this.rssi,
|
||||
required this.manufacturerDataList,
|
||||
required this.serviceData,
|
||||
required this.services,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
@@ -39,6 +39,7 @@ UniversalBleScanResult::UniversalBleScanResult(
|
||||
const bool* is_paired,
|
||||
const int64_t* rssi,
|
||||
const EncodableList* manufacturer_data_list,
|
||||
const EncodableMap* service_data,
|
||||
const EncodableList* services,
|
||||
const int64_t* timestamp)
|
||||
: device_id_(device_id),
|
||||
@@ -46,6 +47,7 @@ UniversalBleScanResult::UniversalBleScanResult(
|
||||
is_paired_(is_paired ? std::optional<bool>(*is_paired) : std::nullopt),
|
||||
rssi_(rssi ? std::optional<int64_t>(*rssi) : std::nullopt),
|
||||
manufacturer_data_list_(manufacturer_data_list ? std::optional<EncodableList>(*manufacturer_data_list) : std::nullopt),
|
||||
service_data_(service_data ? std::optional<EncodableMap>(*service_data) : std::nullopt),
|
||||
services_(services ? std::optional<EncodableList>(*services) : std::nullopt),
|
||||
timestamp_(timestamp ? std::optional<int64_t>(*timestamp) : std::nullopt) {}
|
||||
|
||||
@@ -110,6 +112,19 @@ void UniversalBleScanResult::set_manufacturer_data_list(const EncodableList& val
|
||||
}
|
||||
|
||||
|
||||
const EncodableMap* UniversalBleScanResult::service_data() const {
|
||||
return service_data_ ? &(*service_data_) : nullptr;
|
||||
}
|
||||
|
||||
void UniversalBleScanResult::set_service_data(const EncodableMap* value_arg) {
|
||||
service_data_ = value_arg ? std::optional<EncodableMap>(*value_arg) : std::nullopt;
|
||||
}
|
||||
|
||||
void UniversalBleScanResult::set_service_data(const EncodableMap& value_arg) {
|
||||
service_data_ = value_arg;
|
||||
}
|
||||
|
||||
|
||||
const EncodableList* UniversalBleScanResult::services() const {
|
||||
return services_ ? &(*services_) : nullptr;
|
||||
}
|
||||
@@ -138,12 +153,13 @@ void UniversalBleScanResult::set_timestamp(int64_t value_arg) {
|
||||
|
||||
EncodableList UniversalBleScanResult::ToEncodableList() const {
|
||||
EncodableList list;
|
||||
list.reserve(7);
|
||||
list.reserve(8);
|
||||
list.push_back(EncodableValue(device_id_));
|
||||
list.push_back(name_ ? EncodableValue(*name_) : EncodableValue());
|
||||
list.push_back(is_paired_ ? EncodableValue(*is_paired_) : EncodableValue());
|
||||
list.push_back(rssi_ ? EncodableValue(*rssi_) : EncodableValue());
|
||||
list.push_back(manufacturer_data_list_ ? EncodableValue(*manufacturer_data_list_) : EncodableValue());
|
||||
list.push_back(service_data_ ? EncodableValue(*service_data_) : EncodableValue());
|
||||
list.push_back(services_ ? EncodableValue(*services_) : EncodableValue());
|
||||
list.push_back(timestamp_ ? EncodableValue(*timestamp_) : EncodableValue());
|
||||
return list;
|
||||
@@ -168,11 +184,15 @@ UniversalBleScanResult UniversalBleScanResult::FromEncodableList(const Encodable
|
||||
if (!encodable_manufacturer_data_list.IsNull()) {
|
||||
decoded.set_manufacturer_data_list(std::get<EncodableList>(encodable_manufacturer_data_list));
|
||||
}
|
||||
auto& encodable_services = list[5];
|
||||
auto& encodable_service_data = list[5];
|
||||
if (!encodable_service_data.IsNull()) {
|
||||
decoded.set_service_data(std::get<EncodableMap>(encodable_service_data));
|
||||
}
|
||||
auto& encodable_services = list[6];
|
||||
if (!encodable_services.IsNull()) {
|
||||
decoded.set_services(std::get<EncodableList>(encodable_services));
|
||||
}
|
||||
auto& encodable_timestamp = list[6];
|
||||
auto& encodable_timestamp = list[7];
|
||||
if (!encodable_timestamp.IsNull()) {
|
||||
decoded.set_timestamp(std::get<int64_t>(encodable_timestamp));
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ class UniversalBleScanResult {
|
||||
const bool* is_paired,
|
||||
const int64_t* rssi,
|
||||
const flutter::EncodableList* manufacturer_data_list,
|
||||
const flutter::EncodableMap* service_data,
|
||||
const flutter::EncodableList* services,
|
||||
const int64_t* timestamp);
|
||||
|
||||
@@ -167,6 +168,10 @@ class UniversalBleScanResult {
|
||||
void set_manufacturer_data_list(const flutter::EncodableList* value_arg);
|
||||
void set_manufacturer_data_list(const flutter::EncodableList& value_arg);
|
||||
|
||||
const flutter::EncodableMap* service_data() const;
|
||||
void set_service_data(const flutter::EncodableMap* value_arg);
|
||||
void set_service_data(const flutter::EncodableMap& value_arg);
|
||||
|
||||
const flutter::EncodableList* services() const;
|
||||
void set_services(const flutter::EncodableList* value_arg);
|
||||
void set_services(const flutter::EncodableList& value_arg);
|
||||
@@ -186,6 +191,7 @@ class UniversalBleScanResult {
|
||||
std::optional<bool> is_paired_;
|
||||
std::optional<int64_t> rssi_;
|
||||
std::optional<flutter::EncodableList> manufacturer_data_list_;
|
||||
std::optional<flutter::EncodableMap> service_data_;
|
||||
std::optional<flutter::EncodableList> services_;
|
||||
std::optional<int64_t> timestamp_;
|
||||
};
|
||||
|
||||
@@ -119,7 +119,8 @@ void UniversalBlePlugin::DisableBluetooth(
|
||||
});
|
||||
}
|
||||
|
||||
ErrorOr<bool> UniversalBlePlugin::HasPermissions(bool with_android_fine_location) {
|
||||
ErrorOr<bool>
|
||||
UniversalBlePlugin::HasPermissions(bool with_android_fine_location) {
|
||||
// Windows does not require runtime permissions for Bluetooth
|
||||
return true;
|
||||
}
|
||||
@@ -261,7 +262,8 @@ UniversalBlePlugin::SetLogLevel(const UniversalBleLogLevel &log_level) {
|
||||
}
|
||||
|
||||
std::optional<FlutterError>
|
||||
UniversalBlePlugin::Connect(const std::string &device_id, const bool *auto_connect) {
|
||||
UniversalBlePlugin::Connect(const std::string &device_id,
|
||||
const bool *auto_connect) {
|
||||
// Note: autoConnect is not directly supported on Windows platform
|
||||
ConnectAsync(str_to_mac_address(device_id));
|
||||
return std::nullopt;
|
||||
@@ -455,8 +457,9 @@ void UniversalBlePlugin::RequestMtu(
|
||||
void UniversalBlePlugin::ReadRssi(
|
||||
const std::string &device_id,
|
||||
std::function<void(ErrorOr<int64_t> reply)> result) {
|
||||
result(create_flutter_error(UniversalBleErrorCode::kNotImplemented,
|
||||
"readRssi is not implemented on Windows platform"));
|
||||
result(
|
||||
create_flutter_error(UniversalBleErrorCode::kNotImplemented,
|
||||
"readRssi is not implemented on Windows platform"));
|
||||
}
|
||||
|
||||
void UniversalBlePlugin::IsPaired(
|
||||
@@ -669,6 +672,68 @@ void UniversalBlePlugin::PairingRequestedHandler(
|
||||
event_args.Accept(pin);
|
||||
}
|
||||
|
||||
std::string
|
||||
UniversalBlePlugin::ExpandServiceUuid(const std::vector<uint8_t> &uuid_bytes,
|
||||
uint8_t uuid_type) {
|
||||
if (uuid_type ==
|
||||
static_cast<uint8_t>(AdvertisementSectionType::ServiceData16BitUuids)) {
|
||||
// 16-bit UUID: expand to full 128-bit format
|
||||
if (uuid_bytes.size() >= 2) {
|
||||
uint16_t uuid_16 = (uuid_bytes[1] << 8) | uuid_bytes[0];
|
||||
char uuid_str[37];
|
||||
sprintf_s(uuid_str, "0000%04x-0000-1000-8000-00805f9b34fb", uuid_16);
|
||||
return std::string(uuid_str);
|
||||
}
|
||||
} else if (uuid_type ==
|
||||
static_cast<uint8_t>(
|
||||
AdvertisementSectionType::ServiceData32BitUuids)) {
|
||||
// 32-bit UUID: expand to full 128-bit format
|
||||
if (uuid_bytes.size() >= 4) {
|
||||
uint32_t uuid_32 = (uuid_bytes[3] << 24) | (uuid_bytes[2] << 16) |
|
||||
(uuid_bytes[1] << 8) | uuid_bytes[0];
|
||||
char uuid_str[37];
|
||||
sprintf_s(uuid_str, "%08x-0000-1000-8000-00805f9b34fb", uuid_32);
|
||||
return std::string(uuid_str);
|
||||
}
|
||||
} else if (uuid_type ==
|
||||
static_cast<uint8_t>(
|
||||
AdvertisementSectionType::ServiceData128BitUuids)) {
|
||||
// 128-bit UUID: parse with proper endianness handling
|
||||
// BLE service data stores UUIDs in little-endian byte order
|
||||
// guid_to_uuid reads: Data1/Data2/Data3 as big-endian (reverse), Data4 as
|
||||
// little-endian (forward)
|
||||
if (uuid_bytes.size() >= 16) {
|
||||
guid uuid_guid{};
|
||||
|
||||
// Data1: bytes [0-3] - guid_to_uuid reads in reverse (big-endian), so
|
||||
// store in reverse
|
||||
uuid_guid.Data1 = static_cast<uint32_t>(uuid_bytes[3]) |
|
||||
(static_cast<uint32_t>(uuid_bytes[2]) << 8) |
|
||||
(static_cast<uint32_t>(uuid_bytes[1]) << 16) |
|
||||
(static_cast<uint32_t>(uuid_bytes[0]) << 24);
|
||||
|
||||
// Data2: bytes [4-5] - guid_to_uuid reads in reverse (big-endian), so
|
||||
// store in reverse
|
||||
uuid_guid.Data2 = static_cast<uint16_t>(uuid_bytes[5]) |
|
||||
(static_cast<uint16_t>(uuid_bytes[4]) << 8);
|
||||
|
||||
// Data3: bytes [6-7] - guid_to_uuid reads in reverse (big-endian), so
|
||||
// store in reverse
|
||||
uuid_guid.Data3 = static_cast<uint16_t>(uuid_bytes[7]) |
|
||||
(static_cast<uint16_t>(uuid_bytes[6]) << 8);
|
||||
|
||||
// Data4: bytes [8-15] - guid_to_uuid reads in order (little-endian), so
|
||||
// store in order
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
uuid_guid.Data4[i] = uuid_bytes[8 + i];
|
||||
}
|
||||
|
||||
return guid_to_uuid(uuid_guid);
|
||||
}
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
// Send device to callback channel
|
||||
// if device is already discovered in deviceWatcher then merge the scan result
|
||||
void UniversalBlePlugin::PushUniversalScanResult(
|
||||
@@ -903,22 +968,75 @@ void UniversalBlePlugin::BluetoothLeWatcherReceived(
|
||||
}
|
||||
}
|
||||
|
||||
auto service_data_map = flutter::EncodableMap();
|
||||
auto data_section = args.Advertisement().DataSections();
|
||||
for (auto &&data : data_section) {
|
||||
auto data_bytes = to_bytevc(data.Data());
|
||||
auto data_type = data.DataType();
|
||||
|
||||
// Use CompleteName from dataType if localName is empty
|
||||
if (name.empty() &&
|
||||
data.DataType() == static_cast<uint8_t>(
|
||||
AdvertisementSectionType::CompleteLocalName)) {
|
||||
data_type == static_cast<uint8_t>(
|
||||
AdvertisementSectionType::CompleteLocalName)) {
|
||||
name = std::string(data_bytes.begin(), data_bytes.end());
|
||||
}
|
||||
// Use ShortenedLocalName from dataType if localName is empty
|
||||
else if (name.empty() &&
|
||||
data.DataType() ==
|
||||
static_cast<uint8_t>(
|
||||
AdvertisementSectionType::ShortenedLocalName)) {
|
||||
data_type == static_cast<uint8_t>(
|
||||
AdvertisementSectionType::ShortenedLocalName)) {
|
||||
name = std::string(data_bytes.begin(), data_bytes.end());
|
||||
}
|
||||
// Extract service data
|
||||
else if (data_type ==
|
||||
static_cast<uint8_t>(
|
||||
AdvertisementSectionType::ServiceData16BitUuids) ||
|
||||
data_type ==
|
||||
static_cast<uint8_t>(
|
||||
AdvertisementSectionType::ServiceData32BitUuids) ||
|
||||
data_type ==
|
||||
static_cast<uint8_t>(
|
||||
AdvertisementSectionType::ServiceData128BitUuids)) {
|
||||
// Helper lambda to parse UUID and extract service data
|
||||
auto parse_service_data =
|
||||
[&](const std::vector<uint8_t> &bytes,
|
||||
uint8_t type) -> std::pair<std::string, std::vector<uint8_t>> {
|
||||
std::string uuid;
|
||||
std::vector<uint8_t> data_payload;
|
||||
size_t uuid_size = 0;
|
||||
|
||||
if (type == static_cast<uint8_t>(
|
||||
AdvertisementSectionType::ServiceData16BitUuids)) {
|
||||
uuid_size = 2;
|
||||
} else if (type ==
|
||||
static_cast<uint8_t>(
|
||||
AdvertisementSectionType::ServiceData32BitUuids)) {
|
||||
uuid_size = 4;
|
||||
} else if (type ==
|
||||
static_cast<uint8_t>(
|
||||
AdvertisementSectionType::ServiceData128BitUuids)) {
|
||||
uuid_size = 16;
|
||||
}
|
||||
|
||||
if (bytes.size() >= uuid_size) {
|
||||
std::vector<uint8_t> uuid_bytes(bytes.begin(),
|
||||
bytes.begin() + uuid_size);
|
||||
uuid = ExpandServiceUuid(uuid_bytes, type);
|
||||
if (bytes.size() > uuid_size) {
|
||||
data_payload =
|
||||
std::vector<uint8_t>(bytes.begin() + uuid_size, bytes.end());
|
||||
}
|
||||
}
|
||||
|
||||
return {uuid, data_payload};
|
||||
};
|
||||
|
||||
auto [service_uuid, service_data_bytes] =
|
||||
parse_service_data(data_bytes, data_type);
|
||||
if (!service_uuid.empty()) {
|
||||
service_data_map[service_uuid] =
|
||||
flutter::EncodableValue(service_data_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!name.empty()) {
|
||||
@@ -938,6 +1056,11 @@ void UniversalBlePlugin::BluetoothLeWatcherReceived(
|
||||
services.push_back(guid_to_uuid(uuid));
|
||||
universal_scan_result.set_services(services);
|
||||
|
||||
// Add service data
|
||||
if (!service_data_map.empty()) {
|
||||
universal_scan_result.set_service_data(&service_data_map);
|
||||
}
|
||||
|
||||
// check if this device already discovered in deviceWatcher
|
||||
auto it = device_watcher_devices_.get(device_id);
|
||||
if (it.has_value()) {
|
||||
|
||||
@@ -148,6 +148,8 @@ private:
|
||||
void DisposeDeviceWatcher();
|
||||
void PushUniversalScanResult(UniversalBleScanResult scan_result,
|
||||
bool is_connectable);
|
||||
static std::string ExpandServiceUuid(const std::vector<uint8_t>& uuid_bytes,
|
||||
uint8_t uuid_type);
|
||||
void BluetoothLeWatcherReceived(
|
||||
const BluetoothLEAdvertisementWatcher &sender,
|
||||
const BluetoothLEAdvertisementReceivedEventArgs &args);
|
||||
|
||||
Reference in New Issue
Block a user