Skip to content

Commit

Permalink
make filter for services optional and add parameter requireLocationSe…
Browse files Browse the repository at this point in the history
…rviceEnabled as scan parameter
  • Loading branch information
remonh87 authored Mar 9, 2020
1 parent 9c4b933 commit d723ec1
Show file tree
Hide file tree
Showing 25 changed files with 391 additions and 368 deletions.
15 changes: 13 additions & 2 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
## 2.0.0
This version is introducing the following breaking changes:
* Add parameter requireLocationServicesEnabled to Ble scan that can toggle requirement of location services to be running
* Make filter on advertising services optional and add possibility to filter on more services
* Add manufacturer specific data to scanresults
* Remove global set error handler java

Other improvements:
* Improvements for example app
* Add support for Flutter hot reload on iOS

## 1.1.0
* Add RSSI value to discovered device results
* Improved parsing of UUIDs
* Migrated to latest Android plugin binding
* Improve parsing of UUIDs
* Migrate to latest Android plugin binding
* Small improvements

## 1.0.2
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ The reactive BLE lib supports the following:
Discovering BLE devices should be done like this:

```dart
reactivebleclient.scanForDevices(withService: uuid, scanMode: ScanMode.lowLatency).listen((device) {
reactivebleclient.scanForDevices(withServices: [serviceId], scanMode: ScanMode.lowLatency).listen((device) {
//code for handling results
}, onError: () {
//code for handling error
});
```

The `withService` parameter is required. The parameter `scanMode` is only used on Android and follows the conventions described on [ScanSettings](https://developer.android.com/reference/android/bluetooth/le/ScanSettings#SCAN_MODE_BALANCED) Android reference page. If `scanMode` is omitted the balanced scan mode will be used.
The `withServices` parameter specifies the advertised service IDs to look for. If an empty list is passed, all the advertising devices will be reported. The parameter `scanMode` is only used on Android and follows the conventions described on [ScanSettings](https://developer.android.com/reference/android/bluetooth/le/ScanSettings#SCAN_MODE_BALANCED) Android reference page. If `scanMode` is omitted the balanced scan mode will be used.

### Establishing connection

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface BleClient {
val connectionUpdateSubject: BehaviorSubject<com.signify.hue.flutterreactiveble.ble.ConnectionUpdate>

fun initializeClient()
fun scanForDevices(service: ParcelUuid, scanMode: ScanMode): Observable<com.signify.hue.flutterreactiveble.ble.ScanInfo>
fun scanForDevices(services: List<ParcelUuid>, scanMode: ScanMode, requireLocationServicesEnabled: Boolean): Observable<com.signify.hue.flutterreactiveble.ble.ScanInfo>
fun connectToDevice(deviceId: String, timeout: Duration)
fun disconnectDevice(deviceId: String)
fun disconnectAllDevices()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ enum class BleStatus(val code: Int) {
UNSUPPORTED(code = 1),
UNAUTHORIZED(code = 2),
POWERED_OFF(code = 3),
DISCOVERY_DISABLED(code = 4),
LOCATION_SERVICES_DISABLED(code = 4),
READY(code = 5)
}

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,33 @@ class ScanDevicesHandler(private val bleClient: com.signify.hue.flutterreactiveb
private var scanParameters: ScanParameters? = null

override fun onListen(objectSink: Any?, eventSink: EventChannel.EventSink?) {

eventSink?.let {
scanDevicesSink = eventSink
startDeviceScan()
startDeviceScan()
}
}

override fun onCancel(objectSink: Any?) {
Timber.d("Scanfordevices cancelled called")
Timber.d("Scanning canceled")
stopDeviceScan()
scanDevicesSink = null
}

private fun startDeviceScan() {
scanParameters?.let { params ->
scanForDevicesDisposable = bleClient.scanForDevices(params.filter, params.mode)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ scanResult ->
handleDeviceScanResult(converter.convertScanInfo(scanResult))
},
{ throwable ->
Timber.d("Error while scanning for devices: ${throwable.message}")
handleDeviceScanResult(converter.convertScanErrorInfo(throwable.message))
}
)
} ?: handleDeviceScanResult(converter.convertScanErrorInfo("Scanparameters are not being set"))
scanParameters?.let { params ->
scanForDevicesDisposable = bleClient.scanForDevices(params.filter, params.mode, params.locationServiceIsMandatory)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ scanResult ->
handleDeviceScanResult(converter.convertScanInfo(scanResult))
},
{ throwable ->
Timber.d("Error while scanning for devices: ${throwable.message}")
handleDeviceScanResult(converter.convertScanErrorInfo(throwable.message))
}
)
}
?: handleDeviceScanResult(converter.convertScanErrorInfo("Scanning parameters are not set"))
}

