33https://docs.micropython.org/en/latest/library/bluetooth.html
44"""
55
6- import time
7-
86try :
97 import bluetooth
108except ImportError : # Linux test runner may not provide bluetooth module
119 bluetooth = None
1210
11+ import sys
12+
1313import lvgl as lv
1414from micropython import const
15- from mpos import Activity
15+ from mpos import Activity , TaskManager
1616
17- SCAN_DURATION = const (1000 ) # Duration of each BLE scan in milliseconds
18- _IRQ_SCAN_RESULT = const (5 )
17+ # Scan for 5 seconds,
18+ SCAN_DURATION_MS = const (5000 ) # Duration of each BLE scan in milliseconds
19+ # with very low interval/window (to maximize detection rate):
20+ INTERVAL_US = const (30000 )
21+ WINDOW_US = const (30000 )
1922
23+ _IRQ_SCAN_RESULT = const (5 )
24+ _IRQ_SCAN_DONE = const (6 )
2025
2126# BLE Advertising Data Types (Standardized by Bluetooth SIG)
22- _ADV_TYPE_NAME = const (0x09 )
27+ _ADV_TYPE_SHORT_NAME = const (8 )
28+ _ADV_TYPE_NAME = const (9 )
2329
2430
25- def decode_field (payload : bytes , adv_type : int ) -> list :
26- results = []
31+ def decode_name (payload : bytes ) -> str | None :
2732 i = 0
2833 payload_len = len (payload )
2934 while i < payload_len :
3035 length = payload [i ]
3136 if length == 0 or i + length >= payload_len :
3237 break
3338 field_type = payload [i + 1 ]
34- if field_type == adv_type :
35- results .append (payload [i + 2 : i + length + 1 ])
39+ if field_type in (_ADV_TYPE_SHORT_NAME , _ADV_TYPE_NAME ):
40+ if new_name := payload [i + 2 : i + length + 1 ]:
41+ return str (new_name , "utf-8" )
42+ else :
43+ print (f"Unsupported: { field_type = } with { length = } " )
3644 i += length + 1
37- return results
38-
39-
40- class BluetoothScanner :
41- def __init__ (self , device_callback ):
42- if bluetooth is None :
43- raise RuntimeError ("Bluetooth module not available" )
44- self .device_callback = device_callback
45- self .ble = bluetooth .BLE ()
46- self .ble .irq (self .ble_irq_handler )
47-
48- def __enter__ (self ):
49- print ("Activating BLE" )
50- self .ble .active (True )
51- return self
52-
53- def ble_irq_handler (self , event : int , data : tuple ) -> None :
54- if event == _IRQ_SCAN_RESULT :
55- addr_type , addr , adv_type , rssi , adv_data = data
56- addr = ":" .join (f"{ b :02x} " for b in addr )
57- names = decode_field (adv_data , _ADV_TYPE_NAME )
58- name = str (names [0 ], "utf-8" ) if names else "Unknown"
59- self .device_callback (addr , rssi , name )
60-
61- def scan (self , duration_ms : int ):
62- print (f"BLE scanning for { duration_ms } ms..." )
63- self .ble .gap_scan (duration_ms , 20000 , 10000 )
64-
65- def __exit__ (self , exc_type , exc_val , exc_tb ):
66- print ("Deactivating BLE" )
67- self .ble .active (False )
6845
6946
7047def set_dynamic_column_widths (table , font = None , padding = 8 ):
@@ -85,75 +62,120 @@ def set_cell_value(table, *, row: int, values: tuple):
8562
8663
8764class ScanBluetooth (Activity ):
88- refresh_timer = None
89-
9065 def onCreate (self ):
91- screen = lv .obj ()
92- screen .set_flex_flow (lv .FLEX_FLOW .COLUMN )
93- screen .set_style_pad_all (0 , 0 )
94- screen .set_size (lv .pct (100 ), lv .pct (100 ))
66+ main_content = lv .obj ()
67+ main_content .set_flex_flow (lv .FLEX_FLOW .COLUMN )
68+ main_content .set_style_pad_all (0 , 0 )
69+ main_content .set_size (lv .pct (100 ), lv .pct (100 ))
70+
71+ info_column = lv .obj (main_content )
72+ info_column .set_flex_flow (lv .FLEX_FLOW .COLUMN )
73+ info_column .set_style_pad_all (1 , 1 )
74+ info_column .set_size (lv .pct (100 ), lv .SIZE_CONTENT )
75+
76+ self .info_label = lv .label (info_column )
77+ self .info_label .set_style_text_font (lv .font_montserrat_14 , 0 )
9578
9679 if bluetooth is None :
97- label = lv .label (screen )
98- label .set_text ("Bluetooth not available on this platform" )
99- label .center ()
100- self .setContentView (screen )
80+ self .info ("Bluetooth not available on this platform" )
81+ self .setContentView (main_content )
10182 return
10283
103- self .table = lv .table (screen )
84+ tabel_column = lv .obj (main_content )
85+ tabel_column .set_flex_flow (lv .FLEX_FLOW .COLUMN )
86+ tabel_column .set_style_pad_all (0 , 0 )
87+ tabel_column .set_size (lv .pct (100 ), lv .SIZE_CONTENT )
88+
89+ self .table = lv .table (tabel_column )
10490 set_cell_value (
10591 self .table ,
10692 row = 0 ,
10793 values = ("pos" , "MAC" , "RSSI" , "count" , "Name" ),
10894 )
10995 set_dynamic_column_widths (self .table )
11096
97+ self .scan_count = 0
11198 self .mac2column = {}
11299 self .mac2counts = {}
100+ self .mac2name = {}
113101
114- self .scanner_cm = BluetoothScanner (device_callback = self .scan_callback )
115- self .scanner = self .scanner_cm .__enter__ () # Activate BLE
102+ self .ble = bluetooth .BLE ()
116103
117- self .setContentView (screen )
104+ self .setContentView (main_content )
118105
119- def scan_callback (self , addr , rssi , name ):
120- if not (column_index := self .mac2column .get (addr )):
121- column_index = len (self .mac2column ) + 1
122- self .mac2column [addr ] = column_index
123- self .mac2counts [addr ] = 1
124- else :
125- self .mac2counts [addr ] += 1
106+ def info (self , text ):
107+ print (text )
108+ self .info_label .set_text (text )
126109
127- set_cell_value (
128- self .table ,
129- row = column_index ,
130- values = (
131- str (column_index ),
132- addr ,
133- f"{ rssi } dBm" ,
134- str (self .mac2counts [addr ]),
135- name ,
136- ),
137- )
110+ async def ble_scan (self ):
111+ """Check sensor every second"""
112+ while self .scanning :
113+ print (f"async scan for { SCAN_DURATION_MS } ms..." )
114+ self .ble .gap_scan (SCAN_DURATION_MS , INTERVAL_US , WINDOW_US , True )
115+ await TaskManager .sleep_ms (SCAN_DURATION_MS + 100 )
138116
139117 def onResume (self , screen ):
140118 super ().onResume (screen )
141119 if bluetooth is None :
142120 return
143121
144- def update (timer ):
145- self .scanner .scan (SCAN_DURATION )
146- set_dynamic_column_widths (self .table )
147- time .sleep_ms (SCAN_DURATION + 100 ) # Wait ?
148- print (f"Scan complete: { len (self .mac2column )} unique devices" )
122+ self .info ("Activating Bluetooth..." )
123+ self .ble .irq (self .ble_irq_handler )
124+ self .ble .active (True )
149125
150- self .refresh_timer = lv .timer_create (update , SCAN_DURATION + 1000 , None )
126+ self .scanning = True
127+ TaskManager .create_task (self .ble_scan ())
151128
152129 def onPause (self , screen ):
153130 super ().onPause (screen )
154131 if bluetooth is None :
155132 return
156- self .scanner .__exit__ (None , None , None ) # Deactivate BLE
157- if self .refresh_timer :
158- self .refresh_timer .delete ()
159- self .refresh_timer = None
133+
134+ self .scanning = False
135+
136+ self .info ("Stop scanning..." )
137+ self .ble .gap_scan (None )
138+ self .info ("Deactivating BLE..." )
139+ self .ble .active (False )
140+ self .info ("BLE deactivated" )
141+
142+ def ble_irq_handler (self , event : int , data : tuple ) -> None :
143+ try :
144+ if event == _IRQ_SCAN_RESULT :
145+ addr_type , addr , adv_type , rssi , adv_data = data
146+ addr = ":" .join (f"{ b :02x} " for b in addr )
147+ print (f"{ addr = } { rssi = } { len (adv_data )= } " )
148+ if name := decode_name (adv_data ):
149+ self .mac2name [addr ] = name
150+ else :
151+ name = self .mac2name .get (addr , "Unknown" )
152+
153+ if not (column_index := self .mac2column .get (addr )):
154+ column_index = len (self .mac2column ) + 1
155+ self .mac2column [addr ] = column_index
156+ self .mac2counts [addr ] = 1
157+ else :
158+ self .mac2counts [addr ] += 1
159+
160+ set_cell_value (
161+ self .table ,
162+ row = column_index ,
163+ values = (
164+ str (column_index ),
165+ addr ,
166+ f"{ rssi } dBm" ,
167+ str (self .mac2counts [addr ]),
168+ name ,
169+ ),
170+ )
171+ elif event == _IRQ_SCAN_DONE :
172+ set_dynamic_column_widths (self .table )
173+ self .scan_count += 1
174+ self .info (
175+ f"{ len (self .mac2column )} unique devices (Scan { self .scan_count } )"
176+ )
177+ else :
178+ print (f"Ignored BLE { event = } " )
179+ except Exception as e :
180+ sys .print_exception (e )
181+ print (f"Error in BLE IRQ handler { event = } : { e } " )
0 commit comments