55import logging
66from datetime import datetime
77
8- import pygatt
9- from pygatt .exceptions import (
10- BLEError , NotConnectedError , NotificationTimeout )
8+ import bluepy .btle as btle
9+
1110
1211from uuid import UUID
1312
@@ -142,117 +141,112 @@ def decode_data(self, raw_data):
142141
143142class AirthingsWaveDetect :
144143 def __init__ (self , scan_interval , mac = None ):
145- self .adapter = pygatt .backends .GATTToolBackend ()
146144 self .airthing_devices = [] if mac is None else [mac ]
147145 self .sensors = []
148146 self .sensordata = {}
149147 self .scan_interval = scan_interval
150148 self .last_scan = - 1
151-
152-
153- def find_devices (self ):
154- # Scan for devices and try to figure out if it is an airthings device.
155- self .adapter .start (reset_on_start = False )
156- devices = self .adapter .scan (timeout = 3 )
157- self .adapter .stop ()
158-
159- for device in devices :
160- mac = device ['address' ]
161- _LOGGER .debug ("connecting to {}" .format (mac ))
162- try :
163- self .adapter .start (reset_on_start = False )
164- dev = self .adapter .connect (mac , 3 )
165- _LOGGER .debug ("Connected" )
166- try :
167- data = dev .char_read (manufacturer_characteristics .uuid )
168- manufacturer_name = data .decode (manufacturer_characteristics .format )
169- if "airthings" in manufacturer_name .lower ():
170- self .airthing_devices .append (mac )
171- except (BLEError , NotConnectedError , NotificationTimeout ):
172- _LOGGER .debug ("connection to {} failed" .format (mac ))
173- finally :
174- dev .disconnect ()
175- except (BLEError , NotConnectedError , NotificationTimeout ):
176- _LOGGER .debug ("Faild to connect" )
177- finally :
178- self .adapter .stop ()
149+ self ._dev = None
150+
151+ def _parse_serial_number (self , manufacturer_data ):
152+ try :
153+ (ID , SN , _ ) = struct .unpack ("<HLH" , manufacturer_data )
154+ except Exception as e : # Return None for non-Airthings devices
155+ return None
156+ else : # Executes only if try-block succeeds
157+ if ID == 0x0334 :
158+ return SN
159+
160+ def find_devices (self , scans = 50 , timeout = 0.1 ):
161+ # Search for devices, scan for BLE devices scans times for timeout seconds
162+ # Get manufacturer data and try to match match it to airthings ID.
163+ scanner = btle .Scanner ()
164+ for _count in range (scans ):
165+ advertisements = scanner .scan (timeout )
166+ for adv in advertisements :
167+ sn = self ._parse_serial_number (adv .getValue (btle .ScanEntry .MANUFACTURER ))
168+ if sn is not None :
169+ if adv .addr not in self .airthing_devices :
170+ self .airthing_devices .append (adv .addr )
179171
180172 _LOGGER .debug ("Found {} airthings devices" .format (len (self .airthing_devices )))
181173 return len (self .airthing_devices )
182174
175+ def connect (self , mac , retries = 10 ):
176+ tries = 0
177+ self .disconnect ()
178+ while (tries < retries ):
179+ tries += 1
180+ try :
181+ self ._dev = btle .Peripheral (mac .lower ())
182+ except Exception as e :
183+ print (e )
184+ if tries == retries :
185+ pass
186+ else :
187+ _LOGGER .debug ("Retrying {}" .format (mac ))
188+
189+ def disconnect (self ):
190+ if self ._dev is not None :
191+ self ._dev .disconnect ()
192+ self ._dev = None
193+
183194 def get_info (self ):
184195 # Try to get some info from the discovered airthings devices
185196 self .devices = {}
186-
187197 for mac in self .airthing_devices :
188- device = AirthingsDeviceInfo (serial_nr = mac )
189- try :
190- self .adapter .start (reset_on_start = False )
191- dev = self .adapter .connect (mac , 3 )
198+ self .connect (mac )
199+ if self ._dev is not None :
200+ device = AirthingsDeviceInfo (serial_nr = mac )
192201 for characteristic in device_info_characteristics :
193- try :
194- data = dev .char_read (characteristic .uuid )
195- setattr (device , characteristic .name , data .decode (characteristic .format ))
196- except (BLEError , NotConnectedError , NotificationTimeout ):
197- _LOGGER .exception ("" )
198- dev .disconnect ()
199- except (BLEError , NotConnectedError , NotificationTimeout ):
200- _LOGGER .exception ("" )
201- self .adapter .stop ()
202- self .devices [mac ] = device
202+ char = self ._dev .getCharacteristics (uuid = characteristic .uuid )[0 ]
203+ data = char .read ()
204+ setattr (device , characteristic .name , data .decode (characteristic .format ))
203205
206+ self .devices [mac ] = device
207+ self .disconnect ()
204208 return self .devices
205209
206210 def get_sensors (self ):
207211 self .sensors = {}
208212 for mac in self .airthing_devices :
209- try :
210- self .adapter .start (reset_on_start = False )
211- dev = self .adapter .connect (mac , 3 )
212- characteristics = dev .discover_characteristics ()
213+ self .connect (mac )
214+ if self ._dev is not None :
215+ characteristics = self ._dev .getCharacteristics ()
213216 sensor_characteristics = []
214- for characteristic in characteristics . values () :
217+ for characteristic in characteristics :
215218 _LOGGER .debug (characteristic )
216219 if characteristic .uuid in sensors_characteristics_uuid_str :
217220 sensor_characteristics .append (characteristic )
218221 self .sensors [mac ] = sensor_characteristics
219- except (BLEError , NotConnectedError , NotificationTimeout ):
220- _LOGGER .exception ("Failed to discover sensors" )
221-
222+ self .disconnect ()
222223 return self .sensors
223224
224225 def get_sensor_data (self ):
225226 if time .monotonic () - self .last_scan > self .scan_interval :
226227 self .last_scan = time .monotonic ()
227228 for mac , characteristics in self .sensors .items ():
228- try :
229- self .adapter .start (reset_on_start = False )
230- dev = self .adapter .connect (mac , 3 )
229+ self .connect (mac )
230+ if self ._dev is not None :
231231 for characteristic in characteristics :
232- try :
233- data = dev .char_read_handle ("0x{:04x}" .format (characteristic .handle ))
234- if characteristic .uuid in sensor_decoders :
235- sensor_data = sensor_decoders [characteristic .uuid ].decode_data (data )
236- _LOGGER .debug ("{} Got sensordata {}" .format (mac , sensor_data ))
237- if self .sensordata .get (mac ) is None :
238- self .sensordata [mac ] = sensor_data
239- else :
240- self .sensordata [mac ].update (sensor_data )
241- except (BLEError , NotConnectedError , NotificationTimeout ):
242- _LOGGER .exception ("Failed to read characteristic" )
243-
244- dev .disconnect ()
245- except (BLEError , NotConnectedError , NotificationTimeout ):
246- _LOGGER .exception ("Failed to connect" )
247- self .adapter .stop ()
232+ if str (characteristic .uuid ) in sensor_decoders :
233+ char = self ._dev .getCharacteristics (uuid = characteristic .uuid )[0 ]
234+ data = char .read ()
235+ sensor_data = sensor_decoders [str (characteristic .uuid )].decode_data (data )
236+ _LOGGER .debug ("{} Got sensordata {}" .format (mac , sensor_data ))
237+ if self .sensordata .get (mac ) is None :
238+ self .sensordata [mac ] = sensor_data
239+ else :
240+ self .sensordata [mac ].update (sensor_data )
241+ self .disconnect ()
248242
249243 return self .sensordata
250244
251245
252246if __name__ == "__main__" :
253247 logging .basicConfig ()
254- _LOGGER .setLevel (logging .INFO )
255- ad = AirthingsWaveDetect (180 )
248+ _LOGGER .setLevel (logging .DEBUG )
249+ ad = AirthingsWaveDetect (0 )
256250 num_dev_found = ad .find_devices ()
257251 if num_dev_found > 0 :
258252 devices = ad .get_info ()
@@ -267,5 +261,4 @@ def get_sensor_data(self):
267261 sensordata = ad .get_sensor_data ()
268262 for mac , data in sensordata .items ():
269263 for name , val in data .items ():
270- _LOGGER .info ("{}: {}: {}" .format (mac , name , val ))
271-
264+ _LOGGER .info ("{}: {}: {}" .format (mac , name , val ))
0 commit comments