fun stopDeviceScan() {
Expand All @@ -54,16 +54,16 @@ class ScanDevicesHandler(private val bleClient: com.signify.hue.flutterreactiveb
}

fun prepareScan(scanMessage: pb.ScanForDevicesRequest) {
val scanUuid = scanMessage.serviceUuid.data.toByteArray()
val filter = ParcelUuid(UuidConverter().uuidFromByteArray(scanUuid))
val filter = scanMessage.serviceUuidsList
.map { ParcelUuid(UuidConverter().uuidFromByteArray(it.data.toByteArray())) }
val scanMode = createScanMode(scanMessage.scanMode)

scanParameters = ScanParameters(filter, scanMode)
scanParameters = ScanParameters(filter, scanMode, scanMessage.requireLocationServicesEnabled)
}

private fun handleDeviceScanResult(discoveryMessage: pb.DeviceScanInfo) {
scanDevicesSink?.success(discoveryMessage.toByteArray())
}
}

private data class ScanParameters(val filter: ParcelUuid, val mode: ScanMode)
private data class ScanParameters(val filter: List<ParcelUuid>, val mode: ScanMode, val locationServiceIsMandatory: Boolean)
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ import com.polidea.rxandroidble2.RxBleClient.State.READY
import com.signify.hue.flutterreactiveble.ble.BleStatus
import com.signify.hue.flutterreactiveble.ble.ConnectionPriority

fun RxBleClient.State.toBleState(): com.signify.hue.flutterreactiveble.ble.BleStatus =
fun RxBleClient.State.toBleState(): BleStatus =
when (this) {
BLUETOOTH_NOT_AVAILABLE -> com.signify.hue.flutterreactiveble.ble.BleStatus.UNSUPPORTED
LOCATION_PERMISSION_NOT_GRANTED -> com.signify.hue.flutterreactiveble.ble.BleStatus.UNAUTHORIZED
BLUETOOTH_NOT_ENABLED -> com.signify.hue.flutterreactiveble.ble.BleStatus.POWERED_OFF
LOCATION_SERVICES_NOT_ENABLED -> com.signify.hue.flutterreactiveble.ble.BleStatus.DISCOVERY_DISABLED
READY -> com.signify.hue.flutterreactiveble.ble.BleStatus.READY
BLUETOOTH_NOT_AVAILABLE -> BleStatus.UNSUPPORTED
LOCATION_PERMISSION_NOT_GRANTED -> BleStatus.UNAUTHORIZED
BLUETOOTH_NOT_ENABLED -> BleStatus.POWERED_OFF
LOCATION_SERVICES_NOT_ENABLED -> BleStatus.LOCATION_SERVICES_DISABLED
READY -> BleStatus.READY
}

