implement homeassistant template caching

This commit is contained in:
joBr99
2023-11-25 18:17:21 +01:00
parent 36f2c85ffe
commit 5ab207504d
6 changed files with 75 additions and 63 deletions

2
.vscode/launch.json vendored
View File

@@ -9,7 +9,7 @@
"type": "python", "type": "python",
"request": "launch", "request": "launch",
"program": "main.py", "program": "main.py",
"cwd": "${fileDirname}", "cwd": "/workspaces/nspanel-lovelace-ui/nspanel-lovelace-ui/rootfs/usr/bin/mqtt-manager",
"console": "integratedTerminal", "console": "integratedTerminal",
"justMyCode": true "justMyCode": true
} }

View File

@@ -1,6 +1,6 @@
# https://developers.home-assistant.io/docs/add-ons/configuration#add-on-config # https://developers.home-assistant.io/docs/add-ons/configuration#add-on-config
name: NSPanel Lovelace UI Addon name: NSPanel Lovelace UI Addon
version: "4.7.38" version: "4.7.39"
slug: nspanel-lovelace-ui slug: nspanel-lovelace-ui
description: NSPanel Lovelace UI Addon description: NSPanel Lovelace UI Addon
services: services:
@@ -20,4 +20,4 @@ options:
message: "Hello world..." message: "Hello world..."
schema: schema:
message: "str?" message: "str?"
image: "ghcr.io/jobr99/{arch}-nspanel-lovelace-ui" #image: "ghcr.io/jobr99/{arch}-nspanel-lovelace-ui"

View File

@@ -8,17 +8,24 @@ import dateutil.parser as dp
import babel import babel
from libs.icon_mapping import get_icon_char from libs.icon_mapping import get_icon_char
from libs.helper import rgb_dec565, scale from libs.helper import rgb_dec565, scale
import ha_template
class HAEntity(panel_cards.Entity): class HAEntity(panel_cards.Entity):
def __init__(self, locale, config, panel): def __init__(self, locale, config, panel):
super().__init__(locale, config, panel) super().__init__(locale, config, panel)
def prerender(self):
# pre render templates
for p in ["color_overwrite", "icon_overwrite", "value_overwrite"]:
val = getattr(self, p)
if val and "ha:" in val:
print(f"yyyyyyyyyyy {val}")
libs.home_assistant.cache_template(val)
def render(self, cardType=""): def render(self, cardType=""):
if self.icon_overwrite and self.icon_overwrite.startswith("ha:"): if self.icon_overwrite and self.icon_overwrite.startswith("ha:"):
#icon_char = libs.home_assistant.render_template(self.icon_overwrite[3:]) self.icon_overwrite = libs.home_assistant.get_template(self.icon_overwrite)
self.icon_overwrite = ha_template.render(self.icon_overwrite)
if self.etype in ["delete", "navigate", "iText"]: if self.etype in ["delete", "navigate", "iText"]:
out = super().render() out = super().render()
@@ -217,7 +224,8 @@ class HAEntity(panel_cards.Entity):
name = "unsupported" name = "unsupported"
if self.value_overwrite and self.value_overwrite.startswith("ha:"): if self.value_overwrite and self.value_overwrite.startswith("ha:"):
value = ha_template.render(self.value_overwrite[3:]) value = libs.home_assistant.get_template(self.value_overwrite)[3:]
return f"~{entity_type_panel}~iid.{self.iid}~{icon_char}~{color}~{name}~{value}" return f"~{entity_type_panel}~iid.{self.iid}~{icon_char}~{color}~{name}~{value}"
@@ -273,6 +281,8 @@ class HACard(panel_cards.Card):
class EntitiesCard(HACard): class EntitiesCard(HACard):
def __init__(self, locale, config, panel): def __init__(self, locale, config, panel):
super().__init__(locale, config, panel) super().__init__(locale, config, panel)
if len(self.entities) > 6 and self.type == "cardGrid":
self.type = "cardGrid2"
def render(self): def render(self):
result = f"{self.title}~{self.gen_nav()}" result = f"{self.title}~{self.gen_nav()}"
@@ -287,6 +297,8 @@ class QRCard(HACard):
def render(self): def render(self):
# TODO: Render QRCode as HomeAssistant Template # TODO: Render QRCode as HomeAssistant Template
#qrcode = apis.ha_api.render_template(qrcode) #qrcode = apis.ha_api.render_template(qrcode)
if self.qrcode.startswith("ha:"):
self.qrcode = libs.home_assistant.get_template(self.qrcode)[3:]
result = f"{self.title}~{self.gen_nav()}~{self.qrcode}" result = f"{self.title}~{self.gen_nav()}~{self.qrcode}"
for e in self.entities: for e in self.entities:
result += e.render() result += e.render()
@@ -559,7 +571,6 @@ class Screensaver(HACard):
libs.panel_cmd.statusUpdate(self.panel.sendTopic, f"{statusUpdateResult}~{icon1font}~{icon2font}") libs.panel_cmd.statusUpdate(self.panel.sendTopic, f"{statusUpdateResult}~{icon1font}~{icon2font}")
def card_factory(locale, settings, panel): def card_factory(locale, settings, panel):
match settings["type"]: match settings["type"]:
case 'cardEntities' | 'cardGrid': case 'cardEntities' | 'cardGrid':

