diff --git a/appdaemon/apps/apps.yaml b/appdaemon/apps/apps.yaml new file mode 100644 index 00000000..3609d527 --- /dev/null +++ b/appdaemon/apps/apps.yaml @@ -0,0 +1,4 @@ +--- +nspanel: + module: nspanel + class: NsPanelLovelanceUIManager diff --git a/appdaemon/apps/nspanel.py b/appdaemon/apps/nspanel.py new file mode 100644 index 00000000..437a0c66 --- /dev/null +++ b/appdaemon/apps/nspanel.py @@ -0,0 +1,137 @@ +import os +import json +import appdaemon.plugins.hass.hassapi as hass + +class NsPanelLovelanceUIManager(hass.Hass): + def initialize(self): + + # Check if config folder exists + config_folder = "/config/appdaemon/nspanel_config" + if not os.path.exists(config_folder): + self.log("Config folder not found, creating ...") + os.makedirs(config_folder) + + # Check config folder for config files + for file in os.listdir(config_folder): + filename = os.fsdecode(file) + if filename.endswith(".json"): + filename = os.path.join(config_folder, filename) + self.log("Found Config file: %s", filename) + # Parse config file + with open(filename, 'r') as f: + data = json.loads(f.read()) + # Create Instance of NsPanelLovelanceUI class + NsPanelLovelanceUI(self, data) + + +class NsPanelLovelanceUI: + def __init__(self, api, config): + self.api = api + self.config = config + self.current_page_nr = 0 + + #Setup, mqtt subscription and callbacks + self.mqtt = self.api.get_plugin_api("MQTT") + self.mqtt.mqtt_subscribe(topic=self.config["panelRecvTopic"]) + self.mqtt.listen_event(self.handle_mqtt_incoming_message, "MQTT_MESSAGE", topic=self.config["panelRecvTopic"], namespace='mqtt') + + + def handle_mqtt_incoming_message(self, event_name, data, kwargs): + #Parse Json Message from Tasmota and strip out message from nextion display + msg = json.loads(data["payload"])["CustomRecv"] + self.api.log("Recived Message from Tasmota: %s", msg) + + # Split message into parts seperated by "," + msg = msg.split(",") + + # run action based on received command + # TODO: replace with match case after appdeamon container swiched to python 3.10 - https://pakstech.com/blog/python-switch-case/ - https://www.python.org/dev/peps/pep-0636/ + if msg[0] == "event": + if msg[1] == "pageOpen": + # Calculate current page + recv_page = int(msg[2]) + self.current_page_nr = recv_page % len(self.config["pages"]) + self.api.log("received pageOpen command, raw page: %i, calc page: %i", recv_page, self.current_page_nr) + page_type = self.config["pages"][self.current_page_nr]["type"] + self.generate_page(self.current_page_nr, page_type) + + if msg[1] == "buttonPress": + self.api.log("received buttonPress command") + # TODO: implement button press function + + if msg[1] == "pageOpenDetail": + self.api.log("received pageOpenDetail command") + # TODO: implement pageOpenDetail function + + if msg[1] == "tempUpd": + self.api.log("received tempUpd command") + # TODO: implement tempUpd function + + def send_mqtt_msg(self,msg): + self.mqtt.mqtt_publish(self.config["panelSendTopic"], msg) + + + def generate_entities_item(self, item, item_nr, item_type): + self.api.log("generating item command for %s with type %s", item, item_type) + + if item_type == "delete": + return "entityUpd,{0},{1},{2},{3}".format(item_nr, 0, "dc", item_type) + + + entity = self.api.get_entity(item) + name = entity.attributes.friendly_name + + if item_type == "cover": + return "entityUpd,{0},{1},{2},{3}".format(item_nr, 0, name, "shutter") # TODO: shutter should be renamed to cover in the nextion project + + if item_type == "light": + switch_val = 1 if entity.state == "on" else 0 + return "entityUpd,{0},{1},{2},{3},{4}".format(item_nr, 1, name, item_type, switch_val) + + if item_type == "switch": + switch_val = 1 if entity.state == "on" else 0 + return "entityUpd,{0},{1},{2},{3},{4}".format(item_nr, 4, name, item_type, switch_val) + + if item_type == "sensor": + icon_id = 0 + icon_id = { + "temperature": 2 + }[entity.attributes.device_class] + + value = entity.state + " " + entity.attributes.unit_of_measurement + return "entityUpd,{0},{1},{2},{3},{4}".format(item_nr, icon_id, name, "text", value) + + if item_type == "button": + return "entityUpd,{0},{1},{2},{3},{4}".format(item_nr, 3, name, item_type, "PRESS") + + def generate_thermo_page(self, item): + entity = self.api.get_entity(item) + heading = entity.attributes.friendly_name + current_temp = entity.attributes.current_temperature*10 + dest_temp = entity.attributes.temperature*10 + status = entity.attributes.hvac_action + min_temp = entity.attributes.min_temp*10 + max_temp = entity.attributes.max_temp*10 + step_temp = 0.5*10 + + return "entityUpd,{0},{1},{2},{3},{4},{5},{6}".format(heading, current_temp, dest_temp, status, min_temp, max_temp, step_temp) + + + def generate_page(self, page_number, page_type): + self.api.log("generating page commands for page %i with type %s", self.current_page_nr, page_type) + if page_type == "cardEntities": + # Set Heading of Page + self.send_mqtt_msg("entityUpdHeading,{0}".format(self.config["pages"][self.current_page_nr]["heading"])) + + # Set Items of Page + current_item_nr = 0 + for item in self.config["pages"][self.current_page_nr]["items"]: + current_item_nr += 1 + # type of item is the string before the "." in the item name + item_type = item.split(".")[0] + command = self.generate_entities_item(item, current_item_nr, item_type) + self.send_mqtt_msg(command) + + if page_type == "cardThermo": + command = self.generate_thermo_page(self.config["pages"][self.current_page_nr]["item"]) + self.send_mqtt_msg(command) \ No newline at end of file diff --git a/appdaemon/nspanel_config/shutters.json b/appdaemon/nspanel_config/shutters.json new file mode 100644 index 00000000..6454c408 --- /dev/null +++ b/appdaemon/nspanel_config/shutters.json @@ -0,0 +1,15 @@ +{ + "panelRecvTopic": "tele/tasmota_NsPanelTerrasse/RESULT", + "panelSendTopic": "cmnd/tasmota_NsPanelTerrasse/CustomSend", + "pages": [{ + "type": "cardEntities", + "heading": "Rolladen", + "items": ["cover.rolladenfenster_cover_1", "cover.nspterrasse_cover_1", "cover.rolladenterasse_cover_1", "sensor.nspterrasse_analog_temperature1"] + }, + { + "type": "cardEntities", + "heading": "TestPage", + "items": ["button.beamer_key_left", "cover.rolladenterasse_cover_1", "light.schreibtischlampe", "delete"] + } + ] +} \ No newline at end of file