Skip to content

Commit a948598

Browse files
Revise eddystone scanner (Bugfix) (#2260)
* removed older beacontools removed older beacontools * revised eddystone scanner revised eddystone scanner scripts for a bluetooth controller without extended advertising support * update eddystone unit tests update eddystone unit tests * fix eddystone_scanner fix eddystone_scanner * fix coding style issue fix coding style issue
1 parent fb7b08d commit a948598

29 files changed

Lines changed: 843 additions & 1667 deletions

checkbox-support/checkbox_support/scripts/eddystone_scanner.py

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
EddystoneURLFrame,
2929
)
3030
from checkbox_support.helpers.timeout import timeout
31+
from checkbox_support.helpers.retry import retry
3132
from checkbox_support.interactive_cmd import InteractiveCommand
3233

3334

@@ -36,38 +37,50 @@ def init_bluetooth():
3637
with InteractiveCommand("bluetoothctl") as btctl:
3738
btctl.writeline("power on")
3839
time.sleep(3)
39-
btctl.writeline("scan on")
40-
time.sleep(3)
4140
btctl.writeline("exit")
4241
btctl.kill()
4342

4443

45-
def beacon_scan(hci_device):
46-
TIMEOUT = 10
47-
44+
@retry(3, delay=10)
45+
def beacon_scan(hci_device, debug=False):
46+
TIMEOUT = 60
47+
report_type = None
4848
beacon_mac = beacon_rssi = beacon_packet = ""
4949

50-
def callback(bt_addr, rssi, packet, additional_info):
51-
nonlocal beacon_mac, beacon_rssi, beacon_packet
52-
beacon_mac, beacon_rssi, beacon_packet = bt_addr, rssi, packet
50+
def callback(sub_event, bt_addr, rssi, packet, additional_info):
51+
nonlocal beacon_mac, beacon_rssi, beacon_packet, report_type
52+
report_type, beacon_mac, beacon_rssi, beacon_packet = (
53+
sub_event,
54+
bt_addr,
55+
rssi,
56+
packet,
57+
)
5358

5459
scanner = BeaconScanner(
55-
callback, bt_device_id=hci_device, packet_filter=EddystoneURLFrame
60+
callback,
61+
bt_device_id=hci_device,
62+
packet_filter=EddystoneURLFrame,
63+
debug=debug,
5664
)
5765

5866
scanner.start()
5967
start = time.time()
6068
while not beacon_packet and time.time() - start < TIMEOUT:
61-
time.sleep(1)
69+
time.sleep(0.5)
6270
scanner.stop()
6371
if beacon_packet:
6472
print(
65-
"Eddystone beacon detected: URL: {} <mac: {}> "
66-
"<rssi: {}>".format(beacon_packet.url, beacon_mac, beacon_rssi)
73+
"Eddystone beacon detected: [Adv Report Type: {}({})] "
74+
"URL: {} <mac: {}> <rssi: {}>".format(
75+
report_type.name,
76+
report_type.value,
77+
beacon_packet.url,
78+
beacon_mac,
79+
beacon_rssi,
80+
)
6781
)
6882
return 0
69-
print("No EddyStone URL advertisement detected!")
70-
return 1
83+
raise RuntimeError("No EddyStone URL advertisement detected!")
7184

7285

7386
@timeout(60 * 10) # 10 minutes timeout
@@ -85,6 +98,11 @@ def main(argv=None):
8598
default="hci0",
8699
help="Select the hciX device to use " "(default hci0).",
87100
)
101+
parser.add_argument(
102+
"--debug",
103+
action="store_true",
104+
default=False,
105+
)
88106
args = parser.parse_args(argv)
89107

90108
try:
@@ -93,23 +111,7 @@ def main(argv=None):
93111
print("Bad device argument, defaulting to hci0")
94112
hci_device = 0
95113

96-
# Newer bluetooth controllers and bluez versions allow extended commands
97-
# supported by newer versions of beacontools. But with older controllers,
98-
# especially when running on bionic, core18, bluez < 5.51, etc. they
99-
# only work correctly with legacy commands, and need an older version
100-
# of beacontools to work properly.
101-
# Try the newest one first, then the older one if that doesn't work
102-
rc = beacon_scan(hci_device)
103-
if rc:
104-
print("Trying again with older beacontools version...")
105-
global BeaconScanner, EddystoneURLFrame
106-
from checkbox_support.vendor.beacontools_2_0_2 import (
107-
BeaconScanner,
108-
EddystoneURLFrame,
109-
)
110-
111-
rc = beacon_scan(hci_device)
112-
return rc
114+
return beacon_scan(hci_device, args.debug)
113115

114116

115117
if __name__ == "__main__":