View File

@@ -1,30 +0,0 @@
from jinja2 import Environment, BaseLoader
from typing import (
Any
)
import libs.home_assistant
def states(entity_id):
return libs.home_assistant.get_entity_data(entity_id).get("state")
def state_attr(entity_id, attr):
return libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get(attr)
def iif(value: Any, if_true: Any = True, if_false: Any = False, if_none: Any = False) -> Any:
if value is None:
return if_none
if bool(value):
return if_true
return if_false
func_dict = {
"states": states,
"state_attr": state_attr,
"iif": iif
}
def render(template):
jinja_template = Environment(loader=BaseLoader()).from_string(template)
jinja_template.globals.update(func_dict)
template_string = jinja_template.render()
return template_string

View File

@@ -14,8 +14,10 @@ next_id = 0
request_all_states_id = 0 request_all_states_id = 0
ws_connected = False ws_connected = False
home_assistant_entity_state_cache = {} home_assistant_entity_state_cache = {}
template_cache = {}
response_buffer = {} response_buffer = {}
ON_CONNECT_HANDLER = None ON_CONNECT_HANDLER = None
ON_DISCONNECT_HANDLER = None ON_DISCONNECT_HANDLER = None
@@ -43,7 +45,7 @@ def register_on_disconnect_handler(handler):
def on_message(ws, message): def on_message(ws, message):
global auth_ok, request_all_states_id, home_assistant_entity_state_cache, response_buffer global auth_ok, request_all_states_id, home_assistant_entity_state_cache, response_buffer, template_cache
json_msg = json.loads(message) json_msg = json.loads(message)
if json_msg["type"] == "auth_required": if json_msg["type"] == "auth_required":
authenticate_client() authenticate_client()
@@ -56,11 +58,19 @@ def on_message(ws, message):
ON_CONNECT_HANDLER() ON_CONNECT_HANDLER()
# for templates # for templates
elif json_msg["type"] == "event" and json_msg["id"] in response_buffer: elif json_msg["type"] == "event" and json_msg["id"] in response_buffer:
response_buffer[json_msg["id"]] = json_msg["event"] template_cache[response_buffer[json_msg["id"]]] = {
"result": json_msg["event"]["result"],
"listener-entities": json_msg["event"]["listeners"]["entities"]
}
elif json_msg["type"] == "event" and json_msg["event"]["event_type"] == "state_changed": elif json_msg["type"] == "event" and json_msg["event"]["event_type"] == "state_changed":
entity_id = json_msg["event"]["data"]["entity_id"] entity_id = json_msg["event"]["data"]["entity_id"]
home_assistant_entity_state_cache[entity_id] = json_msg["event"]["data"]["new_state"] home_assistant_entity_state_cache[entity_id] = json_msg["event"]["data"]["new_state"]
send_entity_update(entity_id) send_entity_update(entity_id)
# rerender template
for template, template_cache_entry in template_cache.items():
if entity_id in template_cache_entry.get("listener-entities", []):
cache_template(template)
elif json_msg["type"] == "result" and not json_msg["success"]: elif json_msg["type"] == "result" and not json_msg["success"]:
logging.error("Failed result: ") logging.error("Failed result: ")
logging.error(json_msg) logging.error(json_msg)
@@ -69,7 +79,7 @@ def on_message(ws, message):
for entity in json_msg["result"]: for entity in json_msg["result"]:
home_assistant_entity_state_cache[entity["entity_id"]] = entity home_assistant_entity_state_cache[entity["entity_id"]] = entity
else: else:
if json_msg["id"] in response_buffer: if json_msg["id"] in response_buffer and json_msg.get("result"):
response_buffer[json_msg["id"]] = json_msg["result"] response_buffer[json_msg["id"]] = json_msg["result"]
return None # Ignore success result messages return None # Ignore success result messages
else: else:
@@ -205,32 +215,34 @@ def execute_script(entity_name: str, domain: str, service: str, service_data: di
logging.exception("Failed to call Home Assisatant script.") logging.exception("Failed to call Home Assisatant script.")
return False return False
def render_template(template): def cache_template(template):
global next_id, response_buffer global next_id, response_buffer
try: try:
call_id = next_id call_id = next_id
# request answer for this call response_buffer[call_id] = template
response_buffer[call_id] = True
msg = { msg = {
"id": next_id, "id": call_id,
"type": "render_template", "type": "render_template",
"template": 'template' "template": template
} }
print(json.dumps(msg))
send_message(json.dumps(msg)) send_message(json.dumps(msg))
# busy waiting for response with a timeout of 0.2 seconds - maybe there's a better way of doing this return True
mustend = time.time() + 0.2
while time.time() < mustend:
if response_buffer[call_id] == True or response_buffer[call_id] is None:
#print(f'loooooooooop {time.time()}')
time.sleep(0.0001)
else:
return response_buffer[call_id]["result"]
raise TimeoutError("Did not recive respose in time to HA template render call")
except Exception as e: except Exception as e:
logging.exception("Failed to render template.") logging.exception("Failed to render template.")
return False return False
return ""
def get_template(template):
global template_cache
print(f"xxxxxxxxxxxx: {template_cache}")
if template in template_cache:
return template_cache[template].get("result")
else:
mustend = time.time() + 0.5
while time.time() < mustend:
if template not in template_cache:
time.sleep(0.0001)
else:
return template_cache.get(template, []).get("result", "404")
def get_entity_data(entity_id: str): def get_entity_data(entity_id: str):
if entity_id in home_assistant_entity_state_cache: if entity_id in home_assistant_entity_state_cache:

View File

@@ -24,6 +24,7 @@ class LovelaceUIPanel:
self.privious_cards = [] self.privious_cards = []
self.cards = {} self.cards = {}
self.hidden_cards = {} self.hidden_cards = {}
self.screensaver = None
self.navigate_keys = {} self.navigate_keys = {}
self.entity_iids = {} self.entity_iids = {}
@@ -69,8 +70,29 @@ class LovelaceUIPanel:
schedule_thread.daemon = True schedule_thread.daemon = True
schedule_thread.start() schedule_thread.start()
# check if ha state cache is already populated
ha_control.wait_for_ha_cache()
#request templates on cards
for c in self.cards.values():
if hasattr(c, "qrcode"):
if c.qrcode.startswith("ha:"):
libs.home_assistant.cache_template(c.qrcode)
for e in c.entities:
e.prerender()
for c in self.hidden_cards.values():
if hasattr(c, "qrcode"):
if c.qrcode.startswith("ha:"):
libs.home_assistant.cache_template(c.qrcode)
for e in c.entities:
e.prerender()
self.screensaver = Screensaver(self.settings["locale"], self.settings["screensaver"], self)
for e in self.screensaver.entities:
e.prerender()
libs.panel_cmd.page_type(self.sendTopic, "pageStartup") libs.panel_cmd.page_type(self.sendTopic, "pageStartup")
def schedule_thread_target(self): def schedule_thread_target(self):
while True: while True:
self.schedule.exec_jobs() self.schedule.exec_jobs()
@@ -165,10 +187,7 @@ class LovelaceUIPanel:
self.update_date() self.update_date()
self.update_time() self.update_time()
# check if ha state cache is already populated self.current_card = self.screensaver
ha_control.wait_for_ha_cache()
self.current_card = Screensaver(self.settings["locale"], self.settings["screensaver"], self)
self.render_current_page(switchPages=True) self.render_current_page(switchPages=True)
# send sleepTimeout # send sleepTimeout
@@ -176,11 +195,11 @@ class LovelaceUIPanel:
if self.current_card.config.get("sleepTimeout"): if self.current_card.config.get("sleepTimeout"):
sleepTimeout = self.current_card.config.get("sleepTimeout") sleepTimeout = self.current_card.config.get("sleepTimeout")
libs.panel_cmd.timeout(self.sendTopic, sleepTimeout) libs.panel_cmd.timeout(self.sendTopic, sleepTimeout)
self.dimmode() self.dimmode()
if msg[1] == "sleepReached": if msg[1] == "sleepReached":
self.privious_cards.append(self.current_card) self.privious_cards.append(self.current_card)
self.current_card = Screensaver(self.settings["locale"], self.settings["screensaver"], self) self.current_card = self.screensaver
self.render_current_page(switchPages=True) self.render_current_page(switchPages=True)
if msg[1] == "renderCurrentPage": if msg[1] == "renderCurrentPage":
self.render_current_page(requested=True) self.render_current_page(requested=True)