Merge pull request #68 from joBr99/newConfigFormat

new config parseing
This commit is contained in:
joBr99
2022-03-25 15:05:09 +01:00
committed by GitHub
27 changed files with 940 additions and 907 deletions

View File

@@ -71,6 +71,8 @@ change the page type:
`pageType,popupNotify`
`pageType,screensaver`
### screensaver page
`weatherUpdate,? tMainIcon? tMainText? tMRIcon? tMR? tForecast1? tF1Icon? tForecast1Val? tForecast2? tF2Icon? tForecast2Val`
@@ -136,20 +138,27 @@ The following message can be used to update the content on the cardEntities Page
## Messages from Nextion Display
`event,buttonPress2,pageName,bNext`
`event,buttonPress2,pageName,bPrev`
`event,buttonPress2,pageName,bExit`
### startup page
`event,startup,version`
### screensaver page
`event,screensaverOpen`
`event,buttonPress2,screensaver,exit` - Touch Event on Screensaver
`event,screensaverOpen` - Screensaver has opened
### cardEntities Page
`event,*eventName*,*entityName*,*actionName*,*optionalValue*`
`event,pageOpen,0`
`event,buttonPress2,internalNameEntity,up`
`event,buttonPress2,internalNameEntity,down`
@@ -186,8 +195,6 @@ The following message can be used to update the content on the cardEntities Page
### cardThermo Page
`event,pageOpen,0`
`event,buttonPress2,*entityName*,tempUpd,*temperature*`
`event,buttonPress2,*entityName*,hvac_action,*hvac_action*`

View File

@@ -5,24 +5,11 @@ Program.s
int recvCrc=0
int payloadLength=0
int par0=0,par1=0
// landsspace orientation x has 480px and y has 320px xy limits todo: adjust xy values to something that fit's resulution
//Maximum values in directional change for Swipes beeing detected as swipe (diagonal swipes are invalid) (for one axis at a time)
int xLimit=125,yLimit=125
int ixLimit=-125,iyLimit=-125
//Minimum values for swipes, directional changes below theese values are ignored, because they could be unintended swipes
int xLimitMin=80,yLimitMin=80
int ixLimitMin=-80,iyLimitMin=-80
// Swipe Result Vars
int ycR=0,xcR=0
// Start End Swipe Touch Locations
int yc1=0,xc1=0,yc2=0,xc2=0
// sleep timeout in s
int sleepTimeout=20
int sleepValue=0
// dim value
int dimValue=40
// current page
int nPage=0
// fix touch offset
lcd_dev fffb 0002 0000 0020
page pageStartup

View File

@@ -324,14 +324,7 @@ Button bNext
Events
Touch Press Event
nPage=nPage+1
nPageDisp.val=nPage
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,cardAlarm,bNext"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
@@ -378,14 +371,7 @@ Button bPrev
Events
Touch Press Event
nPage=nPage-1
nPageDisp.val=nPage
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,cardAlarm,bPrev"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC

View File

