Compare commits

..

2 Commits

Author SHA1 Message Date
slajob
2cbc802864 Merge 127671047f into a992dc56c5 2024-01-19 13:17:52 +01:00
slajob
127671047f missing friday entity from example photo 2024-01-10 18:22:05 +01:00
74 changed files with 10082 additions and 23703 deletions

View File

@@ -58,5 +58,5 @@ _If applicable, add screenshots/pictures to help explain your problem._
_Add any other context about the problem here._
_Please note here in case you are using ioBroker_
### PANEL / FIRMWARE VERSION
### PANEL / FIRMWARE VERION
_Please add the Panel/Firmware Version you are using (EU, US-L or US-P)_

View File

@@ -24,5 +24,5 @@ _A clear and concise description of what the feature should do._
### ADDITIONAL CONTEXT
_Add any other context about the problem here._
### PANEL / FIRMWARE VERSION
### PANEL / FIRMWARE VERION
_Please add the Panel/Firmware Version you are using (EU, US-L or US-P)_

View File

@@ -21,7 +21,7 @@ jobs:
changed: ${{ steps.changed_addons.outputs.changed }}
steps:
- name: Check out the repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Get changed files
id: changed_files
@@ -68,7 +68,7 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Get information
id: info
@@ -92,7 +92,7 @@ jobs:
- name: Login to GitHub Container Registry
if: env.BUILD_ARGS != '--test'
uses: docker/login-action@v3.7.0
uses: docker/login-action@v3.0.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -100,7 +100,7 @@ jobs:
- name: Build ${{ matrix.addon }} add-on
if: steps.check.outputs.build_arch == 'true'
uses: home-assistant/builder@2025.09.0
uses: home-assistant/builder@2024.01.0
with:
args: |
${{ env.BUILD_ARGS }} \

View File

@@ -43,11 +43,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# 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).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v4
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -72,4 +72,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@v3

28
.github/workflows/docs-dev.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
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

View File

