Skip to content

Commit cb9ff2f

Browse files
Merge pull request #47 from sverrham/master
Remove pygatt and use bluepy.btle
2 parents 6b6f781 + cb9da94 commit cb9ff2f

File tree

3 files changed

+74
-81
lines changed

3 files changed

+74
-81
lines changed

custom_components/airthings_wave/airthings.py

Lines changed: 71 additions & 78 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
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

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))
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"domain": "airthings_wave",
33
"name": "Airthings Wave",
4-
"version": "2.9.1",
4+
"version": "3.0",
55
"documentation": "https://github.com/custom-components/sensor.airthings_wave/",
66
"issue_tracker": "https://github.com/custom-components/sensor.airthings_wave/issues",
77
"dependencies": [],
88
"codeowners": ["@MartyTremblay"],
99
"requirements": [
10-
"pygatt[GATTTOOL]==4.0.5"
10+
"bluepy==1.3.0"
1111
]
1212
}

custom_components/airthings_wave/sensor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def __init__(self, mac, name, device, device_info, sensor_specifics):
214214
"""Initialize a sensor."""
215215
self.device = device
216216
self._mac = mac
217-
self._name = '{}-{}'.format(mac, name)
217+
self._name = '{}-{}'.format(mac.upper(), name)
218218
_LOGGER.debug("Added sensor entity {}".format(self._name))
219219
self._sensor_name = name
220220

0 commit comments

Comments
 (0)