Merge pull request #114 from joBr99/newConfigFormat

New config format
This commit is contained in:
joBr99
2022-04-02 20:07:24 +02:00
committed by GitHub
7 changed files with 369 additions and 404 deletions

151
README.md
View File

@@ -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
---
@@ -260,57 +260,62 @@ 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
timeoutScreensaver: 20
#brightnessScreensaver: 10
brightnessScreensaver:
updateMode: "auto-notify"
sleepTimeout: 20
#sleepBrightness: 10
sleepBrightness:
- time: "7:00:00"
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:
entity: weather.k3ll3r
cards:
- type: cardEntities
heading: Example Page 1
items:
- cover.example_cover
- switch.example_switch
- input_boolean.example_input_boolean
- sensor.example_sensor
entities:
- entity: switch.example_item
name: NameOverride
icon: lightbulb
- entity: light.example_item
- entity: cover.example_item
- entity: input_boolean.example_item
title: Example Entities 1
- type: cardEntities
heading: Example Page 2
items:
- button.example_button
- input_button.example_input_button
- light.light_example
- delete # (read this as 'empty')
entities:
- entity: switch.example_item
- entity: delete
- entity: cover.example_item
- entity: input_boolean.example_item
title: Example Entities 2
- type: cardEntities
heading: Example Page 3
items:
- scene.example_scene
- delete
- delete
- delete
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
heading: Example Page 4
items:
- light.light_example
- button.example_button
- cover.example_cover
- scene.example_scene
- switch.example_switch
- delete
entities:
- entity: light.example_item
- entity: switch.example_item
- entity: delete
- entity: button.example_item
- entity: cover.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 +332,31 @@ 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 cards that are displayed on panel
`screensaver` | True | complex | | configuration for screensaver
`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:
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 +364,15 @@ 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
`alternativeLayout` | True | boolean | `False` | alternative layout with humidity
`key` | True | string | `None` | Used by navigate items
#### 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 +385,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: 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
heading: Lights
items:
- light.wled
- light.schreibtischlampe
- switch.deckenbeleuchtung_hinten:
icon: lightbulb
name: Lampe
- delete
- delete
- type: cardMedia
title: Exmaple Grid
entities:
- entity: light.test_item
key: testKey
```
## How to update
@@ -405,7 +441,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

View File

@@ -6,59 +6,63 @@ 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"
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
pages:
locale: "de_DE" # used for translations in translations.py and for localized date if babel python package is installed
screensaver:
entity: weather.k3ll3r
weatherOverrideForecast4:
entity: sensor.example_item
name: name
icon: lightbulb
alternativeLayout: True
cards:
- type: cardEntities
heading: Example Page 1
items:
- cover.example_cover
- switch.example_switch
- input_boolean.example_input_boolean
- sensor.example_sensor
entities:
- entity: switch.example_item
name: NameOverride
icon: lightbulb
- entity: light.example_item
- entity: cover.example_item
- entity: input_boolean.example_item
title: Example Entities 1
- type: cardEntities
heading: Example Page 2
items:
- button.example_button
- input_button.example_input_button
- light.light_example
- delete # (read this as 'empty')
entities:
- entity: switch.example_item
- entity: delete
- entity: cover.example_item
- entity: input_boolean.example_item
title: Example Entities 2
- type: cardEntities
heading: Example Page 3
items:
- scene.example_scene
- delete
- delete
- delete
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
heading: Example Page 4
items:
- light.light_example
- button.example_button
- cover.example_cover
- scene.example_scene
- switch.example_switch
- delete
entities:
- entity: light.example_item
- entity: switch.example_item
- entity: delete
- entity: button.example_item
- entity: cover.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

View File

