implement cardunlock

This commit is contained in:
joBr99
2023-11-28 16:41:26 +01:00
parent 51dd978c8c
commit d7e417eaf7
3 changed files with 360 additions and 329 deletions

View File

@@ -533,6 +533,30 @@ class AlarmCard(HACard):
result = f"{self.title}~{self.gen_nav()}~{main_entity.entity_id}{arm_buttons}~{icon}~{color}~{numpad}~{flashing}~{add_btn}" result = f"{self.title}~{self.gen_nav()}~{main_entity.entity_id}{arm_buttons}~{icon}~{color}~{numpad}~{flashing}~{add_btn}"
libs.panel_cmd.entityUpd(self.panel.sendTopic, result) libs.panel_cmd.entityUpd(self.panel.sendTopic, result)
class UnlockCard(HACard):
def __init__(self, locale, config, panel):
super().__init__(locale, config, panel)
def render(self):
color = rgb_dec565([255,0,0])
icon = get_icon_char("lock")
supported_modes = ["cardUnlock-unlock"]
entity_id = self.config.get("entity")
# add padding to arm buttons
arm_buttons = ""
for b in supported_modes:
arm_buttons += f'~{get_translation(self.locale, "frontend.ui.card.lock.unlock")}~{b}'
if len(supported_modes) < 4:
arm_buttons += "~"*((4-len(supported_modes))*2)
numpad = "enable"
result = f"{self.title}~{self.gen_nav()}~{entity_id}{arm_buttons}~{icon}~{color}~{numpad}~disable~"
libs.panel_cmd.entityUpd(self.panel.sendTopic, result)
class Screensaver(HACard): class Screensaver(HACard):
def __init__(self, locale, config, panel): def __init__(self, locale, config, panel):
super().__init__(locale, config, panel) super().__init__(locale, config, panel)
@@ -594,6 +618,8 @@ def card_factory(locale, settings, panel):
card = ClimateCard(locale, settings, panel) card = ClimateCard(locale, settings, panel)
case 'cardAlarm': case 'cardAlarm':
card = AlarmCard(locale, settings, panel) card = AlarmCard(locale, settings, panel)
case 'cardUnlock':
card = UnlockCard(locale, settings, panel)
case _: case _:
logging.error("card type %s not implemented", settings["type"]) logging.error("card type %s not implemented", settings["type"])
return "NotImplemented", None return "NotImplemented", None

View File