@@ -6,51 +6,24 @@ on:
branches:
- main
paths:
- docs/**
- docs-standalone/**
- docs/*
- .github/workflows/docs-release.yml
- mkdocs.yml
- docs-standalone/mkdocs.yml
- HMI/README.md
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v6
- uses: actions/setup-python@v5
with:
python-version: 3.x
- run: pip install zensical
- run: pip install mkdocs-material mkdocs-video markdown-include mike
- run: cp HMI/README.md docs/hmi-serial-protocol.md
- run: zensical build --config-file mkdocs.yml
- run: mv site _site_main
- run: zensical build --config-file docs-standalone/mkdocs.yml
- 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
- run: git config --global user.name Docs deploy
- run: git config --global user.email docs@dummy.bot.com
- run: mike set-default stable
- run: mike deploy --push --update-aliases stable

View File

@@ -11,7 +11,7 @@ jobs:
name: HACS Action
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v6"
- uses: "actions/checkout@v4"
- name: HACS Action
uses: "hacs/action@main"
with:

View File

@@ -18,7 +18,7 @@ jobs:
gen-ioBroker-localization:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
@@ -28,7 +28,7 @@ jobs:
python HMI/code_gen/localization/iobroker.py
mv ioBroker_NSPanel_locales.json ioBroker/ioBroker_NSPanel_locales.json
- uses: stefanzweifel/git-auto-commit-action@v7
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Update iobroker localization file
#file_pattern: "**.txt"

View File

@@ -18,7 +18,7 @@ jobs:
addons: ${{ steps.addons.outputs.addons_list }}
steps:
- name: ⤵️ Check out code from GitHub
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: 🔍 Find add-on directories
id: addons
@@ -33,9 +33,9 @@ jobs:
path: ${{ fromJson(needs.find.outputs.addons) }}
steps:
- name: ⤵️ Check out code from GitHub
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: 🚀 Run Home Assistant Add-on Lint
uses: frenck/action-addon-linter@v2.21
uses: frenck/action-addon-linter@v2.15
with:
path: "./${{ matrix.path }}"

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
@@ -80,7 +80,7 @@ jobs:
find -name "**.txt"
rm Nextion2Text.py* ignore-id.py out.txt
- uses: stefanzweifel/git-auto-commit-action@v7
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: ${{ steps.last-commit-message.outputs.msg }} (add nextion2text)
#file_pattern: "**.txt"

29
.gitignore vendored
View File

@@ -6,31 +6,4 @@ HMI/Nextion2Text.py
panels.yaml
# don't add Webstorm project stuff
.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
.idea

View File

@@ -1,5 +1,17 @@
--- HMI/n2t-out
+++ HMI/US/landscape/n2t-out
├── file list
│ @@ -1,10 +1,9 @@
│ Program.s.txt
│ cardAlarm.txt
│ -cardBurnRec.txt
│ cardChart.txt
│ cardEntities.txt
│ cardGrid.txt
│ cardGrid2.txt
│ cardLChart.txt
│ cardMedia.txt
│ cardPower.txt
│ --- HMI/n2t-out/Program.s.txt
├── +++ HMI/US/landscape/n2t-out/Program.s.txt
│ @@ -13,10 +13,10 @@
@@ -102,49 +114,6 @@
│ crcputs sys0,2
│ crcputs tSend.txt,0
│ //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/US/landscape/n2t-out/popupNotify.txt
│ @@ -439,18 +439,14 @@
│ {
│ page cardPower
│ }
│ if(tId.txt=="cardChart")
│ {
│ page cardChart
│ }
│ - if(tId.txt=="cardLChart")
│ - {
│ - page cardLChart
│ - }
│ }
│ // end of user code
│ udelete payloadLength-1
│ bufferPos=0
│ }
│ }
│ }
│ --- HMI/n2t-out/screensaver.txt
├── +++ HMI/US/landscape/n2t-out/screensaver.txt
│ @@ -19,15 +19,14 @@
@@ -252,7 +221,7 @@
│ vis p0,0
│ vis tNotifyHead,0
│ vis tNotifyText,0
│ @@ -41,52 +40,19 @@
│ @@ -41,17 +40,14 @@
│ }
│ tDate.txt=pageIcons.vaDate.txt
│ dim=dimValue
@@ -263,80 +232,14 @@
│ - Postinitialize Event
│ - click m0,1
│ -
│ -Variable (string) entn1
│ - Attributes
│ - Scope : local
│ - Text :
│ - Max. Text Size: 14
│ -
│ -Variable (string) entn2
│ - Attributes
│ - Scope : local
│ - Text :
│ - Max. Text Size: 14
│ -
│ -Variable (string) entn3
│ - Attributes
│ - Scope : local
│ - Text :
│ - Max. Text Size: 14
│ -
│ -Variable (string) entn4
│ - Attributes
│ - Scope : local
│ - Text :
│ - Max. Text Size: 14
│ -
│ -Variable (string) entn5
│ - Attributes
│ - Scope : local
│ - Text :
│ - Max. Text Size: 14
│ -
│ Variable (string) strCommand
│ Attributes
│ Scope : local
│ Text :
- Max. Text Size: 1935
│ + Max. Text Size: 1979
Max. Text Size: 1979
│ Variable (string) strTmp
Attributes
│ Scope : local
│ Text :
│ Max. Text Size: 2
│ @@ -96,27 +62,27 @@
│ Text :
│ Max. Text Size: 25
│ Variable (string) tInstruction
│ Attributes
│ Scope : local
│ Text :
│ - Max. Text Size: 15
│ + Max. Text Size: 50
│ Variable (string) tSend
│ Attributes
│ Scope : local
│ Text :
│ Max. Text Size: 50
│ Variable (string) tTmp
│ Attributes
│ Scope : local
│ Text :
│ - Max. Text Size: 30
│ + Max. Text Size: 50
│ Variable (int32) vaTap
│ Attributes
│ Scope: local
│ Value: 0
│ Variable (int32) xc1
│ @@ -370,165 +336,50 @@
@@ -376,34 +372,14 @@
│ Scope : local
│ Dragging : 0
│ Send Component ID : disabled
@@ -344,137 +247,6 @@
│ Text :
│ Max. Text Size : 4
│ - Events
│ - Touch Press Event
│ - tSend.txt="event,buttonPress2,"+entn1.txt+",button"
│ - if(entn1.txt=="")
│ - {
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
│ - }
│ - //send calc crc
│ - btlen tSend.txt,sys0
│ - crcrest 1,0xffff // reset CRC
│ - crcputh 55 bb
│ - crcputs sys0,2
│ - crcputs tSend.txt,0
│ - //send cmd
│ - printh 55 bb
│ - prints sys0,2
│ - prints tSend.txt,0
│ - prints crcval,2
│ -
│ Text f2Icon
│ Attributes
│ Scope : local
│ Dragging : 0
│ Send Component ID : disabled
│ Associated Keyboard: none
│ Text :
│ Max. Text Size : 4
│ - Events
│ - Touch Press Event
│ - tSend.txt="event,buttonPress2,"+entn2.txt+",button"
│ - if(entn2.txt=="")
│ - {
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
│ - }
│ - //send calc crc
│ - btlen tSend.txt,sys0
│ - crcrest 1,0xffff // reset CRC
│ - crcputh 55 bb
│ - crcputs sys0,2
│ - crcputs tSend.txt,0
│ - //send cmd
│ - printh 55 bb
│ - prints sys0,2
│ - prints tSend.txt,0
│ - prints crcval,2
│ -
│ Text f3Icon
│ Attributes
│ Scope : local
│ Dragging : 0
│ Send Component ID : disabled
│ Associated Keyboard: none
│ Text :
│ Max. Text Size : 4
│ - Events
│ - Touch Press Event
│ - tSend.txt="event,buttonPress2,"+entn3.txt+",button"
│ - if(entn3.txt=="")
│ - {
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
│ - }
│ - //send calc crc
│ - btlen tSend.txt,sys0
│ - crcrest 1,0xffff // reset CRC
│ - crcputh 55 bb
│ - crcputs sys0,2
│ - crcputs tSend.txt,0
│ - //send cmd
│ - printh 55 bb
│ - prints sys0,2
│ - prints tSend.txt,0
│ - prints crcval,2
│ -
│ Text f4Icon
│ Attributes
│ Scope : local
│ Dragging : 0
│ Send Component ID : disabled
│ Associated Keyboard: none
│ Text :
│ Max. Text Size : 4
│ - Events
│ - Touch Press Event
│ - tSend.txt="event,buttonPress2,"+entn4.txt+",button"
│ - if(entn4.txt=="")
│ - {
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
│ - }
│ - //send calc crc
│ - btlen tSend.txt,sys0
│ - crcrest 1,0xffff // reset CRC
│ - crcputh 55 bb
│ - crcputs sys0,2
│ - crcputs tSend.txt,0
│ - //send cmd
│ - printh 55 bb
│ - prints sys0,2
│ - prints tSend.txt,0
│ - prints crcval,2
│ -
│ Text f5Icon
│ Attributes
│ Scope : local
│ Dragging : 0
│ Send Component ID : disabled
│ Associated Keyboard: none
│ Text :
│ Max. Text Size : 4
│ - Events
│ - Touch Press Event
│ - tSend.txt="event,buttonPress2,"+entn5.txt+",button"
│ - if(entn5.txt=="")
│ - {
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
│ - }
│ - //send calc crc
│ - btlen tSend.txt,sys0
│ - crcrest 1,0xffff // reset CRC
│ - crcputh 55 bb
│ - crcputs sys0,2
│ - crcputs tSend.txt,0
│ - //send cmd
│ - printh 55 bb
│ - prints sys0,2
│ - prints tSend.txt,0
│ - prints crcval,2
│ -
│ -Text m0
│ - Attributes
│ - Scope : local
@@ -502,7 +274,7 @@
│ Send Component ID : disabled
│ Associated Keyboard: none
│ Text : PM
│ @@ -710,15 +561,14 @@
│ @@ -585,15 +561,14 @@
│ if(tTmp.txt!="")
│ {
│ covx tTmp.txt,defaultFontColor,0,0
@@ -518,48 +290,3 @@
│ if(tAMPM.txt=="")
│ {
│ vis tAMPM,0
│ @@ -839,44 +689,34 @@
│ //e6Val
│ spstr strCommand.txt,e6Val.txt,"~",60
│ //f1Icon
│ spstr strCommand.txt,f1Icon.txt,"~",63
│ //f1Icon Color
│ spstr strCommand.txt,tTmp.txt,"~",64
│ covx tTmp.txt,f1Icon.pco,0,0
│ - //f1Icon intNameEntity
│ - spstr strCommand.txt,entn1.txt,"~",62
│ //f2Icon
│ spstr strCommand.txt,f2Icon.txt,"~",69
│ //f2Icon Color
│ spstr strCommand.txt,tTmp.txt,"~",70
│ covx tTmp.txt,f2Icon.pco,0,0
│ - //f2Icon intNameEntity
│ - spstr strCommand.txt,entn2.txt,"~",68
│ //f3Icon
│ spstr strCommand.txt,f3Icon.txt,"~",75
│ //f3Icon Color
│ spstr strCommand.txt,tTmp.txt,"~",76
│ covx tTmp.txt,f3Icon.pco,0,0
│ - //f3Icon intNameEntity
│ - spstr strCommand.txt,entn3.txt,"~",74
│ //f4Icon
│ spstr strCommand.txt,f4Icon.txt,"~",81
│ //f4Icon Color
│ spstr strCommand.txt,tTmp.txt,"~",82
│ covx tTmp.txt,f4Icon.pco,0,0
│ - //f4Icon intNameEntity
│ - spstr strCommand.txt,entn4.txt,"~",80
│ //f5Icon
│ spstr strCommand.txt,f5Icon.txt,"~",87
│ //f5Icon Color
│ spstr strCommand.txt,tTmp.txt,"~",88
│ covx tTmp.txt,f5Icon.pco,0,0
│ - //f5Icon intNameEntity
│ - spstr strCommand.txt,entn5.txt,"~",86
│ }
│ if(tInstruction.txt=="notify")
│ {
│ spstr strCommand.txt,tNotifyHead.txt,"~",1
│ spstr strCommand.txt,tNotifyText.txt,"~",2
│ if(tNotifyHead.txt!=""||tNotifyText.txt!="")
│ {

View File

@@ -1,5 +1,17 @@
--- HMI/n2t-out
+++ HMI/US/portrait/n2t-out
├── file list
│ @@ -1,10 +1,9 @@
│ Program.s.txt
│ cardAlarm.txt
│ -cardBurnRec.txt
│ cardChart.txt
│ cardEntities.txt
│ cardGrid.txt
│ cardGrid2.txt
│ cardLChart.txt
│ cardMedia.txt
│ cardPower.txt
│ --- HMI/n2t-out/Program.s.txt
├── +++ HMI/US/portrait/n2t-out/Program.s.txt
│ @@ -12,11 +12,11 @@
@@ -1985,26 +1997,6 @@
│ Variable (string) entn
│ Attributes
│ 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/US/portrait/n2t-out/popupNotify.txt
│ @@ -348,15 +348,15 @@
@@ -2024,25 +2016,6 @@
│ }
│ if(tInstruction.txt=="exitPopup")
│ {
│ @@ -439,18 +439,14 @@
│ {
│ page cardPower
│ }
│ if(tId.txt=="cardChart")
│ {
│ page cardChart
│ }
│ - if(tId.txt=="cardLChart")
│ - {
│ - page cardLChart
│ - }
│ }
│ // end of user code
│ udelete payloadLength-1
│ bufferPos=0
│ }
│ }
│ }
│ --- HMI/n2t-out/screensaver.txt
├── +++ HMI/US/portrait/n2t-out/screensaver.txt
│ @@ -7,27 +7,14 @@
@@ -2195,7 +2168,7 @@
│ vis p0,0
│ vis tNotifyHead,0
│ vis tNotifyText,0
│ @@ -41,52 +28,19 @@
│ @@ -41,17 +28,14 @@
│ }
│ tDate.txt=pageIcons.vaDate.txt
│ dim=dimValue
@@ -2206,80 +2179,14 @@
│ - Postinitialize Event
│ - click m0,1
│ -
│ -Variable (string) entn1
│ - Attributes
│ - Scope : local
│ - Text :
│ - Max. Text Size: 14
│ -
│ -Variable (string) entn2
│ - Attributes
│ - Scope : local
│ - Text :
│ - Max. Text Size: 14
│ -
│ -Variable (string) entn3
│ - Attributes
│ - Scope : local
│ - Text :
│ - Max. Text Size: 14
│ -
│ -Variable (string) entn4
│ - Attributes
│ - Scope : local
│ - Text :
│ - Max. Text Size: 14
│ -
│ -Variable (string) entn5
│ - Attributes
│ - Scope : local
│ - Text :
│ - Max. Text Size: 14
│ -
│ Variable (string) strCommand
│ Attributes
│ Scope : local
│ Text :
- Max. Text Size: 1935
│ + Max. Text Size: 1979
Max. Text Size: 1979
│ Variable (string) strTmp
Attributes
│ Scope : local
│ Text :
│ Max. Text Size: 2
│ @@ -96,27 +50,27 @@
│ Text :
│ Max. Text Size: 25
│ Variable (string) tInstruction
│ Attributes
│ Scope : local
│ Text :
│ - Max. Text Size: 15
│ + Max. Text Size: 50
│ Variable (string) tSend
│ Attributes
│ Scope : local
│ Text :
│ Max. Text Size: 50
│ Variable (string) tTmp
│ Attributes
│ Scope : local
│ Text :
│ - Max. Text Size: 30
│ + Max. Text Size: 50
│ Variable (int32) vaTap
│ Attributes
│ Scope: local
│ Value: 0
│ Variable (int32) xc1
│ @@ -370,165 +324,50 @@
@@ -376,34 +360,14 @@
│ Scope : local
│ Dragging : 0
│ Send Component ID : disabled
@@ -2287,137 +2194,6 @@
│ Text :
│ Max. Text Size : 4
│ - Events
│ - Touch Press Event
│ - tSend.txt="event,buttonPress2,"+entn1.txt+",button"
│ - if(entn1.txt=="")
│ - {
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
│ - }
│ - //send calc crc
│ - btlen tSend.txt,sys0
│ - crcrest 1,0xffff // reset CRC
│ - crcputh 55 bb
│ - crcputs sys0,2
│ - crcputs tSend.txt,0
│ - //send cmd
│ - printh 55 bb
│ - prints sys0,2
│ - prints tSend.txt,0
│ - prints crcval,2
│ -
│ Text f2Icon
│ Attributes
│ Scope : local
│ Dragging : 0
│ Send Component ID : disabled
│ Associated Keyboard: none
│ Text :
│ Max. Text Size : 4
│ - Events
│ - Touch Press Event
│ - tSend.txt="event,buttonPress2,"+entn2.txt+",button"
│ - if(entn2.txt=="")
│ - {
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
│ - }
│ - //send calc crc
│ - btlen tSend.txt,sys0
│ - crcrest 1,0xffff // reset CRC
│ - crcputh 55 bb
│ - crcputs sys0,2
│ - crcputs tSend.txt,0
│ - //send cmd
│ - printh 55 bb
│ - prints sys0,2
│ - prints tSend.txt,0
│ - prints crcval,2
│ -
│ Text f3Icon
│ Attributes
│ Scope : local
│ Dragging : 0
│ Send Component ID : disabled
│ Associated Keyboard: none
│ Text :
│ Max. Text Size : 4
│ - Events
│ - Touch Press Event
│ - tSend.txt="event,buttonPress2,"+entn3.txt+",button"
│ - if(entn3.txt=="")
│ - {
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
│ - }
│ - //send calc crc
│ - btlen tSend.txt,sys0
│ - crcrest 1,0xffff // reset CRC
│ - crcputh 55 bb
│ - crcputs sys0,2
│ - crcputs tSend.txt,0
│ - //send cmd
│ - printh 55 bb
│ - prints sys0,2
│ - prints tSend.txt,0
│ - prints crcval,2
│ -
│ Text f4Icon
│ Attributes
│ Scope : local
│ Dragging : 0
│ Send Component ID : disabled
│ Associated Keyboard: none
│ Text :
│ Max. Text Size : 4
│ - Events
│ - Touch Press Event
│ - tSend.txt="event,buttonPress2,"+entn4.txt+",button"
│ - if(entn4.txt=="")
│ - {
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
│ - }
│ - //send calc crc
│ - btlen tSend.txt,sys0
│ - crcrest 1,0xffff // reset CRC
│ - crcputh 55 bb
│ - crcputs sys0,2
│ - crcputs tSend.txt,0
│ - //send cmd
│ - printh 55 bb
│ - prints sys0,2
│ - prints tSend.txt,0
│ - prints crcval,2
│ -
│ Text f5Icon
│ Attributes
│ Scope : local
│ Dragging : 0
│ Send Component ID : disabled
│ Associated Keyboard: none
│ Text :
│ Max. Text Size : 4
│ - Events
│ - Touch Press Event
│ - tSend.txt="event,buttonPress2,"+entn5.txt+",button"
│ - if(entn5.txt=="")
│ - {
│ - tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
│ - }
│ - //send calc crc
│ - btlen tSend.txt,sys0
│ - crcrest 1,0xffff // reset CRC
│ - crcputh 55 bb
│ - crcputs sys0,2
│ - crcputs tSend.txt,0
│ - //send cmd
│ - printh 55 bb
│ - prints sys0,2
│ - prints tSend.txt,0
│ - prints crcval,2
│ -
│ -Text m0
│ - Attributes
│ - Scope : local
@@ -2445,7 +2221,7 @@
│ Send Component ID : disabled
│ Associated Keyboard: none
│ Text : PM
│ @@ -710,15 +549,14 @@
│ @@ -585,15 +549,14 @@
│ if(tTmp.txt!="")
│ {
│ covx tTmp.txt,defaultFontColor,0,0
@@ -2461,48 +2237,3 @@
│ if(tAMPM.txt=="")
│ {
│ vis tAMPM,0
│ @@ -839,44 +677,34 @@
│ //e6Val
│ spstr strCommand.txt,e6Val.txt,"~",60
│ //f1Icon
│ spstr strCommand.txt,f1Icon.txt,"~",63
│ //f1Icon Color
│ spstr strCommand.txt,tTmp.txt,"~",64
│ covx tTmp.txt,f1Icon.pco,0,0
│ - //f1Icon intNameEntity
│ - spstr strCommand.txt,entn1.txt,"~",62
│ //f2Icon
│ spstr strCommand.txt,f2Icon.txt,"~",69
│ //f2Icon Color
│ spstr strCommand.txt,tTmp.txt,"~",70
│ covx tTmp.txt,f2Icon.pco,0,0
│ - //f2Icon intNameEntity
│ - spstr strCommand.txt,entn2.txt,"~",68
│ //f3Icon
│ spstr strCommand.txt,f3Icon.txt,"~",75
│ //f3Icon Color
│ spstr strCommand.txt,tTmp.txt,"~",76
│ covx tTmp.txt,f3Icon.pco,0,0
│ - //f3Icon intNameEntity
│ - spstr strCommand.txt,entn3.txt,"~",74
│ //f4Icon
│ spstr strCommand.txt,f4Icon.txt,"~",81
│ //f4Icon Color
│ spstr strCommand.txt,tTmp.txt,"~",82
│ covx tTmp.txt,f4Icon.pco,0,0
│ - //f4Icon intNameEntity
│ - spstr strCommand.txt,entn4.txt,"~",80
│ //f5Icon
│ spstr strCommand.txt,f5Icon.txt,"~",87
│ //f5Icon Color
│ spstr strCommand.txt,tTmp.txt,"~",88
│ covx tTmp.txt,f5Icon.pco,0,0
│ - //f5Icon intNameEntity
│ - spstr strCommand.txt,entn5.txt,"~",86
│ }
│ if(tInstruction.txt=="notify")
│ {
│ spstr strCommand.txt,tNotifyHead.txt,"~",1
│ spstr strCommand.txt,tNotifyText.txt,"~",2
│ if(tNotifyHead.txt!=""||tNotifyText.txt!="")
│ {

View File

@@ -1,4 +1,16 @@
+++ /dev/fd/62 2025-12-26 19:43:25.803166597 +0000
+++ /dev/fd/62 2023-11-27 23:28:52.512823638 +0000
+le list
+ +1,9 @@
+.s.txt
+rm.txt
+nRec.txt
+rt.txt
+ities.txt
+d.txt
+d2.txt
+art.txt
+ia.txt
+er.txt
+I/n2t-out/Program.s.txt
++ HMI/US/portrait/n2t-out/Program.s.txt
+1 +12,11 @@
@@ -1527,26 +1539,6 @@
+e (string) entn
+ributes
+ 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
++ HMI/US/portrait/n2t-out/popupNotify.txt
+15 +348,15 @@
@@ -1566,25 +1558,6 @@
+ }
+ if(tInstruction.txt=="exitPopup")
+ {
+18 +439,14 @@
+ {
+ page cardPower
+ }
+ if(tId.txt=="cardChart")
+ {
+ page cardChart
+ }
+ if(tId.txt=="cardLChart")
+ {
+ page cardLChart
+ }
+ }
+ // end of user code
+ udelete payloadLength-1
+ bufferPos=0
+ }
+ }
+ }
+I/n2t-out/screensaver.txt
++ HMI/US/portrait/n2t-out/screensaver.txt
+ +7,14 @@
@@ -1737,7 +1710,7 @@
+ vis p0,0
+ vis tNotifyHead,0
+ vis tNotifyText,0
+2 +28,19 @@
+7 +28,14 @@
+ }
+ tDate.txt=pageIcons.vaDate.txt
+ dim=dimValue
@@ -1748,80 +1721,14 @@
+ Postinitialize Event
+ click m0,1
+
+e (string) entn1
+ributes
+ Scope : local
+ Text :
+ Max. Text Size: 14
+
+e (string) entn2
+ributes
+ Scope : local
+ Text :
+ Max. Text Size: 14
+
+e (string) entn3
+ributes
+ Scope : local
+ Text :
+ Max. Text Size: 14
+
+e (string) entn4
+ributes
+ Scope : local
+ Text :
+ Max. Text Size: 14
+
+e (string) entn5
+ributes
+ Scope : local
+ Text :
+ Max. Text Size: 14
+
+e (string) strCommand
+ributes
+ Scope : local
+ Text :
+ Max. Text Size: 1935
+ Max. Text Size: 1979
+
+e (string) strTmp
+ributes
+ Scope : local
+ Text :
+ Max. Text Size: 2
+
+7 +50,27 @@
+ Text :
+ Max. Text Size: 25
+
+e (string) tInstruction
+ributes
+ Scope : local
+ Text :
+ Max. Text Size: 15
+ Max. Text Size: 50
+
+e (string) tSend
+ributes
+ Scope : local
+ Text :
+ Max. Text Size: 50
+
+e (string) tTmp
+ributes
+ Scope : local
+ Text :
+ Max. Text Size: 30
+ Max. Text Size: 50
+
+e (int32) vaTap
+ributes
+ Scope: local
+ Value: 0
+
+e (int32) xc1
+165 +324,50 @@
+34 +360,14 @@
+ Scope : local
+ Dragging : 0
+ Send Component ID : disabled
@@ -1829,137 +1736,6 @@
+ Text :
+ Max. Text Size : 4
+
+nts
+ Touch Press Event
+ tSend.txt="event,buttonPress2,"+entn1.txt+",button"
+ if(entn1.txt=="")
+ {
+ tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
+ }
+ //send calc crc
+ btlen tSend.txt,sys0
+ crcrest 1,0xffff // reset CRC
+ crcputh 55 bb
+ crcputs sys0,2
+ crcputs tSend.txt,0
+ //send cmd
+ printh 55 bb
+ prints sys0,2
+ prints tSend.txt,0
+ prints crcval,2
+
+Icon
+ributes
+ Scope : local
+ Dragging : 0
+ Send Component ID : disabled
+ Associated Keyboard: none
+ Text :
+ Max. Text Size : 4
+
+nts
+ Touch Press Event
+ tSend.txt="event,buttonPress2,"+entn2.txt+",button"
+ if(entn2.txt=="")
+ {
+ tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
+ }
+ //send calc crc
+ btlen tSend.txt,sys0
+ crcrest 1,0xffff // reset CRC
+ crcputh 55 bb
+ crcputs sys0,2
+ crcputs tSend.txt,0
+ //send cmd
+ printh 55 bb
+ prints sys0,2
+ prints tSend.txt,0
+ prints crcval,2
+
+Icon
+ributes
+ Scope : local
+ Dragging : 0
+ Send Component ID : disabled
+ Associated Keyboard: none
+ Text :
+ Max. Text Size : 4
+
+nts
+ Touch Press Event
+ tSend.txt="event,buttonPress2,"+entn3.txt+",button"
+ if(entn3.txt=="")
+ {
+ tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
+ }
+ //send calc crc
+ btlen tSend.txt,sys0
+ crcrest 1,0xffff // reset CRC
+ crcputh 55 bb
+ crcputs sys0,2
+ crcputs tSend.txt,0
+ //send cmd
+ printh 55 bb
+ prints sys0,2
+ prints tSend.txt,0
+ prints crcval,2
+
+Icon
+ributes
+ Scope : local
+ Dragging : 0
+ Send Component ID : disabled
+ Associated Keyboard: none
+ Text :
+ Max. Text Size : 4
+
+nts
+ Touch Press Event
+ tSend.txt="event,buttonPress2,"+entn4.txt+",button"
+ if(entn4.txt=="")
+ {
+ tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
+ }
+ //send calc crc
+ btlen tSend.txt,sys0
+ crcrest 1,0xffff // reset CRC
+ crcputh 55 bb
+ crcputs sys0,2
+ crcputs tSend.txt,0
+ //send cmd
+ printh 55 bb
+ prints sys0,2
+ prints tSend.txt,0
+ prints crcval,2
+
+Icon
+ributes
+ Scope : local
+ Dragging : 0
+ Send Component ID : disabled
+ Associated Keyboard: none
+ Text :
+ Max. Text Size : 4
+
+nts
+ Touch Press Event
+ tSend.txt="event,buttonPress2,"+entn5.txt+",button"
+ if(entn5.txt=="")
+ {
+ tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
+ }
+ //send calc crc
+ btlen tSend.txt,sys0
+ crcrest 1,0xffff // reset CRC
+ crcputh 55 bb
+ crcputs sys0,2
+ crcputs tSend.txt,0
+ //send cmd
+ printh 55 bb
+ prints sys0,2
+ prints tSend.txt,0
+ prints crcval,2
+
+
+ributes
+ Scope : local
@@ -2003,48 +1779,3 @@
+ if(tAMPM.txt=="")
+ {
+ vis tAMPM,0
+44 +677,34 @@
+ //e6Val
+ spstr strCommand.txt,e6Val.txt,"~",60
+ //f1Icon
+ spstr strCommand.txt,f1Icon.txt,"~",63
+ //f1Icon Color
+ spstr strCommand.txt,tTmp.txt,"~",64
+ covx tTmp.txt,f1Icon.pco,0,0
+ //f1Icon intNameEntity
+ spstr strCommand.txt,entn1.txt,"~",62
+ //f2Icon
+ spstr strCommand.txt,f2Icon.txt,"~",69
+ //f2Icon Color
+ spstr strCommand.txt,tTmp.txt,"~",70
+ covx tTmp.txt,f2Icon.pco,0,0
+ //f2Icon intNameEntity
+ spstr strCommand.txt,entn2.txt,"~",68
+ //f3Icon
+ spstr strCommand.txt,f3Icon.txt,"~",75
+ //f3Icon Color
+ spstr strCommand.txt,tTmp.txt,"~",76
+ covx tTmp.txt,f3Icon.pco,0,0
+ //f3Icon intNameEntity
+ spstr strCommand.txt,entn3.txt,"~",74
+ //f4Icon
+ spstr strCommand.txt,f4Icon.txt,"~",81
+ //f4Icon Color
+ spstr strCommand.txt,tTmp.txt,"~",82
+ covx tTmp.txt,f4Icon.pco,0,0
+ //f4Icon intNameEntity
+ spstr strCommand.txt,entn4.txt,"~",80
+ //f5Icon
+ spstr strCommand.txt,f5Icon.txt,"~",87
+ //f5Icon Color
+ spstr strCommand.txt,tTmp.txt,"~",88
+ covx tTmp.txt,f5Icon.pco,0,0
+ //f5Icon intNameEntity
+ spstr strCommand.txt,entn5.txt,"~",86
+ }
+ if(tInstruction.txt=="notify")
+ {
+ spstr strCommand.txt,tNotifyHead.txt,"~",1
+ spstr strCommand.txt,tNotifyText.txt,"~",2
+ if(tNotifyHead.txt!=""||tNotifyText.txt!="")
+ {

View File

@@ -1,274 +1,272 @@
from shared import *
head = sharedhead + """
if(tInstruction.txt=="wake")
{
click tc0,1
}
if(tInstruction.txt=="dimmode")
{
// get value
spstr strCommand.txt,tTmp.txt,"~",1
covx tTmp.txt,dimValue,0,0
dim=dimValue
// get value normal
spstr strCommand.txt,tTmp.txt,"~",2
covx tTmp.txt,dimValueNormal,0,0
// get background color
spstr strCommand.txt,tTmp.txt,"~",3
if(tTmp.txt!="")
{
covx tTmp.txt,defaultBcoColor,0,0
}
// get font color
spstr strCommand.txt,tTmp.txt,"~",4
if(tTmp.txt!="")
{
covx tTmp.txt,defaultFontColor,0,0
}
}
if(tInstruction.txt=="time")
{
click m0,1
//get set time to global variable
spstr strCommand.txt,pageIcons.vaTime.txt,"~",1
spstr pageIcons.vaTime.txt,tTime.txt,"?",0
spstr pageIcons.vaTime.txt,tAMPM.txt,"?",1
if(tAMPM.txt=="")
{
vis tAMPM,0
}
spstr strCommand.txt,tTimeAdd.txt,"~",2
ref tIcon1
ref tIcon2
}
if(tInstruction.txt=="date")
{
//get set date to global variable
spstr strCommand.txt,pageIcons.vaDate.txt,"~",1
tDate.txt=pageIcons.vaDate.txt
}
if(tInstruction.txt=="statusUpdate")
{
//statusIcon1
spstr strCommand.txt,tIcon1.txt,"~",1
spstr strCommand.txt,tTmp.txt,"~",2
covx tTmp.txt,tIcon1.pco,0,0
//statusIcon2
spstr strCommand.txt,tIcon2.txt,"~",3
spstr strCommand.txt,tTmp.txt,"~",4
covx tTmp.txt,tIcon2.pco,0,0
spstr strCommand.txt,tTmp.txt,"~",5
if(tTmp.txt!="")
{
tIcon1.font=3
}
spstr strCommand.txt,tTmp.txt,"~",6
if(tTmp.txt!="")
{
tIcon2.font=3
}
}
if(tInstruction.txt=="weatherUpdate"&&tNotifyHead.txt==""&&tNotifyText.txt=="")
{
//tMainIcon
spstr strCommand.txt,tMainIcon.txt,"~",3
//tMainIcon Color
spstr strCommand.txt,tTmp.txt,"~",4
covx tTmp.txt,tMainIcon.pco,0,0
//tMainText
spstr strCommand.txt,tMainText.txt,"~",6
"""
start = 7
for i in range(1,4):
idxstart = start + (i-1)*6
item = f"""
//d{i}Icon
spstr strCommand.txt,d{i}Icon.txt,"~",{idxstart+2}
//d{i}Icon Color
spstr strCommand.txt,tTmp.txt,"~",{idxstart+3}
covx tTmp.txt,d{i}Icon.pco,0,0
//d{i}Val
spstr strCommand.txt,d{i}Val.txt,"~",{idxstart+5}
"""
head = head + item
start = idxstart+6
for i in range(1,7):
idxstart = start + (i-1)*6
item = f"""
//e{i}Name
spstr strCommand.txt,e{i}Name.txt,"~",{idxstart+4}
//e{i}Icon
spstr strCommand.txt,e{i}Icon.txt,"~",{idxstart+2}
//e{i}Icon Color
spstr strCommand.txt,tTmp.txt,"~",{idxstart+3}
covx tTmp.txt,e{i}Icon.pco,0,0
//e{i}Val
spstr strCommand.txt,e{i}Val.txt,"~",{idxstart+5}
"""
head = head + item
start = idxstart+6
for i in range(1,6):
idxstart = start + (i-1)*6
item = f"""
//f{i}Icon
spstr strCommand.txt,f{i}Icon.txt,"~",{idxstart+2}
//f{i}Icon Color
spstr strCommand.txt,tTmp.txt,"~",{idxstart+3}
covx tTmp.txt,f{i}Icon.pco,0,0
//f{i}Icon intNameEntity
spstr strCommand.txt,entn{i}.txt,"~",{idxstart+1}
"""
head = head + item
head = head + """
}
if(tInstruction.txt=="color"&&tNotifyHead.txt==""&&tNotifyText.txt=="")
{
spstr strCommand.txt,tTmp.txt,"~",1
covx tTmp.txt,tTime.bco,0,0
if(tTime.bco!=screensaver.bco)
{
for(sys0=0;sys0<60;sys0++)
{
if(b[sys0].type==98||b[sys0].type==116||b[sys0].type==54)
{
b[sys0].bco=tTime.bco
}
}
}
spstr strCommand.txt,tTmp.txt,"~",2
covx tTmp.txt,tTime.pco,0,0
spstr strCommand.txt,tTmp.txt,"~",3
covx tTmp.txt,tAMPM.pco,0,0
spstr strCommand.txt,tTmp.txt,"~",4
covx tTmp.txt,tDate.pco,0,0
spstr strCommand.txt,tTmp.txt,"~",5
covx tTmp.txt,tMainText.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",6
//covx tTmp.txt,tForecast1.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",7
//covx tTmp.txt,tForecast2.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",8
//covx tTmp.txt,tForecast3.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",9
//covx tTmp.txt,tForecast4.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",10
//covx tTmp.txt,tForecast1Val.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",11
//covx tTmp.txt,tForecast2Val.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",12
//covx tTmp.txt,tForecast3Val.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",13
//covx tTmp.txt,tForecast4Val.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",14
//covx tTmp.txt,t10.bco,0,0
//spstr strCommand.txt,tTmp.txt,"~",15
//covx tTmp.txt,tMainTextAlt2.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",16
//covx tTmp.txt,tTimeAdd.pco,0,0
}
if(tInstruction.txt=="notify")
{
spstr strCommand.txt,tNotifyHead.txt,"~",1
spstr strCommand.txt,tNotifyText.txt,"~",2
if(tNotifyHead.txt!=""||tNotifyText.txt!="")
{
vis tNotifyHead,1
vis tNotifyText,1
}else
{
vis tNotifyHead,0
vis tNotifyText,0
}
tNotifyHead.bco=tTime.bco
tNotifyText.bco=tTime.bco
spstr strCommand.txt,tTmp.txt,"~",3
if(tTmp.txt!="")
{
covx tTmp.txt,tNotifyHead.pco,0,0
}
spstr strCommand.txt,tTmp.txt,"~",4
if(tTmp.txt!="")
{
covx tTmp.txt,tNotifyText.pco,0,0
}
"""
print(head)
#start = 23
#for i in range(1,7):
# idxstart = start + (i-1)*6
# item = f"""
# // get Type
# spstr strCommand.txt,type{i}.txt,"~",{idxstart}
# // get internal name
# spstr strCommand.txt,entn{i}.txt,"~",{idxstart+1}
# if(type{i}.txt=="delete"||type{i}.txt=="")
# {{
# vis tEntity{i},0
# vis bEntity{i},0
# }}else
# {{
# // change icon
# spstr strCommand.txt,bEntity{i}.txt,"~",{idxstart+2}
# vis bEntity{i},1
# // change icon color
# spstr strCommand.txt,tTmp.txt,"~",{idxstart+3}
# covx tTmp.txt,sys0,0,0
# bEntity{i}.pco=sys0
# // set name
# spstr strCommand.txt,tEntity{i}.txt,"~",{idxstart+4}
# vis tEntity{i},1
# }}
#"""
# print(item)
foot = """
}
""" + sharedfoot.replace("sleepValue=0", "dim=100").replace("""
if(tInstruction.txt=="time")
{
// get set time to global variable
spstr strCommand.txt,pageIcons.vaTime.txt,"~",1
}
if(tInstruction.txt=="date")
{
// get set date to global variable
spstr strCommand.txt,pageIcons.vaDate.txt,"~",1
}
if(tInstruction.txt=="dimmode")
{
// get value
spstr strCommand.txt,tTmp.txt,"~",1
covx tTmp.txt,dimValue,0,0
// get value normal
spstr strCommand.txt,tTmp.txt,"~",2
covx tTmp.txt,dimValueNormal,0,0
dim=dimValueNormal
// get background color
spstr strCommand.txt,tTmp.txt,"~",3
if(tTmp.txt!="")
{
covx tTmp.txt,defaultBcoColor,0,0
}
// get font color
spstr strCommand.txt,tTmp.txt,"~",4
if(tTmp.txt!="")
{
covx tTmp.txt,defaultFontColor,0,0
}
}""","")
print(foot)
from shared import *
head = sharedhead + """
if(tInstruction.txt=="wake")
{
click tc0,1
}
if(tInstruction.txt=="dimmode")
{
// get value
spstr strCommand.txt,tTmp.txt,"~",1
covx tTmp.txt,dimValue,0,0
dim=dimValue
// get value normal
spstr strCommand.txt,tTmp.txt,"~",2
covx tTmp.txt,dimValueNormal,0,0
// get background color
spstr strCommand.txt,tTmp.txt,"~",3
if(tTmp.txt!="")
{
covx tTmp.txt,defaultBcoColor,0,0
}
// get font color
spstr strCommand.txt,tTmp.txt,"~",4
if(tTmp.txt!="")
{
covx tTmp.txt,defaultFontColor,0,0
}
}
if(tInstruction.txt=="time")
{
click m0,1
//get set time to global variable
spstr strCommand.txt,pageIcons.vaTime.txt,"~",1
spstr pageIcons.vaTime.txt,tTime.txt,"?",0
spstr pageIcons.vaTime.txt,tAMPM.txt,"?",1
if(tAMPM.txt=="")
{
vis tAMPM,0
}
spstr strCommand.txt,tTimeAdd.txt,"~",2
ref tIcon1
ref tIcon2
}
if(tInstruction.txt=="date")
{
//get set date to global variable
spstr strCommand.txt,pageIcons.vaDate.txt,"~",1
tDate.txt=pageIcons.vaDate.txt
}
if(tInstruction.txt=="statusUpdate")
{
//statusIcon1
spstr strCommand.txt,tIcon1.txt,"~",1
spstr strCommand.txt,tTmp.txt,"~",2
covx tTmp.txt,tIcon1.pco,0,0
//statusIcon2
spstr strCommand.txt,tIcon2.txt,"~",3
spstr strCommand.txt,tTmp.txt,"~",4
covx tTmp.txt,tIcon2.pco,0,0
spstr strCommand.txt,tTmp.txt,"~",5
if(tTmp.txt!="")
{
tIcon1.font=3
}
spstr strCommand.txt,tTmp.txt,"~",6
if(tTmp.txt!="")
{
tIcon2.font=3
}
}
if(tInstruction.txt=="weatherUpdate"&&tNotifyHead.txt==""&&tNotifyText.txt=="")
{
//tMainIcon
spstr strCommand.txt,tMainIcon.txt,"~",3
//tMainIcon Color
spstr strCommand.txt,tTmp.txt,"~",4
covx tTmp.txt,tMainIcon.pco,0,0
//tMainText
spstr strCommand.txt,tMainText.txt,"~",6
"""
start = 7
for i in range(1,4):
idxstart = start + (i-1)*6
item = f"""
//d{i}Icon
spstr strCommand.txt,d{i}Icon.txt,"~",{idxstart+2}
//d{i}Icon Color
spstr strCommand.txt,tTmp.txt,"~",{idxstart+3}
covx tTmp.txt,d{i}Icon.pco,0,0
//d{i}Val
spstr strCommand.txt,d{i}Val.txt,"~",{idxstart+5}
"""
head = head + item
start = idxstart+6
for i in range(1,7):
idxstart = start + (i-1)*6
item = f"""
//e{i}Name
spstr strCommand.txt,e{i}Name.txt,"~",{idxstart+4}
//e{i}Icon
spstr strCommand.txt,e{i}Icon.txt,"~",{idxstart+2}
//e{i}Icon Color
spstr strCommand.txt,tTmp.txt,"~",{idxstart+3}
covx tTmp.txt,e{i}Icon.pco,0,0
//e{i}Val
spstr strCommand.txt,e{i}Val.txt,"~",{idxstart+5}
"""
head = head + item
start = idxstart+6
for i in range(1,6):
idxstart = start + (i-1)*6
item = f"""
//f{i}Icon
spstr strCommand.txt,f{i}Icon.txt,"~",{idxstart+2}
//f{i}Icon Color
spstr strCommand.txt,tTmp.txt,"~",{idxstart+3}
covx tTmp.txt,f{i}Icon.pco,0,0
"""
head = head + item
head = head + """
}
if(tInstruction.txt=="color"&&tNotifyHead.txt==""&&tNotifyText.txt=="")
{
spstr strCommand.txt,tTmp.txt,"~",1
covx tTmp.txt,tTime.bco,0,0
if(tTime.bco!=screensaver.bco)
{
for(sys0=0;sys0<60;sys0++)
{
if(b[sys0].type==98||b[sys0].type==116||b[sys0].type==54)
{
b[sys0].bco=tTime.bco
}
}
}
spstr strCommand.txt,tTmp.txt,"~",2
covx tTmp.txt,tTime.pco,0,0
spstr strCommand.txt,tTmp.txt,"~",3
covx tTmp.txt,tAMPM.pco,0,0
spstr strCommand.txt,tTmp.txt,"~",4
covx tTmp.txt,tDate.pco,0,0
spstr strCommand.txt,tTmp.txt,"~",5
covx tTmp.txt,tMainText.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",6
//covx tTmp.txt,tForecast1.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",7
//covx tTmp.txt,tForecast2.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",8
//covx tTmp.txt,tForecast3.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",9
//covx tTmp.txt,tForecast4.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",10
//covx tTmp.txt,tForecast1Val.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",11
//covx tTmp.txt,tForecast2Val.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",12
//covx tTmp.txt,tForecast3Val.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",13
//covx tTmp.txt,tForecast4Val.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",14
//covx tTmp.txt,t10.bco,0,0
//spstr strCommand.txt,tTmp.txt,"~",15
//covx tTmp.txt,tMainTextAlt2.pco,0,0
//spstr strCommand.txt,tTmp.txt,"~",16
//covx tTmp.txt,tTimeAdd.pco,0,0
}
if(tInstruction.txt=="notify")
{
spstr strCommand.txt,tNotifyHead.txt,"~",1
spstr strCommand.txt,tNotifyText.txt,"~",2
if(tNotifyHead.txt!=""||tNotifyText.txt!="")
{
vis tNotifyHead,1
vis tNotifyText,1
}else
{
vis tNotifyHead,0
vis tNotifyText,0
}
tNotifyHead.bco=tTime.bco
tNotifyText.bco=tTime.bco
spstr strCommand.txt,tTmp.txt,"~",3
if(tTmp.txt!="")
{
covx tTmp.txt,tNotifyHead.pco,0,0
}
spstr strCommand.txt,tTmp.txt,"~",4
if(tTmp.txt!="")
{
covx tTmp.txt,tNotifyText.pco,0,0
}
"""
print(head)
#start = 23
#for i in range(1,7):
# idxstart = start + (i-1)*6
# item = f"""
# // get Type
# spstr strCommand.txt,type{i}.txt,"~",{idxstart}
# // get internal name
# spstr strCommand.txt,entn{i}.txt,"~",{idxstart+1}
# if(type{i}.txt=="delete"||type{i}.txt=="")
# {{
# vis tEntity{i},0
# vis bEntity{i},0
# }}else
# {{
# // change icon
# spstr strCommand.txt,bEntity{i}.txt,"~",{idxstart+2}
# vis bEntity{i},1
# // change icon color
# spstr strCommand.txt,tTmp.txt,"~",{idxstart+3}
# covx tTmp.txt,sys0,0,0
# bEntity{i}.pco=sys0
# // set name
# spstr strCommand.txt,tEntity{i}.txt,"~",{idxstart+4}
# vis tEntity{i},1
# }}
#"""
# print(item)
foot = """
}
""" + sharedfoot.replace("sleepValue=0", "dim=100").replace("""
if(tInstruction.txt=="time")
{
// get set time to global variable
spstr strCommand.txt,pageIcons.vaTime.txt,"~",1
}
if(tInstruction.txt=="date")
{
// get set date to global variable
spstr strCommand.txt,pageIcons.vaDate.txt,"~",1
}
if(tInstruction.txt=="dimmode")
{
// get value
spstr strCommand.txt,tTmp.txt,"~",1
covx tTmp.txt,dimValue,0,0
// get value normal
spstr strCommand.txt,tTmp.txt,"~",2
covx tTmp.txt,dimValueNormal,0,0
dim=dimValueNormal
// get background color
spstr strCommand.txt,tTmp.txt,"~",3
if(tTmp.txt!="")
{
covx tTmp.txt,defaultBcoColor,0,0
}
// get font color
spstr strCommand.txt,tTmp.txt,"~",4
if(tTmp.txt!="")
{
covx tTmp.txt,defaultFontColor,0,0
}
}""","")
print(foot)

View File

@@ -0,0 +1,63 @@
Page cardBurnRec
Attributes
ID : 0
Scope : local
Dragging : 0
Send Component ID : disabled
Opacity : 127
Width : 480
Effect : load
Effect Priority : 0
Effect Time : 300
Locked : no
Swide up page ID : disabled
Swide down page ID : disabled
Swide left page ID : disabled
Swide right page ID: disabled
Fill : solid color
Back. Color : 65535
Timer tm0
Attributes
ID : 1
Scope : local
Period (ms): 1001
Enabled : yes
Events
Timer Event
dim=100
sys0=0
sys1=0
if(sys2==WHITE)
{
sys2=BLACK
}else
{
sys2=WHITE
}
sya1=sys2
while(sys0<8)
{
sya0=sys0%2
if(sya1==WHITE)
{
sya1=BLACK
}else
{
sya1=WHITE
}
fill 0,40*sys0,480,40,sya1
sys0++
}
TouchCap tc0
Attributes
ID : 2
Scope: local
Value: 0
Events
Touch Press Event
page pageStartup

View File

@@ -6,14 +6,10 @@ pageIcons
7 Component(s)
0 Line(s) of event code
0 Unique line(s) of event code
pageTest
25 Component(s)
68 Line(s) of event code
66 Unique line(s) of event code
popupNotify
19 Component(s)
271 Line(s) of event code
174 Unique line(s) of event code
cardBurnRec
3 Component(s)
25 Line(s) of event code
21 Unique line(s) of event code
popupFan
27 Component(s)
355 Line(s) of event code
@@ -30,10 +26,14 @@ popupLightNew
23 Component(s)
412 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
popupNotify
19 Component(s)
267 Line(s) of event code
172 Unique line(s) of event code
pageTest
26 Component(s)
69 Line(s) of event code
67 Unique line(s) of event code
cardGrid2
52 Component(s)
703 Line(s) of event code
@@ -46,10 +46,6 @@ cardChart
33 Component(s)
447 Line(s) of event code
297 Unique line(s) of event code
cardGrid
44 Component(s)
593 Line(s) of event code
333 Unique line(s) of event code
cardQR
34 Component(s)
420 Line(s) of event code
@@ -58,6 +54,10 @@ cardLChart
33 Component(s)
412 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
54 Component(s)
541 Line(s) of event code
@@ -66,6 +66,10 @@ cardThermo
57 Component(s)
550 Line(s) of event code
320 Unique line(s) of event code
cardGrid
44 Component(s)
593 Line(s) of event code
333 Unique line(s) of event code
popupInSel
34 Component(s)
621 Line(s) of event code
@@ -87,16 +91,16 @@ popupThermo
523 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
59 Component(s)
373 Line(s) of event code
256 Unique line(s) of event code
cardEntities
67 Component(s)
1205 Line(s) of event code
536 Unique line(s) of event code
Total
23 Page(s)
881 Component(s)
10798 Line(s) of event code
2472 Unique line(s) of event code
24 Page(s)
880 Component(s)
10740 Line(s) of event code
2475 Unique line(s) of event code

View File

@@ -335,6 +335,41 @@ Button b13
Touch Press Event
showqq
Button b14
Attributes
ID : 25
Scope : local
Dragging : 0
Send Component ID : disabled
Opacity : 127
x coordinate : 229
y coordinate : 188
Width : 106
Height : 40
Effect : load
Effect Priority : 0
Effect Time : 300
Fill : solid color
Style : 3D auto
Font ID : 1
Back. Color : 50712
Back. Picture ID (Pressed): 65535
Back. Color (Pressed) : 1024
Font Color (Unpressed) : 0
Font Color (Pressed) : 65535
Horizontal Alignment : center
Vertical Alignment : center
State : unpressed
Text : burntest
Max. Text Size : 10
Word wrap : disabled
Horizontal Spacing : 0
Vertical Spacing : 0
Events
Touch Press Event
page cardBurnRec
Button b15
Attributes
ID : 21

View File

@@ -806,11 +806,6 @@ Timer tmSerial
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

View File

@@ -672,10 +672,6 @@ Timer tmSerial
{
page cardChart
}
if(tId.txt=="cardLChart")
{
page cardLChart
}
}
// end of user code
udelete payloadLength-1

View File

@@ -56,47 +56,12 @@ Page screensaver2
Postinitialize Event
click m0,1
Variable (string) entn1
Attributes
ID : 59
Scope : local
Text :
Max. Text Size: 14
Variable (string) entn2
Attributes
ID : 60
Scope : local
Text :
Max. Text Size: 14
Variable (string) entn3
Attributes
ID : 61
Scope : local
Text :
Max. Text Size: 14
Variable (string) entn4
Attributes
ID : 62
Scope : local
Text :
Max. Text Size: 14
Variable (string) entn5
Attributes
ID : 63
Scope : local
Text :
Max. Text Size: 14
Variable (string) strCommand
Attributes
ID : 4
Scope : local
Text :
Max. Text Size: 1935
Max. Text Size: 1979
Variable (string) strTmp
Attributes
@@ -117,7 +82,7 @@ Variable (string) tInstruction
ID : 18
Scope : local
Text :
Max. Text Size: 15
Max. Text Size: 50
Variable (string) tSend
Attributes
@@ -131,7 +96,7 @@ Variable (string) tTmp
ID : 19
Scope : local
Text :
Max. Text Size: 30
Max. Text Size: 50
Variable (int32) vaTap
Attributes
@@ -900,25 +865,6 @@ Text f1Icon
Horizontal Spacing : 0
Vertical Spacing : 0
Events
Touch Press Event
tSend.txt="event,buttonPress2,"+entn1.txt+",button"
if(entn1.txt=="")
{
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
}
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
crcputh 55 bb
crcputs sys0,2
crcputs tSend.txt,0
//send cmd
printh 55 bb
prints sys0,2
prints tSend.txt,0
prints crcval,2
Text f2Icon
Attributes
ID : 54
@@ -948,25 +894,6 @@ Text f2Icon
Horizontal Spacing : 0
Vertical Spacing : 0
Events
Touch Press Event
tSend.txt="event,buttonPress2,"+entn2.txt+",button"
if(entn2.txt=="")
{
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
}
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
crcputh 55 bb
crcputs sys0,2
crcputs tSend.txt,0
//send cmd
printh 55 bb
prints sys0,2
prints tSend.txt,0
prints crcval,2
Text f3Icon
Attributes
ID : 55
@@ -996,25 +923,6 @@ Text f3Icon
Horizontal Spacing : 0
Vertical Spacing : 0
Events
Touch Press Event
tSend.txt="event,buttonPress2,"+entn3.txt+",button"
if(entn3.txt=="")
{
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
}
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
crcputh 55 bb
crcputs sys0,2
crcputs tSend.txt,0
//send cmd
printh 55 bb
prints sys0,2
prints tSend.txt,0
prints crcval,2
Text f4Icon
Attributes
ID : 53
@@ -1044,25 +952,6 @@ Text f4Icon
Horizontal Spacing : 0
Vertical Spacing : 0
Events
Touch Press Event
tSend.txt="event,buttonPress2,"+entn4.txt+",button"
if(entn4.txt=="")
{
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
}
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
crcputh 55 bb
crcputs sys0,2
crcputs tSend.txt,0
//send cmd
printh 55 bb
prints sys0,2
prints tSend.txt,0
prints crcval,2
Text f5Icon
Attributes
ID : 52
@@ -1092,25 +981,6 @@ Text f5Icon
Horizontal Spacing : 0
Vertical Spacing : 0
Events
Touch Press Event
tSend.txt="event,buttonPress2,"+entn5.txt+",button"
if(entn5.txt=="")
{
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
}
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
crcputh 55 bb
crcputs sys0,2
crcputs tSend.txt,0
//send cmd
printh 55 bb
prints sys0,2
prints tSend.txt,0
prints crcval,2
Text m0
Attributes
ID : 32
@@ -1681,36 +1551,26 @@ Timer tmSerial
//f1Icon Color
spstr strCommand.txt,tTmp.txt,"~",64
covx tTmp.txt,f1Icon.pco,0,0
//f1Icon intNameEntity
spstr strCommand.txt,entn1.txt,"~",62
//f2Icon
spstr strCommand.txt,f2Icon.txt,"~",69
//f2Icon Color
spstr strCommand.txt,tTmp.txt,"~",70
covx tTmp.txt,f2Icon.pco,0,0
//f2Icon intNameEntity
spstr strCommand.txt,entn2.txt,"~",68
//f3Icon
spstr strCommand.txt,f3Icon.txt,"~",75
//f3Icon Color
spstr strCommand.txt,tTmp.txt,"~",76
covx tTmp.txt,f3Icon.pco,0,0
//f3Icon intNameEntity
spstr strCommand.txt,entn3.txt,"~",74
//f4Icon
spstr strCommand.txt,f4Icon.txt,"~",81
//f4Icon Color
spstr strCommand.txt,tTmp.txt,"~",82
covx tTmp.txt,f4Icon.pco,0,0
//f4Icon intNameEntity
spstr strCommand.txt,entn4.txt,"~",80
//f5Icon
spstr strCommand.txt,f5Icon.txt,"~",87
//f5Icon Color
spstr strCommand.txt,tTmp.txt,"~",88
covx tTmp.txt,f5Icon.pco,0,0
//f5Icon intNameEntity
spstr strCommand.txt,entn5.txt,"~",86
}
if(tInstruction.txt=="notify")
{

View File

@@ -0,0 +1,53 @@
Page cardBurnRec
Attributes
Scope : local
Dragging : 0
Send Component ID : disabled
Locked : no
Swide up page ID : disabled
Swide down page ID : disabled
Swide left page ID : disabled
Swide right page ID: disabled
Timer tm0
Attributes
Scope : local
Period (ms): 1001
Enabled : yes
Events
Timer Event
dim=100
sys0=0
sys1=0
if(sys2==WHITE)
{
sys2=BLACK
}else
{
sys2=WHITE
}
sya1=sys2
while(sys0<8)
{
sya0=sys0%2
if(sya1==WHITE)
{
sya1=BLACK
}else
{
sya1=WHITE
}
fill 0,40*sys0,480,40,sya1
sys0++
}
TouchCap tc0
Attributes
Scope: local
Value: 0
Events
Touch Press Event
page pageStartup

View File

@@ -141,6 +141,19 @@ Button b13
Touch Press Event
showqq
Button b14
Attributes
Scope : local
Dragging : 0
Send Component ID: disabled
State : unpressed
Text : burntest
Max. Text Size : 10
Events
Touch Press Event
page cardBurnRec
Button b15
Attributes
Scope : local

View File

@@ -457,11 +457,6 @@ Timer tmSerial
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

View File

@@ -443,10 +443,6 @@ Timer tmSerial
{
page cardChart
}
if(tId.txt=="cardLChart")
{
page cardLChart
}
}
// end of user code
udelete payloadLength-1

View File

@@ -48,41 +48,11 @@ Page screensaver2
Postinitialize Event
click m0,1
Variable (string) entn1
Attributes
Scope : local
Text :
Max. Text Size: 14
Variable (string) entn2
Attributes
Scope : local
Text :
Max. Text Size: 14
Variable (string) entn3
Attributes
Scope : local
Text :
Max. Text Size: 14
Variable (string) entn4
Attributes
Scope : local
Text :
Max. Text Size: 14
Variable (string) entn5
Attributes
Scope : local
Text :
Max. Text Size: 14
Variable (string) strCommand
Attributes
Scope : local
Text :
Max. Text Size: 1935
Max. Text Size: 1979
Variable (string) strTmp
Attributes
@@ -100,7 +70,7 @@ Variable (string) tInstruction
Attributes
Scope : local
Text :
Max. Text Size: 15
Max. Text Size: 50
Variable (string) tSend
Attributes
@@ -112,7 +82,7 @@ Variable (string) tTmp
Attributes
Scope : local
Text :
Max. Text Size: 30
Max. Text Size: 50
Variable (int32) vaTap
Attributes
@@ -374,25 +344,6 @@ Text f1Icon
Text :
Max. Text Size : 4
Events
Touch Press Event
tSend.txt="event,buttonPress2,"+entn1.txt+",button"
if(entn1.txt=="")
{
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
}
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
crcputh 55 bb
crcputs sys0,2
crcputs tSend.txt,0
//send cmd
printh 55 bb
prints sys0,2
prints tSend.txt,0
prints crcval,2
Text f2Icon
Attributes
Scope : local
@@ -402,25 +353,6 @@ Text f2Icon
Text :
Max. Text Size : 4
Events
Touch Press Event
tSend.txt="event,buttonPress2,"+entn2.txt+",button"
if(entn2.txt=="")
{
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
}
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
crcputh 55 bb
crcputs sys0,2
crcputs tSend.txt,0
//send cmd
printh 55 bb
prints sys0,2
prints tSend.txt,0
prints crcval,2
Text f3Icon
Attributes
Scope : local
@@ -430,25 +362,6 @@ Text f3Icon
Text :
Max. Text Size : 4
Events
Touch Press Event
tSend.txt="event,buttonPress2,"+entn3.txt+",button"
if(entn3.txt=="")
{
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
}
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
crcputh 55 bb
crcputs sys0,2
crcputs tSend.txt,0
//send cmd
printh 55 bb
prints sys0,2
prints tSend.txt,0
prints crcval,2
Text f4Icon
Attributes
Scope : local
@@ -458,25 +371,6 @@ Text f4Icon
Text :
Max. Text Size : 4
Events
Touch Press Event
tSend.txt="event,buttonPress2,"+entn4.txt+",button"
if(entn4.txt=="")
{
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
}
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
crcputh 55 bb
crcputs sys0,2
crcputs tSend.txt,0
//send cmd
printh 55 bb
prints sys0,2
prints tSend.txt,0
prints crcval,2
Text f5Icon
Attributes
Scope : local
@@ -486,25 +380,6 @@ Text f5Icon
Text :
Max. Text Size : 4
Events
Touch Press Event
tSend.txt="event,buttonPress2,"+entn5.txt+",button"
if(entn5.txt=="")
{
tSend.txt="event,buttonPress2,screensaver,bExit,"+tTmp.txt
}
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
crcputh 55 bb
crcputs sys0,2
crcputs tSend.txt,0
//send cmd
printh 55 bb
prints sys0,2
prints tSend.txt,0
prints crcval,2
Text m0
Attributes
Scope : local
@@ -843,36 +718,26 @@ Timer tmSerial
//f1Icon Color
spstr strCommand.txt,tTmp.txt,"~",64
covx tTmp.txt,f1Icon.pco,0,0
//f1Icon intNameEntity
spstr strCommand.txt,entn1.txt,"~",62
//f2Icon
spstr strCommand.txt,f2Icon.txt,"~",69
//f2Icon Color
spstr strCommand.txt,tTmp.txt,"~",70
covx tTmp.txt,f2Icon.pco,0,0
//f2Icon intNameEntity
spstr strCommand.txt,entn2.txt,"~",68
//f3Icon
spstr strCommand.txt,f3Icon.txt,"~",75
//f3Icon Color
spstr strCommand.txt,tTmp.txt,"~",76
covx tTmp.txt,f3Icon.pco,0,0
//f3Icon intNameEntity
spstr strCommand.txt,entn3.txt,"~",74
//f4Icon
spstr strCommand.txt,f4Icon.txt,"~",81
//f4Icon Color
spstr strCommand.txt,tTmp.txt,"~",82
covx tTmp.txt,f4Icon.pco,0,0
//f4Icon intNameEntity
spstr strCommand.txt,entn4.txt,"~",80
//f5Icon
spstr strCommand.txt,f5Icon.txt,"~",87
//f5Icon Color
spstr strCommand.txt,tTmp.txt,"~",88
covx tTmp.txt,f5Icon.pco,0,0
//f5Icon intNameEntity
spstr strCommand.txt,entn5.txt,"~",86
}
if(tInstruction.txt=="notify")
{

Binary file not shown.

Binary file not shown.

View File

@@ -5,7 +5,7 @@ If you like this project consider buying me a pizza 🍕 <a href="https://paypal
[![hacs_badge](https://img.shields.io/badge/HACS-Default-41BDF5.svg)](https://github.com/hacs/integration)
![hacs validation](https://github.com/joBr99/nspanel-lovelace-ui/actions/workflows/hacs-validation.yaml/badge.svg)
[![GitHub Release](https://img.shields.io/github/release/joBr99/nspanel-lovelace-ui.svg)](https://github.com/joBr99/nspanel-lovelace-ui/releases)
![Project Maintenance](https://img.shields.io/maintenance/yes/2026.svg)
![Project Maintenance](https://img.shields.io/maintenance/yes/2023.svg)
[![GitHub Activity](https://img.shields.io/github/commit-activity/y/joBr99/nspanel-lovelace-ui.svg)](https://github.com/joBr99/nspanel-lovelace-ui/commits/main)
@@ -65,5 +65,3 @@ SmartHomeNG: https://github.com/sisamiwe/shng-nspanel-plugin
OpenHAB: https://github.com/donoo/o2n2l
NodeRed: https://github.com/laluz742/node-red-contrib-nspanel-lui
ESPHome without any Backend: https://github.com/olicooper/esphome-nspanel-lovelace-native

View File

@@ -1,3 +1,2 @@
ha_api = None
mqtt_api = None
ad_api = None
mqtt_api = None

View File

@@ -132,7 +132,6 @@ class LuiBackendConfig(object):
'sleepTrackingZones': ["not_home", "off"],
'sleepOverride': None,
'locale': "en_US",
'quiet': True,
'timeFormat': "%H:%M",
'dateFormatBabel': "full",
'dateAdditionalTemplate': "",

View File

@@ -3,7 +3,6 @@ import datetime
import apis
from helper import scale, pos_to_color, rgb_dec565
from pages import LuiPagesGen
from luibackend.config import Card
class LuiController(object):
@@ -151,7 +150,7 @@ class LuiController(object):
items = self._config.get_all_entity_names()
apis.ha_api.log(f"gtest123: {items}")
prefixes = ("navigate.", "delete", "iText")
items = set([x for x in items if not (x is None or x.startswith(prefixes))])
items = [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}")
for item in items:
if apis.ha_api.entity_exists(item):
@@ -206,15 +205,11 @@ class LuiController(object):
self._pages_gen.generate_timer_detail_page(entity_id, True)
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} ")
entity_id_new = entity_id
apis.ha_api.log(f"Button Press Event; entity_id: {entity_id}; button_type: {button_type}; value: {value} ")
if entity_id.startswith('uuid'):
entity_config = self._config._config_entites_table.get(entity_id)
if entity_config is not None:
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
entity_id = entity_config.entityId
# internal buttons
if entity_id == "screensaver" and button_type == "bExit":
# get default card if there is one
@@ -338,11 +333,6 @@ class LuiController(object):
apis.ha_api.get_entity(entity_id).call_service("return_to_base")
elif entity_id.startswith('service'):
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
if button_type == "media-next":
@@ -357,8 +347,8 @@ class LuiController(object):
else:
apis.ha_api.get_entity(entity_id).call_service("turn_off")
if button_type == "media-shuffle":
shuffle = not apis.ha_api.get_entity(entity_id).attributes['shuffle']
apis.ha_api.get_entity(entity_id).call_service("shuffle_set", shuffle=shuffle)
suffle = not apis.ha_api.get_entity(entity_id).attributes.shuffle
apis.ha_api.get_entity(entity_id).call_service("shuffle_set", shuffle=suffle)
if button_type == "volumeSlider":
pos = int(value)
# HA wants this value between 0 and 1 as float
@@ -375,7 +365,7 @@ class LuiController(object):
if button_type == "colorTempSlider":
entity = apis.ha_api.get_entity(entity_id)
#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)
if button_type == "colorWheel":
apis.ha_api.log(value)
@@ -402,9 +392,9 @@ class LuiController(object):
if button_type == "opnSensorNotify":
msg = ""
entity = apis.ha_api.get_entity(entity_id)
if open_sensors := entity.attributes.get("open_sensors") is not None:
for e in open_sensors:
msg += f"- {apis.ha_api.get_entity(e).attributes['friendly_name']}\r\n"
if "open_sensors" in entity.attributes and entity.attributes.open_sensors is not None:
for e in entity.attributes.open_sensors:
msg += f"- {apis.ha_api.get_entity(e).attributes.friendly_name}\r\n"
self._pages_gen.send_message_page("opnSensorNotifyRes", "", msg, "", "")
# for cardUnlock
@@ -421,22 +411,22 @@ class LuiController(object):
if button_type == "mode-preset_modes":
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)
if button_type == "mode-swing_modes":
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)
if button_type == "mode-fan_modes":
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)
if button_type in ["mode-input_select", "mode-select"]:
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)
if button_type == "mode-light":
@@ -448,12 +438,12 @@ class LuiController(object):
if options_list is not None:
option = options_list[int(value)]
else:
option = entity.attributes['effect_list'][int(value)]
option = entity.attributes.effect_list[int(value)]
entity.call_service("turn_on", effect=option)
if button_type == "mode-media_player":
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)
# timer detail page
@@ -468,9 +458,3 @@ class LuiController(object):
apis.ha_api.get_entity(entity_id).call_service("pause")
if button_type == "timer-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

View File

@@ -159,8 +159,8 @@ alarm_control_panel_mapping = {
}
climate_mapping = {
'auto': 'fan-auto',
'heat_cool': 'sun-snowflake-variant',
'auto': 'calendar-sync',
'heat_cool': 'calendar-sync',
'heat': 'fire',
'off': 'power',
'cool': 'snowflake',
@@ -213,9 +213,6 @@ def get_icon_ha(entity_id, overwrite=None, stateOverwrite=None):
entity = apis.ha_api.get_entity(entity_id)
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 type(overwrite) is str:
return get_icon_char(overwrite)
@@ -266,8 +263,8 @@ def get_icon_ha(entity_id, overwrite=None, stateOverwrite=None):
# based on media_content_type
elif ha_type == "media_player":
result_icon = "speaker-off"
if media_content_type := entity.attributes.get("media_content_type"):
if media_content_type in media_content_type_mapping:
result_icon = media_content_type_mapping[media_content_type]
if "media_content_type" in entity.attributes:
if entity.attributes.media_content_type in media_content_type_mapping:
result_icon = media_content_type_mapping[entity.attributes.media_content_type]
return get_icon_char(result_icon)

View File

@@ -77,13 +77,12 @@ class LuiMqttListener(object):
self._controller.detail_open(msg[2], msg[3])
class LuiMqttSender(object):
def __init__(self, api, use_api, topic_send, api_panel_name, quiet):
def __init__(self, api, use_api, topic_send, api_panel_name):
self._ha_api = api
self._use_api = use_api
self._topic_send = topic_send
self._api_panel_name = api_panel_name
self._prev_msg = ""
self._quiet = quiet
def send_mqtt_msg(self, msg, topic=None, force=False):
if not force and self._prev_msg == msg:
@@ -91,9 +90,7 @@ class LuiMqttSender(object):
return
self._prev_msg = msg
if self._quiet is False:
apis.ha_api.log(f"Sending Message: {msg}")
apis.ha_api.log(f"Sending Message: {msg}")
if self._use_api:
apis.ha_api.call_service(service="esphome/" + self._api_panel_name + "_nspanelui_api_call", command=2, data=msg)
else:

View File

@@ -25,7 +25,7 @@ class LuiPagesGen(object):
self._config = config
self._locale = config.get("locale")
self._send_mqtt_msg = send_mqtt_msg
def get_entity_color(self, entity, ha_type=None, stateOverwrite=None, overwrite=None):
if overwrite is not None:
if type(overwrite) in [str, list]:
@@ -82,7 +82,7 @@ class LuiPagesGen(object):
icon_color = 63878 #red
if state == "fog":
icon_color = 38066 #75% grey
if state in ["hail", "snowy"]:
if state in ["hail", "snowy"]:
icon_color = 65535 #white
if state == "lightning":
icon_color = 65120 #golden-yellow
@@ -97,12 +97,13 @@ class LuiPagesGen(object):
if state == "sunny":
icon_color = 65504 #bright-yellow
if color := attr.get("rgb_color"):
if brightness := attr.get("brightness"):
color = rgb_brightness(color, brightness)
if "rgb_color" in attr and attr.rgb_color:
color = attr.rgb_color
if "brightness" in attr and attr.brightness:
color = rgb_brightness(color, attr.brightness)
icon_color = rgb_dec565(color)
elif brightness := attr.get("brightness"):
color = rgb_brightness([253, 216, 53], brightness)
elif "brightness" in attr and attr.brightness:
color = rgb_brightness([253, 216, 53], attr.brightness)
icon_color = rgb_dec565(color)
return icon_color
@@ -128,7 +129,7 @@ class LuiPagesGen(object):
else:
dateformat = self._config.get("dateFormat")
date = datetime.datetime.now().strftime(dateformat)
addTemplate = self._config.get("dateAdditionalTemplate")
addDateText = apis.ha_api.render_template(addTemplate)
self._send_mqtt_msg(f"date~{date}{addDateText}")
@@ -137,7 +138,7 @@ class LuiPagesGen(object):
if target_page == "cardUnlock":
target_page = "cardAlarm"
self._send_mqtt_msg(f"pageType~{target_page}")
def update_screensaver_weather(self, theme):
entities = self._config._config_screensaver.entities
@@ -150,7 +151,7 @@ class LuiPagesGen(object):
item_str = ""
for item in entities:
item_str += self.generate_entities_item(item, "cardEntities", mask=["type", "entityId"])
self._send_mqtt_msg(f"weatherUpdate{item_str}")
# send color if configured in screensaver
if theme is not None:
@@ -180,20 +181,17 @@ class LuiPagesGen(object):
colorOverride = item.colorOverride
name = item.nameOverride
uuid = item.uuid
# check ha template for name
if item.nameOverride is not None and ("{" in item.nameOverride and "}" in item.nameOverride):
name = apis.ha_api.render_template(item.nameOverride)
# type of the item is the string before the "." in the entityId
if entityId is not None:
entityType = entityId.split(".")[0]
else:
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")
status_entity = apis.ha_api.get_entity(item.status) if item.status and apis.ha_api.entity_exists(item.status) else None
@@ -223,7 +221,7 @@ class LuiPagesGen(object):
if status_entity:
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)
if item.status.startswith("sensor") and cardType in ["cardGrid", "cardGrid1", "cardGrid2"] and item.iconOverride is None:
if item.status.startswith("sensor") and (cardType == "cardGrid" or cardType == "cardGrid2") and item.iconOverride is None:
icon_res = status_entity.state[:4]
if icon_res[-1] == ".":
icon_res = icon_res[:-1]
@@ -247,7 +245,7 @@ class LuiPagesGen(object):
if status_entity:
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)
if item.status.startswith("sensor") and cardType in ["cardGrid", "cardGrid1", "cardGrid2"] and item.iconOverride is None:
if item.status.startswith("sensor") and (cardType == "cardGrid" or cardType == "cardGrid2") and item.iconOverride is None:
icon_id = status_entity.state[:4]
if icon_id[-1] == ".":
icon_id = icon_id[:-1]
@@ -256,7 +254,7 @@ class LuiPagesGen(object):
if entity is None:
return f"~text~{entityId}~{get_icon_id('alert-circle-outline')}~17299~Not found check~ apps.yaml"
# HA Entities
# common res vars
entityTypePanel = "text"
@@ -275,7 +273,7 @@ class LuiPagesGen(object):
icon_up_status = "disable"
icon_stop_status = "disable"
icon_down_status = "disable"
bits = entity.attributes.get('supported_features')
bits = entity.attributes.supported_features
pos = entity.attributes.get("current_position")
if pos is None:
pos_status = entity.state
@@ -308,24 +306,19 @@ class LuiPagesGen(object):
device_class = entity.attributes.get("device_class", "")
unit_of_measurement = entity.attributes.get("unit_of_measurement", "")
value = entity.state
try:
value = str(round(float(value), 1))
except:
print("An exception occurred")
# limit value to 4 chars on us-p
if self._config.get("model") == "us-p" and cardType == "cardEntities":
value = entity.state[:4]
if value[-1] == ".":
value = value[:-1]
if device_class != "temperature":
value = value + " "
value = value + unit_of_measurement
if entityType == "binary_sensor":
value = get_translation(self._locale, f"backend.component.binary_sensor.state.{device_class}.{entity.state}")
if cardType in ["cardGrid", "cardGrid1", "cardGrid2"] and entityType == "sensor" and icon is None:
if (cardType == "cardGrid" or cardType == "cardGrid2") and entityType == "sensor" and icon is None:
icon_id = entity.state[:4]
if icon_id[-1] == ".":
icon_id = icon_id[:-1]
@@ -387,51 +380,20 @@ class LuiPagesGen(object):
elif entityType == "weather":
entityTypePanel = "text"
unit = get_attr_safe(entity, "temperature_unit", "")
rt = None
index = item.stype
if type(item.stype) == str and ":" in item.stype and len(item.stype.split(":")) == 2:
spintstr = item.stype.split(":")
rt = spintstr[0]
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)
if type(item.stype) == int and len(entity.attributes.forecast) >= item.stype:
fdate = dp.parse(entity.attributes.forecast[item.stype]['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:
value = f'{get_attr_safe(entity, "temperature", "")}{unit}'
dateformat = "%a" if item.nameOverride is None else item.nameOverride
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:
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:
name = "unsupported"
# Overwrite for value
@@ -490,7 +452,7 @@ class LuiPagesGen(object):
command = f"entityUpd~Not found~{navigation}~{item}~check~220~apps.yaml~150~300~5~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Please~your~~"
else:
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", "")
dest_temp = get_attr_safe(entity, "temperature", None)
dest_temp2 = ""
@@ -511,10 +473,10 @@ class LuiPagesGen(object):
state_value += get_translation(self._locale, f"backend.component.climate.state._.{entity.state}")
if hvac_action != "":
state_value += ")"
min_temp = int(get_attr_safe(entity, "min_temp", 0)*10)
max_temp = int(get_attr_safe(entity, "max_temp", 0)*10)
step_temp = int(get_attr_safe(entity, "target_temp_step", 0.5)*10)
step_temp = int(get_attr_safe(entity, "target_temp_step", 0.5)*10)
icon_res_list = []
icon_res = ""
@@ -539,7 +501,7 @@ class LuiPagesGen(object):
state = 0
if(mode == entity.state):
state = 1
icon_res_list.append(f"~{icon_id}~{color_on}~{state}~{mode}")
icon_res = "".join(icon_res_list)
@@ -554,11 +516,11 @@ class LuiPagesGen(object):
icon_res = "~"*4 + icon_res_list[0] + "~"*4 + icon_res_list[1] + "~"*4 + icon_res_list[2] + "~"*4 + icon_res_list[3]
elif len(icon_res_list) >= 5 or self._config.get("model") == "us-p":
icon_res = "".join(icon_res_list) + "~"*4*(8-len(icon_res_list))
currently_translation = get_translation(self._locale, "frontend.ui.card.climate.currently")
state_translation = get_translation(self._locale, "frontend.ui.panel.config.devices.entities.state")
action_translation = get_translation(self._locale, "frontend.ui.card.climate.operation").replace(' ','\r\n')
detailPage = "1"
if any(x in ["preset_modes", "swing_modes", "fan_modes"] for x in entity.attributes):
detailPage = "0"
@@ -572,7 +534,7 @@ class LuiPagesGen(object):
command = f"entityUpd~Not found~{navigation}"
else:
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
data_raw = apis.ha_api.get_history(entity_id = item, days = 7)
@@ -619,12 +581,12 @@ class LuiPagesGen(object):
else:
media_icon = self.generate_entities_item(entity, "cardGrid")
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", "")
author = get_attr_safe(ha_entity, "media_artist", "")
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")
bits = ha_entity.attributes['supported_features']
bits = ha_entity.attributes.supported_features
onoffbutton = "disable"
if bits & 0b10000000:
if ha_entity.state == "off":
@@ -642,7 +604,7 @@ class LuiPagesGen(object):
item_str = ""
for item in entities:
item_str += self.generate_entities_item(item, "cardGrid")
bck_override = entity.iconOverride
if entity.status is not None:
bck_entity = entity.entityId
@@ -650,14 +612,14 @@ class LuiPagesGen(object):
entity.iconOverride = "mdi:speaker"
item_str += self.generate_entities_item(entity, "cardGrid")
entity.iconOverride = bck_override
if entity.status is not None:
entity.entityId = bck_entity
command = f"entityUpd~{heading}~{navigation}~{entityId}~{title}~~{author}~~{volume}~{iconplaypause}~{onoffbutton}~{shuffleBtn}{media_icon}{item_str}"
self._send_mqtt_msg(command)
def generate_alarm_page(self, navigation, title, entity, overwrite_supported_modes, alarmBtn):
item = entity.entityId
if not apis.ha_api.entity_exists(item):
@@ -674,7 +636,7 @@ class LuiPagesGen(object):
if not entity.attributes.get("code_arm_required", False):
numpad = "disable"
if overwrite_supported_modes is None:
bits = entity.attributes['supported_features']
bits = entity.attributes.supported_features
if bits & 0b000001:
supported_modes.append("arm_home")
if bits & 0b000010:
@@ -713,7 +675,7 @@ class LuiPagesGen(object):
#add button to show sensor state
add_btn = ""
if entity.attributes.get("open_sensors") is not None:
if "open_sensors" in entity.attributes and entity.attributes.open_sensors is not None:
add_btn=f"{get_icon_id('progress-alert')}~{rgb_dec565([243,179,0])}~"
if alarmBtn is not None and type(alarmBtn) is dict:
entity = alarmBtn.get("entity")
@@ -724,8 +686,8 @@ class LuiPagesGen(object):
else:
icon_color = rgb_dec565([243,179,0])
add_btn=f"{iconnav}~{icon_color}~{entity}"
# add padding to arm buttons
arm_buttons = ""
for b in supported_modes:
@@ -735,12 +697,12 @@ class LuiPagesGen(object):
arm_buttons += "~"*((4-len(supported_modes))*2)
command = f"entityUpd~{title}~{navigation}~{item}{arm_buttons}~{icon}~{color}~{numpad}~{flashing}~{add_btn}"
self._send_mqtt_msg(command)
def generate_unlock_page(self, navigation, item, title, destination, pin):
color = rgb_dec565([255,0,0])
icon = get_icon_id("lock")
supported_modes = ["cardUnlock-unlock"]
# add padding to arm buttons
arm_buttons = ""
for b in supported_modes:
@@ -779,8 +741,8 @@ class LuiPagesGen(object):
if (time.time()-card.last_update) < card.cooldown:
return
card.last_update = time.time()
leftBtn = "delete~~~~~"
if card.uuid_prev is not None:
leftBtn = self.generate_entities_item(Entity(
@@ -817,8 +779,6 @@ class LuiPagesGen(object):
if send_page_type:
if card.cardType == "cardGrid" and len(card.entities) > 6:
card.cardType = "cardGrid2"
if card.cardType == "cardGrid1":
card.cardType = "cardGrid"
self.page_type(card.cardType)
# send sleep timeout if there is one configured for the current card
@@ -826,9 +786,9 @@ class LuiPagesGen(object):
self._send_mqtt_msg(f"timeout~{card.sleepTimeout}")
else:
self._send_mqtt_msg(f'timeout~{self._config.get("sleepTimeout")}')
temp_unit = card.raw_config.get("temperatureUnit", "celsius")
if card.cardType in ["cardEntities", "cardGrid", "cardGrid1","cardGrid2"]:
if card.cardType in ["cardEntities", "cardGrid", "cardGrid2"]:
self.generate_entities_page(navigation, card.title, card.entities, card.cardType, temp_unit)
return
if card.cardType == "cardThermo":
@@ -880,26 +840,25 @@ class LuiPagesGen(object):
color_temp = "disable"
color = "disable"
effect_supported = "disable"
supported_color_modes = entity.attributes['supported_color_modes']
if "onoff" not in supported_color_modes:
if "onoff" not in entity.attributes.supported_color_modes:
brightness = 0
if entity.state == "on":
if brightness := entity.attributes.get("brightness"):
if "brightness" in entity.attributes and entity.attributes.brightness:
# scale 0-255 brightness from ha to 0-100
brightness = int(scale(brightness, (0,255), (0,100)))
brightness = int(scale(entity.attributes.brightness,(0,255),(0,100)))
else:
brightness = "disable"
if "color_temp" in supported_color_modes:
if color_temp := entity.attributes.get("color_temp"):
if "color_temp" in entity.attributes.supported_color_modes and entity.attributes.supported_color_modes:
if "color_temp" in entity.attributes and entity.attributes.color_temp:
# scale ha color temp range to 0-100
color_temp = int(scale(color_temp, (entity.attributes['min_mireds'], entity.attributes['max_mireds']),(0, 100)))
color_temp = int(scale(entity.attributes.color_temp,(entity.attributes.min_mireds, entity.attributes.max_mireds),(0,100)))
else:
color_temp = "unknown"
else:
color_temp = "disable"
list_color_modes = ["xy", "rgb", "rgbw", "hs"]
if any(item in list_color_modes for item in supported_color_modes):
if any(item in list_color_modes for item in entity.attributes.supported_color_modes):
color = "enable"
else:
color = "disable"
@@ -909,20 +868,20 @@ class LuiPagesGen(object):
brightness_translation = get_translation(self._locale, "frontend.ui.card.light.brightness")
color_temp_translation = get_translation(self._locale, "frontend.ui.card.light.color_temperature")
self._send_mqtt_msg(f"entityUpdateDetail~{entity_id}~~{icon_color}~{switch_val}~{brightness}~{color_temp}~{color}~{color_translation}~{color_temp_translation}~{brightness_translation}~{effect_supported}", force=is_open_detail)
def generate_shutter_detail_page(self, entity_id, is_open_detail=False):
entity = apis.ha_api.get_entity(entity_id)
entityType = "cover"
device_class = entity.attributes.get("device_class", "window")
icon_id = get_icon_ha(entity_id)
pos = entity.attributes.get("current_position")
if pos is None:
pos_status = entity.state
pos = "disable"
else:
pos_status = pos
pos_translation = ""
icon_up = ""
icon_stop = ""
@@ -939,7 +898,7 @@ class LuiPagesGen(object):
iconTiltRightStatus = "disable"
tilt_pos = "disable"
bits = entity.attributes['supported_features']
bits = entity.attributes.supported_features
# position supported
if bits & 0b00001111:
@@ -1026,7 +985,7 @@ class LuiPagesGen(object):
if modes:
modes_out += f"{heading}~{mode}~{cur_mode}~{modes_res}~"
self._send_mqtt_msg(f"entityUpdateDetail~{entity_id}~{icon_id}~{icon_color}~{modes_out}", force=is_open_detail)
self._send_mqtt_msg(f"entityUpdateDetail~{entity_id}~{icon_id}~{icon_color}~{modes_out}", force=is_open_detail)
def generate_input_select_detail_page(self, entity_id, is_open_detail=False):
options_list = None
@@ -1090,9 +1049,8 @@ class LuiPagesGen(object):
label2 = get_translation(self._locale, "frontend.ui.card.timer.actions.cancel")
label3 = get_translation(self._locale, "frontend.ui.card.timer.actions.finish")
self._send_mqtt_msg(f"entityUpdateDetail~{entity_id}~~{icon_color}~{entity_id}~{min_remaining}~{sec_remaining}~{editable}~{action1}~{action2}~{action3}~{label1}~{label2}~{label3}", force=is_open_detail)
def send_message_page(self, ident, heading, msg, b1, b2):
self._send_mqtt_msg(f"pageType~popupNotify")
self._send_mqtt_msg(f"entityUpdateDetail~{ident}~{heading}~65535~{b1}~65535~{b2}~65535~{msg}~65535~0")

View File

@@ -1,4 +1,4 @@
import adbase as ad
import hassapi as hass
from luibackend.config import LuiBackendConfig
from luibackend.controller import LuiController
@@ -6,19 +6,15 @@ from luibackend.mqtt import LuiMqttListener, LuiMqttSender
from luibackend.updater import Updater
import apis
import json
from typing import Literal
class NsPanelLovelaceUIManager(ad.ADBase):
class NsPanelLovelaceUIManager(hass.Hass):
def initialize(self):
self.adapi = self.get_ad_api()
self.adapi.log('Starting')
apis.ad_api = self.adapi
apis.ha_api = self.get_plugin_api("HASS")
self.log('Starting')
apis.ha_api = self
apis.mqtt_api = self.get_plugin_api("MQTT")
cfg = self._cfg = LuiBackendConfig(apis.ha_api, self.args["config"])
cfg = self._cfg = LuiBackendConfig(self, self.args["config"])
use_api = cfg.get("use_api") == True
@@ -26,15 +22,14 @@ class NsPanelLovelaceUIManager(ad.ADBase):
topic_recv = cfg.get("panelRecvTopic")
api_panel_name = cfg.get("panelName")
api_device_id = cfg.get("panelDeviceId")
quiet = cfg.get("quiet")
mqttsender = self._mqttsender = LuiMqttSender(apis.ha_api, use_api, topic_send, api_panel_name, quiet)
mqttsend = LuiMqttSender(self, use_api, topic_send, api_panel_name)
self._controller = LuiController(cfg, mqttsender.send_mqtt_msg)
controller = LuiController(cfg, mqttsend.send_mqtt_msg)
desired_tasmota_driver_version = 8
desired_display_firmware_version = 53
version = "v4.7.3"
version = "v4.3.3"
model = cfg.get("model")
if model == "us-l":
@@ -46,35 +41,11 @@ class NsPanelLovelaceUIManager(ad.ADBase):
desired_tasmota_driver_url = cfg._config.get("berryURL", "https://raw.githubusercontent.com/joBr99/nspanel-lovelace-ui/main/tasmota/autoexec.be")
mode = cfg.get("updateMode")
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)
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)
# Request Tasmota Driver Version
updater.request_berry_driver_version()
LuiMqttListener(use_api, topic_recv, api_panel_name, api_device_id, self._controller, updater)
LuiMqttListener(use_api, topic_recv, api_panel_name, api_device_id, controller, updater)
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
self.log(f'Started ({version})')

View File

@@ -1,112 +0,0 @@
/*.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;
}

View File

@@ -1,137 +0,0 @@
# 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
```

View File

@@ -1,61 +0,0 @@
# 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
```

View File

@@ -1,45 +0,0 @@
# 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.

View File

@@ -1,77 +0,0 @@
# 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]
```

View File

@@ -1,55 +0,0 @@
# 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`

View File

@@ -1,38 +0,0 @@
# 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.

View File

@@ -1,120 +0,0 @@
# 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.

View File

@@ -1,57 +0,0 @@
# 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
```

View File

@@ -1,63 +0,0 @@
# 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>`

View File

@@ -1,60 +0,0 @@
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

View File

@@ -10,46 +10,14 @@
display: none;
}
/*.md-header,*/ .md-footer,
.md-footer-meta {
/*.md-header,*/ .md-footer {
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__inner.md-grid {
display: none;
}
.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;
}
@@ -109,4 +77,4 @@
ol li::marker {
font-weight: bold;
}
}

