-
Notifications
You must be signed in to change notification settings - Fork 2
/
android_ble_scanner.py
190 lines (162 loc) · 6.59 KB
/
android_ble_scanner.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
"""
Run Bluetooth LE on Android
"""
import asyncio
import toga
from toga import Button, MultilineTextInput
from toga.style import Pack
from toga.style.pack import COLUMN
if toga.platform.current_platform == 'android':
from .bleekWare.Scanner import Scanner as Scanner
else:
from bleak import BleakScanner as Scanner
class BleScannerApp(toga.App):
"""A small App to demonstrate Bluetooth LE functionality with bleekWare.
bleekWare replaces Bleak on the Android platform when working with
Toga and BeeWare (see the conditional import above).
This app demonstrates several possibilities to perform a scan and
read the advertised data.
"""
def startup(self):
"""Set up the GUI."""
self.scan_button = Button(
'Scan for BLE devices (using async "with" container)',
on_press=self.start_scan,
)
self.discover_button = Button(
'Discover BLE devices (classmethod)',
on_press=self.start_discover,
)
self.manual_button = Button(
'Manual start/stop scan (with callback)',
on_press=self.manual_scan_with_callback,
)
self.manual_button_gen = Button(
'Manual start/stop scan (with generator)',
on_press=self.manual_scan_with_generator,
)
self.device_list = MultilineTextInput(
readonly=True,
style=Pack(padding=(10, 5), height=200),
)
self.data_list = MultilineTextInput(
readonly=True,
style=Pack(padding=(10, 5), height=200),
)
box = toga.Box(
children=[
self.scan_button,
self.discover_button,
self.manual_button,
self.manual_button_gen,
self.device_list,
self.data_list,
],
style=Pack(direction=COLUMN),
)
self.main_window = toga.MainWindow(
title='Android BLE Scanner Demo App'
)
self.main_window.content = box
self.main_window.show()
self.scan_on = False
async def start_scan(self, widget):
"""Use scanner with 'with' container.
The scan result in scanner.discovered_devices and
scanner.discovered_devices_and_advertisement_data
contains each discovered device only once.
"""
self.print_device('Start BLE scan...', clear=True)
self.print_data(clear=True)
async with Scanner() as scanner:
await asyncio.sleep(10)
self.print_device('...scanning stopped.')
self.show_scan_result(
scanner.discovered_devices_and_advertisement_data
)
async def start_discover(self, widget):
"""Use class method Scanner.discover().
'return_adv=True' returns a dic.
'return_adv=False' would just return a list of discovered devices.
"""
self.print_device('Start BLE scan...', clear=True)
self.print_data(clear=True)
result = await Scanner.discover(return_adv=True)
self.print_device('...scanning stopped.')
self.show_scan_result(result)
async def manual_scan_with_callback(self, widget):
"""Start and stop a scan manually and display results via callback."""
if not self.scan_on:
self.scanner = Scanner(self.scan_callback)
await self.scanner.start()
self.scan_on = True
self.print_device('Start BLE scan...', clear=True)
self.print_data('Device data:', clear=True)
else:
await self.scanner.stop()
self.scan_on = False
self.print_device('...scanning stopped.')
async def manual_scan_with_generator(self, widget):
"""Start and stop a scan manually and display results via generator.
Scanner.advertisement_data() returns an async generator.
Requires bleak 0.21 or bleekWare.
"""
if not self.scan_on:
self.scan_on = True
self.scanner = Scanner()
await self.scanner.start()
self.print_device('Start BLE scan...', clear=True)
self.print_data('Device data:', clear=True)
async for device, data in self.scanner.advertisement_data():
self.print_device(self.get_name(device))
self.print_data(str(device))
self.print_data(str(data))
self.print_data()
if not self.scan_on:
await self.scanner.stop()
self.print_device('...scanning stopped.')
break
else:
self.scan_on = False
def scan_callback(self, device, advertisement_data):
"""Receive data from scanner each time a device is found.
The callback is called on each detection event, so the same
device can pop up several times during the scan.
This callback can be a normal or an async function.
"""
self.print_device(self.get_name(device))
self.print_data(str(device))
self.print_data(str(advertisement_data))
self.print_data()
def show_scan_result(self, data):
"""Show names of found devices and attached advertisment data.
'data' is a dictionary, where the keys are the BLE addresses
and the values are tuples of BLE device, advertisement data.
"""
self.print_device('Found devices:')
self.print_data('Device data:', clear=True)
for key in data:
device, adv_data = data[key]
self.print_device(self.get_name(device))
self.print_data(f'{device}\n{adv_data}')
self.print_data()
def get_name(self, device):
"""Return name or address of BLE device."""
if device.name:
return device.name
else:
return f'No name ({device.address})'
def print_device(self, device='', clear=False):
"""Write device name to MultilineTextInput for devices."""
if clear:
self.device_list.value = ''
self.device_list.value += device + '\n'
self.device_list.scroll_to_bottom()
def print_data(self, data='', clear=False):
"""Write device data to MultilineTextInput for device data."""
if clear:
self.data_list.value = ''
self.data_list.value += data + '\n'
self.data_list.scroll_to_bottom()
def main():
return BleScannerApp()