1+ # This example finds and connects to a BLE temperature sensor (e.g. the one in ble_temperature.py).
2+
3+ import bluetooth
4+ import random
5+ import struct
6+ import time
7+ import micropython
8+ from ble_advertising import decode_services , decode_name
9+ from micropython import const
10+ from machine import Pin
11+
12+ _IRQ_CENTRAL_CONNECT = const (1 )
13+ _IRQ_CENTRAL_DISCONNECT = const (2 )
14+ _IRQ_GATTS_WRITE = const (3 )
15+ _IRQ_GATTS_READ_REQUEST = const (4 )
16+ _IRQ_SCAN_RESULT = const (5 )
17+ _IRQ_SCAN_DONE = const (6 )
18+ _IRQ_PERIPHERAL_CONNECT = const (7 )
19+ _IRQ_PERIPHERAL_DISCONNECT = const (8 )
20+ _IRQ_GATTC_SERVICE_RESULT = const (9 )
21+ _IRQ_GATTC_SERVICE_DONE = const (10 )
22+ _IRQ_GATTC_CHARACTERISTIC_RESULT = const (11 )
23+ _IRQ_GATTC_CHARACTERISTIC_DONE = const (12 )
24+ _IRQ_GATTC_DESCRIPTOR_RESULT = const (13 )
25+ _IRQ_GATTC_DESCRIPTOR_DONE = const (14 )
26+ _IRQ_GATTC_READ_RESULT = const (15 )
27+ _IRQ_GATTC_READ_DONE = const (16 )
28+ _IRQ_GATTC_WRITE_DONE = const (17 )
29+ _IRQ_GATTC_NOTIFY = const (18 )
30+ _IRQ_GATTC_INDICATE = const (19 )
31+
32+ _ADV_IND = const (0x00 )
33+ _ADV_DIRECT_IND = const (0x01 )
34+ _ADV_SCAN_IND = const (0x02 )
35+ _ADV_NONCONN_IND = const (0x03 )
36+
37+ # org.bluetooth.service.environmental_sensing
38+ _ENV_SENSE_UUID = bluetooth .UUID (0x181A )
39+ # org.bluetooth.characteristic.temperature
40+ _TEMP_UUID = bluetooth .UUID (0x2A6E )
41+ _TEMP_CHAR = (
42+ _TEMP_UUID ,
43+ bluetooth .FLAG_READ | bluetooth .FLAG_NOTIFY ,
44+ )
45+ _ENV_SENSE_SERVICE = (
46+ _ENV_SENSE_UUID ,
47+ (_TEMP_CHAR ,),
48+ )
49+
50+ class BLETemperatureCentral :
51+ def __init__ (self , ble ):
52+ self ._ble = ble
53+ self ._ble .active (True )
54+ self ._ble .irq (self ._irq )
55+ self ._reset ()
56+ self ._led = Pin ('LED' , Pin .OUT )
57+
58+ def _reset (self ):
59+ # Cached name and address from a successful scan.
60+ self ._name = None
61+ self ._addr_type = None
62+ self ._addr = None
63+
64+ # Cached value (if we have one)
65+ self ._value = None
66+
67+ # Callbacks for completion of various operations.
68+ # These reset back to None after being invoked.
69+ self ._scan_callback = None
70+ self ._conn_callback = None
71+ self ._read_callback = None
72+
73+ # Persistent callback for when new data is notified from the device.
74+ self ._notify_callback = None
75+
76+ # Connected device.
77+ self ._conn_handle = None
78+ self ._start_handle = None
79+ self ._end_handle = None
80+ self ._value_handle = None
81+
82+ def _irq (self , event , data ):
83+ if event == _IRQ_SCAN_RESULT :
84+ addr_type , addr , adv_type , rssi , adv_data = data
85+ if adv_type in (_ADV_IND , _ADV_DIRECT_IND ):
86+ type_list = decode_services (adv_data )
87+ if _ENV_SENSE_UUID in type_list :
88+ # Found a potential device, remember it and stop scanning.
89+ self ._addr_type = addr_type
90+ self ._addr = bytes (addr ) # Note: addr buffer is owned by caller so need to copy it.
91+ self ._name = decode_name (adv_data ) or "?"
92+ self ._ble .gap_scan (None )
93+
94+ elif event == _IRQ_SCAN_DONE :
95+ if self ._scan_callback :
96+ if self ._addr :
97+ # Found a device during the scan (and the scan was explicitly stopped).
98+ self ._scan_callback (self ._addr_type , self ._addr , self ._name )
99+ self ._scan_callback = None
100+ else :
101+ # Scan timed out.
102+ self ._scan_callback (None , None , None )
103+
104+ elif event == _IRQ_PERIPHERAL_CONNECT :
105+ # Connect successful.
106+ conn_handle , addr_type , addr = data
107+ if addr_type == self ._addr_type and addr == self ._addr :
108+ self ._conn_handle = conn_handle
109+ self ._ble .gattc_discover_services (self ._conn_handle )
110+
111+ elif event == _IRQ_PERIPHERAL_DISCONNECT :
112+ # Disconnect (either initiated by us or the remote end).
113+ conn_handle , _ , _ = data
114+ if conn_handle == self ._conn_handle :
115+ # If it was initiated by us, it'll already be reset.
116+ self ._reset ()
117+
118+ elif event == _IRQ_GATTC_SERVICE_RESULT :
119+ # Connected device returned a service.
120+ conn_handle , start_handle , end_handle , uuid = data
121+ if conn_handle == self ._conn_handle and uuid == _ENV_SENSE_UUID :
122+ self ._start_handle , self ._end_handle = start_handle , end_handle
123+
124+ elif event == _IRQ_GATTC_SERVICE_DONE :
125+ # Service query complete.
126+ if self ._start_handle and self ._end_handle :
127+ self ._ble .gattc_discover_characteristics (
128+ self ._conn_handle , self ._start_handle , self ._end_handle
129+ )
130+ else :
131+ print ("Failed to find environmental sensing service." )
132+
133+ elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT :
134+ # Connected device returned a characteristic.
135+ conn_handle , def_handle , value_handle , properties , uuid = data
136+ if conn_handle == self ._conn_handle and uuid == _TEMP_UUID :
137+ self ._value_handle = value_handle
138+
139+ elif event == _IRQ_GATTC_CHARACTERISTIC_DONE :
140+ # Characteristic query complete.
141+ if self ._value_handle :
142+ # We've finished connecting and discovering device, fire the connect callback.
143+ if self ._conn_callback :
144+ self ._conn_callback ()
145+ else :
146+ print ("Failed to find temperature characteristic." )
147+
148+ elif event == _IRQ_GATTC_READ_RESULT :
149+ # A read completed successfully.
150+ conn_handle , value_handle , char_data = data
151+ if conn_handle == self ._conn_handle and value_handle == self ._value_handle :
152+ self ._update_value (char_data )
153+ if self ._read_callback :
154+ self ._read_callback (self ._value )
155+ self ._read_callback = None
156+
157+ elif event == _IRQ_GATTC_READ_DONE :
158+ # Read completed (no-op).
159+ conn_handle , value_handle , status = data
160+
161+ elif event == _IRQ_GATTC_NOTIFY :
162+ # The ble_temperature.py demo periodically notifies its value.
163+ conn_handle , value_handle , notify_data = data
164+ if conn_handle == self ._conn_handle and value_handle == self ._value_handle :
165+ self ._update_value (notify_data )
166+ if self ._notify_callback :
167+ self ._notify_callback (self ._value )
168+
169+ # Returns true if we've successfully connected and discovered characteristics.
170+ def is_connected (self ):
171+ return self ._conn_handle is not None and self ._value_handle is not None
172+
173+ # Find a device advertising the environmental sensor service.
174+ def scan (self , callback = None ):
175+ self ._addr_type = None
176+ self ._addr = None
177+ self ._scan_callback = callback
178+ self ._ble .gap_scan (2000 , 30000 , 30000 )
179+
180+ # Connect to the specified device (otherwise use cached address from a scan).
181+ def connect (self , addr_type = None , addr = None , callback = None ):
182+ self ._addr_type = addr_type or self ._addr_type
183+ self ._addr = addr or self ._addr
184+ self ._conn_callback = callback
185+ if self ._addr_type is None or self ._addr is None :
186+ return False
187+ self ._ble .gap_connect (self ._addr_type , self ._addr )
188+ return True
189+
190+ # Disconnect from current device.
191+ def disconnect (self ):
192+ if not self ._conn_handle :
193+ return
194+ self ._ble .gap_disconnect (self ._conn_handle )
195+ self ._reset ()
196+
197+ # Issues an (asynchronous) read, will invoke callback with data.
198+ def read (self , callback ):
199+ if not self .is_connected ():
200+ return
201+ self ._read_callback = callback
202+ try :
203+ self ._ble .gattc_read (self ._conn_handle , self ._value_handle )
204+ except OSError as error :
205+ print (error )
206+
207+ # Sets a callback to be invoked when the device notifies us.
208+ def on_notify (self , callback ):
209+ self ._notify_callback = callback
210+
211+ def _update_value (self , data ):
212+ # Data is sint16 in degrees Celsius with a resolution of 0.01 degrees Celsius.
213+ try :
214+ self ._value = struct .unpack ("<h" , data )[0 ] / 100
215+ except OSError as error :
216+ print (error )
217+
218+ def value (self ):
219+ return self ._value
220+
221+ def sleep_ms_flash_led (self , flash_count , delay_ms ):
222+ self ._led .off ()
223+ while (delay_ms > 0 ):
224+ for i in range (flash_count ):
225+ self ._led .on ()
226+ time .sleep_ms (100 )
227+ self ._led .off ()
228+ time .sleep_ms (100 )
229+ delay_ms -= 200
230+ time .sleep_ms (1000 )
231+ delay_ms -= 1000
232+
233+ def print_temp (result ):
234+ print ("read temp: %.2f degc" % result )
235+
236+ def demo (ble , central ):
237+ not_found = False
238+
239+ def on_scan (addr_type , addr , name ):
240+ if addr_type is not None :
241+ print ("Found sensor: %s" % name )
242+ central .connect ()
243+ else :
244+ nonlocal not_found
245+ not_found = True
246+ print ("No sensor found." )
247+
248+ central .scan (callback = on_scan )
249+
250+ # Wait for connection...
251+ while not central .is_connected ():
252+ time .sleep_ms (100 )
253+ if not_found :
254+ return
255+
256+ print ("Connected" )
257+
258+ # Explicitly issue reads
259+ while central .is_connected ():
260+ central .read (callback = print_temp )
261+ sleep_ms_flash_led (central , 2 , 2000 )
262+
263+ print ("Disconnected" )
264+
265+ if __name__ == "__main__" :
266+ ble = bluetooth .BLE ()
267+ central = BLETemperatureCentral (ble )
268+ while (True ):
269+ demo (ble , central )
270+ sleep_ms_flash_led (central , 1 , 10000 )
0 commit comments