View File

@@ -1,75 +0,0 @@
# 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/)

View File

@@ -44,6 +44,8 @@ Using a 6th entity will automatically activate the alternative layout.
type: 0
- entity: weather.demo_weather_north
type: 1
- entity: weather.demo_weather_north
type: 2
- entity: sensor.energy_usage
- entity: delete
- entity: sensor.indoor_temp

View File

@@ -93,4 +93,3 @@ 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
proceed with the download
7. The Backend Application is now installed, and HACS will inform you when updates are available

View File

@@ -1,71 +1,65 @@
# Flash Tasmota to Your NSPanel
# Flash Tasmota 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](https://tasmota.github.io/install/) to do so.
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 can use the Tasmota Web Installer to do so. [Tasmota Web Installer](https://tasmota.github.io/install/)
Check out Blakadder's Template Repo for more information on flashing. **Do not** use the autoexec.be from that page.
Checkout Blakadders Template Repo for more information on flashing, do not use the autoexec.be from this page.
[NSPanel Page of the Tasmota Template Repository](https://templates.blakadder.com/sonoff_NSPanel.html)
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)
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.
[ESPHome component](https://github.com/sairon/esphome-nspanel-lovelace-ui)
---
## Configure Tasmota Template for NSPanel
Configure the NSPanel template for Tasmota. (Go to Configuration > Configure Other, paste the template there, and make sure to tick the Activate checkbox.)
Configure the NSPanel template for Tasmota. (Go to Configuration and Configure Other and paste the template there, make sure to tick the activate checkbox)
![tasmota-template-config](img/tasmota-template-config.png)
You can use the following template or copy the one from the [Tasmota Template Repo Site](https://templates.blakadder.com/sonoff_NSPanel.html):
You can use the following template or copy the one on 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
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 downloads the autoexec.be file from the repository and restarts Tasmota.
This will download the autoexec.be file from the repository and restart tasmota.
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.)
---
Note: The command is also disabling matter to free up memory, as it's most likely not used by any homeassistant users anyway.
(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
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.
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.
Use one of the following commands in the Tasmota console (not the Berry console) to flash the latest release from this repository:
Use the one following commands in the tasmota console (not 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 process takes around 5 minutes.
After sending the command, the screen should show a progress bar. The flashing progress takes around 5 minutes.
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.
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
---
<details>
<summary>Alternatively, you can use your own web server or the one built into Home Assistant:</summary>
<summary>Alternatively you can use your own webserver or the one build into HomeAssistant:</summary>
<br>
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))
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))
**The web server must be HTTP. HTTPS is not supported due to Berry language limitations in Tasmota.**
**Webserver must be HTTP, HTTPS is not supported, due to limitations of berry lang on tasmota**
FlashNextion http://ip-address-of-your-homeassistant:8123/local/nspanel.tft
`FlashNextion http://ip-address-of-your-homeassistant:8123/local/nspanel.tft`
</details>

