Compare commits

...

77 Commits

Author SHA1 Message Date
Armilar
6e38d4f38d v4.3.3.24 - Update NsPanelTs.ts
* Log info commented out
2023-12-18 16:41:49 +01:00
Armilar
2344c9a9ed v4.3.3.24 - Update NsPanelTs.ts
* Hotfix Update Message
* Add Icon Colors to Entity Button
* Add Color-Const Cyan & Magenta
2023-12-18 16:37:46 +01:00
dependabot[bot]
5180f0f869 Bump home-assistant/builder from 2023.09.0 to 2023.12.0 (#1095)
Bumps [home-assistant/builder](https://github.com/home-assistant/builder) from 2023.09.0 to 2023.12.0.
- [Release notes](https://github.com/home-assistant/builder/releases)
- [Commits](https://github.com/home-assistant/builder/compare/2023.09.0...2023.12.0)

---
updated-dependencies:
- dependency-name: home-assistant/builder
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 11:11:52 +01:00
Armilar
b4f2789834 Merge pull request #1093 from joBr99/Armilar-patch-3
v4.3.3.23 - Update NsPanelTs.ts
2023-12-17 17:09:39 +01:00
Armilar
78c6029200 v4.3.3.23 - Update NsPanelTs.ts
Optimization of the blind control (enable or disable Up/Stop/Down)
2023-12-17 17:07:53 +01:00
Armilar
dca112e42b v4.3.3.23 - Update NsPanelTs.ts
Optimization of the blind control (enable or disable Up/Stop/Down)
2023-12-17 17:04:29 +01:00
Armilar
5ad16dd735 Update Countdown_Timer.ts 2023-12-16 10:42:51 +01:00
joBr99
723be0735e . 2023-12-15 14:29:31 +01:00
joBr99
a5bfb9388f . 2023-12-15 14:16:59 +01:00
joBr99
9c6f24f984 Merge branch 'main' of github.com:joBr99/nspanel-lovelace-ui 2023-12-15 14:09:52 +01:00
joBr99
59843ffea5 . 2023-12-15 14:09:25 +01:00
Armilar
4de9c4a12f 4.3.3.22 - Update ioBroker_NSPanel_locales_service.json
Update Notifications
2023-12-14 23:13:44 +01:00
Armilar
3eb05e5a84 4.3.3.22 - Update NsPanelTs.ts 2023-12-14 23:08:14 +01:00
Armilar
5d7a7ed1a4 Merge pull request #1090 from tt-tom17/main
v4.3.3.22 Update NSPanel.ts
2023-12-14 22:44:30 +01:00
Thomas
7124a22c38 v4.3.3.22 Update NSPanel.ts
- Add UpdateMessage => disable the update messages
- Fix name by static Navi Icon
- Fix colorscale by Role Info
2023-12-14 22:36:41 +01:00
Armilar
fa4d65a383 Merge pull request #1089 from tt-tom17/main
Update Alarm_clock.ts
2023-12-13 22:01:03 +01:00
Thomas
d26306f892 Update Alarm_clock.ts
fix dpAction to val: true
2023-12-13 21:55:33 +01:00
joBr99
9c0bb037fb Merge branch 'main' of github.com:joBr99/nspanel-lovelace-ui 2023-12-13 16:50:46 +01:00
joBr99
4b73e20b9b implement templtates for brightness 2023-12-13 16:50:28 +01:00
dependabot[bot]
3940a0c2e9 Bump actions/setup-python from 4 to 5 (#1087)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-13 14:22:56 +01:00
Armilar
8f57d4a642 v4.3.3.21 - Update NsPanelTs.ts
- Add createAutoALias to popupTimer only for Time
2023-12-09 11:19:38 +01:00
Armilar
3979fdf6a0 Create Countdown_Timer.ts 2023-12-08 22:58:24 +01:00
Armilar
4cd47126eb Create Alarm_clock.ts 2023-12-08 22:54:16 +01:00
Armilar
eb0dd79c80 v4.3.3.20 - Update ioBroker_NSPanel_locales.json 2023-12-08 22:41:11 +01:00
Armilar
3a39a8ca0e Merge pull request #1086 from tt-tom17/main
v4.3.3.20 - Update NSPanel.ts
2023-12-08 22:27:04 +01:00
Armilar
b5c4a2128b v4.3.3.20 - Update NsPanelTs.ts
Remove semicolon
2023-12-08 22:25:04 +01:00
Armilar
41f43fe5d0 v4.3.3.20 - Update NsPanelTs.ts
Remove Example
2023-12-08 22:22:44 +01:00
Armilar
f3f93b7136 v4.3.3.12 - Update NsPanelTs.ts 2023-12-08 22:20:08 +01:00
joBr99
2fb1855842 fixed binary sensors 2023-12-08 22:09:35 +01:00
Thomas
9a3627427f v4.3.3.20 - Update NSPanel.ts
add Role AlarmTime for Alarm Clock
2023-12-08 20:24:23 +01:00
Armilar
94fbf0a5f7 Merge pull request #1084 from joBr99/Armilar-patch-2
v4.3.3.19 - Update NsPanelTs.ts
2023-12-07 20:02:54 +01:00
Armilar
caddec1190 v4.3.3.19 - Update NsPanelTs.ts
- Fix Trigger activeDimmodeBrightness if Dimmode = -1
2023-12-07 20:02:12 +01:00
Armilar
03367ea27d v4.3.3.19 - Update NsPanelTs.ts
- Fix Trigger activeDimmodeBrightness if Dimmode = -1
2023-12-07 19:58:21 +01:00
Armilar
8e792ae8fc Merge pull request #1083 from joBr99/Armilar-patch-1
v4.3.3.18 - Update NsPanelTs.ts
2023-12-06 22:00:28 +01:00
Armilar
0a03f736ce v4.3.3.18 - Update NsPanelTs.ts
- Add (ELAPSED/DURATION) to v2Adapter alexa2
- Replace missing Type console.log --> log(message, 'serverity')
2023-12-06 21:49:15 +01:00
Armilar
8d7f6ffea5 v4.3.3.18 - Update NsPanelTs.ts
- Add (ELAPSED/DURATION) to v2Adapter alexa2
- Replace missing Type console.log --> log(message, 'serverity')
2023-12-06 15:20:28 +01:00
Armilar
93a6a7a88a v4.3.3.17 - Update NsPanelTs.ts
Add SEEK and CROSSFADE to Sonos cardMedia
2023-12-04 23:01:01 +01:00
Armilar
3913b17596 Update ioBroker_NSPanel_locales.json 2023-12-04 22:06:42 +01:00
Armilar
d08e8eb40c Update ioBroker_NSPanel_locales.json 2023-12-04 21:57:44 +01:00
Johannes
c2d281658e Update panel_cmd.py 2023-12-03 16:25:17 +01:00
joBr99
108582cbfb fix 2023-12-02 16:27:50 +01:00
joBr99
b17db265f4 implement temp unit 2023-12-02 16:26:28 +01:00
joBr99
206739dcc5 implement popup on card thermo 2023-12-02 16:20:31 +01:00
joBr99
adcb618a11 . 2023-12-02 14:23:43 +01:00
joBr99
dfba3b6e84 Merge branch 'main' of github.com:joBr99/nspanel-lovelace-ui 2023-12-02 14:11:25 +01:00
joBr99
2726859135 implement input_select and select for light effects 2023-12-02 14:10:53 +01:00
Armilar
c0e20e6f25 Merge pull request #1077 from joBr99/Armilar-patch-1
v4.3.3.16 - Update NsPanelTs.ts
2023-12-02 13:02:38 +01:00
Armilar
148c2fc5a2 v4.3.3.16 - Update NsPanelTs.ts
- Beautification of the Sonos player Strings / Add Duration & Elapsed
- Fix Datapoints with Value null with -1
- Request replaced by Axios
2023-12-02 00:50:11 +01:00
Armilar
526f5e8946 v4.3.3.16 - Update NsPanelTs.ts
- Beautification of the Sonos player Strings / Add Duration & Elapsed
- Fix Datapoints with Value null with -1
- Request replaced by Axios
2023-12-02 00:36:00 +01:00
joBr99
8cd17b9d9a . 2023-12-02 00:03:22 +01:00
joBr99
70ff46ab4b . 2023-12-01 23:59:59 +01:00
joBr99
770348b07b . 2023-12-01 23:48:11 +01:00
joBr99
4795cc23ad fix state update bug with iid 2023-12-01 23:39:49 +01:00
joBr99
bae64dcee5 do not init mqtt in case ha api is used 2023-12-01 23:28:58 +01:00
joBr99
953a8d7110 . 2023-12-01 23:26:52 +01:00
joBr99
3b5eaac976 initial implementation of esphome api comm 2023-12-01 23:26:27 +01:00
joBr99
6e28237ec5 Merge branch 'main' of github.com:joBr99/nspanel-lovelace-ui 2023-12-01 19:55:36 +01:00
joBr99
b8c47948c3 add queue for outgoing messages 2023-12-01 19:55:29 +01:00
Armilar
0e8f9ad220 Merge pull request #1075 from tt-tom17/main
v4.3.3.15 - Update NsPanelTs.ts
2023-12-01 13:11:13 +01:00
Thomas
79e43e2740 Update NsPanelTs.ts
fix activeDimmodeBrightness -> value -1
fix bExitPage -> value -1
2023-12-01 11:22:01 +01:00
joBr99
c32d2958a6 . 2023-11-30 17:48:31 +01:00
joBr99
3b7c934972 . 2023-11-30 17:43:53 +01:00
joBr99
40f29d09c1 . 2023-11-30 17:39:04 +01:00
joBr99
b601f2d860 . 2023-11-30 17:32:21 +01:00
joBr99
5953d7c8dd . 2023-11-30 17:25:32 +01:00
joBr99
e9859c0d32 . 2023-11-30 17:20:12 +01:00
joBr99
4ad997515f Merge branch 'main' of github.com:joBr99/nspanel-lovelace-ui 2023-11-30 17:15:03 +01:00
joBr99
ba637cf11e . 2023-11-30 17:14:41 +01:00
Armilar
a4abcd1734 Merge pull request #1074 from joBr99/Armilar-patch-1
v4.3.3.15 - Minor bug fixes
2023-11-30 00:06:06 +01:00
Armilar
86bbd36813 v4.3.3.15 - Regex Tracklist
- Regex Tracklist
2023-11-30 00:04:52 +01:00
Armilar
c9dffc431c v4.3.3.15 - Minor bug fixes
- Fix cardMedia Volume-Slider
- Add Init Release to Startup
2023-11-29 21:24:02 +01:00
joBr99
04ffd6257e . 2023-11-29 17:25:25 +01:00
Johannes
4102f56cee Update docs-release.yml 2023-11-28 23:46:03 +01:00
Johannes
6a62a6206a Update docs-release.yml 2023-11-28 23:44:30 +01:00
Johannes
0bddceccfa Update docs-release.yml 2023-11-28 23:44:16 +01:00
Johannes
09156fbc89 Update docs-release.yml 2023-11-28 23:43:18 +01:00
joBr99
76d0075c7d . 2023-11-28 23:41:28 +01:00
16 changed files with 1919 additions and 1038 deletions

View File

@@ -100,7 +100,7 @@ jobs:
- name: Build ${{ matrix.addon }} add-on
if: steps.check.outputs.build_arch == 'true'
uses: home-assistant/builder@2023.09.0
uses: home-assistant/builder@2023.12.0
with:
args: |
${{ env.BUILD_ARGS }} \

View File

@@ -4,7 +4,7 @@ on:
workflow_dispatch:
push:
branches:
- main
- dev
paths:
- docs/*
- .github/workflows/docs.yml
@@ -18,18 +18,11 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
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: mkdocs gh-deploy --force
- run: git config --global user.name Docs deploy
- run: git config --global user.email docs@dummy.bot.com
- run: mike deploy --push --update-aliases dev

29
.github/workflows/docs-release.yml vendored Normal file
View 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@v5
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

View File

@@ -0,0 +1,44 @@
const dp_userdata: string = '0_userdata.0.NSPanel';
const dp_alias: string = 'alias.0.NSPanel';
// dpAction wird wenn der Wecker gestellt wird auf false geschaltet
// dpAction wird wenn die Weckzeit erreicht ist auf true geschaltet
// Der nachfolgende Datenpunkt muss manuell erstellt werden...
const dpAction: string = '0_userdata.0.example_boolean';
const Debug = true;
let time: number;
let scheduleAlarmTime: any = null;
on({ id: dp_userdata + '.AlarmTime.State', change: 'ne' }, async (obj) => {
time = getState(dp_userdata + '.AlarmTime.Time').val;
if (Debug) log('Uhrzeit: ' + time, 'info');
if ('paused' == obj.state.val) {
(function () { if (scheduleAlarmTime) {
clearSchedule(scheduleAlarmTime);
scheduleAlarmTime = null;
}
});
} else if ('active' == obj.state.val) {
let stunde: number = Math.floor(time / 60);
let minute: number = time % 60;
if (Debug) log('Weckzeit: ' + ('0' + stunde).slice(-2) + ':' + ('0' + minute).slice(-2), 'info');
scheduleAlarmTime = schedule(minute + ' ' + stunde + ' * * *', async () => {
await setStateAsync(dpAction, <iobJS.State>{ val: true, ack: true });
await setStateAsync(dp_userdata + '.AlarmTime.State', <iobJS.State>{ val: 'paused', ack: true });
});
}
});
async function Init_Datenpunkte() {
if (existsState(dp_alias + '.AlarmTime.ACTUAL') == false) {
await createStateAsync(dp_userdata + '.AlarmTime.Time', '0', { type: 'number' });
await createStateAsync(dp_userdata + '.AlarmTime.State', 'paused', { type: 'string' });
setObject(dp_alias + '.AlarmTime', { type: 'channel', common: { role: 'value.alarmtime', name: 'Alarmtime' }, native: {} });
await createAliasAsync(dp_alias + '.AlarmTime.ACTUAL', dp_userdata + '.AlarmTime.Time', true, <iobJS.StateCommon>{ type: 'number', role: 'state', name: 'ACTUAL' });
await createAliasAsync(dp_alias + '.AlarmTime.STATE', dp_userdata + '.AlarmTime.State', true, <iobJS.StateCommon>{ type: 'string', role: 'state', name: 'STATE' });
log("<PageItem>{id: '"+ dp_alias + ".AlarmTime', name: 'Wecker', onColor: Red, offColor: Green, useColor: true}", 'info');
}
}
Init_Datenpunkte();

View File

@@ -0,0 +1,46 @@
const dp_userdata: string = '0_userdata.0.NSPanel';
const dp_alias: string = 'alias.0.NSPanel';
// Der nachfolgende Datenpunkt muss manuell angelegt werden
const dpAction: string = '0_userdata.0.example_boolean'; // anpassen
const Debug = false;
let intervallCounter: any;
let sec_timer = getState(dp_userdata + '.Countdown.Time').val;
on({ id: dp_userdata + '.Countdown.State', change: 'ne' }, async (obj) => {
switch (obj.state.val) {
case 'active':
if (intervallCounter) { clearInterval(intervallCounter); intervallCounter = null; };
intervallCounter = setInterval(async () => {
if (getState(dp_userdata + '.Countdown.Time').val > 0) {
sec_timer = getState(dp_userdata + '.Countdown.Time').val;
setState(dp_userdata + '.Countdown.Time', (sec_timer - 1), false);
} else {
setState(dp_userdata + '.Countdown.Time', 0, false);
setState(dp_userdata + '.Countdown.State', 'idle', false);
// An dieser Stelle kann auch noch eine Meldung an Alexa oder Telegram, etc. erfolgen
}
}, 1000);
break;
default:
if (intervallCounter) { clearInterval(intervallCounter); intervallCounter = null; };
break;
}
});
async function Init_Datenpunkte() {
if (existsState(dp_alias + '.Countdown.ACTUAL') == false) {
await createStateAsync(dp_userdata + '.Countdown.Time', '0', { type: 'number'});
await createStateAsync(dp_userdata + '.Countdown.State', 'paused', { type: 'string' });
setObject(dp_alias + '.Countdown', { type: 'channel', common: { role: 'level.timer', name: 'Countdown' }, native: {} });
await createAliasAsync(dp_alias + '.Countdown.ACTUAL', dp_userdata + '.Countdown.Time', true, <iobJS.StateCommon>{ type: 'number', role: 'state', name: 'ACTUAL' });
await createAliasAsync(dp_alias + '.Countdown.STATE', dp_userdata + '.Countdown.State', true, <iobJS.StateCommon>{ type: 'string', role: 'state', name: 'STATE' });
log("<PageItem>{id: '"+ dp_alias + ".Countdown', name: 'Timer'}", 'info');
}
}
Init_Datenpunkte();

File diff suppressed because it is too large Load Diff

View File

@@ -1047,6 +1047,14 @@
"zh-CN":"空闲",
"zh-TW":"暫停"
},
"on":{
"en-US":"On",
"de-DE":"Ein"
},
"off":{
"en-US":"Off",
"de-DE":"Aus"
},
"paused":{
"en-US":"Paused",
"de-DE":"pausiert",
@@ -2533,6 +2541,22 @@
"zh-CN":"没有音乐可以控制",
"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":{
"en-US":"Speakerlist",
"de-DE":"Wiedergabegeräte",

View File

@@ -2693,5 +2693,9 @@
"update_nextion_tft":{
"en-US":"Update Nextion TFT",
"de-DE":"Nextion TFT Update"
},
"update_message":{
"en-US":"Update Notifications",
"de-DE":"Update Mitteilungen"
}
}

View File

@@ -1,6 +1,6 @@
# https://developers.home-assistant.io/docs/add-ons/configuration#add-on-config
name: NSPanel Lovelace UI Addon
version: "4.7.54"
version: "4.7.73"
slug: nspanel-lovelace-ui
description: NSPanel Lovelace UI Addon
services:

View File

@@ -52,6 +52,8 @@ class HAEntity(panel_cards.Entity):
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
@@ -188,7 +190,7 @@ class HAEntity(panel_cards.Entity):
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}.{entity.state}")
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))
@@ -297,21 +299,19 @@ class EntitiesCard(HACard):
result = f"{self.title}~{self.gen_nav()}"
for e in self.entities:
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):
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):
# TODO: Render QRCode as HomeAssistant Template
#qrcode = apis.ha_api.render_template(qrcode)
if self.qrcode.startswith("ha:"):
self.qrcode = libs.home_assistant.get_template(self.qrcode)[3:]
result = f"{self.title}~{self.gen_nav()}~{self.qrcode}"
for e in self.entities:
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):
def __init__(self, locale, config, panel):
@@ -329,7 +329,7 @@ class PowerCard(HACard):
# if isinstance(speed, str):
# speed = apis.ha_api.render_template(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):
def __init__(self, locale, config, panel):
@@ -366,7 +366,7 @@ class MediaCard(HACard):
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.sendTopic, result)
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
class ClimateCard(HACard):
def __init__(self, locale, config, panel):
@@ -375,9 +375,8 @@ class ClimateCard(HACard):
def render(self):
main_entity = self.entities[0]
#TODO: temp unit
temp_unit = "celsius"
if(temp_unit == "celsius"):
temp_unit = self.panel.temp_unit
if temp_unit == "celsius":
temperature_unit_icon = get_icon_char("temperature-celsius")
temperature_unit = "°C"
@@ -411,7 +410,12 @@ class ClimateCard(HACard):
min_temp = int(main_entity.attributes.get("min_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 = ""
@@ -460,8 +464,8 @@ class ClimateCard(HACard):
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()}~{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}"
libs.panel_cmd.entityUpd(self.panel.sendTopic, result)
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):
@@ -471,6 +475,8 @@ class AlarmCard(HACard):
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 = []
@@ -531,7 +537,7 @@ class AlarmCard(HACard):
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.sendTopic, result)
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
class UnlockCard(HACard):
@@ -544,7 +550,7 @@ class UnlockCard(HACard):
icon = get_icon_char("lock")
supported_modes = ["cardUnlock-unlock"]
entity_id = self.config.get("entity")
entity_id = self.config.get("destination")
# add padding to arm buttons
arm_buttons = ""
@@ -555,7 +561,7 @@ class UnlockCard(HACard):
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)
libs.panel_cmd.entityUpd(self.panel.msg_out_queue, self.panel.sendTopic, result)
class Screensaver(HACard):
def __init__(self, locale, config, panel):
@@ -583,7 +589,7 @@ class Screensaver(HACard):
result = ""
for e in self.entities:
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 = ""
icon1font = ""
@@ -601,7 +607,7 @@ class Screensaver(HACard):
else:
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):
@@ -625,7 +631,7 @@ def card_factory(locale, settings, panel):
return "NotImplemented", None
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)
if data:
state = data.get("state")
@@ -762,9 +768,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}'
case 'popupThermo' | 'climate':
print(f"not implemented {detail_type}")
case 'popupInSel' | 'input_select' | 'select':
print(f"not implemented {detail_type}")
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"]:
@@ -787,8 +835,8 @@ def detail_open(locale, detail_type, ha_entity_id, entity_id, sendTopic=None):
#update timer in a second
def update_time():
time.sleep(1)
out = detail_open(locale, detail_type, ha_entity_id, entity_id, sendTopic=sendTopic)
libs.panel_cmd.entityUpdateDetail(sendTopic, out)
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()

View File

@@ -8,6 +8,7 @@ def wait_for_ha_cache():
while time.time() < mustend:
if len(libs.home_assistant.home_assistant_entity_state_cache) == 0:
time.sleep(0.1)
time.sleep(1)
def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, screenBrightness, sleepOverride, return_involved_entities=False):
dimmode = 10
@@ -17,6 +18,12 @@ def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, scr
if sleepBrightness:
if isinstance(sleepBrightness, int):
dimmode = sleepBrightness
elif isinstance(sleepBrightness, list):
logging.error("list style config for sleepBrightness no longer supported")
elif sleepBrightness.startswith("ha:"):
time.sleep(1)
dimmode = int(float(libs.home_assistant.get_template(sleepBrightness)[3:]))
involved_entities.extend(libs.home_assistant.get_template_listener_entities(sleepBrightness))
elif libs.home_assistant.is_existent(sleepBrightness):
involved_entities.append(sleepBrightness)
dimmode = int(float(libs.home_assistant.get_entity_data(sleepBrightness).get('state', 10)))
@@ -24,6 +31,12 @@ def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, scr
if screenBrightness:
if isinstance(screenBrightness, int):
dimValueNormal = screenBrightness
elif isinstance(screenBrightness, list):
logging.error("list style config for screenBrightness no longer supported")
elif screenBrightness.startswith("ha:"):
time.sleep(1)
dimValueNormal = int(float(libs.home_assistant.get_template(screenBrightness)[3:]))
involved_entities.extend(libs.home_assistant.get_template_listener_entities(screenBrightness))
elif libs.home_assistant.is_existent(screenBrightness):
involved_entities.append(screenBrightness)
dimValueNormal = int(float(libs.home_assistant.get_entity_data(screenBrightness).get('state', 100)))
@@ -51,7 +64,7 @@ def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, scr
else:
return dimmode, dimValueNormal
def handle_buttons(entity_id, btype, value):
def handle_buttons(entity_id, btype, value, entity_config=None):
match btype:
case 'button':
button_press(entity_id, value)
@@ -60,7 +73,7 @@ def handle_buttons(entity_id, btype, value):
case 'number-set':
if entity_id.startswith('fan'):
attr = libs.home_assistant.get_entity_data(entity_id).get('attributes', [])
value = float(value) * float(attr.get(percentage_step, 0))
value = float(value) * float(attr.get('percentage_step', 0))
service_data = {
"value": int(value)
}
@@ -156,19 +169,20 @@ def handle_buttons(entity_id, btype, value):
}
call_ha_service(entity_id, f"alarm_{btype}", service_data=service_data)
case 'mode-preset_modes' | 'mode-swing_modes' | 'mode-fan_modes':
attr = libs.home_assistant.get_entity_data(entity_id).get('attributes', [])
mapping = {
'mode-preset_modes': 'preset_modes',
'mode-swing_modes': 'swing_modes',
'mode-fan_modes': 'fan_mode'
'mode-fan_modes': 'fan_modes'
}
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:
mode = modes[int(value)]
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':
options = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get("options", [])
if options:
@@ -186,7 +200,7 @@ def handle_buttons(entity_id, btype, value):
}
call_ha_service(entity_id, "select_source", service_data=service_data)
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:
option = options[int(value)]
service_data = {
@@ -216,30 +230,6 @@ def handle_buttons(entity_id, btype, value):
case _:
logging.error("Not implemented: %s", btype)
# # for cardUnlock
# if button_type == "cardUnlock-unlock":
# curCard = self._config.get_card_by_uuid(
# entity_id.replace('navigate.', ''))
# if curCard is not None:
# if int(curCard.raw_config.get("pin")) == int(value):
# dstCard = self._config.search_card(
# curCard.raw_config.get("destination"))
# if dstCard is not None:
# if dstCard.hidden:
# self._previous_cards.append(self._current_card)
# self._current_card = dstCard
# self._pages_gen.render_card(self._current_card)
# if button_type == "opnSensorNotify":
# msg = ""
# entity = apis.ha_api.get_entity(entity_id)
# if "open_sensors" in entity.attributes and entity.attributes.open_sensors is not None:
# for e in entity.attributes.open_sensors:
# msg += f"- {apis.ha_api.get_entity(e).attributes.friendly_name}\r\n"
# self._pages_gen.send_message_page(
# "opnSensorNotifyRes", "", msg, "", "")
def call_ha_service(entity_id, service, service_data = {}):
etype = entity_id.split(".")[0]
libs.home_assistant.call_service(

View File

@@ -70,7 +70,8 @@ def on_message(ws, message):
for template, template_cache_entry in template_cache.items():
if entity_id in template_cache_entry.get("listener-entities", []):
cache_template(template)
elif json_msg["type"] == "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"]:
logging.error("Failed result: ")
logging.error(json_msg)
@@ -143,6 +144,15 @@ def subscribe_to_events():
}
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():
global next_id, request_all_states_id
@@ -158,6 +168,10 @@ def send_entity_update(entity_id):
global on_ha_update
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:
global next_id
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.")
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:
global next_id, response_buffer
try:
@@ -202,7 +232,7 @@ def execute_script(entity_name: str, domain: str, service: str, service_data: di
]
}
send_message(json.dumps(msg))
# busy waiting for response with a timeout of 0.2 seconds - maybe there's a better way of doing this
# busy waiting for response with a timeout of 0.4 seconds- maybe there's a better way of doing this
mustend = time.time() + 0.4
while time.time() < mustend:
if response_buffer[call_id] == True:
@@ -216,6 +246,8 @@ def execute_script(entity_name: str, domain: str, service: str, service_data: di
return {}
def cache_template(template):
if not template:
raise Exception("Invalid template")
global next_id, response_buffer
try:
call_id = next_id
@@ -243,6 +275,18 @@ def get_template(template):
else:
return template_cache.get(template, []).get("result", "404")
def get_template_listener_entities(template):
global template_cache
if template in template_cache:
return template_cache[template].get("listener-entities")
else:
mustend = time.time() + 0.5
while time.time() < mustend:
if template not in template_cache:
time.sleep(0.0001)
else:
return template_cache.get(template, []).get("listener-entities", "404")
def get_entity_data(entity_id: str):
if entity_id in home_assistant_entity_state_cache:
return home_assistant_entity_state_cache[entity_id]

View File

@@ -1,46 +1,47 @@
import logging
def init(mqtt_client_from_manager):
global mqtt_client
mqtt_client = mqtt_client_from_manager
def custom_send(topic, msg):
global mqtt_client
mqtt_client.publish(topic, msg)
def custom_send(msg_out_queue, topic, msg):
msg_out_queue.put((topic, msg))
logging.debug("Sent Message to NsPanel (%s): %s", topic, msg)
def page_type(topic, target_page):
def page_type(msg_out_queue, topic, target_page):
if target_page == "cardUnlock":
target_page = "cardAlarm"
custom_send(topic, f"pageType~{target_page}")
custom_send(msg_out_queue, topic, f"pageType~{target_page}")
def send_time(topic, time, addTimeText=""):
custom_send(topic, f"time~{time}~{addTimeText}")
def send_time(msg_out_queue, topic, time, addTimeText=""):
custom_send(msg_out_queue, topic, f"time~{time}~{addTimeText}")
def send_date(topic, date):
custom_send(topic, f"date~{date}")
def send_date(msg_out_queue, topic, date):
custom_send(msg_out_queue, topic, f"date~{date}")
def entityUpd(topic, data):
custom_send(topic, f"entityUpd~{data}")
def entityUpd(msg_out_queue, topic, data):
custom_send(msg_out_queue, topic, f"entityUpd~{data}")
def weatherUpdate(topic, data):
custom_send(topic, f"weatherUpdate~{data}")
def weatherUpdate(msg_out_queue, topic, data):
custom_send(msg_out_queue, topic, f"weatherUpdate~{data}")
def timeout(topic, timeout):
custom_send(topic, f"timeout~{timeout}")
def timeout(msg_out_queue, topic, timeout):
custom_send(msg_out_queue, topic, f"timeout~{timeout}")
def dimmode(topic, dimValue, dimValueNormal, backgroundColor, fontColor, featExperimentalSliders):
def dimmode(msg_out_queue, topic, dimValue, dimValueNormal, backgroundColor, fontColor, featExperimentalSliders):
if dimValue==dimValueNormal:
dimValue=dimValue-1
custom_send(topic, f"dimmode~{dimValue}~{dimValueNormal}~{backgroundColor}~{fontColor}~{featExperimentalSliders}")
custom_send(msg_out_queue, topic, f"dimmode~{dimValue}~{dimValueNormal}~{backgroundColor}~{fontColor}~{featExperimentalSliders}")
def entityUpdateDetail(topic, data):
custom_send(topic, f"entityUpdateDetail~{data}")
def entityUpdateDetail(msg_out_queue, topic, data):
custom_send(msg_out_queue, topic, f"entityUpdateDetail~{data}")
def statusUpdate(topic, data):
custom_send(topic, f"statusUpdate~{data}")
def entityUpdateDetail2(msg_out_queue, topic, data):
custom_send(msg_out_queue, topic, f"entityUpdateDetail2~{data}")
def statusUpdate(msg_out_queue, topic, data):
custom_send(msg_out_queue, topic, f"statusUpdate~{data}")
def send_message_page(msg_out_queue, topic, ident, heading, msg, b1, b2):
page_type(msg_out_queue, topic, "popupNotify")
custom_send(msg_out_queue, topic, f"entityUpdateDetail~{ident}~{heading}~65535~{b1}~65535~{b2}~65535~{msg}~65535~0")

View File

@@ -1,13 +1,10 @@
#!/usr/bin/env python
import logging
import paho.mqtt.client as mqtt
import time
import json
import subprocess
import libs.home_assistant
import libs.panel_cmd
import yaml
from uuid import getnode as get_mac
from panel import LovelaceUIPanel
import os
import threading
@@ -16,52 +13,97 @@ from watchdog.observers import Observer
import signal
import sys
from queue import Queue
from mqtt import MqttManager
logging.getLogger("watchdog").propagate = False
settings = {}
panels = {}
panel_queues = {}
panel_in_queues = {}
panel_out_queue = Queue(maxsize=20)
last_settings_file_mtime = 0
mqtt_connect_time = 0
has_sent_reload_command = False
mqtt_client_name = "NSPanelLovelaceManager_" + str(get_mac())
client = mqtt.Client(mqtt_client_name)
logging.basicConfig(level=logging.DEBUG)
def on_connect(client, userdata, flags, rc):
global settings
logging.info("Connected to MQTT Server")
# subscribe to panelRecvTopic of each panel
for settings_panel in settings["nspanels"].values():
client.subscribe(settings_panel["panelRecvTopic"])
def on_ha_update(entity_id):
global panel_queues
for queue in panel_queues.values():
global panel_in_queues
# send HA updates to all panels
for queue in panel_in_queues.values():
queue.put(("HA:", entity_id))
def on_message(client, userdata, msg):
global panel_queues
try:
if msg.payload.decode() == "":
return
parts = msg.topic.split('/')
if msg.topic in panel_queues.keys():
data = json.loads(msg.payload.decode('utf-8'))
if "CustomRecv" in data:
queue = panel_queues[msg.topic]
queue.put(("MQTT:", data["CustomRecv"]))
else:
logging.debug("Received unhandled message on topic: %s", msg.topic)
def on_ha_panel_event(device_id, msg):
global panel_in_queues
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.")
if device_id in panel_in_queues.keys():
queue = panel_in_queues[device_id]
queue.put(("MQTT:", msg))
def process_output_to_panel():
while True:
msg = panel_out_queue.get()
#client.publish(msg[0], msg[1])
#apis.ha_api.call_service(service="esphome/" + self._api_panel_name + "_nspanelui_api_call", command=2, data=msg)
service = msg[0] + "_nspanelui_api_call"
service_data = {
"data": msg[1],
"command":2
}
libs.home_assistant.send_msg_to_panel(
service = service,
service_data = service_data
)
def connect():
global settings, panel_out_queue
if "mqtt_server" in settings and not "use_ha_api" in settings:
MqttManager(settings, panel_out_queue, panel_in_queues)
else:
logging.info("MQTT values not configured, will not connect.")
# MQTT Connected, start APIs if configured
if settings["home_assistant_address"] != "" and settings["home_assistant_token"] != "":
libs.home_assistant.init(settings, on_ha_update)
libs.home_assistant.connect()
else:
logging.info("Home Assistant values not configured, will not connect.")
while not libs.home_assistant.ws_connected:
time.sleep(1)
if settings.get("use_ha_api"):
libs.home_assistant.subscribe_to_nspanel_events(on_ha_panel_event)
send_to_panel_thread = threading.Thread(target=process_output_to_panel, args=())
send_to_panel_thread.daemon = True
send_to_panel_thread.start()
def setup_panels():
global settings, panel_in_queues
# Create NsPanel object
for name, settings_panel in settings["nspanels"].items():
if "timeZone" not in settings_panel:
settings_panel["timeZone"] = settings.get("timeZone", "Europe/Berlin")
if "locale" not in settings_panel:
settings_panel["timezone"] = settings.get("locale", "en_US")
if "hiddenCards" not in settings_panel:
settings_panel["hiddenCards"] = settings.get("hiddenCards", [])
msg_in_queue = Queue(maxsize=20)
panel_in_queues[settings_panel["panelRecvTopic"]] = msg_in_queue
panel_thread = threading.Thread(target=panel_thread_target, args=(msg_in_queue, name, settings_panel, panel_out_queue))
panel_thread.daemon = True
panel_thread.start()
def panel_thread_target(queue_in, name, settings_panel, queue_out):
panel = LovelaceUIPanel(name, settings_panel, queue_out)
while True:
msg = queue_in.get()
if msg[0] == "MQTT:":
panel.customrecv_event_callback(msg[1])
elif msg[0] == "HA:":
panel.ha_event_callback(msg[1])
def get_config_file():
CONFIG_FILE = os.getenv('CONFIG_FILE')
@@ -109,79 +151,6 @@ def get_config(file):
settings["is_addon"] = True
return True
def connect():
global settings, home_assistant, client
client.on_connect = on_connect
client.on_message = on_message
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:
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.)
# MQTT Connected, start APIs if configured
if settings["home_assistant_address"] != "" and settings["home_assistant_token"] != "":
libs.home_assistant.init(settings, on_ha_update)
libs.home_assistant.connect()
else:
logging.info("Home Assistant values not configured, will not connect.")
libs.panel_cmd.init(client)
setup_panels()
def loop():
global client
# Loop MQTT
client.loop_forever()
def setup_panels():
global settings, panel_queues
# Create NsPanel object
for name, settings_panel in settings["nspanels"].items():
if "timeZone" not in settings_panel:
settings_panel["timeZone"] = settings.get("timeZone", "Europe/Berlin")
if "locale" not in settings_panel:
settings_panel["timezone"] = settings.get("locale", "en_US")
if "hiddenCards" not in settings_panel:
settings_panel["hiddenCards"] = settings.get("hiddenCards", [])
#panels[name] = LovelaceUIPanel(name, settings_panel)
mqtt_queue = Queue(maxsize=20)
panel_queues[settings_panel["panelRecvTopic"]] = mqtt_queue
panel_thread = threading.Thread(target=panel_thread_target, args=(mqtt_queue, name, settings_panel))
panel_thread.daemon = True
panel_thread.start()
def panel_thread_target(queue, name, settings_panel):
panel = LovelaceUIPanel(name, settings_panel)
while True:
msg = queue.get()
#print(msg)
if msg[0] == "MQTT:":
panel.customrecv_event_callback(msg[1])
elif msg[0] == "HA:":
panel.ha_event_callback(msg[1])
def config_watch():
class ConfigChangeEventHandler(FileSystemEventHandler):
def __init__(self, base_paths):
@@ -218,7 +187,11 @@ if __name__ == '__main__':
threading.Thread(target=config_watch).start()
if (get_config(get_config_file())):
connect()
loop()
setup_panels()
# main thread sleep forever
while True:
time.sleep(100)
else:
while True:
time.sleep(100)

View 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])

View File

@@ -7,18 +7,20 @@ from scheduler import Scheduler
import scheduler.trigger as trigger
import time
import babel.dates
from ha_cards import Screensaver, EntitiesCard, card_factory, detail_open
from ha_cards import Screensaver, card_factory, detail_open
import ha_control
class LovelaceUIPanel:
def __init__(self, name_panel, settings_panel):
def __init__(self, name_panel, settings_panel, msg_out_queue):
self.name = name_panel
self.settings = settings_panel
self.msg_out_queue = msg_out_queue
self.sendTopic = self.settings["panelSendTopic"]
self.recvTopic = self.settings["panelRecvTopic"]
self.model = self.settings.get("model", "eu")
self.temp_unit = self.settings.get("temp_unit", "celsius")
self.current_card = None
self.privious_cards = []
@@ -74,6 +76,10 @@ class LovelaceUIPanel:
ha_control.wait_for_ha_cache()
#request templates on cards
if isinstance(self.settings.get("sleepBrightness",""), str) and self.settings.get("sleepBrightness", "").startswith("ha:"):
libs.home_assistant.cache_template(self.settings.get("sleepBrightness"))
if isinstance(self.settings.get("sleepBrightness",""), str) and self.settings.get("screenBrightness", "").startswith("ha:"):
libs.home_assistant.cache_template(self.settings.get("screenBrightness"))
for c in self.cards.values():
if hasattr(c, "qrcode"):
if c.qrcode.startswith("ha:"):
@@ -94,7 +100,7 @@ class LovelaceUIPanel:
for e in self.screensaver.entities:
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):
@@ -106,13 +112,13 @@ class LovelaceUIPanel:
use_timezone = tz.gettz(self.settings["timeZone"])
time_string = datetime.datetime.now(
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):
dateformat = self.settings["dateFormat"]
date_string = babel.dates.format_date(
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):
if iid in self.navigate_keys:
@@ -129,13 +135,23 @@ class LovelaceUIPanel:
# send update for detail popup in case it's open
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
entity_id_iid = ""
for e in self.current_card.get_iid_entities():
if entity_id == e[1]:
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))
for e in self.current_card.entities:
if entity_id == e.entity_id:
entity_id_iid = f'iid.{e.iid}'
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(
self.settings.get("sleepTracking"),
@@ -150,16 +166,12 @@ class LovelaceUIPanel:
def render_current_page(self, switchPages=False, requested=False):
if not self.current_card:
return
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:
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):
# send dimmode
@@ -178,8 +190,15 @@ class LovelaceUIPanel:
backgroundColor = 0
fontColor = ""
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):
logging.debug("Recv Message from NsPanel (%s): %s", self.name, msg)
@@ -198,7 +217,7 @@ class LovelaceUIPanel:
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)
libs.panel_cmd.timeout(self.msg_out_queue, self.sendTopic, sleepTimeout)
self.dimmode()
if msg[1] == "sleepReached":
@@ -212,13 +231,17 @@ class LovelaceUIPanel:
btype = msg[3]
value = msg[4] if len(msg) > 4 else None
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
# in case privious_cards is empty add a default card
if len(self.privious_cards) == 0:
self.privious_cards.append(
list(self.cards.values())[0]) # TODO: Impelement default card config
self.privious_cards.append(self.get_default_card())
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.render_current_page(switchPages=True)
return
@@ -226,8 +249,10 @@ class LovelaceUIPanel:
# 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]
for e in self.current_card.entities:
if e.iid == iid:
entity_id = e.entity_id
entity_config = e.config
match btype:
case 'button':
@@ -236,8 +261,9 @@ class LovelaceUIPanel:
case 'navigate':
card_iid = entity_id.split(".")[1]
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()
# TODO Handle privious_cards empty with default card
self.render_current_page(switchPages=True)
else:
self.privious_cards.append(self.current_card)
@@ -245,7 +271,7 @@ class LovelaceUIPanel:
self.render_current_page(switchPages=True)
# send ha stuff to ha
case _:
ha_control.handle_buttons(entity_id, btype, value)
ha_control.handle_buttons(entity_id, btype, value, entity_config=entity_config)
case 'cardUnlock-unlock':
card_iid = entity_id.split(".")[1]
if int(self.current_card.config.get("pin")) == int(value):
@@ -260,6 +286,15 @@ class LovelaceUIPanel:
# 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))
for e in self.current_card.entities:
if e.iid == iid:
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))