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:
Rohit Sangwan
2026-01-21 13:43:36 +05:30
committed by GitHub
parent 1334057a8a
commit dae2f5dc4d
18 changed files with 268 additions and 37 deletions
+1
View File
@@ -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()
+6 -2
View File
@@ -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,
]
+14 -4
View File
@@ -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,7 +491,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
public func centralManager(
_: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
timestamp: CFAbsoluteTime,
timestamp _: CFAbsoluteTime,
isReconnecting: Bool,
error: Error?
) {
+5 -5
View File
@@ -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
+19 -5
View File
@@ -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';
}
}
+9
View File
@@ -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,
);
}
+2
View File
@@ -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,
});
+23 -3
View File
@@ -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));
}
+6
View File
@@ -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_;
};
+132 -9
View File
@@ -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()) {
+2
View File
@@ -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);