diff --git a/.vscode/settings.json b/.vscode/settings.json index bf22154..450198f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "BTLE", "bytevc", "connnection", + "Cupertino", "DSIMPLEBLE", "hresult", "hstring", @@ -13,12 +14,14 @@ "LRESULT", "microtask", "modalias", + "msix", "navideck", "NSUUID", "NTDDI", "pairable", "rssi", "simpleble", + "sublist", "unawaited", "UNIVERSALBLE", "Unpair", diff --git a/example/lib/data/capabilities.dart b/example/lib/data/capabilities.dart index b97688a..039d253 100644 --- a/example/lib/data/capabilities.dart +++ b/example/lib/data/capabilities.dart @@ -2,19 +2,19 @@ import 'package:flutter/foundation.dart'; class Capabilities { static bool requiresRuntimePermission = - !_Platform.isWeb && !_Platform.isWindows && !_Platform.isLinux; + !Platform.isWeb && !Platform.isWindows && !Platform.isLinux; static bool supportsBluetoothEnableApi = - !_Platform.isWeb && !_Platform.isCupertino; + !Platform.isWeb && !Platform.isCupertino; - static bool supportsConnectedDevicesApi = !_Platform.isWeb; + static bool supportsConnectedDevicesApi = !Platform.isWeb; - static bool supportsPairingApi = !_Platform.isWeb && !_Platform.isCupertino; + static bool supportsPairingApi = !Platform.isWeb && !Platform.isCupertino; - static bool supportsRequestMtuApi = !_Platform.isWeb; + static bool supportsRequestMtuApi = !Platform.isWeb; } -class _Platform { +class Platform { static bool isWeb = kIsWeb; static bool isIOS = !isWeb && defaultTargetPlatform == TargetPlatform.iOS; static bool isAndroid = diff --git a/example/lib/home/widgets/scanned_item_widget.dart b/example/lib/home/widgets/scanned_item_widget.dart index d3be4fa..aea9106 100644 --- a/example/lib/home/widgets/scanned_item_widget.dart +++ b/example/lib/home/widgets/scanned_item_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:universal_ble/universal_ble.dart'; +import 'package:universal_ble_example/data/capabilities.dart'; class ScannedItemWidget extends StatelessWidget { final BleScanResult scanResult; @@ -17,23 +18,33 @@ class ScannedItemWidget extends StatelessWidget { title: Text( '$name (${scanResult.rssi})', ), - subtitle: scanResult.isPaired != null - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("${scanResult.deviceId} "), - scanResult.isPaired == true - ? const Text( - "Paired", - style: TextStyle(color: Colors.green), - ) - : const Text( - "Not Paired", - style: TextStyle(color: Colors.red), - ), - ], - ) - : Text(scanResult.deviceId), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(scanResult.deviceId), + // Show manufacturer data only on web and desktop + Visibility( + visible: (Platform.isWeb || Platform.isDesktop) && + scanResult.manufacturerData?.isNotEmpty == true, + child: Text( + ManufacturerData.fromData(scanResult.manufacturerData!) + .toString(), + ), + ), + Visibility( + visible: scanResult.isPaired != null, + child: scanResult.isPaired == true + ? const Text( + "Paired", + style: TextStyle(color: Colors.green), + ) + : const Text( + "Not Paired", + style: TextStyle(color: Colors.red), + ), + ), + ], + ), trailing: const Icon(Icons.arrow_forward_ios), onTap: onTap, ), diff --git a/lib/src/models/ble_scan_result.dart b/lib/src/models/ble_scan_result.dart index e2ffebe..c7abc7c 100644 --- a/lib/src/models/ble_scan_result.dart +++ b/lib/src/models/ble_scan_result.dart @@ -22,3 +22,30 @@ class BleScanResult { this.manufacturerData = manufacturerData ?? manufacturerDataHead; } } + +/// Represents the manufacturer data of a BLE device. +/// Use [BleScanResult.manufacturerData] with [ManufacturerData.fromData] to create an instance of this class. +class ManufacturerData { + final int? companyId; + final Uint8List? data; + String? companyIdRadix16; + ManufacturerData(this.companyId, this.data) { + if (companyId != null) { + companyIdRadix16 = "0x0${companyId!.toRadixString(16)}"; + } + } + + factory ManufacturerData.fromData(Uint8List data) { + if (data.length < 2) return ManufacturerData(null, data); + int manufacturerIdInt = (data[0] + (data[1] << 8)); + return ManufacturerData( + manufacturerIdInt, + data.sublist(2), + ); + } + + @override + String toString() { + return 'ManufacturerData: companyId: $companyIdRadix16, data: $data'; + } +} diff --git a/windows/src/helper/universal_enum.h b/windows/src/helper/universal_enum.h index 473655c..0d795a8 100644 --- a/windows/src/helper/universal_enum.h +++ b/windows/src/helper/universal_enum.h @@ -44,4 +44,38 @@ namespace universal_ble poweredOn = 5, }; + enum class AdvertisementSectionType : uint8_t + { + Flags = 0x01, + IncompleteService16BitUuids = 0x02, + CompleteService16BitUuids = 0x03, + IncompleteService32BitUuids = 0x04, + CompleteService32BitUuids = 0x05, + IncompleteService128BitUuids = 0x06, + CompleteService128BitUuids = 0x07, + ShortenedLocalName = 0x08, + CompleteLocalName = 0x09, + TxPowerLevel = 0x0A, + ClassOfDevice = 0x0D, + SimplePairingHashC192 = 0x0E, + SecurityManagerTKValues = 0x10, + SecurityManagerOutOfBandFlags = 0x11, + SlaveConnectionIntervalRange = 0x12, + ServiceSolicitation16BitUuids = 0x14, + ServiceSolicitation32BitUuids = 0x1F, + ServiceSolicitation128BitUuids = 0x15, + ServiceData16BitUuids = 0x16, + ServiceData32BitUuids = 0x20, + ServiceData128BitUuids = 0x21, + PublicTargetAddress = 0x17, + RandomTargetAddress = 0x18, + Appearance = 0x19, + AdvertisingInterval = 0x1A, + LEBluetoothDeviceAddress = 0x1B, + LERole = 0x1C, + SimplePairingHashC256 = 0x1D, + ThreeDimensionInformationData = 0x3D, + ManufacturerSpecificData = 0xFF, + }; + } // namespace universal_ble \ No newline at end of file diff --git a/windows/src/helper/utils.cpp b/windows/src/helper/utils.cpp index 2611234..4d8ebb2 100644 --- a/windows/src/helper/utils.cpp +++ b/windows/src/helper/utils.cpp @@ -119,4 +119,11 @@ namespace universal_ble return std::string{chars}; } + bool isLittleEndian() + { + uint16_t number = 0x1; + char *numPtr = (char *)&number; + return (numPtr[0] == 1); + } + } // namespace SimpleBLE diff --git a/windows/src/helper/utils.h b/windows/src/helper/utils.h index 4d622cf..4d14bf2 100644 --- a/windows/src/helper/utils.h +++ b/windows/src/helper/utils.h @@ -25,6 +25,7 @@ namespace universal_ble std::string to_hexstring(std::vector bytes); std::string to_uuidstr(winrt::guid guid); + bool isLittleEndian(); /// To call async functions synchronously template diff --git a/windows/src/universal_ble_plugin.cpp b/windows/src/universal_ble_plugin.cpp index 7c8016b..0a81d04 100644 --- a/windows/src/universal_ble_plugin.cpp +++ b/windows/src/universal_ble_plugin.cpp @@ -32,12 +32,6 @@ namespace universal_ble std::unique_ptr callbackChannel; std::unordered_map characteristicsTokens{}; // TODO: Remove the map and store the token inside the characteristic object object - union uint16_t_union - { - uint16_t uint16; - byte bytes[sizeof(uint16_t)]; - }; - void UniversalBlePlugin::RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar) { auto plugin = std::make_unique(registrar); @@ -478,24 +472,46 @@ namespace universal_ble { try { + if (advertisement == nullptr) + return {}; + if (advertisement.ManufacturerData().Size() == 0) - { - return std::vector(); - } + return {}; + auto manufacturerData = advertisement.ManufacturerData().GetAt(0); - // FIXME Compat with REG_DWORD_BIG_ENDIAN - uint8_t *prefix = uint16_t_union{manufacturerData.CompanyId()}.bytes; - auto result = std::vector{prefix, prefix + sizeof(uint16_t_union)}; + if (manufacturerData == nullptr) + return {}; + + uint16_t companyId = manufacturerData.CompanyId(); + uint8_t prefix[2]; + auto leastSignificantBit = static_cast(companyId & 0xFF); + auto mostSignificantBit = static_cast(companyId >> 8); + if (isLittleEndian()) + { + prefix[0] = leastSignificantBit; + prefix[1] = mostSignificantBit; + } + else + { + prefix[0] = mostSignificantBit; + prefix[1] = leastSignificantBit; + } + std::vector result = {prefix[0], prefix[1]}; auto data = to_bytevc(manufacturerData.Data()); result.insert(result.end(), data.begin(), data.end()); + return result; } + catch (const std::exception &e) + { + std::cerr << "Error in parsing manufacturer data for device " << deviceId << ": " << e.what() << std::endl; + } catch (...) { - std::cout << "Error in parsing manufacturer data: " << deviceId << std::endl; - return std::vector(); + std::cerr << "Unknown error occurred in parsing manufacturer data for device " << deviceId << std::endl; } + return {}; } winrt::fire_and_forget UniversalBlePlugin::InitializeAsync() @@ -676,18 +692,19 @@ namespace universal_ble auto universalScanResult = UniversalBleScanResult(deviceId); std::string name = winrt::to_string(args.Advertisement().LocalName()); - // Use CompleteName from dataType if localName is empty - if (name.empty()) + auto dataSection = args.Advertisement().DataSections(); + for (auto &&data : dataSection) { - auto dataSection = args.Advertisement().DataSections(); - for (auto &&data : dataSection) + auto dataBytes = to_bytevc(data.Data()); + // Use CompleteName from dataType if localName is empty + if (name.empty() && data.DataType() == static_cast(AdvertisementSectionType::CompleteLocalName)) { - auto dataBytes = to_bytevc(data.Data()); - if (data.DataType() == 0x09) - { - name = std::string(dataBytes.begin(), dataBytes.end()); - break; - } + name = std::string(dataBytes.begin(), dataBytes.end()); + } + // Use ShortenedLocalName from dataType if localName is empty + else if (name.empty() && data.DataType() == static_cast(AdvertisementSectionType::ShortenedLocalName)) + { + name = std::string(dataBytes.begin(), dataBytes.end()); } }