mirror of
https://github.com/joBr99/nspanel-lovelace-ui.git
synced 2025-12-20 14:37:01 +01:00
860 lines
39 KiB
Python
860 lines
39 KiB
Python
import libs.home_assistant
|
|
import ha_icons
|
|
import ha_colors
|
|
from libs.localization import get_translation
|
|
import panel_cards
|
|
import logging
|
|
import dateutil.parser as dp
|
|
import babel
|
|
from libs.icon_mapping import get_icon_char
|
|
from libs.helper import rgb_dec565, scale
|
|
import time
|
|
import threading
|
|
import datetime
|
|
import json
|
|
|
|
class HAEntity(panel_cards.Entity):
|
|
def __init__(self, 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:
|
|
libs.home_assistant.cache_template(val)
|
|
|
|
def render(self, cardType=""):
|
|
|
|
config_icon = self.config.get("icon", None)
|
|
if config_icon and isinstance(config_icon, str) and config_icon.startswith("ha:"):
|
|
self.icon_overwrite = libs.home_assistant.get_template(config_icon)
|
|
config_color = self.config.get("color", None)
|
|
if config_color and isinstance(config_color, str) and config_color.startswith("ha:"):
|
|
self.color_overwrite = json.loads(libs.home_assistant.get_template(config_color)[3:])
|
|
|
|
if self.etype in ["delete", "navigate", "iText"]:
|
|
out = super().render()
|
|
if self.etype == "navigate" and "status" in self.config:
|
|
status_out = HAEntity(self.locale,
|
|
{
|
|
'entity': f'{self.config.get("status")}',
|
|
}, self.panel
|
|
).render().split("~")
|
|
status_out[2] = out.split("~")[2]
|
|
status_out = "~".join(status_out)
|
|
return status_out
|
|
return out
|
|
|
|
# get data from HA
|
|
data = libs.home_assistant.get_entity_data(self.entity_id)
|
|
if data:
|
|
self.state = data.get("state")
|
|
self.attributes = data.get("attributes", [])
|
|
else:
|
|
self.state = "not found"
|
|
self.attributes = []
|
|
return "~text~iid.404~X~6666~not found~"
|
|
|
|
# HA Entities
|
|
entity_type_panel = "text"
|
|
icon_char = ha_icons.get_icon_ha(self.etype, self.state, device_class=self.attributes.get("device_class", None), media_content_type=self.attributes.get("media_content_type", None), overwrite=self.icon_overwrite)
|
|
|
|
color = ha_colors.get_entity_color(self.etype, self.state, self.attributes, self.color_overwrite)
|
|
|
|
name = self.config.get("name", self.attributes.get("friendly_name", "unknown"))
|
|
if self.name_overwrite:
|
|
name = self.name_overwrite
|
|
value = ""
|
|
|
|
match self.etype:
|
|
case 'switch' | 'input_boolean' | 'automation':
|
|
entity_type_panel = "switch"
|
|
value = 1 if self.state == "on" else 0
|
|
case 'lock':
|
|
entity_type_panel = "button"
|
|
value = get_translation(self.locale, "frontend.ui.card.lock.lock") if self.state == "unlocked" else get_translation(
|
|
self.locale, "frontend.ui.card.lock.unlock")
|
|
case 'input_text':
|
|
value = self.state
|
|
case 'input_select' | 'select':
|
|
entity_type_panel = "input_sel"
|
|
value = self.state
|
|
case 'light':
|
|
entity_type_panel = "light"
|
|
value = 1 if self.state == "on" else 0
|
|
case 'fan':
|
|
entity_type_panel = "fan"
|
|
value = 1 if self.state == "on" else 0
|
|
case 'button' | 'input_button':
|
|
entity_type_panel = "button"
|
|
value = get_translation(
|
|
self.locale, "frontend.ui.card.button.press")
|
|
case 'scene':
|
|
entity_type_panel = "button"
|
|
value = get_translation(
|
|
self.locale, "frontend.ui.card.scene.activate")
|
|
case 'script':
|
|
entity_type_panel = "button"
|
|
value = get_translation(
|
|
self.locale, "frontend.ui.card.script.run")
|
|
case 'number' | 'input_number':
|
|
entity_type_panel = "number"
|
|
min_v = self.attributes.get("min", 0)
|
|
max_v = self.attributes.get("max", 100)
|
|
value = f"{self.state}|{min_v}|{max_v}"
|
|
case 'timer':
|
|
entity_type_panel = "timer"
|
|
value = get_translation(
|
|
self.locale, f"backend.component.timer.state._.{self.state}")
|
|
case 'alarm_control_panel':
|
|
value = get_translation(
|
|
self.locale, f"frontend.state_badge.alarm_control_panel.{self.state}")
|
|
case 'vacuum':
|
|
entity_type_panel = "button"
|
|
if self.state == "docked":
|
|
value = get_translation(
|
|
self.locale, "frontend.ui.card.vacuum.actions.start_cleaning")
|
|
else:
|
|
value = get_translation(
|
|
self.locale, "frontend.ui.card.vacuum.actions.return_to_base")
|
|
case 'media_player':
|
|
entity_type_panel = "media_pl"
|
|
value = self.state
|
|
case 'sun':
|
|
value = get_translation(
|
|
self.locale, f"backend.component.sun.state._.{self.state}")
|
|
case 'person':
|
|
value = get_translation(
|
|
self.locale, f"backend.component.person.state._.{self.state}")
|
|
case 'climate':
|
|
# TODO: temp unit
|
|
temp_unit = "celsius"
|
|
state_value = get_translation(
|
|
self.locale, f"backend.component.climate.state._.{self.state}")
|
|
temperature = self.attributes.get("temperature", "")
|
|
temperature_unit = "°C" if (temp_unit == "celsius") else "°F"
|
|
value = f"{state_value} {temperature}{temperature_unit}"
|
|
currently_tanslation = get_translation(
|
|
self.locale, "frontend.ui.card.climate.currently")
|
|
current_temperature = self.attributes.get(
|
|
"current_temperature", "")
|
|
value += f"\r\n{currently_tanslation}: {current_temperature}{temperature_unit}"
|
|
case 'cover':
|
|
entity_type_panel = "shutter"
|
|
device_class = self.attributes.get("device_class", "window")
|
|
icon_up = ""
|
|
icon_stop = ""
|
|
icon_down = ""
|
|
icon_up_status = "disable"
|
|
icon_stop_status = "disable"
|
|
icon_down_status = "disable"
|
|
bits = self.attributes.get("supported_features")
|
|
pos = self.attributes.get("current_position")
|
|
assumed_state = self.attributes.get("assumed_state", False)
|
|
if pos is None:
|
|
pos = "disable"
|
|
if bits & 0b00000001: # SUPPORT_OPEN
|
|
if (pos != 100 and not (self.state == "open" and pos == "disable")):
|
|
icon_up_status = "enable"
|
|
icon_up = ha_icons.get_action_icon(
|
|
etype=self.etype, action="open", device_class=device_class)
|
|
if bits & 0b00000010: # SUPPORT_CLOSE
|
|
if (pos != 0 and not (self.state == "closed" and pos == "disable")):
|
|
icon_down_status = "enable"
|
|
icon_down = ha_icons.get_action_icon(
|
|
etype=self.etype, action="close", device_class=device_class)
|
|
if bits & 0b00001000: # SUPPORT_STOP
|
|
icon_stop = ha_icons.get_action_icon(
|
|
etype=self.etype, action="stop", device_class=device_class)
|
|
icon_stop_status = "enable"
|
|
if assumed_state:
|
|
icon_up_status = "enable"
|
|
icon_stop_status = "enable"
|
|
icon_down_status = "enable"
|
|
value = f"{icon_up}|{icon_stop}|{icon_down}|{icon_up_status}|{icon_stop_status}|{icon_down_status}"
|
|
case 'sensor':
|
|
device_class = self.attributes.get("device_class", "")
|
|
unit_of_measurement = self.attributes.get("unit_of_measurement", "")
|
|
value = self.state
|
|
# limit value to 4 chars on us-p
|
|
if self.panel.model == "us-p" and cardType == "cardEntities":
|
|
value = self.state[:4]
|
|
if value[-1] == ".":
|
|
value = value[:-1]
|
|
|
|
if device_class != "temperature":
|
|
value = value + " "
|
|
value = value + unit_of_measurement
|
|
if cardType in ["cardGrid", "cardGrid2"] and not self.icon_overwrite:
|
|
icon_char = value
|
|
case 'binary_sensor':
|
|
device_class = self.attributes.get("device_class", "")
|
|
value = get_translation(self.locale, f"backend.component.binary_sensor.state.{device_class}.{self.state}")
|
|
case 'weather':
|
|
attr = self.config.get("attribute", "temperature")
|
|
value = str(self.attributes.get(attr, self.state))
|
|
|
|
# settings for forecast
|
|
forecast_type = None
|
|
if self.config.get("day"):
|
|
forecast_type = "daily"
|
|
pos = self.config.get("day")
|
|
datetime_format = "E"
|
|
if self.config.get("hour"):
|
|
forecast_type = "hourly"
|
|
pos = self.config.get("hour")
|
|
datetime_format = "H:mm"
|
|
if forecast_type:
|
|
forecast = libs.home_assistant.execute_script(
|
|
entity_name=self.entity_id,
|
|
domain='weather',
|
|
service="get_forecasts",
|
|
service_data={
|
|
'type': forecast_type
|
|
}
|
|
).get(self.entity_id,{}).get("forecast", [])
|
|
if len(forecast) > pos:
|
|
forcast_pos = forecast[pos]
|
|
forcast_condition = forcast_pos.get("condition", "")
|
|
forcast_date = dp.parse(forcast_pos.get("datetime")).astimezone()
|
|
|
|
icon_char = ha_icons.get_icon_ha(self.etype, forcast_condition)
|
|
name = babel.dates.format_datetime(forcast_date, datetime_format, locale=self.locale)
|
|
|
|
attr = self.config.get("attribute", "temperature")
|
|
value = str(forcast_pos.get(attr, "not found"))
|
|
else:
|
|
name: "unknown"
|
|
# add units
|
|
if attr in ["temperature", "apparent_temperature", "templow"]:
|
|
value += self.config.get("unit", "°C")
|
|
else:
|
|
value += self.config.get("unit", "")
|
|
case _:
|
|
name = "unsupported"
|
|
|
|
if self.value_overwrite and self.value_overwrite.startswith("ha:"):
|
|
value = libs.home_assistant.get_template(self.value_overwrite)[3:]
|
|
|
|
|
|
return f"~{entity_type_panel}~iid.{self.iid}~{icon_char}~{color}~{name}~{value}"
|
|
|
|
class HACard(panel_cards.Card):
|
|
def __init__(self, locale, config, panel):
|
|
super().__init__(locale, config, panel)
|
|
# Generate Entity for each Entity in Config
|
|
self.entities = []
|
|
if "entity" in config:
|
|
entity = HAEntity(locale, config, panel)
|
|
self.entities.append(entity)
|
|
if "entities" in config:
|
|
for e in config.get("entities"):
|
|
entity = HAEntity(locale, e, panel)
|
|
self.entities.append(entity)
|
|
|
|
def get_iid_entities(self):
|
|
return [(e.iid, e.entity_id) for e in self.entities]
|
|
|
|
def get_entities(self):
|
|
return [e.entity_id for e in self.entities]
|
|
|
|
def gen_nav(self):
|
|
leftBtn = "delete~~~~~"
|
|
if self.iid_prev:
|
|
leftBtn = panel_cards.Entity(self.locale,
|
|
{
|
|
'entity': f'navigate.{self.iid_prev}',
|
|
'icon': 'mdi:arrow-left-bold',
|
|
'color': [255, 255, 255],
|
|
}, self.panel
|
|
).render()[1:]
|
|
rightBtn = "delete~~~~~"
|
|
if self.iid_next:
|
|
rightBtn = panel_cards.Entity(self.locale,
|
|
{
|
|
'entity': f'navigate.{self.iid_next}',
|
|
'icon': 'mdi:arrow-right-bold',
|
|
'color': [255, 255, 255],
|
|
}, self.panel
|
|
).render()[1:]
|
|
if self.hidden:
|
|
leftBtn = panel_cards.Entity(self.locale,
|
|
{
|
|
'entity': f'navigate.UP',
|
|
'icon': 'mdi:arrow-up-bold',
|
|
'color': [255, 255, 255],
|
|
}, self.panel
|
|
).render()[1:]
|
|
result = f"{leftBtn}~{rightBtn}"
|
|
return result
|
|
|
|
class EntitiesCard(HACard):
|
|
def __init__(self, locale, config, panel):
|
|
super().__init__(locale, config, panel)
|
|
if len(self.entities) > 6 and self.type == "cardGrid":
|
|
self.type = "cardGrid2"
|
|
|
|
def render(self):
|
|
result = f"{self.title}~{self.gen_nav()}"
|
|
for e in self.entities:
|
|
result += e.render(cardType=self.type)
|
|
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
|
|
|
|
class QRCard(HACard):
|
|
def __init__(self, locale, config, panel):
|
|
super().__init__(locale, config, panel)
|
|
self.qrcode = config.get("qrCode", "https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
|
def render(self):
|
|
if self.qrcode.startswith("ha:"):
|
|
self.qrcode = libs.home_assistant.get_template(self.qrcode)[3:]
|
|
result = f"{self.title}~{self.gen_nav()}~{self.qrcode}"
|
|
for e in self.entities:
|
|
result += e.render()
|
|
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
|
|
|
|
class PowerCard(HACard):
|
|
def __init__(self, locale, config, panel):
|
|
super().__init__(locale, config, panel)
|
|
|
|
def render(self):
|
|
result = f"{self.title}~{self.gen_nav()}"
|
|
for e in self.entities:
|
|
result += e.render()
|
|
speed = 0
|
|
# TODO: Implement speed card power
|
|
#if apis.ha_api.entity_exists(item.entityId):
|
|
# entity = apis.ha_api.get_entity(item.entityId)
|
|
# speed = str(item.entity_input_config.get("speed", 0))
|
|
# if isinstance(speed, str):
|
|
# speed = apis.ha_api.render_template(speed)
|
|
result += f"~{speed}"
|
|
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
|
|
|
|
class MediaCard(HACard):
|
|
def __init__(self, locale, config, panel):
|
|
super().__init__(locale, config, panel)
|
|
|
|
def render(self):
|
|
main_entity = self.entities[0]
|
|
media_icon = main_entity.render()
|
|
if not self.title:
|
|
self.title = main_entity.attributes.get("friendly_name", "unknown")
|
|
title = main_entity.attributes.get("media_title", "")
|
|
author = main_entity.attributes.get("media_artist", "")
|
|
volume = int(main_entity.attributes.get("volume_level", 0)*100)
|
|
iconplaypause = get_icon_char("pause") if main_entity.state == "playing" else get_icon_char("play")
|
|
onoffbutton = "disable"
|
|
shuffleBtn = "disable"
|
|
|
|
bits = main_entity.attributes.get("supported_features")
|
|
if bits & 0b10000000:
|
|
if main_entity.state == "off":
|
|
onoffbutton = 1374
|
|
else:
|
|
onoffbutton = rgb_dec565([255,152,0])
|
|
|
|
if bits & 0b100000000000000:
|
|
shuffle = main_entity.attributes.get("shuffle", "")
|
|
if shuffle == False:
|
|
shuffleBtn = get_icon_char('shuffle-disabled')
|
|
elif shuffle == True:
|
|
shuffleBtn = get_icon_char('shuffle')
|
|
|
|
# generate button entities
|
|
button_str = ""
|
|
for e in self.entities[1:]:
|
|
button_str += e.render()
|
|
result = f"{self.title}~{self.gen_nav()}~{main_entity.entity_id}~{title}~~{author}~~{volume}~{iconplaypause}~{onoffbutton}~{shuffleBtn}{media_icon}{button_str}"
|
|
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
|
|
|
|
class ClimateCard(HACard):
|
|
def __init__(self, locale, config, panel):
|
|
super().__init__(locale, config, panel)
|
|
|
|
def render(self):
|
|
main_entity = self.entities[0]
|
|
|
|
temp_unit = self.panel.temp_unit
|
|
if temp_unit == "celsius":
|
|
temperature_unit_icon = get_icon_char("temperature-celsius")
|
|
temperature_unit = "°C"
|
|
|
|
else:
|
|
temperature_unit_icon = get_icon_char("temperature-fahrenheit")
|
|
temperature_unit = "°F"
|
|
|
|
main_entity.render()
|
|
if not self.title:
|
|
self.title = main_entity.attributes.get("friendly_name", "unknown")
|
|
current_temp = main_entity.attributes.get("current_temperature", "")
|
|
dest_temp = main_entity.attributes.get("temperature", None)
|
|
dest_temp2 = ""
|
|
if dest_temp is None:
|
|
dest_temp = main_entity.attributes.get("target_temp_high", 0)
|
|
dest_temp2 = main_entity.attributes.get("target_temp_low", None)
|
|
if dest_temp2 != None and dest_temp2 != "null":
|
|
dest_temp2 = int(dest_temp2*10)
|
|
else:
|
|
dest_temp2 = ""
|
|
dest_temp = int(dest_temp*10)
|
|
|
|
hvac_action = main_entity.attributes.get("hvac_action", "")
|
|
state_value = ""
|
|
if hvac_action != "":
|
|
state_value = get_translation(self.locale, f"frontend.state_attributes.climate.hvac_action.{hvac_action}")
|
|
state_value += "\r\n("
|
|
state_value += get_translation(self.locale, f"backend.component.climate.state._.{main_entity.state}")
|
|
if hvac_action != "":
|
|
state_value += ")"
|
|
|
|
min_temp = int(main_entity.attributes.get("min_temp", 0)*10)
|
|
max_temp = int(main_entity.attributes.get("max_temp", 0)*10)
|
|
|
|
if temp_unit == "celsius":
|
|
step_default = 0.5
|
|
else:
|
|
step_default = 0.5
|
|
step_temp = int(main_entity.attributes.get("target_temp_step", step_default)*10)
|
|
icon_res_list = []
|
|
icon_res = ""
|
|
|
|
hvac_modes = main_entity.attributes.get("hvac_modes", [])
|
|
if main_entity.config.get("supported_modes"):
|
|
hvac_modes = main_entity.config.get("supported_modes")
|
|
for mode in hvac_modes:
|
|
icon_id = ha_icons.get_icon_ha("climate", mode)
|
|
color_on = 64512
|
|
if mode in ["auto", "heat_cool"]:
|
|
color_on = 1024
|
|
if mode == "heat":
|
|
color_on = 64512
|
|
if mode == "off":
|
|
color_on = 35921
|
|
if mode == "cool":
|
|
color_on = 11487
|
|
if mode == "dry":
|
|
color_on = 60897
|
|
if mode == "fan_only":
|
|
color_on = 35921
|
|
state = 0
|
|
if(mode == main_entity.state):
|
|
state = 1
|
|
|
|
icon_res_list.append(f"~{icon_id}~{color_on}~{state}~{mode}")
|
|
|
|
icon_res = "".join(icon_res_list)
|
|
|
|
if len(icon_res_list) == 1 and not self.panel.model == "us-p":
|
|
icon_res = "~"*4 + icon_res_list[0] + "~"*4*6
|
|
elif len(icon_res_list) == 2 and not self.panel.model == "us-p":
|
|
icon_res = "~"*4*2 + icon_res_list[0] + "~"*4*2 + icon_res_list[1] + "~"*4*2
|
|
elif len(icon_res_list) == 3 and not self.panel.model == "us-p":
|
|
icon_res = "~"*4*2 + icon_res_list[0] + "~"*4 + icon_res_list[1] + "~"*4 + icon_res_list[2] + "~"*4
|
|
elif len(icon_res_list) == 4 and not self.panel.model == "us-p":
|
|
icon_res = "~"*4 + icon_res_list[0] + "~"*4 + icon_res_list[1] + "~"*4 + icon_res_list[2] + "~"*4 + icon_res_list[3]
|
|
elif len(icon_res_list) >= 5 or self.panel.model == "us-p":
|
|
icon_res = "".join(icon_res_list) + "~"*4*(8-len(icon_res_list))
|
|
|
|
currently_translation = get_translation(self.locale, "frontend.ui.card.climate.currently")
|
|
state_translation = get_translation(self.locale, "frontend.ui.panel.config.devices.entities.state")
|
|
action_translation = get_translation(self.locale, "frontend.ui.card.climate.operation").replace(' ','\r\n')
|
|
|
|
detailPage = "1"
|
|
if any(x in ["preset_modes", "swing_modes", "fan_modes"] for x in main_entity.attributes):
|
|
detailPage = "0"
|
|
|
|
result = f"{self.title}~{self.gen_nav()}~iid.{main_entity.iid}~{current_temp} {temperature_unit}~{dest_temp}~{state_value}~{min_temp}~{max_temp}~{step_temp}{icon_res}~{currently_translation}~{state_translation}~{action_translation}~{temperature_unit_icon}~{dest_temp2}~{detailPage}"
|
|
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
|
|
|
|
class AlarmCard(HACard):
|
|
def __init__(self, locale, config, panel):
|
|
super().__init__(locale, config, panel)
|
|
|
|
def render(self):
|
|
main_entity = self.entities[0]
|
|
main_entity.render()
|
|
|
|
print(main_entity.state)
|
|
|
|
icon = get_icon_char("shield-off")
|
|
color = rgb_dec565([255,255,255])
|
|
supported_modes = []
|
|
numpad = "enable"
|
|
if main_entity.state == "disarmed":
|
|
color = rgb_dec565([13,160,53])
|
|
icon = get_icon_char("shield-off")
|
|
if not main_entity.attributes.get("code_arm_required", False):
|
|
numpad = "disable"
|
|
if self.config.get("supported_modes") is None:
|
|
bits = main_entity.attributes.get("supported_features")
|
|
if bits & 0b000001:
|
|
supported_modes.append("arm_home")
|
|
if bits & 0b000010:
|
|
supported_modes.append("arm_away")
|
|
if bits & 0b000100:
|
|
supported_modes.append("arm_night")
|
|
if bits & 0b100000:
|
|
supported_modes.append("arm_vacation")
|
|
else:
|
|
supported_modes = self.config.get("supported_modes")
|
|
else:
|
|
supported_modes.append("disarm")
|
|
|
|
if main_entity.state == "armed_home":
|
|
color = rgb_dec565([223,76,30])
|
|
icon = get_icon_char("shield-home")
|
|
if main_entity.state == "armed_away":
|
|
color = rgb_dec565([223,76,30])
|
|
icon = get_icon_char("shield-lock")
|
|
if main_entity.state == "armed_night":
|
|
color = rgb_dec565([223,76,30])
|
|
icon = get_icon_char("weather-night")
|
|
if main_entity.state == "armed_vacation":
|
|
color = rgb_dec565([223,76,30])
|
|
icon = get_icon_char("shield-airplane")
|
|
|
|
flashing = "disable"
|
|
if main_entity.state in ["arming", "pending"]:
|
|
color = rgb_dec565([243,179,0])
|
|
icon = get_icon_char("shield")
|
|
flashing = "enable"
|
|
if main_entity.state == "triggered":
|
|
color = rgb_dec565([223,76,30])
|
|
icon = get_icon_char("bell-ring")
|
|
flashing = "enable"
|
|
|
|
#add button to show sensor state
|
|
add_btn = ""
|
|
if "open_sensors" in main_entity.attributes and main_entity.attributes.get("open_sensors") is not None:
|
|
add_btn=f"{get_icon_char('progress-alert')}~{rgb_dec565([243,179,0])}~"
|
|
|
|
# add padding to arm buttons
|
|
arm_buttons = ""
|
|
for b in supported_modes:
|
|
modeName = f"frontend.ui.card.alarm_control_panel.{b}"
|
|
arm_buttons += f"~{get_translation(self.locale, modeName)}~{b}"
|
|
if len(supported_modes) < 4:
|
|
arm_buttons += "~"*((4-len(supported_modes))*2)
|
|
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.msg_out_queue, 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("destination")
|
|
|
|
# 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.msg_out_queue, self.panel.sendTopic, result)
|
|
|
|
class Screensaver(HACard):
|
|
def __init__(self, locale, config, panel):
|
|
super().__init__(locale, config, panel)
|
|
|
|
if not self.type:
|
|
self.type = "screensaver"
|
|
|
|
self.statusIcon1 = None
|
|
if "statusIcon1" in config:
|
|
self.statusIcon1 = HAEntity(locale, config.get("statusIcon1"), panel)
|
|
self.statusIcon2 = None
|
|
if "statusIcon2" in config:
|
|
self.statusIcon2 = HAEntity(locale, config.get("statusIcon2"), panel)
|
|
|
|
def get_entities(self):
|
|
ent = [e.entity_id for e in self.entities]
|
|
if self.statusIcon1:
|
|
ent.append(self.statusIcon1.entity_id)
|
|
if self.statusIcon2:
|
|
ent.append(self.statusIcon2.entity_id)
|
|
return ent
|
|
|
|
def render(self):
|
|
result = ""
|
|
for e in self.entities:
|
|
result += e.render(cardType=self.type)
|
|
libs.panel_cmd.weatherUpdate(self.panel.msg_out_queue, self.panel.sendTopic, result[1:])
|
|
|
|
statusUpdateResult = ""
|
|
icon1font = ""
|
|
icon2font = ""
|
|
if self.statusIcon1:
|
|
si1 = self.statusIcon1.render().split('~')
|
|
statusUpdateResult += f"{si1[3]}~{si1[4]}"
|
|
icon1font = self.statusIcon1.font
|
|
else:
|
|
statusUpdateResult += "~"
|
|
if self.statusIcon2:
|
|
si2 = self.statusIcon2.render().split('~')
|
|
statusUpdateResult += f"~{si2[3]}~{si2[4]}"
|
|
icon2font = self.statusIcon2.font
|
|
else:
|
|
statusUpdateResult += "~~"
|
|
|
|
libs.panel_cmd.statusUpdate(self.panel.msg_out_queue, self.panel.sendTopic, f"{statusUpdateResult}~{icon1font}~{icon2font}")
|
|
|
|
|
|
def card_factory(locale, settings, panel):
|
|
match settings["type"]:
|
|
case 'cardEntities' | 'cardGrid':
|
|
card = EntitiesCard(locale, settings, panel)
|
|
case 'cardQR':
|
|
card = QRCard(locale, settings, panel)
|
|
case 'cardPower':
|
|
card = PowerCard(locale, settings, panel)
|
|
case 'cardMedia':
|
|
card = MediaCard(locale, settings, panel)
|
|
case 'cardThermo':
|
|
card = ClimateCard(locale, settings, panel)
|
|
case 'cardAlarm':
|
|
card = AlarmCard(locale, settings, panel)
|
|
case 'cardUnlock':
|
|
card = UnlockCard(locale, settings, panel)
|
|
case _:
|
|
logging.error("card type %s not implemented", settings["type"])
|
|
return "NotImplemented", None
|
|
return card.iid, card
|
|
|
|
def detail_open(locale, detail_type, ha_entity_id, entity_id, msg_out_queue, sendTopic=None, options_list=None):
|
|
data = libs.home_assistant.get_entity_data(ha_entity_id)
|
|
if data:
|
|
state = data.get("state")
|
|
attributes = data.get("attributes", [])
|
|
else:
|
|
logging.error("popup entity %s not found", ha_entity_id)
|
|
return
|
|
|
|
match detail_type:
|
|
case 'popupShutter' | 'cover':
|
|
entityType = "cover"
|
|
device_class = attributes.get("device_class", "window")
|
|
icon_id = ha_icons.get_icon_ha(entityType, state)
|
|
|
|
pos = attributes.get("current_position")
|
|
if pos is None:
|
|
pos_status = state
|
|
pos = "disable"
|
|
else:
|
|
pos_status = pos
|
|
|
|
pos_translation = ""
|
|
icon_up = ""
|
|
icon_stop = ""
|
|
icon_down = ""
|
|
icon_up_status = "disable"
|
|
icon_stop_status = "disable"
|
|
icon_down_status = "disable"
|
|
textTilt = ""
|
|
iconTiltLeft = ""
|
|
iconTiltStop = ""
|
|
iconTiltRight = ""
|
|
iconTiltLeftStatus = "disable"
|
|
iconTiltStopStatus = "disable"
|
|
iconTiltRightStatus = "disable"
|
|
tilt_pos = "disable"
|
|
|
|
bits = attributes.get("supported_features")
|
|
|
|
# position supported
|
|
if bits & 0b00001111:
|
|
pos_translation = get_translation(locale, "frontend.ui.card.cover.position")
|
|
if bits & 0b00000001: # SUPPORT_OPEN
|
|
if ( pos != 100 and not (state == "open" and pos == "disable") ):
|
|
icon_up_status = "enable"
|
|
icon_up = ha_icons.get_action_icon(etype=entityType, action="open", device_class=device_class)
|
|
if bits & 0b00000010: # SUPPORT_CLOSE
|
|
if ( pos != 0 and not (state == "closed" and pos == "disable") ):
|
|
icon_down_status = "enable"
|
|
icon_down = ha_icons.get_action_icon(etype=entityType, action="close", device_class=device_class)
|
|
#if bits & 0b00000100: # SUPPORT_SET_POSITION
|
|
if bits & 0b00001000: # SUPPORT_STOP
|
|
icon_stop = ha_icons.get_action_icon(etype=entityType, action="stop", device_class=device_class)
|
|
icon_stop_status = "enable"
|
|
|
|
# tilt supported
|
|
if bits & 0b11110000:
|
|
textTilt = get_translation(locale, "frontend.ui.card.cover.tilt_position")
|
|
if bits & 0b00010000: # SUPPORT_OPEN_TILT
|
|
iconTiltLeft = get_icon_char('arrow-top-right')
|
|
iconTiltLeftStatus = "enable"
|
|
if bits & 0b00100000: # SUPPORT_CLOSE_TILT
|
|
iconTiltRight = get_icon_char('arrow-bottom-left')
|
|
iconTiltRightStatus = "enable"
|
|
if bits & 0b01000000: # SUPPORT_STOP_TILT
|
|
iconTiltStop = get_icon_char('stop')
|
|
iconTiltStopStatus = "enable"
|
|
if bits & 0b10000000: # SUPPORT_SET_TILT_POSITION
|
|
tilt_pos = attributes.get("current_tilt_position", 0)
|
|
if(tilt_pos == 0):
|
|
iconTiltRightStatus = "disable"
|
|
if(tilt_pos == 100):
|
|
iconTiltLeftStatus = "disable"
|
|
|
|
return f'{entity_id}~{pos}~{pos_translation}: {pos_status}~{pos_translation}~{icon_id}~{icon_up}~{icon_stop}~{icon_down}~{icon_up_status}~{icon_stop_status}~{icon_down_status}~{textTilt}~{iconTiltLeft}~{iconTiltStop}~{iconTiltRight}~{iconTiltLeftStatus}~{iconTiltStopStatus}~{iconTiltRightStatus}~{tilt_pos}'
|
|
|
|
case 'popupLight' | 'light':
|
|
switch_val = 1 if state == "on" else 0
|
|
icon_color = 6666
|
|
brightness = "disable"
|
|
color_temp = "disable"
|
|
color = "disable"
|
|
effect_supported = "disable"
|
|
|
|
if "onoff" not in attributes.get("supported_color_modes"):
|
|
brightness = 0
|
|
if state == "on":
|
|
if "brightness" in attributes and attributes.get("brightness"):
|
|
# scale 0-255 brightness from ha to 0-100
|
|
brightness = int(scale(attributes.get("brightness"),(0,255),(0,100)))
|
|
else:
|
|
brightness = "disable"
|
|
if "color_temp" in attributes.get("supported_color_modes"):
|
|
if "color_temp" in attributes and attributes.get("color_temp"):
|
|
# scale ha color temp range to 0-100
|
|
color_temp = int(scale(attributes.get("color_temp"), (attributes.get("min_mireds"), attributes.get("max_mireds")),(0,100)))
|
|
else:
|
|
color_temp = "unknown"
|
|
else:
|
|
color_temp = "disable"
|
|
list_color_modes = ["xy", "rgb", "rgbw", "hs"]
|
|
if any(item in list_color_modes for item in attributes.get("supported_color_modes")):
|
|
color = "enable"
|
|
else:
|
|
color = "disable"
|
|
if "effect_list" in attributes:
|
|
effect_supported = "enable"
|
|
color_translation = "Color"
|
|
brightness_translation = get_translation(locale, "frontend.ui.card.light.brightness")
|
|
color_temp_translation = get_translation(locale, "frontend.ui.card.light.color_temperature")
|
|
return f'{entity_id}~~{icon_color}~{switch_val}~{brightness}~{color_temp}~{color}~{color_translation}~{color_temp_translation}~{brightness_translation}~{effect_supported}'
|
|
case 'popupFan' | 'fan':
|
|
switch_val = 1 if state == "on" else 0
|
|
icon_color = ha_colors.get_entity_color("fan", state, attributes)
|
|
speed = attributes.get("percentage")
|
|
percentage_step = attributes.get("percentage_step")
|
|
speedMax = 100
|
|
if percentage_step is None:
|
|
speed = "disable"
|
|
else:
|
|
if speed is None:
|
|
speed = 0
|
|
speed = round(speed/percentage_step)
|
|
speedMax = int(100/percentage_step)
|
|
|
|
speed_translation = get_translation(locale, "frontend.ui.card.fan.speed")
|
|
|
|
preset_mode = attributes.get("preset_mode", "")
|
|
preset_modes = attributes.get("preset_modes", [])
|
|
if preset_modes is not None:
|
|
preset_modes = "?".join(attributes.get("preset_modes", []))
|
|
else:
|
|
preset_modes = ""
|
|
|
|
return f'{entity_id}~~{icon_color}~{switch_val}~{speed}~{speedMax}~{speed_translation}~{preset_mode}~{preset_modes}'
|
|
case 'popupThermo' | 'climate':
|
|
icon_id = ha_icons.get_icon_ha("climate", state)
|
|
icon_color = ha_colors.get_entity_color("climate", state, attributes)
|
|
|
|
modes_out = ""
|
|
for mode in ["preset_modes", "swing_modes", "fan_modes"]:
|
|
heading = get_translation(locale, f"frontend.ui.card.climate.{mode[:-1]}")
|
|
cur_mode = attributes.get(mode[:-1], "")
|
|
modes = attributes.get(mode, [])
|
|
if modes is not None:
|
|
if mode == "preset_modes":
|
|
translated_modes = []
|
|
for elem in modes:
|
|
translated_modes.append(get_translation(locale, f"frontend.state_attributes.climate.preset_mode.{elem}"))
|
|
cur_mode = get_translation(locale, f"frontend.state_attributes.climate.preset_mode.{cur_mode}")
|
|
modes_res = "?".join(translated_modes)
|
|
else:
|
|
modes_res = "?".join(modes)
|
|
if modes:
|
|
modes_out += f"{heading}~{mode}~{cur_mode}~{modes_res}~"
|
|
|
|
return f"{entity_id}~{icon_id}~{icon_color}~{modes_out}"
|
|
|
|
|
|
|
|
case 'popupInSel' | 'input_select' | 'select' | 'media_player':
|
|
hatype = ha_entity_id.split(".")[0]
|
|
options = []
|
|
icon_color = 0
|
|
icon_color = ha_colors.get_entity_color(detail_type, state, attributes)
|
|
state = state
|
|
if hatype in ["input_select", "select"]:
|
|
options = attributes.get("options", [])
|
|
elif hatype == "light":
|
|
if options_list is not None:
|
|
options = options_list
|
|
else:
|
|
options = attributes.get("effect_list", [])[:15]
|
|
state = attributes.get("effect")
|
|
elif hatype == "media_player":
|
|
state = attributes.get("source", "")
|
|
options = attributes.get("source_list", [])
|
|
options = "?".join(options)
|
|
return f"{entity_id}~~{icon_color}~{hatype}~{state}~{options}~"
|
|
|
|
|
|
case 'popupTimer' | 'timer':
|
|
icon_color = ha_colors.get_entity_color("timer", state, attributes)
|
|
if state in ["idle", "paused"]:
|
|
editable = 1
|
|
if state == "paused":
|
|
time_remaining = attributes.get("remaining")
|
|
else:
|
|
time_remaining = attributes.get("duration")
|
|
min_remaining = time_remaining.split(":")[1]
|
|
sec_remaining = time_remaining.split(":")[2]
|
|
action1 = ""
|
|
action2 = "start"
|
|
action3 = ""
|
|
label1 = ""
|
|
label2 = get_translation(locale, "frontend.ui.card.timer.actions.start")
|
|
label3 = ""
|
|
else: #active
|
|
editable = 0
|
|
|
|
#update timer in a second
|
|
def update_time():
|
|
time.sleep(1)
|
|
out = detail_open(locale, detail_type, ha_entity_id, entity_id, msg_out_queue, sendTopic=sendTopic)
|
|
libs.panel_cmd.entityUpdateDetail(msg_out_queue, sendTopic, out)
|
|
tt = threading.Thread(target=update_time, args=())
|
|
tt.daemon = True
|
|
tt.start()
|
|
|
|
finishes_at = dp.parse(attributes.get("finishes_at"))
|
|
delta = finishes_at - datetime.datetime.now(datetime.timezone.utc)
|
|
hours, remainder = divmod(delta.total_seconds(), 3600)
|
|
minutes, seconds = divmod(remainder, 60)
|
|
min_remaining = int(minutes)
|
|
sec_remaining = int(seconds)
|
|
action1 = "pause"
|
|
action2 = "cancel"
|
|
action3 = "finish"
|
|
label1 = get_translation(locale, "frontend.ui.card.timer.actions.pause")
|
|
label2 = get_translation(locale, "frontend.ui.card.timer.actions.cancel")
|
|
label3 = get_translation(locale, "frontend.ui.card.timer.actions.finish")
|
|
return f'{entity_id}~~{icon_color}~{entity_id}~{min_remaining}~{sec_remaining}~{editable}~{action1}~{action2}~{action3}~{label1}~{label2}~{label3}'
|
|
case _:
|
|
logging.error("popup type %s not implemented", detail_type)
|
|
return "NotImplemented", None
|