From 416ce60ce745382a9e6f5713e657224b8462d12a Mon Sep 17 00:00:00 2001 From: b3b Date: Sat, 11 Dec 2021 14:16:22 +0300 Subject: [PATCH] add `start_scan` settings and filters arguments #25 --- able/android/dispatcher.py | 15 ++- able/dispatcher.py | 2 +- able/src/org/able/BLE.java | 9 +- tests/notebooks/.gitignore | 3 + tests/notebooks/init.md | 43 ++++++++ tests/notebooks/run | 22 ++++ tests/notebooks/run_all_tests | 4 + tests/notebooks/test_basic.expected | 26 +++++ tests/notebooks/test_basic.md | 74 ++++++++++++++ tests/notebooks/test_scan_filters.expected | 18 ++++ tests/notebooks/test_scan_filters.md | 102 +++++++++++++++++++ tests/notebooks/test_scan_settings.expected | 27 +++++ tests/notebooks/test_scan_settings.md | 105 ++++++++++++++++++++ 13 files changed, 445 insertions(+), 5 deletions(-) create mode 100644 tests/notebooks/.gitignore create mode 100644 tests/notebooks/init.md create mode 100755 tests/notebooks/run create mode 100755 tests/notebooks/run_all_tests create mode 100644 tests/notebooks/test_basic.expected create mode 100644 tests/notebooks/test_basic.md create mode 100644 tests/notebooks/test_scan_filters.expected create mode 100644 tests/notebooks/test_scan_filters.md create mode 100644 tests/notebooks/test_scan_settings.expected create mode 100644 tests/notebooks/test_scan_settings.md diff --git a/able/android/dispatcher.py b/able/android/dispatcher.py index 7c088be..18fb577 100644 --- a/able/android/dispatcher.py +++ b/able/android/dispatcher.py @@ -14,6 +14,8 @@ from able.dispatcher import BluetoothDispatcherBase +ArrayList = autoclass('java.util.ArrayList') + Activity = autoclass('android.app.Activity') BLE = autoclass('org.able.BLE') @@ -21,6 +23,11 @@ BluetoothDevice = autoclass('android.bluetooth.BluetoothDevice') BluetoothGattDescriptor = autoclass('android.bluetooth.BluetoothGattDescriptor') +ScanFilter = autoclass('android.bluetooth.le.ScanFilter') +ScanFilterBuilder = autoclass('android.bluetooth.le.ScanFilter$Builder') +ScanSettings = autoclass('android.bluetooth.le.ScanSettings') +ScanSettingsBuilder = autoclass('android.bluetooth.le.ScanSettings$Builder') + ENABLE_NOTIFICATION_VALUE = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE ENABLE_INDICATION_VALUE = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE DISABLE_NOTIFICATION_VALUE = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE @@ -89,8 +96,12 @@ def _request_runtime_permissions(self): @require_bluetooth_enabled @require_runtime_permissions - def start_scan(self): - self._ble.startScan(self.enable_ble_code) + def start_scan(self, filters=None, settings=None): + if not filters: + filters = ArrayList() + if not settings: + settings = ScanSettingsBuilder().build() + self._ble.startScan(self.enable_ble_code, filters, settings) def stop_scan(self): self._ble.stopScan() diff --git a/able/dispatcher.py b/able/dispatcher.py index 12af34c..17396cb 100644 --- a/able/dispatcher.py +++ b/able/dispatcher.py @@ -104,7 +104,7 @@ def set_queue_timeout(self, timeout): self.queue_timeout = timeout self.queue.set_timeout(timeout) - def start_scan(self): + def start_scan(self, filters=None, settings=None): """Start a scan for devices. Ask for runtime permission to access location. Start a system activity that allows the user to turn on Bluetooth, diff --git a/able/src/org/able/BLE.java b/able/src/org/able/BLE.java index 49a63a2..034ff4c 100644 --- a/able/src/org/able/BLE.java +++ b/able/src/org/able/BLE.java @@ -17,6 +17,9 @@ import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanSettings; + import android.os.Handler; import android.util.Log; import java.util.List; @@ -87,7 +90,9 @@ public BluetoothGatt getGatt() { return mBluetoothGatt; } - public void startScan(int EnableBtCode) { + public void startScan(int EnableBtCode, + List filters, + ScanSettings settings) { Log.d(TAG, "startScan"); BluetoothAdapter adapter = getAdapter(EnableBtCode); if (adapter != null) { @@ -97,7 +102,7 @@ public void startScan(int EnableBtCode) { } if (mBluetoothLeScanner != null) { mScanning = false; - mBluetoothLeScanner.startScan(mScanCallback); + mBluetoothLeScanner.startScan(filters, settings, mScanCallback); } else { showError("Could not get BLE Scanner object."); mPython.on_scan_started(false); diff --git a/tests/notebooks/.gitignore b/tests/notebooks/.gitignore new file mode 100644 index 0000000..55f4e5e --- /dev/null +++ b/tests/notebooks/.gitignore @@ -0,0 +1,3 @@ +there.env +*.asciidoc +*.ipynb diff --git a/tests/notebooks/init.md b/tests/notebooks/init.md new file mode 100644 index 0000000..caf5c02 --- /dev/null +++ b/tests/notebooks/init.md @@ -0,0 +1,43 @@ +--- +jupyter: + jupytext: + formats: ipynb,md + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.11.2 + kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +```python +from time import sleep + +from dataclasses import dataclass, field +from typing import List + +%load_ext pythonhere +%connect-there +``` + +```python +%%there +from able.android.dispatcher import ( + ArrayList, + BluetoothDispatcher, + ScanFilterBuilder, + ScanSettingsBuilder +) +``` + +```python +%%there +@dataclass +class Results: + started: bool = None + completed: bool = None + devices: List = field(default_factory=lambda: []) +``` diff --git a/tests/notebooks/run b/tests/notebooks/run new file mode 100755 index 0000000..19d701a --- /dev/null +++ b/tests/notebooks/run @@ -0,0 +1,22 @@ +#!/bin/bash +set -ex + +name="$1" +command="${2}" +command="${command:=test}" + + +cat "${name}.md" | + jupytext --execute --to ipynb | + jupyter nbconvert --stdin --no-input --to asciidoc --output "${name}" + +cat "${name}".asciidoc + +if [ "${command}" = "test" ]; then + diff "${name}.asciidoc" "${name}.expected" +elif [ "${command}" = "record" ]; then + cp "${name}".asciidoc "${name}".expected +else + echo "Unknown command: ${command}" + exit 1 +fi diff --git a/tests/notebooks/run_all_tests b/tests/notebooks/run_all_tests new file mode 100755 index 0000000..7906d87 --- /dev/null +++ b/tests/notebooks/run_all_tests @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +for name in test_*.md; do ./run "${name%%.*}"; done diff --git a/tests/notebooks/test_basic.expected b/tests/notebooks/test_basic.expected new file mode 100644 index 0000000..bd4bd66 --- /dev/null +++ b/tests/notebooks/test_basic.expected @@ -0,0 +1,26 @@ +[[setup]] += Setup + +[[run-ble-devices-scan]] += Run BLE devices scan + + +---- +Started: None Completed: None +---- + +[[check-that-scan-started-and-completed]] += Check that scan started and completed + + +---- +Started: 1 Completed: 1 +---- + +[[check-that-testing-device-was-discovered]] += Check that testing device was discovered + + +---- +True +---- diff --git a/tests/notebooks/test_basic.md b/tests/notebooks/test_basic.md new file mode 100644 index 0000000..4e16ed9 --- /dev/null +++ b/tests/notebooks/test_basic.md @@ -0,0 +1,74 @@ +--- +jupyter: + jupytext: + formats: ipynb,md + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.11.2 + kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Setup + +```python +%run init.ipynb +``` + +```python +%%there +class BLE(BluetoothDispatcher): + + def on_scan_started(self, success): + results.started = success + + def on_scan_completed(self): + results.completed = 1 + + def on_device(self, device, rssi, advertisement): + results.devices.append(device) + +ble = BLE() +``` + +# Run BLE devices scan + +```python +%%there +results = Results() +print(f"Started: {results.started} Completed: {results.completed}") +ble.start_scan() +``` + +```python +sleep(10) +``` + +```python +%%there +ble.stop_scan() +``` + +```python +sleep(2) +``` + +# Check that scan started and completed + +```python +%%there +print(f"Started: {results.started} Completed: {results.completed}") +``` + +# Check that testing device was discovered + +```python +%%there +print( + "KivyBLETest" in [dev.getName() for dev in results.devices] +) +``` diff --git a/tests/notebooks/test_scan_filters.expected b/tests/notebooks/test_scan_filters.expected new file mode 100644 index 0000000..317182e --- /dev/null +++ b/tests/notebooks/test_scan_filters.expected @@ -0,0 +1,18 @@ +[[setup]] += Setup + +[[test-device-is-found-with-filter-by-name]] += Test device is found with filter by name + + +---- +{'KivyBLETest'} +---- + +[[test-device-is-not-found-filtered-out-by-name]] += Test device is not found: filtered out by name + + +---- +[] +---- diff --git a/tests/notebooks/test_scan_filters.md b/tests/notebooks/test_scan_filters.md new file mode 100644 index 0000000..5a600aa --- /dev/null +++ b/tests/notebooks/test_scan_filters.md @@ -0,0 +1,102 @@ +--- +jupyter: + jupytext: + formats: ipynb,md + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.11.2 + kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Setup + +```python +%run init.ipynb +``` + +```python +%%there +class BLE(BluetoothDispatcher): + + def on_scan_started(self, success): + results.started = success + + def on_scan_completed(self): + results.completed = 1 + + def on_device(self, device, rssi, advertisement): + results.devices.append(device) + +ble = BLE() +``` + +# Test device is found with filter by name + +```python +%%there +results = Results() + +filters = ArrayList() +filters.add( + ScanFilterBuilder().setDeviceName("KivyBLETest").build() +) + +ble.start_scan(filters=filters) +``` + +```python +sleep(10) +``` + +```python +%%there +ble.stop_scan() +``` + +```python +sleep(2) +``` + +```python +%%there +print(set([dev.getName() for dev in results.devices])) +``` + +# Test device is not found: filtered out by name + +```python +%%there +results = Results() + +filters = ArrayList() +filters.add( + ScanFilterBuilder().setDeviceName("No-such-device-8458e2e35158").build() +) + +ble.start_scan(filters=filters) +``` + +```python +sleep(10) +``` + +```python +%%there +ble.stop_scan() +``` + +```python +sleep(2) +``` + +```python +%%there +ble.stop_scan() + +print(results.devices) +``` diff --git a/tests/notebooks/test_scan_settings.expected b/tests/notebooks/test_scan_settings.expected new file mode 100644 index 0000000..8f3caef --- /dev/null +++ b/tests/notebooks/test_scan_settings.expected @@ -0,0 +1,27 @@ +[[setup]] += Setup + +[[run-scan_mode_low_power]] += Run SCAN_MODE_LOW_POWER + + +---- +True +---- + +[[run-scan_mode_low_latency]] += Run SCAN_MODE_LOW_LATENCY + + +---- +True +---- + +[[check-that-received-advertisement-count-is-greater-with-scan_mode_low_latency]] += Check that received advertisement count is greater with +SCAN_MODE_LOW_LATENCY + + +---- +True +---- diff --git a/tests/notebooks/test_scan_settings.md b/tests/notebooks/test_scan_settings.md new file mode 100644 index 0000000..69f7eae --- /dev/null +++ b/tests/notebooks/test_scan_settings.md @@ -0,0 +1,105 @@ +--- +jupyter: + jupytext: + formats: ipynb,md + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.11.2 + kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Setup + +```python +%run init.ipynb +``` + +```python +%%there +class BLE(BluetoothDispatcher): + + def on_scan_started(self, success): + results.started = success + + def on_scan_completed(self): + results.completed = 1 + + def on_device(self, device, rssi, advertisement): + results.devices.append(device) + +def get_advertisemnt_count(): + return len([dev for dev in results.devices if dev.getName() == "KivyBLETest"]) + +ble = BLE() +``` + +# Run SCAN_MODE_LOW_POWER + +```python +%%there +results = Results() +ble.start_scan( + settings=ScanSettingsBuilder().setScanMode(ScanSettings.SCAN_MODE_LOW_POWER).build() +) +``` + +```python +sleep(20) +``` + +```python +%%there +ble.stop_scan() +``` + +```python +sleep(2) +``` + +```python +%%there +low_power_advertisement_count = get_advertisemnt_count() +print(low_power_advertisement_count > 0) +``` + +# Run SCAN_MODE_LOW_LATENCY + +```python +%%there +results = Results() + +ble.start_scan( + settings=ScanSettingsBuilder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build() +) +``` + +```python +sleep(20) +``` + +```python +%%there +ble.stop_scan() +``` + +```python +sleep(2) +``` + +```python +%%there +low_latency_advertisement_count = get_advertisemnt_count() +print(low_latency_advertisement_count > 0) +``` + +# Check that received advertisement count is greater with SCAN_MODE_LOW_LATENCY + +```python +%%there +print(low_latency_advertisement_count - low_power_advertisement_count > 2) +```