From 38e75b97da251e726f22053a1da9697e7c008d8d Mon Sep 17 00:00:00 2001 From: joBr99 <29555657+joBr99@users.noreply.github.com> Date: Thu, 24 Mar 2022 16:12:42 +0100 Subject: [PATCH 01/10] redesign of config format --- HMI/README.md | 6 +- HMI/nspanel.HMI | Bin 7418507 -> 7418507 bytes HMI/nspanel.tft | Bin 6287476 -> 6287408 bytes appdaemon/apps.yaml | 77 +- apps/nspanel-lovelace-ui/luibackend/config.py | 118 +++ .../luibackend/controller.py | 50 ++ .../luibackend/execptions.py | 8 + .../{ => luibackend}/helper.py | 0 .../{ => luibackend}/icon_mapping.py | 0 .../{ => luibackend}/icons.py | 0 .../luibackend/mqttListener.py | 38 + apps/nspanel-lovelace-ui/luibackend/pages.py | 165 +++++ .../nspanel-lovelace-ui.py | 670 ++---------------- 13 files changed, 467 insertions(+), 665 deletions(-) create mode 100644 apps/nspanel-lovelace-ui/luibackend/config.py create mode 100644 apps/nspanel-lovelace-ui/luibackend/controller.py create mode 100644 apps/nspanel-lovelace-ui/luibackend/execptions.py rename apps/nspanel-lovelace-ui/{ => luibackend}/helper.py (100%) rename apps/nspanel-lovelace-ui/{ => luibackend}/icon_mapping.py (100%) rename apps/nspanel-lovelace-ui/{ => luibackend}/icons.py (100%) create mode 100644 apps/nspanel-lovelace-ui/luibackend/mqttListener.py create mode 100644 apps/nspanel-lovelace-ui/luibackend/pages.py diff --git a/HMI/README.md b/HMI/README.md index 6be70ec0..850b39f6 100644 --- a/HMI/README.md +++ b/HMI/README.md @@ -71,6 +71,8 @@ change the page type: `pageType,popupNotify` +`pageType,screensaver` + ### screensaver page `weatherUpdate,? tMainIcon? tMainText? tMRIcon? tMR? tForecast1? tF1Icon? tForecast1Val? tForecast2? tF2Icon? tForecast2Val` @@ -79,6 +81,8 @@ change the page type: `page,0` +`entityUpd,heading_of_exit_page` + ### cardEntities Page The following message can be used to update the content on the cardEntities Page @@ -142,7 +146,7 @@ The following message can be used to update the content on the cardEntities Page ### screensaver page -`event,screensaverOpen` +`event,buttonPress2,screensaver,enter` ### cardEntities Page diff --git a/HMI/nspanel.HMI b/HMI/nspanel.HMI index 054fa6abedab0f36bf4048cba2ae40bee8ec527e..fa7f39f469e9bb00617b24fc9ca916754abcd297 100644 GIT binary patch delta 2279 zcmeIze@s(X6bJD0URz4q3~6cEKt(_XTCuj&SAJ9lVK{NRL-}aX` z`Gmu{=f2+Fd)oF)kLh}Vs~zI({Ngkt1sX!mjho9z9fyYEsfH)wcnW*?%UnmUbAMoN zQvRD2wbGqB1eX?Yba4SUF_LC{@a`s^oJ-uDKyjGj>YOxjz=IC-V1O8D(qzn-d3@ym`;i}xKWzEjniY*%8~Jg~ojNu$E_yqz zp*Q3aH4vqsA?1-6OZ2H4kC}+x(JA@u=;o4zO5DGk$T-{CL<0_@6KajyNz|>z7u`h1 z)woO``aq3;%_Gt&Ydh$-{J;O9y9Go?)S}ndX%96Q6P;4?1EtzSSK)(tRLx&1*XqkF zi2Bw1^fs;jK$TWw*d^yvm3MQpn&>@sfpd@c4y2%DxhkIJ`5X@$`bNAf4Sy?M)w9t* zgmg=5ZA&m1Y7MjnTe5?p1Hl${*=M^g^?WaOd00xN<)n1Z=r=9v8!#s`SD{7EdhN~e zlF#XH=6hPFWIYYoiOZcSztU;5u?t+^IU=R_a@Y+#2(coLq1BuIvZ zum~1I3M_$CNP~1(3K{S;WI`4!gXQ3W6_5=p!3nFt1#ZXz53B}3nh1D5%QBdc-R_uS z*->kKht8&)T7J6L14T=Zu%nUX;oO4zW$|wcKIyw7UR@lQE8V&&m||Jc zl$a?+vO_y`lK7$b7SDQbSeKaR-6nGnicZ$pD43<|_Jkii>_l~fO`3}H4(f54xJQ>o zFQrxp*&K5XHmzqZZEvs}O+w1QefT@^TMK@uhTTvD0oVhz d@G=CU4(eeqG(aQlgC=+dLeMM)F6?)-{{=`_fbIYQ delta 1868 zcmeIyZERCj7zgm@-o8qO%-(jqZKFl(WQ?tQ=^b+x>v*vfCKNXuu9Fr9bL%UUEPi6e z)sz8-X<-LLATtsY9YN|$&WIW7GEIyaw#49v8F33)qGTp~FcYJR{`*3j_|dO=l3(sW z_ctRr(PFA%ET_pgWF@3D%1OKh*ugUI-Odas*Rb|Eraa}T@x}!OTjsiEKx@wv^i368)q?aUw(`?aHVq(?4D*(l4i9DgD?7sejCqB~`KUBqx_guZ~?S8qV_Z=y71}4o!8X=Rv zUQ6_@B9mJ!L?^QO-g=@BvN^huXjL{}vgYQOSLbrFhG-x=XLlXZa5n$3nJAITC3Lku zx4^!g=r9gqYJBJ!AL%8F$|6`#9nTcTx-S+|9cmj+uD+fdD3MbZM>zs}Ij9FK+b2N%LWaMQ5dY-K~TtYvnU)XBmNzCJee zz;EE6+?7uV@gbd(-TWx@B{ON|L(GwXB*CYha!|)AwuQgtrbWF*WUiaGGc0764BYyi zJf)0Hc_lD_5%OU`F+2_Hp#(NSDQtu?CCH7cB^A4V*8Ov4 z>t| zhUdTm&qF;@O?ftR5Pnqe=z0xhr)Uge|ntpz^y zd%xeQJKDpvGl9i`P?Twz;nrL~f9lS)*cMN&pKl!`dP|*|-`JI#-{d1|$hO|aaPI!) zBSep9{}I=_iB4x)^2+xCvt3b9TbpXiW-VW14*sIypC4-^n*O?f?J) diff --git a/HMI/nspanel.tft b/HMI/nspanel.tft index e02f397ac32ce88aa3fde8eb2fc49b56f67a9e47..1f8f24b408919f75add271b2660abbda6068275e 100644 GIT binary patch delta 16297 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 F{|}{c+lK%E delta 15894 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 Y15=o3n&vUnwT9l+-|_ft|J}#`2hQjJ>;M1& diff --git a/appdaemon/apps.yaml b/appdaemon/apps.yaml index 629e2f8a..5f6b33ae 100644 --- a/appdaemon/apps.yaml +++ b/appdaemon/apps.yaml @@ -1,59 +1,30 @@ --- nspanel: - module: nspanel-lovelace-ui + module: nspanel-lovelace-ui2 class: NsPanelLovelaceUIManager config: - panelRecvTopic: "tele/tasmota_your_mqtt_topic/RESULT" - panelSendTopic: "cmnd/tasmota_your_mqtt_topic/CustomSend" - updateMode: auto-notify # possible values are auto, auto-notify and manual - timeoutScreensaver: 15 #in seconds - #brightnessScreensaver: 10 - brightnessScreensaver: - - time: "7:00:00" - value: 10 - - time: "23:00:00" - value: 0 - locale: "de_DE" # only used if babel python package is installed - dateFormatBabel: "full" # only used if babel python package is installed - # formatting options on https://babel.pocoo.org/en/latest/dates.html?highlight=name%20of%20day#date-fields - timeFormat: "%H:%M" - dateFormat: "%A, %d. %B %Y" # ignored if babel python package is installed - weatherEntity: weather.example + panelRecvTopic: "tele/tasmota_nspdebugtest/RESULT" + panelSendTopic: "cmnd/tasmota_nspdebugtest/CustomSend" pages: - - type: cardEntities - heading: Example Page 1 + - type: screensaver + weather: weather.k3ll3r items: - - cover.example_cover - - switch.example_switch - - input_boolean.example_input_boolean - - sensor.example_sensor - - type: cardEntities - heading: Example Page 2 - items: - - button.example_button - - input_button.example_input_button - - light.light_example - - delete # (read this as 'empty') - - type: cardEntities - heading: Example Page 3 - items: - - scene.example_scene - - delete - - delete - - delete - - type: cardGrid - heading: Example Page 4 - items: - - light.light_example - - button.example_button - - cover.example_cover - - scene.example_scene - - switch.example_switch - - delete - - type: cardThermo - heading: Exmaple Thermostat - item: climate.example_climate - - type: cardMedia - heading: Exampe Media - item: media_player.spotify_user - + - type: cardEntities + heading: Test Entities 1 + items: + - switch.test_item + - type: cardEntities + heading: Test Entities 1 + items: + - switch.test_item + - switch.deckenbeleuchtung_hinten + - switch.test_item + - switch.test_item + - switch.deckenbeleuchtung_hinten + - switch.test_item + - type: cardGrid + heading: Test Grid 1 + items: + - switch.test_item + - switch.test_item + - switch.test_item diff --git a/apps/nspanel-lovelace-ui/luibackend/config.py b/apps/nspanel-lovelace-ui/luibackend/config.py new file mode 100644 index 00000000..2794e824 --- /dev/null +++ b/apps/nspanel-lovelace-ui/luibackend/config.py @@ -0,0 +1,118 @@ +import logging + +from luibackend.exceptions import LuiBackendConfigIncomplete +from luibackend.exceptions import LuiBackendConfigError + + +LOGGER = logging.getLogger(__name__) + + +class PageNode(object): + def __init__(self, data, parent=None): + self.data = data + self.name = None + self.childs = [] + self.parent = parent + + if "items" in data: + childs = data.pop("items") + for page in childs: + self.add_child(PageNode(page, self)) + + name = self.data.get("heading", "unkown") if type(self.data) is dict else self.data + ptype = self.data.get("type", "unkown") if type(self.data) is dict else "leaf" + #parent = self.parent.data.get("heading", self.parent.data.get("type", "unknown")) if self.parent is not None else "root" + + self.name = f"{ptype}.{name}" if type(self.data) is dict else self.data + + def add_child(self, obj): + self.childs.append(obj) + + def dump(self, indent=0): + """dump tree to string""" + tab = ' '*(indent-1) + ' |- ' if indent > 0 else '' + name = self.name + parent = self.parent.name if self.parent is not None else "root" + dumpstring = f"{tab}{name} -> {parent} \n" + for obj in self.childs: + dumpstring += obj.dump(indent + 1) + return dumpstring + + def get_items(self): + items = [] + for i in self.childs: + if len(i.childs) > 0: + items.append("navigate.todo") + else: + items.append(i.data) + return items + + + +class LuiBackendConfig(object): + + _DEFAULT_CONFIG = { + 'panelRecvTopic': "tele/tasmota_your_mqtt_topic/RESULT", + 'panelSendTopic': "cmnd/tasmota_your_mqtt_topic/CustomSend", + 'updateMode': "auto-notify", + 'timeoutScreensaver': 20, + 'brightnessScreensaver': 20, + 'locale': "en_US", + 'timeFormat': "%H:%M", + 'dateFormatBabel': "full", + 'dateFormat': "%A, %d. %B %Y", + 'pages': [{ + 'type': 'screensaver', + 'weather': 'weather.example', + 'items': [{ + 'type': 'cardEntities', + 'heading': 'Test Entities 1', + 'items': ['switch.test_item', 'switch.test_item', 'switch.test_item'] + }, { + 'type': 'cardGrid', + 'heading': 'Test Grid 1', + 'items': ['switch.test_item', 'switch.test_item', 'switch.test_item'] + } + ], + }], + } + + def __init__(self, args=None, check=True): + self._config = {} + self._page_config = None + + if args: + self.load(args) + + if check: + self.check() + + def load(self, args): + for k, v in args.items(): + if k in self._DEFAULT_CONFIG: + self._config[k] = v + LOGGER.info(f"Loaded config: {self._config}") + + root_page = self.get("pages")[0] + self._page_config = PageNode(root_page) + + LOGGER.info(f"Parsed Page config to the following Tree: \n {self._page_config.dump()}") + + def check(self): + errors = 0 + + def get(self, name): + value = self._config.get(name) + if value is None: + value = self._DEFAULT_CONFIG.get(name) + return value + + def get_root_page(self): + return self._page_config + + def get_child_by_heading(self, heading): + for page in self._current_page.childs: + if heading == page.data["heading"]: + self._current_page = page + return self._current_page + diff --git a/apps/nspanel-lovelace-ui/luibackend/controller.py b/apps/nspanel-lovelace-ui/luibackend/controller.py new file mode 100644 index 00000000..f6009fd3 --- /dev/null +++ b/apps/nspanel-lovelace-ui/luibackend/controller.py @@ -0,0 +1,50 @@ +import logging +import datetime + +from pages import LuiPages + +LOGGER = logging.getLogger(__name__) + +class LuiController(object): + + def __init__(self, ha_api, config, send_mqtt_msg): + self._ha_api = ha_api + self._config = config + self._send_mqtt_msg = send_mqtt_msg + + self._current_page = None + self._previous_page = None + + self._pages = LuiPages(ha_api, config, send_mqtt_msg) + # Setup time update callback + time = datetime.time(0, 0, 0) + ha_api.run_minutely(self._pages.update_time, time) + + # send panel back to startup page on restart of this script + self._pages.page_type("pageStartup") + + #{'type': 'sceensaver', 'weather': 'weather.k3ll3r', 'items': [{'type': 'cardEntities', 'heading': 'Test Entities 1', 'items': ['switch.test_item', {'type': 'cardEntities', 'heading': 'Test Entities 1', 'items': ['switch.test_item', 'switch.test_item', 'switch.test_item', 'switch.test_item']}, 'switch.test_item', 'switch.test_item']}, {'type': 'cardGrid', 'heading': 'Test Grid 1', 'items': ['switch.test_item', 'switch.test_item', 'switch.test_item']}]} + + + def startup(self, display_firmware_version): + LOGGER.info(f"Startup Event; Display Firmware Version is {display_firmware_version}") + # send time and date on startup + self._pages.update_time("") + self._pages.update_date("") + + # send panel to root page + self._current_page = self._config.get_root_page() + self._pages.render_page(self._current_page) + + + def next(self): + return + + def button_press(self, entity_id, btype, value): + LOGGER.debug(f"Button Press Event; entity_id: {entity_id}; btype: {btype}; value: {value} ") + if(entity_id == "screensaver" and btype == "enter"): + if self._previous_page is None: + self._pages.render_page(self._current_page.childs[0]) + else: + self._pages.render_page(self._previous_page) + diff --git a/apps/nspanel-lovelace-ui/luibackend/execptions.py b/apps/nspanel-lovelace-ui/luibackend/execptions.py new file mode 100644 index 00000000..b5a8e217 --- /dev/null +++ b/apps/nspanel-lovelace-ui/luibackend/execptions.py @@ -0,0 +1,8 @@ +class LuiBackendException(Exception): + pass + +class LuiBackendConfigIncomplete(LuiBackendException): + pass + +class LuiBackendConfigError(LuiBackendException): + pass \ No newline at end of file diff --git a/apps/nspanel-lovelace-ui/helper.py b/apps/nspanel-lovelace-ui/luibackend/helper.py similarity index 100% rename from apps/nspanel-lovelace-ui/helper.py rename to apps/nspanel-lovelace-ui/luibackend/helper.py diff --git a/apps/nspanel-lovelace-ui/icon_mapping.py b/apps/nspanel-lovelace-ui/luibackend/icon_mapping.py similarity index 100% rename from apps/nspanel-lovelace-ui/icon_mapping.py rename to apps/nspanel-lovelace-ui/luibackend/icon_mapping.py diff --git a/apps/nspanel-lovelace-ui/icons.py b/apps/nspanel-lovelace-ui/luibackend/icons.py similarity index 100% rename from apps/nspanel-lovelace-ui/icons.py rename to apps/nspanel-lovelace-ui/luibackend/icons.py diff --git a/apps/nspanel-lovelace-ui/luibackend/mqttListener.py b/apps/nspanel-lovelace-ui/luibackend/mqttListener.py new file mode 100644 index 00000000..c1c067f8 --- /dev/null +++ b/apps/nspanel-lovelace-ui/luibackend/mqttListener.py @@ -0,0 +1,38 @@ +import json + +import logging + +LOGGER = logging.getLogger(__name__) + +class LuiMqttListener(object): + + def __init__(self, mqtt_api, topic, controller): + self._controller = controller + # Setup, mqtt subscription and callback + mqtt_api.mqtt_subscribe(topic=topic) + mqtt_api.listen_event(self.mqtt_event_callback, "MQTT_MESSAGE", topic=topic, namespace='mqtt') + + + def mqtt_event_callback(self, event_name, data, kwargs): + LOGGER.info(f'MQTT callback for: {data}') + # Parse Json Message from Tasmota and strip out message from nextion display + data = json.loads(data["payload"]) + if("CustomRecv" not in data): + return + msg = data["CustomRecv"] + LOGGER.info(f"Received Message from Screen: {msg}") + # Split message into parts seperated by "," + msg = msg.split(",") + # run action based on received command + if msg[0] == "event": + if msg[1] == "startup": + display_firmware_version = int(msg[2]) + self._controller.startup(display_firmware_version) + if msg[1] == "pageOpen": + self._controller.next() + if msg[1] == "buttonPress2": + entity_id = msg[2] + btype = msg[3] + value = msg[4] if len(msg) > 4 else None + self._controller.button_press(entity_id, btype, value) + diff --git a/apps/nspanel-lovelace-ui/luibackend/pages.py b/apps/nspanel-lovelace-ui/luibackend/pages.py new file mode 100644 index 00000000..e81884e6 --- /dev/null +++ b/apps/nspanel-lovelace-ui/luibackend/pages.py @@ -0,0 +1,165 @@ +import logging +import datetime + +from icon_mapping import get_icon_id +from icons import get_icon_id_ha +from helper import scale, pos_to_color, rgb_dec565, rgb_brightness + +# check Babel +import importlib +babel_spec = importlib.util.find_spec("babel") +if babel_spec is not None: + import babel.dates + +LOGGER = logging.getLogger(__name__) + +class LuiPages(object): + + def __init__(self, ha_api, config, send_mqtt_msg): + self._ha_api = ha_api + self._config = config + self._send_mqtt_msg = send_mqtt_msg + + def getEntityColor(self, entity): + attr = entity.attributes + default_color_on = rgb_dec565([253, 216, 53]) + default_color_off = rgb_dec565([68, 115, 158]) + icon_color = default_color_on if entity.state == "on" else default_color_off + + if "rgb_color" in attr: + color = attr.rgb_color + if "brightness" in attr: + color = rgb_brightness(color, attr.brightness) + icon_color = rgb_dec565(color) + elif "brightness" in attr: + color = rgb_brightness([253, 216, 53], attr.brightness) + icon_color = rgb_dec565(color) + return icon_color + + def update_time(self, kwargs): + time = datetime.datetime.now().strftime(self._config.get("timeFormat")) + self._send_mqtt_msg(f"time,{time}") + + def update_date(self, kwargs): + global babel_spec + if babel_spec is not None: + dateformat = self._config.get("dateFormatBabel") + locale = self._config.get("locale") + date = babel.dates.format_date(datetime.datetime.now(), dateformat, locale=locale) + else: + dateformat = self._config.get("dateFormat") + date = datetime.datetime.now().strftime(dateformat) + self._send_mqtt_msg(f"date,?{date}") + + def page_type(self, target_page): + self._send_mqtt_msg(f"pageType,{target_page}") + + def update_screensaver_weather(self, kwargs): + we_name = kwargs['weather'] + unit = kwargs['unit'] + + if self._ha_api.entity_exists(we_name): + we = self._ha_api.get_entity(we_name) + else: + LOGGER.error("Skipping Weather Update, entitiy not found") + return + + icon_cur = get_icon_id_ha("weather", state=we.state) + text_cur = f"{we.attributes.temperature}{unit}" + icon_cur_detail = get_icon_id("water-percent") + text_cur_detail = f"{we.attributes.humidity} %" + + up1 = we.attributes.forecast[0]['datetime'] + up1 = datetime.datetime.fromisoformat(up1) + icon1 = get_icon_id_ha("weather", state=we.attributes.forecast[0]['condition']) + down1 = we.attributes.forecast[0]['temperature'] + + up2 = we.attributes.forecast[1]['datetime'] + up2 = datetime.datetime.fromisoformat(up2) + icon2 = get_icon_id_ha("weather", state=we.attributes.forecast[1]['condition']) + down2 = we.attributes.forecast[1]['temperature'] + + global babel_spec + if babel_spec is not None: + up1 = babel.dates.format_date(up1, "E", locale=self.config["locale"]) + up2 = babel.dates.format_date(up2, "E", locale=self.config["locale"]) + else: + up1 = up1.strftime("%a") + up2 = up2.strftime("%a") + + self._send_mqtt_msg(f"weatherUpdate,?{icon_cur}?{text_cur}?{icon_cur_detail}?{text_cur_detail}?{up1}?{icon1}?{down1}?{up2}?{icon2}?{down2}") + + def generate_entities_item(self, item): + icon = None + if type(item) is dict: + icon = next(iter(item.items()))[1]['icon'] + item = next(iter(item.items()))[0] + # type of the item is the string before the "." in the item name + item_type = item.split(".")[0] + LOGGER.info(f"Generating item command for {item} with type {item_type}",) + # Internal Entities + if item_type == "delete": + return f",{item_type},,,,," + if item_type == "navigate": + icon_id = get_icon_id_ha("button", overwrite=icon) + return f",button,{item},0,17299,{item},PRESS" + if not self._ha_api.entity_exists(item): + return f",text,{item},{get_icon_id('alert-circle-outline')},17299,Not found check, apps.yaml" + # HA Entities + entity = self._ha_api.get_entity(item) + name = entity.attributes.friendly_name + if item_type == "cover": + icon_id = get_icon_id_ha("cover", state=entity.state, overwrite=icon) + return f",shutter,{item},{icon_id},17299,{name}," + if item_type in "light": + switch_val = 1 if entity.state == "on" else 0 + icon_color = self.getEntityColor(entity) + icon_id = get_icon_id_ha("light", overwrite=icon) + return f",{item_type},{item},{icon_id},{icon_color},{name},{switch_val}" + if item_type in ["switch", "input_boolean"]: + switch_val = 1 if entity.state == "on" else 0 + icon_color = self.getEntityColor(entity) + icon_id = get_icon_id_ha(item_type, state=entity.state, overwrite=icon) + return f",switch,{item},{icon_id},{icon_color},{name},{switch_val}" + if item_type in ["sensor", "binary_sensor"]: + device_class = self.get_safe_ha_attribute(entity.attributes, "device_class", "") + icon_id = get_icon_id_ha("sensor", state=entity.state, device_class=device_class, overwrite=icon) + unit_of_measurement = self.get_safe_ha_attribute(entity.attributes, "unit_of_measurement", "") + value = entity.state + " " + unit_of_measurement + return f",text,{item},{icon_id},17299,{name},{value}" + if item_type in ["button", "input_button"]: + icon_id = get_icon_id_ha("button", overwrite=icon) + return f",button,{item},{icon_id},17299,{name},PRESS" + if item_type == "scene": + icon_id = get_icon_id_ha("scene", overwrite=icon) + return f",button,{item},{icon_id},17299,{name},ACTIVATE" + + + def generate_entities_page(self, heading, items): + # Set Heading of Page + self._send_mqtt_msg(f"entityUpdHeading,{heading}") + # Get items and construct cmd string + command = "entityUpd" + for item in items: + command += self.generate_entities_item(item) + self._send_mqtt_msg(command) + + + + def render_page(self, page): + config = page.data + ptype = config["type"] + LOGGER.info(f"Started rendering of page x with type {ptype}") + # Switch to page + self.page_type(ptype) + if ptype == "screensaver": + we_name = config["weather"] + # update weather information + self.update_screensaver_weather(kwargs={"weather": we_name, "unit": "°C"}) + return + if ptype == "cardEntities": + heading = config.get("heading", "unknown") + self.generate_entities_page(heading, page.get_items()) + return + + diff --git a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py index 93f24efb..8245afc5 100644 --- a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py +++ b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py @@ -1,627 +1,75 @@ -import json -import datetime +import asyncio +import logging +import sys +import traceback +import uuid + import hassapi as hass -from helper import scale, pos_to_color, rgb_dec565, rgb_brightness -from icon_mapping import get_icon_id -from icons import get_icon_id_ha -# check Babel -import importlib -babel_spec = importlib.util.find_spec("babel") -if babel_spec is not None: - import babel.dates + +from luibackend.config import LuiBackendConfig +from luibackend.controller import LuiController +from luibackend.mqttListener import LuiMqttListener + + +LOGGER = logging.getLogger(__name__) + + +class AppDaemonLoggingHandler(logging.Handler): + def __init__(self, app): + super().__init__() + self._app = app + + def emit(self, record): + message = record.getMessage() + if record.exc_info: + message += '\nTraceback (most recent call last):\n' + message += '\n'.join(traceback.format_tb(record.exc_info[2])) + message += f'{record.exc_info[0].__name__}: {record.exc_info[1]}' + self._app.log(message, level=record.levelname) + class NsPanelLovelaceUIManager(hass.Hass): - def initialize(self): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._redirect_logging() - data = self.args["config"] - LovelaceUIPanel(self, data) + def _redirect_logging(self): + # Add a handler for the logging module that will convert the + # calls to AppDaemon's logger with the self instance, so that + # we can simply use logging in the rest of the application + rlogger = logging.getLogger() + rlogger.handlers = [ + h for h in rlogger.handlers + if type(h).__name__ != AppDaemonLoggingHandler.__name__ + ] + rlogger.addHandler(AppDaemonLoggingHandler(self)) -class Updater: - def __init__(self, nsplui, mode): - self.desired_display_firmware_version = 16 - self.desired_display_firmware_url = "http://nspanel.pky.eu/lovelace-ui/github/nspanel-v1.7.0.tft" - self.desired_tasmota_driver_version = 3 - self.desired_tasmota_driver_url = "https://raw.githubusercontent.com/joBr99/nspanel-lovelace-ui/main/tasmota/autoexec.be" + # We want to grab all the logs, AppDaemon will + # then care about filtering those we asked for + rlogger.setLevel(logging.DEBUG) - self.mode = mode - self.nsplui = nsplui - self.current_tasmota_driver_version = None - self.current_display_firmware_version = None - def set_tasmota_driver_version(self, driver_version): - self.current_tasmota_driver_version = driver_version - def set_current_display_firmware_version(self, panel_version): - self.current_display_firmware_version = panel_version - def check_pre_req(self): - # we need to know both versions to continue - if self.current_tasmota_driver_version is not None and self.current_display_firmware_version is not None: - # tasmota driver has to be at least version 2 for Update command and panel has to be at version 11 for notify commands - if self.current_tasmota_driver_version >= 2 and self.current_display_firmware_version >= 11: - return True - return False - def check_updates(self): - # return's true if a notification was send to the panel - # run pre req check - if self.check_pre_req(): - self.nsplui.api.log("Update Pre-Check sucessful Tasmota Driver Version: %s Panel Version: %s", self.current_tasmota_driver_version, self.current_display_firmware_version, level="DEBUG") - # check if tasmota driver needs update - if self.current_tasmota_driver_version < self.desired_tasmota_driver_version: - self.nsplui.api.log("Update of Tasmota Driver needed") - # in auto mode just do the update - if self.mode == "auto": - self.update_berry_driver() - return False - # send notification about the update - if self.mode == "auto-notify": - update_msg = "There's an update avalible for the tasmota berry driver, do you want to start the update now? If you encounter issues after the update or this message appears frequently, please checkthe manual and repeat the installation steps for the tasmota berry driver. " - self.nsplui.send_message_page("updateBerryNoYes", "Driver Update available!", update_msg, "Dismiss", "Yes") - return True - return False - # check if display firmware needs an update - if self.current_display_firmware_version < self.desired_display_firmware_version: - self.nsplui.api.log("Update of Display Firmware needed") - # in auto mode just do the update - if self.mode == "auto": - self.update_panel_driver() - return False - # send notification about the update - if self.mode == "auto-notify": - update_msg = "There's a firmware update avalible for the nextion sceen inside of nspanel, do you want to start the update now? If the update fails check the installation manual and flash again over the tasmota console. Be pationed the update will take a while." - self.nsplui.send_message_page("updateDisplayNoYes", "Display Update available!", update_msg, "Dismiss", "Yes") - return True - return False - else: - self.nsplui.api.log("Update Pre-Check failed Tasmota Driver Version: %s Panel Version: %s", self.current_tasmota_driver_version, self.current_display_firmware_version) - return False - def update_berry_driver(self): - self.nsplui.mqtt.mqtt_publish(self.nsplui.config["panelSendTopic"].replace("CustomSend", "UpdateDriverVersion"), self.desired_tasmota_driver_url) - def update_panel_driver(self): - self.nsplui.mqtt.mqtt_publish(self.nsplui.config["panelSendTopic"].replace("CustomSend", "FlashNextion"), self.desired_display_firmware_url) -class LovelaceUIPanel: - def __init__(self, api, config): - self.api = api - self.config = config - self.current_page_nr = 0 - self.current_screensaver_brightness = 10 - - # check configured items - self.check_items() - - # Setup, mqtt subscription and callback - 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') - - # Read updateMode and use "auto-notify" as default - update_mode = self.config["updateMode"] if "updateMode" in self.config else "auto-notify" - self.updater = Updater(self, update_mode) - - # Request Tasmota Driver Version - self.mqtt.mqtt_publish(self.config["panelSendTopic"].replace("CustomSend", "GetDriverVersion"), "x") - - # send panel back to startup page on restart of this script - self.send_mqtt_msg("pageType,pageStartup") - - # Setup time callback - time = datetime.time(0, 0, 0) - self.api.run_minutely(self.update_time, time) - - # Setup date callback - time = datetime.time(0, 0, 0) - self.api.run_daily(self.update_date, time) - # send date update in case config has been changed - self.update_date("") - - # Setup weather callback - weather_interval = 15 * 60 # 15 minutes - self.api.run_every(self.update_screensaver_weather, "now", weather_interval) - - # set brightness of screensaver - if type(self.config["brightnessScreensaver"]) == int: - self.current_screensaver_brightness = self.config["brightnessScreensaver"] - elif type(self.config["brightnessScreensaver"]) == list: - sorted_timesets = sorted(self.config["brightnessScreensaver"], key=lambda d: self.api.parse_time(d['time'])) - found_current_dim_value = False - for index, timeset in enumerate(sorted_timesets): - self.api.run_daily(self.update_screensaver_brightness, timeset["time"], value=timeset["value"]) - self.api.log("Current time %s", self.api.get_now().time(), level="DEBUG") - if self.api.parse_time(timeset["time"]) > self.api.get_now().time() and not found_current_dim_value: - # first time after current time, set dim value - self.current_screensaver_brightness = sorted_timesets[index-1]["value"] - self.api.log("Setting dim value to %s", sorted_timesets[index-1]) #level="DEBUG" - found_current_dim_value = True - # still no dim value - if not found_current_dim_value: - self.current_screensaver_brightness = sorted_timesets[-1]["value"] - # send screensaver brightness in case config has changed - self.update_screensaver_brightness(kwargs={"value": self.current_screensaver_brightness}) - - # register callbacks - self.register_callbacks() - - def filter_dict_from_item_list(self, items): - # remove all dicts from list - cleaned_list = [] - for item in items: - # in case item is a dict, grab the item name - if type(item) is dict: - cleaned_list.append(next(iter(item))) - else: - cleaned_list.append(item) - return cleaned_list - - def get_all_configured_items(self): - items = [] - for page in self.config["pages"]: - if "item" in page: - items.append(page["item"]) - if "items" in page: - items.extend(page["items"]) - return self.filter_dict_from_item_list(items) - - def check_items(self): - items = self.get_all_configured_items() - for item in items: - if self.api.entity_exists(item) or item == "delete": - self.api.log("Found configured item in Home Assistant %s", item, level="DEBUG") - else: - self.api.error("The following item does not exist in Home Assistant, configuration error: %s", item) - - def register_callbacks(self): - items = self.get_all_configured_items() - for item in items: - self.api.log("Enable state callback for %s", item, level="DEBUG") - self.api.handle = self.api.listen_state(self.state_change_callback, entity_id=item, attribute="all") - - def state_change_callback(self, entity, attribute, old, new, kwargs): - current_page_config = self.config["pages"][self.current_page_nr] - page_type = current_page_config["type"] - self.api.log(f"Got state_callback from {entity}", level="DEBUG") - - if page_type in ["cardEntities", "cardGrid"]: - items = current_page_config["items"] - items_filtered = self.filter_dict_from_item_list(items) - if entity in items_filtered: - self.api.log(f"State change on current page for {entity}", level="DEBUG") - # send update of the page - self.generate_entities_page(items) - # send detail pages in case they are open - if(entity.startswith("cover")): - self.generate_shutter_detail_page(entity) - if(entity.startswith("light")): - self.generate_light_detail_page(entity) - return - - if page_type in ["cardThermo", "cardMedia"]: - if entity == current_page_config["item"]: - self.api.log(f"State change on current page for {entity}", level="DEBUG") - # send update of the whole page - if page_type == "cardThermo": - self.generate_thermo_page(entity) - return - if page_type == "cardMedia": - self.generate_media_page(entity) - return - return - - def send_mqtt_msg(self,msg): - self.api.log("Send Message to Tasmota: %s", msg) #, level="DEBUG" - self.mqtt.mqtt_publish(self.config["panelSendTopic"], msg) - - def handle_mqtt_incoming_message(self, event_name, data, kwargs): - # Parse Json Message from Tasmota and strip out message from nextion display - data = json.loads(data["payload"]) - # pass tasmota driver version to updater class - if("nlui_driver_version" in data): - msg = data["nlui_driver_version"] - self.api.log("Received Driver Version from Tasmota: %s", int(msg), level="DEBUG") - self.updater.set_tasmota_driver_version(int(msg)) - return - if("CustomRecv" not in data): - self.api.log("Received Message from Tasmota, but not from nextion screen: %s", data, level="DEBUG") - return - msg = data["CustomRecv"] - self.api.log("Received Message from Tasmota: %s", msg) #, level="DEBUG" - - # Split message into parts seperated by "," - msg = msg.split(",") - - # run action based on received command - if msg[0] == "event": - if msg[1] == "startup": - self.api.log("Handling startup event", level="DEBUG") - # grab version from screen and pass to updater class - self.updater.set_current_display_firmware_version(int(msg[2])) - # send date and time - self.update_time("") - self.update_date("") - # set screensaver timeout - timeout = self.config["timeoutScreensaver"] - self.send_mqtt_msg(f"timeout,{timeout}") - # send screensaver brightness - self.update_screensaver_brightness(kwargs={"value": self.current_screensaver_brightness}) - # check for updates - msg_send = self.updater.check_updates() - # send messages for current page - if not msg_send: - self.generate_page(self.current_page_nr) - - 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, level="DEBUG") - # generate commands for current page - self.generate_page(self.current_page_nr) + def initialize(self): + LOGGER.info('Starting') + mqtt_api = self._mqtt_api = self.get_plugin_api("MQTT") + cfg = self._cfg = LuiBackendConfig(self.args["config"]) - if msg[1] == "buttonPress": - entity_id = msg[4] - btype = msg[6] - if len(msg) > 7: - value = msg[7] - else: - value = None - self.handle_button_press(entity_id, btype, value) + topic_send = cfg.get("panelSendTopic") + def send_mqtt_msg(msg): + LOGGER.info(f"Sending MQTT Message: {msg}") + mqtt_api.mqtt_publish(topic_send, msg) + + controller = LuiController(self, cfg, send_mqtt_msg) + + topic_recv = cfg.get("panelRecvTopic") + mqtt_listener = LuiMqttListener(mqtt_api, topic_recv, controller) + - if msg[1] == "buttonPress2": - entity_id = msg[2] - btype = msg[3] - if len(msg) > 4: - value = msg[4] - else: - value = None - self.handle_button_press(entity_id, btype, value) - - if msg[1] == "pageOpenDetail": - self.api.log("Received pageOpenDetail command", level="DEBUG") - if msg[2] == "popupShutter": - self.generate_shutter_detail_page(msg[3]) - if msg[2] == "popupLight": - self.generate_light_detail_page(msg[3]) - - if msg[1] == "screensaverOpen": - self.update_screensaver_weather("") - - def update_time(self, kwargs): - time = datetime.datetime.now().strftime(self.config["timeFormat"]) - self.send_mqtt_msg(f"time,{time}") - - def update_date(self, kwargs): - global babel_spec - if babel_spec is not None: - self.api.log("Babel package found", level="DEBUG") - if "dateFormatBabel" in self.config: - dateformat = self.config["dateFormatBabel"] - else: - dateformat = "full" - date = babel.dates.format_date(datetime.datetime.now(), dateformat, locale=self.config["locale"]) - self.send_mqtt_msg(f"date,?{date}") - else: - self.api.log("Babel package not found", level="DEBUG") - date = datetime.datetime.now().strftime(self.config["dateFormat"]) - self.send_mqtt_msg(f"date,?{date}") - - def update_screensaver_brightness(self, kwargs): - self.current_screensaver_brightness = kwargs['value'] - self.send_mqtt_msg(f"dimmode,{self.current_screensaver_brightness}") - - def update_screensaver_weather(self, kwargs): - if not ("weatherEntity" in self.config and self.api.entity_exists(self.config["weatherEntity"])): - return - we = self.api.get_entity(self.config["weatherEntity"]) - unit = "°C" - - icon_cur = get_icon_id_ha("weather", state=we.state) - text_cur = f"{we.attributes.temperature}{unit}" - icon_cur_detail = get_icon_id("water-percent") - text_cur_detail = f"{we.attributes.humidity} %" - - up1 = we.attributes.forecast[0]['datetime'] - up1 = datetime.datetime.fromisoformat(up1) - icon1 = get_icon_id_ha("weather", state=we.attributes.forecast[0]['condition']) - down1 = we.attributes.forecast[0]['temperature'] - - up2 = we.attributes.forecast[1]['datetime'] - up2 = datetime.datetime.fromisoformat(up2) - icon2 = get_icon_id_ha("weather", state=we.attributes.forecast[1]['condition']) - down2 = we.attributes.forecast[1]['temperature'] - - global babel_spec - if babel_spec is not None: - up1 = babel.dates.format_date(up1, "E", locale=self.config["locale"]) - up2 = babel.dates.format_date(up2, "E", locale=self.config["locale"]) - else: - up1 = up1.strftime("%a") - up2 = up2.strftime("%a") - - self.send_mqtt_msg(f"weatherUpdate,?{icon_cur}?{text_cur}?{icon_cur_detail}?{text_cur_detail}?{up1}?{icon1}?{down1}?{up2}?{icon2}?{down2}") - def handle_button_press(self, entity_id, btype, optVal=None): - if entity_id == "updateBerryNoYes" and optVal == "yes": - # go back to main page before starting the update - self.generate_page(self.current_page_nr) - self.updater.update_berry_driver() - elif entity_id == "updateBerryNoYes" and optVal == "no": - self.generate_page(self.current_page_nr) - if entity_id == "updateDisplayNoYes" and optVal == "yes": - self.updater.update_panel_driver() - elif entity_id == "updateDisplayNoYes" and optVal == "no": - self.generate_page(self.current_page_nr) - - if btype == "OnOff": - if optVal == "1": - self.api.turn_on(entity_id) - else: - self.api.turn_off(entity_id) - if btype == "up": - self.api.get_entity(entity_id).call_service("open_cover") - if btype == "stop": - self.api.get_entity(entity_id).call_service("stop_cover") - if btype == "down": - self.api.get_entity(entity_id).call_service("close_cover") - - if btype == "button": - if entity_id.startswith('scene'): - self.api.get_entity(entity_id).call_service("turn_on") - elif entity_id.startswith('light') or entity_id.startswith('switch') or entity_id.startswith('input_boolean'): - self.api.get_entity(entity_id).call_service("toggle") - else: - self.api.get_entity(entity_id).call_service("press") - - if btype == "media-next": - self.api.get_entity(entity_id).call_service("media_next_track") - if btype == "media-back": - self.api.get_entity(entity_id).call_service("media_previous_track") - if btype == "media-pause": - self.api.get_entity(entity_id).call_service("media_play_pause") - - if btype == "hvac_action": - self.api.get_entity(entity_id).call_service("set_hvac_mode", hvac_mode=optVal) + LOGGER.info('Started') - if btype == "brightnessSlider": - # scale 0-100 to ha brightness range - brightness = int(scale(int(optVal),(0,100),(0,255))) - self.api.get_entity(entity_id).call_service("turn_on", brightness=brightness) - - if btype == "colorTempSlider": - entity = self.api.get_entity(entity_id) - #scale 0-100 from slider to color range of lamp - color_val = scale(int(optVal), (0, 100), (entity.attributes.min_mireds, entity.attributes.max_mireds)) - self.api.get_entity(entity_id).call_service("turn_on", color_temp=color_val) - if btype == "colorWheel": - self.api.log(optVal) - optVal = optVal.split('|') - color = pos_to_color(int(optVal[0]), int(optVal[1])) - self.api.log(color) - self.api.get_entity(entity_id).call_service("turn_on", rgb_color=color) - - if btype == "positionSlider": - pos = int(optVal) - self.api.get_entity(entity_id).call_service("set_cover_position", position=pos) - - if btype == "volumeSlider": - pos = int(optVal) - # HA wants this value between 0 and 1 as float - pos = pos/100 - self.api.get_entity(entity_id).call_service("volume_set", volume_level=pos) - - if btype == "tempUpd": - temp = int(optVal)/10 - self.api.get_entity(msg[3]).call_service("set_temperature", temperature=temp) - - def generate_page(self, page_number): - # get type of page - page_type = self.config["pages"][self.current_page_nr]["type"] - self.api.log("Generating page commands for page %i with type %s", self.current_page_nr, page_type, level="DEBUG") - - # Send page type - self.send_mqtt_msg(f"pageType,{page_type}") - - if page_type in ["cardEntities", "cardGrid"]: - self.generate_entities_page(self.config["pages"][self.current_page_nr]["items"]) - - if page_type == "cardThermo": - self.generate_thermo_page(self.config["pages"][self.current_page_nr]["item"]) - - if page_type == "cardMedia": - self.generate_media_page(self.config["pages"][self.current_page_nr]["item"]) - - def generate_entities_item(self, item): - icon = None - if type(item) is dict: - icon = next(iter(item.items()))[1]['icon'] - item = next(iter(item.items()))[0] - - # type of the item is the string before the "." in the item name - item_type = item.split(".")[0] - - self.api.log("Generating item command for %s with type %s", item, item_type, level="DEBUG") - - if item_type == "delete": - return f",{item_type},,,,," - - if not self.api.entity_exists(item): - return f",text,{item},{get_icon_id('alert-circle-outline')},17299,Not found check, apps.yaml" - - entity = self.api.get_entity(item) - name = entity.attributes.friendly_name - - if item_type == "cover": - icon_id = get_icon_id_ha("cover", state=entity.state, overwrite=icon) - return f",shutter,{item},{icon_id},17299,{name}," - - if item_type == "light": - switch_val = 1 if entity.state == "on" else 0 - icon_color = self.getEntityColor(entity) - icon_id = get_icon_id_ha("light", overwrite=icon) - return f",{item_type},{item},{icon_id},{icon_color},{name},{switch_val}" - - if item_type == "switch" or item_type == "input_boolean": - icon_id = get_icon_id_ha(item_type, state=entity.state, overwrite=icon) - switch_val = 1 if entity.state == "on" else 0 - icon_color = self.getEntityColor(entity) - return f",switch,{item},{icon_id},{icon_color},{name},{switch_val}" - - if item_type in ["sensor", "binary_sensor"]: - device_class = self.get_safe_ha_attribute(entity.attributes, "device_class", "") - icon_id = get_icon_id_ha("sensor", state=entity.state, device_class=device_class, overwrite=icon) - unit_of_measurement = self.get_safe_ha_attribute(entity.attributes, "unit_of_measurement", "") - value = entity.state + " " + unit_of_measurement - return f",text,{item},{icon_id},17299,{name},{value}" - - if item_type in ["button", "input_button"]: - icon_id = get_icon_id_ha("button", overwrite=icon) - return f",button,{item},{icon_id},17299,{name},PRESS" - - if item_type == "scene": - icon_id = get_icon_id_ha("scene", overwrite=icon) - return f",button,{item},{icon_id},17299,{name},ACTIVATE" - - def generate_entities_page(self, items): - # Set Heading of Page - self.send_mqtt_msg(f"entityUpdHeading,{self.config['pages'][self.current_page_nr]['heading']}") - # Get items and construct cmd string - command = "entityUpd" - for item in items: - command += self.generate_entities_item(item) - self.send_mqtt_msg(command) - - def get_safe_ha_attribute(self, eattr, attr, default): - return eattr[attr] if attr in eattr else default - - def generate_thermo_page(self, item): - if not self.api.entity_exists(item): - command = f"entityUpd,{item},Not found,220,220,Not found,150,300,5" - else: - entity = self.api.get_entity(item) - heading = entity.attributes.friendly_name - current_temp = int(self.get_safe_ha_attribute(entity.attributes, "current_temperature", 0)*10) - dest_temp = int(self.get_safe_ha_attribute(entity.attributes, "temperature", 0)*10) - status = self.get_safe_ha_attribute(entity.attributes, "hvac_action", "") - min_temp = int(self.get_safe_ha_attribute(entity.attributes, "min_temp", 0)*10) - max_temp = int(self.get_safe_ha_attribute(entity.attributes, "max_temp", 0)*10) - step_temp = int(self.get_safe_ha_attribute(entity.attributes, "target_temp_step", 0.5)*10) - - icon_res = "" - hvac_modes = self.get_safe_ha_attribute(entity.attributes, "hvac_modes", []) - for mode in hvac_modes: - icon_id = get_icon_id('alert-circle-outline') - color_on = 64512 - if mode == "auto": - icon_id = get_icon_id("calendar-sync") - color_on = 1024 - if mode == "heat": - icon_id = get_icon_id("fire") - color_on = 64512 - if mode == "off": - icon_id = get_icon_id("power") - color_on = 35921 - if mode == "cool": - icon_id = get_icon_id("snowflake") - color_on = 11487 - if mode == "dry": - icon_id = get_icon_id("water-percent") - color_on = 60897 - if mode == "fan_only": - icon_id = get_icon_id("fan") - color_on = 35921 - state = 0 - if(mode == entity.state): - state = 1 - icon_res += f",{icon_id},{color_on},{state},{mode}" - - len_hvac_modes = len(hvac_modes) - if len_hvac_modes%2 == 0: - # even - padding_len = int((4-len_hvac_modes)/2) - icon_res = ","*4*padding_len + icon_res + ","*4*padding_len - # use last 4 icons - icon_res = ","*4*5 + icon_res - else: - # uneven - padding_len = int((5-len_hvac_modes)/2) - icon_res = ","*4*padding_len + icon_res + ","*4*padding_len - # use first 5 icons - icon_res = icon_res + ","*4*4 - command = f"entityUpd,{item},{heading},{current_temp},{dest_temp},{status},{min_temp},{max_temp},{step_temp}{icon_res}" - self.send_mqtt_msg(command) - - def generate_media_page(self, item): - - if not self.api.entity_exists(item): - command = f"entityUpd,|{item}|Not found|{get_icon_id('alert-circle-outline')}|Please check your|apps.yaml in AppDaemon|50|11" - else: - entity = self.api.get_entity(item) - heading = entity.attributes.friendly_name - icon = 0 - title = self.get_safe_ha_attribute(entity.attributes, "media_title", "") - author = self.get_safe_ha_attribute(entity.attributes, "media_artist", "") - volume = int(self.get_safe_ha_attribute(entity.attributes, "volume_level", 0)*100) - iconplaypause = get_icon_id("pause") if entity.state == "playing" else get_icon_id("play") - if "media_content_type" in entity.attributes: - if entity.attributes.media_content_type == "music": - icon = get_icon_id("music") - command = f"entityUpd,|{item}|{heading}|{icon}|{title}|{author}|{volume}|{iconplaypause}" - - self.send_mqtt_msg(command) - - def getEntityColor(self, entity): - attr = entity.attributes - default_color_on = rgb_dec565([253, 216, 53]) - default_color_off = rgb_dec565([68, 115, 158]) - icon_color = default_color_on if entity.state == "on" else default_color_off - - if "rgb_color" in attr: - color = attr.rgb_color - if "brightness" in attr: - color = rgb_brightness(color, attr.brightness) - icon_color = rgb_dec565(color) - elif "brightness" in attr: - color = rgb_brightness([253, 216, 53], attr.brightness) - icon_color = rgb_dec565(color) - return icon_color - - def generate_light_detail_page(self, entity): - entity = self.api.get_entity(entity) - switch_val = 1 if entity.state == "on" else 0 - icon_color = self.getEntityColor(entity) - brightness = "disable" - color_temp = "disable" - color = "disable" - # scale 0-255 brightness from ha to 0-100 - if entity.state == "on": - if "brightness" in entity.attributes: - brightness = int(scale(entity.attributes.brightness,(0,255),(0,100))) - else: - brightness = "disable" - if "color_temp" in entity.attributes.supported_color_modes: - if "color_temp" in entity.attributes: - # scale ha color temp range to 0-100 - color_temp = int(scale(entity.attributes.color_temp,(entity.attributes.min_mireds, entity.attributes.max_mireds),(0,100))) - else: - color_temp = "unknown" - else: - color_temp = "disable" - - list_color_modes = ["xy", "rgb", "rgbw", "hs"] - if any(item in list_color_modes for item in entity.attributes.supported_color_modes): - color = "enable" - else: - color = "disable" - self.send_mqtt_msg(f"entityUpdateDetail,{get_icon_id('lightbulb')},{icon_color},{switch_val},{brightness},{color_temp},{color}") - - def generate_shutter_detail_page(self, entity): - pos = int(self.get_safe_ha_attribute(entity.attributes, "current_position", 50)) - # reverse position for slider - pos = 100-pos - self.send_mqtt_msg(f"entityUpdateDetail,{pos}") - - def send_message_page(self, id, heading, msg, b1, b2): - self.send_mqtt_msg(f"pageType,popupNotify") - self.send_mqtt_msg(f"entityUpdateDetail,|{id}|{heading}|65535|{b1}|65535|{b2}|65535|{msg}|65535|0") - From 40ac3919c807045cc037888c7c0563a6c5b10edc Mon Sep 17 00:00:00 2001 From: joBr99 <29555657+joBr99@users.noreply.github.com> Date: Thu, 24 Mar 2022 16:18:35 +0100 Subject: [PATCH 02/10] Update README.md --- HMI/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/HMI/README.md b/HMI/README.md index 850b39f6..6f97ec1b 100644 --- a/HMI/README.md +++ b/HMI/README.md @@ -81,8 +81,6 @@ change the page type: `page,0` -`entityUpd,heading_of_exit_page` - ### cardEntities Page The following message can be used to update the content on the cardEntities Page From a3afa30fdbf7847b8cabef2277c2f8700a5126ab Mon Sep 17 00:00:00 2001 From: joBr99 <29555657+joBr99@users.noreply.github.com> Date: Thu, 24 Mar 2022 22:51:51 +0100 Subject: [PATCH 03/10] basic navigation and event handling --- HMI/README.md | 6 +- HMI/nspanel.HMI | Bin 7418507 -> 7418507 bytes HMI/nspanel.tft | Bin 6287408 -> 6257540 bytes apps/nspanel-lovelace-ui/luibackend/config.py | 48 +++--- .../luibackend/controller.py | 114 +++++++++++--- .../luibackend/mqttListener.py | 6 +- apps/nspanel-lovelace-ui/luibackend/pages.py | 142 +++++++++++++++--- 7 files changed, 257 insertions(+), 59 deletions(-) diff --git a/HMI/README.md b/HMI/README.md index 6f97ec1b..a095971d 100644 --- a/HMI/README.md +++ b/HMI/README.md @@ -144,7 +144,11 @@ The following message can be used to update the content on the cardEntities Page ### screensaver page -`event,buttonPress2,screensaver,enter` +`event,buttonPress2,screensaver,exit` - Touch Event on Screensaver + +`event,screensaverOpen` - Screensaver has opened + + ### cardEntities Page diff --git a/HMI/nspanel.HMI b/HMI/nspanel.HMI index fa7f39f469e9bb00617b24fc9ca916754abcd297..bc99b3c109fc3798f9ab4d354bd7d175e2d8c025 100644 GIT binary patch delta 9019 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 N_J;3|``vQ`@O&KkMACsXV0v&_S$Rhwfo-Z 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 B$}a!_ delta 186578 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É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 XK-YCY*vm`AsS;yP6Ytvp%-!*S=KxsS diff --git a/apps/nspanel-lovelace-ui/luibackend/config.py b/apps/nspanel-lovelace-ui/luibackend/config.py index 2794e824..e9f61430 100644 --- a/apps/nspanel-lovelace-ui/luibackend/config.py +++ b/apps/nspanel-lovelace-ui/luibackend/config.py @@ -6,13 +6,13 @@ from luibackend.exceptions import LuiBackendConfigError LOGGER = logging.getLogger(__name__) - class PageNode(object): def __init__(self, data, parent=None): self.data = data self.name = None self.childs = [] self.parent = parent + self.pos = None if "items" in data: childs = data.pop("items") @@ -21,19 +21,34 @@ class PageNode(object): name = self.data.get("heading", "unkown") if type(self.data) is dict else self.data ptype = self.data.get("type", "unkown") if type(self.data) is dict else "leaf" - #parent = self.parent.data.get("heading", self.parent.data.get("type", "unknown")) if self.parent is not None else "root" self.name = f"{ptype}.{name}" if type(self.data) is dict else self.data def add_child(self, obj): + obj.pos = len(self.childs) self.childs.append(obj) + def next(self): + if self.parent is not None: + pos = self.pos + length = len(self.parent.childs) + return self.parent.childs[(pos+1)%length] + else: + return self + def prev(self): + if self.parent is not None: + pos = self.pos + length = len(self.parent.childs) + return self.parent.childs[(pos-1)%length] + else: + return self + def dump(self, indent=0): """dump tree to string""" tab = ' '*(indent-1) + ' |- ' if indent > 0 else '' name = self.name parent = self.parent.name if self.parent is not None else "root" - dumpstring = f"{tab}{name} -> {parent} \n" + dumpstring = f"{tab}{self.pos}:{name} -> {parent} \n" for obj in self.childs: dumpstring += obj.dump(indent + 1) return dumpstring @@ -61,20 +76,17 @@ class LuiBackendConfig(object): 'timeFormat': "%H:%M", 'dateFormatBabel': "full", 'dateFormat': "%A, %d. %B %Y", + 'weather': 'weather.example', 'pages': [{ - 'type': 'screensaver', - 'weather': 'weather.example', - 'items': [{ - 'type': 'cardEntities', - 'heading': 'Test Entities 1', - 'items': ['switch.test_item', 'switch.test_item', 'switch.test_item'] - }, { - 'type': 'cardGrid', - 'heading': 'Test Grid 1', - 'items': ['switch.test_item', 'switch.test_item', 'switch.test_item'] - } - ], - }], + 'type': 'cardEntities', + 'heading': 'Test Entities 1', + 'items': ['switch.test_item', 'switch.test_item', 'switch.test_item'] + }, { + 'type': 'cardGrid', + 'heading': 'Test Grid 1', + 'items': ['switch.test_item', 'switch.test_item', 'switch.test_item'] + } + ] } def __init__(self, args=None, check=True): @@ -93,9 +105,9 @@ class LuiBackendConfig(object): self._config[k] = v LOGGER.info(f"Loaded config: {self._config}") - root_page = self.get("pages")[0] + root_page = {"items": self.get("pages")} self._page_config = PageNode(root_page) - + LOGGER.info(f"Parsed Page config to the following Tree: \n {self._page_config.dump()}") def check(self): diff --git a/apps/nspanel-lovelace-ui/luibackend/controller.py b/apps/nspanel-lovelace-ui/luibackend/controller.py index f6009fd3..f55fdd8f 100644 --- a/apps/nspanel-lovelace-ui/luibackend/controller.py +++ b/apps/nspanel-lovelace-ui/luibackend/controller.py @@ -1,7 +1,7 @@ import logging import datetime -from pages import LuiPages +from pages import LuiPagesGen LOGGER = logging.getLogger(__name__) @@ -13,38 +13,112 @@ class LuiController(object): self._send_mqtt_msg = send_mqtt_msg self._current_page = None - self._previous_page = None - self._pages = LuiPages(ha_api, config, send_mqtt_msg) + self._pages_gen = LuiPagesGen(ha_api, config, send_mqtt_msg) # Setup time update callback time = datetime.time(0, 0, 0) - ha_api.run_minutely(self._pages.update_time, time) + ha_api.run_minutely(self._pages_gen.update_time, time) # send panel back to startup page on restart of this script - self._pages.page_type("pageStartup") + self._pages_gen.page_type("pageStartup") - #{'type': 'sceensaver', 'weather': 'weather.k3ll3r', 'items': [{'type': 'cardEntities', 'heading': 'Test Entities 1', 'items': ['switch.test_item', {'type': 'cardEntities', 'heading': 'Test Entities 1', 'items': ['switch.test_item', 'switch.test_item', 'switch.test_item', 'switch.test_item']}, 'switch.test_item', 'switch.test_item']}, {'type': 'cardGrid', 'heading': 'Test Grid 1', 'items': ['switch.test_item', 'switch.test_item', 'switch.test_item']}]} - def startup(self, display_firmware_version): LOGGER.info(f"Startup Event; Display Firmware Version is {display_firmware_version}") # send time and date on startup - self._pages.update_time("") - self._pages.update_date("") + self._pages_gen.update_time("") + self._pages_gen.update_date("") - # send panel to root page - self._current_page = self._config.get_root_page() - self._pages.render_page(self._current_page) + # send panel to screensaver + self._pages_gen.page_type("screensaver") + self.screensaver_open() + def screensaver_open(self): + we_name = self._config.get("weather") + self._pages_gen.update_screensaver_weather(kwargs={"weather": we_name, "unit": "°C"}) - def next(self): - return + def detail_open(self, detail_type, entity_id): + if detail_type == "popupShutter": + self._pages_gen.generate_shutter_detail_page(entity_id) + if detail_type == "popupLight": + self._pages_gen.generate_light_detail_page(entity_id) - def button_press(self, entity_id, btype, value): - LOGGER.debug(f"Button Press Event; entity_id: {entity_id}; btype: {btype}; value: {value} ") - if(entity_id == "screensaver" and btype == "enter"): - if self._previous_page is None: - self._pages.render_page(self._current_page.childs[0]) + def button_press(self, entity_id, button_type, value): + LOGGER.debug(f"Button Press Event; entity_id: {entity_id}; button_type: {button_type}; value: {value} ") + # internal buttons + if(entity_id == "screensaver" and button_type == "enter"): + # go to first child of root page (default, after startup) + self._current_page = self._config._page_config.childs[0] + self._pages_gen.render_page(self._current_page) + + if(button_type == "bNext"): + self._current_page = self._current_page.next() + self._pages_gen.render_page(self._current_page) + if(button_type == "bPrev"): + self._current_page = self._current_page.prev() + self._pages_gen.render_page(self._current_page) + if(button_type == "bExit"): + self._pages_gen.render_page(self._current_page) + + # buttons with actions on HA + if button_type == "OnOff": + if value == "1": + self._ha_api.turn_on(entity_id) else: - self._pages.render_page(self._previous_page) + self._ha_api.turn_off(entity_id) + # for shutter / covers + if button_type == "up": + self._ha_api.get_entity(entity_id).call_service("open_cover") + if button_type == "stop": + self._ha_api.get_entity(entity_id).call_service("stop_cover") + if button_type == "down": + self._ha_api.get_entity(entity_id).call_service("close_cover") + if button_type == "positionSlider": + pos = int(value) + self._ha_api.get_entity(entity_id).call_service("set_cover_position", position=pos) + + if button_type == "button": + if entity_id.startswith('scene'): + self._ha_api.get_entity(entity_id).call_service("turn_on") + elif entity_id.startswith('light') or entity_id.startswith('switch') or entity_id.startswith('input_boolean'): + self._ha_api.get_entity(entity_id).call_service("toggle") + else: + self._ha_api.get_entity(entity_id).call_service("press") + + # for media page + if button_type == "media-next": + self._ha_api.get_entity(entity_id).call_service("media_next_track") + if button_type == "media-back": + self._ha_api.get_entity(entity_id).call_service("media_previous_track") + if button_type == "media-pause": + self._ha_api.get_entity(entity_id).call_service("media_play_pause") + if button_type == "hvac_action": + self._ha_api.get_entity(entity_id).call_service("set_hvac_mode", hvac_mode=value) + if button_type == "volumeSlider": + pos = int(value) + # HA wants this value between 0 and 1 as float + pos = pos/100 + self._ha_api.get_entity(entity_id).call_service("volume_set", volume_level=pos) + + # for light detail page + if button_type == "brightnessSlider": + # scale 0-100 to ha brightness range + brightness = int(scale(int(value),(0,100),(0,255))) + self._ha_api.get_entity(entity_id).call_service("turn_on", brightness=brightness) + if button_type == "colorTempSlider": + entity = self._ha_api.get_entity(entity_id) + #scale 0-100 from slider to color range of lamp + color_val = scale(int(value), (0, 100), (entity.attributes.min_mireds, entity.attributes.max_mireds)) + self._ha_api.get_entity(entity_id).call_service("turn_on", color_temp=color_val) + if button_type == "colorWheel": + self._ha_api.log(value) + value = value.split('|') + color = pos_to_color(int(value[0]), int(value[1])) + self._ha_api.log(color) + self._ha_api.get_entity(entity_id).call_service("turn_on", rgb_color=color) + + # for climate page + if button_type == "tempUpd": + temp = int(value)/10 + self._ha_api.get_entity(entity_id).call_service("set_temperature", temperature=temp) \ No newline at end of file diff --git a/apps/nspanel-lovelace-ui/luibackend/mqttListener.py b/apps/nspanel-lovelace-ui/luibackend/mqttListener.py index c1c067f8..ad4a7133 100644 --- a/apps/nspanel-lovelace-ui/luibackend/mqttListener.py +++ b/apps/nspanel-lovelace-ui/luibackend/mqttListener.py @@ -28,11 +28,13 @@ class LuiMqttListener(object): if msg[1] == "startup": display_firmware_version = int(msg[2]) self._controller.startup(display_firmware_version) - if msg[1] == "pageOpen": - self._controller.next() + if msg[1] == "screensaverOpen": + self._controller.screensaver_open() if msg[1] == "buttonPress2": entity_id = msg[2] btype = msg[3] value = msg[4] if len(msg) > 4 else None self._controller.button_press(entity_id, btype, value) + if msg[1] == "pageOpenDetail": + self._controller.detail_open(msg[2], msg[3]) diff --git a/apps/nspanel-lovelace-ui/luibackend/pages.py b/apps/nspanel-lovelace-ui/luibackend/pages.py index e81884e6..d75f26b2 100644 --- a/apps/nspanel-lovelace-ui/luibackend/pages.py +++ b/apps/nspanel-lovelace-ui/luibackend/pages.py @@ -13,7 +13,7 @@ if babel_spec is not None: LOGGER = logging.getLogger(__name__) -class LuiPages(object): +class LuiPagesGen(object): def __init__(self, ha_api, config, send_mqtt_msg): self._ha_api = ha_api @@ -86,13 +86,14 @@ class LuiPages(object): else: up1 = up1.strftime("%a") up2 = up2.strftime("%a") - self._send_mqtt_msg(f"weatherUpdate,?{icon_cur}?{text_cur}?{icon_cur_detail}?{text_cur_detail}?{up1}?{icon1}?{down1}?{up2}?{icon2}?{down2}") def generate_entities_item(self, item): icon = None + name = None if type(item) is dict: - icon = next(iter(item.items()))[1]['icon'] + icon = next(iter(item.items()))[1].get('icon') + name = next(iter(item.items()))[1].get('name') item = next(iter(item.items()))[0] # type of the item is the string before the "." in the item name item_type = item.split(".")[0] @@ -107,7 +108,7 @@ class LuiPages(object): return f",text,{item},{get_icon_id('alert-circle-outline')},17299,Not found check, apps.yaml" # HA Entities entity = self._ha_api.get_entity(item) - name = entity.attributes.friendly_name + name = name if name is not None else entity.attributes.friendly_name if item_type == "cover": icon_id = get_icon_id_ha("cover", state=entity.state, overwrite=icon) return f",shutter,{item},{icon_id},17299,{name}," @@ -122,11 +123,12 @@ class LuiPages(object): icon_id = get_icon_id_ha(item_type, state=entity.state, overwrite=icon) return f",switch,{item},{icon_id},{icon_color},{name},{switch_val}" if item_type in ["sensor", "binary_sensor"]: - device_class = self.get_safe_ha_attribute(entity.attributes, "device_class", "") + device_class = self.entity.attributes.get("device_class", "") icon_id = get_icon_id_ha("sensor", state=entity.state, device_class=device_class, overwrite=icon) - unit_of_measurement = self.get_safe_ha_attribute(entity.attributes, "unit_of_measurement", "") + unit_of_measurement = self.entity.attributes.get("unit_of_measurement", "") value = entity.state + " " + unit_of_measurement - return f",text,{item},{icon_id},17299,{name},{value}" + icon_color = self.getEntityColor(entity) + return f",text,{item},{icon_id},{icon_color},{name},{value}" if item_type in ["button", "input_button"]: icon_id = get_icon_id_ha("button", overwrite=icon) return f",button,{item},{icon_id},17299,{name},PRESS" @@ -145,21 +147,125 @@ class LuiPages(object): self._send_mqtt_msg(command) + def generate_thermo_page(self, item): + if not self._ha_api.entity_exists(item): + command = f"entityUpd,{item},Not found,220,220,Not found,150,300,5" + else: + entity = self._ha_api.get_entity(item) + heading = entity.attributes.friendly_name + current_temp = int(entity.attributes.get("current_temperature", 0)*10) + dest_temp = int(entity.attributes.get("temperature", 0)*10) + status = entity.attributes.get("hvac_action", "") + min_temp = int(entity.attributes.get("min_temp", 0)*10) + max_temp = int(entity.attributes.get("max_temp", 0)*10) + step_temp = int(entity.attributes.get("target_temp_step", 0.5)*10) + icon_res = "" + hvac_modes = entity.attributes.get("hvac_modes", []) + for mode in hvac_modes: + icon_id = get_icon_id('alert-circle-outline') + color_on = 64512 + if mode == "auto": + icon_id = get_icon_id("calendar-sync") + color_on = 1024 + if mode == "heat": + icon_id = get_icon_id("fire") + color_on = 64512 + if mode == "off": + icon_id = get_icon_id("power") + color_on = 35921 + if mode == "cool": + icon_id = get_icon_id("snowflake") + color_on = 11487 + if mode == "dry": + icon_id = get_icon_id("water-percent") + color_on = 60897 + if mode == "fan_only": + icon_id = get_icon_id("fan") + color_on = 35921 + state = 0 + if(mode == entity.state): + state = 1 + icon_res += f",{icon_id},{color_on},{state},{mode}" + + len_hvac_modes = len(hvac_modes) + if len_hvac_modes%2 == 0: + # even + padding_len = int((4-len_hvac_modes)/2) + icon_res = ","*4*padding_len + icon_res + ","*4*padding_len + # use last 4 icons + icon_res = ","*4*5 + icon_res + else: + # uneven + padding_len = int((5-len_hvac_modes)/2) + icon_res = ","*4*padding_len + icon_res + ","*4*padding_len + # use first 5 icons + icon_res = icon_res + ","*4*4 + command = f"entityUpd,{item},{heading},{current_temp},{dest_temp},{status},{min_temp},{max_temp},{step_temp}{icon_res}" + self._send_mqtt_msg(command) + + def generate_media_page(self, item): + if not self._ha_api.entity_exists(item): + command = f"entityUpd,|{item}|Not found|{get_icon_id('alert-circle-outline')}|Please check your|apps.yaml in AppDaemon|50|{get_icon_id('alert-circle-outline')}" + else: + entity = self._ha_api.get_entity(item) + heading = entity.attributes.friendly_name + icon = 0 + title = entity.attributes.get("media_title", "") + author = entity.attributes.get("media_artist", "") + volume = int(entity.attributes.get("volume_level", 0)*100) + iconplaypause = get_icon_id("pause") if entity.state == "playing" else get_icon_id("play") + if "media_content_type" in entity.attributes: + if entity.attributes.media_content_type == "music": + icon = get_icon_id("music") + command = f"entityUpd,|{item}|{heading}|{icon}|{title}|{author}|{volume}|{iconplaypause}" + self._send_mqtt_msg(command) def render_page(self, page): - config = page.data - ptype = config["type"] - LOGGER.info(f"Started rendering of page x with type {ptype}") + LOGGER.info(page) + config = page.data + page_type = config["type"] + LOGGER.info(f"Started rendering of page x with type {page_type}") # Switch to page - self.page_type(ptype) - if ptype == "screensaver": - we_name = config["weather"] - # update weather information - self.update_screensaver_weather(kwargs={"weather": we_name, "unit": "°C"}) - return - if ptype == "cardEntities": + self.page_type(page_type) + if page_type in ["cardEntities", "cardGrid"]: heading = config.get("heading", "unknown") self.generate_entities_page(heading, page.get_items()) return + if page_type == "cardThermo": + LOGGER.info(page.data) + self.generate_thermo_page(page.data.get("item")) + if page_type == "cardMedia": + LOGGER.info(page.data) + self.generate_media_page(page.data.get("item")) - + def generate_light_detail_page(self, entity): + entity = self._ha_api.get_entity(entity) + switch_val = 1 if entity.state == "on" else 0 + icon_color = self.getEntityColor(entity) + brightness = "disable" + color_temp = "disable" + color = "disable" + # scale 0-255 brightness from ha to 0-100 + if entity.state == "on": + if "brightness" in entity.attributes: + brightness = int(scale(entity.attributes.brightness,(0,255),(0,100))) + else: + brightness = "disable" + if "color_temp" in entity.attributes.supported_color_modes: + if "color_temp" in entity.attributes: + # scale ha color temp range to 0-100 + color_temp = int(scale(entity.attributes.color_temp,(entity.attributes.min_mireds, entity.attributes.max_mireds),(0,100))) + else: + color_temp = "unknown" + else: + color_temp = "disable" + list_color_modes = ["xy", "rgb", "rgbw", "hs"] + if any(item in list_color_modes for item in entity.attributes.supported_color_modes): + color = "enable" + else: + color = "disable" + self._send_mqtt_msg(f"entityUpdateDetail,{get_icon_id('lightbulb')},{icon_color},{switch_val},{brightness},{color_temp},{color}") + + def generate_shutter_detail_page(self, entity): + pos = 100-int(entity.attributes.get("current_position", 50)) + self.send_mqtt_msg(f"entityUpdateDetail,{pos}") From 8097935c8d124e8c5056ef382fb24ee7f93ebb0f Mon Sep 17 00:00:00 2001 From: joBr99 <29555657+joBr99@users.noreply.github.com> Date: Fri, 25 Mar 2022 00:26:43 +0100 Subject: [PATCH 04/10] added state change callback --- apps/nspanel-lovelace-ui/luibackend/config.py | 18 +++++++---- .../luibackend/controller.py | 32 +++++++++++++++---- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/apps/nspanel-lovelace-ui/luibackend/config.py b/apps/nspanel-lovelace-ui/luibackend/config.py index e9f61430..dcb163bf 100644 --- a/apps/nspanel-lovelace-ui/luibackend/config.py +++ b/apps/nspanel-lovelace-ui/luibackend/config.py @@ -62,7 +62,17 @@ class PageNode(object): items.append(i.data) return items - + def get_all_items_recursive(self): + items = [] + for i in self.childs: + if len(i.childs) > 0: + items.extend(i.get_all_items_recursive()) + else: + if type(i.data) is dict: + items.append(i.data.get("item", next(iter(i.data)))) + else: + items.append(i.data) + return items class LuiBackendConfig(object): @@ -122,9 +132,3 @@ class LuiBackendConfig(object): def get_root_page(self): return self._page_config - def get_child_by_heading(self, heading): - for page in self._current_page.childs: - if heading == page.data["heading"]: - self._current_page = page - return self._current_page - diff --git a/apps/nspanel-lovelace-ui/luibackend/controller.py b/apps/nspanel-lovelace-ui/luibackend/controller.py index f55fdd8f..837edfba 100644 --- a/apps/nspanel-lovelace-ui/luibackend/controller.py +++ b/apps/nspanel-lovelace-ui/luibackend/controller.py @@ -15,14 +15,19 @@ class LuiController(object): self._current_page = None self._pages_gen = LuiPagesGen(ha_api, config, send_mqtt_msg) - # Setup time update callback - time = datetime.time(0, 0, 0) - ha_api.run_minutely(self._pages_gen.update_time, time) # send panel back to startup page on restart of this script self._pages_gen.page_type("pageStartup") - + # time update callback + time = datetime.time(0, 0, 0) + ha_api.run_minutely(self._pages_gen.update_time, time) + # weather callback + weather_interval = 15 * 60 # 15 minutes + ha_api.run_every(self.weather_update, "now", weather_interval) + # register callbacks + self.register_callbacks() + def startup(self, display_firmware_version): LOGGER.info(f"Startup Event; Display Firmware Version is {display_firmware_version}") # send time and date on startup @@ -31,11 +36,24 @@ class LuiController(object): # send panel to screensaver self._pages_gen.page_type("screensaver") - self.screensaver_open() + self.weather_update("") - def screensaver_open(self): + def weather_update(self, kwargs): we_name = self._config.get("weather") - self._pages_gen.update_screensaver_weather(kwargs={"weather": we_name, "unit": "°C"}) + unit = "°C" + self._pages_gen.update_screensaver_weather(kwargs={"weather": we_name, "unit": unit}) + + def register_callbacks(self): + items = self._config.get_root_page().get_all_items_recursive() + LOGGER.info(f"Registering callbacks for the following items: {items}") + for item in items: + self._ha_api.listen_state(self.state_change_callback, entity_id=item, attribute="all") + + def state_change_callback(self, entity, attribute, old, new, kwargs): + LOGGER.info(f"Got callback for: {entity}") + if entity in self._current_page.get_items(): + self._pages_gen.render_page(self._current_page) + def detail_open(self, detail_type, entity_id): if detail_type == "popupShutter": From 0c19ad4adb23491f62678c0e18f50a2ec72ff7ad Mon Sep 17 00:00:00 2001 From: joBr99 <29555657+joBr99@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:09:53 +0100 Subject: [PATCH 05/10] added updater --- HMI/nspanel.HMI | Bin 7418507 -> 7418507 bytes HMI/nspanel.tft | Bin 6257540 -> 6257540 bytes apps/nspanel-lovelace-ui/luibackend/config.py | 2 +- .../luibackend/controller.py | 19 +++-- .../luibackend/execptions.py | 8 -- .../luibackend/mqttListener.py | 15 +++- apps/nspanel-lovelace-ui/luibackend/pages.py | 6 +- .../nspanel-lovelace-ui/luibackend/updater.py | 72 ++++++++++++++++++ .../nspanel-lovelace-ui.py | 17 +++-- 9 files changed, 114 insertions(+), 25 deletions(-) delete mode 100644 apps/nspanel-lovelace-ui/luibackend/execptions.py create mode 100644 apps/nspanel-lovelace-ui/luibackend/updater.py diff --git a/HMI/nspanel.HMI b/HMI/nspanel.HMI index bc99b3c109fc3798f9ab4d354bd7d175e2d8c025..e051071e68f66ce0d82c1beb25e696a086af92d3 100644 GIT binary patch delta 853 zcmdte%TE(g6bA5nhha*l8=+OiN2k6}1koZh10pI4;+v@`iWL+sRjF-ZK}>XG^h+8g zA(Ade5*Nb89jU3w7!yb^Zd}xj8=`BCqALw6H-5S?`X_jk-{R(e=bW57_Tg2@7|H$# z*`ySWr!7NLd7UXdku|CvX{a9_5P#0J&lF2h-hX*%i2S@E-puxBw;$e7++I_(`Gt^C zk^55GTqXW4754*{h$Q%)Yt;-Xa(!B*l!Z~POR?>LqVvyFzoUY2F_#e!W-}a<)gDdG zDBnV9(J{d`2~qe_W=ggCePisdqh}pml0qewxMcu0N+_y>1pIwNXWRXox)J(gmg?3Xb?V-K2kJ@NI9iVpV zpo7#&hv+aJp`&z+j?)S1B8Q@MlDg>>ou(c-LuaX%&QXlcQy=xy1-eKBbcrrgoCYaD zSLiBTqajMtb#Eb_`rNIWH;P}q@qpE?yW>jmllLNI$r!&v_vV7>kJ8Q8rpmI5;WTAv L*jo&bOqKrx@v9{C delta 1103 zcmdtg+e=hY6bA5h&fL5Zjbl2RrQOR*yZ+6usn&LG{_3u>OTM{NQ7+z1QCRZ~`BmY62j* zhlE`5#qfiIHbE3=^p=pUO(08WGyOG23=&!0M8d>y(4fx~Y(cx;EKsmcpA?^&_3z?S z{Z^W(#OBc;E#$8kI_<^*2COF5B~ zco{F}6}*yH@oHYfYk3_fa|)+&8n0&yr*j5pau#QE&X_b}&}pMyp0vu{r@glBGEL|U zJG4ZJVkmp`M1$)5w{W6|bx zWFr~~^)V zqN|?hs+waOh^9kr2ydk3!*YhZ!yZruGhT-%*kpUJH&Z^=DwQZbul$!qC3T@x zsa3ujqc4fFvCNdMI4^oDoPARs39Mj)T*!lbD1Z&H5elIQHh~?Ap#(NVDL7yYY=v!5 z2Ia6Fc0dKpo#lc`*a=lo4K=U}YGF6jK|Sn&25>_oG{IhIhJCOfJa7P7;2<1=R%in+ z_>?J6`*f9TlA@|)r)-K7-d}JtG5QPMe3Bm U1>fWTzMNh^bSOE!owav=0ce^{LjV8( diff --git a/HMI/nspanel.tft b/HMI/nspanel.tft index b936ab795cd29735ae8c26ecd45f10292e800562..f531197ede7e984efaf428751a5da17eeda86546 100644 GIT binary patch delta 264 zcmWN=TQ7oP0KjpoS5i745pPOFgi;hCr%(=g9UtYexpUjP=%TGI{>w*rzlg0HTc4wE z(VeZ$*7Mtb{QVn{8w_H=hzW%u%nV~;1S>X1vE#sri!sKTz|ABcrkKXd46~^Cn8Qzi zd4epkNQfnt3A4f~5!P5|gD5dJiIZT9ZIYzeVV5*}>~lbdLykD+gj2Gdan1!f@?3Jo zH8&KvMdOZp9(d%5=N}PJmE?e=*MmiOE3Jx9Lix;Azbd+`Eb4mqSJ?HoQ<%Sdnvh@Q WqFj<+Sz@^W delta 264 zcmWN=TQ7oP0KoCQdOL_z=58_L> zv$eH(e%p_~zJmP501AVcQL$jfhMgfC3^Rh0QCzt3Fvd7uCYWT3X=d;-i^d%DEZ}F6 z081xO5AbJ1CNwHgkMu*Cdp_AD&B5F6Tzt3e0goQ40lVkjo&ZfG`?NJ`rgxp{3KW8 Us$7%na--YRpF1J*r&9X)2mdf)>;M1& diff --git a/apps/nspanel-lovelace-ui/luibackend/config.py b/apps/nspanel-lovelace-ui/luibackend/config.py index dcb163bf..d2aa16b2 100644 --- a/apps/nspanel-lovelace-ui/luibackend/config.py +++ b/apps/nspanel-lovelace-ui/luibackend/config.py @@ -115,7 +115,7 @@ class LuiBackendConfig(object): self._config[k] = v LOGGER.info(f"Loaded config: {self._config}") - root_page = {"items": self.get("pages")} + root_page = {"items": self.get("pages"), "name": "root"} self._page_config = PageNode(root_page) LOGGER.info(f"Parsed Page config to the following Tree: \n {self._page_config.dump()}") diff --git a/apps/nspanel-lovelace-ui/luibackend/controller.py b/apps/nspanel-lovelace-ui/luibackend/controller.py index 837edfba..97a550ec 100644 --- a/apps/nspanel-lovelace-ui/luibackend/controller.py +++ b/apps/nspanel-lovelace-ui/luibackend/controller.py @@ -12,7 +12,8 @@ class LuiController(object): self._config = config self._send_mqtt_msg = send_mqtt_msg - self._current_page = None + # first child of root page (default, after startup) + self._current_page = self._config._page_config.childs[0] self._pages_gen = LuiPagesGen(ha_api, config, send_mqtt_msg) @@ -28,8 +29,8 @@ class LuiController(object): # register callbacks self.register_callbacks() - def startup(self, display_firmware_version): - LOGGER.info(f"Startup Event; Display Firmware Version is {display_firmware_version}") + def startup(self): + LOGGER.info(f"Startup Event") # send time and date on startup self._pages_gen.update_time("") self._pages_gen.update_date("") @@ -53,6 +54,12 @@ class LuiController(object): LOGGER.info(f"Got callback for: {entity}") if entity in self._current_page.get_items(): self._pages_gen.render_page(self._current_page) + # send detail page update, just in case + if self._current_page.type in ["cardGrid", "cardEntities"]: + if entity.startswith("light"): + self._pages_gen.generate_light_detail_page(entity) + if entity.startswith("switch"): + self._pages_gen.generate_shutter_detail_page(entity) def detail_open(self, detail_type, entity_id): @@ -65,8 +72,8 @@ class LuiController(object): LOGGER.debug(f"Button Press Event; entity_id: {entity_id}; button_type: {button_type}; value: {value} ") # internal buttons if(entity_id == "screensaver" and button_type == "enter"): - # go to first child of root page (default, after startup) - self._current_page = self._config._page_config.childs[0] + self._pages_gen.render_page(self._current_page) + if(button_type == "bExit"): self._pages_gen.render_page(self._current_page) if(button_type == "bNext"): @@ -75,8 +82,6 @@ class LuiController(object): if(button_type == "bPrev"): self._current_page = self._current_page.prev() self._pages_gen.render_page(self._current_page) - if(button_type == "bExit"): - self._pages_gen.render_page(self._current_page) # buttons with actions on HA if button_type == "OnOff": diff --git a/apps/nspanel-lovelace-ui/luibackend/execptions.py b/apps/nspanel-lovelace-ui/luibackend/execptions.py deleted file mode 100644 index b5a8e217..00000000 --- a/apps/nspanel-lovelace-ui/luibackend/execptions.py +++ /dev/null @@ -1,8 +0,0 @@ -class LuiBackendException(Exception): - pass - -class LuiBackendConfigIncomplete(LuiBackendException): - pass - -class LuiBackendConfigError(LuiBackendException): - pass \ No newline at end of file diff --git a/apps/nspanel-lovelace-ui/luibackend/mqttListener.py b/apps/nspanel-lovelace-ui/luibackend/mqttListener.py index ad4a7133..24bf8d87 100644 --- a/apps/nspanel-lovelace-ui/luibackend/mqttListener.py +++ b/apps/nspanel-lovelace-ui/luibackend/mqttListener.py @@ -6,8 +6,9 @@ LOGGER = logging.getLogger(__name__) class LuiMqttListener(object): - def __init__(self, mqtt_api, topic, controller): + def __init__(self, mqtt_api, topic, controller, updater): self._controller = controller + self._updater = updater # Setup, mqtt subscription and callback mqtt_api.mqtt_subscribe(topic=topic) mqtt_api.listen_event(self.mqtt_event_callback, "MQTT_MESSAGE", topic=topic, namespace='mqtt') @@ -17,6 +18,9 @@ class LuiMqttListener(object): LOGGER.info(f'MQTT callback for: {data}') # Parse Json Message from Tasmota and strip out message from nextion display data = json.loads(data["payload"]) + if("nlui_driver_version" in data): + msg = data["nlui_driver_version"] + self._updater.set_tasmota_driver_version(int(msg)) if("CustomRecv" not in data): return msg = data["CustomRecv"] @@ -27,9 +31,14 @@ class LuiMqttListener(object): if msg[0] == "event": if msg[1] == "startup": display_firmware_version = int(msg[2]) - self._controller.startup(display_firmware_version) + self._updater.set_current_display_firmware_version(display_firmware_version) + # check for updates + msg_send = self._updater.check_updates() + # send messages for current page + if not msg_send: + self._controller.startup() if msg[1] == "screensaverOpen": - self._controller.screensaver_open() + self._controller.weather_update("") if msg[1] == "buttonPress2": entity_id = msg[2] btype = msg[3] diff --git a/apps/nspanel-lovelace-ui/luibackend/pages.py b/apps/nspanel-lovelace-ui/luibackend/pages.py index d75f26b2..0184f365 100644 --- a/apps/nspanel-lovelace-ui/luibackend/pages.py +++ b/apps/nspanel-lovelace-ui/luibackend/pages.py @@ -268,4 +268,8 @@ class LuiPagesGen(object): def generate_shutter_detail_page(self, entity): pos = 100-int(entity.attributes.get("current_position", 50)) - self.send_mqtt_msg(f"entityUpdateDetail,{pos}") + self._send_mqtt_msg(f"entityUpdateDetail,{pos}") + + def send_message_page(self, id, heading, msg, b1, b2): + self._send_mqtt_msg(f"pageType,popupNotify") + self._send_mqtt_msg(f"entityUpdateDetail,|{id}|{heading}|65535|{b1}|65535|{b2}|65535|{msg}|65535|0") \ No newline at end of file diff --git a/apps/nspanel-lovelace-ui/luibackend/updater.py b/apps/nspanel-lovelace-ui/luibackend/updater.py new file mode 100644 index 00000000..93d16652 --- /dev/null +++ b/apps/nspanel-lovelace-ui/luibackend/updater.py @@ -0,0 +1,72 @@ +import logging + +LOGGER = logging.getLogger(__name__) + +class Updater: + def __init__(self, controller, mode, desired_display_firmware_version, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url): + self.desired_display_firmware_version = desired_display_firmware_version + self.desired_display_firmware_url = desired_display_firmware_url + self.desired_tasmota_driver_version = desired_tasmota_driver_version + self.desired_tasmota_driver_url = desired_tasmota_driver_url + + self.mode = mode + self.controller = controller + self.current_tasmota_driver_version = None + self.current_display_firmware_version = None + + def set_tasmota_driver_version(self, driver_version): + self.current_tasmota_driver_version = driver_version + def set_current_display_firmware_version(self, panel_version): + self.current_display_firmware_version = panel_version + + def check_pre_req(self): + # we need to know both versions to continue + if self.current_tasmota_driver_version is not None and self.current_display_firmware_version is not None: + # tasmota driver has to be at least version 2 for Update command + # and panel has to be at version 11 for notify commands + # version 16 for new button cmd format + if self.current_tasmota_driver_version >= 2 and self.current_display_firmware_version >= 16: + return True + return False + + def check_updates(self): + # return's true if a notification was send to the panel + # run pre req check + if self.check_pre_req(): + LOGGER.info("Update Pre-Check sucessful Tasmota Driver Version: %s Panel Version: %s", self.current_tasmota_driver_version, self.current_display_firmware_version) + # check if tasmota driver needs update + if self.current_tasmota_driver_version < self.desired_tasmota_driver_version: + LOGGER.info("Update of Tasmota Driver needed") + # in auto mode just do the update + if self.mode == "auto": + self.update_berry_driver() + return False + # send notification about the update + if self.mode == "auto-notify": + update_msg = "There's an update avalible for the tasmota berry driver, do you want to start the update now? If you encounter issues after the update or this message appears frequently, please checkthe manual and repeat the installation steps for the tasmota berry driver. " + self.controller._pages_gen.send_message_page("updateBerryNoYes", "Driver Update available!", update_msg, "Dismiss", "Yes") + return True + return False + # check if display firmware needs an update + if self.current_display_firmware_version < self.desired_display_firmware_version: + LOGGER.info("Update of Display Firmware needed") + # in auto mode just do the update + if self.mode == "auto": + self.update_panel_driver() + return False + # send notification about the update + if self.mode == "auto-notify": + update_msg = "There's a firmware update avalible for the nextion sceen inside of nspanel, do you want to start the update now? If the update fails check the installation manual and flash again over the tasmota console. Be pationed the update will take a while." + self.controller._pages_gen.send_message_page("updateDisplayNoYes", "Display Update available!", update_msg, "Dismiss", "Yes") + return True + return False + else: + LOGGER.info("Update Pre-Check failed Tasmota Driver Version: %s Panel Version: %s", self.current_tasmota_driver_version, self.current_display_firmware_version) + return False + + def update_berry_driver(self): + topic = self.controller._config["panelSendTopic"].replace("CustomSend", "UpdateDriverVersion") + self.controller._send_mqtt_msg(topic, self.desired_tasmota_driver_url) + def update_panel_driver(self): + topic = self.controller._config["panelSendTopic"].replace("CustomSend", "FlashNextion") + self.controller._send_mqtt_msg(topic, self.desired_display_firmware_url) diff --git a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py index 8245afc5..a82bb5c8 100644 --- a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py +++ b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py @@ -9,7 +9,7 @@ import hassapi as hass from luibackend.config import LuiBackendConfig from luibackend.controller import LuiController from luibackend.mqttListener import LuiMqttListener - +from luibackend.updater import Updater LOGGER = logging.getLogger(__name__) @@ -60,14 +60,21 @@ class NsPanelLovelaceUIManager(hass.Hass): LOGGER.info(f"Sending MQTT Message: {msg}") mqtt_api.mqtt_publish(topic_send, msg) + # Request Tasmota Driver Version + mqtt_api.mqtt_publish(topic_send.replace("CustomSend", "GetDriverVersion"), "x") + controller = LuiController(self, cfg, send_mqtt_msg) - topic_recv = cfg.get("panelRecvTopic") - mqtt_listener = LuiMqttListener(mqtt_api, topic_recv, controller) - + desired_display_firmware_version = 17 + desired_display_firmware_url = "http://nspanel.pky.eu/lovelace-ui/github/nspanel-v1.8.0.tft" + desired_tasmota_driver_version = 3 + desired_tasmota_driver_url = "https://raw.githubusercontent.com/joBr99/nspanel-lovelace-ui/main/tasmota/autoexec.be" + mode = cfg.get("updateMode") + updater = Updater(controller, mode, desired_display_firmware_version, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url) - + topic_recv = cfg.get("panelRecvTopic") + mqtt_listener = LuiMqttListener(mqtt_api, topic_recv, controller, updater) LOGGER.info('Started') From 5b2d25bcd680ac69a9bf688caa5ae50bdfe83e90 Mon Sep 17 00:00:00 2001 From: joBr99 <29555657+joBr99@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:11:53 +0100 Subject: [PATCH 06/10] upd docs --- HMI/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/HMI/README.md b/HMI/README.md index a095971d..bbd59dc5 100644 --- a/HMI/README.md +++ b/HMI/README.md @@ -138,6 +138,12 @@ The following message can be used to update the content on the cardEntities Page ## Messages from Nextion Display +`event,buttonPress2,pageName,bNext` + +`event,buttonPress2,pageName,bPrev` + +`event,buttonPress2,pageName,bExit` + ### startup page `event,startup,version` @@ -149,13 +155,10 @@ The following message can be used to update the content on the cardEntities Page `event,screensaverOpen` - Screensaver has opened - ### cardEntities Page `event,*eventName*,*entityName*,*actionName*,*optionalValue*` -`event,pageOpen,0` - `event,buttonPress2,internalNameEntity,up` `event,buttonPress2,internalNameEntity,down` @@ -192,8 +195,6 @@ The following message can be used to update the content on the cardEntities Page ### cardThermo Page -`event,pageOpen,0` - `event,buttonPress2,*entityName*,tempUpd,*temperature*` `event,buttonPress2,*entityName*,hvac_action,*hvac_action*` From b140f33f2bc9ca477c66f7c6797273880f51ebe4 Mon Sep 17 00:00:00 2001 From: joBr99 <29555657+joBr99@users.noreply.github.com> Date: Fri, 25 Mar 2022 14:06:40 +0100 Subject: [PATCH 07/10] added localization fixed updater --- appdaemon/apps.yaml | 76 +++++++++++++------ apps/nspanel-lovelace-ui/luibackend/config.py | 3 - .../luibackend/controller.py | 40 ++++++++-- .../luibackend/localization.py | 12 +++ .../luibackend/mqttListener.py | 4 + apps/nspanel-lovelace-ui/luibackend/pages.py | 15 ++-- .../nspanel-lovelace-ui/luibackend/updater.py | 21 +++-- .../nspanel-lovelace-ui.py | 9 ++- 8 files changed, 132 insertions(+), 48 deletions(-) create mode 100644 apps/nspanel-lovelace-ui/luibackend/localization.py diff --git a/appdaemon/apps.yaml b/appdaemon/apps.yaml index 5f6b33ae..cd7d5544 100644 --- a/appdaemon/apps.yaml +++ b/appdaemon/apps.yaml @@ -3,28 +3,58 @@ nspanel: module: nspanel-lovelace-ui2 class: NsPanelLovelaceUIManager config: - panelRecvTopic: "tele/tasmota_nspdebugtest/RESULT" - panelSendTopic: "cmnd/tasmota_nspdebugtest/CustomSend" + panelRecvTopic: "tele/tasmota_your_mqtt_topic/RESULT" + panelSendTopic: "cmnd/tasmota_your_mqtt_topic/CustomSend" + updateMode: "auto-notify" + timeoutScreensaver: 20 + #brightnessScreensaver: 10 + brightnessScreensaver: + - time: "7:00:00" + value: 10 + - time: "23:00:00" + value: 0 + locale: "de_DE" # only used if babel python package is installed + dateFormatBabel: "full" # only used if babel python package is installed + # formatting options on https://babel.pocoo.org/en/latest/dates.html?highlight=name%20of%20day#date-fields + timeFormat: "%H:%M" + dateFormat: "%A, %d. %B %Y" # ignored if babel python package is installed + buttonText: KLICKEN + sceneText: ACTIVIEREN + weatherEntity: weather.example pages: - - type: screensaver - weather: weather.k3ll3r + - type: cardEntities + heading: Example Page 1 items: - - type: cardEntities - heading: Test Entities 1 - items: - - switch.test_item - - type: cardEntities - heading: Test Entities 1 - items: - - switch.test_item - - switch.deckenbeleuchtung_hinten - - switch.test_item - - switch.test_item - - switch.deckenbeleuchtung_hinten - - switch.test_item - - type: cardGrid - heading: Test Grid 1 - items: - - switch.test_item - - switch.test_item - - switch.test_item + - cover.example_cover + - switch.example_switch + - input_boolean.example_input_boolean + - sensor.example_sensor + - type: cardEntities + heading: Example Page 2 + items: + - button.example_button + - input_button.example_input_button + - light.light_example + - delete # (read this as 'empty') + - type: cardEntities + heading: Example Page 3 + items: + - scene.example_scene + - delete + - delete + - delete + - type: cardGrid + heading: Example Page 4 + items: + - light.light_example + - button.example_button + - cover.example_cover + - scene.example_scene + - switch.example_switch + - delete + - type: cardThermo + heading: Exmaple Thermostat + item: climate.example_climate + - type: cardMedia + heading: Exampe Media + item: media_player.spotify_user \ No newline at end of file diff --git a/apps/nspanel-lovelace-ui/luibackend/config.py b/apps/nspanel-lovelace-ui/luibackend/config.py index d2aa16b2..f2a8258d 100644 --- a/apps/nspanel-lovelace-ui/luibackend/config.py +++ b/apps/nspanel-lovelace-ui/luibackend/config.py @@ -1,8 +1,5 @@ import logging -from luibackend.exceptions import LuiBackendConfigIncomplete -from luibackend.exceptions import LuiBackendConfigError - LOGGER = logging.getLogger(__name__) diff --git a/apps/nspanel-lovelace-ui/luibackend/controller.py b/apps/nspanel-lovelace-ui/luibackend/controller.py index 97a550ec..146e6a65 100644 --- a/apps/nspanel-lovelace-ui/luibackend/controller.py +++ b/apps/nspanel-lovelace-ui/luibackend/controller.py @@ -29,6 +29,28 @@ class LuiController(object): # register callbacks self.register_callbacks() + self.current_screensaver_brightness = 20 + # calc screensaver brightness + # set brightness of screensaver + if type(self._config.get("brightnessScreensaver")) == int: + self.current_screensaver_brightness = self._config.get("brightnessScreensaver") + elif type(self._config.get("brightnessScreensaver")) == list: + sorted_timesets = sorted(self._config.get("brightnessScreensaver"), key=lambda d: self._ha_api.parse_time(d['time'])) + found_current_dim_value = False + for index, timeset in enumerate(sorted_timesets): + self._ha_api.run_daily(self.update_screensaver_brightness, timeset["time"], value=timeset["value"]) + LOGGER.info("Current time %s", self._ha_api.get_now().time()) + if self._ha_api.parse_time(timeset["time"]) > self._ha_api.get_now().time() and not found_current_dim_value: + # first time after current time, set dim value + self.current_screensaver_brightness = sorted_timesets[index-1]["value"] + LOGGER.info("Setting dim value to %s", sorted_timesets[index-1]) + found_current_dim_value = True + # still no dim value + if not found_current_dim_value: + self.current_screensaver_brightness = sorted_timesets[-1]["value"] + # send screensaver brightness in case config has changed + self.update_screensaver_brightness(kwargs={"value": self.current_screensaver_brightness}) + def startup(self): LOGGER.info(f"Startup Event") # send time and date on startup @@ -39,6 +61,10 @@ class LuiController(object): self._pages_gen.page_type("screensaver") self.weather_update("") + def update_screensaver_brightness(self, kwargs): + self.current_screensaver_brightness = kwargs['value'] + self._send_mqtt_msg(f"dimmode,{self.current_screensaver_brightness}") + def weather_update(self, kwargs): we_name = self._config.get("weather") unit = "°C" @@ -48,7 +74,8 @@ class LuiController(object): items = self._config.get_root_page().get_all_items_recursive() LOGGER.info(f"Registering callbacks for the following items: {items}") for item in items: - self._ha_api.listen_state(self.state_change_callback, entity_id=item, attribute="all") + if self._ha_api.entity_exists(item): + self._ha_api.listen_state(self.state_change_callback, entity_id=item, attribute="all") def state_change_callback(self, entity, attribute, old, new, kwargs): LOGGER.info(f"Got callback for: {entity}") @@ -71,18 +98,21 @@ class LuiController(object): def button_press(self, entity_id, button_type, value): LOGGER.debug(f"Button Press Event; entity_id: {entity_id}; button_type: {button_type}; value: {value} ") # internal buttons - if(entity_id == "screensaver" and button_type == "enter"): + if entity_id == "screensaver" and button_type == "enter": self._pages_gen.render_page(self._current_page) - if(button_type == "bExit"): + if button_type == "bExit": self._pages_gen.render_page(self._current_page) - if(button_type == "bNext"): + if button_type == "bNext": self._current_page = self._current_page.next() self._pages_gen.render_page(self._current_page) - if(button_type == "bPrev"): + if button_type == "bPrev": self._current_page = self._current_page.prev() self._pages_gen.render_page(self._current_page) + elif entity_id == "updateDisplayNoYes" and value == "no": + self._pages_gen.render_page(self._current_page) + # buttons with actions on HA if button_type == "OnOff": if value == "1": diff --git a/apps/nspanel-lovelace-ui/luibackend/localization.py b/apps/nspanel-lovelace-ui/luibackend/localization.py new file mode 100644 index 00000000..b59d0d1e --- /dev/null +++ b/apps/nspanel-lovelace-ui/luibackend/localization.py @@ -0,0 +1,12 @@ +translations = { + 'de_DE': { + 'ACTIVATE': "AKTIVIEREN", + 'PRESS': "DRÜCKEN", + } +} + +def get_translation(locale, input): + if locale in translations: + return translations.get(locale).get(input, input) + else: + return input \ No newline at end of file diff --git a/apps/nspanel-lovelace-ui/luibackend/mqttListener.py b/apps/nspanel-lovelace-ui/luibackend/mqttListener.py index 24bf8d87..1d3d1c5a 100644 --- a/apps/nspanel-lovelace-ui/luibackend/mqttListener.py +++ b/apps/nspanel-lovelace-ui/luibackend/mqttListener.py @@ -43,6 +43,10 @@ class LuiMqttListener(object): entity_id = msg[2] btype = msg[3] value = msg[4] if len(msg) > 4 else None + + if entity_id == "updateDisplayNoYes" and value == "yes": + self._updater.update_panel_driver() + self._controller.button_press(entity_id, btype, value) if msg[1] == "pageOpenDetail": self._controller.detail_open(msg[2], msg[3]) diff --git a/apps/nspanel-lovelace-ui/luibackend/pages.py b/apps/nspanel-lovelace-ui/luibackend/pages.py index 0184f365..3fce0ec7 100644 --- a/apps/nspanel-lovelace-ui/luibackend/pages.py +++ b/apps/nspanel-lovelace-ui/luibackend/pages.py @@ -4,6 +4,7 @@ import datetime from icon_mapping import get_icon_id from icons import get_icon_id_ha from helper import scale, pos_to_color, rgb_dec565, rgb_brightness +from localization import get_translation # check Babel import importlib @@ -18,6 +19,7 @@ class LuiPagesGen(object): def __init__(self, ha_api, config, send_mqtt_msg): self._ha_api = ha_api self._config = config + self._locale = config.get("locale") self._send_mqtt_msg = send_mqtt_msg def getEntityColor(self, entity): @@ -44,8 +46,7 @@ class LuiPagesGen(object): global babel_spec if babel_spec is not None: dateformat = self._config.get("dateFormatBabel") - locale = self._config.get("locale") - date = babel.dates.format_date(datetime.datetime.now(), dateformat, locale=locale) + date = babel.dates.format_date(datetime.datetime.now(), dateformat, locale=self._locale) else: dateformat = self._config.get("dateFormat") date = datetime.datetime.now().strftime(dateformat) @@ -81,8 +82,8 @@ class LuiPagesGen(object): global babel_spec if babel_spec is not None: - up1 = babel.dates.format_date(up1, "E", locale=self.config["locale"]) - up2 = babel.dates.format_date(up2, "E", locale=self.config["locale"]) + up1 = babel.dates.format_date(up1, "E", locale=self._locale) + up2 = babel.dates.format_date(up2, "E", locale=self._locale) else: up1 = up1.strftime("%a") up2 = up2.strftime("%a") @@ -103,7 +104,8 @@ class LuiPagesGen(object): return f",{item_type},,,,," if item_type == "navigate": icon_id = get_icon_id_ha("button", overwrite=icon) - return f",button,{item},0,17299,{item},PRESS" + text = get_translation(self._locale,"PRESS") + return f",button,{item},0,17299,{item},{text}" if not self._ha_api.entity_exists(item): return f",text,{item},{get_icon_id('alert-circle-outline')},17299,Not found check, apps.yaml" # HA Entities @@ -134,7 +136,8 @@ class LuiPagesGen(object): return f",button,{item},{icon_id},17299,{name},PRESS" if item_type == "scene": icon_id = get_icon_id_ha("scene", overwrite=icon) - return f",button,{item},{icon_id},17299,{name},ACTIVATE" + text = get_translation(self._locale,"PRESS") + return f",button,{item},{icon_id},17299,{name},{text}" def generate_entities_page(self, heading, items): diff --git a/apps/nspanel-lovelace-ui/luibackend/updater.py b/apps/nspanel-lovelace-ui/luibackend/updater.py index 93d16652..aa976850 100644 --- a/apps/nspanel-lovelace-ui/luibackend/updater.py +++ b/apps/nspanel-lovelace-ui/luibackend/updater.py @@ -3,14 +3,15 @@ import logging LOGGER = logging.getLogger(__name__) class Updater: - def __init__(self, controller, mode, desired_display_firmware_version, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url): + def __init__(self, send_mqtt_msg, topic_send, mode, desired_display_firmware_version, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url): self.desired_display_firmware_version = desired_display_firmware_version self.desired_display_firmware_url = desired_display_firmware_url self.desired_tasmota_driver_version = desired_tasmota_driver_version self.desired_tasmota_driver_url = desired_tasmota_driver_url self.mode = mode - self.controller = controller + self._send_mqtt_msg = send_mqtt_msg + self.topic_send = topic_send self.current_tasmota_driver_version = None self.current_display_firmware_version = None @@ -29,6 +30,10 @@ class Updater: return True return False + def send_message_page(self, id, heading, msg, b1, b2): + self._send_mqtt_msg(f"pageType,popupNotify") + self._send_mqtt_msg(f"entityUpdateDetail,|{id}|{heading}|65535|{b1}|65535|{b2}|65535|{msg}|65535|0") + def check_updates(self): # return's true if a notification was send to the panel # run pre req check @@ -44,7 +49,7 @@ class Updater: # send notification about the update if self.mode == "auto-notify": update_msg = "There's an update avalible for the tasmota berry driver, do you want to start the update now? If you encounter issues after the update or this message appears frequently, please checkthe manual and repeat the installation steps for the tasmota berry driver. " - self.controller._pages_gen.send_message_page("updateBerryNoYes", "Driver Update available!", update_msg, "Dismiss", "Yes") + self.send_message_page("updateBerryNoYes", "Driver Update available!", update_msg, "Dismiss", "Yes") return True return False # check if display firmware needs an update @@ -57,7 +62,7 @@ class Updater: # send notification about the update if self.mode == "auto-notify": update_msg = "There's a firmware update avalible for the nextion sceen inside of nspanel, do you want to start the update now? If the update fails check the installation manual and flash again over the tasmota console. Be pationed the update will take a while." - self.controller._pages_gen.send_message_page("updateDisplayNoYes", "Display Update available!", update_msg, "Dismiss", "Yes") + self.send_message_page("updateDisplayNoYes", "Display Update available!", update_msg, "Dismiss", "Yes") return True return False else: @@ -65,8 +70,8 @@ class Updater: return False def update_berry_driver(self): - topic = self.controller._config["panelSendTopic"].replace("CustomSend", "UpdateDriverVersion") - self.controller._send_mqtt_msg(topic, self.desired_tasmota_driver_url) + topic = self.topic_send.replace("CustomSend", "UpdateDriverVersion") + self._send_mqtt_msg(self.desired_tasmota_driver_url, topic=topic) def update_panel_driver(self): - topic = self.controller._config["panelSendTopic"].replace("CustomSend", "FlashNextion") - self.controller._send_mqtt_msg(topic, self.desired_display_firmware_url) + topic = self.topic_send.replace("CustomSend", "FlashNextion") + self._send_mqtt_msg(self.desired_display_firmware_url, topic=topic) diff --git a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py index a82bb5c8..d15d294a 100644 --- a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py +++ b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py @@ -56,9 +56,11 @@ class NsPanelLovelaceUIManager(hass.Hass): cfg = self._cfg = LuiBackendConfig(self.args["config"]) topic_send = cfg.get("panelSendTopic") - def send_mqtt_msg(msg): + def send_mqtt_msg(msg, topic=None): + if topic is None: + topic = topic_send LOGGER.info(f"Sending MQTT Message: {msg}") - mqtt_api.mqtt_publish(topic_send, msg) + mqtt_api.mqtt_publish(topic, msg) # Request Tasmota Driver Version mqtt_api.mqtt_publish(topic_send.replace("CustomSend", "GetDriverVersion"), "x") @@ -71,7 +73,8 @@ class NsPanelLovelaceUIManager(hass.Hass): desired_tasmota_driver_url = "https://raw.githubusercontent.com/joBr99/nspanel-lovelace-ui/main/tasmota/autoexec.be" mode = cfg.get("updateMode") - updater = Updater(controller, mode, desired_display_firmware_version, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url) + topic_send = cfg.get("panelSendTopic") + updater = Updater(send_mqtt_msg, topic_send, mode, desired_display_firmware_version, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url) topic_recv = cfg.get("panelRecvTopic") mqtt_listener = LuiMqttListener(mqtt_api, topic_recv, controller, updater) From e6057ab5ff46c959f80863ed1a592466396649e4 Mon Sep 17 00:00:00 2001 From: joBr99 <29555657+joBr99@users.noreply.github.com> Date: Fri, 25 Mar 2022 14:34:06 +0100 Subject: [PATCH 08/10] fixed alerts --- apps/nspanel-lovelace-ui/luibackend/config.py | 2 +- apps/nspanel-lovelace-ui/luibackend/controller.py | 1 + apps/nspanel-lovelace-ui/luibackend/pages.py | 2 +- apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py | 5 +---- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/nspanel-lovelace-ui/luibackend/config.py b/apps/nspanel-lovelace-ui/luibackend/config.py index f2a8258d..e66b56ad 100644 --- a/apps/nspanel-lovelace-ui/luibackend/config.py +++ b/apps/nspanel-lovelace-ui/luibackend/config.py @@ -118,7 +118,7 @@ class LuiBackendConfig(object): LOGGER.info(f"Parsed Page config to the following Tree: \n {self._page_config.dump()}") def check(self): - errors = 0 + return def get(self, name): value = self._config.get(name) diff --git a/apps/nspanel-lovelace-ui/luibackend/controller.py b/apps/nspanel-lovelace-ui/luibackend/controller.py index 146e6a65..5b7ad189 100644 --- a/apps/nspanel-lovelace-ui/luibackend/controller.py +++ b/apps/nspanel-lovelace-ui/luibackend/controller.py @@ -1,5 +1,6 @@ import logging import datetime +from helper import scale, pos_to_color from pages import LuiPagesGen diff --git a/apps/nspanel-lovelace-ui/luibackend/pages.py b/apps/nspanel-lovelace-ui/luibackend/pages.py index 3fce0ec7..3582ff6a 100644 --- a/apps/nspanel-lovelace-ui/luibackend/pages.py +++ b/apps/nspanel-lovelace-ui/luibackend/pages.py @@ -3,7 +3,7 @@ import datetime from icon_mapping import get_icon_id from icons import get_icon_id_ha -from helper import scale, pos_to_color, rgb_dec565, rgb_brightness +from helper import scale, rgb_dec565, rgb_brightness from localization import get_translation # check Babel diff --git a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py index 73343348..1d26825b 100644 --- a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py +++ b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py @@ -1,8 +1,5 @@ -import asyncio import logging -import sys import traceback -import uuid import hassapi as hass @@ -77,6 +74,6 @@ class NsPanelLovelaceUIManager(hass.Hass): updater = Updater(send_mqtt_msg, topic_send, mode, desired_display_firmware_version, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url) topic_recv = cfg.get("panelRecvTopic") - mqtt_listener = LuiMqttListener(mqtt_api, topic_recv, controller, updater) + LuiMqttListener(mqtt_api, topic_recv, controller, updater) LOGGER.info('Started') From 828b865f11cf45b7441ed84e49c62bbd8bab3d43 Mon Sep 17 00:00:00 2001 From: joBr99 <29555657+joBr99@users.noreply.github.com> Date: Fri, 25 Mar 2022 13:34:26 +0000 Subject: [PATCH 09/10] fixed alerts (add nextion2text) --- HMI/n2t-out/Program.s.txt | 13 ----- HMI/n2t-out/cardAlarm.txt | 18 +------ HMI/n2t-out/cardEntities.txt | 44 +++++++++++----- HMI/n2t-out/cardGrid.txt | 16 +----- HMI/n2t-out/cardMedia.txt | 79 +++++++--------------------- HMI/n2t-out/cardThermo.txt | 18 +------ HMI/n2t-out/nspanel_Stats.txt | 96 +++++++++++++++++------------------ HMI/n2t-out/pageStartup.txt | 6 ++- HMI/n2t-out/pageTest.txt | 54 ++++---------------- HMI/n2t-out/popupLight.txt | 7 +-- HMI/n2t-out/popupNotify.txt | 7 +-- HMI/n2t-out/popupShutter.txt | 7 +-- HMI/n2t-out/screensaver.txt | 40 +++++---------- 13 files changed, 129 insertions(+), 276 deletions(-) diff --git a/HMI/n2t-out/Program.s.txt b/HMI/n2t-out/Program.s.txt index 36907784..0ea4a9f5 100644 --- a/HMI/n2t-out/Program.s.txt +++ b/HMI/n2t-out/Program.s.txt @@ -5,24 +5,11 @@ Program.s int recvCrc=0 int payloadLength=0 int par0=0,par1=0 - // landsspace orientation x has 480px and y has 320px xy limits todo: adjust xy values to something that fit's resulution - //Maximum values in directional change for Swipes beeing detected as swipe (diagonal swipes are invalid) (for one axis at a time) - int xLimit=125,yLimit=125 - int ixLimit=-125,iyLimit=-125 - //Minimum values for swipes, directional changes below theese values are ignored, because they could be unintended swipes - int xLimitMin=80,yLimitMin=80 - int ixLimitMin=-80,iyLimitMin=-80 - // Swipe Result Vars - int ycR=0,xcR=0 - // Start End Swipe Touch Locations - int yc1=0,xc1=0,yc2=0,xc2=0 // sleep timeout in s int sleepTimeout=20 int sleepValue=0 // dim value int dimValue=40 - // current page - int nPage=0 // fix touch offset lcd_dev fffb 0002 0000 0020 page pageStartup diff --git a/HMI/n2t-out/cardAlarm.txt b/HMI/n2t-out/cardAlarm.txt index e1557b88..db7e0546 100644 --- a/HMI/n2t-out/cardAlarm.txt +++ b/HMI/n2t-out/cardAlarm.txt @@ -324,14 +324,7 @@ Button bNext Events Touch Press Event - nPage=nPage+1 - nPageDisp.val=nPage - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,cardAlarm,bNext" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC @@ -378,14 +371,7 @@ Button bPrev Events Touch Press Event - nPage=nPage-1 - nPageDisp.val=nPage - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,cardAlarm,bPrev" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC diff --git a/HMI/n2t-out/cardEntities.txt b/HMI/n2t-out/cardEntities.txt index 11928259..9ed5176c 100644 --- a/HMI/n2t-out/cardEntities.txt +++ b/HMI/n2t-out/cardEntities.txt @@ -1131,13 +1131,14 @@ Button bPrev Events Touch Press Event - nPage=nPage-1 - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,cardEntities," + if(bPrev.isbr==1) + { + tSend.txt+="bBack" + }else + { + tSend.txt+="bPrev" + } //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC @@ -1184,13 +1185,7 @@ Button bNext Events Touch Press Event - nPage=nPage+1 - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,cardEntities,bNext" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC @@ -1776,6 +1771,27 @@ Timer tmSerial if(tInstruction.txt=="entityUpdHeading") { spstr strCommand.txt,tHeading.txt,",",1 + spstr strCommand.txt,tTmp.txt,",",2 + if(tTmp.txt=="0") + { + vis bPrev,0 + }else + { + vis bPrev,1 + if(tTmp.txt=="2") + { + bPrev.txt="" + bPrev.isbr=1 + } + } + spstr strCommand.txt,tTmp.txt,",",3 + if(tTmp.txt=="0") + { + vis bNext,0 + }else + { + vis bNext,1 + } } if(tInstruction.txt=="entityUpd") { diff --git a/HMI/n2t-out/cardGrid.txt b/HMI/n2t-out/cardGrid.txt index ba246587..1a6ef987 100644 --- a/HMI/n2t-out/cardGrid.txt +++ b/HMI/n2t-out/cardGrid.txt @@ -515,13 +515,7 @@ Button bPrev Events Touch Press Event - nPage=nPage-1 - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,cardGrid,bPrev" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC @@ -568,13 +562,7 @@ Button bNext Events Touch Press Event - nPage=nPage+1 - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,cardGrid,bNext" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC diff --git a/HMI/n2t-out/cardMedia.txt b/HMI/n2t-out/cardMedia.txt index e03c53d4..19f60e65 100644 --- a/HMI/n2t-out/cardMedia.txt +++ b/HMI/n2t-out/cardMedia.txt @@ -23,55 +23,24 @@ Page cardMedia vis p0,0 vis tSend,0 vis tInstruction,0 - vis nPageDisp,0 vis tTmp,0 vis tId,0 //vis nPageDisp,0 Variable (string) strCommand Attributes - ID : 8 + ID : 7 Scope : local Text : Max. Text Size: 200 Variable (string) entn Attributes - ID : 19 + ID : 18 Scope : local Text : Max. Text Size: 50 -Number nPageDisp - Attributes - ID : 6 - Scope : local - Dragging : 0 - Disable release event after dragging: 0 - Send Component ID : disabled - Opacity : 127 - x coordinate : 426 - y coordinate : 0 - Width : 42 - Height : 24 - Effect : load - Effect Priority : 0 - Effect Time : 300 - Fill : solid color - Style : flat - Associated Keyboard : none - Font ID : 0 - Back. Color : 65535 - Font Color : 0 - Horizontal Alignment : center - Vertical Alignment : center - Value : 0 - Significant digits shown : all - Format : decimal - Word wrap : enabled - Horizontal Spacing : 0 - Vertical Spacing : 0 - Text tSend Attributes ID : 2 @@ -134,7 +103,7 @@ Text tTmp Text tInstruction Attributes - ID : 9 + ID : 8 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -164,7 +133,7 @@ Text tInstruction Text tId Attributes - ID : 10 + ID : 9 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -194,7 +163,7 @@ Text tId Text tHeading Attributes - ID : 11 + ID : 10 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -224,7 +193,7 @@ Text tHeading Text tTitle Attributes - ID : 12 + ID : 11 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -254,7 +223,7 @@ Text tTitle Text tAuthor Attributes - ID : 13 + ID : 12 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -284,7 +253,7 @@ Text tAuthor Text t2 Attributes - ID : 14 + ID : 13 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -333,7 +302,7 @@ Text t2 Text tPlayPause Attributes - ID : 15 + ID : 14 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -382,7 +351,7 @@ Text tPlayPause Text t0 Attributes - ID : 16 + ID : 15 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -431,7 +400,7 @@ Text t0 Text tIcon Attributes - ID : 18 + ID : 17 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -478,7 +447,7 @@ Picture p0 Slider hVolume Attributes - ID : 17 + ID : 16 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -554,14 +523,7 @@ Button bNext Events Touch Press Event - nPage=nPage+1 - nPageDisp.val=nPage - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,cardMedia,bNext" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC @@ -608,14 +570,7 @@ Button bPrev Events Touch Press Event - nPage=nPage-1 - nPageDisp.val=nPage - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,cardMedia,bPrev" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC @@ -630,7 +585,7 @@ Button bPrev Timer tmSerial Attributes - ID : 7 + ID : 6 Scope : local Period (ms): 50 Enabled : yes @@ -791,7 +746,7 @@ Timer tmSerial Timer tmSleep Attributes - ID : 20 + ID : 19 Scope : local Period (ms): 1000 Enabled : yes @@ -811,7 +766,7 @@ Timer tmSleep TouchCap tc0 Attributes - ID : 21 + ID : 20 Scope: local Value: 0 diff --git a/HMI/n2t-out/cardThermo.txt b/HMI/n2t-out/cardThermo.txt index 15c24aa6..45818e84 100644 --- a/HMI/n2t-out/cardThermo.txt +++ b/HMI/n2t-out/cardThermo.txt @@ -559,14 +559,7 @@ Button bNext Events Touch Press Event - nPage=nPage+1 - nPageDisp.val=nPage - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,cardThermo,bNext" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC @@ -721,14 +714,7 @@ Button bPrev Events Touch Press Event - nPage=nPage-1 - nPageDisp.val=nPage - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,cardThermo,bPrev" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC diff --git a/HMI/n2t-out/nspanel_Stats.txt b/HMI/n2t-out/nspanel_Stats.txt index 98785905..5634a108 100644 --- a/HMI/n2t-out/nspanel_Stats.txt +++ b/HMI/n2t-out/nspanel_Stats.txt @@ -1,66 +1,62 @@ Program.s 0 Component(s) - 17 Line(s) of event code - 17 Unique line(s) of event code + 10 Line(s) of event code + 10 Unique line(s) of event code pageIcons 6 Component(s) 0 Line(s) of event code 0 Unique line(s) of event code +pageTest + 13 Component(s) + 13 Line(s) of event code + 13 Unique line(s) of event code pageSerialTest 13 Component(s) 48 Line(s) of event code 43 Unique line(s) of event code -popupNotify - 17 Component(s) - 180 Line(s) of event code - 119 Unique line(s) of event code -pageStartup - 19 Component(s) - 146 Line(s) of event code - 111 Unique line(s) of event code -cardMedia - 22 Component(s) - 200 Line(s) of event code - 115 Unique line(s) of event code -pageSwipeTest - 18 Component(s) - 62 Line(s) of event code - 44 Unique line(s) of event code popupShutter 19 Component(s) - 180 Line(s) of event code - 103 Unique line(s) of event code -pageTest - 14 Component(s) - 14 Line(s) of event code - 14 Unique line(s) of event code -screensaver - 25 Component(s) - 173 Line(s) of event code - 124 Unique line(s) of event code -popupLight - 26 Component(s) - 307 Line(s) of event code - 168 Unique line(s) of event code -cardThermo - 42 Component(s) - 412 Line(s) of event code - 221 Unique line(s) of event code -cardGrid - 39 Component(s) - 382 Line(s) of event code - 221 Unique line(s) of event code -cardEntities - 54 Component(s) - 728 Line(s) of event code - 317 Unique line(s) of event code + 179 Line(s) of event code + 102 Unique line(s) of event code +popupNotify + 17 Component(s) + 179 Line(s) of event code + 118 Unique line(s) of event code +pageStartup + 19 Component(s) + 150 Line(s) of event code + 113 Unique line(s) of event code cardAlarm 35 Component(s) - 259 Line(s) of event code - 163 Unique line(s) of event code + 253 Line(s) of event code + 160 Unique line(s) of event code +cardGrid + 39 Component(s) + 378 Line(s) of event code + 219 Unique line(s) of event code +cardThermo + 42 Component(s) + 406 Line(s) of event code + 218 Unique line(s) of event code +cardMedia + 21 Component(s) + 193 Line(s) of event code + 111 Unique line(s) of event code +cardEntities + 54 Component(s) + 752 Line(s) of event code + 330 Unique line(s) of event code +popupLight + 26 Component(s) + 306 Line(s) of event code + 167 Unique line(s) of event code +screensaver + 25 Component(s) + 166 Line(s) of event code + 121 Unique line(s) of event code Total - 14 Page(s) - 349 Component(s) - 3108 Line(s) of event code - 828 Unique line(s) of event code + 13 Page(s) + 329 Component(s) + 3033 Line(s) of event code + 806 Unique line(s) of event code diff --git a/HMI/n2t-out/pageStartup.txt b/HMI/n2t-out/pageStartup.txt index 38919eb7..09b00a7e 100644 --- a/HMI/n2t-out/pageStartup.txt +++ b/HMI/n2t-out/pageStartup.txt @@ -402,7 +402,7 @@ Text tVersion Horizontal Alignment : center Vertical Alignment : center Input Type : character - Text : 16 + Text : 17 Max. Text Size : 10 Word wrap : disabled Horizontal Spacing : 0 @@ -592,6 +592,10 @@ Timer tmSerial { page cardThermo } + if(tId.txt=="screensaver") + { + page screensaver + } if(tId.txt=="popupLight") { pageIcons.tTmp1.txt=tTmp.txt diff --git a/HMI/n2t-out/pageTest.txt b/HMI/n2t-out/pageTest.txt index 1c0cc58d..54098892 100644 --- a/HMI/n2t-out/pageTest.txt +++ b/HMI/n2t-out/pageTest.txt @@ -111,45 +111,9 @@ Button b1 Touch Press Event page pageSerialTest -Button b2 - Attributes - ID : 4 - Scope : local - Dragging : 0 - Disable release event after dragging: 0 - Send Component ID : disabled - Opacity : 127 - x coordinate : 7 - y coordinate : 265 - Width : 100 - Height : 50 - Effect : load - Effect Priority : 0 - Effect Time : 300 - Fill : solid color - Style : 3D auto - Font ID : 4 - Back. Color : 50712 - Back. Picture ID (Pressed) : 65535 - Back. Color (Pressed) : 1024 - Font Color (Unpressed) : 0 - Font Color (Pressed) : 65535 - Horizontal Alignment : center - Vertical Alignment : center - State : unpressed - Text : swipe - Max. Text Size : 10 - Word wrap : disabled - Horizontal Spacing : 0 - Vertical Spacing : 0 - - Events - Touch Press Event - page pageSwipeTest - Button b3 Attributes - ID : 5 + ID : 4 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -185,7 +149,7 @@ Button b3 Button b6 Attributes - ID : 6 + ID : 5 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -221,7 +185,7 @@ Button b6 Button b4 Attributes - ID : 7 + ID : 6 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -257,7 +221,7 @@ Button b4 Button b5 Attributes - ID : 8 + ID : 7 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -293,7 +257,7 @@ Button b5 Button b7 Attributes - ID : 9 + ID : 8 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -329,7 +293,7 @@ Button b7 Button b8 Attributes - ID : 10 + ID : 9 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -366,7 +330,7 @@ Button b8 Button b9 Attributes - ID : 11 + ID : 10 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -402,7 +366,7 @@ Button b9 Button b10 Attributes - ID : 12 + ID : 11 Scope : local Dragging : 0 Disable release event after dragging: 0 @@ -438,7 +402,7 @@ Button b10 Button b11 Attributes - ID : 13 + ID : 12 Scope : local Dragging : 0 Disable release event after dragging: 0 diff --git a/HMI/n2t-out/popupLight.txt b/HMI/n2t-out/popupLight.txt index 785f154b..8167240b 100644 --- a/HMI/n2t-out/popupLight.txt +++ b/HMI/n2t-out/popupLight.txt @@ -593,12 +593,7 @@ Button b0 Events Touch Press Event - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,popupLight,bExit" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC diff --git a/HMI/n2t-out/popupNotify.txt b/HMI/n2t-out/popupNotify.txt index 092fb756..462cb337 100644 --- a/HMI/n2t-out/popupNotify.txt +++ b/HMI/n2t-out/popupNotify.txt @@ -278,12 +278,7 @@ Button b0 Events Touch Press Event sleepTimeout=vaOldSleepT.val - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,popupNotify,bExit" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC diff --git a/HMI/n2t-out/popupShutter.txt b/HMI/n2t-out/popupShutter.txt index 84352b87..e832b2ee 100644 --- a/HMI/n2t-out/popupShutter.txt +++ b/HMI/n2t-out/popupShutter.txt @@ -364,12 +364,7 @@ Button b0 Events Touch Press Event - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,popupShutter,bExit" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC diff --git a/HMI/n2t-out/screensaver.txt b/HMI/n2t-out/screensaver.txt index 15120f9f..93de7693 100644 --- a/HMI/n2t-out/screensaver.txt +++ b/HMI/n2t-out/screensaver.txt @@ -32,7 +32,17 @@ Page screensaver dim=dimValue vis tSend,0 //page open event - // craft command + // clear weather elements, to keep example content in HMI + tMainIcon.txt="" + tMainText.txt="" + tMRIcon.txt="" + tMR.txt="" + tForecast1.txt="" + tF1Icon.txt="" + tForecast1Val.txt="" + tForecast2.txt="" + tF2Icon.txt="" + tForecast2Val.txt="" tSend.txt="event,screensaverOpen" //send calc crc btlen tSend.txt,sys0 @@ -45,17 +55,6 @@ Page screensaver prints sys0,2 prints tSend.txt,0 prints crcval,2 - // clear weather elements, to keep example content in HMI - tMainIcon.txt="" - tMainText.txt="" - tMRIcon.txt="" - tMR.txt="" - tForecast1.txt="" - tF1Icon.txt="" - tForecast1Val.txt="" - tForecast2.txt="" - tF2Icon.txt="" - tForecast2Val.txt="" Variable (string) strCommand Attributes @@ -260,7 +259,7 @@ Text tSend Vertical Alignment : center Input Type : character Text : - Max. Text Size : 25 + Max. Text Size : 50 Word wrap : disabled Horizontal Spacing : 0 Vertical Spacing : 0 @@ -725,14 +724,6 @@ Timer tmSerial //tForecast2Val spstr strCommand.txt,tForecast2Val.txt,"?",10 } - if(tInstruction.txt=="page") - { - //pagenumber - spstr strCommand.txt,tTmp.txt,",",1 - covx tTmp.txt,sys0,0,0 - nPage=sys0 - //don't send current page number, wake will do - } if(tInstruction.txt=="pageType") { dim=100 @@ -804,12 +795,7 @@ TouchCap tc0 Events Touch Press Event - //page open event - // event,pageOpen,cardEntities,pageNumber - // craft command - // convert pageNumber and write to tTmp - covx nPage,tTmp.txt,0,0 - tSend.txt="event,pageOpen,"+tTmp.txt + tSend.txt="event,buttonPress2,screensaver,bExit" //send calc crc btlen tSend.txt,sys0 crcrest 1,0xffff // reset CRC From db1fb5d00dd99ace6812ff18cbab170f490ee126 Mon Sep 17 00:00:00 2001 From: joBr99 <29555657+joBr99@users.noreply.github.com> Date: Fri, 25 Mar 2022 14:43:33 +0100 Subject: [PATCH 10/10] fixed altert --- apps/nspanel-lovelace-ui/luibackend/pages.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/nspanel-lovelace-ui/luibackend/pages.py b/apps/nspanel-lovelace-ui/luibackend/pages.py index 3582ff6a..e73249f6 100644 --- a/apps/nspanel-lovelace-ui/luibackend/pages.py +++ b/apps/nspanel-lovelace-ui/luibackend/pages.py @@ -103,7 +103,6 @@ class LuiPagesGen(object): if item_type == "delete": return f",{item_type},,,,," if item_type == "navigate": - icon_id = get_icon_id_ha("button", overwrite=icon) text = get_translation(self._locale,"PRESS") return f",button,{item},0,17299,{item},{text}" if not self._ha_api.entity_exists(item):