checkbox-support/checkbox_support/tests/test_eddystone_scanner.py

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,69 @@
1717
#
1818
# You should have received a copy of the GNU General Public License
1919
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
import argparse
2021
import unittest
2122
from unittest.mock import patch, MagicMock
2223

24+
from checkbox_support.helpers.timeout import mock_timeout
25+
from checkbox_support.helpers.retry import mock_retry
2326
from checkbox_support.scripts import eddystone_scanner
27+
from checkbox_support.vendor.beacontools.const import MetaEventReportTypeEnum
2428

2529

2630
class TestEddystoneScanner(unittest.TestCase):
31+
@patch("builtins.print")
2732
@patch("checkbox_support.scripts.eddystone_scanner.BeaconScanner")
28-
def test_beacon_scan_ok(self, mock_beacon_scanner):
33+
def test_beacon_scan_ok(self, mock_beacon_scanner, mock_print):
2934
class BeaconScanner:
3035
def __init__(self, callback, *args, **kwargs):
3136
self.callback = callback
3237

3338
def start(self):
3439
packet = MagicMock(url="packet_url")
35-
self.callback("address", "rssi", packet, None)
40+
self.callback(
41+
MetaEventReportTypeEnum.LE_ADVERTISING_REPORT,
42+
"address",
43+
"rssi",
44+
packet,
45+
None,
46+
)
3647

3748
def stop(self):
3849
pass
3950

4051
mock_beacon_scanner.side_effect = BeaconScanner
41-
self.assertEqual(eddystone_scanner.beacon_scan("1"), 0)
52+
self.assertEqual(eddystone_scanner.beacon_scan("1", True), 0)
53+
call_args = mock_beacon_scanner.call_args
54+
_, kwargs = call_args
55+
56+
self.assertEqual(kwargs["bt_device_id"], "1")
57+
self.assertEqual(kwargs["debug"], True)
58+
mock_print.assert_called_with(
59+
(
60+
"Eddystone beacon detected: [Adv Report Type: {}({})] "
61+
"URL: packet_url <mac: address> <rssi: rssi>"
62+
).format(
63+
MetaEventReportTypeEnum.LE_ADVERTISING_REPORT.name,
64+
MetaEventReportTypeEnum.LE_ADVERTISING_REPORT.value,
65+
)
66+
)
4267

68+
@mock_retry()
4369
@patch("checkbox_support.scripts.eddystone_scanner.BeaconScanner")
4470
@patch("time.time")
45-
@patch("time.sleep")
46-
def test_beacon_scan_fail(
47-
self, mock_sleep, mock_time, mock_beacon_scanner
48-
):
49-
mock_time.side_effect = [0, 60 * 60 * 60] # 60h, trigger timeout
50-
self.assertEqual(eddystone_scanner.beacon_scan("1"), 1)
71+
def test_beacon_scan_fail(self, mock_time, mock_beacon_scanner):
72+
mock_time.side_effect = [0, 1, 60 * 60 * 60] # 60h, trigger timeout
73+
with self.assertRaises(RuntimeError):
74+
eddystone_scanner.beacon_scan("1")
5175

52-
@patch("checkbox_support.scripts.eddystone_scanner.BeaconScanner")
53-
@patch("checkbox_support.scripts.eddystone_scanner.InteractiveCommand")
54-
@patch("time.sleep")
76+
@mock_timeout()
77+
@patch.object(argparse.ArgumentParser, "parse_args")
78+
@patch.object(argparse.ArgumentParser, "add_argument")
79+
@patch("checkbox_support.scripts.eddystone_scanner.beacon_scan")
80+
@patch("checkbox_support.scripts.eddystone_scanner.init_bluetooth")
5581
def test_main_ok(
56-
self, mock_sleep, mock_interactive_command, mock_beacon_scanner
82+
self, mock_init, mock_beacon_scan, mock_add_arg, mock_parse_args
5783
):
5884
class BeaconScanner:
5985
def __init__(self, callback, *args, **kwargs):
@@ -66,5 +92,16 @@ def start(self):
6692
def stop(self):
6793
pass
6894

69-
mock_beacon_scanner.side_effect = BeaconScanner
70-
self.assertEqual(eddystone_scanner.main(["--device", "hc1"]), 0)
95+
mock_parse_args.return_value = argparse.Namespace(
96+
device="hci1", debug=True
97+
)
98+
mock_beacon_scan.return_value = 0
99+
100+
input_args = ["--device", "hci1", "--debug"]
101+
self.assertEqual(eddystone_scanner.main(input_args), 0)
102+
103+
mock_add_arg.assert_called_with(
104+
"--debug", action="store_true", default=False
105+
)
106+
mock_init.assert_called_once_with()
107+
mock_beacon_scan.assert_called_with(1, True)

0 commit comments

Comments
 (0)