3838_LOGGER = logging .getLogger (__name__ )
3939
4040# Use full UUID since we do not use UUID from bluepy.btle
41+ CHAR_UUID_CCCD = btle .UUID ('2902' ) # Client Characteristic Configuration Descriptor (CCCD)
4142CHAR_UUID_MANUFACTURER_NAME = UUID ('00002a29-0000-1000-8000-00805f9b34fb' )
4243CHAR_UUID_SERIAL_NUMBER_STRING = UUID ('00002a25-0000-1000-8000-00805f9b34fb' )
4344CHAR_UUID_MODEL_NUMBER_STRING = UUID ('00002a24-0000-1000-8000-00805f9b34fb' )
5152CHAR_UUID_WAVE_PLUS_DATA = UUID ('b42e2a68-ade7-11e4-89d3-123b93f75cba' )
5253CHAR_UUID_WAVE_2_DATA = UUID ('b42e4dcc-ade7-11e4-89d3-123b93f75cba' )
5354CHAR_UUID_WAVEMINI_DATA = UUID ('b42e3b98-ade7-11e4-89d3-123b93f75cba' )
55+ COMMAND_UUID = UUID ('b42e2d06-ade7-11e4-89d3-123b93f75cba' ) # "Access Control Point" Characteristic
5456
5557Characteristic = namedtuple ('Characteristic' , ['uuid' , 'name' , 'format' ])
5658
@@ -74,7 +76,8 @@ def __str__(self):
7476
7577sensors_characteristics_uuid = [CHAR_UUID_DATETIME , CHAR_UUID_TEMPERATURE , CHAR_UUID_HUMIDITY , CHAR_UUID_RADON_1DAYAVG ,
7678 CHAR_UUID_RADON_LONG_TERM_AVG , CHAR_UUID_ILLUMINANCE_ACCELEROMETER ,
77- CHAR_UUID_WAVE_PLUS_DATA ,CHAR_UUID_WAVE_2_DATA ,CHAR_UUID_WAVEMINI_DATA ]
79+ CHAR_UUID_WAVE_PLUS_DATA ,CHAR_UUID_WAVE_2_DATA ,CHAR_UUID_WAVEMINI_DATA ,
80+ COMMAND_UUID ]
7881
7982sensors_characteristics_uuid_str = [str (x ) for x in sensors_characteristics_uuid ]
8083
@@ -153,6 +156,47 @@ def decode_data(self, raw_data):
153156 return data
154157
155158
159+ class CommandDecode :
160+ def __init__ (self , name , format_type , cmd ):
161+ self .name = name
162+ self .format_type = format_type
163+ self .cmd = cmd
164+
165+ def decode_data (self , raw_data ):
166+ if raw_data is None :
167+ return {}
168+ cmd = raw_data [0 :1 ]
169+ if cmd != self .cmd :
170+ _LOGGER .warning ("Result for Wrong command received, expected {} got {}" .format (self .cmd .hex (), cmd .hex ()))
171+ return {}
172+
173+ if len (raw_data [2 :]) != struct .calcsize (self .format_type ):
174+ _LOGGER .debug ("Wrong length data received ({}) verses expected ({})" .format (len (cmd ), struct .calcsize (self .format_type )))
175+ return {}
176+ val = struct .unpack (
177+ self .format_type ,
178+ raw_data [2 :])
179+ res = {}
180+ res ['illuminance' ] = val [2 ]
181+ #res['measurement_periods'] = val[5]
182+ res ['battery' ] = val [17 ] / 1000.0
183+
184+ return res
185+
186+
187+ class MyDelegate (btle .DefaultDelegate ):
188+ def __init__ (self ):
189+ btle .DefaultDelegate .__init__ (self )
190+ # ... initialise here
191+ self .data = None
192+
193+ def handleNotification (self , cHandle , data ):
194+ if self .data is None :
195+ self .data = data
196+ else :
197+ self .data = self .data + data
198+
199+
156200sensor_decoders = {str (CHAR_UUID_WAVE_PLUS_DATA ):WavePlussDecode (name = "Pluss" , format_type = 'BBBBHHHHHHHH' , scale = 0 ),
157201 str (CHAR_UUID_DATETIME ):WaveDecodeDate (name = "date_time" , format_type = 'HBBBBB' , scale = 0 ),
158202 str (CHAR_UUID_HUMIDITY ):BaseDecode (name = "humidity" , format_type = 'H' , scale = 1.0 / 100.0 ),
@@ -163,6 +207,8 @@ def decode_data(self, raw_data):
163207 str (CHAR_UUID_WAVE_2_DATA ):Wave2Decode (name = "Wave2" , format_type = '<4B8H' , scale = 1.0 ),
164208 str (CHAR_UUID_WAVEMINI_DATA ):WaveMiniDecode (name = "WaveMini" , format_type = '<HHHHHHLL' , scale = 1.0 ),}
165209
210+ command_decoders = {str (COMMAND_UUID ):CommandDecode (name = "Battery" , format_type = '<L12B6H' , cmd = struct .pack ('<B' , 0x6d ))}
211+
166212
167213class AirthingsWaveDetect :
168214 def __init__ (self , scan_interval , mac = None ):
@@ -184,7 +230,7 @@ def _parse_serial_number(self, manufacturer_data):
184230
185231 def find_devices (self , scans = 50 , timeout = 0.1 ):
186232 # Search for devices, scan for BLE devices scans times for timeout seconds
187- # Get manufacturer data and try to match match it to airthings ID.
233+ # Get manufacturer data and try to match it to airthings ID.
188234 scanner = btle .Scanner ()
189235 for _count in range (scans ):
190236 advertisements = scanner .scan (timeout )
@@ -204,6 +250,8 @@ def connect(self, mac, retries=10):
204250 tries += 1
205251 try :
206252 self ._dev = btle .Peripheral (mac .lower ())
253+ self .delgate = MyDelegate ()
254+ self ._dev .withDelegate ( self .delgate )
207255 break
208256 except Exception as e :
209257 # print(e)
@@ -264,15 +312,34 @@ def get_sensor_data(self):
264312 if self ._dev is not None :
265313 try :
266314 for characteristic in characteristics :
315+ sensor_data = None
267316 if str (characteristic .uuid ) in sensor_decoders :
268317 char = self ._dev .getCharacteristics (uuid = characteristic .uuid )[0 ]
269318 data = char .read ()
270319 sensor_data = sensor_decoders [str (characteristic .uuid )].decode_data (data )
271320 _LOGGER .debug ("{} Got sensordata {}" .format (mac , sensor_data ))
321+
322+ if str (characteristic .uuid ) in command_decoders :
323+ self .delgate .data = None # Clear the delegate so it is ready for new data.
324+ char = self ._dev .getCharacteristics (uuid = characteristic .uuid )[0 ]
325+ # Do these steps to get notification to work, I do not know how it works, this link should explain it
326+ # https://devzone.nordicsemi.com/guides/short-range-guides/b/bluetooth-low-energy/posts/ble-characteristics-a-beginners-tutorial
327+ desc , = char .getDescriptors (forUUID = CHAR_UUID_CCCD )
328+ desc .write (struct .pack ('<H' , 1 ), True )
329+ char .write (command_decoders [str (characteristic .uuid )].cmd )
330+ for i in range (3 ):
331+ if self ._dev .waitForNotifications (0.1 ):
332+ _LOGGER .debug ("Received notification, total data received len {}" .format (len (self .delgate .data )))
333+
334+ sensor_data = command_decoders [str (characteristic .uuid )].decode_data (self .delgate .data )
335+ _LOGGER .debug ("{} Got cmddata {}" .format (mac , sensor_data ))
336+
337+ if sensor_data is not None :
272338 if self .sensordata .get (mac ) is None :
273339 self .sensordata [mac ] = sensor_data
274340 else :
275341 self .sensordata [mac ].update (sensor_data )
342+
276343 except btle .BTLEDisconnectError :
277344 _LOGGER .exception ("Disconnected" )
278345 self ._dev = None
0 commit comments