@@ -1131,13 +1131,14 @@ Button bPrev
Events
Touch Press Event
nPage=nPage-1
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,cardEntities,"
if(bPrev.isbr==1)
{
tSend.txt+="bBack"
}else
{
tSend.txt+="bPrev"
}
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
@@ -1184,13 +1185,7 @@ Button bNext
Events
Touch Press Event
nPage=nPage+1
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,cardEntities,bNext"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
@@ -1776,6 +1771,27 @@ Timer tmSerial
if(tInstruction.txt=="entityUpdHeading")
{
spstr strCommand.txt,tHeading.txt,",",1
spstr strCommand.txt,tTmp.txt,",",2
if(tTmp.txt=="0")
{
vis bPrev,0
}else
{
vis bPrev,1
if(tTmp.txt=="2")
{
bPrev.txt=""
bPrev.isbr=1
}
}
spstr strCommand.txt,tTmp.txt,",",3
if(tTmp.txt=="0")
{
vis bNext,0
}else
{
vis bNext,1
}
}
if(tInstruction.txt=="entityUpd")
{

View File

@@ -515,13 +515,7 @@ Button bPrev
Events
Touch Press Event
nPage=nPage-1
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,cardGrid,bPrev"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
@@ -568,13 +562,7 @@ Button bNext
Events
Touch Press Event
nPage=nPage+1
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,cardGrid,bNext"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC

View File

@@ -23,55 +23,24 @@ Page cardMedia
vis p0,0
vis tSend,0
vis tInstruction,0
vis nPageDisp,0
vis tTmp,0
vis tId,0
//vis nPageDisp,0
Variable (string) strCommand
Attributes
ID : 8
ID : 7
Scope : local
Text :
Max. Text Size: 200
Variable (string) entn
Attributes
ID : 19
ID : 18
Scope : local
Text :
Max. Text Size: 50
Number nPageDisp
Attributes
ID : 6
Scope : local
Dragging : 0
Disable release event after dragging: 0
Send Component ID : disabled
Opacity : 127
x coordinate : 426
y coordinate : 0
Width : 42
Height : 24
Effect : load
Effect Priority : 0
Effect Time : 300
Fill : solid color
Style : flat
Associated Keyboard : none
Font ID : 0
Back. Color : 65535
Font Color : 0
Horizontal Alignment : center
Vertical Alignment : center
Value : 0
Significant digits shown : all
Format : decimal
Word wrap : enabled
Horizontal Spacing : 0
Vertical Spacing : 0
Text tSend
Attributes
ID : 2
@@ -134,7 +103,7 @@ Text tTmp
Text tInstruction
Attributes
ID : 9
ID : 8
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -164,7 +133,7 @@ Text tInstruction
Text tId
Attributes
ID : 10
ID : 9
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -194,7 +163,7 @@ Text tId
Text tHeading
Attributes
ID : 11
ID : 10
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -224,7 +193,7 @@ Text tHeading
Text tTitle
Attributes
ID : 12
ID : 11
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -254,7 +223,7 @@ Text tTitle
Text tAuthor
Attributes
ID : 13
ID : 12
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -284,7 +253,7 @@ Text tAuthor
Text t2
Attributes
ID : 14
ID : 13
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -333,7 +302,7 @@ Text t2
Text tPlayPause
Attributes
ID : 15
ID : 14
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -382,7 +351,7 @@ Text tPlayPause
Text t0
Attributes
ID : 16
ID : 15
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -431,7 +400,7 @@ Text t0
Text tIcon
Attributes
ID : 18
ID : 17
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -478,7 +447,7 @@ Picture p0
Slider hVolume
Attributes
ID : 17
ID : 16
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -554,14 +523,7 @@ Button bNext
Events
Touch Press Event
nPage=nPage+1
nPageDisp.val=nPage
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,cardMedia,bNext"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
@@ -608,14 +570,7 @@ Button bPrev
Events
Touch Press Event
nPage=nPage-1
nPageDisp.val=nPage
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,cardMedia,bPrev"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
@@ -630,7 +585,7 @@ Button bPrev
Timer tmSerial
Attributes
ID : 7
ID : 6
Scope : local
Period (ms): 50
Enabled : yes
@@ -791,7 +746,7 @@ Timer tmSerial
Timer tmSleep
Attributes
ID : 20
ID : 19
Scope : local
Period (ms): 1000
Enabled : yes
@@ -811,7 +766,7 @@ Timer tmSleep
TouchCap tc0
Attributes
ID : 21
ID : 20
Scope: local
Value: 0

View File

@@ -559,14 +559,7 @@ Button bNext
Events
Touch Press Event
nPage=nPage+1
nPageDisp.val=nPage
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,cardThermo,bNext"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC
@@ -721,14 +714,7 @@ Button bPrev
Events
Touch Press Event
nPage=nPage-1
nPageDisp.val=nPage
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,cardThermo,bPrev"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC

View File

@@ -1,66 +1,62 @@
Program.s
0 Component(s)
17 Line(s) of event code
17 Unique line(s) of event code
10 Line(s) of event code
10 Unique line(s) of event code
pageIcons
6 Component(s)
0 Line(s) of event code
0 Unique line(s) of event code
pageTest
13 Component(s)
13 Line(s) of event code
13 Unique line(s) of event code
pageSerialTest
13 Component(s)
48 Line(s) of event code
43 Unique line(s) of event code
popupNotify
17 Component(s)
180 Line(s) of event code
119 Unique line(s) of event code
pageStartup
19 Component(s)
146 Line(s) of event code
111 Unique line(s) of event code
cardMedia
22 Component(s)
200 Line(s) of event code
115 Unique line(s) of event code
pageSwipeTest
18 Component(s)
62 Line(s) of event code
44 Unique line(s) of event code
popupShutter
19 Component(s)
180 Line(s) of event code
103 Unique line(s) of event code
pageTest
14 Component(s)
14 Line(s) of event code
14 Unique line(s) of event code
screensaver
25 Component(s)
173 Line(s) of event code
124 Unique line(s) of event code
popupLight
26 Component(s)
307 Line(s) of event code
168 Unique line(s) of event code
cardThermo
42 Component(s)
412 Line(s) of event code
221 Unique line(s) of event code
cardGrid
39 Component(s)
382 Line(s) of event code
221 Unique line(s) of event code
cardEntities
54 Component(s)
728 Line(s) of event code
317 Unique line(s) of event code
179 Line(s) of event code
102 Unique line(s) of event code
popupNotify
17 Component(s)
179 Line(s) of event code
118 Unique line(s) of event code
pageStartup
19 Component(s)
150 Line(s) of event code
113 Unique line(s) of event code
cardAlarm
35 Component(s)
259 Line(s) of event code
163 Unique line(s) of event code
253 Line(s) of event code
160 Unique line(s) of event code
cardGrid
39 Component(s)
378 Line(s) of event code
219 Unique line(s) of event code
cardThermo
42 Component(s)
406 Line(s) of event code
218 Unique line(s) of event code
cardMedia
21 Component(s)
193 Line(s) of event code
111 Unique line(s) of event code
cardEntities
54 Component(s)
752 Line(s) of event code
330 Unique line(s) of event code
popupLight
26 Component(s)
306 Line(s) of event code
167 Unique line(s) of event code
screensaver
25 Component(s)
166 Line(s) of event code
121 Unique line(s) of event code
Total
14 Page(s)
349 Component(s)
3108 Line(s) of event code
828 Unique line(s) of event code
13 Page(s)
329 Component(s)
3033 Line(s) of event code
806 Unique line(s) of event code

View File

@@ -402,7 +402,7 @@ Text tVersion
Horizontal Alignment : center
Vertical Alignment : center
Input Type : character
Text : 16
Text : 17
Max. Text Size : 10
Word wrap : disabled
Horizontal Spacing : 0
@@ -592,6 +592,10 @@ Timer tmSerial
{
page cardThermo
}
if(tId.txt=="screensaver")
{
page screensaver
}
if(tId.txt=="popupLight")
{
pageIcons.tTmp1.txt=tTmp.txt

View File

@@ -111,45 +111,9 @@ Button b1
Touch Press Event
page pageSerialTest
Button b2
Attributes
ID : 4
Scope : local
Dragging : 0
Disable release event after dragging: 0
Send Component ID : disabled
Opacity : 127
x coordinate : 7
y coordinate : 265
Width : 100
Height : 50
Effect : load
Effect Priority : 0
Effect Time : 300
Fill : solid color
Style : 3D auto
Font ID : 4
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 : swipe
Max. Text Size : 10
Word wrap : disabled
Horizontal Spacing : 0
Vertical Spacing : 0
Events
Touch Press Event
page pageSwipeTest
Button b3
Attributes
ID : 5
ID : 4
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -185,7 +149,7 @@ Button b3
Button b6
Attributes
ID : 6
ID : 5
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -221,7 +185,7 @@ Button b6
Button b4
Attributes
ID : 7
ID : 6
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -257,7 +221,7 @@ Button b4
Button b5
Attributes
ID : 8
ID : 7
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -293,7 +257,7 @@ Button b5
Button b7
Attributes
ID : 9
ID : 8
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -329,7 +293,7 @@ Button b7
Button b8
Attributes
ID : 10
ID : 9
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -366,7 +330,7 @@ Button b8
Button b9
Attributes
ID : 11
ID : 10
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -402,7 +366,7 @@ Button b9
Button b10
Attributes
ID : 12
ID : 11
Scope : local
Dragging : 0
Disable release event after dragging: 0
@@ -438,7 +402,7 @@ Button b10
Button b11
Attributes
ID : 13
ID : 12
Scope : local
Dragging : 0
Disable release event after dragging: 0

View File

@@ -593,12 +593,7 @@ Button b0
Events
Touch Press Event
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,popupLight,bExit"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC

View File

@@ -278,12 +278,7 @@ Button b0
Events
Touch Press Event
sleepTimeout=vaOldSleepT.val
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,popupNotify,bExit"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC

View File

@@ -364,12 +364,7 @@ Button b0
Events
Touch Press Event
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,popupShutter,bExit"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC

View File

@@ -32,7 +32,17 @@ Page screensaver
dim=dimValue
vis tSend,0
//page open event
// craft command
// clear weather elements, to keep example content in HMI
tMainIcon.txt=""
tMainText.txt=""
tMRIcon.txt=""
tMR.txt=""
tForecast1.txt=""
tF1Icon.txt=""
tForecast1Val.txt=""
tForecast2.txt=""
tF2Icon.txt=""
tForecast2Val.txt=""
tSend.txt="event,screensaverOpen"
//send calc crc
btlen tSend.txt,sys0
@@ -45,17 +55,6 @@ Page screensaver
prints sys0,2
prints tSend.txt,0
prints crcval,2
// clear weather elements, to keep example content in HMI
tMainIcon.txt=""
tMainText.txt=""
tMRIcon.txt=""
tMR.txt=""
tForecast1.txt=""
tF1Icon.txt=""
tForecast1Val.txt=""
tForecast2.txt=""
tF2Icon.txt=""
tForecast2Val.txt=""
Variable (string) strCommand
Attributes
@@ -260,7 +259,7 @@ Text tSend
Vertical Alignment : center
Input Type : character
Text :
Max. Text Size : 25
Max. Text Size : 50
Word wrap : disabled
Horizontal Spacing : 0
Vertical Spacing : 0
@@ -725,14 +724,6 @@ Timer tmSerial
//tForecast2Val
spstr strCommand.txt,tForecast2Val.txt,"?",10
}
if(tInstruction.txt=="page")
{
//pagenumber
spstr strCommand.txt,tTmp.txt,",",1
covx tTmp.txt,sys0,0,0
nPage=sys0
//don't send current page number, wake will do
}
if(tInstruction.txt=="pageType")
{
dim=100
@@ -804,12 +795,7 @@ TouchCap tc0
Events
Touch Press Event
//page open event
// event,pageOpen,cardEntities,pageNumber
// craft command
// convert pageNumber and write to tTmp
covx nPage,tTmp.txt,0,0
tSend.txt="event,pageOpen,"+tTmp.txt
tSend.txt="event,buttonPress2,screensaver,bExit"
//send calc crc
btlen tSend.txt,sys0
crcrest 1,0xffff // reset CRC

Binary file not shown.

Binary file not shown.

View File

@@ -1,12 +1,12 @@
---
nspanel:
module: nspanel-lovelace-ui
module: nspanel-lovelace-ui2
class: NsPanelLovelaceUIManager
config:
panelRecvTopic: "tele/tasmota_your_mqtt_topic/RESULT"
panelSendTopic: "cmnd/tasmota_your_mqtt_topic/CustomSend"
updateMode: auto-notify # possible values are auto, auto-notify and manual
timeoutScreensaver: 15 #in seconds
updateMode: "auto-notify"
timeoutScreensaver: 20
#brightnessScreensaver: 10
brightnessScreensaver:
- time: "7:00:00"
@@ -58,4 +58,3 @@ nspanel:
- type: cardMedia
heading: Exampe Media
item: media_player.spotify_user

View File

@@ -0,0 +1,131 @@
import logging
LOGGER = logging.getLogger(__name__)
class PageNode(object):
def __init__(self, data, parent=None):
self.data = data
self.name = None
self.childs = []
self.parent = parent
self.pos = None
if "items" in data:
childs = data.pop("items")
for page in childs:
self.add_child(PageNode(page, self))
name = self.data.get("heading", "unkown") if type(self.data) is dict else self.data
ptype = self.data.get("type", "unkown") if type(self.data) is dict else "leaf"
self.name = f"{ptype}.{name}" if type(self.data) is dict else self.data
def add_child(self, obj):
obj.pos = len(self.childs)
self.childs.append(obj)
def next(self):
if self.parent is not None:
pos = self.pos
length = len(self.parent.childs)
return self.parent.childs[(pos+1)%length]
else:
return self
def prev(self):
if self.parent is not None:
pos = self.pos
length = len(self.parent.childs)
return self.parent.childs[(pos-1)%length]
else:
return self
def dump(self, indent=0):
"""dump tree to string"""
tab = ' '*(indent-1) + ' |- ' if indent > 0 else ''
name = self.name
parent = self.parent.name if self.parent is not None else "root"
dumpstring = f"{tab}{self.pos}:{name} -> {parent} \n"
for obj in self.childs:
dumpstring += obj.dump(indent + 1)
return dumpstring
def get_items(self):
items = []
for i in self.childs:
if len(i.childs) > 0:
items.append("navigate.todo")
else:
items.append(i.data)
return items
def get_all_items_recursive(self):
items = []
for i in self.childs:
if len(i.childs) > 0:
items.extend(i.get_all_items_recursive())
else:
if type(i.data) is dict:
items.append(i.data.get("item", next(iter(i.data))))
else:
items.append(i.data)
return items
class LuiBackendConfig(object):
_DEFAULT_CONFIG = {
'panelRecvTopic': "tele/tasmota_your_mqtt_topic/RESULT",
'panelSendTopic': "cmnd/tasmota_your_mqtt_topic/CustomSend",
'updateMode': "auto-notify",
'timeoutScreensaver': 20,
'brightnessScreensaver': 20,
'locale': "en_US",
'timeFormat': "%H:%M",
'dateFormatBabel': "full",
'dateFormat': "%A, %d. %B %Y",
'weather': 'weather.example',
'pages': [{
'type': 'cardEntities',
'heading': 'Test Entities 1',
'items': ['switch.test_item', 'switch.test_item', 'switch.test_item']
}, {
'type': 'cardGrid',
'heading': 'Test Grid 1',
'items': ['switch.test_item', 'switch.test_item', 'switch.test_item']
}
]
}
def __init__(self, args=None, check=True):
self._config = {}
self._page_config = None
if args:
self.load(args)
if check:
self.check()
def load(self, args):
for k, v in args.items():
if k in self._DEFAULT_CONFIG:
self._config[k] = v
LOGGER.info(f"Loaded config: {self._config}")
root_page = {"items": self.get("pages"), "name": "root"}
self._page_config = PageNode(root_page)
LOGGER.info(f"Parsed Page config to the following Tree: \n {self._page_config.dump()}")
def check(self):
return
def get(self, name):
value = self._config.get(name)
if value is None:
value = self._DEFAULT_CONFIG.get(name)
return value
def get_root_page(self):
return self._page_config

View File

@@ -0,0 +1,178 @@
import logging
import datetime
from helper import scale, pos_to_color
from pages import LuiPagesGen
LOGGER = logging.getLogger(__name__)
class LuiController(object):
def __init__(self, ha_api, config, send_mqtt_msg):
self._ha_api = ha_api
self._config = config
self._send_mqtt_msg = send_mqtt_msg
# first child of root page (default, after startup)
self._current_page = self._config._page_config.childs[0]
self._pages_gen = LuiPagesGen(ha_api, config, send_mqtt_msg)
# send panel back to startup page on restart of this script
self._pages_gen.page_type("pageStartup")
# time update callback
time = datetime.time(0, 0, 0)
ha_api.run_minutely(self._pages_gen.update_time, time)
# weather callback
weather_interval = 15 * 60 # 15 minutes
ha_api.run_every(self.weather_update, "now", weather_interval)
# register callbacks
self.register_callbacks()
self.current_screensaver_brightness = 20
# calc screensaver brightness
# set brightness of screensaver
if type(self._config.get("brightnessScreensaver")) == int:
self.current_screensaver_brightness = self._config.get("brightnessScreensaver")
elif type(self._config.get("brightnessScreensaver")) == list:
sorted_timesets = sorted(self._config.get("brightnessScreensaver"), key=lambda d: self._ha_api.parse_time(d['time']))
found_current_dim_value = False
for index, timeset in enumerate(sorted_timesets):
self._ha_api.run_daily(self.update_screensaver_brightness, timeset["time"], value=timeset["value"])
LOGGER.info("Current time %s", self._ha_api.get_now().time())
if self._ha_api.parse_time(timeset["time"]) > self._ha_api.get_now().time() and not found_current_dim_value:
# first time after current time, set dim value
self.current_screensaver_brightness = sorted_timesets[index-1]["value"]
LOGGER.info("Setting dim value to %s", sorted_timesets[index-1])
found_current_dim_value = True
# still no dim value
if not found_current_dim_value:
self.current_screensaver_brightness = sorted_timesets[-1]["value"]
# send screensaver brightness in case config has changed
self.update_screensaver_brightness(kwargs={"value": self.current_screensaver_brightness})
def startup(self):
LOGGER.info(f"Startup Event")
# send time and date on startup
self._pages_gen.update_time("")
self._pages_gen.update_date("")
# send panel to screensaver
self._pages_gen.page_type("screensaver")
self.weather_update("")
def update_screensaver_brightness(self, kwargs):
self.current_screensaver_brightness = kwargs['value']
self._send_mqtt_msg(f"dimmode,{self.current_screensaver_brightness}")
def weather_update(self, kwargs):
we_name = self._config.get("weather")
unit = "°C"
self._pages_gen.update_screensaver_weather(kwargs={"weather": we_name, "unit": unit})
def register_callbacks(self):
items = self._config.get_root_page().get_all_items_recursive()
LOGGER.info(f"Registering callbacks for the following items: {items}")
for item in items:
if self._ha_api.entity_exists(item):
self._ha_api.listen_state(self.state_change_callback, entity_id=item, attribute="all")
def state_change_callback(self, entity, attribute, old, new, kwargs):
LOGGER.info(f"Got callback for: {entity}")
if entity in self._current_page.get_items():
self._pages_gen.render_page(self._current_page)
# send detail page update, just in case
if self._current_page.type in ["cardGrid", "cardEntities"]:
if entity.startswith("light"):
self._pages_gen.generate_light_detail_page(entity)
if entity.startswith("switch"):
self._pages_gen.generate_shutter_detail_page(entity)
def detail_open(self, detail_type, entity_id):
if detail_type == "popupShutter":
self._pages_gen.generate_shutter_detail_page(entity_id)
if detail_type == "popupLight":
self._pages_gen.generate_light_detail_page(entity_id)
def button_press(self, entity_id, button_type, value):
LOGGER.debug(f"Button Press Event; entity_id: {entity_id}; button_type: {button_type}; value: {value} ")
# internal buttons
if entity_id == "screensaver" and button_type == "enter":
self._pages_gen.render_page(self._current_page)
if button_type == "bExit":
self._pages_gen.render_page(self._current_page)
if button_type == "bNext":
self._current_page = self._current_page.next()
self._pages_gen.render_page(self._current_page)
if button_type == "bPrev":
self._current_page = self._current_page.prev()
self._pages_gen.render_page(self._current_page)
elif entity_id == "updateDisplayNoYes" and value == "no":
self._pages_gen.render_page(self._current_page)
# buttons with actions on HA
if button_type == "OnOff":
if value == "1":
self._ha_api.turn_on(entity_id)
else:
self._ha_api.turn_off(entity_id)
# for shutter / covers
if button_type == "up":
self._ha_api.get_entity(entity_id).call_service("open_cover")
if button_type == "stop":
self._ha_api.get_entity(entity_id).call_service("stop_cover")
if button_type == "down":
self._ha_api.get_entity(entity_id).call_service("close_cover")
if button_type == "positionSlider":
pos = int(value)
self._ha_api.get_entity(entity_id).call_service("set_cover_position", position=pos)
if button_type == "button":
if entity_id.startswith('scene'):
self._ha_api.get_entity(entity_id).call_service("turn_on")
elif entity_id.startswith('light') or entity_id.startswith('switch') or entity_id.startswith('input_boolean'):
self._ha_api.get_entity(entity_id).call_service("toggle")
else:
self._ha_api.get_entity(entity_id).call_service("press")
# for media page
if button_type == "media-next":
self._ha_api.get_entity(entity_id).call_service("media_next_track")
if button_type == "media-back":
self._ha_api.get_entity(entity_id).call_service("media_previous_track")
if button_type == "media-pause":
self._ha_api.get_entity(entity_id).call_service("media_play_pause")
if button_type == "hvac_action":
self._ha_api.get_entity(entity_id).call_service("set_hvac_mode", hvac_mode=value)
if button_type == "volumeSlider":
pos = int(value)
# HA wants this value between 0 and 1 as float
pos = pos/100
self._ha_api.get_entity(entity_id).call_service("volume_set", volume_level=pos)
# for light detail page
if button_type == "brightnessSlider":
# scale 0-100 to ha brightness range
brightness = int(scale(int(value),(0,100),(0,255)))
self._ha_api.get_entity(entity_id).call_service("turn_on", brightness=brightness)
if button_type == "colorTempSlider":
entity = self._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))
self._ha_api.get_entity(entity_id).call_service("turn_on", color_temp=color_val)
if button_type == "colorWheel":
self._ha_api.log(value)
value = value.split('|')
color = pos_to_color(int(value[0]), int(value[1]))
self._ha_api.log(color)
self._ha_api.get_entity(entity_id).call_service("turn_on", rgb_color=color)
# for climate page
if button_type == "tempUpd":
temp = int(value)/10
self._ha_api.get_entity(entity_id).call_service("set_temperature", temperature=temp)