@@ -1,259 +1,265 @@
from dateutil import tz from dateutil import tz
import datetime import datetime
import threading import threading
import logging import logging
import libs.panel_cmd import libs.panel_cmd
from scheduler import Scheduler from scheduler import Scheduler
import scheduler.trigger as trigger import scheduler.trigger as trigger
import time import time
import babel.dates import babel.dates
from ha_cards import Screensaver, EntitiesCard, card_factory, detail_open from ha_cards import Screensaver, EntitiesCard, card_factory, detail_open
import ha_control import ha_control
class LovelaceUIPanel: class LovelaceUIPanel:
def __init__(self, name_panel, settings_panel): def __init__(self, name_panel, settings_panel):
self.name = name_panel self.name = name_panel
self.settings = settings_panel self.settings = settings_panel
self.sendTopic = self.settings["panelSendTopic"] self.sendTopic = self.settings["panelSendTopic"]
self.recvTopic = self.settings["panelRecvTopic"] self.recvTopic = self.settings["panelRecvTopic"]
self.model = self.settings.get("model", "eu") self.model = self.settings.get("model", "eu")
self.current_card = None self.current_card = None
self.privious_cards = [] self.privious_cards = []
self.cards = {} self.cards = {}
self.hidden_cards = {} self.hidden_cards = {}
self.screensaver = None self.screensaver = None
self.navigate_keys = {} self.navigate_keys = {}
self.entity_iids = {} self.entity_iids = {}
# generate cards for input settings # generate cards for input settings
for c in self.settings.get("cards"): for c in self.settings.get("cards"):
iid, card = card_factory(self.settings["locale"], c, self) iid, card = card_factory(self.settings["locale"], c, self)
# Check if we acually got a card # Check if we acually got a card
if card: if card:
self.cards[iid] = card self.cards[iid] = card
# collect nav keys of cards # collect nav keys of cards
if card.navigate_key: if card.navigate_key:
self.navigate_keys[card.navigate_key] = iid self.navigate_keys[card.navigate_key] = iid
# collect iids of entities # collect iids of entities
for e in card.get_iid_entities(): for e in card.get_iid_entities():
self.entity_iids[e[0]] = e[1] self.entity_iids[e[0]] = e[1]
# setup prev and next iids # setup prev and next iids
top_level_cards = list(self.cards.values()) top_level_cards = list(self.cards.values())
card_iids = [card.iid for card in top_level_cards] card_iids = [card.iid for card in top_level_cards]
prev_iids = card_iids[-1:] + card_iids[:-1] prev_iids = card_iids[-1:] + card_iids[:-1]
next_iids = card_iids[1:] + card_iids[: 1] next_iids = card_iids[1:] + card_iids[: 1]
if len(card_iids) > 1: if len(card_iids) > 1:
for prev_iids, card, next_iids in zip(prev_iids, top_level_cards, next_iids): for prev_iids, card, next_iids in zip(prev_iids, top_level_cards, next_iids):
(card.iid_prev, card.iid_next) = (prev_iids, next_iids) (card.iid_prev, card.iid_next) = (prev_iids, next_iids)
# generate cards for input settings # generate cards for input settings
for c in self.settings.get("hiddenCards", []): for c in self.settings.get("hiddenCards", []):
iid, card = card_factory(self.settings["locale"], c, self) iid, card = card_factory(self.settings["locale"], c, self)
card.hidden = True card.hidden = True
self.hidden_cards[iid] = card self.hidden_cards[iid] = card
# collect nav keys of cards # collect nav keys of cards
if card.navigate_key: if card.navigate_key:
self.navigate_keys[card.navigate_key] = iid self.navigate_keys[card.navigate_key] = iid
# collect iids of entities # collect iids of entities
for e in card.get_iid_entities(): for e in card.get_iid_entities():
self.entity_iids[e[0]] = e[1] self.entity_iids[e[0]] = e[1]
self.schedule = Scheduler() self.schedule = Scheduler()
self.schedule.minutely(datetime.time(second=0), self.update_time) self.schedule.minutely(datetime.time(second=0), self.update_time)
self.schedule.hourly(datetime.time( self.schedule.hourly(datetime.time(
minute=0, second=0), self.update_time) minute=0, second=0), self.update_time)
schedule_thread = threading.Thread(target=self.schedule_thread_target) schedule_thread = threading.Thread(target=self.schedule_thread_target)
schedule_thread.daemon = True schedule_thread.daemon = True
schedule_thread.start() schedule_thread.start()
# check if ha state cache is already populated # check if ha state cache is already populated
ha_control.wait_for_ha_cache() ha_control.wait_for_ha_cache()
#request templates on cards #request templates on cards
for c in self.cards.values(): for c in self.cards.values():
if hasattr(c, "qrcode"): if hasattr(c, "qrcode"):
if c.qrcode.startswith("ha:"): if c.qrcode.startswith("ha:"):
libs.home_assistant.cache_template(c.qrcode) libs.home_assistant.cache_template(c.qrcode)
for e in c.entities: for e in c.entities:
e.prerender() e.prerender()
for c in self.hidden_cards.values(): for c in self.hidden_cards.values():
if hasattr(c, "qrcode"): if hasattr(c, "qrcode"):
if c.qrcode.startswith("ha:"): if c.qrcode.startswith("ha:"):
libs.home_assistant.cache_template(c.qrcode) libs.home_assistant.cache_template(c.qrcode)
for e in c.entities: for e in c.entities:
e.prerender() e.prerender()
self.screensaver = Screensaver(self.settings["locale"], self.settings["screensaver"], self) self.screensaver = Screensaver(self.settings["locale"], self.settings["screensaver"], self)
if self.screensaver.statusIcon1: if self.screensaver.statusIcon1:
self.screensaver.statusIcon1.prerender() self.screensaver.statusIcon1.prerender()
if self.screensaver.statusIcon2: if self.screensaver.statusIcon2:
self.screensaver.statusIcon2.prerender() self.screensaver.statusIcon2.prerender()
for e in self.screensaver.entities: for e in self.screensaver.entities:
e.prerender() 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()
time.sleep(1) time.sleep(1)
def update_time(self): def update_time(self):
use_timezone = tz.gettz(self.settings["timeZone"]) use_timezone = tz.gettz(self.settings["timeZone"])
time_string = datetime.datetime.now( time_string = datetime.datetime.now(
use_timezone).strftime(self.settings["timeFormat"]) use_timezone).strftime(self.settings["timeFormat"])
libs.panel_cmd.send_time(self.sendTopic, time_string) libs.panel_cmd.send_time(self.sendTopic, time_string)
def update_date(self): def update_date(self):
dateformat = self.settings["dateFormat"] dateformat = self.settings["dateFormat"]
date_string = babel.dates.format_date( date_string = babel.dates.format_date(
datetime.datetime.now(), dateformat, locale=self.settings["locale"]) datetime.datetime.now(), dateformat, locale=self.settings["locale"])
libs.panel_cmd.send_date(self.sendTopic, date_string) libs.panel_cmd.send_date(self.sendTopic, date_string)
def searchCard(self, iid): def searchCard(self, iid):
if iid in self.navigate_keys: if iid in self.navigate_keys:
iid = self.navigate_keys[iid] iid = self.navigate_keys[iid]
if iid in self.cards: if iid in self.cards:
return self.cards[iid] return self.cards[iid]
if iid in self.hidden_cards: if iid in self.hidden_cards:
return self.hidden_cards[iid] return self.hidden_cards[iid]
def ha_event_callback(self, entity_id): def ha_event_callback(self, entity_id):
#logging.debug(f"{self.name} {entity_id} updated/state changed") #logging.debug(f"{self.name} {entity_id} updated/state changed")
if self.current_card and entity_id in self.current_card.get_entities(): if self.current_card and entity_id in self.current_card.get_entities():
self.render_current_page(requested=True) self.render_current_page(requested=True)
# send update for detail popup in case it's open # send update for detail popup in case it's open
etype = entity_id.split('.')[0] etype = entity_id.split('.')[0]
if etype in ['light', 'timer', 'cover', 'input_select', 'select', 'fan']: if etype in ['light', 'timer', 'cover', 'input_select', 'select', 'fan']:
# figure out iid of entity # figure out iid of entity
entity_id_iid = "" entity_id_iid = ""
for e in self.current_card.get_iid_entities(): for e in self.current_card.get_iid_entities():
if entity_id == e[1]: if entity_id == e[1]:
entity_id_iid = f'iid.{e[0]}' entity_id_iid = f'iid.{e[0]}'
libs.panel_cmd.entityUpdateDetail(self.sendTopic, detail_open(self.settings["locale"], etype, entity_id, entity_id_iid, sendTopic=self.sendTopic)) libs.panel_cmd.entityUpdateDetail(self.sendTopic, detail_open(self.settings["locale"], etype, entity_id, entity_id_iid, sendTopic=self.sendTopic))
involved_entities = ha_control.calculate_dim_values( involved_entities = ha_control.calculate_dim_values(
self.settings.get("sleepTracking"), self.settings.get("sleepTracking"),
self.settings.get("sleepTrackingZones", ["not_home", "off"]), self.settings.get("sleepTrackingZones", ["not_home", "off"]),
self.settings.get("sleepBrightness"), self.settings.get("sleepBrightness"),
self.settings.get("screenBrightness"), self.settings.get("screenBrightness"),
self.settings.get("sleepOverride"), self.settings.get("sleepOverride"),
return_involved_entities=True return_involved_entities=True
) )
if entity_id in involved_entities: if entity_id in involved_entities:
self.dimmode() self.dimmode()
def render_current_page(self, switchPages=False, requested=False): def render_current_page(self, switchPages=False, requested=False):
if switchPages: if switchPages:
libs.panel_cmd.page_type(self.sendTopic, self.current_card.type) libs.panel_cmd.page_type(self.sendTopic, self.current_card.type)
if requested: if requested:
self.current_card.render() self.current_card.render()
# send sleepTimeout # send sleepTimeout
#sleepTimeout = self.settings.get("sleepTimeout", 20) #sleepTimeout = self.settings.get("sleepTimeout", 20)
#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()
def dimmode(self): def dimmode(self):
# send dimmode # send dimmode
dimValue, dimValueNormal = ha_control.calculate_dim_values( dimValue, dimValueNormal = ha_control.calculate_dim_values(
self.settings.get("sleepTracking"), self.settings.get("sleepTracking"),
self.settings.get("sleepTrackingZones", ["not_home", "off"]), self.settings.get("sleepTrackingZones", ["not_home", "off"]),
self.settings.get("sleepBrightness"), self.settings.get("sleepBrightness"),
self.settings.get("screenBrightness"), self.settings.get("screenBrightness"),
self.settings.get("sleepOverride"), self.settings.get("sleepOverride"),
) )
backgroundColor = self.settings.get("defaultBackgroundColor", "ha-dark") backgroundColor = self.settings.get("defaultBackgroundColor", "ha-dark")
if backgroundColor == "ha-dark": if backgroundColor == "ha-dark":
backgroundColor = 6371 backgroundColor = 6371
elif backgroundColor == "black": elif backgroundColor == "black":
backgroundColor = 0 backgroundColor = 0
fontColor = "" fontColor = ""
featExperimentalSliders = self.settings.get("featExperimentalSliders", 0) featExperimentalSliders = self.settings.get("featExperimentalSliders", 0)
libs.panel_cmd.dimmode(self.sendTopic, dimValue, dimValueNormal, backgroundColor, fontColor, featExperimentalSliders) libs.panel_cmd.dimmode(self.sendTopic, dimValue, dimValueNormal, backgroundColor, fontColor, featExperimentalSliders)
def customrecv_event_callback(self, msg): def customrecv_event_callback(self, msg):
logging.debug("Recv Message from NsPanel (%s): %s", self.name, msg) logging.debug("Recv Message from NsPanel (%s): %s", self.name, msg)
msg = msg.split(",") msg = msg.split(",")
# run action based on received command # run action based on received command
if msg[0] == "event": if msg[0] == "event":
if msg[1] == "startup": if msg[1] == "startup":
# TODO: Handle Update Messages # TODO: Handle Update Messages
self.update_date() self.update_date()
self.update_time() self.update_time()
self.current_card = self.screensaver self.current_card = self.screensaver
self.render_current_page(switchPages=True) self.render_current_page(switchPages=True)
# send sleepTimeout # send sleepTimeout
sleepTimeout = self.settings.get("sleepTimeout", 20) sleepTimeout = self.settings.get("sleepTimeout", 20)
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 = self.screensaver 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)
if msg[1] == "buttonPress2": if msg[1] == "buttonPress2":
entity_id = msg[2] entity_id = msg[2]
btype = msg[3] btype = msg[3]
value = msg[4] if len(msg) > 4 else None value = msg[4] if len(msg) > 4 else None
if btype == "bExit": if btype == "bExit":
if entity_id=="screensaver" and self.settings.get("screensaver").get("doubleTapToUnlock") and value == "1": if entity_id=="screensaver" and self.settings.get("screensaver").get("doubleTapToUnlock") and value == "1":
return return
# in case privious_cards is empty add a default card # in case privious_cards is empty add a default card
if len(self.privious_cards) == 0: if len(self.privious_cards) == 0:
self.privious_cards.append( self.privious_cards.append(
list(self.cards.values())[0]) # TODO: Impelement default card config list(self.cards.values())[0]) # TODO: Impelement default card config
self.current_card = self.privious_cards.pop() self.current_card = self.privious_cards.pop()
self.render_current_page(switchPages=True) self.render_current_page(switchPages=True)
return return
# replace iid with real entity id # replace iid with real entity id
if entity_id.startswith("iid."): if entity_id.startswith("iid."):
iid = entity_id.split(".")[1] iid = entity_id.split(".")[1]
if iid in self.entity_iids: if iid in self.entity_iids:
entity_id = self.entity_iids[iid] entity_id = self.entity_iids[iid]
match btype: match btype:
case 'button': case 'button':
match entity_id.split(".")[0]: match entity_id.split(".")[0]:
# handle internal stuff # handle internal stuff
case 'navigate': case 'navigate':
card_iid = entity_id.split(".")[1] card_iid = entity_id.split(".")[1]
if card_iid == "UP": if card_iid == "UP":
self.current_card = self.privious_cards.pop() self.current_card = self.privious_cards.pop()
# TODO Handle privious_cards empty with default card # TODO Handle privious_cards empty with default card
self.render_current_page(switchPages=True) self.render_current_page(switchPages=True)
else: else:
self.privious_cards.append(self.current_card) self.privious_cards.append(self.current_card)
self.current_card = self.searchCard(card_iid) self.current_card = self.searchCard(card_iid)
self.render_current_page(switchPages=True) self.render_current_page(switchPages=True)
# send ha stuff to ha # send ha stuff to ha
case _: case _:
ha_control.handle_buttons(entity_id, btype, value) ha_control.handle_buttons(entity_id, btype, value)
case _: case 'cardUnlock-unlock':
ha_control.handle_buttons(entity_id, btype, value) card_iid = entity_id.split(".")[1]
if int(self.current_card.config.get("pin")) == int(value):
if msg[1] == "pageOpenDetail": self.privious_cards.append(self.current_card)
entity_id = msg[3] self.current_card = self.searchCard(card_iid)
# replace iid with real entity id self.render_current_page(switchPages=True)
if entity_id.startswith("iid."): case _:
iid = entity_id.split(".")[1] ha_control.handle_buttons(entity_id, btype, value)
if iid in self.entity_iids:
entity_id = self.entity_iids[iid] if msg[1] == "pageOpenDetail":
libs.panel_cmd.entityUpdateDetail(self.sendTopic, detail_open(self.settings["locale"], msg[2], entity_id, msg[3], sendTopic=self.sendTopic)) entity_id = msg[3]
# replace iid with real entity id
if entity_id.startswith("iid."):
iid = entity_id.split(".")[1]
if iid in self.entity_iids:
entity_id = self.entity_iids[iid]
libs.panel_cmd.entityUpdateDetail(self.sendTopic, detail_open(self.settings["locale"], msg[2], entity_id, msg[3], sendTopic=self.sendTopic))

View File

@@ -1,70 +1,69 @@
from libs.helper import iid, rgb_dec565 from libs.helper import iid, rgb_dec565
from libs.icon_mapping import get_icon_char from libs.icon_mapping import get_icon_char
from libs.localization import get_translation from libs.localization import get_translation
class Card: class Card:
def __init__(self, locale, config, panel=None): def __init__(self, locale, config, panel=None):
self.iid = iid() self.iid = iid()
self.iid_prev = None self.iid_prev = None
self.iid_next = None self.iid_next = None
self.navigate_key = config.get("key", None) self.navigate_key = config.get("key", None)
self.locale = locale self.locale = locale
self.title = config.get("title", "") self.title = config.get("title", "")
self.type = config.get("type", "") self.type = config.get("type", "")
self.config = config self.config = config
self.panel = panel self.panel = panel
self.hidden = False self.hidden = False
def render(self): def render(self):
raise NotImplementedError raise NotImplementedError
class Entity:
class Entity: def __init__(self, locale, config, panel):
def __init__(self, locale, config, panel): self.iid = iid()
self.iid = iid() self.locale = locale
self.locale = locale self.entity_id = config["entity"]
self.entity_id = config["entity"] self.etype = self.entity_id.split(".")[0]
self.etype = self.entity_id.split(".")[0] self.config = config
self.config = config self.panel = panel
self.panel = panel self.icon_overwrite = config.get("icon", None)
self.icon_overwrite = config.get("icon", None) self.name_overwrite = config.get("name", None)
self.name_overwrite = config.get("name", None) self.color_overwrite = config.get("color", None)
self.color_overwrite = config.get("color", None) self.value_overwrite = config.get("value", None)
self.value_overwrite = config.get("value", None) font_mapping = {
font_mapping = { "small": "1",
"small": "1", "medium": "2",
"medium": "2", "medium-icon": "3",
"medium-icon": "3", "large": "4",
"large": "4", }
} self.font = font_mapping.get(config.get("font", ""), "")
self.font = font_mapping.get(config.get("font", ""), "")
def render(self, cardType=""):
def render(self, cardType=""): icon_char = self.icon_overwrite or "mdi:gesture-tap-button"
icon_char = self.icon_overwrite or "mdi:gesture-tap-button" color = rgb_dec565([68, 115, 158])
color = rgb_dec565([68, 115, 158]) if self.color_overwrite:
if self.color_overwrite: color = rgb_dec565(self.color_overwrite)
color = rgb_dec565(self.color_overwrite) name = self.name_overwrite or ""
name = self.name_overwrite or "" value = ""
value = "" match self.etype:
match self.etype: case 'delete':
case 'delete': return f"~delete~~~~~"
return f"~delete~~~~~" case 'navigate':
case 'navigate': card_iid = self.entity_id.split(".")[1]
card_iid = self.entity_id.split(".")[1] if card_iid == "UP":
if card_iid == "UP": return f"~button~{self.entity_id}~{get_icon_char(icon_char)}~{color}~{name}~{value}"
return f"~button~{self.entity_id}~{get_icon_char(icon_char)}~{color}~{name}~{value}" else:
else: page_search_res = self.panel.searchCard(card_iid)
page_search_res = self.panel.searchCard(card_iid) if page_search_res is not None:
if page_search_res is not None: if name == "":
if name == "": name = page_search_res.title
name = page_search_res.title value = get_translation(
value = get_translation( self.locale, "frontend.ui.card.button.press")
self.locale, "frontend.ui.card.button.press") return f"~button~{self.entity_id}~{get_icon_char(icon_char)}~{color}~{name}~{value}"
return f"~button~{self.entity_id}~{get_icon_char(icon_char)}~{color}~{name}~{value}" else:
else: return f"~text~{self.entity_id}~{get_icon_char('mdi:alert-circle-outline')}~17299~page not found~"
return f"~text~{self.entity_id}~{get_icon_char('mdi:alert-circle-outline')}~17299~page not found~" case 'iText':
case 'iText': # TODO: Render as HA Template
# TODO: Render as HA Template value = self.entity_id.split(".")[1]
value = self.entity_id.split(".")[1] return f"~text~{self.entity_id}~{get_icon_char(icon_char)}~{color}~{name}~{value}"
return f"~text~{self.entity_id}~{get_icon_char(icon_char)}~{color}~{name}~{value}"