Skip to content

Commit 7939afa

Browse files
committed
Remove pygatt and use bluepy.btle, in a hope to improve stability, tested this file main function on rpi4 and that works.
1 parent 6b6f781 commit 7939afa

File tree

1 file changed

+68
-75
lines changed

1 file changed

+68
-75
lines changed

custom_components/airthings_wave/airthings.py

Lines changed: 68 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
import logging
66
from datetime import datetime
77

8-
import pygatt
9-
from pygatt.exceptions import (
10-
BLEError, NotConnectedError, NotificationTimeout)
8+
import bluepy.btle as btle
9+
1110

1211
from uuid import UUID
1312

@@ -142,117 +141,112 @@ def decode_data(self, raw_data):
142141

143142
class 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
151149

152150

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()
151+
def _parse_serial_number(self, manufacturer_data):
152+
try:
153+
#print(manufacturer_data)
154+
(ID, SN, _) = struct.unpack("<HLH", manufacturer_data)
155+
except Exception as e: # Return None for non-Airthings devices
156+
return None
157+
else: # Executes only if try-block succeeds
158+
if ID == 0x0334:
159+
return SN
160+
161+
def find_devices(self, scanns=10):
162+
scanner = btle.Scanner()
163+
for _count in range(scanns):
164+
advertisements = scanner.scan(0.1)
165+
for adv in advertisements:
166+
sn = self._parse_serial_number(adv.getValue(btle.ScanEntry.MANUFACTURER))
167+
if sn is not None:
168+
if adv.addr not in self.airthing_devices:
169+
self.airthing_devices.append(adv.addr)
179170

180171
_LOGGER.debug("Found {} airthings devices".format(len(self.airthing_devices)))
181172
return len(self.airthing_devices)
182173

174+
def connect(self, mac, retries=1):
175+
tries = 0
176+
self._dev = None
177+
while (tries < retries):
178+
tries += 1
179+
try:
180+
self._dev = btle.Peripheral(mac)
181+
except Exception as e:
182+
print(e)
183+
if tries == retries:
184+
pass
185+
else:
186+
print("Retrying")
187+
188+
def disconnect(self):
189+
if self._dev is not None:
190+
self._dev.disconnect()
191+
self._dev = None
192+
183193
def get_info(self):
184194
# Try to get some info from the discovered airthings devices
185195
self.devices = {}
186196

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+
char = self._dev.getCharacteristics(uuid=characteristic.uuid)[0]
233+
data = char.read()
234+
if str(characteristic.uuid) in sensor_decoders:
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

252246
if __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

Comments
 (0)