Skip to content

Commit

Permalink
Merge branch 'main' into add_manufacturer_data_filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
rohitsangwan01 committed Mar 18, 2024
2 parents 2485afd + 580fca6 commit 206e677
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 64 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"BTLE",
"bytevc",
"connnection",
"Cupertino",
"DSIMPLEBLE",
"hresult",
"hstring",
Expand All @@ -13,13 +14,15 @@
"LRESULT",
"microtask",
"modalias",
"msix",
"navideck",
"NSUUID",
"NTDDI",
"pairable",
"rssi",
"simpleble",
"subdata",
"sublist",
"unawaited",
"UNIVERSALBLE",
"Unpair",
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE
| onPairingStateChange | ✔️ ||| ✔️ | ✔️ ||
| enableBluetooth | ✔️ ||| ✔️ | ✔️ ||
| onAvailabilityChange | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| requestMtu | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 ||
| requestMtu | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ ||

## Getting Started

Expand Down Expand Up @@ -158,8 +158,7 @@ UniversalBle.enableBluetooth();

## Command Queue

By default, all commands will be executed in a queue. Each command will wait for the previous one to finish.
Some platforms (e.g. Android) will fail to send consecutive commands without any delay between them so it is a good idea to leave to queue enabled.
By default, all commands are executed in a queue, with each command waiting for the previous one to finish. This is because some platforms (e.g. Android) may fail to send consecutive commands without a delay between them. Therefore, it is a good idea to leave the queue enabled.

```dart
// Disable queue
Expand Down Expand Up @@ -205,8 +204,12 @@ Add `NSBluetoothPeripheralUsageDescription` and `NSBluetoothAlwaysUsageDescripti

Add the `Bluetooth` capability to the macOS app from Xcode.

### Web
### Windows

When publishing on Windows you need to declare the following [capabilities](https://learn.microsoft.com/en-us/windows/uwp/packaging/app-capability-declarations): `bluetooth, radios`

### Web
`
On web, you have to add filters and specify optional services when scanning for devices. The parameter is ignored on other platforms.

```dart
Expand Down
12 changes: 6 additions & 6 deletions example/lib/data/capabilities.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 && !_Platform.isLinux;
static bool supportsRequestMtuApi = !Platform.isWeb;
}

class _Platform {
class Platform {
static bool isWeb = kIsWeb;
static bool isIOS = !isWeb && defaultTargetPlatform == TargetPlatform.iOS;
static bool isAndroid =
Expand Down
45 changes: 28 additions & 17 deletions example/lib/home/widgets/scanned_item_widget.dart
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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,
),
Expand Down
4 changes: 2 additions & 2 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: bluez
sha256: bfd004c81e3de0f06dce8580bc39a4600e4a6efe465a866b31d4d954c9f356aa
sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
url: "https://pub.dev"
source: hosted
version: "0.8.1"
version: "0.8.2"
boolean_selector:
dependency: transitive
description:
Expand Down
27 changes: 27 additions & 0 deletions lib/src/models/ble_scan_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
}
21 changes: 10 additions & 11 deletions lib/src/universal_ble_linux/universal_ble_linux.dart
Original file line number Diff line number Diff line change
Expand Up @@ -228,17 +228,16 @@ class UniversalBleLinux extends UniversalBlePlatform {

@override
Future<int> requestMtu(String deviceId, int expectedMtu) async {
// var device = _findDeviceById(deviceId);
// if (!device.connected) return 0;
// for (BlueZGattService service in device.gattServices) {
// for (BlueZGattCharacteristic characteristic in service.characteristics) {
// // The value provided by Bluez includes an extra 3 bytes from the GATT header, which needs to be removed.
// // requires `int? get mtu => _object.getUint16Property(_gattCharacteristicInterfaceName, 'MTU');` to add in bluez.dart
// return characteristic.mtu - 3;
// }
// }
// return 0;
throw UnimplementedError();
var device = _findDeviceById(deviceId);
if (!device.connected) throw Exception('Device not connected');
for (BlueZGattService service in device.gattServices) {
for (BlueZGattCharacteristic characteristic in service.characteristics) {
int? mtu = characteristic.mtu;
// The value provided by Bluez includes an extra 3 bytes from the GATT header, which needs to be removed.
if (mtu != null) return mtu - 3;
}
}
throw Exception('MTU not available');
}

@override
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ dependencies:
collection: ^1.17.2
convert: ^3.1.1
flutter_web_bluetooth: ^0.2.2
bluez: ^0.8.1
bluez: ^0.8.2

dev_dependencies:
flutter_test:
Expand Down
34 changes: 34 additions & 0 deletions windows/src/helper/universal_enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions windows/src/helper/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions windows/src/helper/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ namespace universal_ble
std::string to_hexstring(std::vector<uint8_t> bytes);

std::string to_uuidstr(winrt::guid guid);
bool isLittleEndian();

/// To call async functions synchronously
template <typename async_t>
Expand Down
63 changes: 40 additions & 23 deletions windows/src/universal_ble_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ namespace universal_ble
std::unordered_map<std::string, winrt::event_token> characteristicsTokens{}; // TODO: Remove the map and store the token inside the characteristic object object
std::vector<UniversalManufacturerDataFilter> manufacturerScanFilter = std::vector<UniversalManufacturerDataFilter>();

union uint16_t_union
{
uint16_t uint16;
byte bytes[sizeof(uint16_t)];
};

void UniversalBlePlugin::RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar)
{
auto plugin = std::make_unique<UniversalBlePlugin>(registrar);
Expand Down Expand Up @@ -493,24 +487,46 @@ namespace universal_ble
{
try
{
if (advertisement == nullptr)
return {};

if (advertisement.ManufacturerData().Size() == 0)
return {};

auto manufacturerData = advertisement.ManufacturerData().GetAt(0);
if (manufacturerData == nullptr)
return {};

uint16_t companyId = manufacturerData.CompanyId();
uint8_t prefix[2];
auto leastSignificantBit = static_cast<uint8_t>(companyId & 0xFF);
auto mostSignificantBit = static_cast<uint8_t>(companyId >> 8);
if (isLittleEndian())
{
prefix[0] = leastSignificantBit;
prefix[1] = mostSignificantBit;
}
else
{
return std::vector<uint8_t>();
prefix[0] = mostSignificantBit;
prefix[1] = leastSignificantBit;
}
BluetoothLEManufacturerData 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<uint8_t>{prefix, prefix + sizeof(uint16_t_union)};
std::vector<uint8_t> 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<uint8_t>();
std::cerr << "Unknown error occurred in parsing manufacturer data for device " << deviceId << std::endl;
}
return {};
}

winrt::fire_and_forget UniversalBlePlugin::InitializeAsync()
Expand Down Expand Up @@ -702,18 +718,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<uint8_t>(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<uint8_t>(AdvertisementSectionType::ShortenedLocalName))
{
name = std::string(dataBytes.begin(), dataBytes.end());
}
}

Expand Down

0 comments on commit 206e677

Please sign in to comment.