mirror of
https://github.com/joBr99/nspanel-lovelace-ui.git
synced 2025-12-23 16:04:25 +01:00
Compare commits
43 Commits
dev
...
Armilar-pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a03f736ce | ||
|
|
8d7f6ffea5 | ||
|
|
93a6a7a88a | ||
|
|
3913b17596 | ||
|
|
d08e8eb40c | ||
|
|
c2d281658e | ||
|
|
108582cbfb | ||
|
|
b17db265f4 | ||
|
|
206739dcc5 | ||
|
|
adcb618a11 | ||
|
|
dfba3b6e84 | ||
|
|
2726859135 | ||
|
|
c0e20e6f25 | ||
|
|
148c2fc5a2 | ||
|
|
526f5e8946 | ||
|
|
8cd17b9d9a | ||
|
|
70ff46ab4b | ||
|
|
770348b07b | ||
|
|
4795cc23ad | ||
|
|
bae64dcee5 | ||
|
|
953a8d7110 | ||
|
|
3b5eaac976 | ||
|
|
6e28237ec5 | ||
|
|
b8c47948c3 | ||
|
|
0e8f9ad220 | ||
|
|
79e43e2740 | ||
|
|
c32d2958a6 | ||
|
|
3b7c934972 | ||
|
|
40f29d09c1 | ||
|
|
b601f2d860 | ||
|
|
5953d7c8dd | ||
|
|
e9859c0d32 | ||
|
|
4ad997515f | ||
|
|
ba637cf11e | ||
|
|
a4abcd1734 | ||
|
|
86bbd36813 | ||
|
|
c9dffc431c | ||
|
|
04ffd6257e | ||
|
|
4102f56cee | ||
|
|
6a62a6206a | ||
|
|
0bddceccfa | ||
|
|
09156fbc89 | ||
|
|
76d0075c7d |
@@ -1,10 +1,10 @@
|
|||||||
name: docs-ci
|
name: docs-ci
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- dev
|
||||||
paths:
|
paths:
|
||||||
- docs/*
|
- docs/*
|
||||||
- .github/workflows/docs.yml
|
- .github/workflows/docs.yml
|
||||||
@@ -23,13 +23,6 @@ jobs:
|
|||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
- run: pip install mkdocs-material mkdocs-video markdown-include mike
|
- run: pip install mkdocs-material mkdocs-video markdown-include mike
|
||||||
- run: cp HMI/README.md docs/hmi-serial-protocol.md
|
- run: cp HMI/README.md docs/hmi-serial-protocol.md
|
||||||
#- run: mkdocs gh-deploy --force
|
|
||||||
- run: git config --global user.name Docs deploy
|
- run: git config --global user.name Docs deploy
|
||||||
- run: git config --global user.email docs@dummy.bot.com
|
- run: git config --global user.email docs@dummy.bot.com
|
||||||
- run: mike deploy --push --update-aliases dev
|
- run: mike deploy --push --update-aliases dev
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
29
.github/workflows/docs-release.yml
vendored
Normal file
29
.github/workflows/docs-release.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: docs-ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- docs/*
|
||||||
|
- .github/workflows/docs-release.yml
|
||||||
|
- mkdocs.yml
|
||||||
|
- HMI/README.md
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
- run: pip install mkdocs-material mkdocs-video markdown-include mike
|
||||||
|
- run: cp HMI/README.md docs/hmi-serial-protocol.md
|
||||||
|
- run: git config --global user.name Docs deploy
|
||||||
|
- run: git config --global user.email docs@dummy.bot.com
|
||||||
|
- run: mike set-default stable
|
||||||
|
- run: mike deploy --push --update-aliases stable
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -2533,6 +2533,22 @@
|
|||||||
"zh-CN":"没有音乐可以控制",
|
"zh-CN":"没有音乐可以控制",
|
||||||
"zh-TW":"沒有音樂可以控制"
|
"zh-TW":"沒有音樂可以控制"
|
||||||
},
|
},
|
||||||
|
"on":{
|
||||||
|
"en-US":"On",
|
||||||
|
"de-DE":"An"
|
||||||
|
},
|
||||||
|
"off":{
|
||||||
|
"en-US":"Off",
|
||||||
|
"de-DE":"Aus"
|
||||||
|
},
|
||||||
|
"seek":{
|
||||||
|
"en-US":"Seek",
|
||||||
|
"de-DE":"Suchen"
|
||||||
|
},
|
||||||
|
"crossfade":{
|
||||||
|
"en-US":"Crossfade",
|
||||||
|
"de-DE":"Überblenden"
|
||||||
|
},
|
||||||
"speaker":{
|
"speaker":{
|
||||||
"en-US":"Speakerlist",
|
"en-US":"Speakerlist",
|
||||||
"de-DE":"Wiedergabegeräte",
|
"de-DE":"Wiedergabegeräte",
|
||||||
|
|||||||
@@ -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.54"
|
version: "4.7.72"
|
||||||
slug: nspanel-lovelace-ui
|
slug: nspanel-lovelace-ui
|
||||||
description: NSPanel Lovelace UI Addon
|
description: NSPanel Lovelace UI Addon
|
||||||
services:
|
services:
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ class EntitiesCard(HACard):
|
|||||||
result = f"{self.title}~{self.gen_nav()}"
|
result = f"{self.title}~{self.gen_nav()}"
|
||||||
for e in self.entities:
|
for e in self.entities:
|
||||||
result += e.render(cardType=self.type)
|
result += e.render(cardType=self.type)
|
||||||
libs.panel_cmd.entityUpd(self.panel.sendTopic, result)
|
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
|
||||||
|
|
||||||
class QRCard(HACard):
|
class QRCard(HACard):
|
||||||
def __init__(self, locale, config, panel):
|
def __init__(self, locale, config, panel):
|
||||||
@@ -311,7 +311,7 @@ class QRCard(HACard):
|
|||||||
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()
|
||||||
libs.panel_cmd.entityUpd(self.panel.sendTopic, result)
|
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
|
||||||
|
|
||||||
class PowerCard(HACard):
|
class PowerCard(HACard):
|
||||||
def __init__(self, locale, config, panel):
|
def __init__(self, locale, config, panel):
|
||||||
@@ -329,7 +329,7 @@ class PowerCard(HACard):
|
|||||||
# if isinstance(speed, str):
|
# if isinstance(speed, str):
|
||||||
# speed = apis.ha_api.render_template(speed)
|
# speed = apis.ha_api.render_template(speed)
|
||||||
result += f"~{speed}"
|
result += f"~{speed}"
|
||||||
libs.panel_cmd.entityUpd(self.panel.sendTopic, result)
|
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
|
||||||
|
|
||||||
class MediaCard(HACard):
|
class MediaCard(HACard):
|
||||||
def __init__(self, locale, config, panel):
|
def __init__(self, locale, config, panel):
|
||||||
@@ -366,7 +366,7 @@ class MediaCard(HACard):
|
|||||||
for e in self.entities[1:]:
|
for e in self.entities[1:]:
|
||||||
button_str += e.render()
|
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}"
|
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.sendTopic, result)
|
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
|
||||||
|
|
||||||
class ClimateCard(HACard):
|
class ClimateCard(HACard):
|
||||||
def __init__(self, locale, config, panel):
|
def __init__(self, locale, config, panel):
|
||||||
@@ -375,9 +375,8 @@ class ClimateCard(HACard):
|
|||||||
def render(self):
|
def render(self):
|
||||||
main_entity = self.entities[0]
|
main_entity = self.entities[0]
|
||||||
|
|
||||||
#TODO: temp unit
|
temp_unit = self.panel.temp_unit
|
||||||
temp_unit = "celsius"
|
if temp_unit == "celsius":
|
||||||
if(temp_unit == "celsius"):
|
|
||||||
temperature_unit_icon = get_icon_char("temperature-celsius")
|
temperature_unit_icon = get_icon_char("temperature-celsius")
|
||||||
temperature_unit = "°C"
|
temperature_unit = "°C"
|
||||||
|
|
||||||
@@ -411,7 +410,12 @@ class ClimateCard(HACard):
|
|||||||
|
|
||||||
min_temp = int(main_entity.attributes.get("min_temp", 0)*10)
|
min_temp = int(main_entity.attributes.get("min_temp", 0)*10)
|
||||||
max_temp = int(main_entity.attributes.get("max_temp", 0)*10)
|
max_temp = int(main_entity.attributes.get("max_temp", 0)*10)
|
||||||
step_temp = int(main_entity.attributes.get("target_temp_step", 0.5)*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_list = []
|
||||||
icon_res = ""
|
icon_res = ""
|
||||||
|
|
||||||
@@ -460,8 +464,8 @@ class ClimateCard(HACard):
|
|||||||
if any(x in ["preset_modes", "swing_modes", "fan_modes"] for x in main_entity.attributes):
|
if any(x in ["preset_modes", "swing_modes", "fan_modes"] for x in main_entity.attributes):
|
||||||
detailPage = "0"
|
detailPage = "0"
|
||||||
|
|
||||||
result = f"{self.title}~{self.gen_nav()}~{main_entity.entity_id}~{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}"
|
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.sendTopic, result)
|
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
|
||||||
|
|
||||||
class AlarmCard(HACard):
|
class AlarmCard(HACard):
|
||||||
def __init__(self, locale, config, panel):
|
def __init__(self, locale, config, panel):
|
||||||
@@ -531,7 +535,7 @@ class AlarmCard(HACard):
|
|||||||
if len(supported_modes) < 4:
|
if len(supported_modes) < 4:
|
||||||
arm_buttons += "~"*((4-len(supported_modes))*2)
|
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}"
|
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.msg_out_queue, self.panel.sendTopic, result)
|
||||||
|
|
||||||
|
|
||||||
class UnlockCard(HACard):
|
class UnlockCard(HACard):
|
||||||
@@ -544,7 +548,7 @@ class UnlockCard(HACard):
|
|||||||
icon = get_icon_char("lock")
|
icon = get_icon_char("lock")
|
||||||
supported_modes = ["cardUnlock-unlock"]
|
supported_modes = ["cardUnlock-unlock"]
|
||||||
|
|
||||||
entity_id = self.config.get("entity")
|
entity_id = self.config.get("destination")
|
||||||
|
|
||||||
# add padding to arm buttons
|
# add padding to arm buttons
|
||||||
arm_buttons = ""
|
arm_buttons = ""
|
||||||
@@ -555,7 +559,7 @@ class UnlockCard(HACard):
|
|||||||
numpad = "enable"
|
numpad = "enable"
|
||||||
|
|
||||||
result = f"{self.title}~{self.gen_nav()}~{entity_id}{arm_buttons}~{icon}~{color}~{numpad}~disable~"
|
result = f"{self.title}~{self.gen_nav()}~{entity_id}{arm_buttons}~{icon}~{color}~{numpad}~disable~"
|
||||||
libs.panel_cmd.entityUpd(self.panel.sendTopic, result)
|
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
|
||||||
|
|
||||||
class Screensaver(HACard):
|
class Screensaver(HACard):
|
||||||
def __init__(self, locale, config, panel):
|
def __init__(self, locale, config, panel):
|
||||||
@@ -583,7 +587,7 @@ class Screensaver(HACard):
|
|||||||
result = ""
|
result = ""
|
||||||
for e in self.entities:
|
for e in self.entities:
|
||||||
result += e.render(cardType=self.type)
|
result += e.render(cardType=self.type)
|
||||||
libs.panel_cmd.weatherUpdate(self.panel.sendTopic, result[1:])
|
libs.panel_cmd.weatherUpdate(self.panel.msg_out_queue, self.panel.sendTopic, result[1:])
|
||||||
|
|
||||||
statusUpdateResult = ""
|
statusUpdateResult = ""
|
||||||
icon1font = ""
|
icon1font = ""
|
||||||
@@ -601,7 +605,7 @@ class Screensaver(HACard):
|
|||||||
else:
|
else:
|
||||||
statusUpdateResult += "~~"
|
statusUpdateResult += "~~"
|
||||||
|
|
||||||
libs.panel_cmd.statusUpdate(self.panel.sendTopic, f"{statusUpdateResult}~{icon1font}~{icon2font}")
|
libs.panel_cmd.statusUpdate(self.panel.msg_out_queue, self.panel.sendTopic, f"{statusUpdateResult}~{icon1font}~{icon2font}")
|
||||||
|
|
||||||
|
|
||||||
def card_factory(locale, settings, panel):
|
def card_factory(locale, settings, panel):
|
||||||
@@ -625,7 +629,7 @@ def card_factory(locale, settings, panel):
|
|||||||
return "NotImplemented", None
|
return "NotImplemented", None
|
||||||
return card.iid, card
|
return card.iid, card
|
||||||
|
|
||||||
def detail_open(locale, detail_type, ha_entity_id, entity_id, sendTopic=None):
|
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)
|
data = libs.home_assistant.get_entity_data(ha_entity_id)
|
||||||
if data:
|
if data:
|
||||||
state = data.get("state")
|
state = data.get("state")
|
||||||
@@ -762,9 +766,51 @@ def detail_open(locale, detail_type, ha_entity_id, entity_id, sendTopic=None):
|
|||||||
|
|
||||||
return f'{entity_id}~~{icon_color}~{switch_val}~{speed}~{speedMax}~{speed_translation}~{preset_mode}~{preset_modes}'
|
return f'{entity_id}~~{icon_color}~{switch_val}~{speed}~{speedMax}~{speed_translation}~{preset_mode}~{preset_modes}'
|
||||||
case 'popupThermo' | 'climate':
|
case 'popupThermo' | 'climate':
|
||||||
print(f"not implemented {detail_type}")
|
icon_id = ha_icons.get_icon_ha("climate", state)
|
||||||
case 'popupInSel' | 'input_select' | 'select':
|
icon_color = ha_colors.get_entity_color("climate", state, attributes)
|
||||||
print(f"not implemented {detail_type}")
|
|
||||||
|
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':
|
case 'popupTimer' | 'timer':
|
||||||
icon_color = ha_colors.get_entity_color("timer", state, attributes)
|
icon_color = ha_colors.get_entity_color("timer", state, attributes)
|
||||||
if state in ["idle", "paused"]:
|
if state in ["idle", "paused"]:
|
||||||
@@ -787,8 +833,8 @@ def detail_open(locale, detail_type, ha_entity_id, entity_id, sendTopic=None):
|
|||||||
#update timer in a second
|
#update timer in a second
|
||||||
def update_time():
|
def update_time():
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
out = detail_open(locale, detail_type, ha_entity_id, entity_id, sendTopic=sendTopic)
|
out = detail_open(locale, detail_type, ha_entity_id, entity_id, msg_out_queue, sendTopic=sendTopic)
|
||||||
libs.panel_cmd.entityUpdateDetail(sendTopic, out)
|
libs.panel_cmd.entityUpdateDetail(msg_out_queue, sendTopic, out)
|
||||||
tt = threading.Thread(target=update_time, args=())
|
tt = threading.Thread(target=update_time, args=())
|
||||||
tt.daemon = True
|
tt.daemon = True
|
||||||
tt.start()
|
tt.start()
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, scr
|
|||||||
else:
|
else:
|
||||||
return dimmode, dimValueNormal
|
return dimmode, dimValueNormal
|
||||||
|
|
||||||
def handle_buttons(entity_id, btype, value):
|
def handle_buttons(entity_id, btype, value, entity_config=None):
|
||||||
match btype:
|
match btype:
|
||||||
case 'button':
|
case 'button':
|
||||||
button_press(entity_id, value)
|
button_press(entity_id, value)
|
||||||
@@ -156,19 +156,20 @@ def handle_buttons(entity_id, btype, value):
|
|||||||
}
|
}
|
||||||
call_ha_service(entity_id, f"alarm_{btype}", service_data=service_data)
|
call_ha_service(entity_id, f"alarm_{btype}", service_data=service_data)
|
||||||
case 'mode-preset_modes' | 'mode-swing_modes' | 'mode-fan_modes':
|
case 'mode-preset_modes' | 'mode-swing_modes' | 'mode-fan_modes':
|
||||||
|
attr = libs.home_assistant.get_entity_data(entity_id).get('attributes', [])
|
||||||
mapping = {
|
mapping = {
|
||||||
'mode-preset_modes': 'preset_modes',
|
'mode-preset_modes': 'preset_modes',
|
||||||
'mode-swing_modes': 'swing_modes',
|
'mode-swing_modes': 'swing_modes',
|
||||||
'mode-fan_modes': 'fan_mode'
|
'mode-fan_modes': 'fan_modes'
|
||||||
}
|
}
|
||||||
if btype in mapping:
|
if btype in mapping:
|
||||||
modes = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get(mapping[btype], [])
|
modes = attr.get(mapping[btype], [])
|
||||||
if modes:
|
if modes:
|
||||||
mode = modes[int(value)]
|
mode = modes[int(value)]
|
||||||
service_data = {
|
service_data = {
|
||||||
mapping[btype]: mode
|
mapping[btype][:-1]: mode
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, f"set_{mapping[btype]}", service_data=service_data)
|
call_ha_service(entity_id, f"set_{mapping[btype][:-1]}", service_data=service_data)
|
||||||
case 'mode-input_select' | 'mode-select':
|
case 'mode-input_select' | 'mode-select':
|
||||||
options = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get("options", [])
|
options = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get("options", [])
|
||||||
if options:
|
if options:
|
||||||
@@ -186,7 +187,7 @@ def handle_buttons(entity_id, btype, value):
|
|||||||
}
|
}
|
||||||
call_ha_service(entity_id, "select_source", service_data=service_data)
|
call_ha_service(entity_id, "select_source", service_data=service_data)
|
||||||
case 'mode-light':
|
case 'mode-light':
|
||||||
options = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get("effect_list", [])
|
options = entity_config.get("effectList", libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get("effect_list", []))
|
||||||
if options:
|
if options:
|
||||||
option = options[int(value)]
|
option = options[int(value)]
|
||||||
service_data = {
|
service_data = {
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ def on_message(ws, message):
|
|||||||
for template, template_cache_entry in template_cache.items():
|
for template, template_cache_entry in template_cache.items():
|
||||||
if entity_id in template_cache_entry.get("listener-entities", []):
|
if entity_id in template_cache_entry.get("listener-entities", []):
|
||||||
cache_template(template)
|
cache_template(template)
|
||||||
|
elif json_msg["type"] == "event" and json_msg["event"]["event_type"] == "esphome.nspanel.data":
|
||||||
|
nspanel_data_callback(json_msg["event"]["data"]["device_id"], json_msg["event"]["data"]["CustomRecv"])
|
||||||
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)
|
||||||
@@ -143,6 +144,15 @@ def subscribe_to_events():
|
|||||||
}
|
}
|
||||||
send_message(json.dumps(msg))
|
send_message(json.dumps(msg))
|
||||||
|
|
||||||
|
def subscribe_to_nspanel_events(nsp_callback):
|
||||||
|
global next_id, nspanel_data_callback
|
||||||
|
nspanel_data_callback = nsp_callback
|
||||||
|
msg = {
|
||||||
|
"id": next_id,
|
||||||
|
"type": "subscribe_events",
|
||||||
|
"event_type": "esphome.nspanel.data"
|
||||||
|
}
|
||||||
|
send_message(json.dumps(msg))
|
||||||
|
|
||||||
def _get_all_states():
|
def _get_all_states():
|
||||||
global next_id, request_all_states_id
|
global next_id, request_all_states_id
|
||||||
@@ -158,6 +168,10 @@ def send_entity_update(entity_id):
|
|||||||
global on_ha_update
|
global on_ha_update
|
||||||
on_ha_update(entity_id)
|
on_ha_update(entity_id)
|
||||||
|
|
||||||
|
def nspanel_data_callback(device_id, msg):
|
||||||
|
global nspanel_data_callback
|
||||||
|
nspanel_data_callback(device_id, msg)
|
||||||
|
|
||||||
def call_service(entity_name: str, domain: str, service: str, service_data: dict) -> bool:
|
def call_service(entity_name: str, domain: str, service: str, service_data: dict) -> bool:
|
||||||
global next_id
|
global next_id
|
||||||
try:
|
try:
|
||||||
@@ -177,6 +191,22 @@ def call_service(entity_name: str, domain: str, service: str, service_data: dict
|
|||||||
logging.exception("Failed to call Home Assisatant service.")
|
logging.exception("Failed to call Home Assisatant service.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def send_msg_to_panel(service: str, service_data: dict) -> bool:
|
||||||
|
global next_id
|
||||||
|
try:
|
||||||
|
msg = {
|
||||||
|
"id": next_id,
|
||||||
|
"type": "call_service",
|
||||||
|
"domain": "esphome",
|
||||||
|
"service": service,
|
||||||
|
"service_data": service_data,
|
||||||
|
}
|
||||||
|
send_message(json.dumps(msg))
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("Failed to call Home Assisatant service.")
|
||||||
|
return False
|
||||||
|
|
||||||
def execute_script(entity_name: str, domain: str, service: str, service_data: dict) -> str:
|
def execute_script(entity_name: str, domain: str, service: str, service_data: dict) -> str:
|
||||||
global next_id, response_buffer
|
global next_id, response_buffer
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,46 +1,43 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
def init(mqtt_client_from_manager):
|
def custom_send(msg_out_queue, topic, msg):
|
||||||
global mqtt_client
|
msg_out_queue.put((topic, msg))
|
||||||
mqtt_client = mqtt_client_from_manager
|
logging.debug("Sent Message to NsPanel (%s): %s", topic, msg)
|
||||||
|
|
||||||
|
|
||||||
def custom_send(topic, msg):
|
def page_type(msg_out_queue, topic, target_page):
|
||||||
global mqtt_client
|
if target_page == "cardUnlock":
|
||||||
mqtt_client.publish(topic, msg)
|
target_page = "cardAlarm"
|
||||||
logging.debug("Sent Message to NsPanel (%s): %s", topic, msg)
|
custom_send(msg_out_queue, topic, f"pageType~{target_page}")
|
||||||
|
|
||||||
|
|
||||||
def page_type(topic, target_page):
|
def send_time(msg_out_queue, topic, time, addTimeText=""):
|
||||||
if target_page == "cardUnlock":
|
custom_send(msg_out_queue, topic, f"time~{time}~{addTimeText}")
|
||||||
target_page = "cardAlarm"
|
|
||||||
custom_send(topic, f"pageType~{target_page}")
|
|
||||||
|
def send_date(msg_out_queue, topic, date):
|
||||||
|
custom_send(msg_out_queue, topic, f"date~{date}")
|
||||||
def send_time(topic, time, addTimeText=""):
|
|
||||||
custom_send(topic, f"time~{time}~{addTimeText}")
|
|
||||||
|
def entityUpd(msg_out_queue, topic, data):
|
||||||
|
custom_send(msg_out_queue, topic, f"entityUpd~{data}")
|
||||||
def send_date(topic, date):
|
|
||||||
custom_send(topic, f"date~{date}")
|
def weatherUpdate(msg_out_queue, topic, data):
|
||||||
|
custom_send(msg_out_queue, topic, f"weatherUpdate~{data}")
|
||||||
|
|
||||||
def entityUpd(topic, data):
|
def timeout(msg_out_queue, topic, timeout):
|
||||||
custom_send(topic, f"entityUpd~{data}")
|
custom_send(msg_out_queue, topic, f"timeout~{timeout}")
|
||||||
|
|
||||||
def weatherUpdate(topic, data):
|
def dimmode(msg_out_queue, topic, dimValue, dimValueNormal, backgroundColor, fontColor, featExperimentalSliders):
|
||||||
custom_send(topic, f"weatherUpdate~{data}")
|
if dimValue==dimValueNormal:
|
||||||
|
dimValue=dimValue-1
|
||||||
def timeout(topic, timeout):
|
custom_send(msg_out_queue, topic, f"dimmode~{dimValue}~{dimValueNormal}~{backgroundColor}~{fontColor}~{featExperimentalSliders}")
|
||||||
custom_send(topic, f"timeout~{timeout}")
|
|
||||||
|
def entityUpdateDetail(msg_out_queue, topic, data):
|
||||||
def dimmode(topic, dimValue, dimValueNormal, backgroundColor, fontColor, featExperimentalSliders):
|
custom_send(msg_out_queue, topic, f"entityUpdateDetail~{data}")
|
||||||
if dimValue==dimValueNormal:
|
|
||||||
dimValue=dimValue-1
|
def entityUpdateDetail2(msg_out_queue, topic, data):
|
||||||
custom_send(topic, f"dimmode~{dimValue}~{dimValueNormal}~{backgroundColor}~{fontColor}~{featExperimentalSliders}")
|
custom_send(msg_out_queue, topic, f"entityUpdateDetail2~{data}")
|
||||||
|
|
||||||
def entityUpdateDetail(topic, data):
|
def statusUpdate(msg_out_queue, topic, data):
|
||||||
custom_send(topic, f"entityUpdateDetail~{data}")
|
custom_send(msg_out_queue, topic, f"statusUpdate~{data}")
|
||||||
|
|
||||||
def statusUpdate(topic, data):
|
|
||||||
custom_send(topic, f"statusUpdate~{data}")
|
|
||||||
|
|||||||
@@ -1,224 +1,197 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import logging
|
import logging
|
||||||
import paho.mqtt.client as mqtt
|
import time
|
||||||
import time
|
import subprocess
|
||||||
import json
|
import libs.home_assistant
|
||||||
import subprocess
|
import libs.panel_cmd
|
||||||
import libs.home_assistant
|
import yaml
|
||||||
import libs.panel_cmd
|
from panel import LovelaceUIPanel
|
||||||
import yaml
|
import os
|
||||||
from uuid import getnode as get_mac
|
import threading
|
||||||
from panel import LovelaceUIPanel
|
from watchdog.events import FileSystemEventHandler
|
||||||
import os
|
from watchdog.observers import Observer
|
||||||
import threading
|
import signal
|
||||||
from watchdog.events import FileSystemEventHandler
|
import sys
|
||||||
from watchdog.observers import Observer
|
from queue import Queue
|
||||||
import signal
|
from mqtt import MqttManager
|
||||||
import sys
|
|
||||||
from queue import Queue
|
logging.getLogger("watchdog").propagate = False
|
||||||
|
|
||||||
logging.getLogger("watchdog").propagate = False
|
settings = {}
|
||||||
|
panels = {}
|
||||||
settings = {}
|
panel_in_queues = {}
|
||||||
panels = {}
|
panel_out_queue = Queue(maxsize=20)
|
||||||
panel_queues = {}
|
last_settings_file_mtime = 0
|
||||||
last_settings_file_mtime = 0
|
mqtt_connect_time = 0
|
||||||
mqtt_connect_time = 0
|
has_sent_reload_command = False
|
||||||
has_sent_reload_command = False
|
|
||||||
mqtt_client_name = "NSPanelLovelaceManager_" + str(get_mac())
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
client = mqtt.Client(mqtt_client_name)
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
def on_ha_update(entity_id):
|
||||||
|
global panel_in_queues
|
||||||
def on_connect(client, userdata, flags, rc):
|
# send HA updates to all panels
|
||||||
global settings
|
for queue in panel_in_queues.values():
|
||||||
logging.info("Connected to MQTT Server")
|
queue.put(("HA:", entity_id))
|
||||||
# subscribe to panelRecvTopic of each panel
|
|
||||||
for settings_panel in settings["nspanels"].values():
|
def on_ha_panel_event(device_id, msg):
|
||||||
client.subscribe(settings_panel["panelRecvTopic"])
|
global panel_in_queues
|
||||||
|
|
||||||
def on_ha_update(entity_id):
|
if device_id in panel_in_queues.keys():
|
||||||
global panel_queues
|
queue = panel_in_queues[device_id]
|
||||||
for queue in panel_queues.values():
|
queue.put(("MQTT:", msg))
|
||||||
queue.put(("HA:", entity_id))
|
|
||||||
|
def process_output_to_panel():
|
||||||
def on_message(client, userdata, msg):
|
while True:
|
||||||
global panel_queues
|
msg = panel_out_queue.get()
|
||||||
try:
|
|
||||||
if msg.payload.decode() == "":
|
#client.publish(msg[0], msg[1])
|
||||||
return
|
#apis.ha_api.call_service(service="esphome/" + self._api_panel_name + "_nspanelui_api_call", command=2, data=msg)
|
||||||
parts = msg.topic.split('/')
|
service = msg[0] + "_nspanelui_api_call"
|
||||||
if msg.topic in panel_queues.keys():
|
service_data = {
|
||||||
data = json.loads(msg.payload.decode('utf-8'))
|
"data": msg[1],
|
||||||
if "CustomRecv" in data:
|
"command":2
|
||||||
queue = panel_queues[msg.topic]
|
}
|
||||||
queue.put(("MQTT:", data["CustomRecv"]))
|
libs.home_assistant.send_msg_to_panel(
|
||||||
else:
|
service = service,
|
||||||
logging.debug("Received unhandled message on topic: %s", msg.topic)
|
service_data = service_data
|
||||||
|
)
|
||||||
except Exception: # pylint: disable=broad-exception-caught
|
|
||||||
logging.exception("Something went wrong during processing of message:")
|
|
||||||
try:
|
def connect():
|
||||||
logging.error(msg.payload.decode('utf-8'))
|
global settings, panel_out_queue
|
||||||
except: # pylint: disable=bare-except
|
if "mqtt_server" in settings and not "use_ha_api" in settings:
|
||||||
logging.error(
|
MqttManager(settings, panel_out_queue, panel_in_queues)
|
||||||
"Something went wrong when processing the exception message, couldn't decode payload to utf-8.")
|
else:
|
||||||
|
logging.info("MQTT values not configured, will not connect.")
|
||||||
def get_config_file():
|
|
||||||
CONFIG_FILE = os.getenv('CONFIG_FILE')
|
# MQTT Connected, start APIs if configured
|
||||||
if not CONFIG_FILE:
|
if settings["home_assistant_address"] != "" and settings["home_assistant_token"] != "":
|
||||||
CONFIG_FILE = './panels.yaml'
|
libs.home_assistant.init(settings, on_ha_update)
|
||||||
return CONFIG_FILE
|
libs.home_assistant.connect()
|
||||||
|
else:
|
||||||
def get_config(file):
|
logging.info("Home Assistant values not configured, will not connect.")
|
||||||
global settings
|
|
||||||
|
while not libs.home_assistant.ws_connected:
|
||||||
try:
|
time.sleep(1)
|
||||||
with open(file, 'r', encoding="utf8") as file:
|
if settings.get("use_ha_api"):
|
||||||
settings = yaml.safe_load(file)
|
libs.home_assistant.subscribe_to_nspanel_events(on_ha_panel_event)
|
||||||
except yaml.YAMLError as exc:
|
send_to_panel_thread = threading.Thread(target=process_output_to_panel, args=())
|
||||||
print ("Error while parsing YAML file:")
|
send_to_panel_thread.daemon = True
|
||||||
if hasattr(exc, 'problem_mark'):
|
send_to_panel_thread.start()
|
||||||
if exc.context != None:
|
|
||||||
print (' parser says\n' + str(exc.problem_mark) + '\n ' +
|
def setup_panels():
|
||||||
str(exc.problem) + ' ' + str(exc.context) +
|
global settings, panel_in_queues
|
||||||
'\nPlease correct data and retry.')
|
# Create NsPanel object
|
||||||
else:
|
for name, settings_panel in settings["nspanels"].items():
|
||||||
print (' parser says\n' + str(exc.problem_mark) + '\n ' +
|
if "timeZone" not in settings_panel:
|
||||||
str(exc.problem) + '\nPlease correct data and retry.')
|
settings_panel["timeZone"] = settings.get("timeZone", "Europe/Berlin")
|
||||||
else:
|
if "locale" not in settings_panel:
|
||||||
print ("Something went wrong while parsing yaml file")
|
settings_panel["timezone"] = settings.get("locale", "en_US")
|
||||||
return False
|
if "hiddenCards" not in settings_panel:
|
||||||
|
settings_panel["hiddenCards"] = settings.get("hiddenCards", [])
|
||||||
if not settings.get("mqtt_username"):
|
|
||||||
settings["mqtt_username"] = os.getenv('MQTT_USER')
|
msg_in_queue = Queue(maxsize=20)
|
||||||
if not settings.get("mqtt_password"):
|
panel_in_queues[settings_panel["panelRecvTopic"]] = msg_in_queue
|
||||||
settings["mqtt_password"] = os.getenv('MQTT_PASS')
|
panel_thread = threading.Thread(target=panel_thread_target, args=(msg_in_queue, name, settings_panel, panel_out_queue))
|
||||||
if not settings.get("mqtt_port"):
|
panel_thread.daemon = True
|
||||||
settings["mqtt_port"] = os.getenv('MQTT_PORT')
|
panel_thread.start()
|
||||||
if not settings.get("mqtt_server"):
|
|
||||||
settings["mqtt_server"] = os.getenv('MQTT_SERVER')
|
def panel_thread_target(queue_in, name, settings_panel, queue_out):
|
||||||
|
panel = LovelaceUIPanel(name, settings_panel, queue_out)
|
||||||
|
while True:
|
||||||
settings["is_addon"] = False
|
msg = queue_in.get()
|
||||||
|
if msg[0] == "MQTT:":
|
||||||
if not settings.get("home_assistant_token"):
|
panel.customrecv_event_callback(msg[1])
|
||||||
st = os.getenv('SUPERVISOR_TOKEN')
|
elif msg[0] == "HA:":
|
||||||
if st and "home_assistant_token" not in settings and "home_assistant_address" not in settings:
|
panel.ha_event_callback(msg[1])
|
||||||
settings["home_assistant_token"] = st
|
|
||||||
settings["home_assistant_address"] = "http://supervisor"
|
def get_config_file():
|
||||||
settings["is_addon"] = True
|
CONFIG_FILE = os.getenv('CONFIG_FILE')
|
||||||
return True
|
if not CONFIG_FILE:
|
||||||
|
CONFIG_FILE = './panels.yaml'
|
||||||
def connect():
|
return CONFIG_FILE
|
||||||
global settings, home_assistant, client
|
|
||||||
client.on_connect = on_connect
|
def get_config(file):
|
||||||
client.on_message = on_message
|
global settings
|
||||||
client.username_pw_set(
|
|
||||||
settings["mqtt_username"], settings["mqtt_password"])
|
try:
|
||||||
# Wait for connection
|
with open(file, 'r', encoding="utf8") as file:
|
||||||
connection_return_code = 0
|
settings = yaml.safe_load(file)
|
||||||
mqtt_server = settings["mqtt_server"]
|
except yaml.YAMLError as exc:
|
||||||
mqtt_port = int(settings["mqtt_port"])
|
print ("Error while parsing YAML file:")
|
||||||
logging.info("Connecting to %s:%i as %s",
|
if hasattr(exc, 'problem_mark'):
|
||||||
mqtt_server, mqtt_port, mqtt_client_name)
|
if exc.context != None:
|
||||||
while True:
|
print (' parser says\n' + str(exc.problem_mark) + '\n ' +
|
||||||
try:
|
str(exc.problem) + ' ' + str(exc.context) +
|
||||||
client.connect(mqtt_server, mqtt_port, 5)
|
'\nPlease correct data and retry.')
|
||||||
break # Connection call did not raise exception, connection is sucessfull
|
else:
|
||||||
except: # pylint: disable=bare-except
|
print (' parser says\n' + str(exc.problem_mark) + '\n ' +
|
||||||
logging.exception(
|
str(exc.problem) + '\nPlease correct data and retry.')
|
||||||
"Failed to connect to MQTT %s:%i. Will try again in 10 seconds. Code: %s", mqtt_server, mqtt_port, connection_return_code)
|
else:
|
||||||
time.sleep(10.)
|
print ("Something went wrong while parsing yaml file")
|
||||||
|
return False
|
||||||
# MQTT Connected, start APIs if configured
|
|
||||||
if settings["home_assistant_address"] != "" and settings["home_assistant_token"] != "":
|
if not settings.get("mqtt_username"):
|
||||||
libs.home_assistant.init(settings, on_ha_update)
|
settings["mqtt_username"] = os.getenv('MQTT_USER')
|
||||||
libs.home_assistant.connect()
|
if not settings.get("mqtt_password"):
|
||||||
else:
|
settings["mqtt_password"] = os.getenv('MQTT_PASS')
|
||||||
logging.info("Home Assistant values not configured, will not connect.")
|
if not settings.get("mqtt_port"):
|
||||||
|
settings["mqtt_port"] = os.getenv('MQTT_PORT')
|
||||||
libs.panel_cmd.init(client)
|
if not settings.get("mqtt_server"):
|
||||||
|
settings["mqtt_server"] = os.getenv('MQTT_SERVER')
|
||||||
setup_panels()
|
|
||||||
|
|
||||||
def loop():
|
settings["is_addon"] = False
|
||||||
global client
|
|
||||||
# Loop MQTT
|
if not settings.get("home_assistant_token"):
|
||||||
client.loop_forever()
|
st = os.getenv('SUPERVISOR_TOKEN')
|
||||||
|
if st and "home_assistant_token" not in settings and "home_assistant_address" not in settings:
|
||||||
def setup_panels():
|
settings["home_assistant_token"] = st
|
||||||
global settings, panel_queues
|
settings["home_assistant_address"] = "http://supervisor"
|
||||||
# Create NsPanel object
|
settings["is_addon"] = True
|
||||||
for name, settings_panel in settings["nspanels"].items():
|
return True
|
||||||
if "timeZone" not in settings_panel:
|
|
||||||
settings_panel["timeZone"] = settings.get("timeZone", "Europe/Berlin")
|
def config_watch():
|
||||||
if "locale" not in settings_panel:
|
class ConfigChangeEventHandler(FileSystemEventHandler):
|
||||||
settings_panel["timezone"] = settings.get("locale", "en_US")
|
def __init__(self, base_paths):
|
||||||
if "hiddenCards" not in settings_panel:
|
self.base_paths = base_paths
|
||||||
settings_panel["hiddenCards"] = settings.get("hiddenCards", [])
|
|
||||||
|
def dispatch(self, event):
|
||||||
#panels[name] = LovelaceUIPanel(name, settings_panel)
|
for base_path in self.base_paths:
|
||||||
|
if event.src_path.endswith(base_path):
|
||||||
mqtt_queue = Queue(maxsize=20)
|
super(ConfigChangeEventHandler, self).dispatch(event)
|
||||||
panel_queues[settings_panel["panelRecvTopic"]] = mqtt_queue
|
return
|
||||||
panel_thread = threading.Thread(target=panel_thread_target, args=(mqtt_queue, name, settings_panel))
|
|
||||||
panel_thread.daemon = True
|
def on_modified(self, event):
|
||||||
|
logging.info('Modification detected. Reloading panels.')
|
||||||
panel_thread.start()
|
pid = os.getpid()
|
||||||
|
os.kill(pid, signal.SIGTERM)
|
||||||
def panel_thread_target(queue, name, settings_panel):
|
|
||||||
panel = LovelaceUIPanel(name, settings_panel)
|
logging.info('Watching for changes in config file')
|
||||||
while True:
|
project_files = []
|
||||||
msg = queue.get()
|
project_files.append(get_config_file())
|
||||||
#print(msg)
|
handler = ConfigChangeEventHandler(project_files)
|
||||||
if msg[0] == "MQTT:":
|
observer = Observer()
|
||||||
panel.customrecv_event_callback(msg[1])
|
observer.schedule(handler, path=os.path.dirname(get_config_file()), recursive=True)
|
||||||
elif msg[0] == "HA:":
|
observer.start()
|
||||||
panel.ha_event_callback(msg[1])
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
def signal_handler(signum, frame):
|
||||||
|
logging.info(f"Received signal {signum}. Initiating restart...")
|
||||||
|
python = sys.executable
|
||||||
|
os.execl(python, python, *sys.argv)
|
||||||
|
|
||||||
def config_watch():
|
if __name__ == '__main__':
|
||||||
class ConfigChangeEventHandler(FileSystemEventHandler):
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
def __init__(self, base_paths):
|
threading.Thread(target=config_watch).start()
|
||||||
self.base_paths = base_paths
|
if (get_config(get_config_file())):
|
||||||
|
connect()
|
||||||
def dispatch(self, event):
|
setup_panels()
|
||||||
for base_path in self.base_paths:
|
|
||||||
if event.src_path.endswith(base_path):
|
# main thread sleep forever
|
||||||
super(ConfigChangeEventHandler, self).dispatch(event)
|
while True:
|
||||||
return
|
time.sleep(100)
|
||||||
|
else:
|
||||||
def on_modified(self, event):
|
while True:
|
||||||
logging.info('Modification detected. Reloading panels.')
|
|
||||||
pid = os.getpid()
|
|
||||||
os.kill(pid, signal.SIGTERM)
|
|
||||||
|
|
||||||
logging.info('Watching for changes in config file')
|
|
||||||
project_files = []
|
|
||||||
project_files.append(get_config_file())
|
|
||||||
handler = ConfigChangeEventHandler(project_files)
|
|
||||||
observer = Observer()
|
|
||||||
observer.schedule(handler, path=os.path.dirname(get_config_file()), recursive=True)
|
|
||||||
observer.start()
|
|
||||||
while True:
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
def signal_handler(signum, frame):
|
|
||||||
logging.info(f"Received signal {signum}. Initiating restart...")
|
|
||||||
python = sys.executable
|
|
||||||
os.execl(python, python, *sys.argv)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
|
||||||
threading.Thread(target=config_watch).start()
|
|
||||||
if (get_config(get_config_file())):
|
|
||||||
connect()
|
|
||||||
loop()
|
|
||||||
else:
|
|
||||||
while True:
|
|
||||||
time.sleep(100)
|
time.sleep(100)
|
||||||
68
nspanel-lovelace-ui/rootfs/usr/bin/mqtt-manager/mqtt.py
Normal file
68
nspanel-lovelace-ui/rootfs/usr/bin/mqtt-manager/mqtt.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
from uuid import getnode as get_mac
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
class MqttManager:
|
||||||
|
def __init__(self, settings, msg_in_queue, msg_out_queue_list):
|
||||||
|
mqtt_client_name = "NSPanelLovelaceManager_" + str(get_mac())
|
||||||
|
self.client = mqtt.Client(mqtt_client_name)
|
||||||
|
self.msg_in_queue = msg_in_queue
|
||||||
|
self.msg_out_queue_list = msg_out_queue_list
|
||||||
|
self.settings = settings
|
||||||
|
|
||||||
|
self.client.on_connect = self.on_mqtt_connect
|
||||||
|
self.client.on_message = self.on_mqtt_message
|
||||||
|
self.client.username_pw_set(
|
||||||
|
settings["mqtt_username"], settings["mqtt_password"])
|
||||||
|
# Wait for connection
|
||||||
|
connection_return_code = 0
|
||||||
|
mqtt_server = settings["mqtt_server"]
|
||||||
|
mqtt_port = int(settings["mqtt_port"])
|
||||||
|
logging.info("Connecting to %s:%i as %s",
|
||||||
|
mqtt_server, mqtt_port, mqtt_client_name)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self.client.connect(mqtt_server, mqtt_port, 5)
|
||||||
|
break # Connection call did not raise exception, connection is sucessfull
|
||||||
|
except: # pylint: disable=bare-except
|
||||||
|
logging.exception(
|
||||||
|
"Failed to connect to MQTT %s:%i. Will try again in 10 seconds. Code: %s", mqtt_server, mqtt_port, connection_return_code)
|
||||||
|
time.sleep(10.)
|
||||||
|
self.client.loop_start()
|
||||||
|
process_thread = threading.Thread(target=self.process_in_queue, args=(self.client, self.msg_in_queue))
|
||||||
|
process_thread.daemon = True
|
||||||
|
process_thread.start()
|
||||||
|
|
||||||
|
def on_mqtt_connect(self, client, userdata, flags, rc):
|
||||||
|
logging.info("Connected to MQTT Server")
|
||||||
|
# subscribe to panelRecvTopic of each panel
|
||||||
|
for settings_panel in self.settings["nspanels"].values():
|
||||||
|
client.subscribe(settings_panel["panelRecvTopic"])
|
||||||
|
|
||||||
|
def on_mqtt_message(self, client, userdata, msg):
|
||||||
|
try:
|
||||||
|
if msg.payload.decode() == "":
|
||||||
|
return
|
||||||
|
if msg.topic in self.msg_out_queue_list.keys():
|
||||||
|
data = json.loads(msg.payload.decode('utf-8'))
|
||||||
|
if "CustomRecv" in data:
|
||||||
|
queue = self.msg_out_queue_list[msg.topic]
|
||||||
|
queue.put(("MQTT:", data["CustomRecv"]))
|
||||||
|
else:
|
||||||
|
logging.debug("Received unhandled message on topic: %s", msg.topic)
|
||||||
|
except Exception: # pylint: disable=broad-exception-caught
|
||||||
|
logging.exception("Something went wrong during processing of message:")
|
||||||
|
try:
|
||||||
|
logging.error(msg.payload.decode('utf-8'))
|
||||||
|
except: # pylint: disable=bare-except
|
||||||
|
logging.error(
|
||||||
|
"Something went wrong when processing the exception message, couldn't decode payload to utf-8.")
|
||||||
|
|
||||||
|
def process_in_queue(self, client, msg_in_queue):
|
||||||
|
while True:
|
||||||
|
msg = msg_in_queue.get()
|
||||||
|
client.publish(msg[0], msg[1])
|
||||||
@@ -12,13 +12,15 @@ import ha_control
|
|||||||
|
|
||||||
class LovelaceUIPanel:
|
class LovelaceUIPanel:
|
||||||
|
|
||||||
def __init__(self, name_panel, settings_panel):
|
def __init__(self, name_panel, settings_panel, msg_out_queue):
|
||||||
self.name = name_panel
|
self.name = name_panel
|
||||||
self.settings = settings_panel
|
self.settings = settings_panel
|
||||||
|
self.msg_out_queue = msg_out_queue
|
||||||
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.temp_unit = self.settings.get("temp_unit", "celsius")
|
||||||
|
|
||||||
self.current_card = None
|
self.current_card = None
|
||||||
self.privious_cards = []
|
self.privious_cards = []
|
||||||
@@ -94,7 +96,7 @@ class LovelaceUIPanel:
|
|||||||
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.msg_out_queue, self.sendTopic, "pageStartup")
|
||||||
|
|
||||||
|
|
||||||
def schedule_thread_target(self):
|
def schedule_thread_target(self):
|
||||||
@@ -106,13 +108,13 @@ class LovelaceUIPanel:
|
|||||||
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.msg_out_queue, 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.msg_out_queue, self.sendTopic, date_string)
|
||||||
|
|
||||||
def searchCard(self, iid):
|
def searchCard(self, iid):
|
||||||
if iid in self.navigate_keys:
|
if iid in self.navigate_keys:
|
||||||
@@ -129,13 +131,23 @@ class LovelaceUIPanel:
|
|||||||
|
|
||||||
# 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', 'climate']:
|
||||||
# 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.entities:
|
||||||
if entity_id == e[1]:
|
if entity_id == e.entity_id:
|
||||||
entity_id_iid = f'iid.{e[0]}'
|
entity_id_iid = f'iid.{e.iid}'
|
||||||
libs.panel_cmd.entityUpdateDetail(self.sendTopic, detail_open(self.settings["locale"], etype, entity_id, entity_id_iid, sendTopic=self.sendTopic))
|
|
||||||
|
effectList = None
|
||||||
|
if etype=="light":
|
||||||
|
effectList = e.config.get("effectList")
|
||||||
|
if etype == 'light':
|
||||||
|
libs.panel_cmd.entityUpdateDetail2(self.msg_out_queue, self.sendTopic, detail_open(self.settings["locale"], "popupInSel", entity_id, entity_id_iid, self.msg_out_queue, sendTopic=self.sendTopic, options_list=effectList))
|
||||||
|
libs.panel_cmd.entityUpdateDetail(self.msg_out_queue, self.sendTopic, detail_open(self.settings["locale"], "popupLight", entity_id, entity_id_iid, self.msg_out_queue, sendTopic=self.sendTopic))
|
||||||
|
elif etype in ['input_select', 'media_player']:
|
||||||
|
libs.panel_cmd.entityUpdateDetail2(self.msg_out_queue, self.sendTopic, detail_open(self.settings["locale"], etype, entity_id, entity_id_iid, self.msg_out_queue, sendTopic=self.sendTopic))
|
||||||
|
else:
|
||||||
|
libs.panel_cmd.entityUpdateDetail(self.msg_out_queue, self.sendTopic, detail_open(self.settings["locale"], etype, entity_id, entity_id_iid, self.msg_out_queue, 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"),
|
||||||
@@ -150,16 +162,12 @@ class LovelaceUIPanel:
|
|||||||
|
|
||||||
|
|
||||||
def render_current_page(self, switchPages=False, requested=False):
|
def render_current_page(self, switchPages=False, requested=False):
|
||||||
|
if not self.current_card:
|
||||||
|
return
|
||||||
if switchPages:
|
if switchPages:
|
||||||
libs.panel_cmd.page_type(self.sendTopic, self.current_card.type)
|
libs.panel_cmd.page_type(self.msg_out_queue, self.sendTopic, self.current_card.type)
|
||||||
if requested:
|
if requested:
|
||||||
self.current_card.render()
|
self.current_card.render()
|
||||||
# send sleepTimeout
|
|
||||||
#sleepTimeout = self.settings.get("sleepTimeout", 20)
|
|
||||||
#if self.current_card.config.get("sleepTimeout"):
|
|
||||||
# sleepTimeout = self.current_card.config.get("sleepTimeout")
|
|
||||||
#libs.panel_cmd.timeout(self.sendTopic, sleepTimeout)
|
|
||||||
#self.dimmode()
|
|
||||||
|
|
||||||
def dimmode(self):
|
def dimmode(self):
|
||||||
# send dimmode
|
# send dimmode
|
||||||
@@ -178,8 +186,15 @@ class LovelaceUIPanel:
|
|||||||
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.msg_out_queue, self.sendTopic, dimValue, dimValueNormal, backgroundColor, fontColor, featExperimentalSliders)
|
||||||
|
|
||||||
|
def get_default_card(self):
|
||||||
|
defaultCard = self.settings.get("defaultCard")
|
||||||
|
if defaultCard and "." in defaultCard:
|
||||||
|
card = self.searchCard(defaultCard.split(".")[1])
|
||||||
|
if card:
|
||||||
|
return card
|
||||||
|
return list(self.cards.values())[0]
|
||||||
|
|
||||||
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)
|
||||||
@@ -198,7 +213,7 @@ class LovelaceUIPanel:
|
|||||||
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.msg_out_queue, self.sendTopic, sleepTimeout)
|
||||||
self.dimmode()
|
self.dimmode()
|
||||||
|
|
||||||
if msg[1] == "sleepReached":
|
if msg[1] == "sleepReached":
|
||||||
@@ -212,22 +227,34 @@ class LovelaceUIPanel:
|
|||||||
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 in ["screensaver", "screensaver2"] 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(self.get_default_card())
|
||||||
list(self.cards.values())[0]) # TODO: Impelement default card config
|
|
||||||
|
if self.settings.get("defaultCard") and entity_id in ["screensaver", "screensaver2"]:
|
||||||
|
logging.debug("Defaulting to card %s", self.settings.get("defaultCard"))
|
||||||
|
self.privious_cards = [self.get_default_card()]
|
||||||
|
|
||||||
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
|
||||||
|
#if entity_id.startswith("iid."):
|
||||||
|
# iid = entity_id.split(".")[1]
|
||||||
|
# if iid in self.entity_iids:
|
||||||
|
# entity_id = self.entity_iids[iid]
|
||||||
|
|
||||||
# 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:
|
for e in self.current_card.entities:
|
||||||
entity_id = self.entity_iids[iid]
|
if e.iid == iid:
|
||||||
|
entity_id = e.entity_id
|
||||||
|
entity_config = e.config
|
||||||
|
|
||||||
match btype:
|
match btype:
|
||||||
case 'button':
|
case 'button':
|
||||||
@@ -236,8 +263,9 @@ class LovelaceUIPanel:
|
|||||||
case 'navigate':
|
case 'navigate':
|
||||||
card_iid = entity_id.split(".")[1]
|
card_iid = entity_id.split(".")[1]
|
||||||
if card_iid == "UP":
|
if card_iid == "UP":
|
||||||
|
if len(self.privious_cards) == 0:
|
||||||
|
self.privious_cards.append(self.get_default_card())
|
||||||
self.current_card = self.privious_cards.pop()
|
self.current_card = self.privious_cards.pop()
|
||||||
# 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)
|
||||||
@@ -245,7 +273,7 @@ class LovelaceUIPanel:
|
|||||||
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, entity_config=entity_config)
|
||||||
case 'cardUnlock-unlock':
|
case 'cardUnlock-unlock':
|
||||||
card_iid = entity_id.split(".")[1]
|
card_iid = entity_id.split(".")[1]
|
||||||
if int(self.current_card.config.get("pin")) == int(value):
|
if int(self.current_card.config.get("pin")) == int(value):
|
||||||
@@ -260,6 +288,15 @@ class LovelaceUIPanel:
|
|||||||
# 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:
|
for e in self.current_card.entities:
|
||||||
entity_id = self.entity_iids[iid]
|
if e.iid == iid:
|
||||||
libs.panel_cmd.entityUpdateDetail(self.sendTopic, detail_open(self.settings["locale"], msg[2], entity_id, msg[3], sendTopic=self.sendTopic))
|
entity_id = e.entity_id
|
||||||
|
effectList = None
|
||||||
|
if entity_id.startswith("light"):
|
||||||
|
effectList = e.config.get("effectList")
|
||||||
|
if msg[2] == "popupInSel": #entity_id.split(".")[0] in ['input_select', 'media_player']:
|
||||||
|
libs.panel_cmd.entityUpdateDetail2(self.msg_out_queue, self.sendTopic, detail_open(self.settings["locale"], msg[2], entity_id, msg[3], self.msg_out_queue, sendTopic=self.sendTopic, options_list=effectList))
|
||||||
|
else:
|
||||||
|
libs.panel_cmd.entityUpdateDetail(self.msg_out_queue, self.sendTopic, detail_open(self.settings["locale"], msg[2], entity_id, msg[3], self.msg_out_queue, sendTopic=self.sendTopic))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user