View File

@@ -0,0 +1,12 @@
translations = {
'de_DE': {
'ACTIVATE': "AKTIVIEREN",
'PRESS': "DRÜCKEN",
}
}
def get_translation(locale, input):
if locale in translations:
return translations.get(locale).get(input, input)
else:
return input

View File

@@ -0,0 +1,53 @@
import json
import logging
LOGGER = logging.getLogger(__name__)
class LuiMqttListener(object):
def __init__(self, mqtt_api, topic, controller, updater):
self._controller = controller
self._updater = updater
# Setup, mqtt subscription and callback
mqtt_api.mqtt_subscribe(topic=topic)
mqtt_api.listen_event(self.mqtt_event_callback, "MQTT_MESSAGE", topic=topic, namespace='mqtt')
def mqtt_event_callback(self, event_name, data, kwargs):
LOGGER.info(f'MQTT callback for: {data}')
# Parse Json Message from Tasmota and strip out message from nextion display
data = json.loads(data["payload"])
if("nlui_driver_version" in data):
msg = data["nlui_driver_version"]
self._updater.set_tasmota_driver_version(int(msg))
if("CustomRecv" not in data):
return
msg = data["CustomRecv"]
LOGGER.info(f"Received Message from Screen: {msg}")
# Split message into parts seperated by ","
msg = msg.split(",")
# run action based on received command
if msg[0] == "event":
if msg[1] == "startup":
display_firmware_version = int(msg[2])
self._updater.set_current_display_firmware_version(display_firmware_version)
# check for updates
msg_send = self._updater.check_updates()
# send messages for current page
if not msg_send:
self._controller.startup()
if msg[1] == "screensaverOpen":
self._controller.weather_update("")
if msg[1] == "buttonPress2":
entity_id = msg[2]
btype = msg[3]
value = msg[4] if len(msg) > 4 else None
if entity_id == "updateDisplayNoYes" and value == "yes":
self._updater.update_panel_driver()
self._controller.button_press(entity_id, btype, value)
if msg[1] == "pageOpenDetail":
self._controller.detail_open(msg[2], msg[3])

