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