View File

@@ -0,0 +1,74 @@
const idAbfalliCal = 'ical.1'; // iCal Instanz zum Abfallkalender
const idZeichenLoeschen = 14; // x Zeichen links vom String abziehen, wenn vor dem Eventname noch Text steht z.B. Strassenname; Standard = 0
const idRestmuellName ='Hausmüll'; // Schwarze Tonne
const idWertstoffName = 'Gelber Sack'; // Gelbe Tonne / Sack
const idPappePapierName = 'Papier'; // Blaue Tonne
const idBioabfaelleName = 'Biomüll'; // Braune Tonne
var i, Muell_JSON, Event2, Color = 0;
for (i = 1; i <= 4; i++) {
if (!existsState('0_userdata.0.Abfallkalender.' + parseFloat(i) + '.date')) {
log(i + '.date nicht vorhanden, wurde erstellt');
createState('0_userdata.0.Abfallkalender.' + parseFloat(i) + '.date', '',
{
name: parseFloat(i) + '.date',
role: 'state',
type: 'string',
read: true,
write: true,
def: ''
});
};
if (!existsState('0_userdata.0.Abfallkalender.' + parseFloat(i) + '.event')) {
log(i + '.event nicht vorhanden, wurde erstellt');
createState('0_userdata.0.Abfallkalender.' + parseFloat(i) + '.event', '',
{
name: parseFloat(i) + '.event',
role: 'state',
type: 'string',
read: true,
write: true,
def: ''
});
};
if (!existsState('0_userdata.0.Abfallkalender.' + parseFloat(i) + '.color')) {
log(i + '.color nicht vorhanden, wurde erstellt');
createState('0_userdata.0.Abfallkalender.' + parseFloat(i) + '.color', 0,
{
name: parseFloat(i) + '.color',
role: 'state',
type: 'number',
read: true,
write: true,
def: 0
});
};
}
function subsequenceFromStartLast(sequence, at1) {
var start = at1;
var end = sequence.length;
return sequence.slice(start, end);
}
on({ id: idAbfalliCal + '.data.table', change: "ne" }, async function () {
for (i = 0; i <= 3; i++) {
Muell_JSON = getState(idAbfalliCal + '.data.table').val;
setStateDelayed((['0_userdata.0.Abfallkalender.', parseFloat(i) + 1, '.date'].join('')), getAttr(Muell_JSON, (String(i) + '.date')), false, parseInt(((0) || "").toString(), 10), false);
Event2 = subsequenceFromStartLast(getAttr(Muell_JSON, (String(i) + '.event')), idZeichenLoeschen);
setStateDelayed((['0_userdata.0.Abfallkalender.', parseFloat(i) + 1, '.event'].join('')), Event2, false, parseInt(((0) || "").toString(), 10), false);
if (Event2 == idRestmuellName) {
Color = 33840;
} else if (Event2 == idBioabfaelleName) {
Color = 2016;
} else if (Event2 == idPappePapierName) {
Color = 31;
} else if (Event2 == idWertstoffName) {
Color = 65504;
}
setStateDelayed((['0_userdata.0.Abfallkalender.', parseFloat(i) + 1, '.color'].join('')), Color, false, parseInt(((0) || "").toString(), 10), false);
}
});