View File

@@ -0,0 +1,277 @@
import logging
import datetime
from icon_mapping import get_icon_id
from icons import get_icon_id_ha
from helper import scale, rgb_dec565, rgb_brightness
from localization import get_translation
# check Babel
import importlib
babel_spec = importlib.util.find_spec("babel")
if babel_spec is not None:
import babel.dates
LOGGER = logging.getLogger(__name__)
class LuiPagesGen(object):
def __init__(self, ha_api, config, send_mqtt_msg):
self._ha_api = ha_api
self._config = config
self._locale = config.get("locale")
self._send_mqtt_msg = send_mqtt_msg
def getEntityColor(self, entity):
attr = entity.attributes
default_color_on = rgb_dec565([253, 216, 53])
default_color_off = rgb_dec565([68, 115, 158])
icon_color = default_color_on if entity.state == "on" else default_color_off
if "rgb_color" in attr:
color = attr.rgb_color
if "brightness" in attr:
color = rgb_brightness(color, attr.brightness)
icon_color = rgb_dec565(color)
elif "brightness" in attr:
color = rgb_brightness([253, 216, 53], attr.brightness)
icon_color = rgb_dec565(color)
return icon_color
def update_time(self, kwargs):
time = datetime.datetime.now().strftime(self._config.get("timeFormat"))
self._send_mqtt_msg(f"time,{time}")
def update_date(self, kwargs):
global babel_spec
if babel_spec is not None:
dateformat = self._config.get("dateFormatBabel")
date = babel.dates.format_date(datetime.datetime.now(), dateformat, locale=self._locale)
else:
dateformat = self._config.get("dateFormat")
date = datetime.datetime.now().strftime(dateformat)
self._send_mqtt_msg(f"date,?{date}")
def page_type(self, target_page):
self._send_mqtt_msg(f"pageType,{target_page}")
def update_screensaver_weather(self, kwargs):
we_name = kwargs['weather']
unit = kwargs['unit']
if self._ha_api.entity_exists(we_name):
we = self._ha_api.get_entity(we_name)
else:
LOGGER.error("Skipping Weather Update, entitiy not found")
return
icon_cur = get_icon_id_ha("weather", state=we.state)
text_cur = f"{we.attributes.temperature}{unit}"
icon_cur_detail = get_icon_id("water-percent")
text_cur_detail = f"{we.attributes.humidity} %"
up1 = we.attributes.forecast[0]['datetime']
up1 = datetime.datetime.fromisoformat(up1)
icon1 = get_icon_id_ha("weather", state=we.attributes.forecast[0]['condition'])
down1 = we.attributes.forecast[0]['temperature']
up2 = we.attributes.forecast[1]['datetime']
up2 = datetime.datetime.fromisoformat(up2)
icon2 = get_icon_id_ha("weather", state=we.attributes.forecast[1]['condition'])
down2 = we.attributes.forecast[1]['temperature']
global babel_spec
if babel_spec is not None:
up1 = babel.dates.format_date(up1, "E", locale=self._locale)
up2 = babel.dates.format_date(up2, "E", locale=self._locale)
else:
up1 = up1.strftime("%a")
up2 = up2.strftime("%a")
self._send_mqtt_msg(f"weatherUpdate,?{icon_cur}?{text_cur}?{icon_cur_detail}?{text_cur_detail}?{up1}?{icon1}?{down1}?{up2}?{icon2}?{down2}")
def generate_entities_item(self, item):
icon = None
name = None
if type(item) is dict:
icon = next(iter(item.items()))[1].get('icon')
name = next(iter(item.items()))[1].get('name')
item = next(iter(item.items()))[0]
# type of the item is the string before the "." in the item name
item_type = item.split(".")[0]
LOGGER.info(f"Generating item command for {item} with type {item_type}",)
# Internal Entities
if item_type == "delete":
return f",{item_type},,,,,"
if item_type == "navigate":
text = get_translation(self._locale,"PRESS")
return f",button,{item},0,17299,{item},{text}"
if not self._ha_api.entity_exists(item):
return f",text,{item},{get_icon_id('alert-circle-outline')},17299,Not found check, apps.yaml"
# HA Entities
entity = self._ha_api.get_entity(item)
name = name if name is not None else entity.attributes.friendly_name
if item_type == "cover":
icon_id = get_icon_id_ha("cover", state=entity.state, overwrite=icon)
return f",shutter,{item},{icon_id},17299,{name},"
if item_type in "light":
switch_val = 1 if entity.state == "on" else 0
icon_color = self.getEntityColor(entity)
icon_id = get_icon_id_ha("light", overwrite=icon)
return f",{item_type},{item},{icon_id},{icon_color},{name},{switch_val}"
if item_type in ["switch", "input_boolean"]:
switch_val = 1 if entity.state == "on" else 0
icon_color = self.getEntityColor(entity)
icon_id = get_icon_id_ha(item_type, state=entity.state, overwrite=icon)
return f",switch,{item},{icon_id},{icon_color},{name},{switch_val}"
if item_type in ["sensor", "binary_sensor"]:
device_class = self.entity.attributes.get("device_class", "")
icon_id = get_icon_id_ha("sensor", state=entity.state, device_class=device_class, overwrite=icon)
unit_of_measurement = self.entity.attributes.get("unit_of_measurement", "")
value = entity.state + " " + unit_of_measurement
icon_color = self.getEntityColor(entity)
return f",text,{item},{icon_id},{icon_color},{name},{value}"
if item_type in ["button", "input_button"]:
icon_id = get_icon_id_ha("button", overwrite=icon)
return f",button,{item},{icon_id},17299,{name},PRESS"
if item_type == "scene":
icon_id = get_icon_id_ha("scene", overwrite=icon)
text = get_translation(self._locale,"PRESS")
return f",button,{item},{icon_id},17299,{name},{text}"
def generate_entities_page(self, heading, items):
# Set Heading of Page
self._send_mqtt_msg(f"entityUpdHeading,{heading}")
# Get items and construct cmd string
command = "entityUpd"
for item in items:
command += self.generate_entities_item(item)
self._send_mqtt_msg(command)
def generate_thermo_page(self, item):
if not self._ha_api.entity_exists(item):
command = f"entityUpd,{item},Not found,220,220,Not found,150,300,5"
else:
entity = self._ha_api.get_entity(item)
heading = entity.attributes.friendly_name
current_temp = int(entity.attributes.get("current_temperature", 0)*10)
dest_temp = int(entity.attributes.get("temperature", 0)*10)
status = entity.attributes.get("hvac_action", "")
min_temp = int(entity.attributes.get("min_temp", 0)*10)
max_temp = int(entity.attributes.get("max_temp", 0)*10)
step_temp = int(entity.attributes.get("target_temp_step", 0.5)*10)
icon_res = ""
hvac_modes = entity.attributes.get("hvac_modes", [])
for mode in hvac_modes:
icon_id = get_icon_id('alert-circle-outline')
color_on = 64512
if mode == "auto":
icon_id = get_icon_id("calendar-sync")
color_on = 1024
if mode == "heat":
icon_id = get_icon_id("fire")
color_on = 64512
if mode == "off":
icon_id = get_icon_id("power")
color_on = 35921
if mode == "cool":
icon_id = get_icon_id("snowflake")
color_on = 11487
if mode == "dry":
icon_id = get_icon_id("water-percent")
color_on = 60897
if mode == "fan_only":
icon_id = get_icon_id("fan")
color_on = 35921
state = 0
if(mode == entity.state):
state = 1
icon_res += f",{icon_id},{color_on},{state},{mode}"
len_hvac_modes = len(hvac_modes)
if len_hvac_modes%2 == 0:
# even
padding_len = int((4-len_hvac_modes)/2)
icon_res = ","*4*padding_len + icon_res + ","*4*padding_len
# use last 4 icons
icon_res = ","*4*5 + icon_res
else:
# uneven
padding_len = int((5-len_hvac_modes)/2)
icon_res = ","*4*padding_len + icon_res + ","*4*padding_len
# use first 5 icons
icon_res = icon_res + ","*4*4
command = f"entityUpd,{item},{heading},{current_temp},{dest_temp},{status},{min_temp},{max_temp},{step_temp}{icon_res}"
self._send_mqtt_msg(command)
def generate_media_page(self, item):
if not self._ha_api.entity_exists(item):
command = f"entityUpd,|{item}|Not found|{get_icon_id('alert-circle-outline')}|Please check your|apps.yaml in AppDaemon|50|{get_icon_id('alert-circle-outline')}"
else:
entity = self._ha_api.get_entity(item)
heading = entity.attributes.friendly_name
icon = 0
title = entity.attributes.get("media_title", "")
author = entity.attributes.get("media_artist", "")
volume = int(entity.attributes.get("volume_level", 0)*100)
iconplaypause = get_icon_id("pause") if entity.state == "playing" else get_icon_id("play")
if "media_content_type" in entity.attributes:
if entity.attributes.media_content_type == "music":
icon = get_icon_id("music")
command = f"entityUpd,|{item}|{heading}|{icon}|{title}|{author}|{volume}|{iconplaypause}"
self._send_mqtt_msg(command)
def render_page(self, page):
LOGGER.info(page)
config = page.data
page_type = config["type"]
LOGGER.info(f"Started rendering of page x with type {page_type}")
# Switch to page
self.page_type(page_type)
if page_type in ["cardEntities", "cardGrid"]:
heading = config.get("heading", "unknown")
self.generate_entities_page(heading, page.get_items())
return
if page_type == "cardThermo":
LOGGER.info(page.data)
self.generate_thermo_page(page.data.get("item"))
if page_type == "cardMedia":
LOGGER.info(page.data)
self.generate_media_page(page.data.get("item"))
def generate_light_detail_page(self, entity):
entity = self._ha_api.get_entity(entity)
switch_val = 1 if entity.state == "on" else 0
icon_color = self.getEntityColor(entity)
brightness = "disable"
color_temp = "disable"
color = "disable"
# scale 0-255 brightness from ha to 0-100
if entity.state == "on":
if "brightness" in entity.attributes:
brightness = int(scale(entity.attributes.brightness,(0,255),(0,100)))
else:
brightness = "disable"
if "color_temp" in entity.attributes.supported_color_modes:
if "color_temp" in entity.attributes:
# scale ha color temp range to 0-100
color_temp = int(scale(entity.attributes.color_temp,(entity.attributes.min_mireds, entity.attributes.max_mireds),(0,100)))
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 entity.attributes.supported_color_modes):
color = "enable"
else:
color = "disable"
self._send_mqtt_msg(f"entityUpdateDetail,{get_icon_id('lightbulb')},{icon_color},{switch_val},{brightness},{color_temp},{color}")
def generate_shutter_detail_page(self, entity):
pos = 100-int(entity.attributes.get("current_position", 50))
self._send_mqtt_msg(f"entityUpdateDetail,{pos}")
def send_message_page(self, id, heading, msg, b1, b2):
self._send_mqtt_msg(f"pageType,popupNotify")
self._send_mqtt_msg(f"entityUpdateDetail,|{id}|{heading}|65535|{b1}|65535|{b2}|65535|{msg}|65535|0")

