From ac01a757619cf7a9895396660008bf91a267013e Mon Sep 17 00:00:00 2001 From: Johannes Date: Sat, 2 Apr 2022 10:54:48 +0200 Subject: [PATCH 1/8] started on new config format --- appdaemon/apps.yaml | 54 ++---- apps/nspanel-lovelace-ui/luibackend/config.py | 168 ++++++------------ .../nspanel-lovelace-ui.py | 80 ++++----- 3 files changed, 109 insertions(+), 193 deletions(-) diff --git a/appdaemon/apps.yaml b/appdaemon/apps.yaml index 3304263f..2d1cf3a8 100644 --- a/appdaemon/apps.yaml +++ b/appdaemon/apps.yaml @@ -24,41 +24,19 @@ nspanel-1: sensor.solar_power_current: # use this for overriding name and icon name: Sonne icon: solar-power - pages: - - type: cardEntities - heading: Example Page 1 - 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 - item: media_player.spotify_user - - type: cardAlarm - item: alarm_control_panel.alarmo + cards: + - type: entities + entities: + - entity: light.example_item + name: NameOverride + - entity: light.example_item + title: Example Entities + - type: gird + entities: + - entity: select.example_item + - entity: select.example_item + - entity: light.example_item + title: Exmaple Gird + - type: climate + entity: light.example_item + title: Exmaple Climate \ 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 79251208..ada71214 100644 --- a/apps/nspanel-lovelace-ui/luibackend/config.py +++ b/apps/nspanel-lovelace-ui/luibackend/config.py @@ -2,98 +2,27 @@ import logging 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 +HA_API = None - if "items" in data: - childs = data.pop("items") - for page in childs: - self.add_child(PageNode(page, self)) +class Entity(object): + def __init__(self, entity_input_config): + self.entityId = entity_input_config.get("entity", "unknown") + self.nameOverride = entity_input_config.get("name") + self.iconOverride = entity_input_config.get("icon") - 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" +class Card(object): + def __init__(self, card_input_config): + self.cardType = card_input_config.get("type", "unknown") + self.title = card_input_config.get("title", "unknown") + # for single entity card like climate or media + self.entity = None + if card_input_config.get("entity") is not None: + self.entity = Entity(card_input_config.get("entity")) + # for pages like grid or entities + self.entities = [] + for e in card_input_config.get("entities", []): + self.entities.append(Entity(e)) - self.name = f"{ptype}.{name}" if type(self.data) is dict else self.data - self.name = self.name.replace(".","_") - self.name = self.name.replace(",","_") - self.name = self.name.replace(" ","_") - - 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 search_page_by_name(self, name): - name = name.replace("navigate.", "") - pages = [] - for i in self.childs: - # compare name of current page - if i.name == name: - pages.append(i) - # current pages has also childs - if len(i.childs) > 0: - pages.extend(i.search_page_by_name(name)) - return pages - - return items - - 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}{self.pos}:{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(f"navigate.{i.name}") - else: - items.append(i.data) - return items - - def get_all_item_names(self, recursive=True): - items = [] - # current page - if type(self.data) is dict: - items.append(self.data.get("item", next(iter(self.data)))) - else: - items.append(self.data) - # childs of page - for i in self.childs: - if len(i.childs) > 0: - if recursive: - items.extend(i.get_all_item_names()) - 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): _DEFAULT_CONFIG = { @@ -115,27 +44,41 @@ class LuiBackendConfig(object): 'weatherOverrideForecast3': None, 'weatherOverrideForecast4': None, 'doubleTapToUnlock': False, - 'pages': [{ - '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'] - } - ] + 'cards': [{ + 'type': 'entities', + 'entities': [{ + 'entity': 'switch.test_item', + 'name': 'Test Item' + }, { + 'entity': 'switch.test_item' + }], + 'title': 'Example Entities Page' + }, { + 'type': 'grid', + 'entities': [{ + 'entity': 'switch.test_item' + }, { + 'entity': 'switch.test_item' + }, { + 'entity': 'switch.test_item' + } + ], + 'title': 'Example Grid Page' + }, { + 'type': 'climate', + 'entity': 'climate.test_item' + 'title': 'Example Climate Page' + }] } - def __init__(self, args=None, check=True): + def __init__(self, ha_api, config_in): + global HA_API + HA_API = ha_api self._config = {} - self._page_config = None - - if args: - self.load(args) + self._config_cards = [] + self._current_card = None - if check: - self.check() + self.load(config_in) def load(self, args): for k, v in args.items(): @@ -143,13 +86,11 @@ class LuiBackendConfig(object): self._config[k] = v LOGGER.info(f"Loaded config: {self._config}") - root_page = {"items": self.get("pages"), "type": "internal", "heading": "root"} - self._page_config = PageNode(root_page) + for card in self.get("cards"): + self._config_cards.append(Card(card)) + # set current card to first card + self._current_card = self._config_cards[0] - LOGGER.info(f"Parsed Page config to the following Tree: \n {self._page_config.dump()}") - - def check(self): - return def get(self, name): value = self._config.get(name) @@ -157,6 +98,3 @@ class LuiBackendConfig(object): value = self._DEFAULT_CONFIG.get(name) return value - def get_root_page(self): - return self._page_config - diff --git a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py index 3e31b718..ea44bbc4 100644 --- a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py +++ b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py @@ -10,7 +10,6 @@ from luibackend.updater import Updater LOGGER = logging.getLogger(__name__) - class AppDaemonLoggingHandler(logging.Handler): def __init__(self, app): super().__init__() @@ -50,43 +49,44 @@ class NsPanelLovelaceUIManager(hass.Hass): def initialize(self): LOGGER.info('Starting') mqtt_api = self._mqtt_api = self.get_plugin_api("MQTT") - cfg = self._cfg = LuiBackendConfig(self.args["config"]) + cfg = self._cfg = LuiBackendConfig(self, self.args["config"]) - topic_send = cfg.get("panelSendTopic") - 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, msg) - - # Request Tasmota Driver Version - mqtt_api.mqtt_publish(topic_send.replace("CustomSend", "GetDriverVersion"), "x") - - controller = LuiController(self, cfg, send_mqtt_msg) - - desired_display_firmware_version = 26 - version = "v2.4.0" - - model = cfg.get("model") - if model == "us-l": - # us landscape version - desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-us-l-{version}.tft" - elif model == "us-p": - # us portrait version - desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-us-p-{version}.tft" - else: - # eu version - desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-{version}.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") - topic_send = cfg.get("panelSendTopic") - updater = Updater(send_mqtt_msg, topic_send, mode, desired_display_firmware_version, model, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url) - - topic_recv = cfg.get("panelRecvTopic") - LuiMqttListener(mqtt_api, topic_recv, controller, updater) - - LOGGER.info('Started') + #topic_send = cfg.get("panelSendTopic") + #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, msg) +# + ## Request Tasmota Driver Version + #mqtt_api.mqtt_publish(topic_send.replace("CustomSend", "GetDriverVersion"), "x") +# + #controller = LuiController(self, cfg, send_mqtt_msg) +# + #desired_display_firmware_version = 26 + #version = "v2.4.0" + # + #model = cfg.get("model") + #if model == "us-l": + # # us landscape version + # desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-us-l-{version}.tft" + #elif model == "us-p": + # # us portrait version + # desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-us-p-{version}.tft" + #else: + # # eu version + # desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-{version}.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") + #topic_send = cfg.get("panelSendTopic") + #updater = Updater(send_mqtt_msg, topic_send, mode, desired_display_firmware_version, model, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url) +# + #topic_recv = cfg.get("panelRecvTopic") + #LuiMqttListener(mqtt_api, topic_recv, controller, updater) +# + #LOGGER.info('Started') +# \ No newline at end of file From a58cafc1248823648c3ef17388b89399359f9b5c Mon Sep 17 00:00:00 2001 From: Johannes Date: Sat, 2 Apr 2022 16:45:28 +0200 Subject: [PATCH 2/8] new config format --- README.md | 94 +++++------- appdaemon/apps.yaml | 27 ++-- apps/nspanel-lovelace-ui/luibackend/config.py | 102 +++++++++---- .../luibackend/controller.py | 37 ++--- apps/nspanel-lovelace-ui/luibackend/pages.py | 137 +++++++++--------- .../nspanel-lovelace-ui.py | 76 +++++----- 6 files changed, 248 insertions(+), 225 deletions(-) diff --git a/README.md b/README.md index 75b6eda9..5d0af9cd 100644 --- a/README.md +++ b/README.md @@ -260,7 +260,7 @@ nspanel-1: config: panelRecvTopic: "tele/tasmota_your_mqtt_topic/RESULT" panelSendTopic: "cmnd/tasmota_your_mqtt_topic/CustomSend" - #model: us-p # uncomment this if you have the us version, see table below for more information + updateMode: "auto-notify" timeoutScreensaver: 20 #brightnessScreensaver: 10 brightnessScreensaver: @@ -268,49 +268,28 @@ nspanel-1: value: 10 - time: "23:00:00" value: 0 - locale: "de_DE" - dateFormatBabel: "full" - timeFormat: "%H:%M" - dateFormat: "%A, %d. %B %Y" # ignored if babel python package is installed - weather: weather.example - pages: + locale: "de_DE" # used for translations in translations.py and for localized date if babel python package is installed + screensaver: + weather: weather.k3ll3r + cards: - type: cardEntities - heading: Example Page 1 - 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 + entities: + - entity: light.example_item + name: NameOverride + - entity: light.example_item + title: Example Entities - type: cardGrid - heading: Example Page 4 - items: - - light.light_example - - button.example_button - - cover.example_cover - - scene.example_scene - - switch.example_switch - - delete + entities: + - entity: select.example_item + - entity: select.example_item + - entity: light.example_item + title: Exmaple Gird - type: cardThermo - heading: Exmaple Thermostat - item: climate.example_climate + entity: climate.example_item - type: cardMedia - item: media_player.spotify_user + entity: media_player.example_item - type: cardAlarm - item: alarm_control_panel.alarmo + entity: alarm_control_panel.alarmo ``` key | optional | type | default | description @@ -327,13 +306,21 @@ key | optional | type | default | description `panelSendTopic` | False | string | `cmnd/tasmota_your_mqtt_topic/CustomSend` | The mqtt topic used to send messages. `updateMode` | True | string | `auto-notify` | Update Mode; Possible values: "auto", "auto-notify", "manual" `model` | True | string | `eu` | Model; Possible values: "eu", "us-l" and "us-p" -`timeoutScreensaver` | True | integer | `20` | Timeout for the screen to enter screensaver, to disable screensaver use 0 -`brightnessScreensaver` | True | integer/complex | `20` | Brightness for the screen to enter screensaver, see example below for complex/scheduled config. -`brightnessScreensaverTracking` | True | string | None | Forces screensaver brightness to 0 in case entity state is not_home, can be a group, person or device_tracker entity. +`sleepTimeout` | True | integer | `20` | Timeout for the screen to enter screensaver, to disable screensaver use 0 +`sleepBrightness` | True | integer/complex | `20` | Brightness for the screen to enter screensaver, see example below for complex/scheduled config. +`sleepTracking` | True | string | None | Forces screensaver brightness to 0 in case entity state is not_home, can be a group, person or device_tracker entity. `locale` | True | string | `en_US` | Used by babel to determinante Date format on screensaver, also used for localization. `dateFormatBabel` | True | string | `full` | formatting options on https://babel.pocoo.org/en/latest/dates.html?highlight=name%20of%20day#date-fields `timeFormat` | True | string | `%H:%M` | Time Format on screensaver. Substring after `?` is displayed in a seperate smaller textbox. Useful for 12h time format with AM/PM `"%I:%M ?%p"` `dateFormat` | True | string | `%A, %d. %B %Y` | date format used if babel is not installed +`cards` | False | complex | | configuration for pages that are displayed on panel +`screensaver` | True | complex | | configuration for screensaver +`hiddenCards` | True | complex | | configuration for pages that can be accessed though navigate items + +Possible configuration values for screensaver config: + +key | optional | type | default | description +-- | -- | -- | -- | -- `weather` | True | string | `weather.example` | weather entity from homeassistant `weatherUnit` | True | string | `celsius` | unit for temperature, valid values are `celsius` or `fahrenheit` `weatherOverrideForecast1` | True | string | `None` | sensor entity from home assistant here to override the first weather forecast item on the screensaver @@ -341,14 +328,13 @@ key | optional | type | default | description `weatherOverrideForecast3` | True | string | `None` | sensor entity from home assistant here to override the third weather forecast item on the screensaver `weatherOverrideForecast4` | True | string | `None` | sensor entity from home assistant here to override the forth weather forecast item on the screensaver `doubleTapToUnlock` | True | boolean | `False` | requires to tap screensaver two times -`pages` | False | complex | | configuration for pages on panel -#### Schedule screensaver brightness +#### Schedule sleep brightness It is possible to schedule a brightness change for the screen at specific times. ```yaml - brightnessScreensaver: + sleepBrightness: - time: "7:00:00" value: 10 - time: "23:00:00" @@ -361,17 +347,13 @@ To override Icons or Names of entities you can configure an icon and/or name in Only the icons listed in the [Icon Table](HMI#icons-ids) are useable. ```yaml - - type: cardGrid - heading: Lights - items: - - light.wled - - light.schreibtischlampe - - switch.deckenbeleuchtung_hinten: - icon: lightbulb - name: Lampe - - delete - - delete - - type: cardMedia + 'entities': [{ + 'entity': 'switch.test_item', + 'name': 'Test Item' + 'icon': 'lightbulb' + }, { + 'entity': 'switch.test_item' + }], ``` ## How to update diff --git a/appdaemon/apps.yaml b/appdaemon/apps.yaml index 2d1cf3a8..5dbec26f 100644 --- a/appdaemon/apps.yaml +++ b/appdaemon/apps.yaml @@ -13,30 +13,25 @@ nspanel-1: 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 - weather: weather.example - weatherOverrideForecast3: sensor.nas_cpu_perc - weatherOverrideForecast4: - sensor.solar_power_current: # use this for overriding name and icon - name: Sonne - icon: solar-power + locale: "de_DE" # used for translations in translations.py and for localized date if babel python package is installed + screensaver: + weather: weather.k3ll3r cards: - - type: entities + - type: cardEntities entities: - entity: light.example_item name: NameOverride - entity: light.example_item title: Example Entities - - type: gird + - type: cardGrid entities: - entity: select.example_item - entity: select.example_item - entity: light.example_item title: Exmaple Gird - - type: climate - entity: light.example_item - title: Exmaple Climate \ No newline at end of file + - type: cardThermo + entity: climate.example_item + - type: cardMedia + entity: media_player.example_item + - type: cardAlarm + entity: alarm_control_panel.alarmo \ 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 ada71214..f65dd48a 100644 --- a/apps/nspanel-lovelace-ui/luibackend/config.py +++ b/apps/nspanel-lovelace-ui/luibackend/config.py @@ -11,17 +11,31 @@ class Entity(object): self.iconOverride = entity_input_config.get("icon") class Card(object): - def __init__(self, card_input_config): + def __init__(self, card_input_config, pos=None): + self.pos = pos + self.raw_config = card_input_config self.cardType = card_input_config.get("type", "unknown") self.title = card_input_config.get("title", "unknown") + self.key = card_input_config.get("key", "unknown") # for single entity card like climate or media self.entity = None if card_input_config.get("entity") is not None: - self.entity = Entity(card_input_config.get("entity")) + self.entity = Entity(card_input_config) # for pages like grid or entities self.entities = [] for e in card_input_config.get("entities", []): self.entities.append(Entity(e)) + self.id = f"{self.cardType}_{self.key}".replace(".","_").replace("~","_").replace(" ","_") + LOGGER.info(f"Created Card {self.cardType} with pos {pos} and id {self.id}") + + def get_entity_list(self): + entityIds = [] + if self.entity is not None: + entityIds.append(self.entity.entityId) + else: + for e in self.entities: + entityIds.append(e.entityId) + return entityIds class LuiBackendConfig(object): @@ -30,22 +44,15 @@ class LuiBackendConfig(object): 'panelSendTopic': "cmnd/tasmota_your_mqtt_topic/CustomSend", 'updateMode': "auto-notify", 'model': "eu", - 'timeoutScreensaver': 20, - 'brightnessScreensaver': 20, - 'brightnessScreensaverTracking': None, + 'sleepTimeout': 20, + 'sleepBrightness': 20, + 'sleepTracking': None, 'locale': "en_US", 'timeFormat': "%H:%M", 'dateFormatBabel': "full", 'dateFormat': "%A, %d. %B %Y", - 'weather': 'weather.example', - 'weatherUnit': 'celsius', - 'weatherOverrideForecast1': None, - 'weatherOverrideForecast2': None, - 'weatherOverrideForecast3': None, - 'weatherOverrideForecast4': None, - 'doubleTapToUnlock': False, 'cards': [{ - 'type': 'entities', + 'type': 'cardEntities', 'entities': [{ 'entity': 'switch.test_item', 'name': 'Test Item' @@ -54,7 +61,7 @@ class LuiBackendConfig(object): }], 'title': 'Example Entities Page' }, { - 'type': 'grid', + 'type': 'cardGrid', 'entities': [{ 'entity': 'switch.test_item' }, { @@ -66,9 +73,19 @@ class LuiBackendConfig(object): 'title': 'Example Grid Page' }, { 'type': 'climate', - 'entity': 'climate.test_item' - 'title': 'Example Climate Page' - }] + 'entity': 'climate.test_item', + }], + 'screensaver': { + 'type': 'screensaver', + 'weather': 'weather.example', + 'weatherUnit': 'celsius', + 'weatherOverrideForecast1': None, + 'weatherOverrideForecast2': None, + 'weatherOverrideForecast3': None, + 'weatherOverrideForecast4': None, + 'doubleTapToUnlock': False + }, + 'hiddenCards': [] } def __init__(self, ha_api, config_in): @@ -76,7 +93,7 @@ class LuiBackendConfig(object): HA_API = ha_api self._config = {} self._config_cards = [] - self._current_card = None + self._config_screensaver = None self.load(config_in) @@ -85,16 +102,51 @@ class LuiBackendConfig(object): if k in self._DEFAULT_CONFIG: self._config[k] = v LOGGER.info(f"Loaded config: {self._config}") - + + # parse cards displayed on panel + pos = 0 for card in self.get("cards"): - self._config_cards.append(Card(card)) - # set current card to first card - self._current_card = self._config_cards[0] + self._config_cards.append(Card(card, pos)) + pos = pos + 1 + # parse screensaver + screensaver = Card(self.get("screensaver")) + # parsed hidden pages that can be accessed through navigate + for card in self.get("hiddenCards"): + self._config_hidden_cards.append(Card(card)) def get(self, name): - value = self._config.get(name) - if value is None: - value = self._DEFAULT_CONFIG.get(name) + path = name.split(".") + value = self._config + for p in path: + if value is not None: + value = value.get(p, None) + if value is not None: + return value + # try to get a value from default config + value = self._DEFAULT_CONFIG + for p in path: + if value is not None: + value = value.get(p, None) return value + + def get_all_entity_names(self): + entities = [] + for card in self._config_cards: + entities.extend(card.get_entity_list()) + return entities + def getCard(self, pos): + card = self._config_cards[pos%len(self._config_cards)] + return card + + def searchCard(self, id): + id = id.replace("navigate.", "") + for card in self._config_cards: + if card.id == id: + return card + if self._config_screensaver.id == id: + return screensaver + for card in self._config_hidden_cards: + if card.id == id: + return card \ No newline at end of file diff --git a/apps/nspanel-lovelace-ui/luibackend/controller.py b/apps/nspanel-lovelace-ui/luibackend/controller.py index df4cb0d3..f5a7c65d 100644 --- a/apps/nspanel-lovelace-ui/luibackend/controller.py +++ b/apps/nspanel-lovelace-ui/luibackend/controller.py @@ -13,8 +13,8 @@ class LuiController(object): self._config = config self._send_mqtt_msg = send_mqtt_msg - # first child of root page (default, after startup) - self._current_page = self._config._page_config.childs[0] + # first card (default, after startup) + self._current_card = self._config.getCard(0) self._pages_gen = LuiPagesGen(ha_api, config, send_mqtt_msg) @@ -42,7 +42,6 @@ class LuiController(object): # calculate current brightness self.current_screensaver_brightness = self.calc_current_screensaver_brightness() - # call update_screensaver_brightness on changes of entity configured in brightnessScreensaverTracking bst = self._config.get("brightnessScreensaverTracking") if bst is not None and self._ha_api.entity_exists(bst): @@ -103,7 +102,7 @@ class LuiController(object): return current_screensaver_brightness def register_callbacks(self): - items = self._config.get_root_page().get_all_item_names() + items = self._config.get_all_entity_names() LOGGER.debug(f"Registering callbacks for the following items: {items}") for item in items: if self._ha_api.entity_exists(item): @@ -111,12 +110,12 @@ class LuiController(object): def state_change_callback(self, entity, attribute, old, new, kwargs): LOGGER.debug(f"Got callback for: {entity}") - LOGGER.debug(f"Current page has the following items: {self._current_page.get_items()}") - if entity in self._current_page.get_all_item_names(recursive=False): + LOGGER.debug(f"Current page has the following items: {self._current_card.get_entity_list()}") + if entity in self._current_card.get_entity_list(): LOGGER.debug(f"Callback Entity is on current page: {entity}") - self._pages_gen.render_page(self._current_page, send_page_type=False) + self._pages_gen.render_card(self._current_card, send_page_type=False) # send detail page update, just in case - if self._current_page.data.get("type", "unknown") in ["cardGrid", "cardEntities"]: + if self._current_card.cardType in ["cardGrid", "cardEntities"]: if entity.startswith("light"): self._pages_gen.generate_light_detail_page(entity) if entity.startswith("cover"): @@ -134,9 +133,9 @@ class LuiController(object): # internal buttons if entity_id == "screensaver" and button_type == "bExit": if self._config.get("doubleTapToUnlock") and int(value) >= 2: - self._pages_gen.render_page(self._current_page) + self._pages_gen.render_card(self._current_card) elif not self._config.get("doubleTapToUnlock"): - self._pages_gen.render_page(self._current_page) + self._pages_gen.render_card(self._current_card) return if button_type == "sleepReached": @@ -144,17 +143,19 @@ class LuiController(object): return if button_type == "bExit": - self._pages_gen.render_page(self._current_page) + self._pages_gen.render_card(self._current_card) if button_type == "bNext": - self._current_page = self._current_page.next() - self._pages_gen.render_page(self._current_page) + card = self._config.getCard(self._current_card.pos+1) + self._current_card = card + self._pages_gen.render_card(card) if button_type == "bPrev": - self._current_page = self._current_page.prev() - self._pages_gen.render_page(self._current_page) + card = self._config.getCard(self._current_card.pos-1) + self._current_card = card + self._pages_gen.render_card(card) elif entity_id == "updateDisplayNoYes" and value == "no": - self._pages_gen.render_page(self._current_page) + self._pages_gen.render_card(self._current_card) # buttons with actions on HA if button_type == "OnOff": @@ -180,8 +181,8 @@ class LuiController(object): if button_type == "button": if entity_id.startswith('navigate'): # internal for navigation to nested pages - self._current_page = self._config.get_root_page().search_page_by_name(entity_id)[0] - self._pages_gen.render_page(self._current_page) + self._current_card = self._config.searchCard(entity_id) + self._pages_gen.render_card(self._current_card) elif entity_id.startswith('scene'): self._ha_api.get_entity(entity_id).call_service("turn_on") elif entity_id.startswith('script'): diff --git a/apps/nspanel-lovelace-ui/luibackend/pages.py b/apps/nspanel-lovelace-ui/luibackend/pages.py index 1692d546..31da3b53 100644 --- a/apps/nspanel-lovelace-ui/luibackend/pages.py +++ b/apps/nspanel-lovelace-ui/luibackend/pages.py @@ -55,19 +55,19 @@ class LuiPagesGen(object): def page_type(self, target_page): self._send_mqtt_msg(f"pageType~{target_page}") - def generate_screensaver_page(self): + def generate_screensaver_page(self, card): self.page_type("screensaver") self.update_screensaver_weather() def update_screensaver_weather(self): global babel_spec - we_name = self._config.get("weather") - unit = self._config.get("weatherUnit") + we_name = self._config.get("screensaver.weather") + unit = self._config.get("screensaver.weatherUnit") if self._ha_api.entity_exists(we_name): we = self._ha_api.get_entity(we_name) else: - LOGGER.error("Skipping Weather Update, entity not found") + LOGGER.error("Skipping Weather Update, entity {we_name} not found") return icon_cur = get_icon_id_ha("weather", state=we.state) @@ -75,7 +75,7 @@ class LuiPagesGen(object): weather_res = "" for i in range(1,5): - wOF = self._config.get(f"weatherOverrideForecast{i}") + wOF = self._config.get(f"screensaver.weatherOverrideForecast{i}") if wOF is None: up = we.attributes.forecast[i-1]['datetime'] up = datetime.datetime.fromisoformat(up) @@ -87,13 +87,9 @@ class LuiPagesGen(object): down = convert_temperature(we.attributes.forecast[i-1]['temperature'], unit) else: LOGGER.info(f"Forecast {i} is overriden with {wOF}") - icon = None - name = None - if type(wOF) is dict: - icon = next(iter(wOF.items()))[1].get('icon') - name = next(iter(wOF.items()))[1].get('name') - wOF = next(iter(wOF.items()))[0] - entity = self._ha_api.get_entity(wOF) + icon = wOF.iconOverride + name = wOF.nameOverride + entity = self._ha_api.get_entity(wOF.entityId) up = name if name is not None else entity.attributes.friendly_name icon = get_icon_id_ha("sensor", state=entity.state, device_class=entity.attributes.get("device_class", ""), overwrite=icon) unit_of_measurement = entity.attributes.get("unit_of_measurement", "") @@ -102,73 +98,69 @@ class LuiPagesGen(object): self._send_mqtt_msg(f"weatherUpdate~{icon_cur}~{text_cur}{weather_res}") - def generate_entities_item(self, item): - icon = None - name = None - if type(item) is dict: - 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] - LOGGER.debug(f"Generating item command for {item} with type {item_type}",) - # Internal Entities - if item_type == "delete": - return f"~{item_type}~~~~~" - if item_type == "navigate": - page_search = self._config.get_root_page().search_page_by_name(item) - if len(page_search) > 0: - page_data = page_search[0].data - if name is None: - name = page_data.get("heading") + def generate_entities_item(self, entity): + entityId = entity.entityId + icon = entity.iconOverride + name = entity.nameOverride + # type of the item is the string before the "." in the entityId + entityType = entityId.split(".")[0] + + LOGGER.debug(f"Generating item for {entityId} with type {entityType}",) + # Internal types + if entityType == "delete": + return f"~{entityType}~~~~~" + if entityType == "navigate": + page_search_res = self._config.searchPage(entityId) + if page_search_res is not None: + name = page_search_res.title text = get_translation(self._locale,"PRESS") icon_id = get_icon_id(icon) if icon is not None else get_icon_id(page_data.get("icon", "gesture-tap-button")) - return f"~button~{item}~{icon_id}~17299~{name}~{text}" + return f"~button~{entityId}~{icon_id}~17299~{name}~{text}" else: - return f"~text~{item}~{get_icon_id('alert-circle-outline')}~17299~page not found~" - if not self._ha_api.entity_exists(item): - return f"~text~{item}~{get_icon_id('alert-circle-outline')}~17299~Not found check~ apps.yaml" + return f"~text~{entityId}~{get_icon_id('alert-circle-outline')}~17299~page not found~" + if not self._ha_api.entity_exists(entityId): + return f"~text~{entityId}~{get_icon_id('alert-circle-outline')}~17299~Not found check~ apps.yaml" # HA Entities - entity = self._ha_api.get_entity(item) + entity = self._ha_api.get_entity(entityId) name = name if name is not None else entity.attributes.friendly_name - if item_type == "cover": + if entityType == "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": + return f"~shutter~{entityId}~{icon_id}~17299~{name}~" + if entityType in "light": switch_val = 1 if entity.state == "on" else 0 icon_color = self.get_entity_color(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"]: + return f"~{entityType}~{entityId}~{icon_id}~{icon_color}~{name}~{switch_val}" + if entityType in ["switch", "input_boolean"]: switch_val = 1 if entity.state == "on" else 0 icon_color = self.get_entity_color(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"]: + icon_id = get_icon_id_ha(entityType, state=entity.state, overwrite=icon) + return f"~switch~{entityId}~{icon_id}~{icon_color}~{name}~{switch_val}" + if entityType in ["sensor", "binary_sensor"]: device_class = entity.attributes.get("device_class", "") icon_id = get_icon_id_ha("sensor", state=entity.state, device_class=device_class, overwrite=icon) unit_of_measurement = entity.attributes.get("unit_of_measurement", "") value = entity.state + " " + unit_of_measurement icon_color = self.get_entity_color(entity) - return f"~text~{item}~{icon_id}~{icon_color}~{name}~{value}" - if item_type in ["button", "input_button"]: + return f"~text~{entityId}~{icon_id}~{icon_color}~{name}~{value}" + if entityType in ["button", "input_button"]: icon_id = get_icon_id_ha("button", overwrite=icon) text = get_translation(self._locale,"PRESS") - return f"~button~{item}~{icon_id}~17299~{name}~{text}" - if item_type == "scene": + return f"~button~{entityId}~{icon_id}~17299~{name}~{text}" + if entityType == "scene": icon_id = get_icon_id_ha("scene", overwrite=icon) text = get_translation(self._locale,"ACTIVATE") - return f"~button~{item}~{icon_id}~17299~{name}~{text}" - if item_type == "script": + return f"~button~{entityId}~{icon_id}~17299~{name}~{text}" + if entityType == "script": icon_id = get_icon_id_ha("script", overwrite=icon) text = get_translation(self._locale,"run") - return f"~button~{item}~{icon_id}~17299~{name}~{text}" - if item_type == "number": + return f"~button~{entityId}~{icon_id}~17299~{name}~{text}" + if entityType == "number": icon_id = get_icon_id_ha("number", overwrite=icon) min_v = entity.attributes.get("min", 0) max_v = entity.attributes.get("max", 100) - return f"~number~{item}~{icon_id}~17299~{name}~{entity.state}|{min_v}|{max_v}" + return f"~number~{entityId}~{icon_id}~17299~{name}~{entity.state}|{min_v}|{max_v}" def generate_entities_page(self, heading, items): navigation = "" @@ -180,7 +172,8 @@ class LuiPagesGen(object): - def generate_thermo_page(self, item): + def generate_thermo_page(self, entity): + item = entity.entityId if not self._ha_api.entity_exists(item): command = f"entityUpd~{item}~Not found~220~220~Not found~150~300~5" else: @@ -237,7 +230,8 @@ class LuiPagesGen(object): command = f"entityUpd~{heading}~~{item}~{current_temp}~{dest_temp}~{status}~{min_temp}~{max_temp}~{step_temp}{icon_res}" self._send_mqtt_msg(command) - def generate_media_page(self, item): + def generate_media_page(self, entity): + item = entity.entityId 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: @@ -270,7 +264,8 @@ class LuiPagesGen(object): command = f"entityUpd~|{heading}||{item}|{icon}|{title}|{author}|{volume}|{iconplaypause}|{source}|{speakerlist[:200]}|{onoffbutton}" self._send_mqtt_msg(command) - def generate_alarm_page(self, item): + def generate_alarm_page(self, entity): + item = entity.entityId if not self._ha_api.entity_exists(item): command = f"entityUpd~{item}~Not found~Not found~Check your~Check your~apps.~apps.~yaml~yaml~0~~0" else: @@ -330,23 +325,23 @@ class LuiPagesGen(object): command = f"entityUpd~{item}~{navigation}{arm_buttons}~{icon}~{color}~{numpad}~{flashing}" self._send_mqtt_msg(command) - def render_page(self, page, send_page_type=True): - config = page.data - page_type = config["type"] - LOGGER.info(f"Started rendering of page {page.pos} with type {page_type}") + def render_card(self, card, send_page_type=True): + LOGGER.info(f"Started rendering of page {card.pos} with type {card.cardType}") # Switch to page if send_page_type: - self.page_type(page_type) - if page_type in ["cardEntities", "cardGrid"]: - heading = config.get("heading", "unknown") - self.generate_entities_page(heading, page.get_items()) + self.page_type(card.cardType) + if card.cardType in ["cardEntities", "cardGrid"]: + self.generate_entities_page(card.title, card.entities) return - if page_type == "cardThermo": - self.generate_thermo_page(page.data.get("item")) - if page_type == "cardMedia": - self.generate_media_page(page.data.get("item")) - if page_type == "cardAlarm": - self.generate_alarm_page(page.data.get("item")) + if card.cardType == "cardThermo": + self.generate_thermo_page(card.entity) + if card.cardType == "cardMedia": + self.generate_media_page(card.entity) + if card.cardType == "cardAlarm": + self.generate_alarm_page(card.entity) + + if card.cardType == "screensaver": + self.generate_screensaver_page(card) def generate_light_detail_page(self, entity): @@ -380,7 +375,7 @@ class LuiPagesGen(object): def generate_shutter_detail_page(self, entity): entity = self._ha_api.get_entity(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") diff --git a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py index ea44bbc4..ba2e3ae0 100644 --- a/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py +++ b/apps/nspanel-lovelace-ui/nspanel-lovelace-ui.py @@ -51,42 +51,40 @@ class NsPanelLovelaceUIManager(hass.Hass): mqtt_api = self._mqtt_api = self.get_plugin_api("MQTT") cfg = self._cfg = LuiBackendConfig(self, self.args["config"]) - #topic_send = cfg.get("panelSendTopic") - #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, msg) -# - ## Request Tasmota Driver Version - #mqtt_api.mqtt_publish(topic_send.replace("CustomSend", "GetDriverVersion"), "x") -# - #controller = LuiController(self, cfg, send_mqtt_msg) -# - #desired_display_firmware_version = 26 - #version = "v2.4.0" - # - #model = cfg.get("model") - #if model == "us-l": - # # us landscape version - # desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-us-l-{version}.tft" - #elif model == "us-p": - # # us portrait version - # desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-us-p-{version}.tft" - #else: - # # eu version - # desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-{version}.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") - #topic_send = cfg.get("panelSendTopic") - #updater = Updater(send_mqtt_msg, topic_send, mode, desired_display_firmware_version, model, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url) -# - #topic_recv = cfg.get("panelRecvTopic") - #LuiMqttListener(mqtt_api, topic_recv, controller, updater) -# - #LOGGER.info('Started') -# \ No newline at end of file + topic_send = cfg.get("panelSendTopic") + 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, msg) + + # Request Tasmota Driver Version + mqtt_api.mqtt_publish(topic_send.replace("CustomSend", "GetDriverVersion"), "x") + + controller = LuiController(self, cfg, send_mqtt_msg) + + desired_display_firmware_version = 26 + version = "v2.4.0" + + model = cfg.get("model") + if model == "us-l": + # us landscape version + desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-us-l-{version}.tft" + elif model == "us-p": + # us portrait version + desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-us-p-{version}.tft" + else: + # eu version + desired_display_firmware_url = f"http://nspanel.pky.eu/lovelace-ui/github/nspanel-{version}.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") + topic_send = cfg.get("panelSendTopic") + updater = Updater(send_mqtt_msg, topic_send, mode, desired_display_firmware_version, model, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url) + + topic_recv = cfg.get("panelRecvTopic") + LuiMqttListener(mqtt_api, topic_recv, controller, updater) + + LOGGER.info('Started') From f15e5086268ad4f013348d4cb7d29f9ced92aa10 Mon Sep 17 00:00:00 2001 From: Johannes Date: Sat, 2 Apr 2022 16:54:57 +0200 Subject: [PATCH 3/8] upd docs --- README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5d0af9cd..47d88be5 100644 --- a/README.md +++ b/README.md @@ -313,9 +313,19 @@ key | optional | type | default | description `dateFormatBabel` | True | string | `full` | formatting options on https://babel.pocoo.org/en/latest/dates.html?highlight=name%20of%20day#date-fields `timeFormat` | True | string | `%H:%M` | Time Format on screensaver. Substring after `?` is displayed in a seperate smaller textbox. Useful for 12h time format with AM/PM `"%I:%M ?%p"` `dateFormat` | True | string | `%A, %d. %B %Y` | date format used if babel is not installed -`cards` | False | complex | | configuration for pages that are displayed on panel +`cards` | False | complex | | configuration for cards that are displayed on panel `screensaver` | True | complex | | configuration for screensaver -`hiddenCards` | True | complex | | configuration for pages that can be accessed though navigate items +`hiddenCards` | True | complex | | configuration for cards that can be accessed though navigate items + +Possible configuration values for a card in card config: + +key | optional | type | default | description +-- | -- | -- | -- | -- +`type` | False | string | `None` | Used by navigate items +`entities` | False | complex | `None` | contains entities of the card, applys only to cardEntities and cardGrid +`heading` | True | string | `None` | Heading of the Page +`entity` | False | string | `None` | contains the entity of the current card, valid for cardThermo, cardAlarm and cardMedia +`key` | True | string | `None` | Used by navigate items Possible configuration values for screensaver config: @@ -328,6 +338,7 @@ key | optional | type | default | description `weatherOverrideForecast3` | True | string | `None` | sensor entity from home assistant here to override the third weather forecast item on the screensaver `weatherOverrideForecast4` | True | string | `None` | sensor entity from home assistant here to override the forth weather forecast item on the screensaver `doubleTapToUnlock` | True | boolean | `False` | requires to tap screensaver two times +`key` | True | string | `None` | Used by navigate items #### Schedule sleep brightness @@ -387,7 +398,6 @@ Since release 1.1 you can update the berry driver directly from the Tasmota Cons `UpdateDriverVersion https://raw.githubusercontent.com/joBr99/nspanel-lovelace-ui/main/tasmota/autoexec.be` - ## FAQ - Frequently Asked Questions ### Flashing of the Display Firmware with FlashNextion doesn't work From 4c6f9cdfd60f29560119f6643c2e35eed1f31ff3 Mon Sep 17 00:00:00 2001 From: Johannes Date: Sat, 2 Apr 2022 17:34:34 +0200 Subject: [PATCH 4/8] implement subpages --- README.md | 6 ++--- appdaemon/apps.yaml | 6 ++--- apps/nspanel-lovelace-ui/luibackend/config.py | 3 ++- .../luibackend/controller.py | 5 ++-- apps/nspanel-lovelace-ui/luibackend/pages.py | 24 ++++++++++--------- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 47d88be5..58911783 100644 --- a/README.md +++ b/README.md @@ -261,9 +261,9 @@ nspanel-1: panelRecvTopic: "tele/tasmota_your_mqtt_topic/RESULT" panelSendTopic: "cmnd/tasmota_your_mqtt_topic/CustomSend" updateMode: "auto-notify" - timeoutScreensaver: 20 - #brightnessScreensaver: 10 - brightnessScreensaver: + sleepTimeout: 20 + #sleepBrightness: 10 + sleepBrightness: - time: "7:00:00" value: 10 - time: "23:00:00" diff --git a/appdaemon/apps.yaml b/appdaemon/apps.yaml index 5dbec26f..312ce1d3 100644 --- a/appdaemon/apps.yaml +++ b/appdaemon/apps.yaml @@ -6,9 +6,9 @@ nspanel-1: panelRecvTopic: "tele/tasmota_your_mqtt_topic/RESULT" panelSendTopic: "cmnd/tasmota_your_mqtt_topic/CustomSend" updateMode: "auto-notify" - timeoutScreensaver: 20 - #brightnessScreensaver: 10 - brightnessScreensaver: + sleepTimeout: 20 + #sleepBrightness: 10 + sleepBrightness: - time: "7:00:00" value: 10 - time: "23:00:00" diff --git a/apps/nspanel-lovelace-ui/luibackend/config.py b/apps/nspanel-lovelace-ui/luibackend/config.py index f65dd48a..f60ff83a 100644 --- a/apps/nspanel-lovelace-ui/luibackend/config.py +++ b/apps/nspanel-lovelace-ui/luibackend/config.py @@ -94,6 +94,7 @@ class LuiBackendConfig(object): self._config = {} self._config_cards = [] self._config_screensaver = None + self._config_hidden_cards = [] self.load(config_in) @@ -109,7 +110,7 @@ class LuiBackendConfig(object): self._config_cards.append(Card(card, pos)) pos = pos + 1 # parse screensaver - screensaver = Card(self.get("screensaver")) + self._config_screensaver = Card(self.get("screensaver")) # parsed hidden pages that can be accessed through navigate for card in self.get("hiddenCards"): diff --git a/apps/nspanel-lovelace-ui/luibackend/controller.py b/apps/nspanel-lovelace-ui/luibackend/controller.py index f5a7c65d..2c262fba 100644 --- a/apps/nspanel-lovelace-ui/luibackend/controller.py +++ b/apps/nspanel-lovelace-ui/luibackend/controller.py @@ -142,7 +142,7 @@ class LuiController(object): self._pages_gen.generate_screensaver_page() return - if button_type == "bExit": + if button_type in ["bExit", "bUp"]: self._pages_gen.render_card(self._current_card) if button_type == "bNext": @@ -181,8 +181,7 @@ class LuiController(object): if button_type == "button": if entity_id.startswith('navigate'): # internal for navigation to nested pages - self._current_card = self._config.searchCard(entity_id) - self._pages_gen.render_card(self._current_card) + self._pages_gen.render_card(self._config.searchCard(entity_id)) elif entity_id.startswith('scene'): self._ha_api.get_entity(entity_id).call_service("turn_on") elif entity_id.startswith('script'): diff --git a/apps/nspanel-lovelace-ui/luibackend/pages.py b/apps/nspanel-lovelace-ui/luibackend/pages.py index 31da3b53..62e2d19d 100644 --- a/apps/nspanel-lovelace-ui/luibackend/pages.py +++ b/apps/nspanel-lovelace-ui/luibackend/pages.py @@ -110,11 +110,11 @@ class LuiPagesGen(object): if entityType == "delete": return f"~{entityType}~~~~~" if entityType == "navigate": - page_search_res = self._config.searchPage(entityId) + page_search_res = self._config.searchCard(entityId) if page_search_res is not None: name = page_search_res.title text = get_translation(self._locale,"PRESS") - icon_id = get_icon_id(icon) if icon is not None else get_icon_id(page_data.get("icon", "gesture-tap-button")) + icon_id = get_icon_id(icon) if icon is not None else get_icon_id("gesture-tap-button") return f"~button~{entityId}~{icon_id}~17299~{name}~{text}" else: return f"~text~{entityId}~{get_icon_id('alert-circle-outline')}~17299~page not found~" @@ -162,8 +162,7 @@ class LuiPagesGen(object): max_v = entity.attributes.get("max", 100) return f"~number~{entityId}~{icon_id}~17299~{name}~{entity.state}|{min_v}|{max_v}" - def generate_entities_page(self, heading, items): - navigation = "" + def generate_entities_page(self, navigation, heading, items): command = f"entityUpd~{heading}~{navigation}" # Get items and construct cmd string for item in items: @@ -172,7 +171,7 @@ class LuiPagesGen(object): - def generate_thermo_page(self, entity): + def generate_thermo_page(self, navigation, entity): item = entity.entityId if not self._ha_api.entity_exists(item): command = f"entityUpd~{item}~Not found~220~220~Not found~150~300~5" @@ -230,7 +229,7 @@ class LuiPagesGen(object): command = f"entityUpd~{heading}~~{item}~{current_temp}~{dest_temp}~{status}~{min_temp}~{max_temp}~{step_temp}{icon_res}" self._send_mqtt_msg(command) - def generate_media_page(self, entity): + def generate_media_page(self, navigation, entity): item = entity.entityId 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')}" @@ -264,7 +263,7 @@ class LuiPagesGen(object): command = f"entityUpd~|{heading}||{item}|{icon}|{title}|{author}|{volume}|{iconplaypause}|{source}|{speakerlist[:200]}|{onoffbutton}" self._send_mqtt_msg(command) - def generate_alarm_page(self, entity): + def generate_alarm_page(self, navigation, entity): item = entity.entityId if not self._ha_api.entity_exists(item): command = f"entityUpd~{item}~Not found~Not found~Check your~Check your~apps.~apps.~yaml~yaml~0~~0" @@ -327,18 +326,21 @@ class LuiPagesGen(object): def render_card(self, card, send_page_type=True): LOGGER.info(f"Started rendering of page {card.pos} with type {card.cardType}") + navigation = "1|1" + if card.pos is None: + navigation = "2|0" # Switch to page if send_page_type: self.page_type(card.cardType) if card.cardType in ["cardEntities", "cardGrid"]: - self.generate_entities_page(card.title, card.entities) + self.generate_entities_page(navigation, card.title, card.entities) return if card.cardType == "cardThermo": - self.generate_thermo_page(card.entity) + self.generate_thermo_page(navigation, card.entity) if card.cardType == "cardMedia": - self.generate_media_page(card.entity) + self.generate_media_page(navigation, card.entity) if card.cardType == "cardAlarm": - self.generate_alarm_page(card.entity) + self.generate_alarm_page(navigation, card.entity) if card.cardType == "screensaver": self.generate_screensaver_page(card) From 9ace31db1f3b56b3252949d6d400ed4213914688 Mon Sep 17 00:00:00 2001 From: Johannes Date: Sat, 2 Apr 2022 17:40:10 +0200 Subject: [PATCH 5/8] update docs --- README.md | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 58911783..4ebdab2a 100644 --- a/README.md +++ b/README.md @@ -358,13 +358,29 @@ To override Icons or Names of entities you can configure an icon and/or name in Only the icons listed in the [Icon Table](HMI#icons-ids) are useable. ```yaml - 'entities': [{ - 'entity': 'switch.test_item', - 'name': 'Test Item' - 'icon': 'lightbulb' - }, { - 'entity': 'switch.test_item' - }], + entities: + - entity: light.test_item + name: NameOverride + icon: lightbulb +``` + +#### Subpages + +You can configure entities with with the prefix `navigate`, that are navigating to cards, in case it's hidden page, the navigation items will change and the arrow is bringing you back to the current page on the top level. + +```yaml + - entity: navigate.cardGrid_testKey +``` + +will allow you to navigate to a cardGrid page with the configured key testKey + +```yaml + hiddenCards: + - type: cardGrid + title: Exmaple Grid + entities: + - entity: light.test_item + key: testKey ``` ## How to update From a3033a0e6b0ccd3cd0e23d298f9c65849d29abd4 Mon Sep 17 00:00:00 2001 From: Johannes Date: Sat, 2 Apr 2022 19:43:09 +0200 Subject: [PATCH 6/8] alternaitve layout screensaver --- README.md | 39 +++++++++-- appdaemon/apps.yaml | 41 +++++++++-- apps/nspanel-lovelace-ui/luibackend/config.py | 5 +- .../luibackend/controller.py | 10 +-- apps/nspanel-lovelace-ui/luibackend/pages.py | 24 ++++--- info.md | 70 +------------------ 6 files changed, 94 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 4ebdab2a..06339a3d 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ Please see [appdaemon.yaml](appdaemon/appdaemon.yaml) as an exmaple. ### Configure your NSPanel in AppDaemon Confiure your NSPanel as you like, you need to edit the `apps.yaml` inside of your Appdaemon config folder. -You can have multiple nspanel sections. +You can have multiple nspanel sections. There are some more exmaples in the appdaemon folder of this repo. ```yaml --- @@ -270,19 +270,45 @@ nspanel-1: value: 0 locale: "de_DE" # used for translations in translations.py and for localized date if babel python package is installed screensaver: - weather: weather.k3ll3r + entity: weather.k3ll3r cards: - type: cardEntities entities: - - entity: light.example_item + - entity: switch.example_item name: NameOverride + icon: lightbulb - entity: light.example_item - title: Example Entities + - entity: cover.example_item + - entity: input_boolean.example_item + title: Example Entities 1 + - type: cardEntities + entities: + - entity: switch.example_item + - entity: delete + - entity: cover.example_item + - entity: input_boolean.example_item + title: Example Entities 2 + - type: cardEntities + entities: + - entity: binary_sensor.example_item + - entity: sensor.example_item + - entity: button.example_item + - entity: number.example_item + title: Example Entities 3 + - type: cardEntities + entities: + - entity: scenes.example_item + - entity: script.example_item + - entity: button.example_item + - entity: input_button.example_item + title: Example Entities 4 - type: cardGrid entities: - - entity: select.example_item - - entity: select.example_item - entity: light.example_item + - entity: switch.example_item + - entity: delete + - entity: button.example_item + - entity: cover.example_item title: Exmaple Gird - type: cardThermo entity: climate.example_item @@ -338,6 +364,7 @@ key | optional | type | default | description `weatherOverrideForecast3` | True | string | `None` | sensor entity from home assistant here to override the third weather forecast item on the screensaver `weatherOverrideForecast4` | True | string | `None` | sensor entity from home assistant here to override the forth weather forecast item on the screensaver `doubleTapToUnlock` | True | boolean | `False` | requires to tap screensaver two times +`alternativeLayout` | True | boolean | `False` | alternative layout with humidity `key` | True | string | `None` | Used by navigate items #### Schedule sleep brightness diff --git a/appdaemon/apps.yaml b/appdaemon/apps.yaml index 312ce1d3..5fd53fff 100644 --- a/appdaemon/apps.yaml +++ b/appdaemon/apps.yaml @@ -15,19 +15,50 @@ nspanel-1: value: 0 locale: "de_DE" # used for translations in translations.py and for localized date if babel python package is installed screensaver: - weather: weather.k3ll3r + entity: weather.k3ll3r + weatherOverrideForecast4: + entity: sensor.example_item + name: name + icon: lightbulb + alternativeLayout: True cards: - type: cardEntities entities: - - entity: light.example_item + - entity: switch.example_item name: NameOverride + icon: lightbulb - entity: light.example_item - title: Example Entities + - entity: cover.example_item + - entity: input_boolean.example_item + title: Example Entities 1 + - type: cardEntities + entities: + - entity: switch.example_item + - entity: delete + - entity: cover.example_item + - entity: input_boolean.example_item + title: Example Entities 2 + - type: cardEntities + entities: + - entity: binary_sensor.example_item + - entity: sensor.example_item + - entity: button.example_item + - entity: number.example_item + title: Example Entities 3 + - type: cardEntities + entities: + - entity: scenes.example_item + - entity: script.example_item + - entity: button.example_item + - entity: input_button.example_item + title: Example Entities 4 - type: cardGrid entities: - - entity: select.example_item - - entity: select.example_item - entity: light.example_item + - entity: switch.example_item + - entity: delete + - entity: button.example_item + - entity: cover.example_item title: Exmaple Gird - type: cardThermo entity: climate.example_item diff --git a/apps/nspanel-lovelace-ui/luibackend/config.py b/apps/nspanel-lovelace-ui/luibackend/config.py index f60ff83a..ef841f26 100644 --- a/apps/nspanel-lovelace-ui/luibackend/config.py +++ b/apps/nspanel-lovelace-ui/luibackend/config.py @@ -77,13 +77,14 @@ class LuiBackendConfig(object): }], 'screensaver': { 'type': 'screensaver', - 'weather': 'weather.example', + 'entity': 'weather.example', 'weatherUnit': 'celsius', 'weatherOverrideForecast1': None, 'weatherOverrideForecast2': None, 'weatherOverrideForecast3': None, 'weatherOverrideForecast4': None, - 'doubleTapToUnlock': False + 'doubleTapToUnlock': False, + 'alternativeLayout': False }, 'hiddenCards': [] } diff --git a/apps/nspanel-lovelace-ui/luibackend/controller.py b/apps/nspanel-lovelace-ui/luibackend/controller.py index 2c262fba..3a9f6994 100644 --- a/apps/nspanel-lovelace-ui/luibackend/controller.py +++ b/apps/nspanel-lovelace-ui/luibackend/controller.py @@ -36,14 +36,14 @@ class LuiController(object): self.register_callbacks() # register callbacks for each time - if type(self._config.get("brightnessScreensaver")) == list: - for index, timeset in enumerate(self._config.get("brightnessScreensaver")): + if type(self._config.get("sleepBrightness")) == list: + for index, timeset in enumerate(self._config.get("sleepBrightness")): self._ha_api.run_daily(self.update_screensaver_brightness, timeset["time"], value=timeset["value"]) # calculate current brightness self.current_screensaver_brightness = self.calc_current_screensaver_brightness() # call update_screensaver_brightness on changes of entity configured in brightnessScreensaverTracking - bst = self._config.get("brightnessScreensaverTracking") + bst = self._config.get("sleepTracking") if bst is not None and self._ha_api.entity_exists(bst): self._ha_api.listen_state(self.update_screensaver_brightness_state_callback, entity_id=bst) @@ -54,7 +54,7 @@ class LuiController(object): self._pages_gen.update_date("") # set screensaver timeout - timeout = self._config.get("timeoutScreensaver") + timeout = self._config.get("sleepTimeout") self._send_mqtt_msg(f"timeout~{timeout}") # set current screensaver brightness @@ -142,7 +142,7 @@ class LuiController(object): self._pages_gen.generate_screensaver_page() return - if button_type in ["bExit", "bUp"]: + if button_type in ["bExit", "bUp"]: self._pages_gen.render_card(self._current_card) if button_type == "bNext": diff --git a/apps/nspanel-lovelace-ui/luibackend/pages.py b/apps/nspanel-lovelace-ui/luibackend/pages.py index 62e2d19d..9dfd169f 100644 --- a/apps/nspanel-lovelace-ui/luibackend/pages.py +++ b/apps/nspanel-lovelace-ui/luibackend/pages.py @@ -61,8 +61,9 @@ class LuiPagesGen(object): def update_screensaver_weather(self): global babel_spec - we_name = self._config.get("screensaver.weather") - unit = self._config.get("screensaver.weatherUnit") + screensaver_config = self._config._config_screensaver + we_name = self._config._config_screensaver.entity.entityId + unit = self._config._config_screensaver.raw_config.get("weatherUnit", "celsius") if self._ha_api.entity_exists(we_name): we = self._ha_api.get_entity(we_name) @@ -75,7 +76,7 @@ class LuiPagesGen(object): weather_res = "" for i in range(1,5): - wOF = self._config.get(f"screensaver.weatherOverrideForecast{i}") + wOF = self._config._config_screensaver.raw_config.get(f"weatherOverrideForecast{i}") if wOF is None: up = we.attributes.forecast[i-1]['datetime'] up = datetime.datetime.fromisoformat(up) @@ -87,16 +88,20 @@ class LuiPagesGen(object): down = convert_temperature(we.attributes.forecast[i-1]['temperature'], unit) else: LOGGER.info(f"Forecast {i} is overriden with {wOF}") - icon = wOF.iconOverride - name = wOF.nameOverride - entity = self._ha_api.get_entity(wOF.entityId) + icon = wOF.get("icon") + name = wOF.get("name") + entity = self._ha_api.get_entity(wOF.get("entity")) up = name if name is not None else entity.attributes.friendly_name icon = get_icon_id_ha("sensor", state=entity.state, device_class=entity.attributes.get("device_class", ""), overwrite=icon) unit_of_measurement = entity.attributes.get("unit_of_measurement", "") down = f"{entity.state} {unit_of_measurement}" weather_res+=f"~{up}~{icon}~{down}" - self._send_mqtt_msg(f"weatherUpdate~{icon_cur}~{text_cur}{weather_res}") + altLayout = "" + if self._config._config_screensaver.raw_config.get("alternativeLayout", False) is True: + altLayout = f"~26~{we.attributes.humidity} %" + + self._send_mqtt_msg(f"weatherUpdate~{icon_cur}~{text_cur}{weather_res}{altLayout}") def generate_entities_item(self, entity): entityId = entity.entityId @@ -326,7 +331,10 @@ class LuiPagesGen(object): def render_card(self, card, send_page_type=True): LOGGER.info(f"Started rendering of page {card.pos} with type {card.cardType}") - navigation = "1|1" + if len(self._config._config_cards) == 1: + navigation = "0|0" + else: + navigation = "1|1" if card.pos is None: navigation = "2|0" # Switch to page diff --git a/info.md b/info.md index ac599812..1486e7c0 100644 --- a/info.md +++ b/info.md @@ -2,72 +2,4 @@ Checkout [README](https://github.com/joBr99/nspanel-lovelace-ui/blob/main/README.md) for detailed Instructions. -### App Configuration - -```yaml ---- -nspanel-1: - module: nspanel-lovelace-ui - class: NsPanelLovelaceUIManager - config: - 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 - weather: weather.example - pages: - - type: cardEntities - heading: Example Page 1 - 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 - item: media_player.spotify_user - - type: cardAlarm - item: alarm_control_panel.alarmo -``` - -key | optional | type | default | description --- | -- | -- | -- | -- -`module` | False | string | | The module name of the app. -`class` | False | string | | The name of the Class. -`config` | False | complex | | Config/Mapping between Homeassistant and your NsPanel +![screens](doc-pics/screens.png) \ No newline at end of file From 338f12cb188a42f8756b9d0bc9da7e48430b446b Mon Sep 17 00:00:00 2001 From: Johannes Date: Sat, 2 Apr 2022 19:46:34 +0200 Subject: [PATCH 7/8] fix sleepbrightness --- apps/nspanel-lovelace-ui/luibackend/controller.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/nspanel-lovelace-ui/luibackend/controller.py b/apps/nspanel-lovelace-ui/luibackend/controller.py index 3a9f6994..29f22238 100644 --- a/apps/nspanel-lovelace-ui/luibackend/controller.py +++ b/apps/nspanel-lovelace-ui/luibackend/controller.py @@ -68,7 +68,7 @@ class LuiController(object): self.update_screensaver_brightness(kwargs={"value": self.current_screensaver_brightness}) def update_screensaver_brightness(self, kwargs): - bst = self._config.get("brightnessScreensaverTracking") + bst = self._config.get("sleepTracking") brightness = 0 if bst is not None and self._ha_api.entity_exists(bst) and self._ha_api.get_entity(bst).state == "not_home": brightness = 0 @@ -83,10 +83,10 @@ class LuiController(object): def calc_current_screensaver_brightness(self): current_screensaver_brightness = 20 # set brightness of screensaver - if type(self._config.get("brightnessScreensaver")) == int: - 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'])) + if type(self._config.get("sleepBrightness")) == int: + current_screensaver_brightness = self._config.get("sleepBrightness") + elif type(self._config.get("sleepBrightness")) == list: + sorted_timesets = sorted(self._config.get("sleepBrightness"), key=lambda d: self._ha_api.parse_time(d['time'])) # calc current screensaver brightness found_current_dim_value = False for index, timeset in enumerate(sorted_timesets): From f4c870c51c40f7f0ea7a6531ea892cbef0b35ff2 Mon Sep 17 00:00:00 2001 From: Johannes Date: Sat, 2 Apr 2022 20:00:03 +0200 Subject: [PATCH 8/8] fix alert --- 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 9dfd169f..7f6e5f3f 100644 --- a/apps/nspanel-lovelace-ui/luibackend/pages.py +++ b/apps/nspanel-lovelace-ui/luibackend/pages.py @@ -61,7 +61,6 @@ class LuiPagesGen(object): def update_screensaver_weather(self): global babel_spec - screensaver_config = self._config._config_screensaver we_name = self._config._config_screensaver.entity.entityId unit = self._config._config_screensaver.raw_config.get("weatherUnit", "celsius")