View File

@@ -1,224 +0,0 @@
/*
* @author 2023 @tt-tom
*
* Version 5.1.1
*
* Das Script erstellt die Datenpunkte und Alias für den Abfallkalender im Sonoff NSPanel
* Es wird der iCal Adapter benötigt und eine URL mit Terminen vom Entsorger bzw. eine .ics-Datei mit den Terminen.
* Das Script triggert auf dem bereitgestellten JSON im iCal adapter und füllt die 0_userdata.0 Datenpunkte
* Weitere Informationen findest du in der FAQ auf Github https://github.com/joBr99/nspanel-lovelace-ui/wiki
*
* changelog
* - 06.12.2023 - v5.0.2 add custom name for trashtype
* - 06.12.2023 - v5.1.0 Refactoring
* - 22.01.2024 - v5.1.1 Add tow Events more
*
*
*/
const idTrashData: string = 'ical.0.data.table'; // Datenpunkt mit Daten im JSON Format
const idUserdataAbfallVerzeichnis: string = '0_userdata.0.Abfallkalender'; // Name des Datenpunktverzeichnis unter 0_userdata.0 -> Strandard = 0_userdata.0.Abfallkalender
const idAliasPanelVerzeichnis: string = 'alias.0.NSPanel.allgemein'; //Name PanelVerzeichnis unter alias.0. Standard = alias.0.NSPanel.1
const idAliasAbfallVerzeichnis: string = 'Abfall'; //Name Verzeichnis unterhalb der idPanelverzeichnis Standard = Abfall
const anzahlZeichenLoeschen: number = 14; // x Zeichen links vom String abziehen, wenn vor dem Eventname noch Text steht z.B. Strassenname; Standard = 0
const jsonEventName1: string = 'Hausmüll'; // Vergleichstring für Schwarze Tonne
const customEventName1: string = ''; // benutzerdefinierter Text für schwarze Tonne
const jsonEventName2: string = 'Gelber Sack'; // Vergleichstring für Gelbe Tonne / Sack
const customEventName2: string = ''; // benutzerdefinierter Text für gelbe Tonne
const jsonEventName3: string = 'Papier'; // Vergleichstring für Blaue Tonne
const customEventName3: string = ''; // benutzerdefinierter Text für blaue Tonne
const jsonEventName4: string = 'Biomüll'; // Vergleichstring für Braune Tonne
const customEventName4: string = ''; // benutzerdefinierter Text für braune Tonne
const jsonEventName5: string = 'Treppe'; // Vergleichstring für Event 5
const customEventName5: string = 'Besen schwingen'; // benutzerdefinierter Text für Event 5
const jsonEventName6: string = ''; // Vergleichstring für Event 6
const customEventName6: string = ''; // benutzerdefinierter Text für Event 6
const Debug: boolean = false;
// ------------------------- Trigger zum füllen der 0_userdata Datenpunkte aus dem json vom ical Adapter -------------------------------
// Trigger auf JSON Datenpunkt
on({ id: idTrashData, change: 'ne' }, async function () {
JSON_auswerten();
});
// ------------------------------------- Ende Trigger ------------------------------------
// ------------------------------------- Funktion JSON auswerten und DP füllen -------------------------------
async function JSON_auswerten() {
try {
let trashJSON: any;
let instanzName: any;
let eventName: string;
let eventDatum: string;
let eventStartdatum: string;
let farbNummer: number = 0;
let farbString: string;
let abfallNummer: number = 1;
trashJSON = getState(idTrashData).val;
instanzName = idTrashData.split('.');
if (Debug) log('Rohdaten von Instanz ' + instanzName[0] + ': ' + JSON.stringify(trashJSON), 'info')
if (Debug) log('Anzahl Trash - Daten: ' + trashJSON.length, 'info');
for (let i = 0; i < trashJSON.length; i++) {
if (abfallNummer === 7) {
if (Debug) log('Alle Abfall-Datenpunkte gefüllt', 'warn');
break;
}
log('Daten vom ical Adapter werden ausgewertet', 'info');
eventName = getAttr(trashJSON, (String(i) + '.event')).slice(anzahlZeichenLoeschen, getAttr(trashJSON, (String(i) + '.event')).length);
// Leerzeichen vorne und hinten löschen
eventName = eventName.trimEnd();
eventName = eventName.trimStart();
eventDatum = getAttr(trashJSON, (String(i) + '.date'));
eventStartdatum = getAttr(trashJSON, (String(i) + '._date'));
let d: Date = currentDate();
let d1: Date = new Date(eventStartdatum);
if (Debug) log('--------- Nächster Termin wird geprüft ---------', 'info');
//if (Debug) log(d + ' ' + d1, 'info');
if (Debug) log('Startdatum UTC: ' + eventStartdatum, 'info');
if (Debug) log('Datum: ' + eventDatum, 'info');
if (Debug) log('Event: ' + eventName, 'info');
if (Debug) log('Kontrolle Leerzeichen %' + eventName + '%', 'info');
if (d.getTime() <= d1.getTime()) {
if ((eventName == jsonEventName1) || (eventName == jsonEventName2) || (eventName == jsonEventName3) || (eventName == jsonEventName4) || (eventName == jsonEventName5) || (eventName == jsonEventName6)) {
switch (eventName) {
case jsonEventName1:
farbNummer = 33840;
if (customEventName1 != '') {
eventName = customEventName1;
if (Debug) log('Event customName: ' + eventName, 'info');
};
break;
case jsonEventName2:
farbNummer = 65504;
if (customEventName2 != '') {
eventName = customEventName2;
if (Debug) log('Event customName: ' + eventName, 'info');
};
break;
case jsonEventName3:
farbNummer = 31;
if (customEventName3 != '') {
eventName = customEventName3
if (Debug) log('Event customName: ' + eventName, 'info');
};
break;
case jsonEventName4:
farbNummer = 2016;
if (customEventName4 != '') {
eventName = customEventName4;
if (Debug) log('Event customName: ' + eventName, 'info');
};
break;
case jsonEventName5:
farbNummer = 2016;
if (customEventName5 != '') {
eventName = customEventName5;
if (Debug) log('Event customName: ' + eventName, 'info');
};
break;
case jsonEventName6:
farbNummer = 2016;
if (customEventName6 != '') {
eventName = customEventName6
if (Debug) log('Event customName: ' + eventName, 'info');
};
break;
}
//if (farbString != undefined) farbNummer = rgb_dec565(hex_rgb(farbString));
setState(idUserdataAbfallVerzeichnis + '.' + String(abfallNummer) + '.date', eventDatum);
setState(idUserdataAbfallVerzeichnis + '.' + String(abfallNummer) + '.event', eventName);
setState(idUserdataAbfallVerzeichnis + '.' + String(abfallNummer) + '.color', farbNummer);
//if (Debug) log('farbString: ' + farbString + ' farbNummer: ' + farbNummer, 'info');
if (Debug) log('Abfallnummer: ' + abfallNummer, 'info');
abfallNummer += 1
} else {
if (Debug) log('Kein Abfalltermin => Event passt mit keinem Abfallnamen überein.', 'warn');
}
} else {
if (Debug) log('Termin liegt vor dem heutigen Tag', 'warn');
}
}
} catch (err) {
log('error at subscrption: ' + err.message, 'warn');
}
};
// ------------------------------------- Ende Funktion JSON ------------------------------
// ------------------------------------- Funktion zur Prüfung und Erstellung der Datenpunkte in 0_userdata.0 und alias.0 -----------------------
async function Init_Datenpunkte() {
try {
for (let i = 1; i <= 6; i++) {
if (existsObject(idUserdataAbfallVerzeichnis + '.' + String(i)) == false) {
log('Datenpunkt ' + idUserdataAbfallVerzeichnis + '.' + String(i) + ' werden angelegt', 'info')
await createStateAsync(idUserdataAbfallVerzeichnis + '.' + String(i) + '.date', '', { type: 'string' });
await createStateAsync(idUserdataAbfallVerzeichnis + '.' + String(i) + '.event', '', { type: 'string' });
await createStateAsync(idUserdataAbfallVerzeichnis + '.' + String(i) + '.color', 0, { type: 'number' });
setObject(idAliasPanelVerzeichnis + '.' + idAliasAbfallVerzeichnis, { type: 'device', common: { name: { de: 'Abfall', en: 'Trash' } }, native: {} });
setObject(idAliasPanelVerzeichnis + '.' + idAliasAbfallVerzeichnis + '.event' + String(i), { type: 'channel', common: { role: 'warning', name: { de: 'Ereignis ' + String(i), en: 'Event' + String(i) } }, native: {} });
await createAliasAsync(idAliasPanelVerzeichnis + '.' + idAliasAbfallVerzeichnis + '.event' + String(i) + '.TITLE', idUserdataAbfallVerzeichnis + '.' + String(i) + '.event', true, <iobJS.StateCommon>{ type: 'string', role: 'weather.title.short', name: { de: 'TITEL', en: 'TITLE' } });
await createAliasAsync(idAliasPanelVerzeichnis + '.' + idAliasAbfallVerzeichnis + '.event' + String(i) + '.LEVEL', idUserdataAbfallVerzeichnis + '.' + String(i) + '.color', true, <iobJS.StateCommon>{ type: 'number', role: 'value.warning', name: { de: 'LEVEL', en: 'LEVEL' } });
await createAliasAsync(idAliasPanelVerzeichnis + '.' + idAliasAbfallVerzeichnis + '.event' + String(i) + '.INFO', idUserdataAbfallVerzeichnis + '.' + String(i) + '.date', true, <iobJS.StateCommon>{ type: 'string', role: 'weather.title', name: { de: 'INFO', en: 'INFO' } });
log('Fertig', 'info')
} else {
log('Datenpunkt ' + idUserdataAbfallVerzeichnis + '.' + String(i) + ' vorhanden', 'info')
}
}
log('Startabfrage der Daten', 'info');
JSON_auswerten();
} catch (err) {
log('error at function Init_Datenpunkte: ' + err.message, 'warn');
}
}
Init_Datenpunkte();
// --------------------------- Ende Funktion Datenpunkte ------------------------------------------------
// --------------------------- Zusatzfuktionen -------------------------------------------------------------
function currentDate() {
let d: Date = new Date();
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
}
function rgb_dec565(rgb: RGB): number {
//return ((Math.floor(rgb.red / 255 * 31) << 11) | (Math.floor(rgb.green / 255 * 63) << 5) | (Math.floor(rgb.blue / 255 * 31)));
return ((rgb.red >> 3) << 11) | ((rgb.green >> 2)) << 5 | ((rgb.blue) >> 3);
}
function hex_rgb(colorhex: string): RGB {
let r = parseInt(colorhex.substring(1, 3), 16);
let g = parseInt(colorhex.substring(3, 5), 16);
let b = parseInt(colorhex.substring(5, 7), 16);
return { red: r, green: g, blue: b };
}
type RGB = {
red: number,
green: number,
blue: number
};
// -------------------- Ende Zudatzfunktionen --------------------------------------------------------------------------