View File

@@ -0,0 +1,77 @@
import logging
LOGGER = logging.getLogger(__name__)
class Updater:
def __init__(self, send_mqtt_msg, topic_send, mode, desired_display_firmware_version, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url):
self.desired_display_firmware_version = desired_display_firmware_version
self.desired_display_firmware_url = desired_display_firmware_url
self.desired_tasmota_driver_version = desired_tasmota_driver_version
self.desired_tasmota_driver_url = desired_tasmota_driver_url
self.mode = mode
self._send_mqtt_msg = send_mqtt_msg
self.topic_send = topic_send
self.current_tasmota_driver_version = None
self.current_display_firmware_version = None
def set_tasmota_driver_version(self, driver_version):
self.current_tasmota_driver_version = driver_version
def set_current_display_firmware_version(self, panel_version):
self.current_display_firmware_version = panel_version
def check_pre_req(self):
# we need to know both versions to continue
if self.current_tasmota_driver_version is not None and self.current_display_firmware_version is not None:
# tasmota driver has to be at least version 2 for Update command
# and panel has to be at version 11 for notify commands
# version 16 for new button cmd format
if self.current_tasmota_driver_version >= 2 and self.current_display_firmware_version >= 16:
return True
return False
def send_message_page(self, id, heading, msg, b1, b2):
self._send_mqtt_msg(f"pageType,popupNotify")
self._send_mqtt_msg(f"entityUpdateDetail,|{id}|{heading}|65535|{b1}|65535|{b2}|65535|{msg}|65535|0")
def check_updates(self):
# return's true if a notification was send to the panel
# run pre req check
if self.check_pre_req():
LOGGER.info("Update Pre-Check sucessful Tasmota Driver Version: %s Panel Version: %s", self.current_tasmota_driver_version, self.current_display_firmware_version)
# check if tasmota driver needs update
if self.current_tasmota_driver_version < self.desired_tasmota_driver_version:
LOGGER.info("Update of Tasmota Driver needed")
# in auto mode just do the update
if self.mode == "auto":
self.update_berry_driver()
return False
# send notification about the update
if self.mode == "auto-notify":
update_msg = "There's an update avalible for the tasmota berry driver, do you want to start the update now? If you encounter issues after the update or this message appears frequently, please checkthe manual and repeat the installation steps for the tasmota berry driver. "
self.send_message_page("updateBerryNoYes", "Driver Update available!", update_msg, "Dismiss", "Yes")
return True
return False
# check if display firmware needs an update
if self.current_display_firmware_version < self.desired_display_firmware_version:
LOGGER.info("Update of Display Firmware needed")
# in auto mode just do the update
if self.mode == "auto":
self.update_panel_driver()
return False
# send notification about the update
if self.mode == "auto-notify":
update_msg = "There's a firmware update avalible for the nextion sceen inside of nspanel, do you want to start the update now? If the update fails check the installation manual and flash again over the tasmota console. Be pationed the update will take a while."
self.send_message_page("updateDisplayNoYes", "Display Update available!", update_msg, "Dismiss", "Yes")
return True
return False
else:
LOGGER.info("Update Pre-Check failed Tasmota Driver Version: %s Panel Version: %s", self.current_tasmota_driver_version, self.current_display_firmware_version)
return False
def update_berry_driver(self):
topic = self.topic_send.replace("CustomSend", "UpdateDriverVersion")
self._send_mqtt_msg(self.desired_tasmota_driver_url, topic=topic)
def update_panel_driver(self):
topic = self.topic_send.replace("CustomSend", "FlashNextion")
self._send_mqtt_msg(self.desired_display_firmware_url, topic=topic)

View File

