import json import logger from contextlib import contextmanager from struct import unpack from mqtt import MqttMessage from workers.base import BaseWorker _LOGGER = logger.get(__name__) REQUIREMENTS = ["bluepy"] class Lywsd02Worker(BaseWorker): def _setup(self): _LOGGER.info("Adding %d %s devices", len(self.devices), repr(self)) for name, mac in self.devices.items(): _LOGGER.info("Adding %s device '%s' (%s)", repr(self), name, mac) self.devices[name] = Lywsd02(mac, timeout=self.command_timeout) def status_update(self): from bluepy import btle for name, lywsd02 in self.devices.items(): try: ret = lywsd02.readAll() except btle.BTLEDisconnectError as e: self.log_connect_exception(_LOGGER, name, e) except btle.BTLEException as e: self.log_unspecified_exception(_LOGGER, name, e) else: yield [MqttMessage(topic=self.format_topic(name), payload=json.dumps(ret))] class Lywsd02: UUID_DATA = "ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6" UUID_BATT = "ebe0ccc4-7a0a-4b0c-8a1a-6ff2997da3a6" def __init__(self, mac, timeout=30): self.mac = mac self.timeout = timeout self._temperature = None self._humidity = None self._battery = None @contextmanager def connected(self): from bluepy import btle _LOGGER.debug("%s connected ", self.mac) device = btle.Peripheral() device.connect(self.mac) yield device device.disconnect() def readAll(self): with self.connected() as device: temperature, humidity = self.getData(device) battery = self.getBattery(device) _LOGGER.debug("successfully read %f, %d, %d", temperature, humidity, battery) return { "temperature": temperature, "humidity": humidity, "battery": battery, } def getData(self, device): self.subscribe(device, self.UUID_DATA) while True: if device.waitForNotifications(self.timeout): break return self._temperature, self._humidity def getBattery(self, device): c = device.getCharacteristics(uuid=self.UUID_BATT)[0] return ord(c.read()) def subscribe(self, device, uuid): device.setDelegate(self) c = device.getCharacteristics(uuid=uuid)[0] d = c.getDescriptors(forUUID=0x2902)[0] d.write(0x01.to_bytes(2, byteorder="little"), withResponse=True) def processSensorsData(self, data): self._temperature = unpack("H", data[:2])[0] / 100 self._humidity = data[2] def handleNotification(self, handle, data): # 0x4b is sensors data if handle == 0x4b: self.processSensorsData(data)