mirror of
https://github.com/joBr99/nspanel-lovelace-ui.git
synced 2026-02-22 05:58:39 +01:00
Compare commits
364 Commits
01265faef9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d745d63def | ||
|
|
101e69bbde | ||
|
|
d55a63b07c | ||
|
|
52a041521d | ||
|
|
f7fa1653c6 | ||
|
|
b925ea8a2d | ||
|
|
da095b1591 | ||
|
|
ca6e271a54 | ||
|
|
698ad7c771 | ||
|
|
b0f40a1a87 | ||
|
|
2cf7bcb63e | ||
|
|
05d9ff52ca | ||
|
|
bb79cd8f85 | ||
|
|
00f7119cd2 | ||
|
|
8e6f923839 | ||
|
|
cf396b0259 | ||
|
|
42c27a3794 | ||
|
|
c65f1935e5 | ||
|
|
17284d83ca | ||
|
|
2853073a59 | ||
|
|
8de034adf9 | ||
|
|
330fa9bfd4 | ||
|
|
ec971f5f3e | ||
|
|
41f4062ab8 | ||
|
|
f3fffe7b70 | ||
|
|
c4b6a8bd8a | ||
|
|
81d876b53b | ||
|
|
d15cb218ce | ||
|
|
114f630b8a | ||
|
|
53b627be88 | ||
|
|
f2e1a7263d | ||
|
|
1e2f89ed1d | ||
|
|
b6f36d4eac | ||
|
|
fa239f8bf0 | ||
|
|
2d1719673c | ||
|
|
e9c275216c | ||
|
|
b226b61281 | ||
|
|
056d8f95c2 | ||
|
|
52c695cf1c | ||
|
|
8805e2189c | ||
|
|
341cdb47ab | ||
|
|
3b25d47bc7 | ||
|
|
a721d4ccd7 | ||
|
|
044abda65b | ||
|
|
155b08d6d5 | ||
|
|
c038745d1b | ||
|
|
154c9aced1 | ||
|
|
f5119f86ac | ||
|
|
77b7400b04 | ||
|
|
995e7eebd2 | ||
|
|
c91ca0912a | ||
|
|
a6742f88c8 | ||
|
|
9f146ec8b3 | ||
|
|
0cccfac4a6 | ||
|
|
fbe5f9658e | ||
|
|
603d207e12 | ||
|
|
338c3af940 | ||
|
|
ce6ff734d1 | ||
|
|
4308c12508 | ||
|
|
ea0f3cc411 | ||
|
|
2b4c8d3b94 | ||
|
|
ff25f07b8f | ||
|
|
59efd21251 | ||
|
|
20ae492cf3 | ||
|
|
512a89b7d6 | ||
|
|
a6c366ed41 | ||
|
|
e13d1aaecb | ||
|
|
3b6d53c78c | ||
|
|
f789e33fe5 | ||
|
|
22455546f6 | ||
|
|
e0e9815911 | ||
|
|
3e0d0e8854 | ||
|
|
98f70b20f0 | ||
|
|
4c02b5bc26 | ||
|
|
fb841abf45 | ||
|
|
bff89f0364 | ||
|
|
27ccc0cfe7 | ||
|
|
22a54c88c8 | ||
|
|
87c58cc62d | ||
|
|
1ee19213bd | ||
|
|
0c3d173f40 | ||
|
|
cc201fb7fe | ||
|
|
44d2c6fbfc | ||
|
|
9cd2f6a464 | ||
|
|
7473d13762 | ||
|
|
f57b3fe8e4 | ||
|
|
9b1558c9a6 | ||
|
|
6181dec958 | ||
|
|
a94cf0cef3 | ||
|
|
d01ccede57 | ||
|
|
65be5ffeb0 | ||
|
|
d53afb0b20 | ||
|
|
9822870fc9 | ||
|
|
8a54d1422c | ||
|
|
3f573557f0 | ||
|
|
c2ca3b26d1 | ||
|
|
476a252a92 | ||
|
|
0af779973b | ||
|
|
8e0609a781 | ||
|
|
5dab816259 | ||
|
|
5f8409f5f1 | ||
|
|
3d85e86a95 | ||
|
|
85de880cda | ||
|
|
98269b19aa | ||
|
|
d77382ee88 | ||
|
|
e925d133d2 | ||
|
|
5ef3e8132b | ||
|
|
662b79a389 | ||
|
|
c984ff53a3 | ||
|
|
f8b748a418 | ||
|
|
bc31670760 | ||
|
|
cbf6abf4dd | ||
|
|
5c85e4a6e1 | ||
|
|
cbede2412e | ||
|
|
82d22743cc | ||
|
|
640d0dfa14 | ||
|
|
ccc62d1e6a | ||
|
|
6cec0245a3 | ||
|
|
f3c98adf06 | ||
|
|
f4487e4285 | ||
|
|
8e2780b2cb | ||
|
|
0372221973 | ||
|
|
947ef2d592 | ||
|
|
a1f39236c4 | ||
|
|
4cdd1ed586 | ||
|
|
1836d29931 | ||
|
|
fca29cfbd8 | ||
|
|
b27910c1af | ||
|
|
f046ae6031 | ||
|
|
4475ab1277 | ||
|
|
9afdb71a7c | ||
|
|
c84d78551f | ||
|
|
5e2a4b17ae | ||
|
|
acdba468b3 | ||
|
|
5803a489f5 | ||
|
|
54c8d302a8 | ||
|
|
8059905579 | ||
|
|
4dc39c1b79 | ||
|
|
16909f7e7f | ||
|
|
397932a6bc | ||
|
|
04ac10b453 | ||
|
|
ff103927e8 | ||
|
|
c6e94f80de | ||
|
|
5d4ae6247d | ||
|
|
8b63322e50 | ||
|
|
7fd5b3967a | ||
|
|
1d719446c0 | ||
|
|
3913b86228 | ||
|
|
3fddbfe451 | ||
|
|
c7b492c276 | ||
|
|
2e0cff80c1 | ||
|
|
3943dee733 | ||
|
|
41e7f3c3e2 | ||
|
|
5e373ad856 | ||
|
|
0d3d40cca2 | ||
|
|
221063a5a6 | ||
|
|
8e9b8ab476 | ||
|
|
1c6be4681e | ||
|
|
7b4f4cd863 | ||
|
|
9b285efe2d | ||
|
|
22e96f2b3a | ||
|
|
23da51f662 | ||
|
|
b395a08b6e | ||
|
|
ac07dbb185 | ||
|
|
86ab806b6e | ||
|
|
89d08ea507 | ||
|
|
deacd17fc9 | ||
|
|
4372e4193e | ||
|
|
d4cb8b548d | ||
|
|
faa2880e8d | ||
|
|
eeeccc786d | ||
|
|
3bcf2372b0 | ||
|
|
9612f882a5 | ||
|
|
aa29552a90 | ||
|
|
a38393338a | ||
|
|
d2e953410c | ||
|
|
0ac345328b | ||
|
|
11bf450e4c | ||
|
|
e54faea639 | ||
|
|
25062483c9 | ||
|
|
f58be676bf | ||
|
|
cc147d29ab | ||
|
|
78d20f81d2 | ||
|
|
29a3c8123c | ||
|
|
7807651223 | ||
|
|
deb7f6788e | ||
|
|
9ca7a4e829 | ||
|
|
855ab2b28c | ||
|
|
b1b042a25f | ||
|
|
3a82af67b1 | ||
|
|
84e2105361 | ||
|
|
bae90b2b55 | ||
|
|
f6b605443d | ||
|
|
14d337891a | ||
|
|
a3df442a6a | ||
|
|
53af81f84d | ||
|
|
02332cc094 | ||
|
|
19964f4ea6 | ||
|
|
3e777246a0 | ||
|
|
737bb80af0 | ||
|
|
07155018ef | ||
|
|
b8c0939382 | ||
|
|
985d395ba3 | ||
|
|
9f8e899af7 | ||
|
|
5359ed5e4b | ||
|
|
081d176f24 | ||
|
|
0bfe72eec9 | ||
|
|
d417aa2fb9 | ||
|
|
d647fb3b4f | ||
|
|
ebf6300b06 | ||
|
|
3977f9aa53 | ||
|
|
8478194bb0 | ||
|
|
417c99cdb9 | ||
|
|
0e8849a382 | ||
|
|
dd9ccaf076 | ||
|
|
ff461d821c | ||
|
|
6d68165fb6 | ||
|
|
489fd23edb | ||
|
|
4b39d5b438 | ||
|
|
b5a5aa41c6 | ||
|
|
e878e08675 | ||
|
|
246a7f1922 | ||
|
|
c83921ca71 | ||
|
|
c9deae3d5c | ||
|
|
8184c10e93 | ||
|
|
98746cc8d1 | ||
|
|
79fe05eb9c | ||
|
|
b2dd46411a | ||
|
|
2c7dd23220 | ||
|
|
a873e3e29e | ||
|
|
6a3b984c6c | ||
|
|
ebbcf7c21e | ||
|
|
0526b26b4a | ||
|
|
c20dab43a6 | ||
|
|
918a859a74 | ||
|
|
b3d3902399 | ||
|
|
69e02b0886 | ||
|
|
74a602c34d | ||
|
|
710bb2d884 | ||
|
|
7396806fab | ||
|
|
a87044a2b9 | ||
|
|
0c54f747b9 | ||
|
|
d6955eaad7 | ||
|
|
5638720336 | ||
|
|
e33875e9f5 | ||
|
|
9532ca2442 | ||
|
|
47f59e1b63 | ||
|
|
b9195101da | ||
|
|
34a9424c29 | ||
|
|
54ce2a1c10 | ||
|
|
5166fb1f58 | ||
|
|
5e9434b7eb | ||
|
|
a7398e54e3 | ||
|
|
ac54e042ea | ||
|
|
4f17085a81 | ||
|
|
c864eb6e73 | ||
|
|
828aa8fd21 | ||
|
|
84615fad05 | ||
|
|
b90b50395c | ||
|
|
c27a23e5fe | ||
|
|
1b33ddb207 | ||
|
|
d68de45c3b | ||
|
|
5b46b39dac | ||
|
|
2d0201759d | ||
|
|
104788e2a4 | ||
|
|
4f1139a531 | ||
|
|
f673aad38e | ||
|
|
c8a8e1351c | ||
|
|
5d84b59bbd | ||
|
|
2a807702f4 | ||
|
|
448b9cb30c | ||
|
|
248dc3a1c9 | ||
|
|
cd76c0528e | ||
|
|
58e2febf64 | ||
|
|
0372b034f6 | ||
|
|
1d730a0ce5 | ||
|
|
1a629ee8d5 | ||
|
|
cd563ad1ce | ||
|
|
ec18b6349b | ||
|
|
4d7d87d62a | ||
|
|
15a021a58f | ||
|
|
63c85e6b96 | ||
|
|
6a94795a67 | ||
|
|
d302567369 | ||
|
|
f8d6a1543d | ||
|
|
6df5a68682 | ||
|
|
7e1cc36805 | ||
|
|
491ed21f98 | ||
|
|
e53cf5dab0 | ||
|
|
e853889247 | ||
|
|
19b0fe4052 | ||
|
|
d94d937d77 | ||
|
|
e5c1f0588a | ||
|
|
dd88ebe5da | ||
|
|
5536335ac9 | ||
|
|
df4fff6911 | ||
|
|
3dd83fde66 | ||
|
|
f50b1ececa | ||
|
|
ebee7b379e | ||
|
|
255db25f58 | ||
|
|
19050079d4 | ||
|
|
8d97f98a29 | ||
|
|
137ca5855e | ||
|
|
7707b48622 | ||
|
|
193546d1ed | ||
|
|
6703bca1d0 | ||
|
|
5739947586 | ||
|
|
5e1a7f2102 | ||
|
|
a0e574391b | ||
|
|
bd107d930a | ||
|
|
66f83732bb | ||
|
|
e796891d8e | ||
|
|
ba46bc9189 | ||
|
|
64ff369a90 | ||
|
|
92616429ba | ||
|
|
377383d672 | ||
|
|
52d405a6d6 | ||
|
|
2e1492c4fa | ||
|
|
16673df8cf | ||
|
|
cb4c26acfd | ||
|
|
fda7ca4574 | ||
|
|
8e2e8d1e82 | ||
|
|
4e36f47774 | ||
|
|
858dac73d0 | ||
|
|
3cccefb715 | ||
|
|
44640f33d2 | ||
|
|
2ae3b9bd8e | ||
|
|
2db991a371 | ||
|
|
2e52abd76c | ||
|
|
03c3acd214 | ||
|
|
c26b277c56 | ||
|
|
3f7fd40d17 | ||
|
|
0b01c0d236 | ||
|
|
3107b73430 | ||
|
|
5d421ae525 | ||
|
|
56a8495787 | ||
|
|
a12bc03dd7 | ||
|
|
6b1a65f8f4 | ||
|
|
9d94155480 | ||
|
|
3b46759134 | ||
|
|
8d21c653ae | ||
|
|
d983c44db7 | ||
|
|
467a1d92bb | ||
|
|
157d3e3e66 | ||
|
|
51bb320dce | ||
|
|
12c99c6857 | ||
|
|
62e905f336 | ||
|
|
9b5964a758 | ||
|
|
9c49a9c67d | ||
|
|
0a2461f4a5 | ||
|
|
49577ddbb6 | ||
|
|
6172b0c35f | ||
|
|
e7cc10692b | ||
|
|
221d2c717d | ||
|
|
2b54f742c5 | ||
|
|
f02eddcebe | ||
|
|
c25a5cef67 | ||
|
|
0f69ee951c | ||
|
|
fd6650db50 | ||
|
|
f2ad80665a | ||
|
|
cff9c94c27 | ||
|
|
b6fdc12820 | ||
|
|
0efbd9e23c | ||
|
|
cd0c015fea |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -58,5 +58,5 @@ _If applicable, add screenshots/pictures to help explain your problem._
|
|||||||
_Add any other context about the problem here._
|
_Add any other context about the problem here._
|
||||||
_Please note here in case you are using ioBroker_
|
_Please note here in case you are using ioBroker_
|
||||||
|
|
||||||
### PANEL / FIRMWARE VERION
|
### PANEL / FIRMWARE VERSION
|
||||||
_Please add the Panel/Firmware Version you are using (EU, US-L or US-P)_
|
_Please add the Panel/Firmware Version you are using (EU, US-L or US-P)_
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feature-request.md
vendored
2
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@@ -24,5 +24,5 @@ _A clear and concise description of what the feature should do._
|
|||||||
### ADDITIONAL CONTEXT
|
### ADDITIONAL CONTEXT
|
||||||
_Add any other context about the problem here._
|
_Add any other context about the problem here._
|
||||||
|
|
||||||
### PANEL / FIRMWARE VERION
|
### PANEL / FIRMWARE VERSION
|
||||||
_Please add the Panel/Firmware Version you are using (EU, US-L or US-P)_
|
_Please add the Panel/Firmware Version you are using (EU, US-L or US-P)_
|
||||||
|
|||||||
8
.github/workflows/builder.yaml
vendored
8
.github/workflows/builder.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
changed: ${{ steps.changed_addons.outputs.changed }}
|
changed: ${{ steps.changed_addons.outputs.changed }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed_files
|
id: changed_files
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Get information
|
- name: Get information
|
||||||
id: info
|
id: info
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
if: env.BUILD_ARGS != '--test'
|
if: env.BUILD_ARGS != '--test'
|
||||||
uses: docker/login-action@v3.0.0
|
uses: docker/login-action@v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -100,7 +100,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build ${{ matrix.addon }} add-on
|
- name: Build ${{ matrix.addon }} add-on
|
||||||
if: steps.check.outputs.build_arch == 'true'
|
if: steps.check.outputs.build_arch == 'true'
|
||||||
uses: home-assistant/builder@2024.01.0
|
uses: home-assistant/builder@2025.09.0
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
${{ env.BUILD_ARGS }} \
|
${{ env.BUILD_ARGS }} \
|
||||||
|
|||||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -43,11 +43,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@v4
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -72,4 +72,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v4
|
||||||
|
|||||||
28
.github/workflows/docs-dev.yml
vendored
28
.github/workflows/docs-dev.yml
vendored
@@ -1,28 +0,0 @@
|
|||||||
name: docs-ci
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
paths:
|
|
||||||
- docs/*
|
|
||||||
- .github/workflows/docs.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 deploy --push --update-aliases dev
|
|
||||||
43
.github/workflows/docs-release.yml
vendored
43
.github/workflows/docs-release.yml
vendored
@@ -6,24 +6,51 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- docs/*
|
- docs/**
|
||||||
|
- docs-standalone/**
|
||||||
- .github/workflows/docs-release.yml
|
- .github/workflows/docs-release.yml
|
||||||
- mkdocs.yml
|
- mkdocs.yml
|
||||||
|
- docs-standalone/mkdocs.yml
|
||||||
- HMI/README.md
|
- HMI/README.md
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
- run: pip install mkdocs-material mkdocs-video markdown-include mike
|
- run: pip install zensical
|
||||||
- run: cp HMI/README.md docs/hmi-serial-protocol.md
|
- run: cp HMI/README.md docs/hmi-serial-protocol.md
|
||||||
- run: git config --global user.name Docs deploy
|
- run: zensical build --config-file mkdocs.yml
|
||||||
- run: git config --global user.email docs@dummy.bot.com
|
- run: mv site _site_main
|
||||||
- run: mike set-default stable
|
- run: zensical build --config-file docs-standalone/mkdocs.yml
|
||||||
- run: mike deploy --push --update-aliases stable
|
- run: mkdir -p _site/standalone _site/stable
|
||||||
|
- run: cp -a _site_main/. _site/
|
||||||
|
- run: cp -a _site_main/. _site/stable/
|
||||||
|
- run: |
|
||||||
|
if [ -d site-standalone ]; then
|
||||||
|
cp -a site-standalone/. _site/standalone/
|
||||||
|
elif [ -d docs-standalone/site ]; then
|
||||||
|
cp -a docs-standalone/site/. _site/standalone/
|
||||||
|
elif [ -d site ]; then
|
||||||
|
cp -a site/. _site/standalone/
|
||||||
|
elif [ -d docs-standalone/build ]; then
|
||||||
|
cp -a docs-standalone/build/. _site/standalone/
|
||||||
|
elif [ -d build ]; then
|
||||||
|
cp -a build/. _site/standalone/
|
||||||
|
else
|
||||||
|
echo "Standalone docs output not found (tried site-standalone, docs-standalone/site, site, docs-standalone/build, build)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- uses: peaceiris/actions-gh-pages@v4
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_branch: gh-pages
|
||||||
|
publish_dir: _site
|
||||||
|
force_orphan: true
|
||||||
|
|||||||
2
.github/workflows/hacs-validation.yaml
vendored
2
.github/workflows/hacs-validation.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
name: HACS Action
|
name: HACS Action
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
steps:
|
steps:
|
||||||
- uses: "actions/checkout@v4"
|
- uses: "actions/checkout@v6"
|
||||||
- name: HACS Action
|
- name: HACS Action
|
||||||
uses: "hacs/action@main"
|
uses: "hacs/action@main"
|
||||||
with:
|
with:
|
||||||
|
|||||||
4
.github/workflows/iobroker-localization.yml
vendored
4
.github/workflows/iobroker-localization.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
gen-ioBroker-localization:
|
gen-ioBroker-localization:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
python HMI/code_gen/localization/iobroker.py
|
python HMI/code_gen/localization/iobroker.py
|
||||||
mv ioBroker_NSPanel_locales.json ioBroker/ioBroker_NSPanel_locales.json
|
mv ioBroker_NSPanel_locales.json ioBroker/ioBroker_NSPanel_locales.json
|
||||||
|
|
||||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
- uses: stefanzweifel/git-auto-commit-action@v7
|
||||||
with:
|
with:
|
||||||
commit_message: Update iobroker localization file
|
commit_message: Update iobroker localization file
|
||||||
#file_pattern: "**.txt"
|
#file_pattern: "**.txt"
|
||||||
|
|||||||
6
.github/workflows/lint.yaml
vendored
6
.github/workflows/lint.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
addons: ${{ steps.addons.outputs.addons_list }}
|
addons: ${{ steps.addons.outputs.addons_list }}
|
||||||
steps:
|
steps:
|
||||||
- name: ⤵️ Check out code from GitHub
|
- name: ⤵️ Check out code from GitHub
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: 🔍 Find add-on directories
|
- name: 🔍 Find add-on directories
|
||||||
id: addons
|
id: addons
|
||||||
@@ -33,9 +33,9 @@ jobs:
|
|||||||
path: ${{ fromJson(needs.find.outputs.addons) }}
|
path: ${{ fromJson(needs.find.outputs.addons) }}
|
||||||
steps:
|
steps:
|
||||||
- name: ⤵️ Check out code from GitHub
|
- name: ⤵️ Check out code from GitHub
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: 🚀 Run Home Assistant Add-on Lint
|
- name: 🚀 Run Home Assistant Add-on Lint
|
||||||
uses: frenck/action-addon-linter@v2.15
|
uses: frenck/action-addon-linter@v2.21
|
||||||
with:
|
with:
|
||||||
path: "./${{ matrix.path }}"
|
path: "./${{ matrix.path }}"
|
||||||
|
|||||||
4
.github/workflows/nextion2text.yml
vendored
4
.github/workflows/nextion2text.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
find -name "**.txt"
|
find -name "**.txt"
|
||||||
rm Nextion2Text.py* ignore-id.py out.txt
|
rm Nextion2Text.py* ignore-id.py out.txt
|
||||||
|
|
||||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
- uses: stefanzweifel/git-auto-commit-action@v7
|
||||||
with:
|
with:
|
||||||
commit_message: ${{ steps.last-commit-message.outputs.msg }} (add nextion2text)
|
commit_message: ${{ steps.last-commit-message.outputs.msg }} (add nextion2text)
|
||||||
#file_pattern: "**.txt"
|
#file_pattern: "**.txt"
|
||||||
|
|||||||
27
.gitignore
vendored
27
.gitignore
vendored
@@ -7,3 +7,30 @@ panels.yaml
|
|||||||
|
|
||||||
# don't add Webstorm project stuff
|
# don't add Webstorm project stuff
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
__MACOSX/
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
Icon[
|
||||||
|
]
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|||||||
@@ -102,6 +102,28 @@
|
|||||||
│ crcputs sys0,2
|
│ crcputs sys0,2
|
||||||
│ crcputs tSend.txt,0
|
│ crcputs tSend.txt,0
|
||||||
│ //send cmd
|
│ //send cmd
|
||||||
|
│ --- HMI/n2t-out/popupLight.txt
|
||||||
|
├── +++ HMI/US/landscape/n2t-out/popupLight.txt
|
||||||
|
│ @@ -453,19 +453,14 @@
|
||||||
|
│ ucopy strCommand.txt,4,payloadLength-5,0
|
||||||
|
│ // write instruction to tInstuction (debug output, but used as variable here, ui elements will be disabled by default)
|
||||||
|
│ spstr strCommand.txt,tInstruction.txt,"~",0
|
||||||
|
│ spstr strCommand.txt,tTmp.txt,"~",1
|
||||||
|
│ if(tInstruction.txt=="entityUpdateDetail"&&entn.txt==tTmp.txt)
|
||||||
|
│ {
|
||||||
|
│ // change icon
|
||||||
|
│ - spstr strCommand.txt,tTmp.txt,"~",2
|
||||||
|
│ - if(tTmp.txt!="")
|
||||||
|
│ - {
|
||||||
|
│ - tIcon1.txt=tTmp.txt
|
||||||
|
│ - }
|
||||||
|
│ //spstr strCommand.txt,tIcon1.txt,"~",2
|
||||||
|
│ vis tIcon1,1
|
||||||
|
│ // change icon color
|
||||||
|
│ spstr strCommand.txt,tTmp.txt,"~",3
|
||||||
|
│ covx tTmp.txt,sys0,0,0
|
||||||
|
│ tIcon1.pco=sys0
|
||||||
|
│ // get Button State
|
||||||
│ --- HMI/n2t-out/popupNotify.txt
|
│ --- HMI/n2t-out/popupNotify.txt
|
||||||
├── +++ HMI/US/landscape/n2t-out/popupNotify.txt
|
├── +++ HMI/US/landscape/n2t-out/popupNotify.txt
|
||||||
│ @@ -439,18 +439,14 @@
|
│ @@ -439,18 +439,14 @@
|
||||||
@@ -314,7 +336,7 @@
|
|||||||
│ Value: 0
|
│ Value: 0
|
||||||
│
|
│
|
||||||
│ Variable (int32) xc1
|
│ Variable (int32) xc1
|
||||||
│ @@ -370,145 +336,50 @@
|
│ @@ -370,165 +336,50 @@
|
||||||
│ Scope : local
|
│ Scope : local
|
||||||
│ Dragging : 0
|
│ Dragging : 0
|
||||||
│ Send Component ID : disabled
|
│ Send Component ID : disabled
|
||||||
@@ -325,6 +347,10 @@
|
|||||||
│ - Events
|
│ - Events
|
||||||
│ - Touch Press Event
|
│ - Touch Press Event
|
||||||
│ - tSend.txt="event,buttonPress2,"+entn1.txt+",button"
|
│ - tSend.txt="event,buttonPress2,"+entn1.txt+",button"
|
||||||
|
│ - if(entn1.txt=="")
|
||||||
|
│ - {
|
||||||
|
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
│ - }
|
||||||
│ - //send calc crc
|
│ - //send calc crc
|
||||||
│ - btlen tSend.txt,sys0
|
│ - btlen tSend.txt,sys0
|
||||||
│ - crcrest 1,0xffff // reset CRC
|
│ - crcrest 1,0xffff // reset CRC
|
||||||
@@ -349,6 +375,10 @@
|
|||||||
│ - Events
|
│ - Events
|
||||||
│ - Touch Press Event
|
│ - Touch Press Event
|
||||||
│ - tSend.txt="event,buttonPress2,"+entn2.txt+",button"
|
│ - tSend.txt="event,buttonPress2,"+entn2.txt+",button"
|
||||||
|
│ - if(entn2.txt=="")
|
||||||
|
│ - {
|
||||||
|
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
│ - }
|
||||||
│ - //send calc crc
|
│ - //send calc crc
|
||||||
│ - btlen tSend.txt,sys0
|
│ - btlen tSend.txt,sys0
|
||||||
│ - crcrest 1,0xffff // reset CRC
|
│ - crcrest 1,0xffff // reset CRC
|
||||||
@@ -373,6 +403,10 @@
|
|||||||
│ - Events
|
│ - Events
|
||||||
│ - Touch Press Event
|
│ - Touch Press Event
|
||||||
│ - tSend.txt="event,buttonPress2,"+entn3.txt+",button"
|
│ - tSend.txt="event,buttonPress2,"+entn3.txt+",button"
|
||||||
|
│ - if(entn3.txt=="")
|
||||||
|
│ - {
|
||||||
|
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
│ - }
|
||||||
│ - //send calc crc
|
│ - //send calc crc
|
||||||
│ - btlen tSend.txt,sys0
|
│ - btlen tSend.txt,sys0
|
||||||
│ - crcrest 1,0xffff // reset CRC
|
│ - crcrest 1,0xffff // reset CRC
|
||||||
@@ -397,6 +431,10 @@
|
|||||||
│ - Events
|
│ - Events
|
||||||
│ - Touch Press Event
|
│ - Touch Press Event
|
||||||
│ - tSend.txt="event,buttonPress2,"+entn4.txt+",button"
|
│ - tSend.txt="event,buttonPress2,"+entn4.txt+",button"
|
||||||
|
│ - if(entn4.txt=="")
|
||||||
|
│ - {
|
||||||
|
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
│ - }
|
||||||
│ - //send calc crc
|
│ - //send calc crc
|
||||||
│ - btlen tSend.txt,sys0
|
│ - btlen tSend.txt,sys0
|
||||||
│ - crcrest 1,0xffff // reset CRC
|
│ - crcrest 1,0xffff // reset CRC
|
||||||
@@ -421,6 +459,10 @@
|
|||||||
│ - Events
|
│ - Events
|
||||||
│ - Touch Press Event
|
│ - Touch Press Event
|
||||||
│ - tSend.txt="event,buttonPress2,"+entn5.txt+",button"
|
│ - tSend.txt="event,buttonPress2,"+entn5.txt+",button"
|
||||||
|
│ - if(entn5.txt=="")
|
||||||
|
│ - {
|
||||||
|
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
│ - }
|
||||||
│ - //send calc crc
|
│ - //send calc crc
|
||||||
│ - btlen tSend.txt,sys0
|
│ - btlen tSend.txt,sys0
|
||||||
│ - crcrest 1,0xffff // reset CRC
|
│ - crcrest 1,0xffff // reset CRC
|
||||||
@@ -460,7 +502,7 @@
|
|||||||
│ Send Component ID : disabled
|
│ Send Component ID : disabled
|
||||||
│ Associated Keyboard: none
|
│ Associated Keyboard: none
|
||||||
│ Text : PM
|
│ Text : PM
|
||||||
│ @@ -690,15 +561,14 @@
|
│ @@ -710,15 +561,14 @@
|
||||||
│ if(tTmp.txt!="")
|
│ if(tTmp.txt!="")
|
||||||
│ {
|
│ {
|
||||||
│ covx tTmp.txt,defaultFontColor,0,0
|
│ covx tTmp.txt,defaultFontColor,0,0
|
||||||
@@ -476,7 +518,7 @@
|
|||||||
│ if(tAMPM.txt=="")
|
│ if(tAMPM.txt=="")
|
||||||
│ {
|
│ {
|
||||||
│ vis tAMPM,0
|
│ vis tAMPM,0
|
||||||
│ @@ -819,44 +689,34 @@
|
│ @@ -839,44 +689,34 @@
|
||||||
│ //e6Val
|
│ //e6Val
|
||||||
│ spstr strCommand.txt,e6Val.txt,"~",60
|
│ spstr strCommand.txt,e6Val.txt,"~",60
|
||||||
│ //f1Icon
|
│ //f1Icon
|
||||||
@@ -521,22 +563,3 @@
|
|||||||
│ spstr strCommand.txt,tNotifyText.txt,"~",2
|
│ spstr strCommand.txt,tNotifyText.txt,"~",2
|
||||||
│ if(tNotifyHead.txt!=""||tNotifyText.txt!="")
|
│ if(tNotifyHead.txt!=""||tNotifyText.txt!="")
|
||||||
│ {
|
│ {
|
||||||
│ @@ -952,14 +812,18 @@
|
|
||||||
│ {
|
|
||||||
│ page cardPower
|
|
||||||
│ }
|
|
||||||
│ if(tId.txt=="cardChart")
|
|
||||||
│ {
|
|
||||||
│ page cardChart
|
|
||||||
│ }
|
|
||||||
│ + if(tId.txt=="cardLChart")
|
|
||||||
│ + {
|
|
||||||
│ + page cardLChart
|
|
||||||
│ + }
|
|
||||||
│ }
|
|
||||||
│ if(tInstruction.txt=="timeout")
|
|
||||||
│ {
|
|
||||||
│ //set timeout to global var
|
|
||||||
│ spstr strCommand.txt,tTmp.txt,"~",1
|
|
||||||
│ covx tTmp.txt,sleepTimeout,0,0
|
|
||||||
│ }
|
|
||||||
|
|||||||
@@ -1985,6 +1985,26 @@
|
|||||||
│ Variable (string) entn
|
│ Variable (string) entn
|
||||||
│ Attributes
|
│ Attributes
|
||||||
│ Scope : local
|
│ Scope : local
|
||||||
|
│ @@ -453,19 +453,14 @@
|
||||||
|
│ ucopy strCommand.txt,4,payloadLength-5,0
|
||||||
|
│ // write instruction to tInstuction (debug output, but used as variable here, ui elements will be disabled by default)
|
||||||
|
│ spstr strCommand.txt,tInstruction.txt,"~",0
|
||||||
|
│ spstr strCommand.txt,tTmp.txt,"~",1
|
||||||
|
│ if(tInstruction.txt=="entityUpdateDetail"&&entn.txt==tTmp.txt)
|
||||||
|
│ {
|
||||||
|
│ // change icon
|
||||||
|
│ - spstr strCommand.txt,tTmp.txt,"~",2
|
||||||
|
│ - if(tTmp.txt!="")
|
||||||
|
│ - {
|
||||||
|
│ - tIcon1.txt=tTmp.txt
|
||||||
|
│ - }
|
||||||
|
│ //spstr strCommand.txt,tIcon1.txt,"~",2
|
||||||
|
│ vis tIcon1,1
|
||||||
|
│ // change icon color
|
||||||
|
│ spstr strCommand.txt,tTmp.txt,"~",3
|
||||||
|
│ covx tTmp.txt,sys0,0,0
|
||||||
|
│ tIcon1.pco=sys0
|
||||||
|
│ // get Button State
|
||||||
│ --- HMI/n2t-out/popupNotify.txt
|
│ --- HMI/n2t-out/popupNotify.txt
|
||||||
├── +++ HMI/US/portrait/n2t-out/popupNotify.txt
|
├── +++ HMI/US/portrait/n2t-out/popupNotify.txt
|
||||||
│ @@ -348,15 +348,15 @@
|
│ @@ -348,15 +348,15 @@
|
||||||
@@ -2259,7 +2279,7 @@
|
|||||||
│ Value: 0
|
│ Value: 0
|
||||||
│
|
│
|
||||||
│ Variable (int32) xc1
|
│ Variable (int32) xc1
|
||||||
│ @@ -370,145 +324,50 @@
|
│ @@ -370,165 +324,50 @@
|
||||||
│ Scope : local
|
│ Scope : local
|
||||||
│ Dragging : 0
|
│ Dragging : 0
|
||||||
│ Send Component ID : disabled
|
│ Send Component ID : disabled
|
||||||
@@ -2270,6 +2290,10 @@
|
|||||||
│ - Events
|
│ - Events
|
||||||
│ - Touch Press Event
|
│ - Touch Press Event
|
||||||
│ - tSend.txt="event,buttonPress2,"+entn1.txt+",button"
|
│ - tSend.txt="event,buttonPress2,"+entn1.txt+",button"
|
||||||
|
│ - if(entn1.txt=="")
|
||||||
|
│ - {
|
||||||
|
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
│ - }
|
||||||
│ - //send calc crc
|
│ - //send calc crc
|
||||||
│ - btlen tSend.txt,sys0
|
│ - btlen tSend.txt,sys0
|
||||||
│ - crcrest 1,0xffff // reset CRC
|
│ - crcrest 1,0xffff // reset CRC
|
||||||
@@ -2294,6 +2318,10 @@
|
|||||||
│ - Events
|
│ - Events
|
||||||
│ - Touch Press Event
|
│ - Touch Press Event
|
||||||
│ - tSend.txt="event,buttonPress2,"+entn2.txt+",button"
|
│ - tSend.txt="event,buttonPress2,"+entn2.txt+",button"
|
||||||
|
│ - if(entn2.txt=="")
|
||||||
|
│ - {
|
||||||
|
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
│ - }
|
||||||
│ - //send calc crc
|
│ - //send calc crc
|
||||||
│ - btlen tSend.txt,sys0
|
│ - btlen tSend.txt,sys0
|
||||||
│ - crcrest 1,0xffff // reset CRC
|
│ - crcrest 1,0xffff // reset CRC
|
||||||
@@ -2318,6 +2346,10 @@
|
|||||||
│ - Events
|
│ - Events
|
||||||
│ - Touch Press Event
|
│ - Touch Press Event
|
||||||
│ - tSend.txt="event,buttonPress2,"+entn3.txt+",button"
|
│ - tSend.txt="event,buttonPress2,"+entn3.txt+",button"
|
||||||
|
│ - if(entn3.txt=="")
|
||||||
|
│ - {
|
||||||
|
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
│ - }
|
||||||
│ - //send calc crc
|
│ - //send calc crc
|
||||||
│ - btlen tSend.txt,sys0
|
│ - btlen tSend.txt,sys0
|
||||||
│ - crcrest 1,0xffff // reset CRC
|
│ - crcrest 1,0xffff // reset CRC
|
||||||
@@ -2342,6 +2374,10 @@
|
|||||||
│ - Events
|
│ - Events
|
||||||
│ - Touch Press Event
|
│ - Touch Press Event
|
||||||
│ - tSend.txt="event,buttonPress2,"+entn4.txt+",button"
|
│ - tSend.txt="event,buttonPress2,"+entn4.txt+",button"
|
||||||
|
│ - if(entn4.txt=="")
|
||||||
|
│ - {
|
||||||
|
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
│ - }
|
||||||
│ - //send calc crc
|
│ - //send calc crc
|
||||||
│ - btlen tSend.txt,sys0
|
│ - btlen tSend.txt,sys0
|
||||||
│ - crcrest 1,0xffff // reset CRC
|
│ - crcrest 1,0xffff // reset CRC
|
||||||
@@ -2366,6 +2402,10 @@
|
|||||||
│ - Events
|
│ - Events
|
||||||
│ - Touch Press Event
|
│ - Touch Press Event
|
||||||
│ - tSend.txt="event,buttonPress2,"+entn5.txt+",button"
|
│ - tSend.txt="event,buttonPress2,"+entn5.txt+",button"
|
||||||
|
│ - if(entn5.txt=="")
|
||||||
|
│ - {
|
||||||
|
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
│ - }
|
||||||
│ - //send calc crc
|
│ - //send calc crc
|
||||||
│ - btlen tSend.txt,sys0
|
│ - btlen tSend.txt,sys0
|
||||||
│ - crcrest 1,0xffff // reset CRC
|
│ - crcrest 1,0xffff // reset CRC
|
||||||
@@ -2405,7 +2445,7 @@
|
|||||||
│ Send Component ID : disabled
|
│ Send Component ID : disabled
|
||||||
│ Associated Keyboard: none
|
│ Associated Keyboard: none
|
||||||
│ Text : PM
|
│ Text : PM
|
||||||
│ @@ -690,15 +549,14 @@
|
│ @@ -710,15 +549,14 @@
|
||||||
│ if(tTmp.txt!="")
|
│ if(tTmp.txt!="")
|
||||||
│ {
|
│ {
|
||||||
│ covx tTmp.txt,defaultFontColor,0,0
|
│ covx tTmp.txt,defaultFontColor,0,0
|
||||||
@@ -2421,7 +2461,7 @@
|
|||||||
│ if(tAMPM.txt=="")
|
│ if(tAMPM.txt=="")
|
||||||
│ {
|
│ {
|
||||||
│ vis tAMPM,0
|
│ vis tAMPM,0
|
||||||
│ @@ -819,44 +677,34 @@
|
│ @@ -839,44 +677,34 @@
|
||||||
│ //e6Val
|
│ //e6Val
|
||||||
│ spstr strCommand.txt,e6Val.txt,"~",60
|
│ spstr strCommand.txt,e6Val.txt,"~",60
|
||||||
│ //f1Icon
|
│ //f1Icon
|
||||||
@@ -2466,22 +2506,3 @@
|
|||||||
│ spstr strCommand.txt,tNotifyText.txt,"~",2
|
│ spstr strCommand.txt,tNotifyText.txt,"~",2
|
||||||
│ if(tNotifyHead.txt!=""||tNotifyText.txt!="")
|
│ if(tNotifyHead.txt!=""||tNotifyText.txt!="")
|
||||||
│ {
|
│ {
|
||||||
│ @@ -952,14 +800,18 @@
|
|
||||||
│ {
|
|
||||||
│ page cardPower
|
|
||||||
│ }
|
|
||||||
│ if(tId.txt=="cardChart")
|
|
||||||
│ {
|
|
||||||
│ page cardChart
|
|
||||||
│ }
|
|
||||||
│ + if(tId.txt=="cardLChart")
|
|
||||||
│ + {
|
|
||||||
│ + page cardLChart
|
|
||||||
│ + }
|
|
||||||
│ }
|
|
||||||
│ if(tInstruction.txt=="timeout")
|
|
||||||
│ {
|
|
||||||
│ //set timeout to global var
|
|
||||||
│ spstr strCommand.txt,tTmp.txt,"~",1
|
|
||||||
│ covx tTmp.txt,sleepTimeout,0,0
|
|
||||||
│ }
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
+++ /dev/fd/62 2024-01-20 19:56:08.135834636 +0000
|
+++ /dev/fd/62 2025-12-26 19:43:25.803166597 +0000
|
||||||
+I/n2t-out/Program.s.txt
|
+I/n2t-out/Program.s.txt
|
||||||
++ HMI/US/portrait/n2t-out/Program.s.txt
|
++ HMI/US/portrait/n2t-out/Program.s.txt
|
||||||
+1 +12,11 @@
|
+1 +12,11 @@
|
||||||
@@ -686,6 +686,13 @@
|
|||||||
+ covx tTmp.txt,sys0,0,0
|
+ covx tTmp.txt,sys0,0,0
|
||||||
+ hSlider6.maxval=sys0
|
+ hSlider6.maxval=sys0
|
||||||
+ }
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if(tInstruction.txt=="pageType")
|
||||||
|
+ {
|
||||||
|
+ sleepValue=0
|
||||||
|
+ //command format pageType,specialPageName
|
||||||
|
+ //write name of speical page to tId
|
||||||
|
+ spstr strCommand.txt,tId.txt,"~",1
|
||||||
+I/n2t-out/cardGrid.txt
|
+I/n2t-out/cardGrid.txt
|
||||||
++ HMI/US/portrait/n2t-out/cardGrid.txt
|
++ HMI/US/portrait/n2t-out/cardGrid.txt
|
||||||
+ +7,14 @@
|
+ +7,14 @@
|
||||||
@@ -939,13 +946,6 @@
|
|||||||
+ spstr strCommand.txt,tEntity9.txt,"~",66
|
+ spstr strCommand.txt,tEntity9.txt,"~",66
|
||||||
+ vis tEntity9,1
|
+ vis tEntity9,1
|
||||||
+ }
|
+ }
|
||||||
+ }
|
|
||||||
+ if(tInstruction.txt=="pageType")
|
|
||||||
+ {
|
|
||||||
+ sleepValue=0
|
|
||||||
+ //command format pageType,specialPageName
|
|
||||||
+ //write name of speical page to tId
|
|
||||||
+ spstr strCommand.txt,tId.txt,"~",1
|
|
||||||
+I/n2t-out/cardLChart.txt
|
+I/n2t-out/cardLChart.txt
|
||||||
++ HMI/US/portrait/n2t-out/cardLChart.txt
|
++ HMI/US/portrait/n2t-out/cardLChart.txt
|
||||||
+ +7,14 @@
|
+ +7,14 @@
|
||||||
@@ -1527,6 +1527,26 @@
|
|||||||
+e (string) entn
|
+e (string) entn
|
||||||
+ributes
|
+ributes
|
||||||
+ Scope : local
|
+ Scope : local
|
||||||
|
+19 +453,14 @@
|
||||||
|
+ ucopy strCommand.txt,4,payloadLength-5,0
|
||||||
|
+ // write instruction to tInstuction (debug output, but used as variable here, ui elements will be disabled by default)
|
||||||
|
+ spstr strCommand.txt,tInstruction.txt,"~",0
|
||||||
|
+ spstr strCommand.txt,tTmp.txt,"~",1
|
||||||
|
+ if(tInstruction.txt=="entityUpdateDetail"&&entn.txt==tTmp.txt)
|
||||||
|
+ {
|
||||||
|
+ // change icon
|
||||||
|
+ spstr strCommand.txt,tTmp.txt,"~",2
|
||||||
|
+ if(tTmp.txt!="")
|
||||||
|
+ {
|
||||||
|
+ tIcon1.txt=tTmp.txt
|
||||||
|
+ }
|
||||||
|
+ //spstr strCommand.txt,tIcon1.txt,"~",2
|
||||||
|
+ vis tIcon1,1
|
||||||
|
+ // change icon color
|
||||||
|
+ spstr strCommand.txt,tTmp.txt,"~",3
|
||||||
|
+ covx tTmp.txt,sys0,0,0
|
||||||
|
+ tIcon1.pco=sys0
|
||||||
|
+ // get Button State
|
||||||
+I/n2t-out/popupNotify.txt
|
+I/n2t-out/popupNotify.txt
|
||||||
++ HMI/US/portrait/n2t-out/popupNotify.txt
|
++ HMI/US/portrait/n2t-out/popupNotify.txt
|
||||||
+15 +348,15 @@
|
+15 +348,15 @@
|
||||||
@@ -1801,7 +1821,7 @@
|
|||||||
+ Value: 0
|
+ Value: 0
|
||||||
+
|
+
|
||||||
+e (int32) xc1
|
+e (int32) xc1
|
||||||
+145 +324,50 @@
|
+165 +324,50 @@
|
||||||
+ Scope : local
|
+ Scope : local
|
||||||
+ Dragging : 0
|
+ Dragging : 0
|
||||||
+ Send Component ID : disabled
|
+ Send Component ID : disabled
|
||||||
@@ -1812,6 +1832,10 @@
|
|||||||
+nts
|
+nts
|
||||||
+ Touch Press Event
|
+ Touch Press Event
|
||||||
+ tSend.txt="event,buttonPress2,"+entn1.txt+",button"
|
+ tSend.txt="event,buttonPress2,"+entn1.txt+",button"
|
||||||
|
+ if(entn1.txt=="")
|
||||||
|
+ {
|
||||||
|
+ tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
+ }
|
||||||
+ //send calc crc
|
+ //send calc crc
|
||||||
+ btlen tSend.txt,sys0
|
+ btlen tSend.txt,sys0
|
||||||
+ crcrest 1,0xffff // reset CRC
|
+ crcrest 1,0xffff // reset CRC
|
||||||
@@ -1836,6 +1860,10 @@
|
|||||||
+nts
|
+nts
|
||||||
+ Touch Press Event
|
+ Touch Press Event
|
||||||
+ tSend.txt="event,buttonPress2,"+entn2.txt+",button"
|
+ tSend.txt="event,buttonPress2,"+entn2.txt+",button"
|
||||||
|
+ if(entn2.txt=="")
|
||||||
|
+ {
|
||||||
|
+ tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
+ }
|
||||||
+ //send calc crc
|
+ //send calc crc
|
||||||
+ btlen tSend.txt,sys0
|
+ btlen tSend.txt,sys0
|
||||||
+ crcrest 1,0xffff // reset CRC
|
+ crcrest 1,0xffff // reset CRC
|
||||||
@@ -1860,6 +1888,10 @@
|
|||||||
+nts
|
+nts
|
||||||
+ Touch Press Event
|
+ Touch Press Event
|
||||||
+ tSend.txt="event,buttonPress2,"+entn3.txt+",button"
|
+ tSend.txt="event,buttonPress2,"+entn3.txt+",button"
|
||||||
|
+ if(entn3.txt=="")
|
||||||
|
+ {
|
||||||
|
+ tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
+ }
|
||||||
+ //send calc crc
|
+ //send calc crc
|
||||||
+ btlen tSend.txt,sys0
|
+ btlen tSend.txt,sys0
|
||||||
+ crcrest 1,0xffff // reset CRC
|
+ crcrest 1,0xffff // reset CRC
|
||||||
@@ -1884,6 +1916,10 @@
|
|||||||
+nts
|
+nts
|
||||||
+ Touch Press Event
|
+ Touch Press Event
|
||||||
+ tSend.txt="event,buttonPress2,"+entn4.txt+",button"
|
+ tSend.txt="event,buttonPress2,"+entn4.txt+",button"
|
||||||
|
+ if(entn4.txt=="")
|
||||||
|
+ {
|
||||||
|
+ tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
+ }
|
||||||
+ //send calc crc
|
+ //send calc crc
|
||||||
+ btlen tSend.txt,sys0
|
+ btlen tSend.txt,sys0
|
||||||
+ crcrest 1,0xffff // reset CRC
|
+ crcrest 1,0xffff // reset CRC
|
||||||
@@ -1908,6 +1944,10 @@
|
|||||||
+nts
|
+nts
|
||||||
+ Touch Press Event
|
+ Touch Press Event
|
||||||
+ tSend.txt="event,buttonPress2,"+entn5.txt+",button"
|
+ tSend.txt="event,buttonPress2,"+entn5.txt+",button"
|
||||||
|
+ if(entn5.txt=="")
|
||||||
|
+ {
|
||||||
|
+ tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
+ }
|
||||||
+ //send calc crc
|
+ //send calc crc
|
||||||
+ btlen tSend.txt,sys0
|
+ btlen tSend.txt,sys0
|
||||||
+ crcrest 1,0xffff // reset CRC
|
+ crcrest 1,0xffff // reset CRC
|
||||||
@@ -2008,22 +2048,3 @@
|
|||||||
+ spstr strCommand.txt,tNotifyText.txt,"~",2
|
+ spstr strCommand.txt,tNotifyText.txt,"~",2
|
||||||
+ if(tNotifyHead.txt!=""||tNotifyText.txt!="")
|
+ if(tNotifyHead.txt!=""||tNotifyText.txt!="")
|
||||||
+ {
|
+ {
|
||||||
+14 +800,18 @@
|
|
||||||
+ {
|
|
||||||
+ page cardPower
|
|
||||||
+ }
|
|
||||||
+ if(tId.txt=="cardChart")
|
|
||||||
+ {
|
|
||||||
+ page cardChart
|
|
||||||
+ }
|
|
||||||
+ if(tId.txt=="cardLChart")
|
|
||||||
+ {
|
|
||||||
+ page cardLChart
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ if(tInstruction.txt=="timeout")
|
|
||||||
+ {
|
|
||||||
+ //set timeout to global var
|
|
||||||
+ spstr strCommand.txt,tTmp.txt,"~",1
|
|
||||||
+ covx tTmp.txt,sleepTimeout,0,0
|
|
||||||
+ }
|
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ popupLightNew
|
|||||||
23 Component(s)
|
23 Component(s)
|
||||||
412 Line(s) of event code
|
412 Line(s) of event code
|
||||||
209 Unique line(s) of event code
|
209 Unique line(s) of event code
|
||||||
|
popupLight
|
||||||
|
28 Component(s)
|
||||||
|
417 Line(s) of event code
|
||||||
|
228 Unique line(s) of event code
|
||||||
cardGrid2
|
cardGrid2
|
||||||
52 Component(s)
|
52 Component(s)
|
||||||
703 Line(s) of event code
|
703 Line(s) of event code
|
||||||
@@ -54,10 +58,6 @@ cardLChart
|
|||||||
33 Component(s)
|
33 Component(s)
|
||||||
412 Line(s) of event code
|
412 Line(s) of event code
|
||||||
267 Unique line(s) of event code
|
267 Unique line(s) of event code
|
||||||
popupLight
|
|
||||||
28 Component(s)
|
|
||||||
412 Line(s) of event code
|
|
||||||
227 Unique line(s) of event code
|
|
||||||
cardPower
|
cardPower
|
||||||
54 Component(s)
|
54 Component(s)
|
||||||
541 Line(s) of event code
|
541 Line(s) of event code
|
||||||
@@ -66,10 +66,6 @@ cardThermo
|
|||||||
57 Component(s)
|
57 Component(s)
|
||||||
550 Line(s) of event code
|
550 Line(s) of event code
|
||||||
320 Unique line(s) of event code
|
320 Unique line(s) of event code
|
||||||
screensaver2
|
|
||||||
64 Component(s)
|
|
||||||
424 Line(s) of event code
|
|
||||||
264 Unique line(s) of event code
|
|
||||||
popupInSel
|
popupInSel
|
||||||
34 Component(s)
|
34 Component(s)
|
||||||
621 Line(s) of event code
|
621 Line(s) of event code
|
||||||
@@ -90,6 +86,10 @@ popupThermo
|
|||||||
44 Component(s)
|
44 Component(s)
|
||||||
523 Line(s) of event code
|
523 Line(s) of event code
|
||||||
276 Unique line(s) of event code
|
276 Unique line(s) of event code
|
||||||
|
screensaver2
|
||||||
|
64 Component(s)
|
||||||
|
448 Line(s) of event code
|
||||||
|
272 Unique line(s) of event code
|
||||||
cardEntities
|
cardEntities
|
||||||
67 Component(s)
|
67 Component(s)
|
||||||
1205 Line(s) of event code
|
1205 Line(s) of event code
|
||||||
@@ -98,5 +98,5 @@ cardEntities
|
|||||||
Total
|
Total
|
||||||
23 Page(s)
|
23 Page(s)
|
||||||
881 Component(s)
|
881 Component(s)
|
||||||
10769 Line(s) of event code
|
10798 Line(s) of event code
|
||||||
2466 Unique line(s) of event code
|
2472 Unique line(s) of event code
|
||||||
|
|||||||
@@ -806,6 +806,11 @@ Timer tmSerial
|
|||||||
if(tInstruction.txt=="entityUpdateDetail"&&entn.txt==tTmp.txt)
|
if(tInstruction.txt=="entityUpdateDetail"&&entn.txt==tTmp.txt)
|
||||||
{
|
{
|
||||||
// change icon
|
// change icon
|
||||||
|
spstr strCommand.txt,tTmp.txt,"~",2
|
||||||
|
if(tTmp.txt!="")
|
||||||
|
{
|
||||||
|
tIcon1.txt=tTmp.txt
|
||||||
|
}
|
||||||
//spstr strCommand.txt,tIcon1.txt,"~",2
|
//spstr strCommand.txt,tIcon1.txt,"~",2
|
||||||
vis tIcon1,1
|
vis tIcon1,1
|
||||||
// change icon color
|
// change icon color
|
||||||
|
|||||||
@@ -903,6 +903,10 @@ Text f1Icon
|
|||||||
Events
|
Events
|
||||||
Touch Press Event
|
Touch Press Event
|
||||||
tSend.txt="event,buttonPress2,"+entn1.txt+",button"
|
tSend.txt="event,buttonPress2,"+entn1.txt+",button"
|
||||||
|
if(entn1.txt=="")
|
||||||
|
{
|
||||||
|
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
}
|
||||||
//send calc crc
|
//send calc crc
|
||||||
btlen tSend.txt,sys0
|
btlen tSend.txt,sys0
|
||||||
crcrest 1,0xffff // reset CRC
|
crcrest 1,0xffff // reset CRC
|
||||||
@@ -947,6 +951,10 @@ Text f2Icon
|
|||||||
Events
|
Events
|
||||||
Touch Press Event
|
Touch Press Event
|
||||||
tSend.txt="event,buttonPress2,"+entn2.txt+",button"
|
tSend.txt="event,buttonPress2,"+entn2.txt+",button"
|
||||||
|
if(entn2.txt=="")
|
||||||
|
{
|
||||||
|
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
}
|
||||||
//send calc crc
|
//send calc crc
|
||||||
btlen tSend.txt,sys0
|
btlen tSend.txt,sys0
|
||||||
crcrest 1,0xffff // reset CRC
|
crcrest 1,0xffff // reset CRC
|
||||||
@@ -991,6 +999,10 @@ Text f3Icon
|
|||||||
Events
|
Events
|
||||||
Touch Press Event
|
Touch Press Event
|
||||||
tSend.txt="event,buttonPress2,"+entn3.txt+",button"
|
tSend.txt="event,buttonPress2,"+entn3.txt+",button"
|
||||||
|
if(entn3.txt=="")
|
||||||
|
{
|
||||||
|
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
}
|
||||||
//send calc crc
|
//send calc crc
|
||||||
btlen tSend.txt,sys0
|
btlen tSend.txt,sys0
|
||||||
crcrest 1,0xffff // reset CRC
|
crcrest 1,0xffff // reset CRC
|
||||||
@@ -1035,6 +1047,10 @@ Text f4Icon
|
|||||||
Events
|
Events
|
||||||
Touch Press Event
|
Touch Press Event
|
||||||
tSend.txt="event,buttonPress2,"+entn4.txt+",button"
|
tSend.txt="event,buttonPress2,"+entn4.txt+",button"
|
||||||
|
if(entn4.txt=="")
|
||||||
|
{
|
||||||
|
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
}
|
||||||
//send calc crc
|
//send calc crc
|
||||||
btlen tSend.txt,sys0
|
btlen tSend.txt,sys0
|
||||||
crcrest 1,0xffff // reset CRC
|
crcrest 1,0xffff // reset CRC
|
||||||
@@ -1079,6 +1095,10 @@ Text f5Icon
|
|||||||
Events
|
Events
|
||||||
Touch Press Event
|
Touch Press Event
|
||||||
tSend.txt="event,buttonPress2,"+entn5.txt+",button"
|
tSend.txt="event,buttonPress2,"+entn5.txt+",button"
|
||||||
|
if(entn5.txt=="")
|
||||||
|
{
|
||||||
|
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
}
|
||||||
//send calc crc
|
//send calc crc
|
||||||
btlen tSend.txt,sys0
|
btlen tSend.txt,sys0
|
||||||
crcrest 1,0xffff // reset CRC
|
crcrest 1,0xffff // reset CRC
|
||||||
@@ -1794,6 +1814,10 @@ Timer tmSerial
|
|||||||
{
|
{
|
||||||
page cardChart
|
page cardChart
|
||||||
}
|
}
|
||||||
|
if(tId.txt=="cardLChart")
|
||||||
|
{
|
||||||
|
page cardLChart
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(tInstruction.txt=="timeout")
|
if(tInstruction.txt=="timeout")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -457,6 +457,11 @@ Timer tmSerial
|
|||||||
if(tInstruction.txt=="entityUpdateDetail"&&entn.txt==tTmp.txt)
|
if(tInstruction.txt=="entityUpdateDetail"&&entn.txt==tTmp.txt)
|
||||||
{
|
{
|
||||||
// change icon
|
// change icon
|
||||||
|
spstr strCommand.txt,tTmp.txt,"~",2
|
||||||
|
if(tTmp.txt!="")
|
||||||
|
{
|
||||||
|
tIcon1.txt=tTmp.txt
|
||||||
|
}
|
||||||
//spstr strCommand.txt,tIcon1.txt,"~",2
|
//spstr strCommand.txt,tIcon1.txt,"~",2
|
||||||
vis tIcon1,1
|
vis tIcon1,1
|
||||||
// change icon color
|
// change icon color
|
||||||
|
|||||||
@@ -377,6 +377,10 @@ Text f1Icon
|
|||||||
Events
|
Events
|
||||||
Touch Press Event
|
Touch Press Event
|
||||||
tSend.txt="event,buttonPress2,"+entn1.txt+",button"
|
tSend.txt="event,buttonPress2,"+entn1.txt+",button"
|
||||||
|
if(entn1.txt=="")
|
||||||
|
{
|
||||||
|
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
}
|
||||||
//send calc crc
|
//send calc crc
|
||||||
btlen tSend.txt,sys0
|
btlen tSend.txt,sys0
|
||||||
crcrest 1,0xffff // reset CRC
|
crcrest 1,0xffff // reset CRC
|
||||||
@@ -401,6 +405,10 @@ Text f2Icon
|
|||||||
Events
|
Events
|
||||||
Touch Press Event
|
Touch Press Event
|
||||||
tSend.txt="event,buttonPress2,"+entn2.txt+",button"
|
tSend.txt="event,buttonPress2,"+entn2.txt+",button"
|
||||||
|
if(entn2.txt=="")
|
||||||
|
{
|
||||||
|
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
}
|
||||||
//send calc crc
|
//send calc crc
|
||||||
btlen tSend.txt,sys0
|
btlen tSend.txt,sys0
|
||||||
crcrest 1,0xffff // reset CRC
|
crcrest 1,0xffff // reset CRC
|
||||||
@@ -425,6 +433,10 @@ Text f3Icon
|
|||||||
Events
|
Events
|
||||||
Touch Press Event
|
Touch Press Event
|
||||||
tSend.txt="event,buttonPress2,"+entn3.txt+",button"
|
tSend.txt="event,buttonPress2,"+entn3.txt+",button"
|
||||||
|
if(entn3.txt=="")
|
||||||
|
{
|
||||||
|
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
}
|
||||||
//send calc crc
|
//send calc crc
|
||||||
btlen tSend.txt,sys0
|
btlen tSend.txt,sys0
|
||||||
crcrest 1,0xffff // reset CRC
|
crcrest 1,0xffff // reset CRC
|
||||||
@@ -449,6 +461,10 @@ Text f4Icon
|
|||||||
Events
|
Events
|
||||||
Touch Press Event
|
Touch Press Event
|
||||||
tSend.txt="event,buttonPress2,"+entn4.txt+",button"
|
tSend.txt="event,buttonPress2,"+entn4.txt+",button"
|
||||||
|
if(entn4.txt=="")
|
||||||
|
{
|
||||||
|
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
}
|
||||||
//send calc crc
|
//send calc crc
|
||||||
btlen tSend.txt,sys0
|
btlen tSend.txt,sys0
|
||||||
crcrest 1,0xffff // reset CRC
|
crcrest 1,0xffff // reset CRC
|
||||||
@@ -473,6 +489,10 @@ Text f5Icon
|
|||||||
Events
|
Events
|
||||||
Touch Press Event
|
Touch Press Event
|
||||||
tSend.txt="event,buttonPress2,"+entn5.txt+",button"
|
tSend.txt="event,buttonPress2,"+entn5.txt+",button"
|
||||||
|
if(entn5.txt=="")
|
||||||
|
{
|
||||||
|
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
|
||||||
|
}
|
||||||
//send calc crc
|
//send calc crc
|
||||||
btlen tSend.txt,sys0
|
btlen tSend.txt,sys0
|
||||||
crcrest 1,0xffff // reset CRC
|
crcrest 1,0xffff // reset CRC
|
||||||
@@ -956,6 +976,10 @@ Timer tmSerial
|
|||||||
{
|
{
|
||||||
page cardChart
|
page cardChart
|
||||||
}
|
}
|
||||||
|
if(tId.txt=="cardLChart")
|
||||||
|
{
|
||||||
|
page cardLChart
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(tInstruction.txt=="timeout")
|
if(tInstruction.txt=="timeout")
|
||||||
{
|
{
|
||||||
|
|||||||
BIN
HMI/nspanel.HMI
BIN
HMI/nspanel.HMI
Binary file not shown.
BIN
HMI/nspanel.tft
BIN
HMI/nspanel.tft
Binary file not shown.
@@ -5,7 +5,7 @@ If you like this project consider buying me a pizza 🍕 <a href="https://paypal
|
|||||||
[](https://github.com/hacs/integration)
|
[](https://github.com/hacs/integration)
|
||||||

|

|
||||||
[](https://github.com/joBr99/nspanel-lovelace-ui/releases)
|
[](https://github.com/joBr99/nspanel-lovelace-ui/releases)
|
||||||

|

|
||||||
[](https://github.com/joBr99/nspanel-lovelace-ui/commits/main)
|
[](https://github.com/joBr99/nspanel-lovelace-ui/commits/main)
|
||||||
|
|
||||||
|
|
||||||
@@ -65,3 +65,5 @@ SmartHomeNG: https://github.com/sisamiwe/shng-nspanel-plugin
|
|||||||
OpenHAB: https://github.com/donoo/o2n2l
|
OpenHAB: https://github.com/donoo/o2n2l
|
||||||
|
|
||||||
NodeRed: https://github.com/laluz742/node-red-contrib-nspanel-lui
|
NodeRed: https://github.com/laluz742/node-red-contrib-nspanel-lui
|
||||||
|
|
||||||
|
ESPHome without any Backend: https://github.com/olicooper/esphome-nspanel-lovelace-native
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
ha_api = None
|
ha_api = None
|
||||||
mqtt_api = None
|
mqtt_api = None
|
||||||
|
ad_api = None
|
||||||
@@ -132,6 +132,7 @@ class LuiBackendConfig(object):
|
|||||||
'sleepTrackingZones': ["not_home", "off"],
|
'sleepTrackingZones': ["not_home", "off"],
|
||||||
'sleepOverride': None,
|
'sleepOverride': None,
|
||||||
'locale': "en_US",
|
'locale': "en_US",
|
||||||
|
'quiet': True,
|
||||||
'timeFormat': "%H:%M",
|
'timeFormat': "%H:%M",
|
||||||
'dateFormatBabel': "full",
|
'dateFormatBabel': "full",
|
||||||
'dateAdditionalTemplate': "",
|
'dateAdditionalTemplate': "",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import datetime
|
|||||||
import apis
|
import apis
|
||||||
from helper import scale, pos_to_color, rgb_dec565
|
from helper import scale, pos_to_color, rgb_dec565
|
||||||
from pages import LuiPagesGen
|
from pages import LuiPagesGen
|
||||||
|
from luibackend.config import Card
|
||||||
|
|
||||||
class LuiController(object):
|
class LuiController(object):
|
||||||
|
|
||||||
@@ -150,7 +151,7 @@ class LuiController(object):
|
|||||||
items = self._config.get_all_entity_names()
|
items = self._config.get_all_entity_names()
|
||||||
apis.ha_api.log(f"gtest123: {items}")
|
apis.ha_api.log(f"gtest123: {items}")
|
||||||
prefixes = ("navigate.", "delete", "iText")
|
prefixes = ("navigate.", "delete", "iText")
|
||||||
items = [x for x in items if not (x is None or x.startswith(prefixes))]
|
items = set([x for x in items if not (x is None or x.startswith(prefixes))])
|
||||||
apis.ha_api.log(f"Registering callbacks for the following items: {items}")
|
apis.ha_api.log(f"Registering callbacks for the following items: {items}")
|
||||||
for item in items:
|
for item in items:
|
||||||
if apis.ha_api.entity_exists(item):
|
if apis.ha_api.entity_exists(item):
|
||||||
@@ -205,11 +206,15 @@ class LuiController(object):
|
|||||||
self._pages_gen.generate_timer_detail_page(entity_id, True)
|
self._pages_gen.generate_timer_detail_page(entity_id, True)
|
||||||
|
|
||||||
def button_press(self, entity_id, button_type, value):
|
def button_press(self, entity_id, button_type, value):
|
||||||
apis.ha_api.log(f"Button Press Event; entity_id: {entity_id}; button_type: {button_type}; value: {value} ")
|
#apis.ha_api.log(f"Button Press Event; entity_id: {entity_id}; button_type: {button_type}; value: {value} ")
|
||||||
|
entity_id_new = entity_id
|
||||||
if entity_id.startswith('uuid'):
|
if entity_id.startswith('uuid'):
|
||||||
entity_config = self._config._config_entites_table.get(entity_id)
|
entity_config = self._config._config_entites_table.get(entity_id)
|
||||||
if entity_config is not None:
|
if entity_config is not None:
|
||||||
entity_id = entity_config.entityId
|
entity_id_new = entity_config.entityId
|
||||||
|
apis.ha_api.log(f"Button Press Event; entity_id: {entity_id}; entity_id_resolved: {entity_id_new}; button_type: {button_type}; value: {value} ")
|
||||||
|
entity_id = entity_id_new
|
||||||
|
|
||||||
# internal buttons
|
# internal buttons
|
||||||
if entity_id == "screensaver" and button_type == "bExit":
|
if entity_id == "screensaver" and button_type == "bExit":
|
||||||
# get default card if there is one
|
# get default card if there is one
|
||||||
@@ -333,6 +338,11 @@ class LuiController(object):
|
|||||||
apis.ha_api.get_entity(entity_id).call_service("return_to_base")
|
apis.ha_api.get_entity(entity_id).call_service("return_to_base")
|
||||||
elif entity_id.startswith('service'):
|
elif entity_id.startswith('service'):
|
||||||
apis.ha_api.call_service(entity_id.replace('service.', '', 1).replace('.','/', 1), **entity_config.data)
|
apis.ha_api.call_service(entity_id.replace('service.', '', 1).replace('.','/', 1), **entity_config.data)
|
||||||
|
elif entity_id.startswith('valve'):
|
||||||
|
if apis.ha_api.get_entity(entity_id).state == "open":
|
||||||
|
apis.ha_api.get_entity(entity_id).call_service("close_valve")
|
||||||
|
else:
|
||||||
|
apis.ha_api.get_entity(entity_id).call_service("open_valve")
|
||||||
|
|
||||||
# for media page
|
# for media page
|
||||||
if button_type == "media-next":
|
if button_type == "media-next":
|
||||||
@@ -347,8 +357,8 @@ class LuiController(object):
|
|||||||
else:
|
else:
|
||||||
apis.ha_api.get_entity(entity_id).call_service("turn_off")
|
apis.ha_api.get_entity(entity_id).call_service("turn_off")
|
||||||
if button_type == "media-shuffle":
|
if button_type == "media-shuffle":
|
||||||
suffle = not apis.ha_api.get_entity(entity_id).attributes.shuffle
|
shuffle = not apis.ha_api.get_entity(entity_id).attributes['shuffle']
|
||||||
apis.ha_api.get_entity(entity_id).call_service("shuffle_set", shuffle=suffle)
|
apis.ha_api.get_entity(entity_id).call_service("shuffle_set", shuffle=shuffle)
|
||||||
if button_type == "volumeSlider":
|
if button_type == "volumeSlider":
|
||||||
pos = int(value)
|
pos = int(value)
|
||||||
# HA wants this value between 0 and 1 as float
|
# HA wants this value between 0 and 1 as float
|
||||||
@@ -365,7 +375,7 @@ class LuiController(object):
|
|||||||
if button_type == "colorTempSlider":
|
if button_type == "colorTempSlider":
|
||||||
entity = apis.ha_api.get_entity(entity_id)
|
entity = apis.ha_api.get_entity(entity_id)
|
||||||
#scale 0-100 from slider to color range of lamp
|
#scale 0-100 from slider to color range of lamp
|
||||||
color_val = scale(int(value), (0, 100), (entity.attributes.min_mireds, entity.attributes.max_mireds))
|
color_val = scale(int(value), (0, 100), (entity.attributes['min_mireds'], entity.attributes['max_mireds']))
|
||||||
apis.ha_api.get_entity(entity_id).call_service("turn_on", color_temp=color_val)
|
apis.ha_api.get_entity(entity_id).call_service("turn_on", color_temp=color_val)
|
||||||
if button_type == "colorWheel":
|
if button_type == "colorWheel":
|
||||||
apis.ha_api.log(value)
|
apis.ha_api.log(value)
|
||||||
@@ -392,9 +402,9 @@ class LuiController(object):
|
|||||||
if button_type == "opnSensorNotify":
|
if button_type == "opnSensorNotify":
|
||||||
msg = ""
|
msg = ""
|
||||||
entity = apis.ha_api.get_entity(entity_id)
|
entity = apis.ha_api.get_entity(entity_id)
|
||||||
if "open_sensors" in entity.attributes and entity.attributes.open_sensors is not None:
|
if open_sensors := entity.attributes.get("open_sensors") is not None:
|
||||||
for e in entity.attributes.open_sensors:
|
for e in open_sensors:
|
||||||
msg += f"- {apis.ha_api.get_entity(e).attributes.friendly_name}\r\n"
|
msg += f"- {apis.ha_api.get_entity(e).attributes['friendly_name']}\r\n"
|
||||||
self._pages_gen.send_message_page("opnSensorNotifyRes", "", msg, "", "")
|
self._pages_gen.send_message_page("opnSensorNotifyRes", "", msg, "", "")
|
||||||
|
|
||||||
# for cardUnlock
|
# for cardUnlock
|
||||||
@@ -411,22 +421,22 @@ class LuiController(object):
|
|||||||
|
|
||||||
if button_type == "mode-preset_modes":
|
if button_type == "mode-preset_modes":
|
||||||
entity = apis.ha_api.get_entity(entity_id)
|
entity = apis.ha_api.get_entity(entity_id)
|
||||||
preset_mode = entity.attributes.preset_modes[int(value)]
|
preset_mode = entity.attributes['preset_modes'][int(value)]
|
||||||
entity.call_service("set_preset_mode", preset_mode=preset_mode)
|
entity.call_service("set_preset_mode", preset_mode=preset_mode)
|
||||||
|
|
||||||
if button_type == "mode-swing_modes":
|
if button_type == "mode-swing_modes":
|
||||||
entity = apis.ha_api.get_entity(entity_id)
|
entity = apis.ha_api.get_entity(entity_id)
|
||||||
swing_mode = entity.attributes.swing_modes[int(value)]
|
swing_mode = entity.attributes['swing_modes'][int(value)]
|
||||||
entity.call_service("set_swing_mode", swing_mode=swing_mode)
|
entity.call_service("set_swing_mode", swing_mode=swing_mode)
|
||||||
|
|
||||||
if button_type == "mode-fan_modes":
|
if button_type == "mode-fan_modes":
|
||||||
entity = apis.ha_api.get_entity(entity_id)
|
entity = apis.ha_api.get_entity(entity_id)
|
||||||
fan_mode = entity.attributes.fan_modes[int(value)]
|
fan_mode = entity.attributes['fan_modes'][int(value)]
|
||||||
entity.call_service("set_fan_mode", fan_mode=fan_mode)
|
entity.call_service("set_fan_mode", fan_mode=fan_mode)
|
||||||
|
|
||||||
if button_type in ["mode-input_select", "mode-select"]:
|
if button_type in ["mode-input_select", "mode-select"]:
|
||||||
entity = apis.ha_api.get_entity(entity_id)
|
entity = apis.ha_api.get_entity(entity_id)
|
||||||
option = entity.attributes.options[int(value)]
|
option = entity.attributes['options'][int(value)]
|
||||||
entity.call_service("select_option", option=option)
|
entity.call_service("select_option", option=option)
|
||||||
|
|
||||||
if button_type == "mode-light":
|
if button_type == "mode-light":
|
||||||
@@ -438,12 +448,12 @@ class LuiController(object):
|
|||||||
if options_list is not None:
|
if options_list is not None:
|
||||||
option = options_list[int(value)]
|
option = options_list[int(value)]
|
||||||
else:
|
else:
|
||||||
option = entity.attributes.effect_list[int(value)]
|
option = entity.attributes['effect_list'][int(value)]
|
||||||
entity.call_service("turn_on", effect=option)
|
entity.call_service("turn_on", effect=option)
|
||||||
|
|
||||||
if button_type == "mode-media_player":
|
if button_type == "mode-media_player":
|
||||||
entity = apis.ha_api.get_entity(entity_id)
|
entity = apis.ha_api.get_entity(entity_id)
|
||||||
option = entity.attributes.source_list[int(value)]
|
option = entity.attributes['source_list'][int(value)]
|
||||||
entity.call_service("select_source", source=option)
|
entity.call_service("select_source", source=option)
|
||||||
|
|
||||||
# timer detail page
|
# timer detail page
|
||||||
@@ -458,3 +468,9 @@ class LuiController(object):
|
|||||||
apis.ha_api.get_entity(entity_id).call_service("pause")
|
apis.ha_api.get_entity(entity_id).call_service("pause")
|
||||||
if button_type == "timer-finish":
|
if button_type == "timer-finish":
|
||||||
apis.ha_api.get_entity(entity_id).call_service("finish")
|
apis.ha_api.get_entity(entity_id).call_service("finish")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_card(self) -> Card:
|
||||||
|
"""Used to get the current card"""
|
||||||
|
|
||||||
|
return self._current_card
|
||||||
|
|||||||
@@ -159,8 +159,8 @@ alarm_control_panel_mapping = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
climate_mapping = {
|
climate_mapping = {
|
||||||
'auto': 'calendar-sync',
|
'auto': 'fan-auto',
|
||||||
'heat_cool': 'calendar-sync',
|
'heat_cool': 'sun-snowflake-variant',
|
||||||
'heat': 'fire',
|
'heat': 'fire',
|
||||||
'off': 'power',
|
'off': 'power',
|
||||||
'cool': 'snowflake',
|
'cool': 'snowflake',
|
||||||
@@ -213,6 +213,9 @@ def get_icon_ha(entity_id, overwrite=None, stateOverwrite=None):
|
|||||||
entity = apis.ha_api.get_entity(entity_id)
|
entity = apis.ha_api.get_entity(entity_id)
|
||||||
state = entity.state if stateOverwrite is None else stateOverwrite
|
state = entity.state if stateOverwrite is None else stateOverwrite
|
||||||
|
|
||||||
|
if entity_id in ["sensor.weather_forecast_daily", "sensor.weather_forecast_hourly"]:
|
||||||
|
ha_type = "weather"
|
||||||
|
|
||||||
if overwrite is not None:
|
if overwrite is not None:
|
||||||
if type(overwrite) is str:
|
if type(overwrite) is str:
|
||||||
return get_icon_char(overwrite)
|
return get_icon_char(overwrite)
|
||||||
@@ -263,8 +266,8 @@ def get_icon_ha(entity_id, overwrite=None, stateOverwrite=None):
|
|||||||
# based on media_content_type
|
# based on media_content_type
|
||||||
elif ha_type == "media_player":
|
elif ha_type == "media_player":
|
||||||
result_icon = "speaker-off"
|
result_icon = "speaker-off"
|
||||||
if "media_content_type" in entity.attributes:
|
if media_content_type := entity.attributes.get("media_content_type"):
|
||||||
if entity.attributes.media_content_type in media_content_type_mapping:
|
if media_content_type in media_content_type_mapping:
|
||||||
result_icon = media_content_type_mapping[entity.attributes.media_content_type]
|
result_icon = media_content_type_mapping[media_content_type]
|
||||||
|
|
||||||
return get_icon_char(result_icon)
|
return get_icon_char(result_icon)
|
||||||
|
|||||||
@@ -77,12 +77,13 @@ class LuiMqttListener(object):
|
|||||||
self._controller.detail_open(msg[2], msg[3])
|
self._controller.detail_open(msg[2], msg[3])
|
||||||
|
|
||||||
class LuiMqttSender(object):
|
class LuiMqttSender(object):
|
||||||
def __init__(self, api, use_api, topic_send, api_panel_name):
|
def __init__(self, api, use_api, topic_send, api_panel_name, quiet):
|
||||||
self._ha_api = api
|
self._ha_api = api
|
||||||
self._use_api = use_api
|
self._use_api = use_api
|
||||||
self._topic_send = topic_send
|
self._topic_send = topic_send
|
||||||
self._api_panel_name = api_panel_name
|
self._api_panel_name = api_panel_name
|
||||||
self._prev_msg = ""
|
self._prev_msg = ""
|
||||||
|
self._quiet = quiet
|
||||||
|
|
||||||
def send_mqtt_msg(self, msg, topic=None, force=False):
|
def send_mqtt_msg(self, msg, topic=None, force=False):
|
||||||
if not force and self._prev_msg == msg:
|
if not force and self._prev_msg == msg:
|
||||||
@@ -90,7 +91,9 @@ class LuiMqttSender(object):
|
|||||||
return
|
return
|
||||||
self._prev_msg = msg
|
self._prev_msg = msg
|
||||||
|
|
||||||
apis.ha_api.log(f"Sending Message: {msg}")
|
if self._quiet is False:
|
||||||
|
apis.ha_api.log(f"Sending Message: {msg}")
|
||||||
|
|
||||||
if self._use_api:
|
if self._use_api:
|
||||||
apis.ha_api.call_service(service="esphome/" + self._api_panel_name + "_nspanelui_api_call", command=2, data=msg)
|
apis.ha_api.call_service(service="esphome/" + self._api_panel_name + "_nspanelui_api_call", command=2, data=msg)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -97,13 +97,12 @@ class LuiPagesGen(object):
|
|||||||
if state == "sunny":
|
if state == "sunny":
|
||||||
icon_color = 65504 #bright-yellow
|
icon_color = 65504 #bright-yellow
|
||||||
|
|
||||||
if "rgb_color" in attr and attr.rgb_color:
|
if color := attr.get("rgb_color"):
|
||||||
color = attr.rgb_color
|
if brightness := attr.get("brightness"):
|
||||||
if "brightness" in attr and attr.brightness:
|
color = rgb_brightness(color, brightness)
|
||||||
color = rgb_brightness(color, attr.brightness)
|
|
||||||
icon_color = rgb_dec565(color)
|
icon_color = rgb_dec565(color)
|
||||||
elif "brightness" in attr and attr.brightness:
|
elif brightness := attr.get("brightness"):
|
||||||
color = rgb_brightness([253, 216, 53], attr.brightness)
|
color = rgb_brightness([253, 216, 53], brightness)
|
||||||
icon_color = rgb_dec565(color)
|
icon_color = rgb_dec565(color)
|
||||||
return icon_color
|
return icon_color
|
||||||
|
|
||||||
@@ -192,6 +191,9 @@ class LuiPagesGen(object):
|
|||||||
else:
|
else:
|
||||||
entityType = "delete"
|
entityType = "delete"
|
||||||
|
|
||||||
|
if entityId in ["sensor.weather_forecast_daily", "sensor.weather_forecast_hourly"]:
|
||||||
|
entityType = "weather"
|
||||||
|
|
||||||
apis.ha_api.log(f"Generating item for {entityId} with type {entityType}", level="DEBUG")
|
apis.ha_api.log(f"Generating item for {entityId} with type {entityType}", level="DEBUG")
|
||||||
|
|
||||||
status_entity = apis.ha_api.get_entity(item.status) if item.status and apis.ha_api.entity_exists(item.status) else None
|
status_entity = apis.ha_api.get_entity(item.status) if item.status and apis.ha_api.entity_exists(item.status) else None
|
||||||
@@ -221,7 +223,7 @@ class LuiPagesGen(object):
|
|||||||
if status_entity:
|
if status_entity:
|
||||||
icon_res = get_icon_ha(item.status, overwrite=icon)
|
icon_res = get_icon_ha(item.status, overwrite=icon)
|
||||||
icon_color = self.get_entity_color(status_entity, ha_type=item.status.split(".")[0], overwrite=colorOverride)
|
icon_color = self.get_entity_color(status_entity, ha_type=item.status.split(".")[0], overwrite=colorOverride)
|
||||||
if item.status.startswith("sensor") and (cardType == "cardGrid" or cardType == "cardGrid2") and item.iconOverride is None:
|
if item.status.startswith("sensor") and cardType in ["cardGrid", "cardGrid1", "cardGrid2"] and item.iconOverride is None:
|
||||||
icon_res = status_entity.state[:4]
|
icon_res = status_entity.state[:4]
|
||||||
if icon_res[-1] == ".":
|
if icon_res[-1] == ".":
|
||||||
icon_res = icon_res[:-1]
|
icon_res = icon_res[:-1]
|
||||||
@@ -245,7 +247,7 @@ class LuiPagesGen(object):
|
|||||||
if status_entity:
|
if status_entity:
|
||||||
icon_id = get_icon_ha(item.status, overwrite=icon)
|
icon_id = get_icon_ha(item.status, overwrite=icon)
|
||||||
icon_color = self.get_entity_color(status_entity, ha_type=item.status.split(".")[0], overwrite=colorOverride)
|
icon_color = self.get_entity_color(status_entity, ha_type=item.status.split(".")[0], overwrite=colorOverride)
|
||||||
if item.status.startswith("sensor") and (cardType == "cardGrid" or cardType == "cardGrid2") and item.iconOverride is None:
|
if item.status.startswith("sensor") and cardType in ["cardGrid", "cardGrid1", "cardGrid2"] and item.iconOverride is None:
|
||||||
icon_id = status_entity.state[:4]
|
icon_id = status_entity.state[:4]
|
||||||
if icon_id[-1] == ".":
|
if icon_id[-1] == ".":
|
||||||
icon_id = icon_id[:-1]
|
icon_id = icon_id[:-1]
|
||||||
@@ -273,7 +275,7 @@ class LuiPagesGen(object):
|
|||||||
icon_up_status = "disable"
|
icon_up_status = "disable"
|
||||||
icon_stop_status = "disable"
|
icon_stop_status = "disable"
|
||||||
icon_down_status = "disable"
|
icon_down_status = "disable"
|
||||||
bits = entity.attributes.supported_features
|
bits = entity.attributes.get('supported_features')
|
||||||
pos = entity.attributes.get("current_position")
|
pos = entity.attributes.get("current_position")
|
||||||
if pos is None:
|
if pos is None:
|
||||||
pos_status = entity.state
|
pos_status = entity.state
|
||||||
@@ -307,6 +309,11 @@ class LuiPagesGen(object):
|
|||||||
unit_of_measurement = entity.attributes.get("unit_of_measurement", "")
|
unit_of_measurement = entity.attributes.get("unit_of_measurement", "")
|
||||||
value = entity.state
|
value = entity.state
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = str(round(float(value), 1))
|
||||||
|
except:
|
||||||
|
print("An exception occurred")
|
||||||
|
|
||||||
# limit value to 4 chars on us-p
|
# limit value to 4 chars on us-p
|
||||||
if self._config.get("model") == "us-p" and cardType == "cardEntities":
|
if self._config.get("model") == "us-p" and cardType == "cardEntities":
|
||||||
value = entity.state[:4]
|
value = entity.state[:4]
|
||||||
@@ -318,7 +325,7 @@ class LuiPagesGen(object):
|
|||||||
value = value + unit_of_measurement
|
value = value + unit_of_measurement
|
||||||
if entityType == "binary_sensor":
|
if entityType == "binary_sensor":
|
||||||
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}.{entity.state}")
|
||||||
if (cardType == "cardGrid" or cardType == "cardGrid2") and entityType == "sensor" and icon is None:
|
if cardType in ["cardGrid", "cardGrid1", "cardGrid2"] and entityType == "sensor" and icon is None:
|
||||||
icon_id = entity.state[:4]
|
icon_id = entity.state[:4]
|
||||||
if icon_id[-1] == ".":
|
if icon_id[-1] == ".":
|
||||||
icon_id = icon_id[:-1]
|
icon_id = icon_id[:-1]
|
||||||
@@ -380,20 +387,51 @@ class LuiPagesGen(object):
|
|||||||
elif entityType == "weather":
|
elif entityType == "weather":
|
||||||
entityTypePanel = "text"
|
entityTypePanel = "text"
|
||||||
unit = get_attr_safe(entity, "temperature_unit", "")
|
unit = get_attr_safe(entity, "temperature_unit", "")
|
||||||
if type(item.stype) == int and len(entity.attributes.forecast) >= item.stype:
|
rt = None
|
||||||
fdate = dp.parse(entity.attributes.forecast[item.stype]['datetime'])
|
index = item.stype
|
||||||
global babel_spec
|
if type(item.stype) == str and ":" in item.stype and len(item.stype.split(":")) == 2:
|
||||||
if babel_spec is not None:
|
spintstr = item.stype.split(":")
|
||||||
dateformat = "E" if item.nameOverride is None else item.nameOverride
|
rt = spintstr[0]
|
||||||
name = babel.dates.format_datetime(fdate.astimezone(), dateformat, locale=self._locale)
|
index = int(spintstr[1])
|
||||||
|
if type(index) == int:
|
||||||
|
bits = get_attr_safe(entity, "supported_features", 0b0)
|
||||||
|
if not rt:
|
||||||
|
rt = "daily"
|
||||||
|
if bits & 0b001: #FORECAST_DAILY
|
||||||
|
rt = "daily"
|
||||||
|
elif bits & 0b010: #FORECAST_HOURLY
|
||||||
|
rt = "hourly"
|
||||||
|
elif bits & 0b100: #FORECAST_TWICE_DAILY
|
||||||
|
rt = "twice_daily"
|
||||||
|
|
||||||
|
results = apis.ha_api.call_service(
|
||||||
|
"weather/get_forecasts", target={"entity_id": entityId}, service_data={"type": rt}
|
||||||
|
)
|
||||||
|
forecast = results.get("result", {}).get("response", {}).get(entityId, {}).get('forecast') or entity.attributes.get('forecast', [])
|
||||||
|
if len(forecast) >= index:
|
||||||
|
day_forecast = forecast[index]
|
||||||
|
fdate = dp.parse(day_forecast['datetime'])
|
||||||
|
global babel_spec
|
||||||
|
if babel_spec is not None:
|
||||||
|
dateformat = "E" if item.nameOverride is None else item.nameOverride
|
||||||
|
name = babel.dates.format_datetime(fdate.astimezone(), dateformat, locale=self._locale)
|
||||||
|
else:
|
||||||
|
dateformat = "%a" if item.nameOverride is None else item.nameOverride
|
||||||
|
name = fdate.astimezone().strftime(dateformat)
|
||||||
|
icon_id = get_icon_ha(entityId, stateOverwrite=day_forecast['condition'])
|
||||||
|
value = f'{day_forecast.get("temperature", "")}{unit}'
|
||||||
|
color = self.get_entity_color(entity, ha_type=entityType, stateOverwrite=day_forecast['condition'], overwrite=colorOverride)
|
||||||
else:
|
else:
|
||||||
dateformat = "%a" if item.nameOverride is None else item.nameOverride
|
value = f'{get_attr_safe(entity, "temperature", "")}{unit}'
|
||||||
name = fdate.astimezone().strftime(dateformat)
|
|
||||||
icon_id = get_icon_ha(entityId, stateOverwrite=entity.attributes.forecast[item.stype]['condition'])
|
|
||||||
value = f'{entity.attributes.forecast[item.stype].get("temperature", "")}{unit}'
|
|
||||||
color = self.get_entity_color(entity, ha_type=entityType, stateOverwrite=entity.attributes.forecast[item.stype]['condition'], overwrite=colorOverride)
|
|
||||||
else:
|
else:
|
||||||
value = f'{get_attr_safe(entity, "temperature", "")}{unit}'
|
value = f'{get_attr_safe(entity, "temperature", "")}{unit}'
|
||||||
|
elif entityType == "valve":
|
||||||
|
entityTypePanel = "valve"
|
||||||
|
value = get_translation(self._locale, f"backend.component.binary_sensor.state.door.{entity.state}")
|
||||||
|
if entity.state == "open":
|
||||||
|
icon_id = get_icon_id("valve-open")
|
||||||
|
else:
|
||||||
|
icon_id = get_icon_id("valve-closed")
|
||||||
else:
|
else:
|
||||||
name = "unsupported"
|
name = "unsupported"
|
||||||
# Overwrite for value
|
# Overwrite for value
|
||||||
@@ -452,7 +490,7 @@ class LuiPagesGen(object):
|
|||||||
command = f"entityUpd~Not found~{navigation}~{item}~check~220~apps.yaml~150~300~5~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Please~your~~"
|
command = f"entityUpd~Not found~{navigation}~{item}~check~220~apps.yaml~150~300~5~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Please~your~~"
|
||||||
else:
|
else:
|
||||||
entity = apis.ha_api.get_entity(item)
|
entity = apis.ha_api.get_entity(item)
|
||||||
heading = title if title != "unknown" else entity.attributes.friendly_name
|
heading = title if title != "unknown" else entity.attributes['friendly_name']
|
||||||
current_temp = get_attr_safe(entity, "current_temperature", "")
|
current_temp = get_attr_safe(entity, "current_temperature", "")
|
||||||
dest_temp = get_attr_safe(entity, "temperature", None)
|
dest_temp = get_attr_safe(entity, "temperature", None)
|
||||||
dest_temp2 = ""
|
dest_temp2 = ""
|
||||||
@@ -534,7 +572,7 @@ class LuiPagesGen(object):
|
|||||||
command = f"entityUpd~Not found~{navigation}"
|
command = f"entityUpd~Not found~{navigation}"
|
||||||
else:
|
else:
|
||||||
entity = apis.ha_api.get_entity(item)
|
entity = apis.ha_api.get_entity(item)
|
||||||
heading = title if title != "unknown" else entity.attributes.friendly_name
|
heading = title if title != "unknown" else entity.attributes['friendly_name']
|
||||||
|
|
||||||
# get data from homeassistant
|
# get data from homeassistant
|
||||||
data_raw = apis.ha_api.get_history(entity_id = item, days = 7)
|
data_raw = apis.ha_api.get_history(entity_id = item, days = 7)
|
||||||
@@ -581,12 +619,12 @@ class LuiPagesGen(object):
|
|||||||
else:
|
else:
|
||||||
media_icon = self.generate_entities_item(entity, "cardGrid")
|
media_icon = self.generate_entities_item(entity, "cardGrid")
|
||||||
ha_entity = apis.ha_api.get_entity(entityId)
|
ha_entity = apis.ha_api.get_entity(entityId)
|
||||||
heading = title if title != "unknown" else ha_entity.attributes.friendly_name
|
heading = title if title != "unknown" else ha_entity.attributes['friendly_name']
|
||||||
title = get_attr_safe(ha_entity, "media_title", "")
|
title = get_attr_safe(ha_entity, "media_title", "")
|
||||||
author = get_attr_safe(ha_entity, "media_artist", "")
|
author = get_attr_safe(ha_entity, "media_artist", "")
|
||||||
volume = int(get_attr_safe(ha_entity, "volume_level", 0)*100)
|
volume = int(get_attr_safe(ha_entity, "volume_level", 0)*100)
|
||||||
iconplaypause = get_icon_id("pause") if ha_entity.state == "playing" else get_icon_id("play")
|
iconplaypause = get_icon_id("pause") if ha_entity.state == "playing" else get_icon_id("play")
|
||||||
bits = ha_entity.attributes.supported_features
|
bits = ha_entity.attributes['supported_features']
|
||||||
onoffbutton = "disable"
|
onoffbutton = "disable"
|
||||||
if bits & 0b10000000:
|
if bits & 0b10000000:
|
||||||
if ha_entity.state == "off":
|
if ha_entity.state == "off":
|
||||||
@@ -636,7 +674,7 @@ class LuiPagesGen(object):
|
|||||||
if not entity.attributes.get("code_arm_required", False):
|
if not entity.attributes.get("code_arm_required", False):
|
||||||
numpad = "disable"
|
numpad = "disable"
|
||||||
if overwrite_supported_modes is None:
|
if overwrite_supported_modes is None:
|
||||||
bits = entity.attributes.supported_features
|
bits = entity.attributes['supported_features']
|
||||||
if bits & 0b000001:
|
if bits & 0b000001:
|
||||||
supported_modes.append("arm_home")
|
supported_modes.append("arm_home")
|
||||||
if bits & 0b000010:
|
if bits & 0b000010:
|
||||||
@@ -675,7 +713,7 @@ class LuiPagesGen(object):
|
|||||||
|
|
||||||
#add button to show sensor state
|
#add button to show sensor state
|
||||||
add_btn = ""
|
add_btn = ""
|
||||||
if "open_sensors" in entity.attributes and entity.attributes.open_sensors is not None:
|
if entity.attributes.get("open_sensors") is not None:
|
||||||
add_btn=f"{get_icon_id('progress-alert')}~{rgb_dec565([243,179,0])}~"
|
add_btn=f"{get_icon_id('progress-alert')}~{rgb_dec565([243,179,0])}~"
|
||||||
if alarmBtn is not None and type(alarmBtn) is dict:
|
if alarmBtn is not None and type(alarmBtn) is dict:
|
||||||
entity = alarmBtn.get("entity")
|
entity = alarmBtn.get("entity")
|
||||||
@@ -779,6 +817,8 @@ class LuiPagesGen(object):
|
|||||||
if send_page_type:
|
if send_page_type:
|
||||||
if card.cardType == "cardGrid" and len(card.entities) > 6:
|
if card.cardType == "cardGrid" and len(card.entities) > 6:
|
||||||
card.cardType = "cardGrid2"
|
card.cardType = "cardGrid2"
|
||||||
|
if card.cardType == "cardGrid1":
|
||||||
|
card.cardType = "cardGrid"
|
||||||
self.page_type(card.cardType)
|
self.page_type(card.cardType)
|
||||||
|
|
||||||
# send sleep timeout if there is one configured for the current card
|
# send sleep timeout if there is one configured for the current card
|
||||||
@@ -788,7 +828,7 @@ class LuiPagesGen(object):
|
|||||||
self._send_mqtt_msg(f'timeout~{self._config.get("sleepTimeout")}')
|
self._send_mqtt_msg(f'timeout~{self._config.get("sleepTimeout")}')
|
||||||
|
|
||||||
temp_unit = card.raw_config.get("temperatureUnit", "celsius")
|
temp_unit = card.raw_config.get("temperatureUnit", "celsius")
|
||||||
if card.cardType in ["cardEntities", "cardGrid", "cardGrid2"]:
|
if card.cardType in ["cardEntities", "cardGrid", "cardGrid1","cardGrid2"]:
|
||||||
self.generate_entities_page(navigation, card.title, card.entities, card.cardType, temp_unit)
|
self.generate_entities_page(navigation, card.title, card.entities, card.cardType, temp_unit)
|
||||||
return
|
return
|
||||||
if card.cardType == "cardThermo":
|
if card.cardType == "cardThermo":
|
||||||
@@ -840,25 +880,26 @@ class LuiPagesGen(object):
|
|||||||
color_temp = "disable"
|
color_temp = "disable"
|
||||||
color = "disable"
|
color = "disable"
|
||||||
effect_supported = "disable"
|
effect_supported = "disable"
|
||||||
|
supported_color_modes = entity.attributes['supported_color_modes']
|
||||||
|
|
||||||
if "onoff" not in entity.attributes.supported_color_modes:
|
if "onoff" not in supported_color_modes:
|
||||||
brightness = 0
|
brightness = 0
|
||||||
if entity.state == "on":
|
if entity.state == "on":
|
||||||
if "brightness" in entity.attributes and entity.attributes.brightness:
|
if brightness := entity.attributes.get("brightness"):
|
||||||
# scale 0-255 brightness from ha to 0-100
|
# scale 0-255 brightness from ha to 0-100
|
||||||
brightness = int(scale(entity.attributes.brightness,(0,255),(0,100)))
|
brightness = int(scale(brightness, (0,255), (0,100)))
|
||||||
else:
|
else:
|
||||||
brightness = "disable"
|
brightness = "disable"
|
||||||
if "color_temp" in entity.attributes.supported_color_modes and entity.attributes.supported_color_modes:
|
if "color_temp" in supported_color_modes:
|
||||||
if "color_temp" in entity.attributes and entity.attributes.color_temp:
|
if color_temp := entity.attributes.get("color_temp"):
|
||||||
# scale ha color temp range to 0-100
|
# scale ha color temp range to 0-100
|
||||||
color_temp = int(scale(entity.attributes.color_temp,(entity.attributes.min_mireds, entity.attributes.max_mireds),(0,100)))
|
color_temp = int(scale(color_temp, (entity.attributes['min_mireds'], entity.attributes['max_mireds']),(0, 100)))
|
||||||
else:
|
else:
|
||||||
color_temp = "unknown"
|
color_temp = "unknown"
|
||||||
else:
|
else:
|
||||||
color_temp = "disable"
|
color_temp = "disable"
|
||||||
list_color_modes = ["xy", "rgb", "rgbw", "hs"]
|
list_color_modes = ["xy", "rgb", "rgbw", "hs"]
|
||||||
if any(item in list_color_modes for item in entity.attributes.supported_color_modes):
|
if any(item in list_color_modes for item in supported_color_modes):
|
||||||
color = "enable"
|
color = "enable"
|
||||||
else:
|
else:
|
||||||
color = "disable"
|
color = "disable"
|
||||||
@@ -898,7 +939,7 @@ class LuiPagesGen(object):
|
|||||||
iconTiltRightStatus = "disable"
|
iconTiltRightStatus = "disable"
|
||||||
tilt_pos = "disable"
|
tilt_pos = "disable"
|
||||||
|
|
||||||
bits = entity.attributes.supported_features
|
bits = entity.attributes['supported_features']
|
||||||
|
|
||||||
# position supported
|
# position supported
|
||||||
if bits & 0b00001111:
|
if bits & 0b00001111:
|
||||||
@@ -1054,3 +1095,4 @@ class LuiPagesGen(object):
|
|||||||
self._send_mqtt_msg(f"pageType~popupNotify")
|
self._send_mqtt_msg(f"pageType~popupNotify")
|
||||||
self._send_mqtt_msg(f"entityUpdateDetail~{ident}~{heading}~65535~{b1}~65535~{b2}~65535~{msg}~65535~0")
|
self._send_mqtt_msg(f"entityUpdateDetail~{ident}~{heading}~65535~{b1}~65535~{b2}~65535~{msg}~65535~0")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import hassapi as hass
|
import adbase as ad
|
||||||
|
|
||||||
from luibackend.config import LuiBackendConfig
|
from luibackend.config import LuiBackendConfig
|
||||||
from luibackend.controller import LuiController
|
from luibackend.controller import LuiController
|
||||||
@@ -6,15 +6,19 @@ from luibackend.mqtt import LuiMqttListener, LuiMqttSender
|
|||||||
from luibackend.updater import Updater
|
from luibackend.updater import Updater
|
||||||
|
|
||||||
import apis
|
import apis
|
||||||
|
import json
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
class NsPanelLovelaceUIManager(hass.Hass):
|
class NsPanelLovelaceUIManager(ad.ADBase):
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.log('Starting')
|
self.adapi = self.get_ad_api()
|
||||||
apis.ha_api = self
|
self.adapi.log('Starting')
|
||||||
|
apis.ad_api = self.adapi
|
||||||
|
apis.ha_api = self.get_plugin_api("HASS")
|
||||||
apis.mqtt_api = self.get_plugin_api("MQTT")
|
apis.mqtt_api = self.get_plugin_api("MQTT")
|
||||||
|
|
||||||
cfg = self._cfg = LuiBackendConfig(self, self.args["config"])
|
cfg = self._cfg = LuiBackendConfig(apis.ha_api, self.args["config"])
|
||||||
|
|
||||||
use_api = cfg.get("use_api") == True
|
use_api = cfg.get("use_api") == True
|
||||||
|
|
||||||
@@ -22,14 +26,15 @@ class NsPanelLovelaceUIManager(hass.Hass):
|
|||||||
topic_recv = cfg.get("panelRecvTopic")
|
topic_recv = cfg.get("panelRecvTopic")
|
||||||
api_panel_name = cfg.get("panelName")
|
api_panel_name = cfg.get("panelName")
|
||||||
api_device_id = cfg.get("panelDeviceId")
|
api_device_id = cfg.get("panelDeviceId")
|
||||||
|
quiet = cfg.get("quiet")
|
||||||
|
|
||||||
mqttsend = LuiMqttSender(self, use_api, topic_send, api_panel_name)
|
mqttsender = self._mqttsender = LuiMqttSender(apis.ha_api, use_api, topic_send, api_panel_name, quiet)
|
||||||
|
|
||||||
controller = LuiController(cfg, mqttsend.send_mqtt_msg)
|
self._controller = LuiController(cfg, mqttsender.send_mqtt_msg)
|
||||||
|
|
||||||
desired_tasmota_driver_version = 8
|
desired_tasmota_driver_version = 8
|
||||||
desired_display_firmware_version = 53
|
desired_display_firmware_version = 53
|
||||||
version = "v4.3.3"
|
version = "v4.7.3"
|
||||||
|
|
||||||
model = cfg.get("model")
|
model = cfg.get("model")
|
||||||
if model == "us-l":
|
if model == "us-l":
|
||||||
@@ -41,11 +46,35 @@ class NsPanelLovelaceUIManager(hass.Hass):
|
|||||||
desired_tasmota_driver_url = cfg._config.get("berryURL", "https://raw.githubusercontent.com/joBr99/nspanel-lovelace-ui/main/tasmota/autoexec.be")
|
desired_tasmota_driver_url = cfg._config.get("berryURL", "https://raw.githubusercontent.com/joBr99/nspanel-lovelace-ui/main/tasmota/autoexec.be")
|
||||||
|
|
||||||
mode = cfg.get("updateMode")
|
mode = cfg.get("updateMode")
|
||||||
updater = Updater(self.log, mqttsend, topic_send, mode, desired_display_firmware_version, model, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url)
|
updater = Updater(self.adapi.log, mqttsender, topic_send, mode, desired_display_firmware_version, model, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url)
|
||||||
|
|
||||||
# Request Tasmota Driver Version
|
# Request Tasmota Driver Version
|
||||||
updater.request_berry_driver_version()
|
updater.request_berry_driver_version()
|
||||||
|
|
||||||
LuiMqttListener(use_api, topic_recv, api_panel_name, api_device_id, controller, updater)
|
LuiMqttListener(use_api, topic_recv, api_panel_name, api_device_id, self._controller, updater)
|
||||||
|
|
||||||
self.log(f'Started ({version})')
|
self.adapi.log(f'Started ({version})')
|
||||||
|
|
||||||
|
#
|
||||||
|
# helpers
|
||||||
|
#
|
||||||
|
|
||||||
|
def show_card(self, card_key: str) -> None:
|
||||||
|
"""Used to show card on panel"""
|
||||||
|
|
||||||
|
msg = json.dumps({"CustomRecv":f"event,buttonPress2,navigate.{card_key},button"})
|
||||||
|
topic = self._cfg.get("panelRecvTopic")
|
||||||
|
self._mqttsender.send_mqtt_msg(msg, topic)
|
||||||
|
|
||||||
|
def navigate(self, direction: Literal['up', 'prev', 'next']) -> None:
|
||||||
|
"""Used to navigate different directions on the panel"""
|
||||||
|
|
||||||
|
msg = json.dumps({"CustomRecv":f"event,buttonPress2,nav{direction.title()},button"})
|
||||||
|
topic = self._cfg.get("panelRecvTopic")
|
||||||
|
self._mqttsender.send_mqtt_msg(msg, topic)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_card(self) -> str:
|
||||||
|
"""Used to get the panel's current card"""
|
||||||
|
|
||||||
|
return self._controller.current_card.key
|
||||||
|
|||||||
112
docs-standalone/docs/_assets/user.css
Normal file
112
docs-standalone/docs/_assets/user.css
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
/*.md-header__button.md-logo img {
|
||||||
|
width: unset;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
.md-main__inner {
|
||||||
|
margin-top: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-nav__title {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.md-header,*/ .md-footer,
|
||||||
|
.md-footer-meta {
|
||||||
|
background-color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer contrast fixes for Zensical/Material variants */
|
||||||
|
:root {
|
||||||
|
--md-footer-bg-color: #333333;
|
||||||
|
--md-footer-bg-color--dark: #2b2b2b;
|
||||||
|
--md-footer-fg-color: #f2f2f2;
|
||||||
|
--md-footer-fg-color--light: #ffffff;
|
||||||
|
--md-footer-fg-color--lighter: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-footer,
|
||||||
|
.md-footer-meta,
|
||||||
|
.md-footer * {
|
||||||
|
color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-footer a,
|
||||||
|
.md-footer-meta a,
|
||||||
|
.md-footer .md-footer__link {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-footer a:hover,
|
||||||
|
.md-footer-meta a:hover,
|
||||||
|
.md-footer .md-footer__link:hover {
|
||||||
|
color: #d9e7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-footer .md-icon svg,
|
||||||
|
.md-footer-meta .md-icon svg {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zensical keeps footer content in the inner/meta containers.
|
||||||
|
Don't hide footer structure, only style it. */
|
||||||
|
|
||||||
|
.md-sidebar {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.md-sidebar.md-sidebar--primary {
|
||||||
|
position: unset;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
.md-sidebar.md-sidebar--secondary {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-sidebar.md-sidebar--primary .md-sidebar__scrollwrap {
|
||||||
|
/*overflow-y: unset;*/
|
||||||
|
padding-right: 1px;
|
||||||
|
border-right: 1px solid #adadad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-sidebar.md-sidebar--primary .md-sidebar__inner {
|
||||||
|
/*border-right: 1px solid #adadad;*/
|
||||||
|
padding-bottom: 30px;
|
||||||
|
}
|
||||||
|
.md-sidebar.md-sidebar--secondary .md-sidebar__inner {
|
||||||
|
border-left: 1px solid #adadad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-nav__item .md-nav__list {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-content {
|
||||||
|
margin-top: 25px;
|
||||||
|
/*border-left: 1px solid #adadad;
|
||||||
|
border-right: 1px solid #adadad;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-top {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-typeset hr {
|
||||||
|
border-bottom: 1px solid #adadad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-typeset h1,
|
||||||
|
.md-typeset h2,
|
||||||
|
.md-typeset h3,
|
||||||
|
.md-typeset h4,
|
||||||
|
.md-typeset h5 {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-typeset table:not([class]) td:not(:last-child),
|
||||||
|
.md-typeset table:not([class]) th:not(:last-child) {
|
||||||
|
border-right: .05rem solid var(--md-typeset-table-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
ol li::marker {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
137
docs-standalone/docs/cards.md
Normal file
137
docs-standalone/docs/cards.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# Cards
|
||||||
|
|
||||||
|
## Supported card types
|
||||||
|
|
||||||
|
- `cardEntities`
|
||||||
|
- `cardGrid`
|
||||||
|
- `cardQR`
|
||||||
|
- `cardPower`
|
||||||
|
- `cardMedia`
|
||||||
|
- `cardThermo`
|
||||||
|
- `cardAlarm`
|
||||||
|
- `cardUnlock`
|
||||||
|
|
||||||
|
## Common card keys
|
||||||
|
|
||||||
|
key | required | type | description
|
||||||
|
-- | -- | -- | --
|
||||||
|
`type` | yes | string | Card type.
|
||||||
|
`title` | no | string | Card title.
|
||||||
|
`key` | no | string | Navigation key used by `navigate.<key>`.
|
||||||
|
|
||||||
|
## `cardEntities` and `cardGrid`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- type: cardEntities
|
||||||
|
title: Main
|
||||||
|
key: main
|
||||||
|
entities:
|
||||||
|
- entity: light.kitchen
|
||||||
|
- entity: navigate.settings
|
||||||
|
icon: mdi:cog
|
||||||
|
```
|
||||||
|
|
||||||
|
- `entities` is required.
|
||||||
|
- `cardGrid` auto-switches to `cardGrid2` if more than 6 entities are present.
|
||||||
|
|
||||||
|
## `cardQR`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- type: cardQR
|
||||||
|
title: Guest WiFi
|
||||||
|
qrCode: "WIFI:S:myssid;T:WPA;P:mypassword;;"
|
||||||
|
entities:
|
||||||
|
- entity: iText.myssid
|
||||||
|
name: SSID
|
||||||
|
icon: mdi:wifi
|
||||||
|
```
|
||||||
|
|
||||||
|
Keys:
|
||||||
|
|
||||||
|
- `qrCode` optional (default value exists, but set it explicitly)
|
||||||
|
- supports optional `entity` / `entities`
|
||||||
|
|
||||||
|
## `cardPower`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- type: cardPower
|
||||||
|
title: Energy
|
||||||
|
entities:
|
||||||
|
- entity: sensor.house_power
|
||||||
|
- entity: delete
|
||||||
|
- entity: sensor.solar_power
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `entities` is required.
|
||||||
|
- `speed` key is accepted in config but currently not applied by the renderer.
|
||||||
|
|
||||||
|
## `cardMedia`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- type: cardMedia
|
||||||
|
title: Living Room
|
||||||
|
entity: media_player.living_room
|
||||||
|
entities:
|
||||||
|
- entity: light.ambient
|
||||||
|
- entity: switch.tv_bias_light
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Main media entity must exist (`entity` or first generated entity).
|
||||||
|
- Additional `entities` are rendered as action buttons on the bottom row.
|
||||||
|
|
||||||
|
## `cardThermo`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- type: cardThermo
|
||||||
|
title: Heating
|
||||||
|
entity: climate.downstairs
|
||||||
|
supported_modes: ["heat", "off"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Keys:
|
||||||
|
|
||||||
|
- `entity` required
|
||||||
|
- `supported_modes` optional (filters shown HVAC mode buttons)
|
||||||
|
|
||||||
|
## `cardAlarm`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- type: cardAlarm
|
||||||
|
title: House Alarm
|
||||||
|
entity: alarm_control_panel.house
|
||||||
|
supported_modes: ["arm_home", "arm_away", "arm_night"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Keys:
|
||||||
|
|
||||||
|
- `entity` required
|
||||||
|
- `supported_modes` optional
|
||||||
|
|
||||||
|
## `cardUnlock`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- type: cardUnlock
|
||||||
|
title: Admin
|
||||||
|
pin: 1234
|
||||||
|
destination: navigate.admin
|
||||||
|
```
|
||||||
|
|
||||||
|
Keys:
|
||||||
|
|
||||||
|
- `pin` required
|
||||||
|
- `destination` required
|
||||||
|
|
||||||
|
Typical target in `hiddenCards`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
hiddenCards:
|
||||||
|
- type: cardGrid
|
||||||
|
key: admin
|
||||||
|
title: Admin
|
||||||
|
entities:
|
||||||
|
- entity: switch.maintenance_mode
|
||||||
|
```
|
||||||
61
docs-standalone/docs/configuration.md
Normal file
61
docs-standalone/docs/configuration.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Configuration
|
||||||
|
|
||||||
|
The runtime reads one YAML file (default: `./panels.yaml`, add-on mode: `/config/panels.yaml`).
|
||||||
|
|
||||||
|
## Top-level keys
|
||||||
|
|
||||||
|
key | required | type | default | description
|
||||||
|
-- | -- | -- | -- | --
|
||||||
|
`nspanels` | yes | object | none | Map of panel definitions.
|
||||||
|
`home_assistant_address` | recommended | string | none | Home Assistant base URL. In add-on mode it is auto-filled as `http://supervisor` if missing.
|
||||||
|
`home_assistant_token` | recommended | string | none | Long-lived token or Supervisor token.
|
||||||
|
`mqtt_server` | required in MQTT mode | string | from env | MQTT host.
|
||||||
|
`mqtt_port` | required in MQTT mode | int | from env | MQTT port.
|
||||||
|
`mqtt_username` | required in MQTT mode | string | from env | MQTT username.
|
||||||
|
`mqtt_password` | required in MQTT mode | string | from env | MQTT password.
|
||||||
|
`use_ha_api` | optional | any | absent | If present, MQTT input mode is disabled and HA event mode is used.
|
||||||
|
`timeZone` | optional | string | `Europe/Berlin` | Global fallback for panel `timeZone`.
|
||||||
|
`hiddenCards` | optional | list | `[]` | Global fallback for panel `hiddenCards`.
|
||||||
|
|
||||||
|
## Panel keys (`nspanels.<name>`)
|
||||||
|
|
||||||
|
key | required | type | default | description
|
||||||
|
-- | -- | -- | -- | --
|
||||||
|
`panelRecvTopic` | yes | string | none | Receive channel for panel events.
|
||||||
|
`panelSendTopic` | yes | string | none | Send channel for panel commands.
|
||||||
|
`locale` | yes | string | none | Locale used for translations and date formatting.
|
||||||
|
`timeZone` | recommended | string | from top-level `timeZone` | Time zone for clock.
|
||||||
|
`timeFormat` | yes | string | none | Python `strftime` format.
|
||||||
|
`dateFormat` | yes | string | none | Babel date format (example: `full`, `medium`).
|
||||||
|
`model` | optional | string | `eu` | Panel model (`eu`, `us-p`, `us-l`).
|
||||||
|
`temp_unit` | optional | string | `celsius` | Thermostat card unit (`celsius` or `fahrenheit`).
|
||||||
|
`sleepTimeout` | optional | int | `20` | Seconds before screensaver.
|
||||||
|
`sleepBrightness` | optional | int or entity_id | `10` | Screensaver brightness.
|
||||||
|
`screenBrightness` | optional | int or entity_id | `100` | Active-screen brightness.
|
||||||
|
`sleepTracking` | optional | entity_id | none | Forces sleep brightness to 0 when entity state matches `sleepTrackingZones`.
|
||||||
|
`sleepTrackingZones` | optional | list | `["not_home", "off"]` | States that trigger forced dimming.
|
||||||
|
`sleepOverride` | optional | object | none | Override sleep brightness when entity is `on`/`true`/`home`.
|
||||||
|
`defaultBackgroundColor` | optional | string | `ha-dark` | `ha-dark` or `black`.
|
||||||
|
`featExperimentalSliders` | optional | int | `0` | Forwarded in dimmode command.
|
||||||
|
`defaultCard` | optional | string | none | Default card when leaving screensaver (`navigate.<key>`).
|
||||||
|
`screensaver` | yes | object | none | Screensaver definition.
|
||||||
|
`cards` | yes | list | none | Top-level cards.
|
||||||
|
`hiddenCards` | optional | list | `[]` | Hidden cards addressable through `navigate.<key>`.
|
||||||
|
|
||||||
|
## Brightness behavior
|
||||||
|
|
||||||
|
- Integer values are used directly.
|
||||||
|
- Entity values read Home Assistant state and cast to number.
|
||||||
|
- List/schedule style brightness is not supported in this rewrite.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sleepBrightness: input_number.nspanel_sleep
|
||||||
|
screenBrightness: input_number.nspanel_awake
|
||||||
|
sleepTracking: person.john
|
||||||
|
sleepTrackingZones: ["not_home", "off"]
|
||||||
|
sleepOverride:
|
||||||
|
entity: light.bedroom
|
||||||
|
brightness: 30
|
||||||
|
```
|
||||||
45
docs-standalone/docs/connection-modes.md
Normal file
45
docs-standalone/docs/connection-modes.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Connection Modes
|
||||||
|
|
||||||
|
The rewrite supports two panel input/output modes.
|
||||||
|
|
||||||
|
## 1) MQTT mode (default)
|
||||||
|
|
||||||
|
Enabled when:
|
||||||
|
|
||||||
|
- `mqtt_server` is configured
|
||||||
|
- `use_ha_api` is not present
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
|
||||||
|
- Subscribes to every panel `panelRecvTopic`
|
||||||
|
- Expects JSON payload containing `CustomRecv`
|
||||||
|
- Publishes commands to `panelSendTopic`
|
||||||
|
|
||||||
|
Example receive payload:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"CustomRecv":"event,startup,54,eu"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2) Home Assistant API mode (`use_ha_api`)
|
||||||
|
|
||||||
|
Enabled when key `use_ha_api` exists in config.
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
|
||||||
|
- Subscribes to HA event `esphome.nspanel.data`
|
||||||
|
- Routes events by `device_id` (must match configured `panelRecvTopic`)
|
||||||
|
- Sends panel commands by calling Home Assistant service:
|
||||||
|
- `<panelSendTopic>_nspanelui_api_call`
|
||||||
|
|
||||||
|
Service payload shape:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
data: "...panel command..."
|
||||||
|
command: 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common to both modes
|
||||||
|
|
||||||
|
- Home Assistant websocket connection is used for entity state cache and service calls.
|
||||||
|
- UI actions (button presses, sliders, mode selects) are translated to Home Assistant service calls.
|
||||||
77
docs-standalone/docs/entities.md
Normal file
77
docs-standalone/docs/entities.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Entities
|
||||||
|
|
||||||
|
Entities are used in cards and screensaver lists.
|
||||||
|
|
||||||
|
## Entity keys
|
||||||
|
|
||||||
|
key | required | type | description
|
||||||
|
-- | -- | -- | --
|
||||||
|
`entity` | yes | string | Home Assistant entity id, or internal entity (`navigate.*`, `delete`, `iText.*`).
|
||||||
|
`name` | no | string | Display name override.
|
||||||
|
`icon` | no | string or map | Icon override (`mdi:*`), optionally per state.
|
||||||
|
`color` | no | `[r,g,b]` or map | Color override, optionally per state.
|
||||||
|
`value` | no | string | Value override.
|
||||||
|
`font` | no | string | Icon font variant (`small`, `medium`, `medium-icon`, `large`).
|
||||||
|
`status` | no | string | Extra status entity for `navigate.*` items.
|
||||||
|
`effectList` | no | list | Custom light effect list for detail popup.
|
||||||
|
`attribute` | no | string | Weather attribute to display.
|
||||||
|
`day` | no | int | Weather daily forecast index.
|
||||||
|
`hour` | no | int | Weather hourly forecast index.
|
||||||
|
`unit` | no | string | Value suffix.
|
||||||
|
|
||||||
|
## Supported Home Assistant domains
|
||||||
|
|
||||||
|
- `switch`
|
||||||
|
- `input_boolean`
|
||||||
|
- `automation`
|
||||||
|
- `lock`
|
||||||
|
- `input_text`
|
||||||
|
- `input_select`
|
||||||
|
- `select`
|
||||||
|
- `light`
|
||||||
|
- `fan`
|
||||||
|
- `button`
|
||||||
|
- `input_button`
|
||||||
|
- `scene`
|
||||||
|
- `script`
|
||||||
|
- `number`
|
||||||
|
- `input_number`
|
||||||
|
- `timer`
|
||||||
|
- `alarm_control_panel`
|
||||||
|
- `vacuum`
|
||||||
|
- `media_player`
|
||||||
|
- `sun`
|
||||||
|
- `person`
|
||||||
|
- `climate`
|
||||||
|
- `cover`
|
||||||
|
- `sensor`
|
||||||
|
- `binary_sensor`
|
||||||
|
- `weather`
|
||||||
|
|
||||||
|
## Internal entities
|
||||||
|
|
||||||
|
- `navigate.<key>`: Navigate to card with matching `key`.
|
||||||
|
- `navigate.UP`: Navigate back.
|
||||||
|
- `delete`: Placeholder/empty slot.
|
||||||
|
- `iText.<text>`: Static text entry.
|
||||||
|
|
||||||
|
## Template-based values
|
||||||
|
|
||||||
|
The rewrite supports Home Assistant template rendering for selected fields when prefixed with `ha:`:
|
||||||
|
|
||||||
|
- `icon: "ha:{{ ... }}"`
|
||||||
|
- `color: "ha:{{ ... }}"` (must evaluate to JSON RGB list)
|
||||||
|
- `value: "ha:{{ ... }}"`
|
||||||
|
- `qrCode: "ha:{{ ... }}"`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- entity: light.kitchen
|
||||||
|
icon:
|
||||||
|
"on": mdi:lightbulb
|
||||||
|
"off": mdi:lightbulb-outline
|
||||||
|
color:
|
||||||
|
"on": [255, 210, 90]
|
||||||
|
"off": [80, 120, 170]
|
||||||
|
```
|
||||||
55
docs-standalone/docs/getting-started.md
Normal file
55
docs-standalone/docs/getting-started.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Getting Started
|
||||||
|
|
||||||
|
## Home Assistant add-on mode
|
||||||
|
|
||||||
|
In add-on mode, the container startup script:
|
||||||
|
|
||||||
|
- reads MQTT credentials from Home Assistant service discovery
|
||||||
|
- sets `CONFIG_FILE=/config/panels.yaml`
|
||||||
|
- creates `/config/panels.yaml` from the bundled example if it does not exist
|
||||||
|
|
||||||
|
Relevant files:
|
||||||
|
|
||||||
|
- `nspanel-lovelace-ui/rootfs/usr/bin/mqtt-manager/run.sh`
|
||||||
|
- `nspanel-lovelace-ui/config.yaml`
|
||||||
|
|
||||||
|
## Minimal `panels.yaml`
|
||||||
|
|
||||||
|
Start with one panel:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
home_assistant_address: "http://supervisor"
|
||||||
|
home_assistant_token: "SUPERVISOR_TOKEN_OR_LONG_LIVED_TOKEN"
|
||||||
|
|
||||||
|
nspanels:
|
||||||
|
kitchen:
|
||||||
|
panelRecvTopic: "tele/tasmota_kitchen/RESULT"
|
||||||
|
panelSendTopic: "cmnd/tasmota_kitchen/CustomSend"
|
||||||
|
locale: "en_US"
|
||||||
|
timeZone: "Europe/Berlin"
|
||||||
|
timeFormat: "%H:%M"
|
||||||
|
dateFormat: "full"
|
||||||
|
screensaver:
|
||||||
|
entities:
|
||||||
|
- entity: weather.home
|
||||||
|
cards:
|
||||||
|
- type: cardEntities
|
||||||
|
title: Main
|
||||||
|
entities:
|
||||||
|
- entity: light.kitchen
|
||||||
|
- entity: switch.coffee_machine
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important notes
|
||||||
|
|
||||||
|
- `cards` and `screensaver` are required per panel.
|
||||||
|
- `timeFormat`, `dateFormat`, and `locale` should be set per panel.
|
||||||
|
- `panelRecvTopic` / `panelSendTopic` are required.
|
||||||
|
|
||||||
|
## Running standalone (outside HA add-on)
|
||||||
|
|
||||||
|
If you run this container/process outside Supervisor:
|
||||||
|
|
||||||
|
- provide `home_assistant_address` and `home_assistant_token` in YAML
|
||||||
|
- provide MQTT values in YAML (`mqtt_server`, `mqtt_port`, `mqtt_username`, `mqtt_password`) or environment
|
||||||
|
- set `CONFIG_FILE` if the config is not `./panels.yaml`
|
||||||
38
docs-standalone/docs/index.md
Normal file
38
docs-standalone/docs/index.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Overview
|
||||||
|
|
||||||
|
This documentation covers the standalone rewrite located in `nspanel-lovelace-ui/`.
|
||||||
|
|
||||||
|
It is a Python backend that:
|
||||||
|
|
||||||
|
- receives panel input (MQTT mode or Home Assistant API mode)
|
||||||
|
- reads Home Assistant state through the websocket API
|
||||||
|
- renders cards and screensaver pages
|
||||||
|
- sends panel commands back to the device
|
||||||
|
|
||||||
|
This docs set is intentionally separate from the AppDaemon docs in `docs/`.
|
||||||
|
|
||||||
|
## Rewrite location
|
||||||
|
|
||||||
|
- Add-on package: `nspanel-lovelace-ui/`
|
||||||
|
- Runtime code: `nspanel-lovelace-ui/rootfs/usr/bin/mqtt-manager/`
|
||||||
|
- Example panel config: `nspanel-lovelace-ui/rootfs/usr/bin/mqtt-manager/panels.yaml.example`
|
||||||
|
|
||||||
|
## What is supported
|
||||||
|
|
||||||
|
- `cardEntities`
|
||||||
|
- `cardGrid` (auto-switches to `cardGrid2` when needed)
|
||||||
|
- `cardQR`
|
||||||
|
- `cardPower`
|
||||||
|
- `cardMedia`
|
||||||
|
- `cardThermo`
|
||||||
|
- `cardAlarm`
|
||||||
|
- `cardUnlock`
|
||||||
|
- screensaver with status icons and weather forecast entities
|
||||||
|
|
||||||
|
## Runtime model
|
||||||
|
|
||||||
|
1. Load `panels.yaml`.
|
||||||
|
2. Resolve MQTT and Home Assistant connection settings.
|
||||||
|
3. Create one thread per panel.
|
||||||
|
4. Listen for events and state changes.
|
||||||
|
5. Re-render active pages and detail popups when relevant entities change.
|
||||||
120
docs-standalone/docs/migration-appdaemon.md
Normal file
120
docs-standalone/docs/migration-appdaemon.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# Migration from AppDaemon Config
|
||||||
|
|
||||||
|
This page explains how to migrate panel configuration from the legacy AppDaemon `apps.yaml` format to the standalone rewrite `panels.yaml` format.
|
||||||
|
|
||||||
|
## File and structure changes
|
||||||
|
|
||||||
|
Old (AppDaemon):
|
||||||
|
|
||||||
|
- panel config lived under `apps.yaml`
|
||||||
|
- MQTT and Home Assistant base connection config was split across AppDaemon files (`appdaemon.yaml`, plugin config, and app config)
|
||||||
|
|
||||||
|
New (rewrite):
|
||||||
|
|
||||||
|
- panel config lives in one file: `panels.yaml` (usually `/config/panels.yaml`)
|
||||||
|
- connection values are read from this file and/or environment variables
|
||||||
|
|
||||||
|
## Minimal before/after example
|
||||||
|
|
||||||
|
Old AppDaemon (`apps.yaml`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
nspanel-1:
|
||||||
|
module: nspanel-lovelace-ui
|
||||||
|
class: NsPanelLovelaceUIManager
|
||||||
|
config:
|
||||||
|
panelRecvTopic: "tele/tasmota_panel/RESULT"
|
||||||
|
panelSendTopic: "cmnd/tasmota_panel/CustomSend"
|
||||||
|
model: eu
|
||||||
|
locale: en_US
|
||||||
|
timeFormat: "%H:%M"
|
||||||
|
```
|
||||||
|
|
||||||
|
New rewrite (`panels.yaml`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
home_assistant_address: "http://supervisor"
|
||||||
|
home_assistant_token: "YOUR_TOKEN"
|
||||||
|
|
||||||
|
nspanels:
|
||||||
|
panel-1:
|
||||||
|
panelRecvTopic: "tele/tasmota_panel/RESULT"
|
||||||
|
panelSendTopic: "cmnd/tasmota_panel/CustomSend"
|
||||||
|
model: eu
|
||||||
|
locale: en_US
|
||||||
|
timeZone: "Europe/Berlin"
|
||||||
|
timeFormat: "%H:%M"
|
||||||
|
dateFormat: "full"
|
||||||
|
screensaver:
|
||||||
|
entities:
|
||||||
|
- entity: weather.home
|
||||||
|
cards:
|
||||||
|
- type: cardEntities
|
||||||
|
title: Main
|
||||||
|
entities:
|
||||||
|
- entity: light.kitchen
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key mapping
|
||||||
|
|
||||||
|
Legacy AppDaemon key or concept | Standalone rewrite | Notes
|
||||||
|
-- | -- | --
|
||||||
|
`module`, `class`, `config` wrapper | removed | Rewrite uses `nspanels.<panel_name>` directly.
|
||||||
|
`panelRecvTopic` | `panelRecvTopic` | Same meaning.
|
||||||
|
`panelSendTopic` | `panelSendTopic` | Same meaning.
|
||||||
|
`model` | `model` | Same meaning (`eu`, `us-p`, `us-l`).
|
||||||
|
`locale` | `locale` | Same meaning.
|
||||||
|
`timeFormat` | `timeFormat` | Same meaning.
|
||||||
|
`timezone` (legacy docs casing) | `timeZone` | Use exact camelCase `timeZone`.
|
||||||
|
`dateFormatBabel` / `dateFormat` | `dateFormat` | Rewrite expects `dateFormat`.
|
||||||
|
`cards` | `cards` | Same concept.
|
||||||
|
`hiddenCards` | `hiddenCards` | Same concept.
|
||||||
|
`screensaver` | `screensaver` | Same concept; some legacy theme options are not available.
|
||||||
|
`defaultCard` under screensaver usage | `defaultCard` (panel level) | Use as panel-level key in rewrite.
|
||||||
|
`temperatureUnit` (card-level legacy usage) | `temp_unit` (panel level) | Rewrite reads panel-level `temp_unit`.
|
||||||
|
`sleepBrightness` list schedule | not supported | Rewrite supports integer or entity id, not list-based schedules.
|
||||||
|
`screenBrightness` list schedule | not supported | Rewrite supports integer or entity id, not list-based schedules.
|
||||||
|
`sleepTracking` | `sleepTracking` | Same concept.
|
||||||
|
`sleepTrackingZones` | `sleepTrackingZones` | Same concept.
|
||||||
|
`sleepOverride` | `sleepOverride` | Same concept.
|
||||||
|
`updateMode` / OTA URL overrides (`displayURL-*`, `berryURL`) | not supported | Rewrite does not implement these legacy update keys.
|
||||||
|
`theme`, `dateAdditionalTemplate`, `timeAdditionalTemplate` | not supported | Not implemented in rewrite config.
|
||||||
|
|
||||||
|
## Connection config differences
|
||||||
|
|
||||||
|
In AppDaemon setups, MQTT and Home Assistant connectivity was mostly configured via AppDaemon plugin settings.
|
||||||
|
|
||||||
|
In the rewrite, connectivity is resolved directly by the runtime:
|
||||||
|
|
||||||
|
- Home Assistant:
|
||||||
|
- `home_assistant_address`
|
||||||
|
- `home_assistant_token`
|
||||||
|
- MQTT (for MQTT mode):
|
||||||
|
- `mqtt_server`, `mqtt_port`, `mqtt_username`, `mqtt_password`
|
||||||
|
- Optional mode switch:
|
||||||
|
- set `use_ha_api` to use Home Assistant event mode instead of MQTT receive mode
|
||||||
|
|
||||||
|
## Entity-level differences to watch
|
||||||
|
|
||||||
|
Some legacy entity config fields are not implemented in the rewrite parser/renderer:
|
||||||
|
|
||||||
|
- `state`, `state_not`, `state_template`
|
||||||
|
- direct `service.*` action entries with custom `data`
|
||||||
|
- `action_name`
|
||||||
|
|
||||||
|
Supported and commonly used fields in rewrite:
|
||||||
|
|
||||||
|
- `entity`, `name`, `icon`, `color`, `value`, `font`
|
||||||
|
- weather-related: `attribute`, `day`, `hour`, `unit`
|
||||||
|
- light detail helper: `effectList`
|
||||||
|
- navigation helper: `status` for `navigate.*` entities
|
||||||
|
|
||||||
|
## Migration checklist
|
||||||
|
|
||||||
|
1. Create `/config/panels.yaml` from the rewrite example.
|
||||||
|
2. Move each old app entry (`nspanel-1`, `nspanel-2`, ...) into `nspanels`.
|
||||||
|
3. Remove `module/class/config` wrappers.
|
||||||
|
4. Rename `timezone` to `timeZone`.
|
||||||
|
5. Ensure each panel has `dateFormat`, `timeFormat`, `screensaver`, and `cards`.
|
||||||
|
6. Replace unsupported scheduled brightness lists with integer/entity-based values.
|
||||||
|
7. Remove unsupported legacy-only keys listed above.
|
||||||
57
docs-standalone/docs/screensaver.md
Normal file
57
docs-standalone/docs/screensaver.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Screensaver
|
||||||
|
|
||||||
|
`screensaver` is a required object in each panel config.
|
||||||
|
|
||||||
|
## Keys
|
||||||
|
|
||||||
|
key | required | type | default | description
|
||||||
|
-- | -- | -- | -- | --
|
||||||
|
`type` | no | string | `screensaver` | Layout type (`screensaver` / `screensaver2`).
|
||||||
|
`entities` | yes* | list | none | Screensaver entities.
|
||||||
|
`entity` | yes* | string | none | Single-entity shortcut.
|
||||||
|
`statusIcon1` | no | object | none | Left status icon near date.
|
||||||
|
`statusIcon2` | no | object | none | Right status icon near date.
|
||||||
|
`doubleTapToUnlock` | no | bool | `false` | Requires double tap when leaving screensaver.
|
||||||
|
`sleepTimeout` | no | int | panel `sleepTimeout` | Per-screensaver timeout override.
|
||||||
|
|
||||||
|
`*` Provide at least one of `entity` or `entities`.
|
||||||
|
|
||||||
|
## Screensaver entities
|
||||||
|
|
||||||
|
Screensaver entities use the same entity format as other cards.
|
||||||
|
|
||||||
|
For `weather.<entity>` you can also use:
|
||||||
|
|
||||||
|
- `attribute` (default `temperature`)
|
||||||
|
- `day` (daily forecast index)
|
||||||
|
- `hour` (hourly forecast index)
|
||||||
|
- `unit` (suffix, default `°C` for temperature-like attributes)
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
screensaver:
|
||||||
|
type: screensaver
|
||||||
|
doubleTapToUnlock: true
|
||||||
|
sleepTimeout: 30
|
||||||
|
statusIcon1:
|
||||||
|
entity: binary_sensor.front_door
|
||||||
|
icon:
|
||||||
|
"on": mdi:door-open
|
||||||
|
"off": mdi:door-closed
|
||||||
|
font: medium-icon
|
||||||
|
statusIcon2:
|
||||||
|
entity: sensor.outdoor_temperature
|
||||||
|
icon: mdi:thermometer
|
||||||
|
entities:
|
||||||
|
- entity: weather.home
|
||||||
|
attribute: temperature
|
||||||
|
- entity: weather.home
|
||||||
|
day: 1
|
||||||
|
attribute: temperature
|
||||||
|
- entity: weather.home
|
||||||
|
day: 2
|
||||||
|
attribute: temperature
|
||||||
|
- entity: sensor.indoor_temperature
|
||||||
|
icon: mdi:home-thermometer
|
||||||
|
```
|
||||||
63
docs-standalone/docs/troubleshooting.md
Normal file
63
docs-standalone/docs/troubleshooting.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
## Config does not load
|
||||||
|
|
||||||
|
Symptoms:
|
||||||
|
|
||||||
|
- no panel output
|
||||||
|
- log shows YAML parse error or file missing
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
|
||||||
|
1. Confirm `CONFIG_FILE` path.
|
||||||
|
2. Validate YAML syntax.
|
||||||
|
3. Ensure required per-panel keys exist: `panelRecvTopic`, `panelSendTopic`, `locale`, `timeFormat`, `dateFormat`, `screensaver`, `cards`.
|
||||||
|
|
||||||
|
## MQTT not connected
|
||||||
|
|
||||||
|
Symptoms:
|
||||||
|
|
||||||
|
- log repeats MQTT connection retry
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
|
||||||
|
1. Verify `mqtt_server`, `mqtt_port`, `mqtt_username`, `mqtt_password`.
|
||||||
|
2. Verify panel publishes on the same topic as `panelRecvTopic`.
|
||||||
|
3. Verify payload includes `CustomRecv` JSON key.
|
||||||
|
|
||||||
|
## Home Assistant websocket not connected
|
||||||
|
|
||||||
|
Symptoms:
|
||||||
|
|
||||||
|
- log repeatedly waits for websocket/auth
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
|
||||||
|
1. Verify `home_assistant_address` and `home_assistant_token`.
|
||||||
|
2. In add-on mode, verify Supervisor token access is available.
|
||||||
|
3. Confirm HA is reachable from container network.
|
||||||
|
|
||||||
|
## Card does not open or navigate
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
|
||||||
|
1. For `navigate.<key>`, confirm target card has matching `key`.
|
||||||
|
2. For `cardUnlock`, confirm `destination` and `pin` are set.
|
||||||
|
3. Confirm card `type` is one of the implemented types.
|
||||||
|
|
||||||
|
## Brightness behaves unexpectedly
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
|
||||||
|
1. If using entity-based brightness, verify entity state is numeric.
|
||||||
|
2. Avoid list-based brightness schedules in this rewrite (not supported).
|
||||||
|
3. Review interaction between `sleepTracking`, `sleepTrackingZones`, and `sleepOverride`.
|
||||||
|
|
||||||
|
## Useful logs to look for
|
||||||
|
|
||||||
|
- `Config file not found`
|
||||||
|
- `Error while parsing YAML file`
|
||||||
|
- `Connected to MQTT Server`
|
||||||
|
- `Home Assistant auth OK`
|
||||||
|
- `card type ... not implemented`
|
||||||
|
- `Not implemented: <button action>`
|
||||||
60
docs-standalone/mkdocs.yml
Normal file
60
docs-standalone/mkdocs.yml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
site_name: NsPanel Lovelace UI Standalone Docs
|
||||||
|
site_description: Documentation for the standalone/Home Assistant add-on rewrite in nspanel-lovelace-ui.
|
||||||
|
site_author: Johannes Braun
|
||||||
|
site_url: https://jobr99.github.io/nspanel-lovelace-ui/standalone
|
||||||
|
|
||||||
|
repo_name: jobr99/nspanel-lovelace-ui
|
||||||
|
repo_url: https://github.com/jobr99/nspanel-lovelace-ui
|
||||||
|
edit_uri: ""
|
||||||
|
|
||||||
|
copyright: "Copyright © 2026 Johannes Braun"
|
||||||
|
|
||||||
|
docs_dir: docs
|
||||||
|
|
||||||
|
theme:
|
||||||
|
name: material
|
||||||
|
palette:
|
||||||
|
accent: blue
|
||||||
|
font:
|
||||||
|
text: "arial, sans-serif"
|
||||||
|
code: monospace
|
||||||
|
features:
|
||||||
|
- navigation.indexes
|
||||||
|
- navigation.sections
|
||||||
|
- navigation.top
|
||||||
|
- navigation.tracking
|
||||||
|
- navigation.expand
|
||||||
|
- search.highlight
|
||||||
|
- search.share
|
||||||
|
- search.suggest
|
||||||
|
|
||||||
|
extra_css:
|
||||||
|
- _assets/user.css
|
||||||
|
|
||||||
|
markdown_extensions:
|
||||||
|
- admonition
|
||||||
|
- def_list
|
||||||
|
- attr_list
|
||||||
|
- pymdownx.tilde
|
||||||
|
- pymdownx.details
|
||||||
|
- pymdownx.superfences
|
||||||
|
- pymdownx.magiclink
|
||||||
|
- toc:
|
||||||
|
permalink: true
|
||||||
|
- codehilite:
|
||||||
|
guess_lang: false
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- search:
|
||||||
|
lang: en
|
||||||
|
|
||||||
|
nav:
|
||||||
|
- "Overview": index.md
|
||||||
|
- "Getting Started": getting-started.md
|
||||||
|
- "Configuration": configuration.md
|
||||||
|
- "Migration from AppDaemon": migration-appdaemon.md
|
||||||
|
- "Screensaver": screensaver.md
|
||||||
|
- "Cards": cards.md
|
||||||
|
- "Entities": entities.md
|
||||||
|
- "Connection Modes": connection-modes.md
|
||||||
|
- "Troubleshooting": troubleshooting.md
|
||||||
@@ -10,14 +10,46 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*.md-header,*/ .md-footer {
|
/*.md-header,*/ .md-footer,
|
||||||
|
.md-footer-meta {
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-footer__inner.md-grid {
|
/* Footer contrast fixes for Zensical/Material variants */
|
||||||
display: none;
|
:root {
|
||||||
|
--md-footer-bg-color: #333333;
|
||||||
|
--md-footer-bg-color--dark: #2b2b2b;
|
||||||
|
--md-footer-fg-color: #f2f2f2;
|
||||||
|
--md-footer-fg-color--light: #ffffff;
|
||||||
|
--md-footer-fg-color--lighter: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-footer,
|
||||||
|
.md-footer-meta,
|
||||||
|
.md-footer * {
|
||||||
|
color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-footer a,
|
||||||
|
.md-footer-meta a,
|
||||||
|
.md-footer .md-footer__link {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-footer a:hover,
|
||||||
|
.md-footer-meta a:hover,
|
||||||
|
.md-footer .md-footer__link:hover {
|
||||||
|
color: #d9e7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-footer .md-icon svg,
|
||||||
|
.md-footer-meta .md-icon svg {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zensical keeps footer content in the inner/meta containers.
|
||||||
|
Don't hide footer structure, only style it. */
|
||||||
|
|
||||||
.md-sidebar {
|
.md-sidebar {
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
}
|
}
|
||||||
|
|||||||
75
docs/config-migration-standalone.md
Normal file
75
docs/config-migration-standalone.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Migration to Standalone Rewrite Config
|
||||||
|
|
||||||
|
This page compares the legacy AppDaemon `apps.yaml` config with the standalone rewrite `panels.yaml` config.
|
||||||
|
|
||||||
|
For the full rewrite docs, including full key descriptions, see:
|
||||||
|
|
||||||
|
- [Standalone documentation](https://docs.nspanel.pky.eu/standalone/)
|
||||||
|
- [Standalone migration page](https://docs.nspanel.pky.eu/standalone/migration-appdaemon/)
|
||||||
|
|
||||||
|
## High-level differences
|
||||||
|
|
||||||
|
Old AppDaemon version:
|
||||||
|
|
||||||
|
- panel config in `apps.yaml` with `module` / `class` / `config`
|
||||||
|
- connectivity partly configured in AppDaemon plugin config (`appdaemon.yaml`)
|
||||||
|
|
||||||
|
Standalone rewrite:
|
||||||
|
|
||||||
|
- one runtime config file: `/config/panels.yaml`
|
||||||
|
- panel definitions under `nspanels`
|
||||||
|
- Home Assistant and MQTT connection values resolved directly by the rewrite runtime
|
||||||
|
|
||||||
|
## Minimal before/after example
|
||||||
|
|
||||||
|
Old (`apps.yaml`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
nspanel-1:
|
||||||
|
module: nspanel-lovelace-ui
|
||||||
|
class: NsPanelLovelaceUIManager
|
||||||
|
config:
|
||||||
|
panelRecvTopic: "tele/tasmota_panel/RESULT"
|
||||||
|
panelSendTopic: "cmnd/tasmota_panel/CustomSend"
|
||||||
|
model: eu
|
||||||
|
```
|
||||||
|
|
||||||
|
New (`panels.yaml`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
home_assistant_address: "http://supervisor"
|
||||||
|
home_assistant_token: "YOUR_TOKEN"
|
||||||
|
|
||||||
|
nspanels:
|
||||||
|
panel-1:
|
||||||
|
panelRecvTopic: "tele/tasmota_panel/RESULT"
|
||||||
|
panelSendTopic: "cmnd/tasmota_panel/CustomSend"
|
||||||
|
model: eu
|
||||||
|
locale: en_US
|
||||||
|
timeZone: "Europe/Berlin"
|
||||||
|
timeFormat: "%H:%M"
|
||||||
|
dateFormat: "full"
|
||||||
|
screensaver:
|
||||||
|
entities:
|
||||||
|
- entity: weather.home
|
||||||
|
cards:
|
||||||
|
- type: cardEntities
|
||||||
|
title: Main
|
||||||
|
entities:
|
||||||
|
- entity: light.kitchen
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important key changes
|
||||||
|
|
||||||
|
Legacy key/concept | Rewrite key/concept | Notes
|
||||||
|
-- | -- | --
|
||||||
|
`module`, `class`, `config` wrapper | removed | Rewrite uses `nspanels.<panel_name>` directly.
|
||||||
|
`timezone` | `timeZone` | Casing changed.
|
||||||
|
`dateFormatBabel` | `dateFormat` | Use `dateFormat` in rewrite.
|
||||||
|
`temperatureUnit` (legacy card-level usage) | `temp_unit` (panel-level) | Rewrite reads `temp_unit` from panel settings.
|
||||||
|
brightness schedule lists | not supported | Rewrite supports integer or entity id for brightness values.
|
||||||
|
`updateMode` / OTA URL override keys | not supported | Legacy update behavior is not part of rewrite config.
|
||||||
|
|
||||||
|
If you are migrating now, use the standalone migration page for the complete mapping:
|
||||||
|
|
||||||
|
- [Complete mapping and checklist](https://docs.nspanel.pky.eu/standalone/migration-appdaemon/)
|
||||||
@@ -93,3 +93,4 @@ Now, to install NSPanel Lovelace UI Backend with HACS, follow these steps:
|
|||||||
6. A confirmation panel will appear, click on `Download`, and wait for HACS to
|
6. A confirmation panel will appear, click on `Download`, and wait for HACS to
|
||||||
proceed with the download
|
proceed with the download
|
||||||
7. The Backend Application is now installed, and HACS will inform you when updates are available
|
7. The Backend Application is now installed, and HACS will inform you when updates are available
|
||||||
|
|
||||||
|
|||||||
@@ -1,65 +1,71 @@
|
|||||||
# Flash Tasmota to your NSPanel
|
# Flash Tasmota to Your NSPanel
|
||||||
|
|
||||||
You need to connect to your nspanel via serial and flash tasmota [tasmota32-nspanel.bin](http://ota.tasmota.com/tasmota32/release/tasmota32-nspanel.bin) to your NSPanel.
|
You need to connect to your NSPanel via serial and flash Tasmota using [tasmota32-nspanel.bin](http://ota.tasmota.com/tasmota32/release/tasmota32-nspanel.bin).
|
||||||
You can use the Tasmota Web Installer to do so. [Tasmota Web Installer](https://tasmota.github.io/install/)
|
You can use the [Tasmota Web Installer](https://tasmota.github.io/install/) to do so.
|
||||||
|
|
||||||
Checkout Blakadders Template Repo for more information on flashing, do not use the autoexec.be from this page.
|
Check out Blakadder's Template Repo for more information on flashing. **Do not** use the autoexec.be from that page.
|
||||||
[NSPanel Page of the Tasmota Template Repository](https://templates.blakadder.com/sonoff_NSPanel.html)
|
[NSPanel Page of the Tasmota Template Repository](https://templates.blakadder.com/sonoff_NSPanel.html)
|
||||||
|
|
||||||
If you prefer EspHome over Tasmota, you can use this thrid party esphome component, which is replacing tasmota and the berry driver of this project.
|
If you prefer ESPHome over Tasmota, you can use this third-party ESPHome component, which replaces Tasmota and the Berry driver used in this project.
|
||||||
[ESPHome component](https://github.com/sairon/esphome-nspanel-lovelace-ui)
|
[ESPHome Component](https://github.com/sairon/esphome-nspanel-lovelace-ui)
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Configure Tasmota Template for NSPanel
|
## Configure Tasmota Template for NSPanel
|
||||||
|
|
||||||
Configure the NSPanel template for Tasmota. (Go to Configuration and Configure Other and paste the template there, make sure to tick the activate checkbox)
|
Configure the NSPanel template for Tasmota. (Go to Configuration > Configure Other, paste the template there, and make sure to tick the Activate checkbox.)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
You can use the following template or copy the one on the [Tasmota Template Repo Site](https://templates.blakadder.com/sonoff_NSPanel.html).
|
You can use the following template or copy the one from the [Tasmota Template Repo Site](https://templates.blakadder.com/sonoff_NSPanel.html):
|
||||||
|
|
||||||
`{"NAME":"NSPanel","GPIO":[0,0,0,0,3872,0,0,0,0,0,32,0,0,0,0,225,0,480,224,1,0,0,0,33,0,0,0,0,0,0,0,0,0,0,4736,0],"FLAG":0,"BASE":1,"CMND":"ADCParam 2,11200,10000,3950 | Sleep 0 | BuzzerPWM 1"}`
|
{"NAME":"NSPanel","GPIO":[0,0,0,0,3872,0,0,0,0,0,32,0,0,0,0,225,0,480,224,1,0,0,0,33,0,0,0,0,0,0,0,0,0,0,4736,0],"FLAG":0,"BASE":1,"CMND":"ADCParam 2,11200,10000,3950 | Sleep 0 | BuzzerPWM 1"}
|
||||||
|
|
||||||
After a reboot of tasmota your screen will light up with the stock display firmware.
|
After a reboot of Tasmota, your screen will light up with the stock display firmware.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Upload Berry Driver to Tasmota
|
## Upload Berry Driver to Tasmota
|
||||||
|
|
||||||
Go to `Consoles` > `Console` in Tasmota and execute the following command:
|
Go to Consoles > Console in Tasmota and execute the following command:
|
||||||
|
|
||||||
```
|
Backlog UrlFetch https://raw.githubusercontent.com/joBr99/nspanel-lovelace-ui/main/tasmota/autoexec.be; SetOption151 0; Restart 1
|
||||||
Backlog UrlFetch https://raw.githubusercontent.com/joBr99/nspanel-lovelace-ui/main/tasmota/autoexec.be; SetOption151 0;Restart 1
|
|
||||||
```
|
|
||||||
|
|
||||||
This will download the autoexec.be file from the repository and restart tasmota.
|
This downloads the autoexec.be file from the repository and restarts Tasmota.
|
||||||
|
|
||||||
Note: The command is also disabling matter to free up memory, as it's most likely not used by any homeassistant users anyway.
|
Note: This command also disables Matter to free up memory, as it's unlikely to be used by most Home Assistant users. (Matter can cause memory issues during flashing of the Nextion screen, but you can re-enable it later if needed.)
|
||||||
(Matter could cause memory issues during flashing of the Nextion Screen, but you can still enable it if you need to.)
|
|
||||||
|
---
|
||||||
|
|
||||||
## Flash Firmware to Nextion Screen
|
## Flash Firmware to Nextion Screen
|
||||||
|
|
||||||
Due the limitations of Berry, it's not possible to download the tft file directly from github, so I'm also renting a small server where you can download the file via HTTP.
|
Due to the limitations of Berry, it's not possible to download the TFT file directly from GitHub. A small server is available to download the file via HTTP.
|
||||||
|
|
||||||
Use the one following commands in the tasmota console (not berry console) to flash the latest release from this repository:
|
Use one of the following commands in the Tasmota console (not the Berry console) to flash the latest release from this repository:
|
||||||
|
|
||||||
EU Version: `FlashNextion http://nspanel.pky.eu/lui-release.tft`
|
EU Version:
|
||||||
|
FlashNextion http://nspanel.pky.eu/lui-release.tft
|
||||||
|
|
||||||
US Version Portrait: `FlashNextion http://nspanel.pky.eu/lui-us-p-release.tft`
|
US Version Portrait:
|
||||||
|
FlashNextion http://nspanel.pky.eu/lui-us-p-release.tft
|
||||||
|
|
||||||
US Version Landscape: `FlashNextion http://nspanel.pky.eu/lui-us-l-release.tft`
|
US Version Landscape:
|
||||||
|
FlashNextion http://nspanel.pky.eu/lui-us-l-release.tft
|
||||||
|
|
||||||
After sending the command, the screen should show a progress bar. The flashing progress takes around 5 minutes.
|
After sending the command, the screen should show a progress bar. The flashing process takes around 5 minutes.
|
||||||
|
|
||||||
Note: For the US Version Users - keep in mind that you need to add the model config option to your apps.yaml later, more details on config overview page
|
Note for US users: You'll need to add the model config option to your apps.yaml later. More details can be found on the config overview page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Alternatively you can use your own webserver or the one build into HomeAssistant:</summary>
|
<summary>Alternatively, you can use your own web server or the one built into Home Assistant:</summary>
|
||||||
<br>
|
<br>
|
||||||
Upload the nspanel.tft from the lastest release to a Webserver (for example www folder of Home Assistant) and execute the following command in Tasmota Console. (Development Version: [tft file from HMI folder](HMI/nspanel.tft))
|
Upload the nspanel.tft from the latest release to a web server (for example, the www folder of Home Assistant) and execute the following command in the Tasmota Console.
|
||||||
|
(Development version: [TFT file from HMI folder](HMI/nspanel.tft))
|
||||||
|
|
||||||
**Webserver must be HTTP, HTTPS is not supported, due to limitations of berry lang on tasmota**
|
**The web server must be HTTP. HTTPS is not supported due to Berry language limitations in Tasmota.**
|
||||||
|
|
||||||
`FlashNextion http://ip-address-of-your-homeassistant:8123/local/nspanel.tft`
|
FlashNextion http://ip-address-of-your-homeassistant:8123/local/nspanel.tft
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
var sourceDP = 'alias.0.Wohnzimmer.Heizung.ACTUAL';
|
const sourceDP = 'alias.0.Wohnzimmer.Heizung.ACTUAL';
|
||||||
var targetDP = '0_userdata.0.Test.chartTest';
|
const targetDP = '0_userdata.0.Test.chartTest';
|
||||||
var rangeHours = 24;
|
const rangeHours = 24;
|
||||||
var maxXAchsisTicks = 6;
|
const maxXAchsisTicks = 6;
|
||||||
var historyInstance = 'history.0';
|
const historyInstance = 'history.0';
|
||||||
|
const factor = 1; // Bei zu großen Werten und negativen Anzeigen im Panel um das 10fache erhöhen
|
||||||
|
|
||||||
on({id: sourceDP, change: "any"}, async function (obj) {
|
on({id: sourceDP, change: "any"}, async function (obj) {
|
||||||
sendTo(historyInstance, 'getHistory', {
|
sendTo(historyInstance, 'getHistory', {
|
||||||
@@ -25,7 +26,7 @@ on({id: sourceDP, change: "any"}, async function (obj) {
|
|||||||
//Check history items for requested hours
|
//Check history items for requested hours
|
||||||
for (var j = 0, targetValue = 0; j < result.result.length; j++) {
|
for (var j = 0, targetValue = 0; j < result.result.length; j++) {
|
||||||
var valueDate = new Date(result.result[j].ts);
|
var valueDate = new Date(result.result[j].ts);
|
||||||
var value = (Math.round(result.result[j].val * 10) / 10);
|
var value = Math.round(result.result[j].val / factor * 10);
|
||||||
|
|
||||||
if (valueDate > targetDate){
|
if (valueDate > targetDate){
|
||||||
if ((targetDate.getHours() % stepXAchsis) == 0){
|
if ((targetDate.getHours() % stepXAchsis) == 0){
|
||||||
|
|||||||
130
ioBroker/Blockly/CardLChart_Influx2.ts
Normal file
130
ioBroker/Blockly/CardLChart_Influx2.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Script fragt eine influxDb ab, um Daten für die cardLcart (Liniendiagramm) zuberechnen und im richtigen Format bereitzustellen.
|
||||||
|
* Es erstellt automatisch einen Datenpunkt.
|
||||||
|
* Die Abfrage muss ggf. angepasst werden. Aktuell ermittelt sie Werte der letzten 24h, zu Stundenwerten zusammengefasst.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Debug = false; // true für erweiterte Ausgaben im Log
|
||||||
|
|
||||||
|
const NSPanel_Path = '0_userdata.0.NSPanel.';
|
||||||
|
const Path = NSPanel_Path + 'Influx2NSPanel.cardLChart.';
|
||||||
|
const InfluxInstance = 'influxdb.0';
|
||||||
|
const influxDbBucket = 'storage_short';
|
||||||
|
const numberOfHoursAgo = 24;
|
||||||
|
const xAxisTicksEveryM = 60;
|
||||||
|
const xAxisLabelEveryM = 240;
|
||||||
|
//
|
||||||
|
|
||||||
|
const sensors : Record<string, Record <string, string>> = {};
|
||||||
|
/**
|
||||||
|
* Hier werden die Sensoren festgelegt nach flogendem Schema
|
||||||
|
*
|
||||||
|
* sensors[‘Datenpunkt(kompletter Pfad) des Messwertes'] = {'taget': 'Name des Datenpunkt für die Chartwerte', 'measurement': 'genutzter Alias in der Influxdb für den Messwert'};
|
||||||
|
*
|
||||||
|
* Wenn der Wert in der Datenbank keinen Alias hat bleibt der Wert 'measurement': weg.
|
||||||
|
* Jeder Messwert bekommt einen eigenen sensors[...] = {'target':....}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
sensors['netatmo-crawler.0.stationData.1.temperature'] = {'target':'AussenTemp', 'measurement':'wetter.temperatur'};
|
||||||
|
|
||||||
|
// ##### ab hier keine Änderungen mehr nötig #####
|
||||||
|
|
||||||
|
// create data source for NsPanel on script startup
|
||||||
|
Object.keys(sensors).forEach(async id => {
|
||||||
|
await generateDateAsync(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// then listen to the sensors and update the data source states accordingly
|
||||||
|
on({ id: Object.keys(sensors), change: 'any' }, async function (obj) {
|
||||||
|
if (!obj.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await generateDateAsync(obj.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
//__________________________
|
||||||
|
// Beschreibe diese Funktion: Daten generieren
|
||||||
|
async function generateDateAsync(sensorId: string) {
|
||||||
|
let idMeasurement = sensors[sensorId].measurement;
|
||||||
|
if (idMeasurement =='' ||idMeasurement == undefined) {idMeasurement = sensorId};
|
||||||
|
const dataPointId:string = Path + sensors[sensorId].target +'.ACTUAL';
|
||||||
|
if (Debug) log(`(f) generateDateAsync: ${sensorId} ${dataPointId} > ${idMeasurement}`);
|
||||||
|
|
||||||
|
const query =[
|
||||||
|
'from(bucket: "' + influxDbBucket + '")',
|
||||||
|
'|> range(start: -' + numberOfHoursAgo + 'h)',
|
||||||
|
'|> filter(fn: (r) => r["_measurement"] == "' + idMeasurement + '")',
|
||||||
|
'|> filter(fn: (r) => r["_field"] == "value")',
|
||||||
|
'|> drop(columns: ["from", "ack", "q"])',
|
||||||
|
'|> aggregateWindow(every: 1h, fn: last, createEmpty: false)',
|
||||||
|
'|> map(fn: (r) => ({ r with _rtime: int(v: r._time) - int(v: r._start)}))',
|
||||||
|
'|> yield(name: "_result")'].join('');
|
||||||
|
|
||||||
|
if (Debug) console.log('Query: ' + query);
|
||||||
|
|
||||||
|
const result : any = await sendToAsync(InfluxInstance, 'query', query);
|
||||||
|
if (result.error) {
|
||||||
|
console.error(result.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Debug) console.log(JSON.stringify(result));
|
||||||
|
const numResults = result.result.length;
|
||||||
|
let coordinates : string = '';
|
||||||
|
for (let r = 0; r < numResults; r++)
|
||||||
|
{
|
||||||
|
const list : string[] = [];
|
||||||
|
const numValues = result.result[r].length;
|
||||||
|
|
||||||
|
for (let i = 0; i < numValues; i++)
|
||||||
|
{
|
||||||
|
const time = Math.round(result.result[r][i]._rtime/1000/1000/1000/60);
|
||||||
|
const value = Math.round(result.result[r][i]._value * 10);
|
||||||
|
list.push(time + ":" + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
coordinates = list.join("~");
|
||||||
|
if (Debug) console.log(coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ticksAndLabelsList : string[] = []
|
||||||
|
const date = new Date();
|
||||||
|
date.setMinutes(0, 0, 0);
|
||||||
|
const ts = Math.round(date.getTime() / 1000);
|
||||||
|
const tsYesterday = ts - (numberOfHoursAgo * 3600);
|
||||||
|
if (Debug) console.log('Iterate from ' + tsYesterday + ' to ' + ts + ' stepsize=' + (xAxisTicksEveryM * 60));
|
||||||
|
for (let x = tsYesterday, i = 0; x < ts; x += (xAxisTicksEveryM * 60), i += xAxisTicksEveryM)
|
||||||
|
{
|
||||||
|
if ((i % xAxisLabelEveryM))
|
||||||
|
ticksAndLabelsList.push('' + i);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const currentDate = new Date(x * 1000);
|
||||||
|
// Hours part from the timestamp
|
||||||
|
const hours = "0" + String(currentDate.getHours());
|
||||||
|
// Minutes part from the timestamp
|
||||||
|
const minutes = "0" + String(currentDate.getMinutes());
|
||||||
|
const formattedTime = hours.slice(-2) + ':' + minutes.slice(-2);
|
||||||
|
|
||||||
|
ticksAndLabelsList.push(String(i) + "^" + formattedTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Debug) console.log('Ticks & Label: ' + ticksAndLabelsList);
|
||||||
|
if (Debug) console.log('Coordinates: ' + coordinates);
|
||||||
|
await setOrCreate(dataPointId, ticksAndLabelsList.join("+") + '~' + coordinates, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//__________________________
|
||||||
|
// Beschreibe diese Funktion: Datenpunkte anlegen bzw. schreiben
|
||||||
|
async function setOrCreate(id : string, value : any, ack : boolean) {
|
||||||
|
if (!(await existsStateAsync(id))) {
|
||||||
|
await createStateAsync(id, value, {
|
||||||
|
name: id.split('.').reverse()[0],
|
||||||
|
desc: 'Sensor Values [~<time>:<value>]*',
|
||||||
|
type: 'string',
|
||||||
|
role: 'value',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await setStateAsync(id, value, ack);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
const Debug = false;
|
|
||||||
|
|
||||||
const NSPanel_Path = '0_userdata.0.NSPanel.1.';
|
|
||||||
const Path = NSPanel_Path + 'Influx2NSPanel.cardLChart.';
|
|
||||||
const InfluxInstance = 'influxdb.1';
|
|
||||||
const influxDbBucket = 'iobroker';
|
|
||||||
const numberOfHoursAgo = 24;
|
|
||||||
const xAxisTicksEveryM = 60;
|
|
||||||
const xAxisLabelEveryM = 240;
|
|
||||||
|
|
||||||
// this records holds all sensors and their corresponding states which act as the data source for the charts
|
|
||||||
// add all sensors which are to be displayed in this script, there is no need to use multiple scripts
|
|
||||||
const sensors : Record<string, string> = {};
|
|
||||||
/* ↓ Id of the sensor ↓ Id of the data source for the charts */
|
|
||||||
sensors['deconz.0.Sensors.65.temperature'] = Path + 'buero_temperature';
|
|
||||||
sensors['deconz.0.Sensors.65.humidity'] = Path + 'buero_luftfeuchte';
|
|
||||||
|
|
||||||
// create data source for NsPanel on script startup
|
|
||||||
Object.keys(sensors).forEach(async x => {
|
|
||||||
await generateDateAsync(x, sensors[x]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// then listen to the sensors and update the data source states accordingly
|
|
||||||
on({ id: Object.keys(sensors), change: 'any' }, async function (obj) {
|
|
||||||
if (!obj.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await generateDateAsync(obj.id, sensors[obj.id]);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function generateDateAsync(sensorId: string, dataPointId: string) {
|
|
||||||
const query =[
|
|
||||||
'from(bucket: "' + influxDbBucket + '")',
|
|
||||||
'|> range(start: -' + numberOfHoursAgo + 'h)',
|
|
||||||
'|> filter(fn: (r) => r["_measurement"] == "' + sensorId + '")',
|
|
||||||
'|> filter(fn: (r) => r["_field"] == "value")',
|
|
||||||
'|> drop(columns: ["from", "ack", "q"])',
|
|
||||||
'|> aggregateWindow(every: 1h, fn: last, createEmpty: false)',
|
|
||||||
'|> map(fn: (r) => ({ r with _rtime: int(v: r._time) - int(v: r._start)}))',
|
|
||||||
'|> yield(name: "_result")'].join('');
|
|
||||||
|
|
||||||
if (Debug) console.log('Query: ' + query);
|
|
||||||
|
|
||||||
const result : any = await sendToAsync(InfluxInstance, 'query', query);
|
|
||||||
if (result.error) {
|
|
||||||
console.error(result.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Debug) console.log(result);
|
|
||||||
const numResults = result.result.length;
|
|
||||||
let coordinates : string = '';
|
|
||||||
for (let r = 0; r < numResults; r++)
|
|
||||||
{
|
|
||||||
const list : string[] = []
|
|
||||||
const numValues = result.result[r].length;
|
|
||||||
|
|
||||||
for (let i = 0; i < numValues; i++)
|
|
||||||
{
|
|
||||||
const time = Math.round(result.result[r][i]._rtime/1000/1000/1000/60);
|
|
||||||
const value = Math.round(result.result[r][i]._value * 10);
|
|
||||||
list.push(time + ":" + value);
|
|
||||||
}
|
|
||||||
|
|
||||||
coordinates = list.join("~");
|
|
||||||
|
|
||||||
if (Debug) console.log(coordinates);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ticksAndLabelsList : string[] = []
|
|
||||||
const date = new Date();
|
|
||||||
date.setMinutes(0, 0, 0);
|
|
||||||
const ts = Math.round(date.getTime() / 1000);
|
|
||||||
const tsYesterday = ts - (numberOfHoursAgo * 3600);
|
|
||||||
if (Debug) console.log('Iterate from ' + tsYesterday + ' to ' + ts + ' stepsize=' + (xAxisTicksEveryM * 60));
|
|
||||||
for (let x = tsYesterday, i = 0; x < ts; x += (xAxisTicksEveryM * 60), i += xAxisTicksEveryM)
|
|
||||||
{
|
|
||||||
if ((i % xAxisLabelEveryM))
|
|
||||||
ticksAndLabelsList.push('' + i);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const currentDate = new Date(x * 1000);
|
|
||||||
// Hours part from the timestamp
|
|
||||||
const hours = "0" + String(currentDate.getHours());
|
|
||||||
// Minutes part from the timestamp
|
|
||||||
const minutes = "0" + String(currentDate.getMinutes());
|
|
||||||
const formattedTime = hours.slice(-2) + ':' + minutes.slice(-2);
|
|
||||||
|
|
||||||
ticksAndLabelsList.push(String(i) + "^" + formattedTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Debug) console.log('Ticks & Label: ' + ticksAndLabelsList);
|
|
||||||
if (Debug) console.log('Coordinates: ' + coordinates);
|
|
||||||
await setOrCreate(dataPointId, ticksAndLabelsList.join("+") + '~' + coordinates, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setOrCreate(id : string, value : any, ack : boolean) {
|
|
||||||
if (!(await existsStateAsync(id))) {
|
|
||||||
await createStateAsync(id, value, {
|
|
||||||
name: id.split('.').reverse()[0],
|
|
||||||
desc: 'Sensor Values [~<time>:<value>]*',
|
|
||||||
type: 'string',
|
|
||||||
role: 'value',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await setStateAsync(id, value, ack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
BIN
ioBroker/HMI/nspanel-us-l-v4.5.0.HMI
Normal file
BIN
ioBroker/HMI/nspanel-us-l-v4.5.0.HMI
Normal file
Binary file not shown.
BIN
ioBroker/HMI/nspanel-us-p-v4.5.0.HMI
Normal file
BIN
ioBroker/HMI/nspanel-us-p-v4.5.0.HMI
Normal file
Binary file not shown.
BIN
ioBroker/HMI/nspanel-v4.5.0.HMI
Normal file
BIN
ioBroker/HMI/nspanel-v4.5.0.HMI
Normal file
Binary file not shown.
13731
ioBroker/NsPanelTs.ts
13731
ioBroker/NsPanelTs.ts
File diff suppressed because it is too large
Load Diff
@@ -2565,6 +2565,10 @@
|
|||||||
"en-US":"Crossfade",
|
"en-US":"Crossfade",
|
||||||
"de-DE":"Überblenden"
|
"de-DE":"Überblenden"
|
||||||
},
|
},
|
||||||
|
"tools":{
|
||||||
|
"en-US":"Tools",
|
||||||
|
"de-DE":"Tools"
|
||||||
|
},
|
||||||
"speaker":{
|
"speaker":{
|
||||||
"en-US":"Speakerlist",
|
"en-US":"Speakerlist",
|
||||||
"de-DE":"Wiedergabegeräte",
|
"de-DE":"Wiedergabegeräte",
|
||||||
|
|||||||
@@ -2701,5 +2701,13 @@
|
|||||||
"scriptname":{
|
"scriptname":{
|
||||||
"en-US":"Script name",
|
"en-US":"Script name",
|
||||||
"de-DE":"Skriptname"
|
"de-DE":"Skriptname"
|
||||||
|
},
|
||||||
|
"hiddencards_offon":{
|
||||||
|
"en-US":"hidden Cards (on/off)",
|
||||||
|
"de-DE":"ausgeblendete Seiten (an/aus)"
|
||||||
|
},
|
||||||
|
"easyview_layout":{
|
||||||
|
"en-US":"Easyview Layout",
|
||||||
|
"de-DE":"Einfaches Layout"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ repo_name: jobr99/nspanel-lovelace-ui
|
|||||||
repo_url: https://github.com/jobr99/nspanel-lovelace-ui
|
repo_url: https://github.com/jobr99/nspanel-lovelace-ui
|
||||||
edit_uri: ""
|
edit_uri: ""
|
||||||
|
|
||||||
copyright: "Copyright © 2023 Johannes Braun"
|
copyright: "Copyright © 2026 Johannes Braun"
|
||||||
|
|
||||||
docs_dir: docs
|
docs_dir: docs
|
||||||
|
|
||||||
@@ -42,8 +42,6 @@ extra_css:
|
|||||||
extra:
|
extra:
|
||||||
analytics:
|
analytics:
|
||||||
provider: custom
|
provider: custom
|
||||||
version:
|
|
||||||
provider: mike
|
|
||||||
|
|
||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
- admonition
|
- admonition
|
||||||
@@ -63,7 +61,6 @@ markdown_extensions:
|
|||||||
plugins:
|
plugins:
|
||||||
- search:
|
- search:
|
||||||
lang: en
|
lang: en
|
||||||
- mkdocs-video
|
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
- "Overview": index.md
|
- "Overview": index.md
|
||||||
@@ -75,6 +72,7 @@ nav:
|
|||||||
- "FAQ": faq.md
|
- "FAQ": faq.md
|
||||||
- "Configuration - apps.yaml (Home Assistant)":
|
- "Configuration - apps.yaml (Home Assistant)":
|
||||||
- "Overview": config-overview.md
|
- "Overview": config-overview.md
|
||||||
|
#- "Migration to Standalone Rewrite": config-migration-standalone.md
|
||||||
- "Screensaver": config-screensaver.md
|
- "Screensaver": config-screensaver.md
|
||||||
- "Cards":
|
- "Cards":
|
||||||
- "Entities Card": card-entities.md
|
- "Entities Card": card-entities.md
|
||||||
|
|||||||
@@ -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.74"
|
version: "4.7.85"
|
||||||
slug: nspanel-lovelace-ui
|
slug: nspanel-lovelace-ui
|
||||||
description: NSPanel Lovelace UI Addon
|
description: NSPanel Lovelace UI Addon
|
||||||
services:
|
services:
|
||||||
|
|||||||
@@ -209,11 +209,11 @@ class HAEntity(panel_cards.Entity):
|
|||||||
forecast = libs.home_assistant.execute_script(
|
forecast = libs.home_assistant.execute_script(
|
||||||
entity_name=self.entity_id,
|
entity_name=self.entity_id,
|
||||||
domain='weather',
|
domain='weather',
|
||||||
service="get_forecast",
|
service="get_forecasts",
|
||||||
service_data={
|
service_data={
|
||||||
'type': forecast_type
|
'type': forecast_type
|
||||||
}
|
}
|
||||||
).get("forecast", [])
|
).get(self.entity_id,{}).get("forecast", [])
|
||||||
if len(forecast) > pos:
|
if len(forecast) > pos:
|
||||||
forcast_pos = forecast[pos]
|
forcast_pos = forecast[pos]
|
||||||
forcast_condition = forcast_pos.get("condition", "")
|
forcast_condition = forcast_pos.get("condition", "")
|
||||||
@@ -475,7 +475,7 @@ class AlarmCard(HACard):
|
|||||||
main_entity = self.entities[0]
|
main_entity = self.entities[0]
|
||||||
main_entity.render()
|
main_entity.render()
|
||||||
|
|
||||||
print(main_entity.state)
|
logging.debug("Alarm card state for '%s': %s", main_entity.entity_id, main_entity.state)
|
||||||
|
|
||||||
icon = get_icon_char("shield-off")
|
icon = get_icon_char("shield-off")
|
||||||
color = rgb_dec565([255,255,255])
|
color = rgb_dec565([255,255,255])
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ def wait_for_ha_cache():
|
|||||||
while time.time() < mustend:
|
while time.time() < mustend:
|
||||||
if len(libs.home_assistant.home_assistant_entity_state_cache) == 0:
|
if len(libs.home_assistant.home_assistant_entity_state_cache) == 0:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
if len(libs.home_assistant.home_assistant_entity_state_cache) == 0:
|
||||||
|
logging.warning("Home Assistant entity cache is still empty after waiting 5 seconds")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, screenBrightness, sleepOverride, return_involved_entities=False):
|
def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, screenBrightness, sleepOverride, return_involved_entities=False):
|
||||||
@@ -20,27 +22,32 @@ def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, scr
|
|||||||
dimmode = sleepBrightness
|
dimmode = sleepBrightness
|
||||||
elif isinstance(sleepBrightness, list):
|
elif isinstance(sleepBrightness, list):
|
||||||
logging.error("list style config for sleepBrightness no longer supported")
|
logging.error("list style config for sleepBrightness no longer supported")
|
||||||
elif sleepBrightness.startswith("ha:"):
|
#elif sleepBrightness.startswith("ha:"):
|
||||||
time.sleep(1)
|
# time.sleep(1)
|
||||||
dimmode = int(float(libs.home_assistant.get_template(sleepBrightness)[3:]))
|
# dimmode = int(float(libs.home_assistant.get_template(sleepBrightness)[3:]))
|
||||||
involved_entities.extend(libs.home_assistant.get_template_listener_entities(sleepBrightness))
|
# involved_entities.extend(libs.home_assistant.get_template_listener_entities(sleepBrightness))
|
||||||
elif libs.home_assistant.is_existent(sleepBrightness):
|
elif libs.home_assistant.is_existent(sleepBrightness):
|
||||||
involved_entities.append(sleepBrightness)
|
involved_entities.append(sleepBrightness)
|
||||||
dimmode = int(float(libs.home_assistant.get_entity_data(sleepBrightness).get('state', 10)))
|
try:
|
||||||
|
dimmode = int(float(libs.home_assistant.get_entity_data(sleepBrightness).get('state', 10)))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
logging.exception("sleepBrightness entity '%s' has an invalid state value", sleepBrightness)
|
||||||
|
|
||||||
if screenBrightness:
|
if screenBrightness:
|
||||||
if isinstance(screenBrightness, int):
|
if isinstance(screenBrightness, int):
|
||||||
dimValueNormal = screenBrightness
|
dimValueNormal = screenBrightness
|
||||||
elif isinstance(screenBrightness, list):
|
elif isinstance(screenBrightness, list):
|
||||||
logging.error("list style config for screenBrightness no longer supported")
|
logging.error("list style config for screenBrightness no longer supported")
|
||||||
elif screenBrightness.startswith("ha:"):
|
#elif screenBrightness.startswith("ha:"):
|
||||||
time.sleep(1)
|
# time.sleep(1)
|
||||||
dimValueNormal = int(float(libs.home_assistant.get_template(screenBrightness)[3:]))
|
# dimValueNormal = int(float(libs.home_assistant.get_template(screenBrightness)[3:]))
|
||||||
involved_entities.extend(libs.home_assistant.get_template_listener_entities(screenBrightness))
|
# involved_entities.extend(libs.home_assistant.get_template_listener_entities(screenBrightness))
|
||||||
elif libs.home_assistant.is_existent(screenBrightness):
|
elif libs.home_assistant.is_existent(screenBrightness):
|
||||||
involved_entities.append(screenBrightness)
|
involved_entities.append(screenBrightness)
|
||||||
dimValueNormal = int(float(libs.home_assistant.get_entity_data(screenBrightness).get('state', 100)))
|
try:
|
||||||
|
dimValueNormal = int(float(libs.home_assistant.get_entity_data(screenBrightness).get('state', 100)))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
logging.exception("screenBrightness entity '%s' has an invalid state value", screenBrightness)
|
||||||
# force sleep brightness to zero in case sleepTracking is active
|
# force sleep brightness to zero in case sleepTracking is active
|
||||||
if sleepTracking:
|
if sleepTracking:
|
||||||
if libs.home_assistant.is_existent(sleepTracking):
|
if libs.home_assistant.is_existent(sleepTracking):
|
||||||
@@ -64,12 +71,13 @@ def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, scr
|
|||||||
else:
|
else:
|
||||||
return dimmode, dimValueNormal
|
return dimmode, dimValueNormal
|
||||||
|
|
||||||
def handle_buttons(entity_id, btype, value, entity_config=None):
|
def handle_buttons(entity_id, btype, value, entity_config=None, action_context=None):
|
||||||
|
action_context = action_context or {}
|
||||||
match btype:
|
match btype:
|
||||||
case 'button':
|
case 'button':
|
||||||
button_press(entity_id, value)
|
button_press(entity_id, value, action_context=action_context)
|
||||||
case 'OnOff':
|
case 'OnOff':
|
||||||
on_off(entity_id, value)
|
on_off(entity_id, value, action_context=action_context)
|
||||||
case 'number-set':
|
case 'number-set':
|
||||||
if entity_id.startswith('fan'):
|
if entity_id.startswith('fan'):
|
||||||
attr = libs.home_assistant.get_entity_data(entity_id).get('attributes', [])
|
attr = libs.home_assistant.get_entity_data(entity_id).get('attributes', [])
|
||||||
@@ -77,7 +85,7 @@ def handle_buttons(entity_id, btype, value, entity_config=None):
|
|||||||
service_data = {
|
service_data = {
|
||||||
"value": int(value)
|
"value": int(value)
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "set_value", service_data=service_data)
|
call_ha_service(entity_id, "set_value", service_data=service_data, action_context=action_context)
|
||||||
case 'up' | 'stop' | 'down' | 'tiltOpen' | 'tiltStop' | 'tiltClose' | 'media-next' | 'media-back' | 'media-pause' | 'timer-cancel' | 'timer-pause' | 'timer-finish':
|
case 'up' | 'stop' | 'down' | 'tiltOpen' | 'tiltStop' | 'tiltClose' | 'media-next' | 'media-back' | 'media-pause' | 'timer-cancel' | 'timer-pause' | 'timer-finish':
|
||||||
action_service_mapping = {
|
action_service_mapping = {
|
||||||
'up': 'open_cover',
|
'up': 'open_cover',
|
||||||
@@ -94,37 +102,37 @@ def handle_buttons(entity_id, btype, value, entity_config=None):
|
|||||||
'timer-finish': 'finish',
|
'timer-finish': 'finish',
|
||||||
}
|
}
|
||||||
service = action_service_mapping[btype]
|
service = action_service_mapping[btype]
|
||||||
call_ha_service(entity_id, service)
|
call_ha_service(entity_id, service, action_context=action_context)
|
||||||
case 'timer-start':
|
case 'timer-start':
|
||||||
if value:
|
if value:
|
||||||
service_data = {
|
service_data = {
|
||||||
"duration": value
|
"duration": value
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "start", service_data=service_data)
|
call_ha_service(entity_id, "start", service_data=service_data, action_context=action_context)
|
||||||
else:
|
else:
|
||||||
call_ha_service(entity_id, "start")
|
call_ha_service(entity_id, "start", action_context=action_context)
|
||||||
case 'positionSlider':
|
case 'positionSlider':
|
||||||
service_data = {
|
service_data = {
|
||||||
"position": int(value)
|
"position": int(value)
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "set_cover_position", service_data=service_data)
|
call_ha_service(entity_id, "set_cover_position", service_data=service_data, action_context=action_context)
|
||||||
case 'tiltSlider':
|
case 'tiltSlider':
|
||||||
service_data = {
|
service_data = {
|
||||||
"tilt_position": int(value)
|
"tilt_position": int(value)
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "set_cover_tilt_position", service_data=service_data)
|
call_ha_service(entity_id, "set_cover_tilt_position", service_data=service_data, action_context=action_context)
|
||||||
case 'media-OnOff':
|
case 'media-OnOff':
|
||||||
state = libs.home_assistant.get_entity_data(entity_id).get('state', '')
|
state = libs.home_assistant.get_entity_data(entity_id).get('state', '')
|
||||||
if state == "off":
|
if state == "off":
|
||||||
call_ha_service(entity_id, "turn_on")
|
call_ha_service(entity_id, "turn_on", action_context=action_context)
|
||||||
else:
|
else:
|
||||||
call_ha_service(entity_id, "turn_off")
|
call_ha_service(entity_id, "turn_off", action_context=action_context)
|
||||||
case 'media-shuffle':
|
case 'media-shuffle':
|
||||||
suffle = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get('shuffle')
|
suffle = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get('shuffle')
|
||||||
service_data = {
|
service_data = {
|
||||||
"shuffle": not suffle
|
"shuffle": not suffle
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "set_value", service_data=service_data)
|
call_ha_service(entity_id, "set_value", service_data=service_data, action_context=action_context)
|
||||||
case 'volumeSlider':
|
case 'volumeSlider':
|
||||||
pos = int(value)
|
pos = int(value)
|
||||||
# HA wants to have this value between 0 and 1 as float
|
# HA wants to have this value between 0 and 1 as float
|
||||||
@@ -132,12 +140,12 @@ def handle_buttons(entity_id, btype, value, entity_config=None):
|
|||||||
service_data = {
|
service_data = {
|
||||||
"volume_level": pos
|
"volume_level": pos
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "volume_set", service_data=service_data)
|
call_ha_service(entity_id, "volume_set", service_data=service_data, action_context=action_context)
|
||||||
case 'speaker-sel':
|
case 'speaker-sel':
|
||||||
service_data = {
|
service_data = {
|
||||||
"volume_level": value
|
"volume_level": value
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "select_source", service_data=service_data)
|
call_ha_service(entity_id, "select_source", service_data=service_data, action_context=action_context)
|
||||||
# for light detail page
|
# for light detail page
|
||||||
case 'brightnessSlider':
|
case 'brightnessSlider':
|
||||||
# scale 0-100 to ha brightness range
|
# scale 0-100 to ha brightness range
|
||||||
@@ -145,7 +153,7 @@ def handle_buttons(entity_id, btype, value, entity_config=None):
|
|||||||
service_data = {
|
service_data = {
|
||||||
"brightness": brightness
|
"brightness": brightness
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "turn_on", service_data=service_data)
|
call_ha_service(entity_id, "turn_on", service_data=service_data, action_context=action_context)
|
||||||
case 'colorTempSlider':
|
case 'colorTempSlider':
|
||||||
attr = libs.home_assistant.get_entity_data(entity_id).get('attributes', [])
|
attr = libs.home_assistant.get_entity_data(entity_id).get('attributes', [])
|
||||||
min_mireds = attr.get("min_mireds")
|
min_mireds = attr.get("min_mireds")
|
||||||
@@ -155,19 +163,19 @@ def handle_buttons(entity_id, btype, value, entity_config=None):
|
|||||||
service_data = {
|
service_data = {
|
||||||
"color_temp": color_val
|
"color_temp": color_val
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "turn_on", service_data=service_data)
|
call_ha_service(entity_id, "turn_on", service_data=service_data, action_context=action_context)
|
||||||
case 'colorWheel':
|
case 'colorWheel':
|
||||||
value = value.split('|')
|
value = value.split('|')
|
||||||
color = pos_to_color(int(value[0]), int(value[1]), int(value[2]))
|
color = pos_to_color(int(value[0]), int(value[1]), int(value[2]))
|
||||||
service_data = {
|
service_data = {
|
||||||
"rgb_color": color
|
"rgb_color": color
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "turn_on", service_data=service_data)
|
call_ha_service(entity_id, "turn_on", service_data=service_data, action_context=action_context)
|
||||||
case 'disarm' | 'arm_home' | 'arm_away' | 'arm_night' | 'arm_vacation':
|
case 'disarm' | 'arm_home' | 'arm_away' | 'arm_night' | 'arm_vacation':
|
||||||
service_data = {
|
service_data = {
|
||||||
"code": value
|
"code": 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, action_context=action_context)
|
||||||
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', [])
|
attr = libs.home_assistant.get_entity_data(entity_id).get('attributes', [])
|
||||||
mapping = {
|
mapping = {
|
||||||
@@ -182,7 +190,7 @@ def handle_buttons(entity_id, btype, value, entity_config=None):
|
|||||||
service_data = {
|
service_data = {
|
||||||
mapping[btype][:-1]: mode
|
mapping[btype][:-1]: mode
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, f"set_{mapping[btype][:-1]}", service_data=service_data)
|
call_ha_service(entity_id, f"set_{mapping[btype][:-1]}", service_data=service_data, action_context=action_context)
|
||||||
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:
|
||||||
@@ -190,7 +198,7 @@ def handle_buttons(entity_id, btype, value, entity_config=None):
|
|||||||
service_data = {
|
service_data = {
|
||||||
"option": option
|
"option": option
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "select_option", service_data=service_data)
|
call_ha_service(entity_id, "select_option", service_data=service_data, action_context=action_context)
|
||||||
case 'mode-media_player':
|
case 'mode-media_player':
|
||||||
options = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get("source_list", [])
|
options = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get("source_list", [])
|
||||||
if options:
|
if options:
|
||||||
@@ -198,7 +206,7 @@ def handle_buttons(entity_id, btype, value, entity_config=None):
|
|||||||
service_data = {
|
service_data = {
|
||||||
"source": option
|
"source": option
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "select_source", service_data=service_data)
|
call_ha_service(entity_id, "select_source", service_data=service_data, action_context=action_context)
|
||||||
case 'mode-light':
|
case 'mode-light':
|
||||||
options = entity_config.get("effectList", 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:
|
||||||
@@ -206,13 +214,13 @@ def handle_buttons(entity_id, btype, value, entity_config=None):
|
|||||||
service_data = {
|
service_data = {
|
||||||
"effect": option
|
"effect": option
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "turn_on", service_data=service_data)
|
call_ha_service(entity_id, "turn_on", service_data=service_data, action_context=action_context)
|
||||||
case 'tempUpd':
|
case 'tempUpd':
|
||||||
temp = int(value)/10
|
temp = int(value)/10
|
||||||
service_data = {
|
service_data = {
|
||||||
"temperature": temp
|
"temperature": temp
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "set_temperature", service_data=service_data)
|
call_ha_service(entity_id, "set_temperature", service_data=service_data, action_context=action_context)
|
||||||
case 'tempUpdHighLow':
|
case 'tempUpdHighLow':
|
||||||
value = value.split("|")
|
value = value.split("|")
|
||||||
temp_high = int(value[0])/10
|
temp_high = int(value[0])/10
|
||||||
@@ -221,47 +229,67 @@ def handle_buttons(entity_id, btype, value, entity_config=None):
|
|||||||
"target_temp_high": temp_high,
|
"target_temp_high": temp_high,
|
||||||
"target_temp_low": temp_low,
|
"target_temp_low": temp_low,
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "set_temperature", service_data=service_data)
|
call_ha_service(entity_id, "set_temperature", service_data=service_data, action_context=action_context)
|
||||||
case 'hvac_action':
|
case 'hvac_action':
|
||||||
service_data = {
|
service_data = {
|
||||||
"hvac_mode": value
|
"hvac_mode": value
|
||||||
}
|
}
|
||||||
call_ha_service(entity_id, "set_hvac_mode", service_data=service_data)
|
call_ha_service(entity_id, "set_hvac_mode", service_data=service_data, action_context=action_context)
|
||||||
case _:
|
case _:
|
||||||
logging.error("Not implemented: %s", btype)
|
logging.error("Not implemented: %s", btype)
|
||||||
|
|
||||||
def call_ha_service(entity_id, service, service_data = {}):
|
def call_ha_service(entity_id, service, service_data=None, action_context=None):
|
||||||
|
if service_data is None:
|
||||||
|
service_data = {}
|
||||||
|
action_context = action_context or {}
|
||||||
etype = entity_id.split(".")[0]
|
etype = entity_id.split(".")[0]
|
||||||
libs.home_assistant.call_service(
|
ok = libs.home_assistant.call_service(
|
||||||
entity_name=entity_id,
|
entity_name=entity_id,
|
||||||
domain=etype,
|
domain=etype,
|
||||||
service=service,
|
service=service,
|
||||||
service_data=service_data
|
service_data=service_data
|
||||||
)
|
)
|
||||||
|
if ok:
|
||||||
|
logging.info(
|
||||||
|
"Panel action forwarded to Home Assistant: panel='%s', action='%s', value='%s', entity='%s', service='%s', data=%s",
|
||||||
|
action_context.get("panel", "unknown"),
|
||||||
|
action_context.get("btype", "unknown"),
|
||||||
|
action_context.get("value"),
|
||||||
|
entity_id,
|
||||||
|
service,
|
||||||
|
service_data,
|
||||||
|
)
|
||||||
|
if not ok:
|
||||||
|
logging.error(
|
||||||
|
"Home Assistant service call failed: entity='%s', service='%s', data=%s",
|
||||||
|
entity_id,
|
||||||
|
service,
|
||||||
|
service_data,
|
||||||
|
)
|
||||||
|
|
||||||
def button_press(entity_id, value):
|
def button_press(entity_id, value, action_context=None):
|
||||||
etype = entity_id.split(".")[0]
|
etype = entity_id.split(".")[0]
|
||||||
match etype:
|
match etype:
|
||||||
case 'scene' | 'script':
|
case 'scene' | 'script':
|
||||||
call_ha_service(entity_id, "turn_on")
|
call_ha_service(entity_id, "turn_on", action_context=action_context)
|
||||||
case 'light' | 'switch' | 'input_boolean' | 'automation' | 'fan':
|
case 'light' | 'switch' | 'input_boolean' | 'automation' | 'fan':
|
||||||
call_ha_service(entity_id, "toggle")
|
call_ha_service(entity_id, "toggle", action_context=action_context)
|
||||||
case 'lock':
|
case 'lock':
|
||||||
state = libs.home_assistant.get_entity_data(entity_id).get('state', '')
|
state = libs.home_assistant.get_entity_data(entity_id).get('state', '')
|
||||||
if state == "locked":
|
if state == "locked":
|
||||||
call_ha_service(entity_id, "unlock")
|
call_ha_service(entity_id, "unlock", action_context=action_context)
|
||||||
else:
|
else:
|
||||||
call_ha_service(entity_id, "lock")
|
call_ha_service(entity_id, "lock", action_context=action_context)
|
||||||
case 'button' | 'input_button':
|
case 'button' | 'input_button':
|
||||||
call_ha_service(entity_id, "press")
|
call_ha_service(entity_id, "press", action_context=action_context)
|
||||||
case 'input_select' | 'select':
|
case 'input_select' | 'select':
|
||||||
call_ha_service(entity_id, "select_next")
|
call_ha_service(entity_id, "select_next", action_context=action_context)
|
||||||
case 'vacuum':
|
case 'vacuum':
|
||||||
state = libs.home_assistant.get_entity_data(entity_id).get('state', '')
|
state = libs.home_assistant.get_entity_data(entity_id).get('state', '')
|
||||||
if state == "docked":
|
if state == "docked":
|
||||||
call_ha_service(entity_id, "start")
|
call_ha_service(entity_id, "start", action_context=action_context)
|
||||||
else:
|
else:
|
||||||
call_ha_service(entity_id, "return_to_base")
|
call_ha_service(entity_id, "return_to_base", action_context=action_context)
|
||||||
case _:
|
case _:
|
||||||
logging.error("buttonpress for entity type %s not implemented", etype)
|
logging.error("buttonpress for entity type %s not implemented", etype)
|
||||||
|
|
||||||
@@ -269,14 +297,14 @@ def button_press(entity_id, value):
|
|||||||
# apis.ha_api.call_service(entity_id.replace(
|
# apis.ha_api.call_service(entity_id.replace(
|
||||||
# 'service.', '', 1).replace('.', '/', 1), **entity_config.data)
|
# 'service.', '', 1).replace('.', '/', 1), **entity_config.data)
|
||||||
|
|
||||||
def on_off(entity_id, value):
|
def on_off(entity_id, value, action_context=None):
|
||||||
etype = entity_id.split(".")[0]
|
etype = entity_id.split(".")[0]
|
||||||
match etype:
|
match etype:
|
||||||
case 'light' | 'switch' | 'input_boolean' | 'automation' | 'fan':
|
case 'light' | 'switch' | 'input_boolean' | 'automation' | 'fan':
|
||||||
service = "turn_off"
|
service = "turn_off"
|
||||||
if value == "1":
|
if value == "1":
|
||||||
service = "turn_on"
|
service = "turn_on"
|
||||||
call_ha_service(entity_id, service)
|
call_ha_service(entity_id, service, action_context=action_context)
|
||||||
case _:
|
case _:
|
||||||
logging.error(
|
logging.error(
|
||||||
"Control action on_off not implemented for %s", entity_id)
|
"Control action on_off not implemented for %s", entity_id)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ ws_connected = False
|
|||||||
home_assistant_entity_state_cache = {}
|
home_assistant_entity_state_cache = {}
|
||||||
template_cache = {}
|
template_cache = {}
|
||||||
response_buffer = {}
|
response_buffer = {}
|
||||||
|
nspanel_event_handler = None
|
||||||
|
|
||||||
|
|
||||||
ON_CONNECT_HANDLER = None
|
ON_CONNECT_HANDLER = None
|
||||||
@@ -46,10 +47,16 @@ def register_on_disconnect_handler(handler):
|
|||||||
|
|
||||||
def on_message(ws, message):
|
def on_message(ws, message):
|
||||||
global auth_ok, request_all_states_id, home_assistant_entity_state_cache, response_buffer, template_cache
|
global auth_ok, request_all_states_id, home_assistant_entity_state_cache, response_buffer, template_cache
|
||||||
json_msg = json.loads(message)
|
try:
|
||||||
if json_msg["type"] == "auth_required":
|
json_msg = json.loads(message)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logging.exception("Failed to parse Home Assistant websocket message as JSON")
|
||||||
|
return
|
||||||
|
|
||||||
|
message_type = json_msg.get("type")
|
||||||
|
if message_type == "auth_required":
|
||||||
authenticate_client()
|
authenticate_client()
|
||||||
elif json_msg["type"] == "auth_ok":
|
elif message_type == "auth_ok":
|
||||||
auth_ok = True
|
auth_ok = True
|
||||||
logging.info("Home Assistant auth OK. Requesting existing states.")
|
logging.info("Home Assistant auth OK. Requesting existing states.")
|
||||||
subscribe_to_events()
|
subscribe_to_events()
|
||||||
@@ -57,30 +64,41 @@ def on_message(ws, message):
|
|||||||
if ON_CONNECT_HANDLER is not None:
|
if ON_CONNECT_HANDLER is not None:
|
||||||
ON_CONNECT_HANDLER()
|
ON_CONNECT_HANDLER()
|
||||||
# for templates
|
# for templates
|
||||||
elif json_msg["type"] == "event" and json_msg["id"] in response_buffer:
|
elif message_type == "event" and json_msg.get("id") in response_buffer:
|
||||||
|
event = json_msg.get("event", {})
|
||||||
|
listeners = event.get("listeners", {})
|
||||||
template_cache[response_buffer[json_msg["id"]]] = {
|
template_cache[response_buffer[json_msg["id"]]] = {
|
||||||
"result": json_msg["event"]["result"],
|
"result": event.get("result"),
|
||||||
"listener-entities": json_msg["event"]["listeners"]["entities"]
|
"listener-entities": listeners.get("entities", [])
|
||||||
}
|
}
|
||||||
elif json_msg["type"] == "event" and json_msg["event"]["event_type"] == "state_changed":
|
elif message_type == "event" and json_msg.get("event", {}).get("event_type") == "state_changed":
|
||||||
entity_id = json_msg["event"]["data"]["entity_id"]
|
event_data = json_msg.get("event", {}).get("data", {})
|
||||||
home_assistant_entity_state_cache[entity_id] = json_msg["event"]["data"]["new_state"]
|
entity_id = event_data.get("entity_id")
|
||||||
|
if not entity_id:
|
||||||
|
logging.debug("Received state_changed event without entity_id")
|
||||||
|
return
|
||||||
|
home_assistant_entity_state_cache[entity_id] = event_data.get("new_state")
|
||||||
send_entity_update(entity_id)
|
send_entity_update(entity_id)
|
||||||
# rerender template
|
# rerender template
|
||||||
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":
|
elif message_type == "event" and json_msg.get("event", {}).get("event_type") == "esphome.nspanel.data":
|
||||||
nspanel_data_callback(json_msg["event"]["data"]["device_id"], json_msg["event"]["data"]["CustomRecv"])
|
event_data = json_msg.get("event", {}).get("data", {})
|
||||||
elif json_msg["type"] == "result" and not json_msg["success"]:
|
device_id = event_data.get("device_id")
|
||||||
logging.error("Failed result: ")
|
custom_recv = event_data.get("CustomRecv")
|
||||||
logging.error(json_msg)
|
if nspanel_event_handler is None:
|
||||||
elif json_msg["type"] == "result" and json_msg["success"]:
|
logging.debug("No NsPanel event handler registered; dropping event for device '%s'", device_id)
|
||||||
if json_msg["id"] == request_all_states_id:
|
return
|
||||||
for entity in json_msg["result"]:
|
nspanel_event_handler(device_id, custom_recv)
|
||||||
|
elif message_type == "result" and not json_msg.get("success"):
|
||||||
|
logging.error("Home Assistant request failed: %s", json_msg)
|
||||||
|
elif message_type == "result" and json_msg.get("success"):
|
||||||
|
if json_msg.get("id") == request_all_states_id:
|
||||||
|
for entity in json_msg.get("result", []):
|
||||||
home_assistant_entity_state_cache[entity["entity_id"]] = entity
|
home_assistant_entity_state_cache[entity["entity_id"]] = entity
|
||||||
else:
|
else:
|
||||||
if json_msg["id"] in response_buffer and json_msg.get("result"):
|
if json_msg.get("id") in response_buffer and json_msg.get("result"):
|
||||||
response_buffer[json_msg["id"]] = json_msg["result"]
|
response_buffer[json_msg["id"]] = json_msg["result"]
|
||||||
return None # Ignore success result messages
|
return None # Ignore success result messages
|
||||||
else:
|
else:
|
||||||
@@ -98,7 +116,11 @@ def _ws_connection_open(ws):
|
|||||||
def _ws_connection_close(ws, close_status_code, close_msg):
|
def _ws_connection_close(ws, close_status_code, close_msg):
|
||||||
global ws_connected
|
global ws_connected
|
||||||
ws_connected = False
|
ws_connected = False
|
||||||
logging.error("WebSocket connection closed!")
|
logging.error(
|
||||||
|
"WebSocket connection closed (status=%s, message=%s)",
|
||||||
|
close_status_code,
|
||||||
|
close_msg,
|
||||||
|
)
|
||||||
if ON_DISCONNECT_HANDLER is not None:
|
if ON_DISCONNECT_HANDLER is not None:
|
||||||
ON_DISCONNECT_HANDLER()
|
ON_DISCONNECT_HANDLER()
|
||||||
|
|
||||||
@@ -119,9 +141,12 @@ def _do_connection():
|
|||||||
on_open=_ws_connection_open, on_close=_ws_connection_close)
|
on_open=_ws_connection_open, on_close=_ws_connection_close)
|
||||||
while True:
|
while True:
|
||||||
logging.info(F"Connecting to Home Assistant at {ws_url}")
|
logging.info(F"Connecting to Home Assistant at {ws_url}")
|
||||||
ws.close()
|
try:
|
||||||
time.sleep(1)
|
ws.close()
|
||||||
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
|
time.sleep(1)
|
||||||
|
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
|
||||||
|
except Exception:
|
||||||
|
logging.exception("WebSocket connection loop failed")
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
|
|
||||||
@@ -145,8 +170,8 @@ def subscribe_to_events():
|
|||||||
send_message(json.dumps(msg))
|
send_message(json.dumps(msg))
|
||||||
|
|
||||||
def subscribe_to_nspanel_events(nsp_callback):
|
def subscribe_to_nspanel_events(nsp_callback):
|
||||||
global next_id, nspanel_data_callback
|
global next_id, nspanel_event_handler
|
||||||
nspanel_data_callback = nsp_callback
|
nspanel_event_handler = nsp_callback
|
||||||
msg = {
|
msg = {
|
||||||
"id": next_id,
|
"id": next_id,
|
||||||
"type": "subscribe_events",
|
"type": "subscribe_events",
|
||||||
@@ -169,8 +194,10 @@ def send_entity_update(entity_id):
|
|||||||
on_ha_update(entity_id)
|
on_ha_update(entity_id)
|
||||||
|
|
||||||
def nspanel_data_callback(device_id, msg):
|
def nspanel_data_callback(device_id, msg):
|
||||||
global nspanel_data_callback
|
if nspanel_event_handler is None:
|
||||||
nspanel_data_callback(device_id, msg)
|
logging.debug("NsPanel callback invoked before handler was registered")
|
||||||
|
return
|
||||||
|
nspanel_event_handler(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
|
||||||
@@ -187,8 +214,11 @@ def call_service(entity_name: str, domain: str, service: str, service_data: dict
|
|||||||
}
|
}
|
||||||
send_message(json.dumps(msg))
|
send_message(json.dumps(msg))
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logging.exception("Failed to call Home Assisatant service.")
|
logging.exception(
|
||||||
|
"Failed to call Home Assistant service: %s.%s for %s",
|
||||||
|
domain, service, entity_name
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def send_msg_to_panel(service: str, service_data: dict) -> bool:
|
def send_msg_to_panel(service: str, service_data: dict) -> bool:
|
||||||
@@ -203,8 +233,8 @@ def send_msg_to_panel(service: str, service_data: dict) -> bool:
|
|||||||
}
|
}
|
||||||
send_message(json.dumps(msg))
|
send_message(json.dumps(msg))
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logging.exception("Failed to call Home Assisatant service.")
|
logging.exception("Failed to call Home Assistant panel service: %s", service)
|
||||||
return False
|
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:
|
||||||
@@ -241,13 +271,13 @@ def execute_script(entity_name: str, domain: str, service: str, service_data: di
|
|||||||
else:
|
else:
|
||||||
return response_buffer[call_id]["response"]
|
return response_buffer[call_id]["response"]
|
||||||
raise TimeoutError("Did not recive respose in time to HA script call")
|
raise TimeoutError("Did not recive respose in time to HA script call")
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logging.exception("Failed to call Home Assisatant script.")
|
logging.exception("Failed to call Home Assistant script: %s.%s", domain, service)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def cache_template(template):
|
def cache_template(template):
|
||||||
if not template:
|
if not template:
|
||||||
raise Exception("Invalid template")
|
raise ValueError("Invalid template")
|
||||||
global next_id, response_buffer
|
global next_id, response_buffer
|
||||||
try:
|
try:
|
||||||
call_id = next_id
|
call_id = next_id
|
||||||
@@ -259,7 +289,7 @@ def cache_template(template):
|
|||||||
}
|
}
|
||||||
send_message(json.dumps(msg))
|
send_message(json.dumps(msg))
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logging.exception("Failed to render template.")
|
logging.exception("Failed to render template.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -301,5 +331,10 @@ def is_existent(entity_id: str):
|
|||||||
|
|
||||||
def send_message(message):
|
def send_message(message):
|
||||||
global ws, next_id
|
global ws, next_id
|
||||||
next_id += 1
|
try:
|
||||||
ws.send(message)
|
next_id += 1
|
||||||
|
ws.send(message)
|
||||||
|
except NameError:
|
||||||
|
logging.error("WebSocket client is not initialized; dropping outgoing message")
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Failed sending websocket message to Home Assistant")
|
||||||
|
|||||||
@@ -25,53 +25,76 @@ last_settings_file_mtime = 0
|
|||||||
mqtt_connect_time = 0
|
mqtt_connect_time = 0
|
||||||
has_sent_reload_command = False
|
has_sent_reload_command = False
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
log_level_name = os.getenv("LOGLEVEL", "DEBUG").upper()
|
||||||
|
log_level = getattr(logging, log_level_name, None)
|
||||||
|
invalid_log_level = not isinstance(log_level, int)
|
||||||
|
if invalid_log_level:
|
||||||
|
log_level = logging.DEBUG
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=log_level,
|
||||||
|
format="%(asctime)s %(levelname)s [%(threadName)s] %(name)s: %(message)s",
|
||||||
|
)
|
||||||
|
if invalid_log_level:
|
||||||
|
logging.warning("Invalid loglevel '%s', defaulting to DEBUG", log_level_name)
|
||||||
|
|
||||||
def on_ha_update(entity_id):
|
def on_ha_update(entity_id):
|
||||||
global panel_in_queues
|
global panel_in_queues
|
||||||
# send HA updates to all panels
|
# send HA updates to all panels
|
||||||
for queue in panel_in_queues.values():
|
for queue in panel_in_queues.values():
|
||||||
queue.put(("HA:", entity_id))
|
try:
|
||||||
|
queue.put(("HA:", entity_id))
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Failed to enqueue HA update for entity '%s'", entity_id)
|
||||||
|
|
||||||
def on_ha_panel_event(device_id, msg):
|
def on_ha_panel_event(device_id, msg):
|
||||||
global panel_in_queues
|
global panel_in_queues
|
||||||
|
|
||||||
if device_id in panel_in_queues.keys():
|
if device_id in panel_in_queues.keys():
|
||||||
queue = panel_in_queues[device_id]
|
queue = panel_in_queues[device_id]
|
||||||
queue.put(("MQTT:", msg))
|
try:
|
||||||
|
queue.put(("MQTT:", msg))
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Failed to enqueue panel event for device '%s'", device_id)
|
||||||
|
|
||||||
def process_output_to_panel():
|
def process_output_to_panel():
|
||||||
while True:
|
while True:
|
||||||
msg = panel_out_queue.get()
|
try:
|
||||||
|
msg = panel_out_queue.get()
|
||||||
#client.publish(msg[0], msg[1])
|
service = msg[0] + "_nspanelui_api_call"
|
||||||
#apis.ha_api.call_service(service="esphome/" + self._api_panel_name + "_nspanelui_api_call", command=2, data=msg)
|
service_data = {
|
||||||
service = msg[0] + "_nspanelui_api_call"
|
"data": msg[1],
|
||||||
service_data = {
|
"command": 2
|
||||||
"data": msg[1],
|
|
||||||
"command":2
|
|
||||||
}
|
}
|
||||||
libs.home_assistant.send_msg_to_panel(
|
libs.home_assistant.send_msg_to_panel(
|
||||||
service = service,
|
service=service,
|
||||||
service_data = service_data
|
service_data=service_data
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Failed to process outgoing panel message")
|
||||||
|
|
||||||
|
|
||||||
def connect():
|
def connect():
|
||||||
global settings, panel_out_queue
|
global settings, panel_out_queue
|
||||||
|
ha_is_configured = settings["home_assistant_address"] != "" and settings["home_assistant_token"] != ""
|
||||||
if "mqtt_server" in settings and not "use_ha_api" in settings:
|
if "mqtt_server" in settings and not "use_ha_api" in settings:
|
||||||
MqttManager(settings, panel_out_queue, panel_in_queues)
|
MqttManager(settings, panel_out_queue, panel_in_queues)
|
||||||
else:
|
else:
|
||||||
logging.info("MQTT values not configured, will not connect.")
|
logging.info("MQTT values not configured, will not connect.")
|
||||||
|
|
||||||
# MQTT Connected, start APIs if configured
|
# MQTT Connected, start APIs if configured
|
||||||
if settings["home_assistant_address"] != "" and settings["home_assistant_token"] != "":
|
if ha_is_configured:
|
||||||
libs.home_assistant.init(settings, on_ha_update)
|
libs.home_assistant.init(settings, on_ha_update)
|
||||||
libs.home_assistant.connect()
|
libs.home_assistant.connect()
|
||||||
else:
|
else:
|
||||||
logging.info("Home Assistant values not configured, will not connect.")
|
logging.info("Home Assistant values not configured, will not connect.")
|
||||||
|
return
|
||||||
|
|
||||||
|
wait_seconds = 0
|
||||||
while not libs.home_assistant.ws_connected:
|
while not libs.home_assistant.ws_connected:
|
||||||
|
wait_seconds += 1
|
||||||
|
if wait_seconds % 10 == 0:
|
||||||
|
logging.info("Waiting for Home Assistant websocket connection... (%ss)", wait_seconds)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
if settings.get("use_ha_api"):
|
if settings.get("use_ha_api"):
|
||||||
libs.home_assistant.subscribe_to_nspanel_events(on_ha_panel_event)
|
libs.home_assistant.subscribe_to_nspanel_events(on_ha_panel_event)
|
||||||
@@ -97,13 +120,20 @@ def setup_panels():
|
|||||||
panel_thread.start()
|
panel_thread.start()
|
||||||
|
|
||||||
def panel_thread_target(queue_in, name, settings_panel, queue_out):
|
def panel_thread_target(queue_in, name, settings_panel, queue_out):
|
||||||
panel = LovelaceUIPanel(name, settings_panel, queue_out)
|
try:
|
||||||
|
panel = LovelaceUIPanel(name, settings_panel, queue_out)
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Failed to initialize panel thread for '%s'", name)
|
||||||
|
return
|
||||||
while True:
|
while True:
|
||||||
msg = queue_in.get()
|
try:
|
||||||
if msg[0] == "MQTT:":
|
msg = queue_in.get()
|
||||||
panel.customrecv_event_callback(msg[1])
|
if msg[0] == "MQTT:":
|
||||||
elif msg[0] == "HA:":
|
panel.customrecv_event_callback(msg[1])
|
||||||
panel.ha_event_callback(msg[1])
|
elif msg[0] == "HA:":
|
||||||
|
panel.ha_event_callback(msg[1])
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Panel thread '%s' failed while handling queue message", name)
|
||||||
|
|
||||||
def get_config_file():
|
def get_config_file():
|
||||||
CONFIG_FILE = os.getenv('CONFIG_FILE')
|
CONFIG_FILE = os.getenv('CONFIG_FILE')
|
||||||
@@ -117,18 +147,27 @@ def get_config(file):
|
|||||||
try:
|
try:
|
||||||
with open(file, 'r', encoding="utf8") as file:
|
with open(file, 'r', encoding="utf8") as file:
|
||||||
settings = yaml.safe_load(file)
|
settings = yaml.safe_load(file)
|
||||||
|
except FileNotFoundError:
|
||||||
|
logging.error("Config file not found: %s", file)
|
||||||
|
return False
|
||||||
|
except OSError:
|
||||||
|
logging.exception("Failed reading config file: %s", file)
|
||||||
|
return False
|
||||||
except yaml.YAMLError as exc:
|
except yaml.YAMLError as exc:
|
||||||
print ("Error while parsing YAML file:")
|
logging.error("Error while parsing YAML file: %s", file)
|
||||||
if hasattr(exc, 'problem_mark'):
|
if hasattr(exc, 'problem_mark'):
|
||||||
if exc.context != None:
|
if exc.context != None:
|
||||||
print (' parser says\n' + str(exc.problem_mark) + '\n ' +
|
logging.error(
|
||||||
str(exc.problem) + ' ' + str(exc.context) +
|
"Parser says\n%s\n%s %s\nPlease correct data and retry.",
|
||||||
'\nPlease correct data and retry.')
|
str(exc.problem_mark), str(exc.problem), str(exc.context)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print (' parser says\n' + str(exc.problem_mark) + '\n ' +
|
logging.error(
|
||||||
str(exc.problem) + '\nPlease correct data and retry.')
|
"Parser says\n%s\n%s\nPlease correct data and retry.",
|
||||||
|
str(exc.problem_mark), str(exc.problem)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print ("Something went wrong while parsing yaml file")
|
logging.exception("Something went wrong while parsing yaml file")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not settings.get("mqtt_username"):
|
if not settings.get("mqtt_username"):
|
||||||
@@ -172,10 +211,14 @@ def config_watch():
|
|||||||
project_files.append(get_config_file())
|
project_files.append(get_config_file())
|
||||||
handler = ConfigChangeEventHandler(project_files)
|
handler = ConfigChangeEventHandler(project_files)
|
||||||
observer = Observer()
|
observer = Observer()
|
||||||
observer.schedule(handler, path=os.path.dirname(get_config_file()), recursive=True)
|
watch_path = os.path.dirname(get_config_file()) or "."
|
||||||
|
observer.schedule(handler, path=watch_path, recursive=True)
|
||||||
observer.start()
|
observer.start()
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
try:
|
||||||
|
time.sleep(1)
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Config watch loop failed")
|
||||||
|
|
||||||
def signal_handler(signum, frame):
|
def signal_handler(signum, frame):
|
||||||
logging.info(f"Received signal {signum}. Initiating restart...")
|
logging.info(f"Received signal {signum}. Initiating restart...")
|
||||||
@@ -194,4 +237,5 @@ if __name__ == '__main__':
|
|||||||
time.sleep(100)
|
time.sleep(100)
|
||||||
else:
|
else:
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
time.sleep(100)
|
time.sleep(100)
|
||||||
@@ -19,7 +19,6 @@ class MqttManager:
|
|||||||
self.client.username_pw_set(
|
self.client.username_pw_set(
|
||||||
settings["mqtt_username"], settings["mqtt_password"])
|
settings["mqtt_username"], settings["mqtt_password"])
|
||||||
# Wait for connection
|
# Wait for connection
|
||||||
connection_return_code = 0
|
|
||||||
mqtt_server = settings["mqtt_server"]
|
mqtt_server = settings["mqtt_server"]
|
||||||
mqtt_port = int(settings["mqtt_port"])
|
mqtt_port = int(settings["mqtt_port"])
|
||||||
logging.info("Connecting to %s:%i as %s",
|
logging.info("Connecting to %s:%i as %s",
|
||||||
@@ -28,9 +27,12 @@ class MqttManager:
|
|||||||
try:
|
try:
|
||||||
self.client.connect(mqtt_server, mqtt_port, 5)
|
self.client.connect(mqtt_server, mqtt_port, 5)
|
||||||
break # Connection call did not raise exception, connection is sucessfull
|
break # Connection call did not raise exception, connection is sucessfull
|
||||||
except: # pylint: disable=bare-except
|
except Exception: # pylint: disable=broad-exception-caught
|
||||||
logging.exception(
|
logging.exception(
|
||||||
"Failed to connect to MQTT %s:%i. Will try again in 10 seconds. Code: %s", mqtt_server, mqtt_port, connection_return_code)
|
"Failed to connect to MQTT %s:%i. Will try again in 10 seconds.",
|
||||||
|
mqtt_server,
|
||||||
|
mqtt_port,
|
||||||
|
)
|
||||||
time.sleep(10.)
|
time.sleep(10.)
|
||||||
self.client.loop_start()
|
self.client.loop_start()
|
||||||
process_thread = threading.Thread(target=self.process_in_queue, args=(self.client, self.msg_in_queue))
|
process_thread = threading.Thread(target=self.process_in_queue, args=(self.client, self.msg_in_queue))
|
||||||
@@ -38,31 +40,52 @@ class MqttManager:
|
|||||||
process_thread.start()
|
process_thread.start()
|
||||||
|
|
||||||
def on_mqtt_connect(self, client, userdata, flags, rc):
|
def on_mqtt_connect(self, client, userdata, flags, rc):
|
||||||
|
if rc != 0:
|
||||||
|
logging.error("MQTT connection failed with return code: %s", rc)
|
||||||
|
return
|
||||||
logging.info("Connected to MQTT Server")
|
logging.info("Connected to MQTT Server")
|
||||||
# subscribe to panelRecvTopic of each panel
|
# subscribe to panelRecvTopic of each panel
|
||||||
for settings_panel in self.settings["nspanels"].values():
|
for settings_panel in self.settings["nspanels"].values():
|
||||||
client.subscribe(settings_panel["panelRecvTopic"])
|
topic = settings_panel["panelRecvTopic"]
|
||||||
|
result, _ = client.subscribe(topic)
|
||||||
|
if result == mqtt.MQTT_ERR_SUCCESS:
|
||||||
|
logging.debug("Subscribed to panel topic: %s", topic)
|
||||||
|
else:
|
||||||
|
logging.error("Failed to subscribe to panel topic '%s' (result=%s)", topic, result)
|
||||||
|
|
||||||
def on_mqtt_message(self, client, userdata, msg):
|
def on_mqtt_message(self, client, userdata, msg):
|
||||||
try:
|
try:
|
||||||
if msg.payload.decode() == "":
|
payload_text = msg.payload.decode('utf-8')
|
||||||
|
if payload_text == "":
|
||||||
|
logging.debug("Ignoring empty MQTT payload on topic: %s", msg.topic)
|
||||||
return
|
return
|
||||||
if msg.topic in self.msg_out_queue_list.keys():
|
if msg.topic in self.msg_out_queue_list.keys():
|
||||||
data = json.loads(msg.payload.decode('utf-8'))
|
data = json.loads(payload_text)
|
||||||
if "CustomRecv" in data:
|
if "CustomRecv" in data:
|
||||||
queue = self.msg_out_queue_list[msg.topic]
|
queue = self.msg_out_queue_list[msg.topic]
|
||||||
queue.put(("MQTT:", data["CustomRecv"]))
|
queue.put(("MQTT:", data["CustomRecv"]))
|
||||||
|
else:
|
||||||
|
logging.debug("JSON payload on topic '%s' has no 'CustomRecv' key", msg.topic)
|
||||||
else:
|
else:
|
||||||
logging.debug("Received unhandled message on topic: %s", msg.topic)
|
logging.debug("Received unhandled message on topic: %s", msg.topic)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
logging.exception("Failed to decode MQTT payload as UTF-8 on topic: %s", msg.topic)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logging.exception("Failed to parse MQTT JSON payload on topic: %s", msg.topic)
|
||||||
except Exception: # pylint: disable=broad-exception-caught
|
except Exception: # pylint: disable=broad-exception-caught
|
||||||
logging.exception("Something went wrong during processing of message:")
|
logging.exception("Unexpected error while processing MQTT message on topic: %s", msg.topic)
|
||||||
try:
|
try:
|
||||||
logging.error(msg.payload.decode('utf-8'))
|
logging.error(msg.payload.decode('utf-8'))
|
||||||
except: # pylint: disable=bare-except
|
except Exception: # pylint: disable=broad-exception-caught
|
||||||
logging.error(
|
logging.error(
|
||||||
"Something went wrong when processing the exception message, couldn't decode payload to utf-8.")
|
"Something went wrong when processing the exception message, couldn't decode payload to utf-8.")
|
||||||
|
|
||||||
def process_in_queue(self, client, msg_in_queue):
|
def process_in_queue(self, client, msg_in_queue):
|
||||||
while True:
|
while True:
|
||||||
msg = msg_in_queue.get()
|
try:
|
||||||
client.publish(msg[0], msg[1])
|
msg = msg_in_queue.get()
|
||||||
|
result = client.publish(msg[0], msg[1])
|
||||||
|
if result.rc != mqtt.MQTT_ERR_SUCCESS:
|
||||||
|
logging.error("Failed publishing message to topic '%s' (rc=%s)", msg[0], result.rc)
|
||||||
|
except Exception: # pylint: disable=broad-exception-caught
|
||||||
|
logging.exception("Failed processing outgoing MQTT queue message")
|
||||||
|
|||||||
@@ -105,7 +105,10 @@ class LovelaceUIPanel:
|
|||||||
|
|
||||||
def schedule_thread_target(self):
|
def schedule_thread_target(self):
|
||||||
while True:
|
while True:
|
||||||
self.schedule.exec_jobs()
|
try:
|
||||||
|
self.schedule.exec_jobs()
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Scheduler execution failed for panel '%s'", self.name)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def update_time(self):
|
def update_time(self):
|
||||||
@@ -203,6 +206,9 @@ class LovelaceUIPanel:
|
|||||||
def customrecv_event_callback(self, msg):
|
def customrecv_event_callback(self, msg):
|
||||||
logging.debug("Recv Message from NsPanel (%s): %s", self.name, msg)
|
logging.debug("Recv Message from NsPanel (%s): %s", self.name, msg)
|
||||||
msg = msg.split(",")
|
msg = msg.split(",")
|
||||||
|
if len(msg) < 2:
|
||||||
|
logging.error("Malformed panel message on '%s': %s", self.name, msg)
|
||||||
|
return
|
||||||
# run action based on received command
|
# run action based on received command
|
||||||
if msg[0] == "event":
|
if msg[0] == "event":
|
||||||
if msg[1] == "startup":
|
if msg[1] == "startup":
|
||||||
@@ -227,9 +233,20 @@ class LovelaceUIPanel:
|
|||||||
if msg[1] == "renderCurrentPage":
|
if msg[1] == "renderCurrentPage":
|
||||||
self.render_current_page(requested=True)
|
self.render_current_page(requested=True)
|
||||||
if msg[1] == "buttonPress2":
|
if msg[1] == "buttonPress2":
|
||||||
|
if len(msg) < 4:
|
||||||
|
logging.error("Malformed buttonPress2 payload on '%s': %s", self.name, msg)
|
||||||
|
return
|
||||||
entity_id = msg[2]
|
entity_id = msg[2]
|
||||||
|
if entity_id == "":
|
||||||
|
return
|
||||||
btype = msg[3]
|
btype = msg[3]
|
||||||
value = msg[4] if len(msg) > 4 else None
|
value = msg[4] if len(msg) > 4 else None
|
||||||
|
entity_config = {}
|
||||||
|
action_context = {
|
||||||
|
"panel": self.name,
|
||||||
|
"btype": btype,
|
||||||
|
"value": value,
|
||||||
|
}
|
||||||
if btype == "bExit":
|
if btype == "bExit":
|
||||||
if entity_id in ["screensaver", "screensaver2"] 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
|
||||||
@@ -271,25 +288,47 @@ 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, entity_config=entity_config)
|
ha_control.handle_buttons(
|
||||||
|
entity_id,
|
||||||
|
btype,
|
||||||
|
value,
|
||||||
|
entity_config=entity_config,
|
||||||
|
action_context=action_context,
|
||||||
|
)
|
||||||
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):
|
||||||
self.privious_cards.append(self.current_card)
|
self.privious_cards.append(self.current_card)
|
||||||
self.current_card = self.searchCard(card_iid)
|
self.current_card = self.searchCard(card_iid)
|
||||||
self.render_current_page(switchPages=True)
|
self.render_current_page(switchPages=True)
|
||||||
|
case 'mode-light':
|
||||||
|
ha_control.handle_buttons(
|
||||||
|
entity_id,
|
||||||
|
btype,
|
||||||
|
value,
|
||||||
|
entity_config=entity_config,
|
||||||
|
action_context=action_context,
|
||||||
|
)
|
||||||
case _:
|
case _:
|
||||||
ha_control.handle_buttons(entity_id, btype, value)
|
ha_control.handle_buttons(
|
||||||
|
entity_id,
|
||||||
|
btype,
|
||||||
|
value,
|
||||||
|
action_context=action_context,
|
||||||
|
)
|
||||||
|
|
||||||
if msg[1] == "pageOpenDetail":
|
if msg[1] == "pageOpenDetail":
|
||||||
|
if len(msg) < 4:
|
||||||
|
logging.error("Malformed pageOpenDetail payload on '%s': %s", self.name, msg)
|
||||||
|
return
|
||||||
entity_id = msg[3]
|
entity_id = msg[3]
|
||||||
|
effectList = None
|
||||||
# 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]
|
||||||
for e in self.current_card.entities:
|
for e in self.current_card.entities:
|
||||||
if e.iid == iid:
|
if e.iid == iid:
|
||||||
entity_id = e.entity_id
|
entity_id = e.entity_id
|
||||||
effectList = None
|
|
||||||
if entity_id.startswith("light"):
|
if entity_id.startswith("light"):
|
||||||
effectList = e.config.get("effectList")
|
effectList = e.config.get("effectList")
|
||||||
if msg[2] == "popupInSel": #entity_id.split(".")[0] in ['input_select', 'media_player']:
|
if msg[2] == "popupInSel": #entity_id.split(".")[0] in ['input_select', 'media_player']:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
paho-mqtt
|
paho-mqtt==1.6.1
|
||||||
pyyaml
|
pyyaml
|
||||||
websockets
|
websockets
|
||||||
websocket-client
|
websocket-client
|
||||||
|
|||||||
Reference in New Issue
Block a user