@@ -1,633 +1,79 @@
import json
import datetime
import logging
import traceback
import hassapi as hass
from helper import scale, pos_to_color, rgb_dec565, rgb_brightness
from icon_mapping import get_icon_id
from icons import get_icon_id_ha
# check Babel
import importlib
babel_spec = importlib.util.find_spec("babel")
if babel_spec is not None:
import babel.dates
from luibackend.config import LuiBackendConfig
from luibackend.controller import LuiController
from luibackend.mqttListener import LuiMqttListener
from luibackend.updater import Updater
LOGGER = logging.getLogger(__name__)
class AppDaemonLoggingHandler(logging.Handler):
def __init__(self, app):
super().__init__()
self._app = app
def emit(self, record):
message = record.getMessage()
if record.exc_info:
message += '\nTraceback (most recent call last):\n'
message += '\n'.join(traceback.format_tb(record.exc_info[2]))
message += f'{record.exc_info[0].__name__}: {record.exc_info[1]}'
self._app.log(message, level=record.levelname)
class NsPanelLovelaceUIManager(hass.Hass):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._redirect_logging()
def _redirect_logging(self):
# Add a handler for the logging module that will convert the
# calls to AppDaemon's logger with the self instance, so that
# we can simply use logging in the rest of the application
rlogger = logging.getLogger()
rlogger.handlers = [
h for h in rlogger.handlers
if type(h).__name__ != AppDaemonLoggingHandler.__name__
]
rlogger.addHandler(AppDaemonLoggingHandler(self))
# We want to grab all the logs, AppDaemon will
# then care about filtering those we asked for
rlogger.setLevel(logging.DEBUG)
def initialize(self):
LOGGER.info('Starting')
mqtt_api = self._mqtt_api = self.get_plugin_api("MQTT")
cfg = self._cfg = LuiBackendConfig(self.args["config"])
data = self.args["config"]
LovelaceUIPanel(self, data)
class Updater:
def __init__(self, nsplui, mode):
self.desired_display_firmware_version = 16
self.desired_display_firmware_url = "http://nspanel.pky.eu/lovelace-ui/github/nspanel-v1.7.0.tft"
self.desired_tasmota_driver_version = 3
self.desired_tasmota_driver_url = "https://raw.githubusercontent.com/joBr99/nspanel-lovelace-ui/main/tasmota/autoexec.be"
self.mode = mode
self.nsplui = nsplui
self.current_tasmota_driver_version = None
self.current_display_firmware_version = None
def set_tasmota_driver_version(self, driver_version):
self.current_tasmota_driver_version = driver_version
def set_current_display_firmware_version(self, panel_version):
self.current_display_firmware_version = panel_version
def check_pre_req(self):
# we need to know both versions to continue
if self.current_tasmota_driver_version is not None and self.current_display_firmware_version is not None:
# tasmota driver has to be at least version 2 for Update command and panel has to be at version 11 for notify commands
if self.current_tasmota_driver_version >= 2 and self.current_display_firmware_version >= 11:
return True
return False
def check_updates(self):
# return's true if a notification was send to the panel
# run pre req check
if self.check_pre_req():
self.nsplui.api.log("Update Pre-Check sucessful Tasmota Driver Version: %s Panel Version: %s", self.current_tasmota_driver_version, self.current_display_firmware_version, level="DEBUG")
# check if tasmota driver needs update
if self.current_tasmota_driver_version < self.desired_tasmota_driver_version:
self.nsplui.api.log("Update of Tasmota Driver needed")
# in auto mode just do the update
if self.mode == "auto":
self.update_berry_driver()
return False
# send notification about the update
if self.mode == "auto-notify":
update_msg = "There's an update avalible for the tasmota berry driver, do you want to start the update now? If you encounter issues after the update or this message appears frequently, please checkthe manual and repeat the installation steps for the tasmota berry driver. "
self.nsplui.send_message_page("updateBerryNoYes", "Driver Update available!", update_msg, "Dismiss", "Yes")
return True
return False
# check if display firmware needs an update
if self.current_display_firmware_version < self.desired_display_firmware_version:
self.nsplui.api.log("Update of Display Firmware needed")
# in auto mode just do the update
if self.mode == "auto":
self.update_panel_driver()
return False
# send notification about the update
if self.mode == "auto-notify":
update_msg = "There's a firmware update avalible for the nextion sceen inside of nspanel, do you want to start the update now? If the update fails check the installation manual and flash again over the tasmota console. Be pationed the update will take a while."
self.nsplui.send_message_page("updateDisplayNoYes", "Display Update available!", update_msg, "Dismiss", "Yes")
return True
return False
else:
self.nsplui.api.log("Update Pre-Check failed Tasmota Driver Version: %s Panel Version: %s", self.current_tasmota_driver_version, self.current_display_firmware_version)
return False
def update_berry_driver(self):
self.nsplui.mqtt.mqtt_publish(self.nsplui.config["panelSendTopic"].replace("CustomSend", "UpdateDriverVersion"), self.desired_tasmota_driver_url)
def update_panel_driver(self):
self.nsplui.mqtt.mqtt_publish(self.nsplui.config["panelSendTopic"].replace("CustomSend", "FlashNextion"), self.desired_display_firmware_url)
class LovelaceUIPanel:
def __init__(self, api, config):
self.api = api
self.config = config
self.current_page_nr = 0
self.current_screensaver_brightness = 10
self.button_text = self.config.get("buttonText", "PRESS")
self.scene_text = self.config.get("sceneText", "ACTIVATE")
# check configured items
self.check_items()
# Setup, mqtt subscription and callback
self.mqtt = self.api.get_plugin_api("MQTT")
self.mqtt.mqtt_subscribe(topic=self.config["panelRecvTopic"])
self.mqtt.listen_event(self.handle_mqtt_incoming_message, "MQTT_MESSAGE", topic=self.config["panelRecvTopic"], namespace='mqtt')
# Read updateMode and use "auto-notify" as default
update_mode = self.config["updateMode"] if "updateMode" in self.config else "auto-notify"
self.updater = Updater(self, update_mode)
topic_send = cfg.get("panelSendTopic")
def send_mqtt_msg(msg, topic=None):
if topic is None:
topic = topic_send
LOGGER.info(f"Sending MQTT Message: {msg}")
mqtt_api.mqtt_publish(topic, msg)
# Request Tasmota Driver Version
self.mqtt.mqtt_publish(self.config["panelSendTopic"].replace("CustomSend", "GetDriverVersion"), "x")
mqtt_api.mqtt_publish(topic_send.replace("CustomSend", "GetDriverVersion"), "x")
# send panel back to startup page on restart of this script
self.send_mqtt_msg("pageType,pageStartup")
controller = LuiController(self, cfg, send_mqtt_msg)
# Setup time callback
time = datetime.time(0, 0, 0)
self.api.run_minutely(self.update_time, time)
desired_display_firmware_version = 17
desired_display_firmware_url = "http://nspanel.pky.eu/lovelace-ui/github/nspanel-v1.8.0.tft"
desired_tasmota_driver_version = 3
desired_tasmota_driver_url = "https://raw.githubusercontent.com/joBr99/nspanel-lovelace-ui/main/tasmota/autoexec.be"
# Setup date callback
time = datetime.time(0, 0, 0)
self.api.run_daily(self.update_date, time)
# send date update in case config has been changed
self.update_date("")
mode = cfg.get("updateMode")
topic_send = cfg.get("panelSendTopic")
updater = Updater(send_mqtt_msg, topic_send, mode, desired_display_firmware_version, desired_display_firmware_url, desired_tasmota_driver_version, desired_tasmota_driver_url)
# Setup weather callback
weather_interval = 15 * 60 # 15 minutes
self.api.run_every(self.update_screensaver_weather, "now", weather_interval)
# set brightness of screensaver
if type(self.config["brightnessScreensaver"]) == int:
self.current_screensaver_brightness = self.config["brightnessScreensaver"]
elif type(self.config["brightnessScreensaver"]) == list:
sorted_timesets = sorted(self.config["brightnessScreensaver"], key=lambda d: self.api.parse_time(d['time']))
found_current_dim_value = False
for index, timeset in enumerate(sorted_timesets):
self.api.run_daily(self.update_screensaver_brightness, timeset["time"], value=timeset["value"])
self.api.log("Current time %s", self.api.get_now().time(), level="DEBUG")
if self.api.parse_time(timeset["time"]) > self.api.get_now().time() and not found_current_dim_value:
# first time after current time, set dim value
self.current_screensaver_brightness = sorted_timesets[index-1]["value"]
self.api.log("Setting dim value to %s", sorted_timesets[index-1]) #level="DEBUG"
found_current_dim_value = True
# still no dim value
if not found_current_dim_value:
self.current_screensaver_brightness = sorted_timesets[-1]["value"]
# send screensaver brightness in case config has changed
self.update_screensaver_brightness(kwargs={"value": self.current_screensaver_brightness})
# register callbacks
self.register_callbacks()
def filter_dict_from_item_list(self, items):
# remove all dicts from list
cleaned_list = []
for item in items:
# in case item is a dict, grab the item name
if type(item) is dict:
cleaned_list.append(next(iter(item)))
else:
cleaned_list.append(item)
return cleaned_list
def get_all_configured_items(self):
items = []
for page in self.config["pages"]:
if "item" in page:
items.append(page["item"])
if "items" in page:
items.extend(page["items"])
return self.filter_dict_from_item_list(items)
def check_items(self):
items = self.get_all_configured_items()
for item in items:
if self.api.entity_exists(item) or item == "delete":
self.api.log("Found configured item in Home Assistant %s", item, level="DEBUG")
else:
self.api.error("The following item does not exist in Home Assistant, configuration error: %s", item)
def register_callbacks(self):
items = self.get_all_configured_items()
for item in items:
self.api.log("Enable state callback for %s", item, level="DEBUG")
self.api.handle = self.api.listen_state(self.state_change_callback, entity_id=item, attribute="all")
def state_change_callback(self, entity, attribute, old, new, kwargs):
current_page_config = self.config["pages"][self.current_page_nr]
page_type = current_page_config["type"]
self.api.log(f"Got state_callback from {entity}", level="DEBUG")
if page_type in ["cardEntities", "cardGrid"]:
items = current_page_config["items"]
items_filtered = self.filter_dict_from_item_list(items)
if entity in items_filtered:
self.api.log(f"State change on current page for {entity}", level="DEBUG")
# send update of the page
self.generate_entities_page(items)
# send detail pages in case they are open
if(entity.startswith("cover")):
self.generate_shutter_detail_page(entity)
if(entity.startswith("light")):
self.generate_light_detail_page(entity)
return
if page_type in ["cardThermo", "cardMedia"]:
if entity == current_page_config["item"]:
self.api.log(f"State change on current page for {entity}", level="DEBUG")
# send update of the whole page
if page_type == "cardThermo":
self.generate_thermo_page(entity)
return
if page_type == "cardMedia":
self.generate_media_page(entity)
return
return
def send_mqtt_msg(self,msg):
self.api.log("Send Message to Tasmota: %s", msg) #, level="DEBUG"
self.mqtt.mqtt_publish(self.config["panelSendTopic"], msg)
def handle_mqtt_incoming_message(self, event_name, data, kwargs):
# Parse Json Message from Tasmota and strip out message from nextion display
data = json.loads(data["payload"])
# pass tasmota driver version to updater class
if("nlui_driver_version" in data):
msg = data["nlui_driver_version"]
self.api.log("Received Driver Version from Tasmota: %s", int(msg), level="DEBUG")
self.updater.set_tasmota_driver_version(int(msg))
return
if("CustomRecv" not in data):
self.api.log("Received Message from Tasmota, but not from nextion screen: %s", data, level="DEBUG")
return
msg = data["CustomRecv"]
self.api.log("Received Message from Tasmota: %s", msg) #, level="DEBUG"
# Split message into parts seperated by ","
msg = msg.split(",")
# run action based on received command
if msg[0] == "event":
if msg[1] == "startup":
self.api.log("Handling startup event", level="DEBUG")
# grab version from screen and pass to updater class
self.updater.set_current_display_firmware_version(int(msg[2]))
# send date and time
self.update_time("")
self.update_date("")
# set screensaver timeout
timeout = self.config["timeoutScreensaver"]
self.send_mqtt_msg(f"timeout,{timeout}")
# send screensaver brightness
self.update_screensaver_brightness(kwargs={"value": self.current_screensaver_brightness})
# check for updates
msg_send = self.updater.check_updates()
# send messages for current page
if not msg_send:
self.generate_page(self.current_page_nr)
if msg[1] == "pageOpen":
# Calculate current page
recv_page = int(msg[2])
self.current_page_nr = recv_page % len(self.config["pages"])
self.api.log("Received pageOpen command, raw page: %i, calc page: %i", recv_page, self.current_page_nr, level="DEBUG")
# generate commands for current page
self.generate_page(self.current_page_nr)
if msg[1] == "buttonPress":
entity_id = msg[4]
btype = msg[6]
if len(msg) > 7:
value = msg[7]
else:
value = None
self.handle_button_press(entity_id, btype, value)
if msg[1] == "buttonPress2":
entity_id = msg[2]
btype = msg[3]
if len(msg) > 4:
value = msg[4]
else:
value = None
self.handle_button_press(entity_id, btype, value)
if msg[1] == "pageOpenDetail":
self.api.log("Received pageOpenDetail command", level="DEBUG")
if msg[2] == "popupShutter":
self.generate_shutter_detail_page(msg[3])
if msg[2] == "popupLight":
self.generate_light_detail_page(msg[3])
if msg[1] == "screensaverOpen":
self.update_screensaver_weather("")
def update_time(self, kwargs):
time = datetime.datetime.now().strftime(self.config["timeFormat"])
self.send_mqtt_msg(f"time,{time}")
def update_date(self, kwargs):
global babel_spec
if babel_spec is not None:
self.api.log("Babel package found", level="DEBUG")
if "dateFormatBabel" in self.config:
dateformat = self.config["dateFormatBabel"]
else:
dateformat = "full"
date = babel.dates.format_date(datetime.datetime.now(), dateformat, locale=self.config["locale"])
self.send_mqtt_msg(f"date,?{date}")
else:
self.api.log("Babel package not found", level="DEBUG")
date = datetime.datetime.now().strftime(self.config["dateFormat"])
self.send_mqtt_msg(f"date,?{date}")
def update_screensaver_brightness(self, kwargs):
self.current_screensaver_brightness = kwargs['value']
self.send_mqtt_msg(f"dimmode,{self.current_screensaver_brightness}")
def update_screensaver_weather(self, kwargs):
if not ("weatherEntity" in self.config and self.api.entity_exists(self.config["weatherEntity"])):
return
we = self.api.get_entity(self.config["weatherEntity"])
unit = "°C"
icon_cur = get_icon_id_ha("weather", state=we.state)
text_cur = f"{we.attributes.temperature}{unit}"
icon_cur_detail = get_icon_id("water-percent")
text_cur_detail = f"{we.attributes.humidity} %"
up1 = we.attributes.forecast[0]['datetime']
up1 = datetime.datetime.fromisoformat(up1)
icon1 = get_icon_id_ha("weather", state=we.attributes.forecast[0]['condition'])
down1 = we.attributes.forecast[0]['temperature']
up2 = we.attributes.forecast[1]['datetime']
up2 = datetime.datetime.fromisoformat(up2)
icon2 = get_icon_id_ha("weather", state=we.attributes.forecast[1]['condition'])
down2 = we.attributes.forecast[1]['temperature']
global babel_spec
if babel_spec is not None:
up1 = babel.dates.format_date(up1, "E", locale=self.config["locale"])
up2 = babel.dates.format_date(up2, "E", locale=self.config["locale"])
else:
up1 = up1.strftime("%a")
up2 = up2.strftime("%a")
self.send_mqtt_msg(f"weatherUpdate,?{icon_cur}?{text_cur}?{icon_cur_detail}?{text_cur_detail}?{up1}?{icon1}?{down1}?{up2}?{icon2}?{down2}")
def handle_button_press(self, entity_id, btype, optVal=None):
if entity_id == "updateBerryNoYes" and optVal == "yes":
# go back to main page before starting the update
self.generate_page(self.current_page_nr)
self.updater.update_berry_driver()
elif entity_id == "updateBerryNoYes" and optVal == "no":
self.generate_page(self.current_page_nr)
if entity_id == "updateDisplayNoYes" and optVal == "yes":
self.updater.update_panel_driver()
elif entity_id == "updateDisplayNoYes" and optVal == "no":
self.generate_page(self.current_page_nr)
if btype == "OnOff":
if optVal == "1":
self.api.turn_on(entity_id)
else:
self.api.turn_off(entity_id)
if btype == "up":
self.api.get_entity(entity_id).call_service("open_cover")
if btype == "stop":
self.api.get_entity(entity_id).call_service("stop_cover")
if btype == "down":
self.api.get_entity(entity_id).call_service("close_cover")
if btype == "button":
if entity_id.startswith('scene'):
self.api.get_entity(entity_id).call_service("turn_on")
elif entity_id.startswith('light') or entity_id.startswith('switch') or entity_id.startswith('input_boolean'):
self.api.get_entity(entity_id).call_service("toggle")
else:
self.api.get_entity(entity_id).call_service("press")
if btype == "media-next":
self.api.get_entity(entity_id).call_service("media_next_track")
if btype == "media-back":
self.api.get_entity(entity_id).call_service("media_previous_track")
if btype == "media-pause":
self.api.get_entity(entity_id).call_service("media_play_pause")
if btype == "hvac_action":
self.api.get_entity(entity_id).call_service("set_hvac_mode", hvac_mode=optVal)
if btype == "brightnessSlider":
# scale 0-100 to ha brightness range
brightness = int(scale(int(optVal),(0,100),(0,255)))
self.api.get_entity(entity_id).call_service("turn_on", brightness=brightness)
if btype == "colorTempSlider":
entity = self.api.get_entity(entity_id)
#scale 0-100 from slider to color range of lamp
color_val = scale(int(optVal), (0, 100), (entity.attributes.min_mireds, entity.attributes.max_mireds))
self.api.get_entity(entity_id).call_service("turn_on", color_temp=color_val)
if btype == "colorWheel":
self.api.log(optVal)
optVal = optVal.split('|')
color = pos_to_color(int(optVal[0]), int(optVal[1]))
self.api.log(color)
self.api.get_entity(entity_id).call_service("turn_on", rgb_color=color)
if btype == "positionSlider":
pos = int(optVal)
self.api.get_entity(entity_id).call_service("set_cover_position", position=pos)
if btype == "volumeSlider":
pos = int(optVal)
# HA wants this value between 0 and 1 as float
pos = pos/100
self.api.get_entity(entity_id).call_service("volume_set", volume_level=pos)
if btype == "tempUpd":
temp = int(optVal)/10
self.api.get_entity(entity_id).call_service("set_temperature", temperature=temp)
def generate_page(self, page_number):
# get type of page
page_type = self.config["pages"][self.current_page_nr]["type"]
self.api.log("Generating page commands for page %i with type %s", self.current_page_nr, page_type, level="DEBUG")
# Send page type
self.send_mqtt_msg(f"pageType,{page_type}")
if page_type in ["cardEntities", "cardGrid"]:
self.generate_entities_page(self.config["pages"][self.current_page_nr]["items"])
if page_type == "cardThermo":
self.generate_thermo_page(self.config["pages"][self.current_page_nr]["item"])
if page_type == "cardMedia":
self.generate_media_page(self.config["pages"][self.current_page_nr]["item"])
def generate_entities_item(self, item):
icon = None
name = None
if type(item) is dict:
icon = next(iter(item.items()))[1].get('icon')
name = next(iter(item.items()))[1].get('name')
item = next(iter(item.items()))[0]
# type of the item is the string before the "." in the item name
item_type = item.split(".")[0]
self.api.log("Generating item command for %s with type %s", item, item_type, level="DEBUG")
if item_type == "delete":
return f",{item_type},,,,,"
if not self.api.entity_exists(item):
return f",text,{item},{get_icon_id('alert-circle-outline')},17299,Not found check, apps.yaml"
entity = self.api.get_entity(item)
name = name if name is not None else entity.attributes.friendly_name
if item_type == "cover":
icon_id = get_icon_id_ha("cover", state=entity.state, overwrite=icon)
return f",shutter,{item},{icon_id},17299,{name},"
if item_type == "light":
switch_val = 1 if entity.state == "on" else 0
icon_color = self.getEntityColor(entity)
icon_id = get_icon_id_ha("light", overwrite=icon)
return f",{item_type},{item},{icon_id},{icon_color},{name},{switch_val}"
if item_type in ["switch", "input_boolean"]:
icon_id = get_icon_id_ha(item_type, state=entity.state, overwrite=icon)
switch_val = 1 if entity.state == "on" else 0
icon_color = self.getEntityColor(entity)
return f",switch,{item},{icon_id},{icon_color},{name},{switch_val}"
if item_type in ["sensor", "binary_sensor"]:
device_class = self.get_safe_ha_attribute(entity.attributes, "device_class", "")
icon_id = get_icon_id_ha("sensor", state=entity.state, device_class=device_class, overwrite=icon)
unit_of_measurement = self.get_safe_ha_attribute(entity.attributes, "unit_of_measurement", "")
value = entity.state + " " + unit_of_measurement
icon_color = self.getEntityColor(entity)
return f",text,{item},{icon_id},{icon_color},{name},{value}"
if item_type in ["button", "input_button"]:
icon_id = get_icon_id_ha("button", overwrite=icon)
return f",button,{item},{icon_id},17299,{name},{self.button_text}"
if item_type == "scene":
icon_id = get_icon_id_ha("scene", overwrite=icon)
return f",button,{item},{icon_id},17299,{name},{self.scene_text}"
def generate_entities_page(self, items):
# Set Heading of Page
self.send_mqtt_msg(f"entityUpdHeading,{self.config['pages'][self.current_page_nr]['heading']}")
# Get items and construct cmd string
command = "entityUpd"
for item in items:
command += self.generate_entities_item(item)
self.send_mqtt_msg(command)
def get_safe_ha_attribute(self, eattr, attr, default):
return eattr[attr] if attr in eattr else default
def generate_thermo_page(self, item):
if not self.api.entity_exists(item):
command = f"entityUpd,{item},Not found,220,220,Not found,150,300,5"
else:
entity = self.api.get_entity(item)
heading = entity.attributes.friendly_name
current_temp = int(self.get_safe_ha_attribute(entity.attributes, "current_temperature", 0)*10)
dest_temp = int(self.get_safe_ha_attribute(entity.attributes, "temperature", 0)*10)
status = self.get_safe_ha_attribute(entity.attributes, "hvac_action", "")
min_temp = int(self.get_safe_ha_attribute(entity.attributes, "min_temp", 0)*10)
max_temp = int(self.get_safe_ha_attribute(entity.attributes, "max_temp", 0)*10)
step_temp = int(self.get_safe_ha_attribute(entity.attributes, "target_temp_step", 0.5)*10)
icon_res = ""
hvac_modes = self.get_safe_ha_attribute(entity.attributes, "hvac_modes", [])
for mode in hvac_modes:
icon_id = get_icon_id('alert-circle-outline')
color_on = 64512
if mode == "auto":
icon_id = get_icon_id("calendar-sync")
color_on = 1024
if mode == "heat":
icon_id = get_icon_id("fire")
color_on = 64512
if mode == "off":
icon_id = get_icon_id("power")
color_on = 35921
if mode == "cool":
icon_id = get_icon_id("snowflake")
color_on = 11487
if mode == "dry":
icon_id = get_icon_id("water-percent")
color_on = 60897
if mode == "fan_only":
icon_id = get_icon_id("fan")
color_on = 35921
state = 0
if(mode == entity.state):
state = 1
icon_res += f",{icon_id},{color_on},{state},{mode}"
len_hvac_modes = len(hvac_modes)
if len_hvac_modes%2 == 0:
# even
padding_len = int((4-len_hvac_modes)/2)
icon_res = ","*4*padding_len + icon_res + ","*4*padding_len
# use last 4 icons
icon_res = ","*4*5 + icon_res
else:
# uneven
padding_len = int((5-len_hvac_modes)/2)
icon_res = ","*4*padding_len + icon_res + ","*4*padding_len
# use first 5 icons
icon_res = icon_res + ","*4*4
command = f"entityUpd,{item},{heading},{current_temp},{dest_temp},{status},{min_temp},{max_temp},{step_temp}{icon_res}"
self.send_mqtt_msg(command)
def generate_media_page(self, item):
if not self.api.entity_exists(item):
command = f"entityUpd,|{item}|Not found|{get_icon_id('alert-circle-outline')}|Please check your|apps.yaml in AppDaemon|50|{get_icon_id('alert-circle-outline')}"
else:
entity = self.api.get_entity(item)
heading = entity.attributes.friendly_name
icon = 0
title = self.get_safe_ha_attribute(entity.attributes, "media_title", "")
author = self.get_safe_ha_attribute(entity.attributes, "media_artist", "")
volume = int(self.get_safe_ha_attribute(entity.attributes, "volume_level", 0)*100)
iconplaypause = get_icon_id("pause") if entity.state == "playing" else get_icon_id("play")
if "media_content_type" in entity.attributes:
if entity.attributes.media_content_type == "music":
icon = get_icon_id("music")
command = f"entityUpd,|{item}|{heading}|{icon}|{title}|{author}|{volume}|{iconplaypause}"
self.send_mqtt_msg(command)
def getEntityColor(self, entity):
attr = entity.attributes
default_color_on = rgb_dec565([253, 216, 53])
default_color_off = rgb_dec565([68, 115, 158])
icon_color = default_color_on if entity.state == "on" else default_color_off
if "rgb_color" in attr:
color = attr.rgb_color
if "brightness" in attr:
color = rgb_brightness(color, attr.brightness)
icon_color = rgb_dec565(color)
elif "brightness" in attr:
color = rgb_brightness([253, 216, 53], attr.brightness)
icon_color = rgb_dec565(color)
return icon_color
def generate_light_detail_page(self, entity):
entity = self.api.get_entity(entity)
switch_val = 1 if entity.state == "on" else 0
icon_color = self.getEntityColor(entity)
brightness = "disable"
color_temp = "disable"
color = "disable"
# scale 0-255 brightness from ha to 0-100
if entity.state == "on":
if "brightness" in entity.attributes:
brightness = int(scale(entity.attributes.brightness,(0,255),(0,100)))
else:
brightness = "disable"
if "color_temp" in entity.attributes.supported_color_modes:
if "color_temp" in entity.attributes:
# scale ha color temp range to 0-100
color_temp = int(scale(entity.attributes.color_temp,(entity.attributes.min_mireds, entity.attributes.max_mireds),(0,100)))
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 entity.attributes.supported_color_modes):
color = "enable"
else:
color = "disable"
self.send_mqtt_msg(f"entityUpdateDetail,{get_icon_id('lightbulb')},{icon_color},{switch_val},{brightness},{color_temp},{color}")
def generate_shutter_detail_page(self, entity):
pos = int(self.get_safe_ha_attribute(entity.attributes, "current_position", 50))
# reverse position for slider
pos = 100-pos
self.send_mqtt_msg(f"entityUpdateDetail,{pos}")
def send_message_page(self, id, heading, msg, b1, b2):
self.send_mqtt_msg(f"pageType,popupNotify")
self.send_mqtt_msg(f"entityUpdateDetail,|{id}|{heading}|65535|{b1}|65535|{b2}|65535|{msg}|65535|0")
topic_recv = cfg.get("panelRecvTopic")
LuiMqttListener(mqtt_api, topic_recv, controller, updater)
LOGGER.info('Started')