From 4003c41c3d41dd8f258d83300ca39b6829e2ab64 Mon Sep 17 00:00:00 2001 From: Ingolf Wagner Date: Wed, 17 Mar 2021 09:09:44 +0100 Subject: [PATCH] introduced fyrtur-control --- configs/pepe/home-assistant.nix | 282 +----------------- .../pepe/home-assistant/heater-control.nix | 15 + configs/pepe/home-assistant/light-control.nix | 37 ++- mqtt/fyrtur.py | 150 ++++++++++ mqtt/heater.py | 8 +- 5 files changed, 205 insertions(+), 287 deletions(-) create mode 100644 mqtt/fyrtur.py diff --git a/configs/pepe/home-assistant.nix b/configs/pepe/home-assistant.nix index b1dbbdb..866a6d9 100644 --- a/configs/pepe/home-assistant.nix +++ b/configs/pepe/home-assistant.nix @@ -54,22 +54,18 @@ in { # all scenes input_select.scene = { icon = "mdi:brightness-auto"; - options = [ "default" "night" "outside" "cooking" ]; + options = [ "up-bright" "up-dark" "half" "down" "night" "outside" ]; }; # scenes controlled by buttons input_select.scene_button = { icon = "mdi:brightness-auto"; - options = [ "default" "night" ]; + options = [ "up-dark" "night" ]; }; input_boolean.situation_toggle.icon = "mdi:toggle-switch"; input_boolean.printer_toggle.icon = "mdi:toggle-switch"; input_boolean.windows_up.icon = "mdi:toggle-switch"; - # heater scenes - #input_select.heater_state.options = [ "off" "on1" "on2" "on3" ]; - #input_select.heater_state_memory.options = [ "off" "on1" "on2" "on3" ]; - automation = [ { @@ -152,28 +148,7 @@ in { entity_id = "input_select.scene"; option = "outside"; }; - } - #{ - # service = "input_select.select_option"; - # data_template = { - # entity_id = "input_select.heater_state_memory"; - # option = '' - # {% if not is_state("input_select.heater_state", "off") %} - # {{ states('input_select.heater_state') }} - # {%- else -%} - # {{ states('input_select.heater_state_memory') }} - # {%- endif %} - # ''; - # }; - #} - #{ - # service = "input_select.select_option"; - # data = { - # entity_id = "input_select.heater_state"; - # option = "off"; - # }; - #} - ]; + }]; } { @@ -202,155 +177,11 @@ in { service = "input_select.select_option"; data = { entity_id = "input_select.scene"; - option = "default"; + option = "up-dark"; }; - } - #{ - # service = "input_select.select_option"; - # data_template = { - # entity_id = "input_select.heater_state"; - # option = "{{ states('input_select.heater_state_memory') }}"; - # }; - #} - ]; - } - - # window roles - { - alias = "windows state = down in the evening"; - trigger = [{ - platform = "sun"; - event = "sunset"; - offset = "+00:01:00"; # 10 min after sunset - }]; - action = [{ - service = "input_boolean.turn_off"; - data.entity_id = "input_boolean.windows_up"; - }]; - } - { - alias = "windows state = up in the morning"; - trigger = [{ - platform = "time"; - at = "08:30:00"; - }]; - action = [{ - service = "input_boolean.turn_on"; - data.entity_id = "input_boolean.windows_up"; - }]; - } - { - alias = "handle windows up state"; - trigger = [ - #{ - # platform = "time_pattern"; - # minutes = "/5"; # every 5 minutes - #} - { - platform = "state"; - entity_id = "input_boolean.windows_up"; - } - ]; - action = [{ - service = "script.turn_on"; - data_template.entity_id = '' - {% if is_state('input_boolean.windows_up','on') -%} - script.fyrtur_up - {%- else -%} - script.fyrtur_down - {%- endif %} - ''; }]; } - # heater - #{ - # alias = "heater state = on1 in the morning"; - # trigger = [{ - # platform = "time"; - # at = "09:00:00"; - # }]; - # action = [ - # { - # service = "input_select.select_option"; - # data = { - # entity_id = "input_select.heater_state"; - # option = "on1"; - # }; - # } - # { - # service = "input_select.select_option"; - # data = { - # entity_id = "input_select.heater_state_memory"; - # option = "on1"; - # }; - # } - # ]; - #} - #{ - # alias = "heater state = on2 in the early evening"; - # trigger = [{ - # platform = "time"; - # at = "22:30:00"; - # }]; - # action = [ - # { - # service = "input_select.select_option"; - # data = { - # entity_id = "input_select.heater_state"; - # option = "on2"; - # }; - # } - # { - # service = "input_select.select_option"; - # data = { - # entity_id = "input_select.heater_state_memory"; - # option = "on2"; - # }; - # } - # ]; - #} - #{ - # alias = "heater state = off in the evening"; - # trigger = [{ - # platform = "time"; - # at = "23:30:00"; - # }]; - # action = [ - # { - # service = "input_select.select_option"; - # data = { - # entity_id = "input_select.heater_state"; - # option = "off"; - # }; - # } - # { - # service = "input_select.select_option"; - # data = { - # entity_id = "input_select.heater_state_memory"; - # option = "off"; - # }; - # } - # ]; - #} - #{ - # alias = "handle heater state"; - # trigger = [ - # { - # platform = "time_pattern"; - # minutes = "/10"; # every 5 minutes - # } - # { - # platform = "state"; - # entity_id = "input_select.heater_state"; - # } - # ]; - # action = [{ - # service = "script.turn_on"; - # data_template.entity_id = - # "script.heater_{{ states('input_select.heater_state') }}"; - # }]; - #} ]; group = let @@ -405,111 +236,6 @@ in { }; - script = let - # delay in seconds - delay = 2; - - heater_on = heater: temperatur: { - service = "mqtt.publish"; - data_template = { - topic = "zigbee2mqtt/${heater}/set"; # office - payload_template = builtins.toJSON { - system_mode = "auto"; - current_heating_setpoint = toString temperatur; - eurotronic_host_flags.window_open = false; - }; - }; - }; - #heater_off = heater: { - # service = "mqtt.publish"; - # data_template = { - # topic = "zigbee2mqtt/${heater}/set"; # office - # payload_template = ''{"system_mode":"off"}''; - # }; - #}; - - hot = 23; - cold = 14; - - fyrtur_command = device: position: { - service = "mqtt.publish"; - data_template = { - topic = "zigbee2mqtt/${device}/set"; - payload_template = ''{"position":"${toString position}"}''; - }; - }; - - in { - #heater_off = { - # sequence = [ - # (heater_on "heater1" 5) # office - # { delay = delay; } - # (heater_on "heater2" 5) # office - # { delay = delay; } - # (heater_on "heater3" 5) # bed room - # { delay = delay; } - # (heater_on "heater4" 5) # storage room - # ]; - #}; - #heater_on1 = { - # sequence = [ - # (heater_on "heater1" hot) # office - # { delay = delay; } - # (heater_on "heater2" hot) # office - # { delay = delay; } - # (heater_on "heater3" cold) # bed room - # { delay = delay; } - # (heater_on "heater4" cold) # storage room - # ]; - #}; - #heater_on2 = { - # sequence = [ - # (heater_on "heater1" hot) # office - # { delay = delay; } - # (heater_on "heater2" hot) # office - # { delay = delay; } - # (heater_on "heater3" hot) # bed room - # { delay = delay; } - # (heater_on "heater4" cold) # storage room - # ]; - #}; - #heater_on3 = { - # sequence = [ - # (heater_on "heater1" cold) # office - # { delay = delay; } - # (heater_on "heater2" cold) # office - # { delay = delay; } - # (heater_on "heater3" hot) # bed room - # { delay = delay; } - # (heater_on "heater4" cold) # storage room - # ]; - #}; - - fyrtur_up = { - sequence = [ - (fyrtur_command "fyrtur1" 100) - { delay = delay; } - (fyrtur_command "fyrtur2" 100) - { delay = delay; } - (fyrtur_command "fyrtur3" 100) - { delay = delay; } - (fyrtur_command "fyrtur4" 100) - ]; - }; - - fyrtur_down = { - sequence = [ - (fyrtur_command "fyrtur1" 16) - { delay = delay; } - (fyrtur_command "fyrtur2" 16) - { delay = delay; } - (fyrtur_command "fyrtur3" 22) - { delay = delay; } - (fyrtur_command "fyrtur4" 22) - ]; - }; - - }; }; diff --git a/configs/pepe/home-assistant/heater-control.nix b/configs/pepe/home-assistant/heater-control.nix index 99dd39d..d778358 100644 --- a/configs/pepe/home-assistant/heater-control.nix +++ b/configs/pepe/home-assistant/heater-control.nix @@ -14,4 +14,19 @@ ${myPython}/bin/python heater.py ''; }; + + users.users.fyrtur-control = { }; + + systemd.services.fyrtur-control = { + enable = true; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { User = "fyrtur-control"; }; + script = + let myPython = pkgs.python3.withPackages (ps: with ps; [ paho-mqtt ]); + in '' + cd ${} + ${myPython}/bin/python fyrtur.py + ''; + }; } diff --git a/configs/pepe/home-assistant/light-control.nix b/configs/pepe/home-assistant/light-control.nix index a571f46..8b589f8 100644 --- a/configs/pepe/home-assistant/light-control.nix +++ b/configs/pepe/home-assistant/light-control.nix @@ -10,9 +10,40 @@ }; scenes = [ { - name = "default"; - ignored_sensors = - [ "zigbee2mqtt/door_sensor_5" "zigbee2mqtt/door_sensor_1" ]; + name = "up-dark"; + ignored_sensors = [ + "zigbee2mqtt/door_sensor_1" + "zigbee2mqtt/door_sensor_4" + "zigbee2mqtt/door_sensor_5" + ]; + } + { + name = "half"; + ignored_sensors = [ + "zigbee2mqtt/door_sensor_1" + "zigbee2mqtt/door_sensor_4" + "zigbee2mqtt/door_sensor_5" + ]; + } + { + name = "down"; + ignored_sensors = [ + "zigbee2mqtt/door_sensor_1" + "zigbee2mqtt/door_sensor_4" + "zigbee2mqtt/door_sensor_5" + ]; + } + { + name = "up-bright"; + disabled_switches = [ + "zigbee2mqtt/led_1" + "zigbee2mqtt/led_2" + "zigbee2mqtt/light_2" + "zigbee2mqtt/light_4" + "zigbee2mqtt/light_5" + "zigbee2mqtt/light_7" + ]; + ignored_sensors = [ "zigbee2mqtt/door_sensor_4" ]; } { name = "outside"; diff --git a/mqtt/fyrtur.py b/mqtt/fyrtur.py new file mode 100644 index 0000000..3fcddc3 --- /dev/null +++ b/mqtt/fyrtur.py @@ -0,0 +1,150 @@ +import time +from enum import Enum +from typing import Dict + +import paho.mqtt.client as mqtt +import json +import threading + +# initial scene +from heater.modules.heater import Heater +from heater.modules.watcher import Watcher + +scene = "up-dark" + + +class Position(Enum): + UP = 1 + DOWN = 2 + HALF = 3 + + +class Fyrtur: + + def __init__(self, topic, set_topic, top, bottom): + self.topic = topic + self.set_topic = set_topic + self.top = top + self.bottom = bottom + self.current_position = 100 + self.wanted_position = 100 + + def update_position(self, payload): + self.current_position = payload["position"] + + def needs_publish(self): + return self.wanted_position != self.current_position + + def payload(self): + payload = {"position": self.wanted_position} + return json.dumps(payload) + + +class FyrturWatcher: + def __init__(self, fyrturs: Dict[str, Fyrtur]): + self.fyrturs = fyrturs + + def get_topics(self): + return [fyrtur.topic for fyrtur in self.fyrturs.values()] + + def update_position(self, topic, payload): + for fyrtur in self.fyrturs.values(): + if fyrtur.topic == topic: + fyrtur.update_position(payload) + return + + def update(self, name, position : Position): + fyrtur: Fyrtur = self.fyrturs.get(name) + if position == Position.UP: + fyrtur.wanted_position = fyrtur.top + elif position == Position.DOWN: + fyrtur.wanted_position = fyrtur.bottom + elif position == Position.HALF: + fyrtur.wanted_position = round((fyrtur.top - fyrtur.bottom) / 2) + + def publish(self, client): + for fyrtur in self.fyrturs.values(): + if fyrtur.needs_publish(): + client.publish(fyrtur.set_topic, fyrtur.payload()) + time.sleep(2) + + + +watcher = FyrturWatcher({ + "office1": Fyrtur(topic="zigbee2mqtt/fyrtur1", set_topic="zigbee2mqtt/fyrtur1/set", top=100, bottom=16), + "office2": Fyrtur(topic="zigbee2mqtt/fyrtur4", set_topic="zigbee2mqtt/fyrtur4/set", top=100, bottom=22), + "bedroom": Fyrtur(topic="zigbee2mqtt/fyrtur2", set_topic="zigbee2mqtt/fyrtur2/set", top=100, bottom=16), +}) + + +# The callback for when the client receives a CONNACK response from the server. +def on_connect(client, _userdata, _flags, rc): + print("Connected with result code " + str(rc)) + + threading.Thread(target=loop_thread, args=(client,), daemon=True).start() + + # Subscribing in on_connect() means that if we lose the connection and + # reconnect then subscriptions will be renewed. + client.subscribe("control/lights/set") + for topic in watcher.get_topics(): + client.subscribe(topic) + # watcher.pull_values(client) + + +# The callback for when a PUBLISH message is received from the server. +def on_message(client, _userdata, msg): + global scene + (topic, payload) = parse_message(msg) + if topic == "control/lights/set": + print("set scene %s -> %s" % (scene, payload['scene'])) + scene = payload['scene'] + update_scene(client) + else: + print("got %s" % topic) + watcher.update_position(topic, payload) + + +def parse_message(msg): + m_decode = str(msg.payload.decode("utf-8", "ignore")) + payload = json.loads(m_decode) # decode json data + return msg.topic, payload + + +def update_scene(client): + if scene in ["night", "down"]: + watcher.update("office1", Position.DOWN) + watcher.update("office2", Position.DOWN) + watcher.update("bedroom", Position.DOWN) + elif scene in ["default", "up-bright", "up-dark"]: + watcher.update("office1", Position.UP) + watcher.update("office2", Position.UP) + watcher.update("bedroom", Position.UP) + elif scene in ["half"]: + watcher.update("office1", Position.HALF) + watcher.update("office2", Position.HALF) + watcher.update("bedroom", Position.HALF) + else: + watcher.update("office1", Position.DOWN) + watcher.update("office2", Position.DOWN) + watcher.update("bedroom", Position.DOWN) + + watcher.publish(client) + + +def loop_thread(client): + while True: + watcher.publish(client) + time.sleep(120) + + +if __name__ == "__main__": + mqttClient = mqtt.Client() + mqttClient.on_connect = on_connect + mqttClient.on_message = on_message + mqttClient.username_pw_set("homeassistant", password="password") + mqttClient.connect("pepe.private", 1883, 60) + # Blocking call that processes network traffic, dispatches callbacks and + # handles reconnecting. + # Other loop*() functions are available that give a threaded interface and a + # manual interface. + mqttClient.loop_forever() diff --git a/mqtt/heater.py b/mqtt/heater.py index afcacfe..616cc9b 100644 --- a/mqtt/heater.py +++ b/mqtt/heater.py @@ -52,18 +52,14 @@ def parse_message(msg): def update_scene(client): - if scene == "night": + if scene in ["night", "outside"]: watcher.update("office1", 10) watcher.update("office2", 10) watcher.update("bedroom", 13) - elif scene == "default": + elif scene in ["default", "up-bright", "up-dark", "half", "down"]: watcher.update("office1", 23) watcher.update("office2", 23) watcher.update("bedroom", 18) - elif scene == "outside": - watcher.update("office1", 10) - watcher.update("office2", 10) - watcher.update("bedroom", 13) else: watcher.update("office1", 10) watcher.update("office2", 10)