View File

@@ -1,51 +0,0 @@
const sourceDP = 'alias.0.Wohnzimmer.Heizung.ACTUAL';
const targetDP = '0_userdata.0.Test.chartTest';
const rangeHours = 24;
const maxXAchsisTicks = 6;
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) {
sendTo(historyInstance, 'getHistory', {
id: sourceDP,
options: {
start: Date.now() - (60 * 60 * 1000 * rangeHours),
end: Date.now(),
count: rangeHours,
limit: rangeHours,
aggregate: 'average'
}
}, function (result) {
var cardChartString = "";
var stepXAchsis = rangeHours / maxXAchsisTicks;
for (var i = 0; i < rangeHours; i++){
var deltaHour = rangeHours - i;
var targetDate = new Date(Date.now() - (deltaHour * 60 * 60 * 1000));
//Check history items for requested hours
for (var j = 0, targetValue = 0; j < result.result.length; j++) {
var valueDate = new Date(result.result[j].ts);
var value = Math.round(result.result[j].val / factor * 10);
if (valueDate > targetDate){
if ((targetDate.getHours() % stepXAchsis) == 0){
cardChartString += targetValue + '^' + targetDate.getHours() + ':00' + '~';
} else {
cardChartString += targetValue + '~';
}
break;
} else {
targetValue = value;
}
}
}
cardChartString = cardChartString.substring(0,cardChartString.length-1);
if (existsState(targetDP) == false ) {
createState(targetDP, cardChartString, true, { type: 'string' });
} else {
setState(targetDP, cardChartString, true);
}
});
});

View File