fun Int.toConnectionPriority() =
when (this) {
0 -> com.signify.hue.flutterreactiveble.ble.ConnectionPriority.BALANCED
1 -> com.signify.hue.flutterreactiveble.ble.ConnectionPriority.HIGH_PERFORMACE
2 -> com.signify.hue.flutterreactiveble.ble.ConnectionPriority.LOW_POWER
else -> com.signify.hue.flutterreactiveble.ble.ConnectionPriority.BALANCED
0 -> ConnectionPriority.BALANCED
1 -> ConnectionPriority.HIGH_PERFORMACE
2 -> ConnectionPriority.LOW_POWER
else -> ConnectionPriority.BALANCED
}
5 changes: 3 additions & 2 deletions example/lib/src/ble/ble_scanner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ class BleScanner implements ReactiveState<BleScannerState> {
@override
Stream<BleScannerState> get state => _stateStreamController.stream;

void startScan(Uuid uuid) {
void startScan(List<Uuid> serviceIds) {
_devices.clear();
_subscription?.cancel();
_subscription = _ble.scanForDevices(withService: uuid).listen((device) {
_subscription =
_ble.scanForDevices(withServices: serviceIds).listen((device) {
final knownDeviceIndex = _devices.indexWhere((d) => d.id == device.id);
if (knownDeviceIndex >= 0) {
_devices[knownDeviceIndex] = device;
Expand Down
4 changes: 2 additions & 2 deletions example/lib/src/ui/ble_status_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class BleStatusScreen extends StatelessWidget {
return "Authorize the FlutterReactiveBle example app to use Bluetooth and location";
case BleStatus.poweredOff:
return "Bluetooth is powered off on your device turn it on";
case BleStatus.discoveryDisabled:
return "Bluetooth discovery is disabled turn on location services";
case BleStatus.locationServicesDisabled:
return "Enable location services";
case BleStatus.ready:
return "Bluetooth is up and running";
default:
Expand Down
18 changes: 11 additions & 7 deletions example/lib/src/ui/device_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class _DeviceList extends StatefulWidget {
assert(stopScan != null);

final BleScannerState scannerState;
final void Function(Uuid) startScan;
final void Function(List<Uuid>) startScan;
final VoidCallback stopScan;

@override
Expand All @@ -53,17 +53,21 @@ class _DeviceListState extends State<_DeviceList> {

bool _isValidUuidInput() {
final uuidText = _uuidController.text;
try {
Uuid.parse(uuidText);
if (uuidText.isEmpty) {
return true;
} on Exception {
return false;
} else {
try {
Uuid.parse(uuidText);
return true;
} on Exception {
return false;
}
}
}

void _startScanning() {
final uuid = Uuid.parse(_uuidController.text);
widget.startScan(uuid);
final text = _uuidController.text;
widget.startScan(text.isEmpty ? [] : [Uuid.parse(_uuidController.text)]);
}

@override
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ packages:
path: ".."
relative: true
source: path
version: "1.1.0"
version: "2.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_reactive_ble_example
description: Demonstrates how to use the flutter_reactive_ble plugin.
version: 1.1.0
version: 2.0.0
publish_to: 'none'

environment:
Expand Down
84 changes: 23 additions & 61 deletions ios/Classes/BleData/bledata.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,15 @@ struct ScanForDevicesRequest {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.

var serviceUuid: Uuid {
get {return _storage._serviceUuid ?? Uuid()}
set {_uniqueStorage()._serviceUuid = newValue}
}
/// Returns true if `serviceUuid` has been explicitly set.
var hasServiceUuid: Bool {return _storage._serviceUuid != nil}
/// Clears the value of `serviceUuid`. Subsequent reads from it will return its default value.
mutating func clearServiceUuid() {_uniqueStorage()._serviceUuid = nil}
var serviceUuids: [Uuid] = []

var scanMode: Int32 {
get {return _storage._scanMode}
set {_uniqueStorage()._scanMode = newValue}
}
var scanMode: Int32 = 0

var requireLocationServicesEnabled: Bool = false

var unknownFields = SwiftProtobuf.UnknownStorage()

init() {}

fileprivate var _storage = _StorageClass.defaultInstance
}

struct DeviceScanInfo {
Expand Down Expand Up @@ -579,67 +569,39 @@ struct GenericFailure {
extension ScanForDevicesRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "ScanForDevicesRequest"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "serviceUuid"),
1: .same(proto: "serviceUuids"),
2: .same(proto: "scanMode"),
3: .same(proto: "requireLocationServicesEnabled"),
]

fileprivate class _StorageClass {
var _serviceUuid: Uuid? = nil
var _scanMode: Int32 = 0

static let defaultInstance = _StorageClass()

private init() {}

init(copying source: _StorageClass) {
_serviceUuid = source._serviceUuid
_scanMode = source._scanMode
}
}

fileprivate mutating func _uniqueStorage() -> _StorageClass {
if !isKnownUniquelyReferenced(&_storage) {
_storage = _StorageClass(copying: _storage)
}
return _storage
}

mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
_ = _uniqueStorage()
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularMessageField(value: &_storage._serviceUuid)
case 2: try decoder.decodeSingularInt32Field(value: &_storage._scanMode)
default: break
}
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeRepeatedMessageField(value: &self.serviceUuids)
case 2: try decoder.decodeSingularInt32Field(value: &self.scanMode)
case 3: try decoder.decodeSingularBoolField(value: &self.requireLocationServicesEnabled)
default: break
}
}
}

func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
if let v = _storage._serviceUuid {
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
}
if _storage._scanMode != 0 {
try visitor.visitSingularInt32Field(value: _storage._scanMode, fieldNumber: 2)
}
if !self.serviceUuids.isEmpty {
try visitor.visitRepeatedMessageField(value: self.serviceUuids, fieldNumber: 1)
}
if self.scanMode != 0 {
try visitor.visitSingularInt32Field(value: self.scanMode, fieldNumber: 2)
}
if self.requireLocationServicesEnabled != false {
try visitor.visitSingularBoolField(value: self.requireLocationServicesEnabled, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}

static func ==(lhs: ScanForDevicesRequest, rhs: ScanForDevicesRequest) -> Bool {
if lhs._storage !== rhs._storage {
let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in
let _storage = _args.0
let rhs_storage = _args.1
if _storage._serviceUuid != rhs_storage._serviceUuid {return false}
if _storage._scanMode != rhs_storage._scanMode {return false}
return true
}
if !storagesAreEqual {return false}
}
if lhs.serviceUuids != rhs.serviceUuids {return false}
if lhs.scanMode != rhs.scanMode {return false}
if lhs.requireLocationServicesEnabled != rhs.requireLocationServicesEnabled {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
Expand Down
12 changes: 3 additions & 9 deletions ios/Classes/Plugin/PluginController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import var CoreBluetooth.CBAdvertisementDataManufacturerDataKey
final class PluginController {

struct Scan {
let service: CBUUID
let services: [CBUUID]
}

private var central: Central?
Expand Down Expand Up @@ -145,13 +145,7 @@ final class PluginController {

assert(!central.isScanning)

guard args.hasServiceUuid && !args.serviceUuid.data.isEmpty
else {
completion(.failure(PluginError.invalidMethodCall(method: name, details: "\"serviceUuid\" is required").asFlutterError))
return
}

scan = StreamingTask(parameters: .init(service: CBUUID(data: args.serviceUuid.data)))
scan = StreamingTask(parameters: .init(services: args.serviceUuids.map({ uuid in CBUUID(data: uuid.data) })))

completion(.success(nil))
}
Expand All @@ -165,7 +159,7 @@ final class PluginController {

self.scan = scan.with(sink: sink)

central.scanForDevices(with: [scan.parameters.service])
central.scanForDevices(with: scan.parameters.services)

return nil
}
Expand Down
Loading

0 comments on commit d723ec1

Please sign in to comment.