1212_LOGGER = logging .getLogger (__name__ )
1313
1414# Use full UUID since we do not use UUID from bluepy.btle
15+ CHAR_UUID_CCCD = btle .UUID ('2902' ) # Client Characteristic Configuration Descriptor (CCCD)
1516CHAR_UUID_MANUFACTURER_NAME = UUID ('00002a29-0000-1000-8000-00805f9b34fb' )
1617CHAR_UUID_SERIAL_NUMBER_STRING = UUID ('00002a25-0000-1000-8000-00805f9b34fb' )
1718CHAR_UUID_MODEL_NUMBER_STRING = UUID ('00002a24-0000-1000-8000-00805f9b34fb' )
2526CHAR_UUID_WAVE_PLUS_DATA = UUID ('b42e2a68-ade7-11e4-89d3-123b93f75cba' )
2627CHAR_UUID_WAVE_2_DATA = UUID ('b42e4dcc-ade7-11e4-89d3-123b93f75cba' )
2728CHAR_UUID_WAVEMINI_DATA = UUID ('b42e3b98-ade7-11e4-89d3-123b93f75cba' )
29+ COMMAND_UUID = UUID ('b42e2d06-ade7-11e4-89d3-123b93f75cba' ) # "Access Control Point" Characteristic
2830
2931Characteristic = namedtuple ('Characteristic' , ['uuid' , 'name' , 'format' ])
3032
@@ -48,7 +50,8 @@ def __str__(self):
4850
4951sensors_characteristics_uuid = [CHAR_UUID_DATETIME , CHAR_UUID_TEMPERATURE , CHAR_UUID_HUMIDITY , CHAR_UUID_RADON_1DAYAVG ,
5052 CHAR_UUID_RADON_LONG_TERM_AVG , CHAR_UUID_ILLUMINANCE_ACCELEROMETER ,
51- CHAR_UUID_WAVE_PLUS_DATA ,CHAR_UUID_WAVE_2_DATA ,CHAR_UUID_WAVEMINI_DATA ]
53+ CHAR_UUID_WAVE_PLUS_DATA ,CHAR_UUID_WAVE_2_DATA ,CHAR_UUID_WAVEMINI_DATA ,
54+ COMMAND_UUID ]
5255
5356sensors_characteristics_uuid_str = [str (x ) for x in sensors_characteristics_uuid ]
5457
@@ -127,6 +130,47 @@ def decode_data(self, raw_data):
127130 return data
128131
129132
133+ class CommandDecode :
134+ def __init__ (self , name , format_type , cmd ):
135+ self .name = name
136+ self .format_type = format_type
137+ self .cmd = cmd
138+
139+ def decode_data (self , raw_data ):
140+ if raw_data is None :
141+ return {}
142+ cmd = raw_data [0 :1 ]
143+ if cmd != self .cmd :
144+ _LOGGER .warning ("Result for Wrong command received, expected {} got {}" .format (self .cmd .hex (), cmd .hex ()))
145+ return {}
146+
147+ if len (raw_data [2 :]) != struct .calcsize (self .format_type ):
148+ _LOGGER .debug ("Wrong length data received ({}) verses expected ({})" .format (len (cmd ), struct .calcsize (self .format_type )))
149+ return {}
150+ val = struct .unpack (
151+ self .format_type ,
152+ raw_data [2 :])
153+ res = {}
154+ res ['illuminance' ] = val [2 ]
155+ #res['measurement_periods'] = val[5]
156+ res ['battery' ] = val [17 ] / 1000.0
157+
158+ return res
159+
160+
161+ class MyDelegate (btle .DefaultDelegate ):
162+ def __init__ (self ):
163+ btle .DefaultDelegate .__init__ (self )
164+ # ... initialise here
165+ self .data = None
166+
167+ def handleNotification (self , cHandle , data ):
168+ if self .data is None :
169+ self .data = data
170+ else :
171+ self .data = self .data + data
172+
173+
130174sensor_decoders = {str (CHAR_UUID_WAVE_PLUS_DATA ):WavePlussDecode (name = "Pluss" , format_type = 'BBBBHHHHHHHH' , scale = 0 ),
131175 str (CHAR_UUID_DATETIME ):WaveDecodeDate (name = "date_time" , format_type = 'HBBBBB' , scale = 0 ),
132176 str (CHAR_UUID_HUMIDITY ):BaseDecode (name = "humidity" , format_type = 'H' , scale = 1.0 / 100.0 ),
@@ -137,6 +181,8 @@ def decode_data(self, raw_data):
137181 str (CHAR_UUID_WAVE_2_DATA ):Wave2Decode (name = "Wave2" , format_type = '<4B8H' , scale = 1.0 ),
138182 str (CHAR_UUID_WAVEMINI_DATA ):WaveMiniDecode (name = "WaveMini" , format_type = '<HHHHHHLL' , scale = 1.0 ),}
139183
184+ command_decoders = {str (COMMAND_UUID ):CommandDecode (name = "Battery" , format_type = '<L12B6H' , cmd = struct .pack ('<B' , 0x6d ))}
185+
140186
141187class AirthingsWaveDetect :
142188 def __init__ (self , scan_interval , mac = None ):
@@ -158,7 +204,7 @@ def _parse_serial_number(self, manufacturer_data):
158204
159205 def find_devices (self , scans = 50 , timeout = 0.1 ):
160206 # Search for devices, scan for BLE devices scans times for timeout seconds
161- # Get manufacturer data and try to match match it to airthings ID.
207+ # Get manufacturer data and try to match it to airthings ID.
162208 scanner = btle .Scanner ()
163209 for _count in range (scans ):
164210 advertisements = scanner .scan (timeout )
@@ -178,6 +224,8 @@ def connect(self, mac, retries=10):
178224 tries += 1
179225 try :
180226 self ._dev = btle .Peripheral (mac .lower ())
227+ self .delgate = MyDelegate ()
228+ self ._dev .withDelegate ( self .delgate )
181229 break
182230 except Exception as e :
183231 print (e )
@@ -238,15 +286,34 @@ def get_sensor_data(self):
238286 if self ._dev is not None :
239287 try :
240288 for characteristic in characteristics :
289+ sensor_data = None
241290 if str (characteristic .uuid ) in sensor_decoders :
242291 char = self ._dev .getCharacteristics (uuid = characteristic .uuid )[0 ]
243292 data = char .read ()
244293 sensor_data = sensor_decoders [str (characteristic .uuid )].decode_data (data )
245294 _LOGGER .debug ("{} Got sensordata {}" .format (mac , sensor_data ))
295+
296+ if str (characteristic .uuid ) in command_decoders :
297+ self .delgate .data = None # Clear the delegate so it is ready for new data.
298+ char = self ._dev .getCharacteristics (uuid = characteristic .uuid )[0 ]
299+ # Do these steps to get notification to work, I do not know how it works, this link should explain it
300+ # https://devzone.nordicsemi.com/guides/short-range-guides/b/bluetooth-low-energy/posts/ble-characteristics-a-beginners-tutorial
301+ desc , = char .getDescriptors (forUUID = CHAR_UUID_CCCD )
302+ desc .write (struct .pack ('<H' , 1 ), True )
303+ char .write (command_decoders [str (characteristic .uuid )].cmd )
304+ for i in range (3 ):
305+ if self ._dev .waitForNotifications (0.1 ):
306+ _LOGGER .debug ("Received notification, total data received len {}" .format (len (self .delgate .data )))
307+
308+ sensor_data = command_decoders [str (characteristic .uuid )].decode_data (self .delgate .data )
309+ _LOGGER .debug ("{} Got cmddata {}" .format (mac , sensor_data ))
310+
311+ if sensor_data is not None :
246312 if self .sensordata .get (mac ) is None :
247313 self .sensordata [mac ] = sensor_data
248314 else :
249315 self .sensordata [mac ].update (sensor_data )
316+
250317 except btle .BTLEDisconnectError :
251318 _LOGGER .exception ("Disconnected" )
252319 self ._dev = None
0 commit comments