@@ -1,78 +0,0 @@
const sourceDP = 'alias.0.Wohnzimmer.Heizung.ACTUAL';
const targetDP = '0_userdata.0.Test.chartTest';
const numberOfHoursAgo = 24; // Period of time in hours which shall be visualized
const xAxisTicksEveryM = 60; // Time after x axis gets a tick in minutes
const xAxisLabelEveryM = 240; // Time after x axis is labeled in minutes
const historyInstance = 'history.0';
const Debug = false;
const maxX = 1420;
const limitMeasurements = 35;
createState(targetDP, "", {
name: 'SensorGrid',
desc: 'Sensor Values [~<time>:<value>]*',
type: 'string',
role: 'value',
});
on({id: sourceDP, change: "any"}, async function (obj) {
sendTo(historyInstance, 'getHistory', {
id: sourceDP,
options: {
start: Date.now() - (numberOfHoursAgo * 60 * 60 * 1000 ), //Time in ms: hours * 60m * 60s * 1000ms
end: Date.now(),
count: limitMeasurements,
limit: limitMeasurements,
aggregate: 'average'
}
}, function (result) {
var ticksAndLabels = ""
var coordinates = "";
var cardLChartString = "";
let ticksAndLabelsList = []
var date = new Date();
date.setMinutes(0, 0, 0);
var ts = Math.round(date.getTime() / 1000);
var tsYesterday = ts - (numberOfHoursAgo * 3600);
for (var x = tsYesterday, i = 0; x < ts; x += (xAxisTicksEveryM * 60), i += xAxisTicksEveryM)
{
if (i % xAxisLabelEveryM)
{
ticksAndLabelsList.push(i);
} else
{
var currentDate = new Date(x * 1000);
// Hours part from the timestamp
var hours = "0" + currentDate.getHours();
// Minutes part from the timestamp
var minutes = "0" + currentDate.getMinutes();
// Seconds part from the timestamp
var seconds = "0" + currentDate.getSeconds();
var formattedTime = hours.slice(-2) + ':' + minutes.slice(-2);
ticksAndLabelsList.push(String(i) + "^" + formattedTime);
}
}
ticksAndLabels = ticksAndLabelsList.join("+");
let list = [];
let offSetTime = Math.round(result.result[0].ts / 1000);
let counter = Math.round((result.result[result.result.length -1 ].ts / 1000 - offSetTime) / maxX);
for (var i = 0; i < result.result.length; i++)
{
var time = Math.round(((result.result[i].ts / 1000) - offSetTime) / counter);
var value = Math.round(result.result[i].val * 10);
if ((value != null) && (value != 0)){
list.push(time + ":" + value)
}
}
coordinates = list.join("~");
cardLChartString = ticksAndLabels + '~' + coordinates
setState(targetDP, cardLChartString, true);
if (Debug) console.log(cardLChartString);
});
});

View File

@@ -1,130 +0,0 @@
/**
* 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);
}
}

View File

@@ -1,54 +0,0 @@
/**
* generate an JSON for display Power-Card on NSPanel
* Source: https://github.com/joBr99/nspanel-lovelace-ui/wiki/ioBroker-Card-Definitionen-(Seiten)#cardpower-ab-ts-script-v341
* Version: 0.1 - L4rs
*/
schedule("* * * * *", function () {
// Definition der Datenpunkte für das JSON der POWER-Card und der anzuzeigenden Leistungswerte
var powerCardJson = "0_userdata.0.NSPanel.Energie.PowerCard",
pwr1 = "", // Batterie
pwr2 = Math.round(getState("mqtt.0.SmartHome.Energie.PV.openDTU.114180710360.0.power").val), // Solar
pwr3 = "", // Wind
pwr4 = "", // Verbraucher
pwr5 = Math.round(getState("hm-rpc.0.MEQ0706303.1.POWER").val), // Stromnetz
pwr6 = 0, // Auto
pwrHome = Math.round(pwr5 - pwr2); // Berechnung des Energiefluss anstelle eines Datenpunktes
// Definition der Keys im JSON
var keys = ["id", "value", "unit", "icon", "iconColor", "speed"];
// Definition der "Kacheln", inkl. StandardIcon. Es können alle Icon aus dem Iconmapping genutzt werden.
// Kacheln die nicht genutzt werden sollen, müssen wie z.b. item1 formatiert sein
var home = [0, pwrHome, "W", "home-lightning-bolt-outline", 0]; // Icon home
var item1 = [1, pwr1, "", "", 0, ""]; // Icon battery-charging-60
var item2 = [2, pwr2, "W", "solar-power-variant-outline", 3, pwr2 > 0 ? -2 : 0]; // Icon solar-power-variant
var item3 = [3, pwr3, "", "", 0, ""]; // Icon wind-turbine
var item4 = [4, pwr4, "", "", 0, ""]; // Icon shape
var item5 = [5, pwr5, "W", "transmission-tower", 10, 10]; // Icon transmission-tower
var item6 = [6, pwr6, "kW", "car-electric-outline", 5, 0]; // Icon car
/**
* JSON generieren und in den Datenpunkt schreiben,
*
* --- ab hier keine Änderungen mehr ---
*/
function func(tags, values) {
return Object.assign(
...tags.map((element, index) => ({ [element]: values[index] }))
);
}
setState(
powerCardJson,
JSON.stringify([
func(keys, home),
func(keys, item1),
func(keys, item2),
func(keys, item3),
func(keys, item4),
func(keys, item5),
func(keys, item6),
])
);
});

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -2564,11 +2564,7 @@
"crossfade":{
"en-US":"Crossfade",
"de-DE":"Überblenden"
},
"tools":{
"en-US":"Tools",
"de-DE":"Tools"
},
},
"speaker":{
"en-US":"Speakerlist",
"de-DE":"Wiedergabegeräte",

View File

@@ -2701,13 +2701,5 @@
"scriptname":{
"en-US":"Script name",
"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"
}
}

View File

@@ -12,7 +12,7 @@ repo_name: jobr99/nspanel-lovelace-ui
repo_url: https://github.com/jobr99/nspanel-lovelace-ui
edit_uri: ""
copyright: "Copyright &copy; 2026 Johannes Braun"
copyright: "Copyright &copy; 2023 Johannes Braun"
docs_dir: docs
@@ -42,6 +42,8 @@ extra_css:
extra:
analytics:
provider: custom
version:
provider: mike
markdown_extensions:
- admonition
@@ -61,6 +63,7 @@ markdown_extensions:
plugins:
- search:
lang: en
- mkdocs-video
nav:
- "Overview": index.md
@@ -72,7 +75,6 @@ nav:
- "FAQ": faq.md
- "Configuration - apps.yaml (Home Assistant)":
- "Overview": config-overview.md
#- "Migration to Standalone Rewrite": config-migration-standalone.md
- "Screensaver": config-screensaver.md
- "Cards":
- "Entities Card": card-entities.md

View File

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

View File

@@ -209,11 +209,11 @@ class HAEntity(panel_cards.Entity):
forecast = libs.home_assistant.execute_script(
entity_name=self.entity_id,
domain='weather',
service="get_forecasts",
service="get_forecast",
service_data={
'type': forecast_type
}
).get(self.entity_id,{}).get("forecast", [])
).get("forecast", [])
if len(forecast) > pos:
forcast_pos = forecast[pos]
forcast_condition = forcast_pos.get("condition", "")
@@ -475,7 +475,7 @@ class AlarmCard(HACard):
main_entity = self.entities[0]
main_entity.render()
logging.debug("Alarm card state for '%s': %s", main_entity.entity_id, main_entity.state)
print(main_entity.state)
icon = get_icon_char("shield-off")
color = rgb_dec565([255,255,255])

View File

