From 3195ad1f2098ad9e0c2d16357eec2b7809a17238 Mon Sep 17 00:00:00 2001 From: zewelor Date: Thu, 23 Nov 2017 22:02:41 +0100 Subject: [PATCH] Add xiaomi scale + dynamic requirements support --- gateway.py | 8 +++---- requirements.txt | 1 - workers/miscale.py | 50 +++++++++++++++++++++++++++++++++++++++++++ workers/thermostat.py | 31 ++++++++++++--------------- workers_manager.py | 29 ++++++++++++++++++------- 5 files changed, 88 insertions(+), 31 deletions(-) create mode 100644 workers/miscale.py diff --git a/gateway.py b/gateway.py index 9c5ee45..7ddd864 100644 --- a/gateway.py +++ b/gateway.py @@ -4,8 +4,6 @@ from workers_manager import WorkersManager mqtt = MqttClient(settings['mqtt']) -workers = WorkersManager(settings['workers']) -mqtt.publish(workers.get_updates()) - -# args = settings['workers']['thermostat']['args'] -# mqtt.publish(ThermostatWorker(**args).status_update()) \ No newline at end of file +manager = WorkersManager(settings['workers']) +for worker_name in manager.workers.keys(): + mqtt.publish(manager.update(worker_name)) diff --git a/requirements.txt b/requirements.txt index 1561b6e..fd3e4f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ paho-mqtt -python-eq3bt pyyaml diff --git a/workers/miscale.py b/workers/miscale.py new file mode 100644 index 0000000..2d6a6dc --- /dev/null +++ b/workers/miscale.py @@ -0,0 +1,50 @@ +import time + +REQUIREMENTS = ['bluepy', 'interruptingcow'] + +class MiscaleWorker(): + def __init__(self, mac): + self._mac = mac + + def status_update(self): + return [{ + 'topic': 'weight/kg', + 'payload': self._get_weight(), + }] + + + def _get_weight(self): + from bluepy import btle + from interruptingcow import timeout + + scan_processor = ScanProcessor(self._mac) + scanner = btle.Scanner().withDelegate(scan_processor) + scanner.scan(5, passive=True) + + with timeout(5): + while scan_processor.weight is None: + time.sleep(1) + return scan_processor.weight + + return -1 + +class ScanProcessor(): + def __init__(self, mac): + self._mac = mac + self._weight = None + + def handleDiscovery(self, dev, isNewDev, _): + if dev.addr == self.mac.lower() and isNewDev: + for (sdid, desc, data) in dev.getScanData(): + if data.startswith('1d18') and sdid == 22: + measured = int((data[8:10] + data[6:8]), 16) * 0.01 + + self._weight = round(measured / 2, 2) + + @property + def mac(self): + return self._mac + + @property + def weight(self): + return self._weight diff --git a/workers/thermostat.py b/workers/thermostat.py index 446e73f..79a9e24 100644 --- a/workers/thermostat.py +++ b/workers/thermostat.py @@ -1,22 +1,23 @@ -from eq3bt import Thermostat, Mode - -haModesMapping = { - Mode.Closed: 'off', - Mode.Open: 'on', - Mode.Auto: 'auto', - Mode.Manual: 'manual', - Mode.Away: 'away', - Mode.Boost: 'boost', -} +REQUIREMENTS = ['python-eq3bt'] monitoredAttrs = ["low_battery", "valve_state", "target_temperature"] class ThermostatWorker: - def __init__(self, devices=None, topic_prefix=None): + def __init__(self, devices=None): self._devices = devices - self._topic_prefix = topic_prefix def status_update(self): + from eq3bt import Thermostat, Mode + + haModesMapping = { + Mode.Closed: 'off', + Mode.Open: 'on', + Mode.Auto: 'auto', + Mode.Manual: 'manual', + Mode.Away: 'away', + Mode.Boost: 'boost', + } + ret = [] for name, mac in self.devices.items(): thermostat = Thermostat(mac) @@ -44,9 +45,5 @@ class ThermostatWorker: def devices(self): return self._devices - @property - def topic_prefix(self): - return self._topic_prefix - def format_topic(self, device, attr): - return '/'.join([self.topic_prefix, device, attr]) + return '/'.join([device, attr]) diff --git a/workers_manager.py b/workers_manager.py index 3378fc9..6abff12 100644 --- a/workers_manager.py +++ b/workers_manager.py @@ -1,24 +1,37 @@ import importlib - +import pip class WorkersManager: def __init__(self, config): self._config = config self._workers = self._register_klasses() - def get_updates(self): - lazy_map = map(lambda worker: worker.status_update(), self._workers) - flat_list = sum(lazy_map, []) # List flatten - return flat_list + def update(self, worker_name): + messages = self.workers[worker_name].status_update() + for message in messages: + message['topic'] = '/'.join([self._config[worker_name]['topic_prefix'], message['topic']]) + + return messages + + @property + def workers(self): + return self._workers def _register_klasses(self): - arr = [] + arr = {} for worker_klass in self._config.keys(): - klass = getattr(importlib.import_module("workers.%s" % worker_klass), "%sWorker" % worker_klass.title()) - arr.append(klass(**self._args_for(worker_klass))) + moduleObj = importlib.import_module("workers.%s" % worker_klass) + klass = getattr(moduleObj, "%sWorker" % worker_klass.title()) + if moduleObj.REQUIREMENTS is not None: + self._pip_install_helper(moduleObj.REQUIREMENTS) + arr[worker_klass] = klass(**self._args_for(worker_klass)) return arr def _args_for(self, klass): return self._config[klass]['args'] + + def _pip_install_helper(self, package_names): + for package in package_names: + pip.main(['install','-q', package])