@@ -2,98 +2,41 @@ 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"
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
class Card(object):
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)
# 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_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))))
def get_entity_list(self):
entityIds = []
if self.entity is not None:
entityIds.append(self.entity.entityId)
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
for e in self.entities:
entityIds.append(e.entityId)
return entityIds
class LuiBackendConfig(object):
_DEFAULT_CONFIG = {
@@ -101,62 +44,111 @@ 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,
'pages': [{
'cards': [{
'type': 'cardEntities',
'heading': 'Test Entities 1',
'items': ['switch.test_item', 'switch.test_item', 'switch.test_item']
}, {
'entities': [{
'entity': 'switch.test_item',
'name': 'Test Item'
}, {
'entity': 'switch.test_item'
}],
'title': 'Example Entities Page'
}, {
'type': 'cardGrid',
'heading': 'Test Grid 1',
'items': ['switch.test_item', 'switch.test_item', 'switch.test_item']
}
]
'entities': [{
'entity': 'switch.test_item'
}, {
'entity': 'switch.test_item'
}, {
'entity': 'switch.test_item'
}
],
'title': 'Example Grid Page'
}, {
'type': 'climate',
'entity': 'climate.test_item',
}],
'screensaver': {
'type': 'screensaver',
'entity': 'weather.example',
'weatherUnit': 'celsius',
'weatherOverrideForecast1': None,
'weatherOverrideForecast2': None,
'weatherOverrideForecast3': None,
'weatherOverrideForecast4': None,
'doubleTapToUnlock': False,
'alternativeLayout': False
},
'hiddenCards': []
}
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._config_screensaver = None
self._config_hidden_cards = []
if check:
self.check()
self.load(config_in)
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}")
# parse cards displayed on panel
pos = 0
for card in self.get("cards"):
self._config_cards.append(Card(card, pos))
pos = pos + 1
# parse screensaver
self._config_screensaver = Card(self.get("screensaver"))
root_page = {"items": self.get("pages"), "type": "internal", "heading": "root"}
self._page_config = PageNode(root_page)
LOGGER.info(f"Parsed Page config to the following Tree: \n {self._page_config.dump()}")
def check(self):
return
# 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 get_root_page(self):
return self._page_config
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

View File

@@ -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)
@@ -36,15 +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)
@@ -55,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
@@ -69,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
@@ -84,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):
@@ -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,27 +133,29 @@ 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":
self._pages_gen.generate_screensaver_page()
return
if button_type == "bExit":
self._pages_gen.render_page(self._current_page)
if button_type in ["bExit", "bUp"]:
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,7 @@ 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._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'):

View File

@@ -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._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)
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._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,91 +87,86 @@ 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.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} %"
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")
self._send_mqtt_msg(f"weatherUpdate~{icon_cur}~{text_cur}{weather_res}{altLayout}")
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.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"))
return f"~button~{item}~{icon_id}~17299~{name}~{text}"
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~{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 = ""
def generate_entities_page(self, navigation, heading, items):
command = f"entityUpd~{heading}~{navigation}"
# Get items and construct cmd string
for item in items:
@@ -180,7 +175,8 @@ class LuiPagesGen(object):
def generate_thermo_page(self, item):
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"
else:
@@ -237,7 +233,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, 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')}"
else:
@@ -270,7 +267,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, 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"
else:
@@ -330,23 +328,29 @@ 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}")
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
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(navigation, 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(navigation, card.entity)
if card.cardType == "cardMedia":
self.generate_media_page(navigation, card.entity)
if card.cardType == "cardAlarm":
self.generate_alarm_page(navigation, card.entity)
if card.cardType == "screensaver":
self.generate_screensaver_page(card)
def generate_light_detail_page(self, entity):
@@ -380,7 +384,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")

View File

@@ -10,7 +10,6 @@ from luibackend.updater import Updater
LOGGER = logging.getLogger(__name__)
class AppDaemonLoggingHandler(logging.Handler):
def __init__(self, app):
super().__init__()
@@ -50,7 +49,7 @@ 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):
@@ -63,7 +62,7 @@ class NsPanelLovelaceUIManager(hass.Hass):
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"
@@ -77,7 +76,6 @@ class NsPanelLovelaceUIManager(hass.Hass):
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"

70
info.md
View File

@@ -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)