@@ -8,8 +8,6 @@ def wait_for_ha_cache():
while time.time() < mustend:
if len(libs.home_assistant.home_assistant_entity_state_cache) == 0:
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)
def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, screenBrightness, sleepOverride, return_involved_entities=False):
@@ -22,32 +20,27 @@ def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, scr
dimmode = sleepBrightness
elif isinstance(sleepBrightness, list):
logging.error("list style config for sleepBrightness no longer supported")
#elif sleepBrightness.startswith("ha:"):
# time.sleep(1)
# dimmode = int(float(libs.home_assistant.get_template(sleepBrightness)[3:]))
# involved_entities.extend(libs.home_assistant.get_template_listener_entities(sleepBrightness))
elif sleepBrightness.startswith("ha:"):
time.sleep(1)
dimmode = int(float(libs.home_assistant.get_template(sleepBrightness)[3:]))
involved_entities.extend(libs.home_assistant.get_template_listener_entities(sleepBrightness))
elif libs.home_assistant.is_existent(sleepBrightness):
involved_entities.append(sleepBrightness)
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)
dimmode = int(float(libs.home_assistant.get_entity_data(sleepBrightness).get('state', 10)))
if screenBrightness:
if isinstance(screenBrightness, int):
dimValueNormal = screenBrightness
elif isinstance(screenBrightness, list):
logging.error("list style config for screenBrightness no longer supported")
#elif screenBrightness.startswith("ha:"):
# time.sleep(1)
# dimValueNormal = int(float(libs.home_assistant.get_template(screenBrightness)[3:]))
# involved_entities.extend(libs.home_assistant.get_template_listener_entities(screenBrightness))
elif screenBrightness.startswith("ha:"):
time.sleep(1)
dimValueNormal = int(float(libs.home_assistant.get_template(screenBrightness)[3:]))
involved_entities.extend(libs.home_assistant.get_template_listener_entities(screenBrightness))
elif libs.home_assistant.is_existent(screenBrightness):
involved_entities.append(screenBrightness)
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)
dimValueNormal = int(float(libs.home_assistant.get_entity_data(screenBrightness).get('state', 100)))
# force sleep brightness to zero in case sleepTracking is active
if sleepTracking:
if libs.home_assistant.is_existent(sleepTracking):
@@ -71,13 +64,12 @@ def calculate_dim_values(sleepTracking, sleepTrackingZones, sleepBrightness, scr
else:
return dimmode, dimValueNormal
def handle_buttons(entity_id, btype, value, entity_config=None, action_context=None):
action_context = action_context or {}
def handle_buttons(entity_id, btype, value, entity_config=None):
match btype:
case 'button':
button_press(entity_id, value, action_context=action_context)
button_press(entity_id, value)
case 'OnOff':
on_off(entity_id, value, action_context=action_context)
on_off(entity_id, value)
case 'number-set':
if entity_id.startswith('fan'):
attr = libs.home_assistant.get_entity_data(entity_id).get('attributes', [])
@@ -85,7 +77,7 @@ def handle_buttons(entity_id, btype, value, entity_config=None, action_context=N
service_data = {
"value": int(value)
}
call_ha_service(entity_id, "set_value", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "set_value", service_data=service_data)
case 'up' | 'stop' | 'down' | 'tiltOpen' | 'tiltStop' | 'tiltClose' | 'media-next' | 'media-back' | 'media-pause' | 'timer-cancel' | 'timer-pause' | 'timer-finish':
action_service_mapping = {
'up': 'open_cover',
@@ -102,37 +94,37 @@ def handle_buttons(entity_id, btype, value, entity_config=None, action_context=N
'timer-finish': 'finish',
}
service = action_service_mapping[btype]
call_ha_service(entity_id, service, action_context=action_context)
call_ha_service(entity_id, service)
case 'timer-start':
if value:
service_data = {
"duration": value
}
call_ha_service(entity_id, "start", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "start", service_data=service_data)
else:
call_ha_service(entity_id, "start", action_context=action_context)
call_ha_service(entity_id, "start")
case 'positionSlider':
service_data = {
"position": int(value)
}
call_ha_service(entity_id, "set_cover_position", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "set_cover_position", service_data=service_data)
case 'tiltSlider':
service_data = {
"tilt_position": int(value)
}
call_ha_service(entity_id, "set_cover_tilt_position", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "set_cover_tilt_position", service_data=service_data)
case 'media-OnOff':
state = libs.home_assistant.get_entity_data(entity_id).get('state', '')
if state == "off":
call_ha_service(entity_id, "turn_on", action_context=action_context)
call_ha_service(entity_id, "turn_on")
else:
call_ha_service(entity_id, "turn_off", action_context=action_context)
call_ha_service(entity_id, "turn_off")
case 'media-shuffle':
suffle = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get('shuffle')
service_data = {
"shuffle": not suffle
}
call_ha_service(entity_id, "set_value", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "set_value", service_data=service_data)
case 'volumeSlider':
pos = int(value)
# HA wants to have this value between 0 and 1 as float
@@ -140,12 +132,12 @@ def handle_buttons(entity_id, btype, value, entity_config=None, action_context=N
service_data = {
"volume_level": pos
}
call_ha_service(entity_id, "volume_set", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "volume_set", service_data=service_data)
case 'speaker-sel':
service_data = {
"volume_level": value
}
call_ha_service(entity_id, "select_source", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "select_source", service_data=service_data)
# for light detail page
case 'brightnessSlider':
# scale 0-100 to ha brightness range
@@ -153,7 +145,7 @@ def handle_buttons(entity_id, btype, value, entity_config=None, action_context=N
service_data = {
"brightness": brightness
}
call_ha_service(entity_id, "turn_on", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "turn_on", service_data=service_data)
case 'colorTempSlider':
attr = libs.home_assistant.get_entity_data(entity_id).get('attributes', [])
min_mireds = attr.get("min_mireds")
@@ -163,19 +155,19 @@ def handle_buttons(entity_id, btype, value, entity_config=None, action_context=N
service_data = {
"color_temp": color_val
}
call_ha_service(entity_id, "turn_on", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "turn_on", service_data=service_data)
case 'colorWheel':
value = value.split('|')
color = pos_to_color(int(value[0]), int(value[1]), int(value[2]))
service_data = {
"rgb_color": color
}
call_ha_service(entity_id, "turn_on", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "turn_on", service_data=service_data)
case 'disarm' | 'arm_home' | 'arm_away' | 'arm_night' | 'arm_vacation':
service_data = {
"code": value
}
call_ha_service(entity_id, f"alarm_{btype}", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, f"alarm_{btype}", service_data=service_data)
case 'mode-preset_modes' | 'mode-swing_modes' | 'mode-fan_modes':
attr = libs.home_assistant.get_entity_data(entity_id).get('attributes', [])
mapping = {
@@ -190,7 +182,7 @@ def handle_buttons(entity_id, btype, value, entity_config=None, action_context=N
service_data = {
mapping[btype][:-1]: mode
}
call_ha_service(entity_id, f"set_{mapping[btype][:-1]}", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, f"set_{mapping[btype][:-1]}", service_data=service_data)
case 'mode-input_select' | 'mode-select':
options = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get("options", [])
if options:
@@ -198,7 +190,7 @@ def handle_buttons(entity_id, btype, value, entity_config=None, action_context=N
service_data = {
"option": option
}
call_ha_service(entity_id, "select_option", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "select_option", service_data=service_data)
case 'mode-media_player':
options = libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get("source_list", [])
if options:
@@ -206,7 +198,7 @@ def handle_buttons(entity_id, btype, value, entity_config=None, action_context=N
service_data = {
"source": option
}
call_ha_service(entity_id, "select_source", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "select_source", service_data=service_data)
case 'mode-light':
options = entity_config.get("effectList", libs.home_assistant.get_entity_data(entity_id).get('attributes', []).get("effect_list", []))
if options:
@@ -214,13 +206,13 @@ def handle_buttons(entity_id, btype, value, entity_config=None, action_context=N
service_data = {
"effect": option
}
call_ha_service(entity_id, "turn_on", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "turn_on", service_data=service_data)
case 'tempUpd':
temp = int(value)/10
service_data = {
"temperature": temp
}
call_ha_service(entity_id, "set_temperature", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "set_temperature", service_data=service_data)
case 'tempUpdHighLow':
value = value.split("|")
temp_high = int(value[0])/10
@@ -229,67 +221,47 @@ def handle_buttons(entity_id, btype, value, entity_config=None, action_context=N
"target_temp_high": temp_high,
"target_temp_low": temp_low,
}
call_ha_service(entity_id, "set_temperature", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "set_temperature", service_data=service_data)
case 'hvac_action':
service_data = {
"hvac_mode": value
}
call_ha_service(entity_id, "set_hvac_mode", service_data=service_data, action_context=action_context)
call_ha_service(entity_id, "set_hvac_mode", service_data=service_data)
case _:
logging.error("Not implemented: %s", btype)
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 {}
def call_ha_service(entity_id, service, service_data = {}):
etype = entity_id.split(".")[0]
ok = libs.home_assistant.call_service(
libs.home_assistant.call_service(
entity_name=entity_id,
domain=etype,
service=service,
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, action_context=None):
def button_press(entity_id, value):
etype = entity_id.split(".")[0]
match etype:
case 'scene' | 'script':
call_ha_service(entity_id, "turn_on", action_context=action_context)
call_ha_service(entity_id, "turn_on")
case 'light' | 'switch' | 'input_boolean' | 'automation' | 'fan':
call_ha_service(entity_id, "toggle", action_context=action_context)
call_ha_service(entity_id, "toggle")
case 'lock':
state = libs.home_assistant.get_entity_data(entity_id).get('state', '')
if state == "locked":
call_ha_service(entity_id, "unlock", action_context=action_context)
call_ha_service(entity_id, "unlock")
else:
call_ha_service(entity_id, "lock", action_context=action_context)
call_ha_service(entity_id, "lock")
case 'button' | 'input_button':
call_ha_service(entity_id, "press", action_context=action_context)
call_ha_service(entity_id, "press")
case 'input_select' | 'select':
call_ha_service(entity_id, "select_next", action_context=action_context)
call_ha_service(entity_id, "select_next")
case 'vacuum':
state = libs.home_assistant.get_entity_data(entity_id).get('state', '')
if state == "docked":
call_ha_service(entity_id, "start", action_context=action_context)
call_ha_service(entity_id, "start")
else:
call_ha_service(entity_id, "return_to_base", action_context=action_context)
call_ha_service(entity_id, "return_to_base")
case _:
logging.error("buttonpress for entity type %s not implemented", etype)
@@ -297,14 +269,14 @@ def button_press(entity_id, value, action_context=None):
# apis.ha_api.call_service(entity_id.replace(
# 'service.', '', 1).replace('.', '/', 1), **entity_config.data)
def on_off(entity_id, value, action_context=None):
def on_off(entity_id, value):
etype = entity_id.split(".")[0]
match etype:
case 'light' | 'switch' | 'input_boolean' | 'automation' | 'fan':
service = "turn_off"
if value == "1":
service = "turn_on"
call_ha_service(entity_id, service, action_context=action_context)
call_ha_service(entity_id, service)
case _:
logging.error(
"Control action on_off not implemented for %s", entity_id)

View File

@@ -14,9 +14,8 @@ next_id = 0
request_all_states_id = 0
ws_connected = False
home_assistant_entity_state_cache = {}
template_cache = {}
response_buffer = {}
nspanel_event_handler = None
template_cache = {}
response_buffer = {}
ON_CONNECT_HANDLER = None
@@ -45,64 +44,47 @@ def register_on_disconnect_handler(handler):
ON_DISCONNECT_HANDLER = handler
def on_message(ws, message):
global auth_ok, request_all_states_id, home_assistant_entity_state_cache, response_buffer, template_cache
try:
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()
elif message_type == "auth_ok":
auth_ok = True
logging.info("Home Assistant auth OK. Requesting existing states.")
subscribe_to_events()
_get_all_states()
if ON_CONNECT_HANDLER is not None:
ON_CONNECT_HANDLER()
# for templates
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"]]] = {
"result": event.get("result"),
"listener-entities": listeners.get("entities", [])
}
elif message_type == "event" and json_msg.get("event", {}).get("event_type") == "state_changed":
event_data = json_msg.get("event", {}).get("data", {})
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)
# rerender template
for template, template_cache_entry in template_cache.items():
if entity_id in template_cache_entry.get("listener-entities", []):
cache_template(template)
elif message_type == "event" and json_msg.get("event", {}).get("event_type") == "esphome.nspanel.data":
event_data = json_msg.get("event", {}).get("data", {})
device_id = event_data.get("device_id")
custom_recv = event_data.get("CustomRecv")
if nspanel_event_handler is None:
logging.debug("No NsPanel event handler registered; dropping event for device '%s'", device_id)
return
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
else:
if json_msg.get("id") in response_buffer and json_msg.get("result"):
response_buffer[json_msg["id"]] = json_msg["result"]
return None # Ignore success result messages
else:
logging.debug(message)
def on_message(ws, message):
global auth_ok, request_all_states_id, home_assistant_entity_state_cache, response_buffer, template_cache
json_msg = json.loads(message)
if json_msg["type"] == "auth_required":
authenticate_client()
elif json_msg["type"] == "auth_ok":
auth_ok = True
logging.info("Home Assistant auth OK. Requesting existing states.")
subscribe_to_events()
_get_all_states()
if ON_CONNECT_HANDLER is not None:
ON_CONNECT_HANDLER()
# for templates
elif json_msg["type"] == "event" and json_msg["id"] in response_buffer:
template_cache[response_buffer[json_msg["id"]]] = {
"result": json_msg["event"]["result"],
"listener-entities": json_msg["event"]["listeners"]["entities"]
}
elif json_msg["type"] == "event" and json_msg["event"]["event_type"] == "state_changed":
entity_id = json_msg["event"]["data"]["entity_id"]
home_assistant_entity_state_cache[entity_id] = json_msg["event"]["data"]["new_state"]
send_entity_update(entity_id)
# rerender template
for template, template_cache_entry in template_cache.items():
if entity_id in template_cache_entry.get("listener-entities", []):
cache_template(template)
elif json_msg["type"] == "event" and json_msg["event"]["event_type"] == "esphome.nspanel.data":
nspanel_data_callback(json_msg["event"]["data"]["device_id"], json_msg["event"]["data"]["CustomRecv"])
elif json_msg["type"] == "result" and not json_msg["success"]:
logging.error("Failed result: ")
logging.error(json_msg)
elif json_msg["type"] == "result" and json_msg["success"]:
if json_msg["id"] == request_all_states_id:
for entity in json_msg["result"]:
home_assistant_entity_state_cache[entity["entity_id"]] = entity
else:
if json_msg["id"] in response_buffer and json_msg.get("result"):
response_buffer[json_msg["id"]] = json_msg["result"]
return None # Ignore success result messages
else:
logging.debug(message)
def _ws_connection_open(ws):
@@ -113,24 +95,20 @@ def _ws_connection_open(ws):
ON_CONNECT_HANDLER()
def _ws_connection_close(ws, close_status_code, close_msg):
global ws_connected
ws_connected = False
logging.error(
"WebSocket connection closed (status=%s, message=%s)",
close_status_code,
close_msg,
)
if ON_DISCONNECT_HANDLER is not None:
ON_DISCONNECT_HANDLER()
def _ws_connection_close(ws, close_status_code, close_msg):
global ws_connected
ws_connected = False
logging.error("WebSocket connection closed!")
if ON_DISCONNECT_HANDLER is not None:
ON_DISCONNECT_HANDLER()
def connect():
Thread(target=_do_connection, daemon=True).start()
def _do_connection():
global home_assistant_url, ws, settings
def _do_connection():
global home_assistant_url, ws, settings
ws_url = home_assistant_url.replace(
"https://", "wss://").replace("http://", "ws://")
if settings["is_addon"]:
@@ -139,15 +117,12 @@ def _do_connection():
ws_url += "/api/websocket"
ws = websocket.WebSocketApp(F"{ws_url}", on_message=on_message,
on_open=_ws_connection_open, on_close=_ws_connection_close)
while True:
logging.info(F"Connecting to Home Assistant at {ws_url}")
try:
ws.close()
time.sleep(1)
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
except Exception:
logging.exception("WebSocket connection loop failed")
time.sleep(10)
while True:
logging.info(F"Connecting to Home Assistant at {ws_url}")
ws.close()
time.sleep(1)
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
time.sleep(10)
def authenticate_client():
@@ -169,9 +144,9 @@ def subscribe_to_events():
}
send_message(json.dumps(msg))
def subscribe_to_nspanel_events(nsp_callback):
global next_id, nspanel_event_handler
nspanel_event_handler = nsp_callback
def subscribe_to_nspanel_events(nsp_callback):
global next_id, nspanel_data_callback
nspanel_data_callback = nsp_callback
msg = {
"id": next_id,
"type": "subscribe_events",
@@ -193,13 +168,11 @@ def send_entity_update(entity_id):
global on_ha_update
on_ha_update(entity_id)
def nspanel_data_callback(device_id, msg):
if nspanel_event_handler is None:
logging.debug("NsPanel callback invoked before handler was registered")
return
nspanel_event_handler(device_id, msg)
def nspanel_data_callback(device_id, msg):
global nspanel_data_callback
nspanel_data_callback(device_id, msg)
def call_service(entity_name: str, domain: str, service: str, service_data: dict) -> bool:
def call_service(entity_name: str, domain: str, service: str, service_data: dict) -> bool:
global next_id
try:
msg = {
@@ -214,12 +187,9 @@ def call_service(entity_name: str, domain: str, service: str, service_data: dict
}
send_message(json.dumps(msg))
return True
except Exception:
logging.exception(
"Failed to call Home Assistant service: %s.%s for %s",
domain, service, entity_name
)
return False
except Exception as e:
logging.exception("Failed to call Home Assisatant service.")
return False
def send_msg_to_panel(service: str, service_data: dict) -> bool:
global next_id
@@ -233,9 +203,9 @@ def send_msg_to_panel(service: str, service_data: dict) -> bool:
}
send_message(json.dumps(msg))
return True
except Exception:
logging.exception("Failed to call Home Assistant panel service: %s", service)
return False
except Exception as e:
logging.exception("Failed to call Home Assisatant service.")
return False
def execute_script(entity_name: str, domain: str, service: str, service_data: dict) -> str:
global next_id, response_buffer
@@ -271,13 +241,13 @@ def execute_script(entity_name: str, domain: str, service: str, service_data: di
else:
return response_buffer[call_id]["response"]
raise TimeoutError("Did not recive respose in time to HA script call")
except Exception:
logging.exception("Failed to call Home Assistant script: %s.%s", domain, service)
return {}
except Exception as e:
logging.exception("Failed to call Home Assisatant script.")
return {}
def cache_template(template):
if not template:
raise ValueError("Invalid template")
def cache_template(template):
if not template:
raise Exception("Invalid template")
global next_id, response_buffer
try:
call_id = next_id
@@ -289,9 +259,9 @@ def cache_template(template):
}
send_message(json.dumps(msg))
return True
except Exception:
logging.exception("Failed to render template.")
return False
except Exception as e:
logging.exception("Failed to render template.")
return False
def get_template(template):
global template_cache
@@ -329,12 +299,7 @@ def is_existent(entity_id: str):
return False
def send_message(message):
global ws, next_id
try:
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")
def send_message(message):
global ws, next_id
next_id += 1
ws.send(message)

View File

@@ -25,76 +25,53 @@ last_settings_file_mtime = 0
mqtt_connect_time = 0
has_sent_reload_command = False
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)
logging.basicConfig(level=logging.DEBUG)
def on_ha_update(entity_id):
global panel_in_queues
# send HA updates to all panels
for queue in panel_in_queues.values():
try:
queue.put(("HA:", entity_id))
except Exception:
logging.exception("Failed to enqueue HA update for entity '%s'", entity_id)
queue.put(("HA:", entity_id))
def on_ha_panel_event(device_id, msg):
global panel_in_queues
if device_id in panel_in_queues.keys():
queue = panel_in_queues[device_id]
try:
queue.put(("MQTT:", msg))
except Exception:
logging.exception("Failed to enqueue panel event for device '%s'", device_id)
queue.put(("MQTT:", msg))
def process_output_to_panel():
while True:
try:
msg = panel_out_queue.get()
service = msg[0] + "_nspanelui_api_call"
service_data = {
"data": msg[1],
"command": 2
msg = panel_out_queue.get()
#client.publish(msg[0], msg[1])
#apis.ha_api.call_service(service="esphome/" + self._api_panel_name + "_nspanelui_api_call", command=2, data=msg)
service = msg[0] + "_nspanelui_api_call"
service_data = {
"data": msg[1],
"command":2
}
libs.home_assistant.send_msg_to_panel(
service=service,
service_data=service_data
)
except Exception:
logging.exception("Failed to process outgoing panel message")
libs.home_assistant.send_msg_to_panel(
service = service,
service_data = service_data
)
def connect():
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:
MqttManager(settings, panel_out_queue, panel_in_queues)
else:
logging.info("MQTT values not configured, will not connect.")
# MQTT Connected, start APIs if configured
if ha_is_configured:
if settings["home_assistant_address"] != "" and settings["home_assistant_token"] != "":
libs.home_assistant.init(settings, on_ha_update)
libs.home_assistant.connect()
else:
logging.info("Home Assistant values not configured, will not connect.")
return
wait_seconds = 0
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)
if settings.get("use_ha_api"):
libs.home_assistant.subscribe_to_nspanel_events(on_ha_panel_event)
@@ -120,20 +97,13 @@ def setup_panels():
panel_thread.start()
def panel_thread_target(queue_in, 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
panel = LovelaceUIPanel(name, settings_panel, queue_out)
while True:
try:
msg = queue_in.get()
if msg[0] == "MQTT:":
panel.customrecv_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)
msg = queue_in.get()
if msg[0] == "MQTT:":
panel.customrecv_event_callback(msg[1])
elif msg[0] == "HA:":
panel.ha_event_callback(msg[1])
def get_config_file():
CONFIG_FILE = os.getenv('CONFIG_FILE')
@@ -147,27 +117,18 @@ def get_config(file):
try:
with open(file, 'r', encoding="utf8") as 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:
logging.error("Error while parsing YAML file: %s", file)
print ("Error while parsing YAML file:")
if hasattr(exc, 'problem_mark'):
if exc.context != None:
logging.error(
"Parser says\n%s\n%s %s\nPlease correct data and retry.",
str(exc.problem_mark), str(exc.problem), str(exc.context)
)
print (' parser says\n' + str(exc.problem_mark) + '\n ' +
str(exc.problem) + ' ' + str(exc.context) +
'\nPlease correct data and retry.')
else:
logging.error(
"Parser says\n%s\n%s\nPlease correct data and retry.",
str(exc.problem_mark), str(exc.problem)
)
print (' parser says\n' + str(exc.problem_mark) + '\n ' +
str(exc.problem) + '\nPlease correct data and retry.')
else:
logging.exception("Something went wrong while parsing yaml file")
print ("Something went wrong while parsing yaml file")
return False
if not settings.get("mqtt_username"):
@@ -211,14 +172,10 @@ def config_watch():
project_files.append(get_config_file())
handler = ConfigChangeEventHandler(project_files)
observer = Observer()
watch_path = os.path.dirname(get_config_file()) or "."
observer.schedule(handler, path=watch_path, recursive=True)
observer.schedule(handler, path=os.path.dirname(get_config_file()), recursive=True)
observer.start()
while True:
try:
time.sleep(1)
except Exception:
logging.exception("Config watch loop failed")
time.sleep(1)
def signal_handler(signum, frame):
logging.info(f"Received signal {signum}. Initiating restart...")
@@ -237,5 +194,4 @@ if __name__ == '__main__':
time.sleep(100)
else:
while True:
time.sleep(100)
time.sleep(100)

View File

@@ -19,6 +19,7 @@ class MqttManager:
self.client.username_pw_set(
settings["mqtt_username"], settings["mqtt_password"])
# Wait for connection
connection_return_code = 0
mqtt_server = settings["mqtt_server"]
mqtt_port = int(settings["mqtt_port"])
logging.info("Connecting to %s:%i as %s",
@@ -27,12 +28,9 @@ class MqttManager:
try:
self.client.connect(mqtt_server, mqtt_port, 5)
break # Connection call did not raise exception, connection is sucessfull
except Exception: # pylint: disable=broad-exception-caught
except: # pylint: disable=bare-except
logging.exception(
"Failed to connect to MQTT %s:%i. Will try again in 10 seconds.",
mqtt_server,
mqtt_port,
)
"Failed to connect to MQTT %s:%i. Will try again in 10 seconds. Code: %s", mqtt_server, mqtt_port, connection_return_code)
time.sleep(10.)
self.client.loop_start()
process_thread = threading.Thread(target=self.process_in_queue, args=(self.client, self.msg_in_queue))
@@ -40,52 +38,31 @@ class MqttManager:
process_thread.start()
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")
# subscribe to panelRecvTopic of each panel
for settings_panel in self.settings["nspanels"].values():
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)
client.subscribe(settings_panel["panelRecvTopic"])
def on_mqtt_message(self, client, userdata, msg):
try:
payload_text = msg.payload.decode('utf-8')
if payload_text == "":
logging.debug("Ignoring empty MQTT payload on topic: %s", msg.topic)
if msg.payload.decode() == "":
return
if msg.topic in self.msg_out_queue_list.keys():
data = json.loads(payload_text)
data = json.loads(msg.payload.decode('utf-8'))
if "CustomRecv" in data:
queue = self.msg_out_queue_list[msg.topic]
queue.put(("MQTT:", data["CustomRecv"]))
else:
logging.debug("JSON payload on topic '%s' has no 'CustomRecv' key", msg.topic)
else:
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
logging.exception("Unexpected error while processing MQTT message on topic: %s", msg.topic)
logging.exception("Something went wrong during processing of message:")
try:
logging.error(msg.payload.decode('utf-8'))
except Exception: # pylint: disable=broad-exception-caught
except: # pylint: disable=bare-except
logging.error(
"Something went wrong when processing the exception message, couldn't decode payload to utf-8.")
def process_in_queue(self, client, msg_in_queue):
while True:
try:
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")
msg = msg_in_queue.get()
client.publish(msg[0], msg[1])

View File

@@ -103,13 +103,10 @@ class LovelaceUIPanel:
libs.panel_cmd.page_type(self.msg_out_queue, self.sendTopic, "pageStartup")
def schedule_thread_target(self):
while True:
try:
self.schedule.exec_jobs()
except Exception:
logging.exception("Scheduler execution failed for panel '%s'", self.name)
time.sleep(1)
def schedule_thread_target(self):
while True:
self.schedule.exec_jobs()
time.sleep(1)
def update_time(self):
use_timezone = tz.gettz(self.settings["timeZone"])
@@ -203,15 +200,12 @@ class LovelaceUIPanel:
return card
return list(self.cards.values())[0]
def customrecv_event_callback(self, msg):
logging.debug("Recv Message from NsPanel (%s): %s", self.name, msg)
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
if msg[0] == "event":
if msg[1] == "startup":
def customrecv_event_callback(self, msg):
logging.debug("Recv Message from NsPanel (%s): %s", self.name, msg)
msg = msg.split(",")
# run action based on received command
if msg[0] == "event":
if msg[1] == "startup":
# TODO: Handle Update Messages
self.update_date()
self.update_time()
@@ -232,24 +226,13 @@ class LovelaceUIPanel:
self.render_current_page(switchPages=True)
if msg[1] == "renderCurrentPage":
self.render_current_page(requested=True)
if msg[1] == "buttonPress2":
if len(msg) < 4:
logging.error("Malformed buttonPress2 payload on '%s': %s", self.name, msg)
return
entity_id = msg[2]
if entity_id == "":
return
btype = msg[3]
value = msg[4] if len(msg) > 4 else None
entity_config = {}
action_context = {
"panel": self.name,
"btype": btype,
"value": value,
}
if btype == "bExit":
if entity_id in ["screensaver", "screensaver2"] and self.settings.get("screensaver").get("doubleTapToUnlock") and value == "1":
return
if msg[1] == "buttonPress2":
entity_id = msg[2]
btype = msg[3]
value = msg[4] if len(msg) > 4 else None
if btype == "bExit":
if entity_id in ["screensaver", "screensaver2"] and self.settings.get("screensaver").get("doubleTapToUnlock") and value == "1":
return
# in case privious_cards is empty add a default card
if len(self.privious_cards) == 0:
@@ -264,12 +247,12 @@ class LovelaceUIPanel:
return
# replace iid with real entity id
if entity_id.startswith("iid."):
iid = entity_id.split(".")[1]
for e in self.current_card.entities:
if e.iid == iid:
entity_id = e.entity_id
entity_config = e.config
if entity_id.startswith("iid."):
iid = entity_id.split(".")[1]
for e in self.current_card.entities:
if e.iid == iid:
entity_id = e.entity_id
entity_config = e.config
match btype:
case 'button':
@@ -286,51 +269,29 @@ class LovelaceUIPanel:
self.privious_cards.append(self.current_card)
self.current_card = self.searchCard(card_iid)
self.render_current_page(switchPages=True)
# send ha stuff to ha
case _:
ha_control.handle_buttons(
entity_id,
btype,
value,
entity_config=entity_config,
action_context=action_context,
)
# send ha stuff to ha
case _:
ha_control.handle_buttons(entity_id, btype, value, entity_config=entity_config)
case 'cardUnlock-unlock':
card_iid = entity_id.split(".")[1]
if int(self.current_card.config.get("pin")) == int(value):
self.privious_cards.append(self.current_card)
self.current_card = self.searchCard(card_iid)
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 _:
ha_control.handle_buttons(
entity_id,
btype,
value,
action_context=action_context,
)
case _:
ha_control.handle_buttons(entity_id, btype, value)
if msg[1] == "pageOpenDetail":
if len(msg) < 4:
logging.error("Malformed pageOpenDetail payload on '%s': %s", self.name, msg)
return
entity_id = msg[3]
effectList = None
# replace iid with real entity id
if entity_id.startswith("iid."):
iid = entity_id.split(".")[1]
for e in self.current_card.entities:
if e.iid == iid:
entity_id = e.entity_id
if entity_id.startswith("light"):
effectList = e.config.get("effectList")
if msg[1] == "pageOpenDetail":
entity_id = msg[3]
# replace iid with real entity id
if entity_id.startswith("iid."):
iid = entity_id.split(".")[1]
for e in self.current_card.entities:
if e.iid == iid:
entity_id = e.entity_id
effectList = None
if entity_id.startswith("light"):
effectList = e.config.get("effectList")
if msg[2] == "popupInSel": #entity_id.split(".")[0] in ['input_select', 'media_player']:
libs.panel_cmd.entityUpdateDetail2(self.msg_out_queue, self.sendTopic, detail_open(self.settings["locale"], msg[2], entity_id, msg[3], self.msg_out_queue, sendTopic=self.sendTopic, options_list=effectList))
else:

View File

@@ -1,4 +1,4 @@
paho-mqtt==1.6.1
paho-mqtt
pyyaml
websockets
websocket-client
@@ -7,4 +7,4 @@ python-dateutil
scheduler
babel
watchdog
jinja2
jinja2