diff --git a/src/citadel/scene/__init__.py b/src/citadel/scene/__init__.py index 9d33e2a..72f8f16 100644 --- a/src/citadel/scene/__init__.py +++ b/src/citadel/scene/__init__.py @@ -3,6 +3,7 @@ import threading import queue import logging import concurrent.futures +from typing import Union, Callable import pebble @@ -172,8 +173,9 @@ class WaitForTopic(threading.Thread, mqtt.Client): def __init__(self, config: Configuration, topic: str, - payload: str=None, + payload: Union[str, Callable[[str],bool]], logger: logging.Logger=None): + """ payload: str or function stop if payload is received or function return True, the function must have this signature func(payload: str) -> bool """ threading.Thread.__init__(self) mqtt.Client.__init__(self) @@ -181,7 +183,11 @@ class WaitForTopic(threading.Thread, mqtt.Client): self.configuration = config self.topic = topic - self.payload = payload + + if callable(payload): + self.check_payload = payload + else: + self.check_payload = lambda x: x == payload self.daemon = True @@ -209,9 +215,10 @@ class WaitForTopic(threading.Thread, mqtt.Client): self.logger.error('Invaliad topic: %s', topic) return - if self.payload and payload != self.payload: - return + if self.check_payload(payload): + self.__run = False + def stop(self): self.__run = False def run(self): @@ -275,23 +282,50 @@ class Publish(Action): if self.check_response: checker.join(timeout=self.check_response['timeout']) if checker.is_alive(): #timeout occur + checker.stop() return self.failed() return self.ok() -class Shutter(Action): +class TasmotaShutter(Action): + + TOPIC_FORMAT = "cmnd/tasmota/shutter/{shutter_name}/{action}" + TOPIC_CHECK_FORMAT = "stat/tasmota/shutter/{shutter_name}/SHUTTER1" + ACTION_OPEN = 'ShutterOpen' + ACTION_CLOSE = 'ShutterClose' def __init__(self, shutter_name: str): Action.__init__(self) - pass + self.shutter_name = shutter_name + self.action = None def Close(self): + self.action = self.ACTION_CLOSE return self def Open(self): + self.action = self.ACTION_OPEN return self def run(self): + if self.action is None: + raise RuntimeError('No action specified') + + topic = self.TOPIC_FORMAT.format( + shutter_name = self.shutter_name, + action = self.action + ) + + self.proxy.threaded_publish(topic, qos=1, retain=False) + + checker = WaitForTopic(self.proxy.configuration, + self.TOPIC_CHECK_FORMAT.format(shutter_name = self.shutter_name), + payload= '100' if self.action == self.ACTION_OPEN else '0') + + checker.join(timeout=30) + if checker.is_alive(): + return self.failed() + return self.ok() class Sleep(Scene): @@ -300,6 +334,10 @@ class Sleep(Scene): Publish('cmnd/tasmota/screen/Screen/POWER').Payload('OFF').Qos(2)\ .CheckResponse('stat/tasmota/screen/Screen/POWER', 'OFF', 3), Publish('cmnd/tasmota/light/LivingroomFireplace/POWER').Payload('OFF').Qos(2), + TasmotaShutter('Bedroom1x1').Close(), + TasmotaShutter('Bedroom1x2').Close(), + TasmotaShutter('Staircase1').Close(), + TasmotaShutter('Bathroom').Close(), ] WATCH = [