mirror of
https://github.com/joBr99/nspanel-lovelace-ui.git
synced 2025-12-19 22:24:15 +01:00
implement homeassistant template caching
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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':
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user