diff --git a/android/src/main/java/com/pauldemarco/flutterblue/AdvertisementParser.java b/android/src/main/java/com/pauldemarco/flutterblue/AdvertisementParser.java index b68f79b4..315ffe44 100644 --- a/android/src/main/java/com/pauldemarco/flutterblue/AdvertisementParser.java +++ b/android/src/main/java/com/pauldemarco/flutterblue/AdvertisementParser.java @@ -84,7 +84,7 @@ static AdvertisementData parse(byte[] rawData) { break; } case 0x0A: { // Power level. - ret.setTxPowerLevel(data.get()); + ret.setTxPowerLevel(Protos.Int32Value.newBuilder().setValue(data.get())); break; } case 0x16: // Service Data with 16 bit UUID. @@ -114,9 +114,15 @@ static AdvertisementData parse(byte[] rawData) { break; } case 0xFF: {// Manufacturer specific data. - byte[] msd = new byte[length]; - data.get(msd); - ret.setManufacturerData(ByteString.copyFrom(msd)); + if(length < 2) { + throw new ArrayIndexOutOfBoundsException("Not enough data for Manufacturer specific data."); + } + int manufacturerId = data.getShort(); + if((length - 2) > 0) { + byte[] msd = new byte[length - 2]; + data.get(msd); + ret.putManufacturerData(manufacturerId, ByteString.copyFrom(msd)); + } break; } default: { diff --git a/android/src/main/java/com/pauldemarco/flutterblue/FlutterBluePlugin.java b/android/src/main/java/com/pauldemarco/flutterblue/FlutterBluePlugin.java index bbb43b84..f1db5292 100644 --- a/android/src/main/java/com/pauldemarco/flutterblue/FlutterBluePlugin.java +++ b/android/src/main/java/com/pauldemarco/flutterblue/FlutterBluePlugin.java @@ -622,7 +622,7 @@ private ScanCallback getScanCallback21() { public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); if(scanResultsSink != null) { - Protos.ScanResult scanResult = ProtoMaker.from(result.getDevice(), result.getScanRecord().getBytes(), result.getRssi()); + Protos.ScanResult scanResult = ProtoMaker.from(result.getDevice(), result); scanResultsSink.success(scanResult.toByteArray()); } } diff --git a/android/src/main/java/com/pauldemarco/flutterblue/ProtoMaker.java b/android/src/main/java/com/pauldemarco/flutterblue/ProtoMaker.java index 4f5fda39..1569ce4e 100644 --- a/android/src/main/java/com/pauldemarco/flutterblue/ProtoMaker.java +++ b/android/src/main/java/com/pauldemarco/flutterblue/ProtoMaker.java @@ -4,15 +4,26 @@ package com.pauldemarco.flutterblue; +import android.annotation.TargetApi; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanResult; +import android.os.Build; +import android.os.Parcel; +import android.os.ParcelUuid; +import android.util.Log; +import android.util.SparseArray; import com.google.protobuf.ByteString; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.UUID; /** @@ -32,6 +43,56 @@ static Protos.ScanResult from(BluetoothDevice device, byte[] advertisementData, return p.build(); } + @TargetApi(21) + static Protos.ScanResult from(BluetoothDevice device, ScanResult scanResult) { + Protos.ScanResult.Builder p = Protos.ScanResult.newBuilder(); + p.setDevice(from(device)); + Protos.AdvertisementData.Builder a = Protos.AdvertisementData.newBuilder(); + ScanRecord scanRecord = scanResult.getScanRecord(); + if(Build.VERSION.SDK_INT >= 26) { + a.setConnectable(scanResult.isConnectable()); + } else { + if(scanRecord != null) { + int flags = scanRecord.getAdvertiseFlags(); + a.setConnectable((flags & 0x2) > 0); + } + } + if(scanRecord != null) { + String deviceName = scanRecord.getDeviceName(); + if(deviceName != null) { + a.setLocalName(deviceName); + } + int txPower = scanRecord.getTxPowerLevel(); + if(txPower != Integer.MIN_VALUE) { + a.setTxPowerLevel(Protos.Int32Value.newBuilder().setValue(txPower)); + } + // Manufacturer Specific Data + SparseArray msd = scanRecord.getManufacturerSpecificData(); + for (int i = 0; i < msd.size(); i++) { + int key = msd.keyAt(i); + byte[] value = msd.valueAt(i); + a.putManufacturerData(key, ByteString.copyFrom(value)); + } + // Service Data + Map serviceData = scanRecord.getServiceData(); + for (Map.Entry entry : serviceData.entrySet()) { + ParcelUuid key = entry.getKey(); + byte[] value = entry.getValue(); + a.putServiceData(key.getUuid().toString(), ByteString.copyFrom(value)); + } + // Service UUIDs + List serviceUuids = scanRecord.getServiceUuids(); + if(serviceUuids != null) { + for (ParcelUuid s : serviceUuids) { + a.addServiceUuids(s.getUuid().toString()); + } + } + } + p.setRssi(scanResult.getRssi()); + p.setAdvertisementData(a.build()); + return p.build(); + } + static Protos.BluetoothDevice from(BluetoothDevice device) { Protos.BluetoothDevice.Builder p = Protos.BluetoothDevice.newBuilder(); p.setRemoteId(device.getAddress()); diff --git a/example/lib/widgets.dart b/example/lib/widgets.dart index 6b2e9163..74acf750 100644 --- a/example/lib/widgets.dart +++ b/example/lib/widgets.dart @@ -25,10 +25,55 @@ class ScanResultTile extends StatelessWidget { } } + Widget _buildAdvRow(BuildContext context, String title, String value) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: Theme.of(context).textTheme.caption), + SizedBox( + width: 12.0, + ), + Expanded( + child: Text( + value, + style: Theme.of(context) + .textTheme + .caption + .apply(color: Colors.black), + softWrap: true, + ), + ), + ], + ), + ); + } + + String getNiceManufacturerData(Map> data) { + if (data.isEmpty) { + return null; + } + List res = []; + data.forEach((id, bytes) { + res.add('${id.toRadixString(16).toUpperCase()}: $bytes'); + }); + return res.join(', '); + } + + String getNiceServiceData(Map> data) { + if (data.isEmpty) { + return null; + } + List res = []; + data.forEach((id, bytes) { + res.add('$id: $bytes'); + }); + return res.join(', '); + } + @override Widget build(BuildContext context) { - print('MANU DATA: ${result.advertisementData.manufacturerData}'); - print('TX POWER: ${result.advertisementData.txPowerLevel}'); return ExpansionTile( title: _buildTitle(context), leading: Text(result.rssi.toString()), @@ -36,15 +81,27 @@ class ScanResultTile extends StatelessWidget { child: Text('CONNECT'), color: Colors.black, textColor: Colors.white, - onPressed: onTap, + onPressed: (result.advertisementData.connectable) ? onTap : null, ), children: [ - Row( - children: [ - Text('Complete Local Name:'), - Text(result.advertisementData.localName) - ], - ) + _buildAdvRow( + context, 'Complete Local Name', result.advertisementData.localName), + _buildAdvRow(context, 'Tx Power Level', + '${result.advertisementData.txPowerLevel ?? 'N/A'}'), + _buildAdvRow( + context, + 'Manufacturer Data', + getNiceManufacturerData( + result.advertisementData.manufacturerData) ?? + 'N/A'), + _buildAdvRow( + context, + 'Service UUIDs', + (result.advertisementData.serviceUuids.isNotEmpty) + ? result.advertisementData.serviceUuids.join(', ') + : 'N/A'), + _buildAdvRow(context, 'Service Data', + getNiceServiceData(result.advertisementData.serviceData) ?? 'N/A'), ], ); } diff --git a/ios/gen/Flutterblue.pbobjc.h b/ios/gen/Flutterblue.pbobjc.h index b16cb2c1..86df6248 100644 --- a/ios/gen/Flutterblue.pbobjc.h +++ b/ios/gen/Flutterblue.pbobjc.h @@ -33,6 +33,7 @@ CF_EXTERN_C_BEGIN @class ProtosBluetoothDevice; @class ProtosBluetoothService; @class ProtosCharacteristicProperties; +@class ProtosInt32Value; @class ProtosReadDescriptorRequest; @class ProtosWriteCharacteristicRequest; @class ProtosWriteDescriptorRequest; @@ -147,6 +148,24 @@ BOOL ProtosDeviceStateResponse_BluetoothDeviceState_IsValidValue(int32_t value); @interface ProtosFlutterblueRoot : GPBRootObject @end +#pragma mark - ProtosInt32Value + +typedef GPB_ENUM(ProtosInt32Value_FieldNumber) { + ProtosInt32Value_FieldNumber_Value = 1, +}; + +/** + * Wrapper message for `int32`. + * + * The JSON representation for `Int32Value` is JSON number. + **/ +@interface ProtosInt32Value : GPBMessage + +/** The int32 value. */ +@property(nonatomic, readwrite) int32_t value; + +@end + #pragma mark - ProtosBluetoothState typedef GPB_ENUM(ProtosBluetoothState_FieldNumber) { @@ -186,11 +205,16 @@ typedef GPB_ENUM(ProtosAdvertisementData_FieldNumber) { @property(nonatomic, readwrite, copy, null_resettable) NSString *localName; -@property(nonatomic, readwrite) int32_t txPowerLevel; +@property(nonatomic, readwrite, strong, null_resettable) ProtosInt32Value *txPowerLevel; +/** Test to see if @c txPowerLevel has been set. */ +@property(nonatomic, readwrite) BOOL hasTxPowerLevel; @property(nonatomic, readwrite) BOOL connectable; -@property(nonatomic, readwrite, copy, null_resettable) NSData *manufacturerData; +/** Map of manufacturers to their data */ +@property(nonatomic, readwrite, strong, null_resettable) GPBInt32ObjectDictionary *manufacturerData; +/** The number of items in @c manufacturerData without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger manufacturerData_Count; /** Map of service UUIDs to their data. */ @property(nonatomic, readwrite, strong, null_resettable) NSMutableDictionary *serviceData; diff --git a/ios/gen/Flutterblue.pbobjc.m b/ios/gen/Flutterblue.pbobjc.m index 386fc25f..efe85579 100644 --- a/ios/gen/Flutterblue.pbobjc.m +++ b/ios/gen/Flutterblue.pbobjc.m @@ -45,6 +45,49 @@ @implementation ProtosFlutterblueRoot return descriptor; } +#pragma mark - ProtosInt32Value + +@implementation ProtosInt32Value + +@dynamic value; + +typedef struct ProtosInt32Value__storage_ { + uint32_t _has_storage_[1]; + int32_t value; +} ProtosInt32Value__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "value", + .dataTypeSpecific.className = NULL, + .number = ProtosInt32Value_FieldNumber_Value, + .hasIndex = 0, + .offset = (uint32_t)offsetof(ProtosInt32Value__storage_, value), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[ProtosInt32Value class] + rootClass:[ProtosFlutterblueRoot class] + file:ProtosFlutterblueRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(ProtosInt32Value__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + #pragma mark - ProtosBluetoothState @implementation ProtosBluetoothState @@ -151,17 +194,17 @@ BOOL ProtosBluetoothState_State_IsValidValue(int32_t value__) { @implementation ProtosAdvertisementData @dynamic localName; -@dynamic txPowerLevel; +@dynamic hasTxPowerLevel, txPowerLevel; @dynamic connectable; -@dynamic manufacturerData; +@dynamic manufacturerData, manufacturerData_Count; @dynamic serviceData, serviceData_Count; @dynamic serviceUuidsArray, serviceUuidsArray_Count; typedef struct ProtosAdvertisementData__storage_ { uint32_t _has_storage_[1]; - int32_t txPowerLevel; NSString *localName; - NSData *manufacturerData; + ProtosInt32Value *txPowerLevel; + GPBInt32ObjectDictionary *manufacturerData; NSMutableDictionary *serviceData; NSMutableArray *serviceUuidsArray; } ProtosAdvertisementData__storage_; @@ -183,12 +226,12 @@ + (GPBDescriptor *)descriptor { }, { .name = "txPowerLevel", - .dataTypeSpecific.className = NULL, + .dataTypeSpecific.className = GPBStringifySymbol(ProtosInt32Value), .number = ProtosAdvertisementData_FieldNumber_TxPowerLevel, .hasIndex = 1, .offset = (uint32_t)offsetof(ProtosAdvertisementData__storage_, txPowerLevel), .flags = GPBFieldOptional, - .dataType = GPBDataTypeInt32, + .dataType = GPBDataTypeMessage, }, { .name = "connectable", @@ -203,9 +246,9 @@ + (GPBDescriptor *)descriptor { .name = "manufacturerData", .dataTypeSpecific.className = NULL, .number = ProtosAdvertisementData_FieldNumber_ManufacturerData, - .hasIndex = 4, + .hasIndex = GPBNoHasBit, .offset = (uint32_t)offsetof(ProtosAdvertisementData__storage_, manufacturerData), - .flags = GPBFieldOptional, + .flags = GPBFieldMapKeyInt32, .dataType = GPBDataTypeBytes, }, { diff --git a/lib/gen/flutterblue.pb.dart b/lib/gen/flutterblue.pb.dart index bee8c92d..d9e8af40 100644 --- a/lib/gen/flutterblue.pb.dart +++ b/lib/gen/flutterblue.pb.dart @@ -12,6 +12,36 @@ import 'flutterblue.pbenum.dart'; export 'flutterblue.pbenum.dart'; +class Int32Value extends GeneratedMessage { + static final BuilderInfo _i = new BuilderInfo('Int32Value') + ..a(1, 'value', PbFieldType.O3) + ..hasRequiredFields = false + ; + + Int32Value() : super(); + Int32Value.fromBuffer(List i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromBuffer(i, r); + Int32Value.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromJson(i, r); + Int32Value clone() => new Int32Value()..mergeFromMessage(this); + BuilderInfo get info_ => _i; + static Int32Value create() => new Int32Value(); + static PbList createRepeated() => new PbList(); + static Int32Value getDefault() { + if (_defaultInstance == null) _defaultInstance = new _ReadonlyInt32Value(); + return _defaultInstance; + } + static Int32Value _defaultInstance; + static void $checkItem(Int32Value v) { + if (v is! Int32Value) checkItemFailed(v, 'Int32Value'); + } + + int get value => $_get(0, 0); + set value(int v) { $_setSignedInt32(0, v); } + bool hasValue() => $_has(0); + void clearValue() => clearField(1); +} + +class _ReadonlyInt32Value extends Int32Value with ReadonlyMessageMixin {} + class BluetoothState extends GeneratedMessage { static final BuilderInfo _i = new BuilderInfo('BluetoothState') ..e(1, 'state', PbFieldType.OE, BluetoothState_State.UNKNOWN, BluetoothState_State.valueOf, BluetoothState_State.values) @@ -42,6 +72,42 @@ class BluetoothState extends GeneratedMessage { class _ReadonlyBluetoothState extends BluetoothState with ReadonlyMessageMixin {} +class AdvertisementData_ManufacturerDataEntry extends GeneratedMessage { + static final BuilderInfo _i = new BuilderInfo('AdvertisementData_ManufacturerDataEntry') + ..a(1, 'key', PbFieldType.O3) + ..a>(2, 'value', PbFieldType.OY) + ..hasRequiredFields = false + ; + + AdvertisementData_ManufacturerDataEntry() : super(); + AdvertisementData_ManufacturerDataEntry.fromBuffer(List i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromBuffer(i, r); + AdvertisementData_ManufacturerDataEntry.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromJson(i, r); + AdvertisementData_ManufacturerDataEntry clone() => new AdvertisementData_ManufacturerDataEntry()..mergeFromMessage(this); + BuilderInfo get info_ => _i; + static AdvertisementData_ManufacturerDataEntry create() => new AdvertisementData_ManufacturerDataEntry(); + static PbList createRepeated() => new PbList(); + static AdvertisementData_ManufacturerDataEntry getDefault() { + if (_defaultInstance == null) _defaultInstance = new _ReadonlyAdvertisementData_ManufacturerDataEntry(); + return _defaultInstance; + } + static AdvertisementData_ManufacturerDataEntry _defaultInstance; + static void $checkItem(AdvertisementData_ManufacturerDataEntry v) { + if (v is! AdvertisementData_ManufacturerDataEntry) checkItemFailed(v, 'AdvertisementData_ManufacturerDataEntry'); + } + + int get key => $_get(0, 0); + set key(int v) { $_setSignedInt32(0, v); } + bool hasKey() => $_has(0); + void clearKey() => clearField(1); + + List get value => $_getN(1); + set value(List v) { $_setBytes(1, v); } + bool hasValue() => $_has(1); + void clearValue() => clearField(2); +} + +class _ReadonlyAdvertisementData_ManufacturerDataEntry extends AdvertisementData_ManufacturerDataEntry with ReadonlyMessageMixin {} + class AdvertisementData_ServiceDataEntry extends GeneratedMessage { static final BuilderInfo _i = new BuilderInfo('AdvertisementData_ServiceDataEntry') ..aOS(1, 'key') @@ -81,9 +147,9 @@ class _ReadonlyAdvertisementData_ServiceDataEntry extends AdvertisementData_Serv class AdvertisementData extends GeneratedMessage { static final BuilderInfo _i = new BuilderInfo('AdvertisementData') ..aOS(1, 'localName') - ..a(2, 'txPowerLevel', PbFieldType.O3) + ..a(2, 'txPowerLevel', PbFieldType.OM, Int32Value.getDefault, Int32Value.create) ..aOB(3, 'connectable') - ..a>(4, 'manufacturerData', PbFieldType.OY) + ..pp(4, 'manufacturerData', PbFieldType.PM, AdvertisementData_ManufacturerDataEntry.$checkItem, AdvertisementData_ManufacturerDataEntry.create) ..pp(5, 'serviceData', PbFieldType.PM, AdvertisementData_ServiceDataEntry.$checkItem, AdvertisementData_ServiceDataEntry.create) ..pPS(6, 'serviceUuids') ..hasRequiredFields = false @@ -110,8 +176,8 @@ class AdvertisementData extends GeneratedMessage { bool hasLocalName() => $_has(0); void clearLocalName() => clearField(1); - int get txPowerLevel => $_get(1, 0); - set txPowerLevel(int v) { $_setSignedInt32(1, v); } + Int32Value get txPowerLevel => $_getN(1); + set txPowerLevel(Int32Value v) { setField(2, v); } bool hasTxPowerLevel() => $_has(1); void clearTxPowerLevel() => clearField(2); @@ -120,10 +186,7 @@ class AdvertisementData extends GeneratedMessage { bool hasConnectable() => $_has(2); void clearConnectable() => clearField(3); - List get manufacturerData => $_getN(3); - set manufacturerData(List v) { $_setBytes(3, v); } - bool hasManufacturerData() => $_has(3); - void clearManufacturerData() => clearField(4); + List get manufacturerData => $_getList(3); List get serviceData => $_getList(4); diff --git a/lib/gen/flutterblue.pbjson.dart b/lib/gen/flutterblue.pbjson.dart index ea86f605..7ed1b045 100644 --- a/lib/gen/flutterblue.pbjson.dart +++ b/lib/gen/flutterblue.pbjson.dart @@ -3,6 +3,13 @@ /// // ignore_for_file: non_constant_identifier_names,library_prefixes +const Int32Value$json = const { + '1': 'Int32Value', + '2': const [ + const {'1': 'value', '3': 1, '4': 1, '5': 5, '10': 'value'}, + ], +}; + const BluetoothState$json = const { '1': 'BluetoothState', '2': const [ @@ -28,13 +35,22 @@ const AdvertisementData$json = const { '1': 'AdvertisementData', '2': const [ const {'1': 'local_name', '3': 1, '4': 1, '5': 9, '10': 'localName'}, - const {'1': 'tx_power_level', '3': 2, '4': 1, '5': 5, '10': 'txPowerLevel'}, + const {'1': 'tx_power_level', '3': 2, '4': 1, '5': 11, '6': '.Int32Value', '10': 'txPowerLevel'}, const {'1': 'connectable', '3': 3, '4': 1, '5': 8, '10': 'connectable'}, - const {'1': 'manufacturer_data', '3': 4, '4': 1, '5': 12, '10': 'manufacturerData'}, + const {'1': 'manufacturer_data', '3': 4, '4': 3, '5': 11, '6': '.AdvertisementData.ManufacturerDataEntry', '10': 'manufacturerData'}, const {'1': 'service_data', '3': 5, '4': 3, '5': 11, '6': '.AdvertisementData.ServiceDataEntry', '10': 'serviceData'}, const {'1': 'service_uuids', '3': 6, '4': 3, '5': 9, '10': 'serviceUuids'}, ], - '3': const [AdvertisementData_ServiceDataEntry$json], + '3': const [AdvertisementData_ManufacturerDataEntry$json, AdvertisementData_ServiceDataEntry$json], +}; + +const AdvertisementData_ManufacturerDataEntry$json = const { + '1': 'ManufacturerDataEntry', + '2': const [ + const {'1': 'key', '3': 1, '4': 1, '5': 5, '10': 'key'}, + const {'1': 'value', '3': 2, '4': 1, '5': 12, '10': 'value'}, + ], + '7': const {'7': true}, }; const AdvertisementData_ServiceDataEntry$json = const { diff --git a/lib/src/flutter_blue.dart b/lib/src/flutter_blue.dart index 4015845c..bfab991a 100644 --- a/lib/src/flutter_blue.dart +++ b/lib/src/flutter_blue.dart @@ -66,7 +66,7 @@ class FlutterBlue { StreamController controller; controller = new StreamController( onListen: () { - if(timeout != null) { + if (timeout != null) { new Future.delayed(timeout, () => controller.close()); } }, @@ -106,8 +106,9 @@ class FlutterBlue { StreamController controller; controller = new StreamController( onListen: () { - if(timeout != null) { - new Future.delayed(timeout, () => (!connected) ? controller.close(): null); + if (timeout != null) { + new Future.delayed( + timeout, () => (!connected) ? controller.close() : null); } }, onCancel: () { @@ -119,22 +120,23 @@ class FlutterBlue { await _channel.invokeMethod('connect', request.writeToBuffer()); subscription = device.onStateChanged().listen( - (data) { - if(data == BluetoothDeviceState.connected) { - print('connected!'); - connected = true; - } - controller.add(data); - }, - onError: controller.addError, - onDone: controller.close, - ); + (data) { + if (data == BluetoothDeviceState.connected) { + print('connected!'); + connected = true; + } + controller.add(data); + }, + onError: controller.addError, + onDone: controller.close, + ); yield* controller.stream; } /// Cancels connection to the Bluetooth Device - Future _cancelConnection(BluetoothDevice device) => _channel.invokeMethod('disconnect', device.id.toString()); + Future _cancelConnection(BluetoothDevice device) => + _channel.invokeMethod('disconnect', device.id.toString()); } /// State of the bluetooth adapter. @@ -188,23 +190,28 @@ class ScanResult { class AdvertisementData { final String localName; - final List manufacturerData; - final Map> serviceData; final int txPowerLevel; final bool connectable; + final Map> manufacturerData; + final Map> serviceData; + final List serviceUuids; AdvertisementData( {this.localName, + this.txPowerLevel, + this.connectable, this.manufacturerData, this.serviceData, - this.txPowerLevel, - this.connectable}); + this.serviceUuids}); AdvertisementData.fromProto(protos.AdvertisementData p) : localName = p.localName, - manufacturerData = p.manufacturerData, + txPowerLevel = + (p.txPowerLevel.hasValue()) ? p.txPowerLevel.value : null, + connectable = p.connectable, + manufacturerData = new Map.fromIterable(p.manufacturerData, + key: (v) => v.key, value: (v) => v.value), serviceData = new Map.fromIterable(p.serviceData, key: (v) => v.key, value: (v) => v.value), - txPowerLevel = p.txPowerLevel, - connectable = p.connectable; + serviceUuids = p.serviceUuids; } diff --git a/protos/flutterblue.proto b/protos/flutterblue.proto index ce348989..fe95a34b 100644 --- a/protos/flutterblue.proto +++ b/protos/flutterblue.proto @@ -8,6 +8,14 @@ option java_package = "com.pauldemarco.flutterblue"; option java_outer_classname = "Protos"; option objc_class_prefix = "Protos"; +// Wrapper message for `int32`. +// +// Allows for nullability of fields in messages +message Int32Value { + // The int32 value. + int32 value = 1; +} + message BluetoothState { enum State { UNKNOWN = 0; @@ -23,9 +31,9 @@ message BluetoothState { message AdvertisementData { string local_name = 1; - int32 tx_power_level = 2; + Int32Value tx_power_level = 2; bool connectable = 3; - bytes manufacturer_data = 4; + map manufacturer_data = 4; // Map of manufacturers to their data map service_data = 5; // Map of service UUIDs to their data. repeated string service_uuids = 6; }