Compare commits

...

4 Commits

Author SHA1 Message Date
c319928c65 knxprod 2026-02-10 12:49:53 +01:00
c4734a11ca knxprod 2026-02-10 12:16:22 +01:00
e22979c1c7 Fixes 2026-02-10 11:54:01 +01:00
a430110f94 Fixes 2026-02-09 15:28:12 +01:00
29 changed files with 995 additions and 142 deletions

View File

@ -18,13 +18,13 @@
</ParameterRefs> </ParameterRefs>
<ComObjectTable> <ComObjectTable>
<ComObject Id="%AID%_O-%TT%%CC%%PPP+00%" Name="KOf-%TT%%CC%%PPP+00% " <ComObject Id="%AID%_O-%TT%%CC%%PPP+00%" Name="KOf-%TT%%CC%%PPP+00% "
Text="Eingang %CC%%Pn%" Number="%CC%%Pn%" Text="Eingang %CC%%Pn%" Number="%Pn%"
FunctionText="Bereich %CC%, Funktion Eingang %Pn%" ObjectSize="4 Bytes" FunctionText="Bereich %CC%, Funktion Eingang %Pn%" ObjectSize="4 Bytes"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" /> ReadOnInitFlag="Disabled" />
<ComObject Id="%AID%_O-%TT%%CC%%PPP+01%" Name="KOf-%TT%%CC%%PPP+01% " <ComObject Id="%AID%_O-%TT%%CC%%PPP+01%" Name="KOf-%TT%%CC%%PPP+01% "
Text="Ausgang %CC%%Pn+1%" Number="%CC%%Pn+1%" Text="Ausgang %CC%%Pn+1%" Number="%Pn+1%"
FunctionText="Bereich %CC%, Funktion Ausgang %Pn+1%" ObjectSize="4 Bytes" FunctionText="Bereich %CC%, Funktion Ausgang %Pn+1%" ObjectSize="4 Bytes"
ReadFlag="Disabled" WriteFlag="Disabled" CommunicationFlag="Enabled" ReadFlag="Disabled" WriteFlag="Disabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Disabled" TransmitFlag="Enabled" UpdateFlag="Disabled"

View File

@ -26,16 +26,23 @@
</ParameterType> </ParameterType>
<ParameterType Id="%AID%_PT-FunctionCount" Name="FunctionCount"> <ParameterType Id="%AID%_PT-FunctionCount" Name="FunctionCount">
<TypeRestriction Base="Value" SizeInBit="4"> <TypeRestriction Base="Value" SizeInBit="4">
<Enumeration Text="1" Value="0" Id="%ENID%" /> <Enumeration Text="0" Value="0" Id="%ENID%" />
<Enumeration Text="2" Value="1" Id="%ENID%" /> <Enumeration Text="1" Value="1" Id="%ENID%" />
<Enumeration Text="3" Value="2" Id="%ENID%" /> <Enumeration Text="2" Value="2" Id="%ENID%" />
<Enumeration Text="4" Value="3" Id="%ENID%" /> <Enumeration Text="3" Value="3" Id="%ENID%" />
<Enumeration Text="5" Value="4" Id="%ENID%" /> <Enumeration Text="4" Value="4" Id="%ENID%" />
<Enumeration Text="6" Value="5" Id="%ENID%" /> <Enumeration Text="5" Value="5" Id="%ENID%" />
<Enumeration Text="7" Value="6" Id="%ENID%" /> <Enumeration Text="6" Value="6" Id="%ENID%" />
<Enumeration Text="8" Value="7" Id="%ENID%" /> <Enumeration Text="7" Value="7" Id="%ENID%" />
<Enumeration Text="9" Value="8" Id="%ENID%" /> <Enumeration Text="8" Value="8" Id="%ENID%" />
<Enumeration Text="10" Value="9" Id="%ENID%" /> <Enumeration Text="9" Value="9" Id="%ENID%" />
<Enumeration Text="10" Value="10" Id="%ENID%" />
</TypeRestriction>
</ParameterType>
<ParameterType Id="%AID%_PT-Checkbox" Name="Checkbox">
<TypeRestriction Base="Value" SizeInBit="1" UIHint="CheckBox">
<Enumeration Text="Deaktiviert" Value="0" Id="%ENID%" />
<Enumeration Text="Aktiviert" Value="1" Id="%ENID%" />
</TypeRestriction> </TypeRestriction>
</ParameterType> </ParameterType>
<ParameterType Id="%AID%_PT-FunctionType" Name="FunctionType"> <ParameterType Id="%AID%_PT-FunctionType" Name="FunctionType">

View File

@ -7,9 +7,9 @@
<ApplicationPrograms> <ApplicationPrograms>
<ApplicationProgram Id="%AID%" ApplicationNumber="621"> <ApplicationProgram Id="%AID%" ApplicationNumber="621">
<op:part href="Function.parts.xml" name="FN" instances="10"> <op:part href="Function.parts.xml" name="FN" instances="10">
<op:param name="%ParamOffset%" value="59" increment="20" /> <op:param name="%ParamOffset%" value="62" increment="20" />
<op:param name="%Pl%" value="0" asLetter="true" increment="1" /> <op:param name="%Pl%" value="0" asLetter="true" increment="1" />
<op:param name="%Pn%" value="1" increment="2" digits="3" /> <op:param name="%Pn%" value="30" increment="2" digits="3" />
<op:param name="%PPP%" value="100" increment="100" digits="3" /> <op:param name="%PPP%" value="100" increment="100" digits="3" />
<op:param name="%TRef%" value="26" increment="1" digits="2" /> <op:param name="%TRef%" value="26" increment="1" digits="2" />
</op:part> </op:part>
@ -22,6 +22,63 @@
ParameterType="%AID%_PT-Text40Byte" ParameterType="%AID%_PT-Text40Byte"
Text="Beschreibung des Bereiches" Text="Beschreibung des Bereiches"
Value="" /> Value="" />
<!-- Checkbox-Parameter für Standard-KOs (3 Bytes Union, BitOffset 0-16) -->
<Union SizeInBit="24">
<Memory CodeSegment="%AID%_RS-04-00000" Offset="59" BitOffset="0" />
<Parameter Id="%AID%_UP-%TT%%CC%010" Name="p%C%ChkTemperatur"
ParameterType="%AID%_PT-Checkbox" Offset="0" BitOffset="0"
Text="Temperatur" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%011" Name="p%C%ChkLuftfeuchtigkeit"
ParameterType="%AID%_PT-Checkbox" Offset="0" BitOffset="1"
Text="Luftfeuchtigkeit" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%012" Name="p%C%ChkPraesenz"
ParameterType="%AID%_PT-Checkbox" Offset="0" BitOffset="2"
Text="Präsenz" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%013" Name="p%C%ChkFensterkontakt"
ParameterType="%AID%_PT-Checkbox" Offset="0" BitOffset="3"
Text="Fensterkontakt" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%014" Name="p%C%ChkSolltemperatur"
ParameterType="%AID%_PT-Checkbox" Offset="0" BitOffset="4"
Text="Solltemperatur" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%015" Name="p%C%ChkHeizungsventil"
ParameterType="%AID%_PT-Checkbox" Offset="0" BitOffset="5"
Text="Heizungsventil" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%016" Name="p%C%ChkSteckdose1"
ParameterType="%AID%_PT-Checkbox" Offset="0" BitOffset="6"
Text="Steckdose 1" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%017" Name="p%C%ChkSteckdose2"
ParameterType="%AID%_PT-Checkbox" Offset="0" BitOffset="7"
Text="Steckdose 2" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%018" Name="p%C%ChkLicht1"
ParameterType="%AID%_PT-Checkbox" Offset="1" BitOffset="0"
Text="Licht 1" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%019" Name="p%C%ChkLicht2"
ParameterType="%AID%_PT-Checkbox" Offset="1" BitOffset="1"
Text="Licht 2" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%020" Name="p%C%ChkDimmen1"
ParameterType="%AID%_PT-Checkbox" Offset="1" BitOffset="2"
Text="Dimmen 1" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%021" Name="p%C%ChkDimmen2"
ParameterType="%AID%_PT-Checkbox" Offset="1" BitOffset="3"
Text="Dimmen 2" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%022" Name="p%C%ChkJalousie1"
ParameterType="%AID%_PT-Checkbox" Offset="1" BitOffset="4"
Text="Jalousie 1" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%023" Name="p%C%ChkJalousie2"
ParameterType="%AID%_PT-Checkbox" Offset="1" BitOffset="5"
Text="Jalousie 2" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%024" Name="p%C%ChkCO2"
ParameterType="%AID%_PT-Checkbox" Offset="1" BitOffset="6"
Text="CO2" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%025" Name="p%C%ChkHelligkeit"
ParameterType="%AID%_PT-Checkbox" Offset="1" BitOffset="7"
Text="Helligkeit" Value="0" />
<Parameter Id="%AID%_UP-%TT%%CC%026" Name="p%C%ChkSzene"
ParameterType="%AID%_PT-Checkbox" Offset="2" BitOffset="0"
Text="Szene" Value="0" />
</Union>
<Parameter Id="%AID%_P-%TT%%CC%005" Name="p%C%FunctionCount" Offset="0" <Parameter Id="%AID%_P-%TT%%CC%005" Name="p%C%FunctionCount" Offset="0"
BitOffset="8" ParameterType="%AID%_PT-FunctionCount" BitOffset="8" ParameterType="%AID%_PT-FunctionCount"
Text="Anzahl der Funktionen" Value="0" /> Text="Anzahl der Funktionen" Value="0" />
@ -32,6 +89,42 @@
<ParameterRef Id="%AID%_P-%TT%%CC%002_R-%TT%%CC%00201" <ParameterRef Id="%AID%_P-%TT%%CC%002_R-%TT%%CC%00201"
RefId="%AID%_P-%TT%%CC%002" /> RefId="%AID%_P-%TT%%CC%002" />
<!-- Checkbox ParameterRefs -->
<ParameterRef Id="%AID%_UP-%TT%%CC%010_R-%TT%%CC%01001"
RefId="%AID%_UP-%TT%%CC%010" />
<ParameterRef Id="%AID%_UP-%TT%%CC%011_R-%TT%%CC%01101"
RefId="%AID%_UP-%TT%%CC%011" />
<ParameterRef Id="%AID%_UP-%TT%%CC%012_R-%TT%%CC%01201"
RefId="%AID%_UP-%TT%%CC%012" />
<ParameterRef Id="%AID%_UP-%TT%%CC%013_R-%TT%%CC%01301"
RefId="%AID%_UP-%TT%%CC%013" />
<ParameterRef Id="%AID%_UP-%TT%%CC%014_R-%TT%%CC%01401"
RefId="%AID%_UP-%TT%%CC%014" />
<ParameterRef Id="%AID%_UP-%TT%%CC%015_R-%TT%%CC%01501"
RefId="%AID%_UP-%TT%%CC%015" />
<ParameterRef Id="%AID%_UP-%TT%%CC%016_R-%TT%%CC%01601"
RefId="%AID%_UP-%TT%%CC%016" />
<ParameterRef Id="%AID%_UP-%TT%%CC%017_R-%TT%%CC%01701"
RefId="%AID%_UP-%TT%%CC%017" />
<ParameterRef Id="%AID%_UP-%TT%%CC%018_R-%TT%%CC%01801"
RefId="%AID%_UP-%TT%%CC%018" />
<ParameterRef Id="%AID%_UP-%TT%%CC%019_R-%TT%%CC%01901"
RefId="%AID%_UP-%TT%%CC%019" />
<ParameterRef Id="%AID%_UP-%TT%%CC%020_R-%TT%%CC%02001"
RefId="%AID%_UP-%TT%%CC%020" />
<ParameterRef Id="%AID%_UP-%TT%%CC%021_R-%TT%%CC%02101"
RefId="%AID%_UP-%TT%%CC%021" />
<ParameterRef Id="%AID%_UP-%TT%%CC%022_R-%TT%%CC%02201"
RefId="%AID%_UP-%TT%%CC%022" />
<ParameterRef Id="%AID%_UP-%TT%%CC%023_R-%TT%%CC%02301"
RefId="%AID%_UP-%TT%%CC%023" />
<ParameterRef Id="%AID%_UP-%TT%%CC%024_R-%TT%%CC%02401"
RefId="%AID%_UP-%TT%%CC%024" />
<ParameterRef Id="%AID%_UP-%TT%%CC%025_R-%TT%%CC%02501"
RefId="%AID%_UP-%TT%%CC%025" />
<ParameterRef Id="%AID%_UP-%TT%%CC%026_R-%TT%%CC%02601"
RefId="%AID%_UP-%TT%%CC%026" />
<ParameterRef Id="%AID%_UP-%TT%%CC%005_R-%TT%%CC%00501" <ParameterRef Id="%AID%_UP-%TT%%CC%005_R-%TT%%CC%00501"
RefId="%AID%_P-%TT%%CC%005" /> RefId="%AID%_P-%TT%%CC%005" />
@ -39,9 +132,276 @@
instanceTo="10" /> instanceTo="10" />
</ParameterRefs> </ParameterRefs>
<ComObjectTable> <ComObjectTable>
<!-- Standard-KOs (Index 0-28) -->
<!-- 0: Temperatur -->
<ComObject Id="%AID%_O-%TT%%CC%S000" Name="KoS%CC%000"
Text="Temperatur" Number="0"
FunctionText="Bereich %CC%, Temperatur" ObjectSize="2 Bytes"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 1: Luftfeuchtigkeit -->
<ComObject Id="%AID%_O-%TT%%CC%S001" Name="KoS%CC%001"
Text="Luftfeuchtigkeit" Number="1"
FunctionText="Bereich %CC%, Luftfeuchtigkeit" ObjectSize="2 Bytes"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 2: Präsenz -->
<ComObject Id="%AID%_O-%TT%%CC%S002" Name="KoS%CC%002"
Text="Präsenz" Number="2"
FunctionText="Bereich %CC%, Präsenz" ObjectSize="1 Bit"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 3: Fensterkontakt -->
<ComObject Id="%AID%_O-%TT%%CC%S003" Name="KoS%CC%003"
Text="Fensterkontakt" Number="3"
FunctionText="Bereich %CC%, Fensterkontakt" ObjectSize="1 Bit"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 4: Solltemperatur -->
<ComObject Id="%AID%_O-%TT%%CC%S004" Name="KoS%CC%004"
Text="Solltemperatur" Number="4"
FunctionText="Bereich %CC%, Solltemperatur" ObjectSize="2 Bytes"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 5: Heizungsventil -->
<ComObject Id="%AID%_O-%TT%%CC%S005" Name="KoS%CC%005"
Text="Heizungsventil" Number="5"
FunctionText="Bereich %CC%, Heizungsventil" ObjectSize="1 Byte"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 6: Steckdose 1 Schalten -->
<ComObject Id="%AID%_O-%TT%%CC%S006" Name="KoS%CC%006"
Text="Steckdose 1 Schalten" Number="6"
FunctionText="Bereich %CC%, Steckdose 1 Schalten" ObjectSize="1 Bit"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 7: Steckdose 1 Status -->
<ComObject Id="%AID%_O-%TT%%CC%S007" Name="KoS%CC%007"
Text="Steckdose 1 Status" Number="7"
FunctionText="Bereich %CC%, Steckdose 1 Status" ObjectSize="1 Bit"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 8: Steckdose 2 Schalten -->
<ComObject Id="%AID%_O-%TT%%CC%S008" Name="KoS%CC%008"
Text="Steckdose 2 Schalten" Number="8"
FunctionText="Bereich %CC%, Steckdose 2 Schalten" ObjectSize="1 Bit"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 9: Steckdose 2 Status -->
<ComObject Id="%AID%_O-%TT%%CC%S009" Name="KoS%CC%009"
Text="Steckdose 2 Status" Number="9"
FunctionText="Bereich %CC%, Steckdose 2 Status" ObjectSize="1 Bit"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 10: Licht 1 Schalten -->
<ComObject Id="%AID%_O-%TT%%CC%S010" Name="KoS%CC%010"
Text="Licht 1 Schalten" Number="10"
FunctionText="Bereich %CC%, Licht 1 Schalten" ObjectSize="1 Bit"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 11: Licht 1 Status -->
<ComObject Id="%AID%_O-%TT%%CC%S011" Name="KoS%CC%011"
Text="Licht 1 Status" Number="11"
FunctionText="Bereich %CC%, Licht 1 Status" ObjectSize="1 Bit"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 12: Licht 2 Schalten -->
<ComObject Id="%AID%_O-%TT%%CC%S012" Name="KoS%CC%012"
Text="Licht 2 Schalten" Number="12"
FunctionText="Bereich %CC%, Licht 2 Schalten" ObjectSize="1 Bit"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 13: Licht 2 Status -->
<ComObject Id="%AID%_O-%TT%%CC%S013" Name="KoS%CC%013"
Text="Licht 2 Status" Number="13"
FunctionText="Bereich %CC%, Licht 2 Status" ObjectSize="1 Bit"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 14: Dimmen 1 Schalten -->
<ComObject Id="%AID%_O-%TT%%CC%S014" Name="KoS%CC%014"
Text="Dimmen 1 Schalten" Number="14"
FunctionText="Bereich %CC%, Dimmen 1 Schalten" ObjectSize="1 Bit"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 15: Dimmen 1 Wert -->
<ComObject Id="%AID%_O-%TT%%CC%S015" Name="KoS%CC%015"
Text="Dimmen 1 Wert" Number="15"
FunctionText="Bereich %CC%, Dimmen 1 Wert" ObjectSize="1 Byte"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 16: Dimmen 1 Status -->
<ComObject Id="%AID%_O-%TT%%CC%S016" Name="KoS%CC%016"
Text="Dimmen 1 Status" Number="16"
FunctionText="Bereich %CC%, Dimmen 1 Status" ObjectSize="1 Byte"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 17: Dimmen 2 Schalten -->
<ComObject Id="%AID%_O-%TT%%CC%S017" Name="KoS%CC%017"
Text="Dimmen 2 Schalten" Number="17"
FunctionText="Bereich %CC%, Dimmen 2 Schalten" ObjectSize="1 Bit"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 18: Dimmen 2 Wert -->
<ComObject Id="%AID%_O-%TT%%CC%S018" Name="KoS%CC%018"
Text="Dimmen 2 Wert" Number="18"
FunctionText="Bereich %CC%, Dimmen 2 Wert" ObjectSize="1 Byte"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 19: Dimmen 2 Status -->
<ComObject Id="%AID%_O-%TT%%CC%S019" Name="KoS%CC%019"
Text="Dimmen 2 Status" Number="19"
FunctionText="Bereich %CC%, Dimmen 2 Status" ObjectSize="1 Byte"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 20: Jalousie 1 Auf/Ab -->
<ComObject Id="%AID%_O-%TT%%CC%S020" Name="KoS%CC%020"
Text="Jalousie 1 Auf/Ab" Number="20"
FunctionText="Bereich %CC%, Jalousie 1 Auf/Ab" ObjectSize="1 Bit"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 21: Jalousie 1 Position -->
<ComObject Id="%AID%_O-%TT%%CC%S021" Name="KoS%CC%021"
Text="Jalousie 1 Position" Number="21"
FunctionText="Bereich %CC%, Jalousie 1 Position" ObjectSize="1 Byte"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 22: Jalousie 1 Pos. Status -->
<ComObject Id="%AID%_O-%TT%%CC%S022" Name="KoS%CC%022"
Text="Jalousie 1 Pos. Status" Number="22"
FunctionText="Bereich %CC%, Jalousie 1 Pos. Status" ObjectSize="1 Byte"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 23: Jalousie 2 Auf/Ab -->
<ComObject Id="%AID%_O-%TT%%CC%S023" Name="KoS%CC%023"
Text="Jalousie 2 Auf/Ab" Number="23"
FunctionText="Bereich %CC%, Jalousie 2 Auf/Ab" ObjectSize="1 Bit"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 24: Jalousie 2 Position -->
<ComObject Id="%AID%_O-%TT%%CC%S024" Name="KoS%CC%024"
Text="Jalousie 2 Position" Number="24"
FunctionText="Bereich %CC%, Jalousie 2 Position" ObjectSize="1 Byte"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 25: Jalousie 2 Pos. Status -->
<ComObject Id="%AID%_O-%TT%%CC%S025" Name="KoS%CC%025"
Text="Jalousie 2 Pos. Status" Number="25"
FunctionText="Bereich %CC%, Jalousie 2 Pos. Status" ObjectSize="1 Byte"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 26: CO2 -->
<ComObject Id="%AID%_O-%TT%%CC%S026" Name="KoS%CC%026"
Text="CO2" Number="26"
FunctionText="Bereich %CC%, CO2" ObjectSize="2 Bytes"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 27: Helligkeit -->
<ComObject Id="%AID%_O-%TT%%CC%S027" Name="KoS%CC%027"
Text="Helligkeit" Number="27"
FunctionText="Bereich %CC%, Helligkeit" ObjectSize="2 Bytes"
ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Disabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- 28: Szene -->
<ComObject Id="%AID%_O-%TT%%CC%S028" Name="KoS%CC%028"
Text="Szene" Number="28"
FunctionText="Bereich %CC%, Szene" ObjectSize="1 Byte"
ReadFlag="Enabled" WriteFlag="Enabled" CommunicationFlag="Enabled"
TransmitFlag="Enabled" UpdateFlag="Enabled"
ReadOnInitFlag="Disabled" />
<!-- Benutzerdefinierte Funktions-KOs (ab Index 30) -->
<op:usePart name="FN" xpath="//ComObjectTable/*" /> <op:usePart name="FN" xpath="//ComObjectTable/*" />
</ComObjectTable> </ComObjectTable>
<ComObjectRefs> <ComObjectRefs>
<!-- Standard-KO ComObjectRefs mit DPT -->
<ComObjectRef Id="%AID%_O-%TT%%CC%S000_R-%TT%%CC%S00001"
RefId="%AID%_O-%TT%%CC%S000" DatapointType="DPST-9-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S001_R-%TT%%CC%S00101"
RefId="%AID%_O-%TT%%CC%S001" DatapointType="DPST-9-7" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S002_R-%TT%%CC%S00201"
RefId="%AID%_O-%TT%%CC%S002" DatapointType="DPST-1-18" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S003_R-%TT%%CC%S00301"
RefId="%AID%_O-%TT%%CC%S003" DatapointType="DPST-1-19" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S004_R-%TT%%CC%S00401"
RefId="%AID%_O-%TT%%CC%S004" DatapointType="DPST-9-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S005_R-%TT%%CC%S00501"
RefId="%AID%_O-%TT%%CC%S005" DatapointType="DPST-5-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S006_R-%TT%%CC%S00601"
RefId="%AID%_O-%TT%%CC%S006" DatapointType="DPST-1-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S007_R-%TT%%CC%S00701"
RefId="%AID%_O-%TT%%CC%S007" DatapointType="DPST-1-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S008_R-%TT%%CC%S00801"
RefId="%AID%_O-%TT%%CC%S008" DatapointType="DPST-1-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S009_R-%TT%%CC%S00901"
RefId="%AID%_O-%TT%%CC%S009" DatapointType="DPST-1-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S010_R-%TT%%CC%S01001"
RefId="%AID%_O-%TT%%CC%S010" DatapointType="DPST-1-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S011_R-%TT%%CC%S01101"
RefId="%AID%_O-%TT%%CC%S011" DatapointType="DPST-1-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S012_R-%TT%%CC%S01201"
RefId="%AID%_O-%TT%%CC%S012" DatapointType="DPST-1-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S013_R-%TT%%CC%S01301"
RefId="%AID%_O-%TT%%CC%S013" DatapointType="DPST-1-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S014_R-%TT%%CC%S01401"
RefId="%AID%_O-%TT%%CC%S014" DatapointType="DPST-1-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S015_R-%TT%%CC%S01501"
RefId="%AID%_O-%TT%%CC%S015" DatapointType="DPST-5-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S016_R-%TT%%CC%S01601"
RefId="%AID%_O-%TT%%CC%S016" DatapointType="DPST-5-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S017_R-%TT%%CC%S01701"
RefId="%AID%_O-%TT%%CC%S017" DatapointType="DPST-1-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S018_R-%TT%%CC%S01801"
RefId="%AID%_O-%TT%%CC%S018" DatapointType="DPST-5-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S019_R-%TT%%CC%S01901"
RefId="%AID%_O-%TT%%CC%S019" DatapointType="DPST-5-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S020_R-%TT%%CC%S02001"
RefId="%AID%_O-%TT%%CC%S020" DatapointType="DPST-1-8" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S021_R-%TT%%CC%S02101"
RefId="%AID%_O-%TT%%CC%S021" DatapointType="DPST-5-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S022_R-%TT%%CC%S02201"
RefId="%AID%_O-%TT%%CC%S022" DatapointType="DPST-5-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S023_R-%TT%%CC%S02301"
RefId="%AID%_O-%TT%%CC%S023" DatapointType="DPST-1-8" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S024_R-%TT%%CC%S02401"
RefId="%AID%_O-%TT%%CC%S024" DatapointType="DPST-5-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S025_R-%TT%%CC%S02501"
RefId="%AID%_O-%TT%%CC%S025" DatapointType="DPST-5-1" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S026_R-%TT%%CC%S02601"
RefId="%AID%_O-%TT%%CC%S026" DatapointType="DPST-9-8" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S027_R-%TT%%CC%S02701"
RefId="%AID%_O-%TT%%CC%S027" DatapointType="DPST-9-4" />
<ComObjectRef Id="%AID%_O-%TT%%CC%S028_R-%TT%%CC%S02801"
RefId="%AID%_O-%TT%%CC%S028" DatapointType="DPST-17-1" />
<!-- Benutzerdefinierte Funktions-KO Refs -->
<op:usePart name="FN" xpath="//ComObjectRefs/*" /> <op:usePart name="FN" xpath="//ComObjectRefs/*" />
</ComObjectRefs> </ComObjectRefs>
</Static> </Static>
@ -63,6 +423,161 @@
<ParameterRefRef RefId="%AID%_P-%TT%%CC%002_R-%TT%%CC%00201" <ParameterRefRef RefId="%AID%_P-%TT%%CC%002_R-%TT%%CC%00201"
IndentLevel="1" /> IndentLevel="1" />
<!-- Standard-Objekte -->
<ParameterSeparator Id="%AID%_PS-nnn" Text="Standard-Objekte"
UIHint="Headline" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%010_R-%TT%%CC%01001"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%011_R-%TT%%CC%01101"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%012_R-%TT%%CC%01201"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%013_R-%TT%%CC%01301"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%014_R-%TT%%CC%01401"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%015_R-%TT%%CC%01501"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%016_R-%TT%%CC%01601"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%017_R-%TT%%CC%01701"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%018_R-%TT%%CC%01801"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%019_R-%TT%%CC%01901"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%020_R-%TT%%CC%02001"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%021_R-%TT%%CC%02101"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%022_R-%TT%%CC%02201"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%023_R-%TT%%CC%02301"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%024_R-%TT%%CC%02401"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%025_R-%TT%%CC%02501"
IndentLevel="1" />
<ParameterRefRef RefId="%AID%_UP-%TT%%CC%026_R-%TT%%CC%02601"
IndentLevel="1" />
<!-- Bedingte KO-Anzeige per Checkbox: Temperatur -->
<choose ParamRefId="%AID%_UP-%TT%%CC%010_R-%TT%%CC%01001">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S000_R-%TT%%CC%S00001" />
</when>
</choose>
<!-- Luftfeuchtigkeit -->
<choose ParamRefId="%AID%_UP-%TT%%CC%011_R-%TT%%CC%01101">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S001_R-%TT%%CC%S00101" />
</when>
</choose>
<!-- Präsenz -->
<choose ParamRefId="%AID%_UP-%TT%%CC%012_R-%TT%%CC%01201">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S002_R-%TT%%CC%S00201" />
</when>
</choose>
<!-- Fensterkontakt -->
<choose ParamRefId="%AID%_UP-%TT%%CC%013_R-%TT%%CC%01301">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S003_R-%TT%%CC%S00301" />
</when>
</choose>
<!-- Solltemperatur -->
<choose ParamRefId="%AID%_UP-%TT%%CC%014_R-%TT%%CC%01401">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S004_R-%TT%%CC%S00401" />
</when>
</choose>
<!-- Heizungsventil -->
<choose ParamRefId="%AID%_UP-%TT%%CC%015_R-%TT%%CC%01501">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S005_R-%TT%%CC%S00501" />
</when>
</choose>
<!-- Steckdose 1 (2 KOs) -->
<choose ParamRefId="%AID%_UP-%TT%%CC%016_R-%TT%%CC%01601">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S006_R-%TT%%CC%S00601" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S007_R-%TT%%CC%S00701" />
</when>
</choose>
<!-- Steckdose 2 (2 KOs) -->
<choose ParamRefId="%AID%_UP-%TT%%CC%017_R-%TT%%CC%01701">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S008_R-%TT%%CC%S00801" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S009_R-%TT%%CC%S00901" />
</when>
</choose>
<!-- Licht 1 (2 KOs) -->
<choose ParamRefId="%AID%_UP-%TT%%CC%018_R-%TT%%CC%01801">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S010_R-%TT%%CC%S01001" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S011_R-%TT%%CC%S01101" />
</when>
</choose>
<!-- Licht 2 (2 KOs) -->
<choose ParamRefId="%AID%_UP-%TT%%CC%019_R-%TT%%CC%01901">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S012_R-%TT%%CC%S01201" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S013_R-%TT%%CC%S01301" />
</when>
</choose>
<!-- Dimmen 1 (3 KOs) -->
<choose ParamRefId="%AID%_UP-%TT%%CC%020_R-%TT%%CC%02001">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S014_R-%TT%%CC%S01401" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S015_R-%TT%%CC%S01501" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S016_R-%TT%%CC%S01601" />
</when>
</choose>
<!-- Dimmen 2 (3 KOs) -->
<choose ParamRefId="%AID%_UP-%TT%%CC%021_R-%TT%%CC%02101">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S017_R-%TT%%CC%S01701" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S018_R-%TT%%CC%S01801" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S019_R-%TT%%CC%S01901" />
</when>
</choose>
<!-- Jalousie 1 (3 KOs) -->
<choose ParamRefId="%AID%_UP-%TT%%CC%022_R-%TT%%CC%02201">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S020_R-%TT%%CC%S02001" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S021_R-%TT%%CC%S02101" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S022_R-%TT%%CC%S02201" />
</when>
</choose>
<!-- Jalousie 2 (3 KOs) -->
<choose ParamRefId="%AID%_UP-%TT%%CC%023_R-%TT%%CC%02301">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S023_R-%TT%%CC%S02301" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S024_R-%TT%%CC%S02401" />
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S025_R-%TT%%CC%S02501" />
</when>
</choose>
<!-- CO2 -->
<choose ParamRefId="%AID%_UP-%TT%%CC%024_R-%TT%%CC%02401">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S026_R-%TT%%CC%S02601" />
</when>
</choose>
<!-- Helligkeit -->
<choose ParamRefId="%AID%_UP-%TT%%CC%025_R-%TT%%CC%02501">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S027_R-%TT%%CC%S02701" />
</when>
</choose>
<!-- Szene -->
<choose ParamRefId="%AID%_UP-%TT%%CC%026_R-%TT%%CC%02601">
<when test="1">
<ComObjectRefRef RefId="%AID%_O-%TT%%CC%S028_R-%TT%%CC%S02801" />
</when>
</choose>
<!-- Funktionen -->
<ParameterSeparator Id="%AID%_PS-nnn" Text="Funktionen" <ParameterSeparator Id="%AID%_PS-nnn" Text="Funktionen"
UIHint="Headline" /> UIHint="Headline" />
<ParameterRefRef <ParameterRefRef
@ -73,55 +588,55 @@
xpath="//Dynamic/ChannelIndependentBlock/*" instance="1" /> xpath="//Dynamic/ChannelIndependentBlock/*" instance="1" />
<choose ParamRefId="%AID%_UP-%TT%%CC%005_R-%TT%%CC%00501"> <choose ParamRefId="%AID%_UP-%TT%%CC%005_R-%TT%%CC%00501">
<!-- FN 2 --> <!-- FN 2 -->
<when test=">0"> <when test=">1">
<op:usePart name="FN" <op:usePart name="FN"
xpath="//Dynamic/ChannelIndependentBlock/*" xpath="//Dynamic/ChannelIndependentBlock/*"
instance="2" /> instance="2" />
</when> </when>
<!-- FN 3 --> <!-- FN 3 -->
<when test=">1"> <when test=">2">
<op:usePart name="FN" <op:usePart name="FN"
xpath="//Dynamic/ChannelIndependentBlock/*" xpath="//Dynamic/ChannelIndependentBlock/*"
instance="3" /> instance="3" />
</when> </when>
<!-- FN 4 --> <!-- FN 4 -->
<when test=">2"> <when test=">3">
<op:usePart name="FN" <op:usePart name="FN"
xpath="//Dynamic/ChannelIndependentBlock/*" xpath="//Dynamic/ChannelIndependentBlock/*"
instance="4" /> instance="4" />
</when> </when>
<!-- FN 5 --> <!-- FN 5 -->
<when test=">3"> <when test=">4">
<op:usePart name="FN" <op:usePart name="FN"
xpath="//Dynamic/ChannelIndependentBlock/*" xpath="//Dynamic/ChannelIndependentBlock/*"
instance="5" /> instance="5" />
</when> </when>
<!-- FN 6 --> <!-- FN 6 -->
<when test=">4"> <when test=">5">
<op:usePart name="FN" <op:usePart name="FN"
xpath="//Dynamic/ChannelIndependentBlock/*" xpath="//Dynamic/ChannelIndependentBlock/*"
instance="6" /> instance="6" />
</when> </when>
<!-- FN 7 --> <!-- FN 7 -->
<when test=">5"> <when test=">6">
<op:usePart name="FN" <op:usePart name="FN"
xpath="//Dynamic/ChannelIndependentBlock/*" xpath="//Dynamic/ChannelIndependentBlock/*"
instance="7" /> instance="7" />
</when> </when>
<!-- FN 8 --> <!-- FN 8 -->
<when test=">6"> <when test=">7">
<op:usePart name="FN" <op:usePart name="FN"
xpath="//Dynamic/ChannelIndependentBlock/*" xpath="//Dynamic/ChannelIndependentBlock/*"
instance="8" /> instance="8" />
</when> </when>
<!-- FN 9 --> <!-- FN 9 -->
<when test=">7"> <when test=">8">
<op:usePart name="FN" <op:usePart name="FN"
xpath="//Dynamic/ChannelIndependentBlock/*" xpath="//Dynamic/ChannelIndependentBlock/*"
instance="9" /> instance="9" />
</when> </when>
<!-- FN 10 --> <!-- FN 10 -->
<when test=">8"> <when test=">9">
<op:usePart name="FN" <op:usePart name="FN"
xpath="//Dynamic/ChannelIndependentBlock/*" xpath="//Dynamic/ChannelIndependentBlock/*"
instance="10" /> instance="10" />

View File

@ -29,8 +29,8 @@
<op:define prefix="SC" <op:define prefix="SC"
share="Section.share.xml" share="Section.share.xml"
template="Section.templ.xml" template="Section.templ.xml"
NumChannels="1" NumChannels="25"
KoOffset="20" KoOffset="10"
ModuleType="10" > ModuleType="10" >
<!--<op:verify File="../lib/OFM-LogicModule/library.json" ModuleVersion="%LOG_VerifyVersion%" /> --> <!--<op:verify File="../lib/OFM-LogicModule/library.json" ModuleVersion="%LOG_VerifyVersion%" /> -->
</op:define> </op:define>

View File

@ -321,6 +321,7 @@ bool KnxWorker::getGroupObjectInfo(size_t index, KnxGroupObjectInfo& info) {
info.commFlag = go.commFlag(); info.commFlag = go.commFlag();
info.readFlag = go.readEnable(); info.readFlag = go.readEnable();
info.writeFlag = go.writeEnable(); info.writeFlag = go.writeEnable();
info.transmitFlag = go.transmitEnable();
// Resolve the primary group address via association/address tables // Resolve the primary group address via association/address tables
info.groupAddress = resolveGroupAddress(static_cast<uint16_t>(index)); info.groupAddress = resolveGroupAddress(static_cast<uint16_t>(index));
@ -333,6 +334,47 @@ bool KnxWorker::getGroupObjectInfo(size_t index, KnxGroupObjectInfo& info) {
#endif #endif
} }
bool KnxWorker::sendSwitch(uint16_t groupAddr, bool value) {
#if !UART_DEBUG_MODE
if (!knxBau.configured()) {
ESP_LOGW(TAG, "sendSwitch: KNX not configured");
return false;
}
// Find the GroupObject index for this group address
size_t goCount = getGroupObjectCount();
for (size_t i = 1; i <= goCount; i++) {
uint16_t addr = resolveGroupAddress(static_cast<uint16_t>(i));
if (addr == groupAddr) {
GroupObject& go = knx.getGroupObject(i);
// Check if this GO can transmit
if (!go.transmitEnable()) {
ESP_LOGW(TAG, "sendSwitch: GO %d cannot transmit", (int)i);
return false;
}
// Set value and trigger send
KNXValue knxVal = value;
if (go.value(knxVal, DPT_Switch)) {
ESP_LOGI(TAG, "sendSwitch: GA=%d GO=%d value=%d", groupAddr, (int)i, value);
return true;
} else {
ESP_LOGW(TAG, "sendSwitch: Failed to set value for GO %d", (int)i);
return false;
}
}
}
ESP_LOGW(TAG, "sendSwitch: No GO found for GA=%d", groupAddr);
return false;
#else
(void)groupAddr;
(void)value;
return false;
#endif
}
void KnxWorker::formatGroupAddress(uint16_t addr, char* buf, size_t bufSize) { void KnxWorker::formatGroupAddress(uint16_t addr, char* buf, size_t bufSize) {
// Format: main/middle/sub (5/3/8 bit) // Format: main/middle/sub (5/3/8 bit)
uint8_t main = (addr >> 11) & 0x1F; uint8_t main = (addr >> 11) & 0x1F;

View File

@ -11,7 +11,8 @@ struct KnxGroupObjectInfo {
uint8_t dptSub; // DPT Subtyp uint8_t dptSub; // DPT Subtyp
bool commFlag; // Kommunikations-Flag bool commFlag; // Kommunikations-Flag
bool readFlag; // Lese-Flag bool readFlag; // Lese-Flag
bool writeFlag; // Schreib-Flag bool writeFlag; // Schreib-Flag (empfangen vom Bus)
bool transmitFlag; // Sende-Flag (senden auf den Bus)
}; };
class KnxWorker { class KnxWorker {
@ -28,6 +29,9 @@ public:
size_t getGroupObjectCount(); size_t getGroupObjectCount();
bool getGroupObjectInfo(size_t index, KnxGroupObjectInfo& info); bool getGroupObjectInfo(size_t index, KnxGroupObjectInfo& info);
// KNX Telegramm senden
bool sendSwitch(uint16_t groupAddr, bool value);
// Gruppenadresse als String formatieren (z.B. "1/2/3") // Gruppenadresse als String formatieren (z.B. "1/2/3")
static void formatGroupAddress(uint16_t addr, char* buf, size_t bufSize); static void formatGroupAddress(uint16_t addr, char* buf, size_t bufSize);

View File

@ -119,20 +119,30 @@ void WidgetConfig::serialize(uint8_t* buf) const {
buf[pos++] = cardStyle; buf[pos++] = cardStyle;
for (size_t i = 0; i < MAX_SUBBUTTONS; ++i) { for (size_t i = 0; i < MAX_SUBBUTTONS; ++i) {
const SubButtonConfig& sb = subButtons[i]; const SubButtonConfig& sb = subButtons[i];
buf[pos++] = sb.iconCodepoint & 0xFF; buf[pos++] = sb.iconCodepointOff & 0xFF;
buf[pos++] = (sb.iconCodepoint >> 8) & 0xFF; buf[pos++] = (sb.iconCodepointOff >> 8) & 0xFF;
buf[pos++] = (sb.iconCodepoint >> 16) & 0xFF; buf[pos++] = (sb.iconCodepointOff >> 16) & 0xFF;
buf[pos++] = (sb.iconCodepoint >> 24) & 0xFF; buf[pos++] = (sb.iconCodepointOff >> 24) & 0xFF;
buf[pos++] = sb.iconCodepointOn & 0xFF;
buf[pos++] = (sb.iconCodepointOn >> 8) & 0xFF;
buf[pos++] = (sb.iconCodepointOn >> 16) & 0xFF;
buf[pos++] = (sb.iconCodepointOn >> 24) & 0xFF;
buf[pos++] = sb.knxAddrRead & 0xFF; buf[pos++] = sb.knxAddrRead & 0xFF;
buf[pos++] = (sb.knxAddrRead >> 8) & 0xFF; buf[pos++] = (sb.knxAddrRead >> 8) & 0xFF;
buf[pos++] = sb.knxAddrWrite & 0xFF; buf[pos++] = sb.knxAddrWrite & 0xFF;
buf[pos++] = (sb.knxAddrWrite >> 8) & 0xFF; buf[pos++] = (sb.knxAddrWrite >> 8) & 0xFF;
buf[pos++] = sb.colorOn.r; buf[pos++] = sb.bgColorOn.r;
buf[pos++] = sb.colorOn.g; buf[pos++] = sb.bgColorOn.g;
buf[pos++] = sb.colorOn.b; buf[pos++] = sb.bgColorOn.b;
buf[pos++] = sb.colorOff.r; buf[pos++] = sb.bgColorOff.r;
buf[pos++] = sb.colorOff.g; buf[pos++] = sb.bgColorOff.g;
buf[pos++] = sb.colorOff.b; buf[pos++] = sb.bgColorOff.b;
buf[pos++] = sb.iconColorOn.r;
buf[pos++] = sb.iconColorOn.g;
buf[pos++] = sb.iconColorOn.b;
buf[pos++] = sb.iconColorOff.r;
buf[pos++] = sb.iconColorOff.g;
buf[pos++] = sb.iconColorOff.b;
buf[pos++] = static_cast<uint8_t>(sb.position); buf[pos++] = static_cast<uint8_t>(sb.position);
buf[pos++] = static_cast<uint8_t>(sb.action); buf[pos++] = static_cast<uint8_t>(sb.action);
buf[pos++] = sb.targetScreen; buf[pos++] = sb.targetScreen;
@ -303,18 +313,27 @@ void WidgetConfig::deserialize(const uint8_t* buf) {
for (size_t i = 0; i < MAX_SUBBUTTONS; ++i) { for (size_t i = 0; i < MAX_SUBBUTTONS; ++i) {
SubButtonConfig& sb = subButtons[i]; SubButtonConfig& sb = subButtons[i];
if (pos + 20 <= SERIALIZED_SIZE) { if (pos + 30 <= SERIALIZED_SIZE) {
sb.iconCodepoint = buf[pos] | (buf[pos + 1] << 8) | sb.iconCodepointOff = buf[pos] | (buf[pos + 1] << 8) |
(buf[pos + 2] << 16) | (buf[pos + 3] << 24); (buf[pos + 2] << 16) | (buf[pos + 3] << 24);
pos += 4;
sb.iconCodepointOn = buf[pos] | (buf[pos + 1] << 8) |
(buf[pos + 2] << 16) | (buf[pos + 3] << 24);
pos += 4; pos += 4;
sb.knxAddrRead = buf[pos] | (buf[pos + 1] << 8); pos += 2; sb.knxAddrRead = buf[pos] | (buf[pos + 1] << 8); pos += 2;
sb.knxAddrWrite = buf[pos] | (buf[pos + 1] << 8); pos += 2; sb.knxAddrWrite = buf[pos] | (buf[pos + 1] << 8); pos += 2;
sb.colorOn.r = buf[pos++]; sb.bgColorOn.r = buf[pos++];
sb.colorOn.g = buf[pos++]; sb.bgColorOn.g = buf[pos++];
sb.colorOn.b = buf[pos++]; sb.bgColorOn.b = buf[pos++];
sb.colorOff.r = buf[pos++]; sb.bgColorOff.r = buf[pos++];
sb.colorOff.g = buf[pos++]; sb.bgColorOff.g = buf[pos++];
sb.colorOff.b = buf[pos++]; sb.bgColorOff.b = buf[pos++];
sb.iconColorOn.r = buf[pos++];
sb.iconColorOn.g = buf[pos++];
sb.iconColorOn.b = buf[pos++];
sb.iconColorOff.r = buf[pos++];
sb.iconColorOff.g = buf[pos++];
sb.iconColorOff.b = buf[pos++];
sb.position = static_cast<SubButtonPosition>(buf[pos++]); sb.position = static_cast<SubButtonPosition>(buf[pos++]);
sb.action = static_cast<SubButtonAction>(buf[pos++]); sb.action = static_cast<SubButtonAction>(buf[pos++]);
sb.targetScreen = buf[pos++]; sb.targetScreen = buf[pos++];
@ -559,6 +578,8 @@ void GuiConfig::clear() {
knxDateAddress = 0; knxDateAddress = 0;
knxDateTimeAddress = 0; knxDateTimeAddress = 0;
knxNightModeAddress = 0; knxNightModeAddress = 0;
screenAnimType = ScreenAnimType::FADE;
screenAnimDuration = 300; // Default 300ms
for (size_t i = 0; i < MAX_SCREENS; i++) { for (size_t i = 0; i < MAX_SCREENS; i++) {
screens[i].clear(static_cast<uint8_t>(i), nullptr); screens[i].clear(static_cast<uint8_t>(i), nullptr);
} }

View File

@ -169,19 +169,22 @@ enum class SubButtonAction : uint8_t {
NAVIGATE = 1, // Navigate to screen NAVIGATE = 1, // Navigate to screen
}; };
// Sub-button configuration for RoomCard (20 bytes) // Sub-button configuration for RoomCard (30 bytes)
struct SubButtonConfig { struct SubButtonConfig {
uint32_t iconCodepoint; // 4 bytes - Icon codepoint uint32_t iconCodepointOff; // 4 bytes - Icon codepoint when OFF
uint32_t iconCodepointOn; // 4 bytes - Icon codepoint when ON (0 = use iconCodepointOff)
uint16_t knxAddrRead; // 2 bytes - KNX address to read status uint16_t knxAddrRead; // 2 bytes - KNX address to read status
uint16_t knxAddrWrite; // 2 bytes - KNX address to write on click uint16_t knxAddrWrite; // 2 bytes - KNX address to write on click
Color colorOn; // 3 bytes - Color when ON Color bgColorOn; // 3 bytes - Background color when ON
Color colorOff; // 3 bytes - Color when OFF Color bgColorOff; // 3 bytes - Background color when OFF
Color iconColorOn; // 3 bytes - Icon color when ON
Color iconColorOff; // 3 bytes - Icon color when OFF
SubButtonPosition position; // 1 byte - Position around bubble SubButtonPosition position; // 1 byte - Position around bubble
SubButtonAction action; // 1 byte - Action type SubButtonAction action; // 1 byte - Action type
uint8_t targetScreen; // 1 byte - Target screen for navigate uint8_t targetScreen; // 1 byte - Target screen for navigate
bool enabled; // 1 byte - Is this sub-button active? bool enabled; // 1 byte - Is this sub-button active?
uint8_t _padding[2]; // 2 bytes - Alignment padding uint8_t _padding[2]; // 2 bytes - Alignment padding
// Total: 20 bytes per SubButton // Total: 30 bytes per SubButton
}; };
// Text line configuration for RoomCard (24 bytes) // Text line configuration for RoomCard (24 bytes)
@ -296,8 +299,8 @@ struct WidgetConfig {
uint8_t arcValueFontSize; // Center value font size index uint8_t arcValueFontSize; // Center value font size index
// Serialization size (fixed for NVS storage) // Serialization size (fixed for NVS storage)
// 331 + 14 (arcMin + arcMax + arcUnit + arcShowValue + arcScaleOffset + arcScaleColor + arcValueColor + arcValueFontSize) = 345 // 369 + 36 (6 subbuttons * 6 bytes for icon colors) = 405
static constexpr size_t SERIALIZED_SIZE = 345; static constexpr size_t SERIALIZED_SIZE = 405;
void serialize(uint8_t* buf) const; void serialize(uint8_t* buf) const;
void deserialize(const uint8_t* buf); void deserialize(const uint8_t* buf);
@ -340,6 +343,20 @@ struct ScreenConfig {
const WidgetConfig* findWidget(uint8_t id) const; const WidgetConfig* findWidget(uint8_t id) const;
}; };
// Screen transition animation types
enum class ScreenAnimType : uint8_t {
NONE = 0,
FADE = 1,
SLIDE_LEFT = 2,
SLIDE_RIGHT = 3,
SLIDE_UP = 4,
SLIDE_DOWN = 5,
OVER_LEFT = 6,
OVER_RIGHT = 7,
OVER_UP = 8,
OVER_DOWN = 9,
};
struct GuiConfig { struct GuiConfig {
uint8_t screenCount; uint8_t screenCount;
ScreenConfig screens[MAX_SCREENS]; ScreenConfig screens[MAX_SCREENS];
@ -352,6 +369,10 @@ struct GuiConfig {
uint16_t knxDateTimeAddress; uint16_t knxDateTimeAddress;
uint16_t knxNightModeAddress; uint16_t knxNightModeAddress;
// Screen transition animation
ScreenAnimType screenAnimType;
uint16_t screenAnimDuration; // Duration in ms (0 = default 300ms)
void clear(); void clear();
ScreenConfig* findScreen(uint8_t id); ScreenConfig* findScreen(uint8_t id);
const ScreenConfig* findScreen(uint8_t id) const; const ScreenConfig* findScreen(uint8_t id) const;

View File

@ -4,6 +4,7 @@
#include "widgets/RoomCardTileWidget.hpp" #include "widgets/RoomCardTileWidget.hpp"
#include "HistoryStore.hpp" #include "HistoryStore.hpp"
#include "SdCard.hpp" #include "SdCard.hpp"
#include "Gui.hpp"
#include "esp_lv_adapter.h" #include "esp_lv_adapter.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_timer.h" #include "esp_timer.h"
@ -358,6 +359,23 @@ void WidgetManager::applyScreen(uint8_t screenId) {
esp_lv_adapter_unlock(); esp_lv_adapter_unlock();
} }
// Helper to convert ScreenAnimType to LVGL animation type (LVGL 9.x API)
static lv_screen_load_anim_t screenAnimToLvgl(ScreenAnimType type) {
switch (type) {
case ScreenAnimType::NONE: return LV_SCREEN_LOAD_ANIM_NONE;
case ScreenAnimType::FADE: return LV_SCREEN_LOAD_ANIM_FADE_IN;
case ScreenAnimType::SLIDE_LEFT: return LV_SCREEN_LOAD_ANIM_MOVE_LEFT;
case ScreenAnimType::SLIDE_RIGHT: return LV_SCREEN_LOAD_ANIM_MOVE_RIGHT;
case ScreenAnimType::SLIDE_UP: return LV_SCREEN_LOAD_ANIM_MOVE_TOP;
case ScreenAnimType::SLIDE_DOWN: return LV_SCREEN_LOAD_ANIM_MOVE_BOTTOM;
case ScreenAnimType::OVER_LEFT: return LV_SCREEN_LOAD_ANIM_OVER_LEFT;
case ScreenAnimType::OVER_RIGHT: return LV_SCREEN_LOAD_ANIM_OVER_RIGHT;
case ScreenAnimType::OVER_UP: return LV_SCREEN_LOAD_ANIM_OVER_TOP;
case ScreenAnimType::OVER_DOWN: return LV_SCREEN_LOAD_ANIM_OVER_BOTTOM;
default: return LV_SCREEN_LOAD_ANIM_FADE_IN;
}
}
void WidgetManager::applyScreenLocked(uint8_t screenId) { void WidgetManager::applyScreenLocked(uint8_t screenId) {
ESP_LOGI(TAG, "applyScreen(%d) - start", screenId); ESP_LOGI(TAG, "applyScreen(%d) - start", screenId);
@ -374,35 +392,40 @@ void WidgetManager::applyScreenLocked(uint8_t screenId) {
closeModalLocked(); closeModalLocked();
} }
lv_display_t* disp = lv_display_get_default(); // SAFE DESTRUCTION of C++ widgets:
if (disp) {
lv_display_enable_invalidation(disp, false);
}
// SAFE DESTRUCTION:
// 1. Mark all C++ widgets as "LVGL object already gone" // 1. Mark all C++ widgets as "LVGL object already gone"
for (auto& widget : widgets_) { for (auto& widget : widgets_) {
if (widget) widget->clearLvglObject(); if (widget) widget->clearLvglObject();
} }
// 2. Clean layer_top (sub-buttons etc)
// 2. Delete all LVGL objects on layers we use
lv_obj_clean(lv_scr_act());
lv_obj_clean(lv_layer_top()); lv_obj_clean(lv_layer_top());
// 3. Destroy C++ objects (their destructors won't call lv_obj_delete)
// 3. Now destroy C++ objects (their destructors won't call lv_obj_delete)
destroyAllWidgets(); destroyAllWidgets();
// Create a NEW screen object for the transition
lv_obj_t* newScreen = lv_obj_create(NULL);
lv_obj_remove_style_all(newScreen);
lv_obj_set_size(newScreen, LV_PCT(100), LV_PCT(100));
lv_obj_clear_flag(newScreen, LV_OBJ_FLAG_SCROLLABLE);
ESP_LOGI(TAG, "Creating new widgets for screen '%s' (%d widgets)...", ESP_LOGI(TAG, "Creating new widgets for screen '%s' (%d widgets)...",
screen->name, screen->widgetCount); screen->name, screen->widgetCount);
lv_obj_t* root = lv_scr_act(); createAllWidgets(*screen, newScreen);
createAllWidgets(*screen, root);
ESP_LOGI(TAG, "Widgets created"); ESP_LOGI(TAG, "Widgets created");
applyCachedValuesToWidgets(); applyCachedValuesToWidgets();
if (disp) { // Get animation settings
lv_display_enable_invalidation(disp, true); ESP_LOGI(TAG, "Animation config: type=%d, duration=%u",
} (int)config_->screenAnimType, config_->screenAnimDuration);
lv_obj_invalidate(lv_scr_act()); lv_screen_load_anim_t animType = screenAnimToLvgl(config_->screenAnimType);
uint32_t animDuration = config_->screenAnimDuration > 0 ? config_->screenAnimDuration : 300;
ESP_LOGI(TAG, "Loading screen with animation: lvgl_type=%d, duration=%lums",
(int)animType, (unsigned long)animDuration);
// Load the new screen with animation (old screen auto-deleted after animation)
// Parameters: new_scr, anim_type, time_ms, delay_ms, auto_del_old_scr
lv_screen_load_anim(newScreen, animType, animDuration, 0, true);
ESP_LOGI(TAG, "applyScreen(%d) - complete", screenId); ESP_LOGI(TAG, "applyScreen(%d) - complete", screenId);
} }
@ -604,13 +627,31 @@ void WidgetManager::handleButtonAction(const WidgetConfig& cfg, lv_obj_t* target
case ButtonAction::KNX: case ButtonAction::KNX:
default: { default: {
if (cfg.knxAddressWrite > 0) { if (cfg.knxAddressWrite > 0) {
bool state = false; bool currentState = false;
if (target) { if (target) {
state = (lv_obj_get_state(target) & LV_STATE_CHECKED) != 0; currentState = (lv_obj_get_state(target) & LV_STATE_CHECKED) != 0;
}
// For toggle buttons: send the opposite of current state
// For non-toggle buttons: send ON (true)
bool sendValue = cfg.isToggle ? !currentState : true;
ESP_LOGI(TAG, "Button %d clicked, KNX write to %d, value=%d (toggle=%d, currentState=%d)",
cfg.id, cfg.knxAddressWrite, sendValue, cfg.isToggle, currentState);
// Send KNX telegram
if (Gui::knxWorker.sendSwitch(cfg.knxAddressWrite, sendValue)) {
// Update local UI state immediately for responsive feedback
if (cfg.isToggle && target) {
if (sendValue) {
lv_obj_add_state(target, LV_STATE_CHECKED);
} else {
lv_obj_clear_state(target, LV_STATE_CHECKED);
}
}
// Also update the cache
cacheKnxSwitch(cfg.knxAddressWrite, sendValue);
} }
ESP_LOGI(TAG, "Button %d clicked, KNX write to %d, state=%d",
cfg.id, cfg.knxAddressWrite, state);
// TODO: Send KNX telegram
} }
break; break;
} }
@ -680,9 +721,12 @@ void WidgetManager::navigateBack() {
void WidgetManager::sendKnxSwitch(uint16_t groupAddr, bool value) { void WidgetManager::sendKnxSwitch(uint16_t groupAddr, bool value) {
ESP_LOGI(TAG, "sendKnxSwitch: GA=%d, value=%d", groupAddr, value); ESP_LOGI(TAG, "sendKnxSwitch: GA=%d, value=%d", groupAddr, value);
// TODO: Send actual KNX telegram via KnxWorker
// For now, just log and update cache so UI reflects the change // Send actual KNX telegram via KnxWorker
cacheKnxSwitch(groupAddr, value); if (Gui::knxWorker.sendSwitch(groupAddr, value)) {
// Update cache so UI reflects the change
cacheKnxSwitch(groupAddr, value);
}
} }
void WidgetManager::enterStandby() { void WidgetManager::enterStandby() {
@ -1481,6 +1525,11 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
cJSON_AddNumberToObject(knx, "dateTime", config_->knxDateTimeAddress); cJSON_AddNumberToObject(knx, "dateTime", config_->knxDateTimeAddress);
cJSON_AddNumberToObject(knx, "night", config_->knxNightModeAddress); cJSON_AddNumberToObject(knx, "night", config_->knxNightModeAddress);
// Screen transition animation
cJSON* anim = cJSON_AddObjectToObject(root, "screenAnim");
cJSON_AddNumberToObject(anim, "type", static_cast<int>(config_->screenAnimType));
cJSON_AddNumberToObject(anim, "duration", config_->screenAnimDuration);
cJSON* screens = cJSON_AddArrayToObject(root, "screens"); cJSON* screens = cJSON_AddArrayToObject(root, "screens");
for (uint8_t s = 0; s < config_->screenCount; s++) { for (uint8_t s = 0; s < config_->screenCount; s++) {
@ -1693,19 +1742,26 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
cJSON* sbJson = cJSON_CreateObject(); cJSON* sbJson = cJSON_CreateObject();
cJSON_AddNumberToObject(sbJson, "pos", static_cast<int>(sb.position)); cJSON_AddNumberToObject(sbJson, "pos", static_cast<int>(sb.position));
cJSON_AddNumberToObject(sbJson, "icon", sb.iconCodepoint); cJSON_AddNumberToObject(sbJson, "iconOff", sb.iconCodepointOff);
cJSON_AddNumberToObject(sbJson, "iconOn", sb.iconCodepointOn);
cJSON_AddNumberToObject(sbJson, "knxRead", sb.knxAddrRead); cJSON_AddNumberToObject(sbJson, "knxRead", sb.knxAddrRead);
cJSON_AddNumberToObject(sbJson, "knxWrite", sb.knxAddrWrite); cJSON_AddNumberToObject(sbJson, "knxWrite", sb.knxAddrWrite);
cJSON_AddNumberToObject(sbJson, "action", static_cast<int>(sb.action)); cJSON_AddNumberToObject(sbJson, "action", static_cast<int>(sb.action));
cJSON_AddNumberToObject(sbJson, "target", sb.targetScreen); cJSON_AddNumberToObject(sbJson, "target", sb.targetScreen);
char colorOnStr[8], colorOffStr[8]; char bgColorOnStr[8], bgColorOffStr[8], iconColorOnStr[8], iconColorOffStr[8];
snprintf(colorOnStr, sizeof(colorOnStr), "#%02X%02X%02X", snprintf(bgColorOnStr, sizeof(bgColorOnStr), "#%02X%02X%02X",
sb.colorOn.r, sb.colorOn.g, sb.colorOn.b); sb.bgColorOn.r, sb.bgColorOn.g, sb.bgColorOn.b);
snprintf(colorOffStr, sizeof(colorOffStr), "#%02X%02X%02X", snprintf(bgColorOffStr, sizeof(bgColorOffStr), "#%02X%02X%02X",
sb.colorOff.r, sb.colorOff.g, sb.colorOff.b); sb.bgColorOff.r, sb.bgColorOff.g, sb.bgColorOff.b);
cJSON_AddStringToObject(sbJson, "colorOn", colorOnStr); snprintf(iconColorOnStr, sizeof(iconColorOnStr), "#%02X%02X%02X",
cJSON_AddStringToObject(sbJson, "colorOff", colorOffStr); sb.iconColorOn.r, sb.iconColorOn.g, sb.iconColorOn.b);
snprintf(iconColorOffStr, sizeof(iconColorOffStr), "#%02X%02X%02X",
sb.iconColorOff.r, sb.iconColorOff.g, sb.iconColorOff.b);
cJSON_AddStringToObject(sbJson, "bgColorOn", bgColorOnStr);
cJSON_AddStringToObject(sbJson, "bgColorOff", bgColorOffStr);
cJSON_AddStringToObject(sbJson, "iconColorOn", iconColorOnStr);
cJSON_AddStringToObject(sbJson, "iconColorOff", iconColorOffStr);
cJSON_AddItemToArray(subButtons, sbJson); cJSON_AddItemToArray(subButtons, sbJson);
} }
@ -2035,9 +2091,10 @@ bool WidgetManager::updateConfigFromJson(const char* json) {
cond.style.flags |= ConditionStyle::FLAG_USE_TEXT_COLOR; cond.style.flags |= ConditionStyle::FLAG_USE_TEXT_COLOR;
} }
// Background color // Background color (only if not empty and starts with #)
cJSON* bgColor = cJSON_GetObjectItem(condItem, "bgColor"); cJSON* bgColor = cJSON_GetObjectItem(condItem, "bgColor");
if (cJSON_IsString(bgColor)) { if (cJSON_IsString(bgColor) && bgColor->valuestring &&
bgColor->valuestring[0] == '#' && strlen(bgColor->valuestring) >= 4) {
cond.style.bgColor = Color::fromHex(parseHexColor(bgColor->valuestring)); cond.style.bgColor = Color::fromHex(parseHexColor(bgColor->valuestring));
cond.style.flags |= ConditionStyle::FLAG_USE_BG_COLOR; cond.style.flags |= ConditionStyle::FLAG_USE_BG_COLOR;
} }
@ -2139,9 +2196,20 @@ bool WidgetManager::updateConfigFromJson(const char* json) {
sb.position = static_cast<SubButtonPosition>(pos->valueint); sb.position = static_cast<SubButtonPosition>(pos->valueint);
} }
cJSON* icon = cJSON_GetObjectItem(sbItem, "icon"); cJSON* iconOff = cJSON_GetObjectItem(sbItem, "iconOff");
if (cJSON_IsNumber(icon)) { if (cJSON_IsNumber(iconOff)) {
sb.iconCodepoint = static_cast<uint32_t>(icon->valuedouble); sb.iconCodepointOff = static_cast<uint32_t>(iconOff->valuedouble);
} else {
// Backward compatibility: try old "icon" field
cJSON* icon = cJSON_GetObjectItem(sbItem, "icon");
if (cJSON_IsNumber(icon)) {
sb.iconCodepointOff = static_cast<uint32_t>(icon->valuedouble);
}
}
cJSON* iconOn = cJSON_GetObjectItem(sbItem, "iconOn");
if (cJSON_IsNumber(iconOn)) {
sb.iconCodepointOn = static_cast<uint32_t>(iconOn->valuedouble);
} }
cJSON* knxRead = cJSON_GetObjectItem(sbItem, "knxRead"); cJSON* knxRead = cJSON_GetObjectItem(sbItem, "knxRead");
@ -2164,14 +2232,42 @@ bool WidgetManager::updateConfigFromJson(const char* json) {
sb.targetScreen = target->valueint; sb.targetScreen = target->valueint;
} }
cJSON* colorOn = cJSON_GetObjectItem(sbItem, "colorOn"); // Background colors
if (cJSON_IsString(colorOn)) { cJSON* bgColorOn = cJSON_GetObjectItem(sbItem, "bgColorOn");
sb.colorOn = Color::fromHex(parseHexColor(colorOn->valuestring)); if (cJSON_IsString(bgColorOn)) {
sb.bgColorOn = Color::fromHex(parseHexColor(bgColorOn->valuestring));
} else {
// Backward compatibility: try old "colorOn" field
cJSON* colorOn = cJSON_GetObjectItem(sbItem, "colorOn");
if (cJSON_IsString(colorOn)) {
sb.bgColorOn = Color::fromHex(parseHexColor(colorOn->valuestring));
}
} }
cJSON* colorOff = cJSON_GetObjectItem(sbItem, "colorOff"); cJSON* bgColorOff = cJSON_GetObjectItem(sbItem, "bgColorOff");
if (cJSON_IsString(colorOff)) { if (cJSON_IsString(bgColorOff)) {
sb.colorOff = Color::fromHex(parseHexColor(colorOff->valuestring)); sb.bgColorOff = Color::fromHex(parseHexColor(bgColorOff->valuestring));
} else {
// Backward compatibility: try old "colorOff" field
cJSON* colorOff = cJSON_GetObjectItem(sbItem, "colorOff");
if (cJSON_IsString(colorOff)) {
sb.bgColorOff = Color::fromHex(parseHexColor(colorOff->valuestring));
}
}
// Icon colors (default to white if not specified)
cJSON* iconColorOn = cJSON_GetObjectItem(sbItem, "iconColorOn");
if (cJSON_IsString(iconColorOn)) {
sb.iconColorOn = Color::fromHex(parseHexColor(iconColorOn->valuestring));
} else {
sb.iconColorOn = {255, 255, 255}; // Default white
}
cJSON* iconColorOff = cJSON_GetObjectItem(sbItem, "iconColorOff");
if (cJSON_IsString(iconColorOff)) {
sb.iconColorOff = Color::fromHex(parseHexColor(iconColorOff->valuestring));
} else {
sb.iconColorOff = {255, 255, 255}; // Default white
} }
sbIdx++; sbIdx++;
@ -2375,6 +2471,19 @@ bool WidgetManager::updateConfigFromJson(const char* json) {
if (cJSON_IsNumber(nightAddr)) newConfig->knxNightModeAddress = nightAddr->valueint; if (cJSON_IsNumber(nightAddr)) newConfig->knxNightModeAddress = nightAddr->valueint;
} }
// Screen transition animation
cJSON* anim = cJSON_GetObjectItem(root, "screenAnim");
if (cJSON_IsObject(anim)) {
cJSON* animType = cJSON_GetObjectItem(anim, "type");
if (cJSON_IsNumber(animType)) {
newConfig->screenAnimType = static_cast<ScreenAnimType>(animType->valueint);
}
cJSON* animDuration = cJSON_GetObjectItem(anim, "duration");
if (cJSON_IsNumber(animDuration)) {
newConfig->screenAnimDuration = static_cast<uint16_t>(animDuration->valueint);
}
}
if (newConfig->screenCount == 0) { if (newConfig->screenCount == 0) {
cJSON_Delete(root); cJSON_Delete(root);
return false; return false;

View File

@ -24,7 +24,7 @@ esp_err_t WebServer::getKnxAddressesHandler(httpd_req_t* req) {
cJSON_AddStringToObject(obj, "addrStr", addrStr); cJSON_AddStringToObject(obj, "addrStr", addrStr);
cJSON_AddBoolToObject(obj, "comm", info.commFlag); cJSON_AddBoolToObject(obj, "comm", info.commFlag);
cJSON_AddBoolToObject(obj, "read", info.readFlag); cJSON_AddBoolToObject(obj, "read", info.readFlag);
cJSON_AddBoolToObject(obj, "write", info.writeFlag); cJSON_AddBoolToObject(obj, "write", info.transmitFlag); // transmit = kann auf Bus schreiben
cJSON_AddItemToArray(arr, obj); cJSON_AddItemToArray(arr, obj);
} }
} }

View File

@ -235,7 +235,9 @@ void ButtonWidget::applyStyle() {
lv_obj_set_style_text_color(iconLabel_, lv_color_make( lv_obj_set_style_text_color(iconLabel_, lv_color_make(
config_.textColor.r, config_.textColor.g, config_.textColor.b), 0); config_.textColor.r, config_.textColor.g, config_.textColor.b), 0);
uint8_t sizeIdx = config_.iconSize < 6 ? config_.iconSize : config_.fontSize; // Icon fonts support sizes 0-13 (14px to 260px)
uint8_t sizeIdx = config_.iconSize;
if (sizeIdx > 13) sizeIdx = 13;
lv_obj_set_style_text_font(iconLabel_, Fonts::iconFont(sizeIdx), 0); lv_obj_set_style_text_font(iconLabel_, Fonts::iconFont(sizeIdx), 0);
} }
} }
@ -336,10 +338,16 @@ bool ButtonWidget::evaluateConditions(float primaryValue, float secondaryValue,
} }
if (bestMatch->style.flags & ConditionStyle::FLAG_USE_BG_COLOR) { if (bestMatch->style.flags & ConditionStyle::FLAG_USE_BG_COLOR) {
lv_obj_set_style_bg_color(obj_, lv_color_make( lv_color_t bgColor = lv_color_make(
bestMatch->style.bgColor.r, bestMatch->style.bgColor.r,
bestMatch->style.bgColor.g, bestMatch->style.bgColor.g,
bestMatch->style.bgColor.b), 0); bestMatch->style.bgColor.b);
// Set for default state
lv_obj_set_style_bg_color(obj_, bgColor, LV_PART_MAIN | LV_STATE_DEFAULT);
// Also set for checked state (toggle buttons)
lv_obj_set_style_bg_color(obj_, bgColor, LV_PART_MAIN | LV_STATE_CHECKED);
// And for pressed state
lv_obj_set_style_bg_color(obj_, bgColor, LV_PART_MAIN | LV_STATE_PRESSED);
} }
if (bestMatch->style.flags & ConditionStyle::FLAG_USE_BG_OPACITY) { if (bestMatch->style.flags & ConditionStyle::FLAG_USE_BG_OPACITY) {
@ -411,8 +419,45 @@ void ButtonWidget::applyFakeShadowStyle() {
} }
void ButtonWidget::updateIcon(uint32_t codepoint) { void ButtonWidget::updateIcon(uint32_t codepoint) {
if (!iconLabel_ || codepoint == 0) return; if (codepoint == 0) return;
// If no icon label exists yet, we need to create it dynamically
if (!iconLabel_ && obj_ && Fonts::hasIconFont()) {
// For buttons without a content container, we need to create the icon label
if (!contentContainer_) {
// Create a simple icon label directly in the button
iconLabel_ = lv_label_create(obj_);
if (iconLabel_) {
lv_obj_clear_flag(iconLabel_, LV_OBJ_FLAG_CLICKABLE);
// Position it based on icon position setting
if (config_.iconPosition == static_cast<uint8_t>(IconPosition::LEFT)) {
lv_obj_align(iconLabel_, LV_ALIGN_LEFT_MID, config_.iconPositionX, 0);
} else if (config_.iconPosition == static_cast<uint8_t>(IconPosition::RIGHT)) {
lv_obj_align(iconLabel_, LV_ALIGN_RIGHT_MID, -config_.iconPositionX, 0);
} else {
lv_obj_align(iconLabel_, LV_ALIGN_CENTER, 0, 0);
}
}
}
}
if (!iconLabel_) return;
// Set the icon text
char iconText[5]; char iconText[5];
encodeUtf8(codepoint, iconText); encodeUtf8(codepoint, iconText);
lv_label_set_text(iconLabel_, iconText); lv_label_set_text(iconLabel_, iconText);
// Apply the correct font size
// If iconSize is 0 (no icon was originally configured), use fontSize instead
// Default to size 2 (22px) if both are 0
// Icon fonts support sizes 0-13 (14px to 260px)
uint8_t sizeIdx = config_.iconSize > 0 ? config_.iconSize : config_.fontSize;
if (sizeIdx == 0) sizeIdx = 2; // Default to medium size (22px)
if (sizeIdx > 13) sizeIdx = 13; // Cap at max icon font size
lv_obj_set_style_text_font(iconLabel_, Fonts::iconFont(sizeIdx), 0);
// Apply text color from config
lv_obj_set_style_text_color(iconLabel_, lv_color_make(
config_.textColor.r, config_.textColor.g, config_.textColor.b), 0);
} }

View File

@ -115,12 +115,13 @@ void RoomCardWidgetBase::createSubButtonsCommon() {
int16_t absY = config_.y + relY; int16_t absY = config_.y + relY;
lv_obj_set_pos(btn, absX, absY); lv_obj_set_pos(btn, absX, absY);
// Create icon // Create icon (use iconCodepointOff initially, will be updated based on state)
if (cfg.iconCodepoint > 0 && Fonts::hasIconFont()) { uint32_t initialIcon = cfg.iconCodepointOff;
if (initialIcon > 0 && Fonts::hasIconFont()) {
lv_obj_t* icon = lv_label_create(btn); lv_obj_t* icon = lv_label_create(btn);
lv_obj_clear_flag(icon, LV_OBJ_FLAG_CLICKABLE); lv_obj_clear_flag(icon, LV_OBJ_FLAG_CLICKABLE);
char iconText[5]; char iconText[5];
encodeUtf8(cfg.iconCodepoint, iconText); encodeUtf8(initialIcon, iconText);
lv_label_set_text(icon, iconText); lv_label_set_text(icon, iconText);
lv_obj_center(icon); lv_obj_center(icon);
subButtonIcons_[i] = icon; subButtonIcons_[i] = icon;
@ -149,7 +150,10 @@ void RoomCardWidgetBase::applySubButtonStyle() {
for (uint8_t i = 0; i < config_.subButtonCount && i < MAX_SUBBUTTONS; ++i) { for (uint8_t i = 0; i < config_.subButtonCount && i < MAX_SUBBUTTONS; ++i) {
if (subButtonIcons_[i] && subBtnIconFont) { if (subButtonIcons_[i] && subBtnIconFont) {
lv_obj_set_style_text_font(subButtonIcons_[i], subBtnIconFont, 0); lv_obj_set_style_text_font(subButtonIcons_[i], subBtnIconFont, 0);
lv_obj_set_style_text_color(subButtonIcons_[i], lv_color_white(), 0); // Set initial icon color based on state (OFF by default)
const Color& iconColor = subButtonStates_[i] ?
config_.subButtons[i].iconColorOn : config_.subButtons[i].iconColorOff;
lv_obj_set_style_text_color(subButtonIcons_[i], lv_color_hex(iconColor.toLvColor()), 0);
} }
} }
} }
@ -158,9 +162,25 @@ void RoomCardWidgetBase::updateSubButtonColor(uint8_t index) {
if (index >= MAX_SUBBUTTONS || !subButtonObjs_[index]) return; if (index >= MAX_SUBBUTTONS || !subButtonObjs_[index]) return;
const SubButtonConfig& cfg = config_.subButtons[index]; const SubButtonConfig& cfg = config_.subButtons[index];
const Color& color = subButtonStates_[index] ? cfg.colorOn : cfg.colorOff; bool isOn = subButtonStates_[index];
lv_obj_set_style_bg_color(subButtonObjs_[index], lv_color_hex(color.toLvColor()), 0); // Update background color
const Color& bgColor = isOn ? cfg.bgColorOn : cfg.bgColorOff;
lv_obj_set_style_bg_color(subButtonObjs_[index], lv_color_hex(bgColor.toLvColor()), 0);
// Update icon and icon color based on state
if (subButtonIcons_[index]) {
uint32_t iconCodepoint = isOn && cfg.iconCodepointOn > 0 ? cfg.iconCodepointOn : cfg.iconCodepointOff;
if (iconCodepoint > 0) {
char iconText[5];
encodeUtf8(iconCodepoint, iconText);
lv_label_set_text(subButtonIcons_[index], iconText);
}
// Set icon color
const Color& iconColor = isOn ? cfg.iconColorOn : cfg.iconColorOff;
lv_obj_set_style_text_color(subButtonIcons_[index], lv_color_hex(iconColor.toLvColor()), 0);
}
} }
void RoomCardWidgetBase::updateTemperature(float value) { void RoomCardWidgetBase::updateTemperature(float value) {

View File

@ -2845,8 +2845,8 @@ CONFIG_LV_COLOR_DEPTH=16
# #
# Memory Settings # Memory Settings
# #
CONFIG_LV_USE_BUILTIN_MALLOC=y # CONFIG_LV_USE_BUILTIN_MALLOC is not set
# CONFIG_LV_USE_CLIB_MALLOC is not set CONFIG_LV_USE_CLIB_MALLOC=y
# CONFIG_LV_USE_MICROPYTHON_MALLOC is not set # CONFIG_LV_USE_MICROPYTHON_MALLOC is not set
# CONFIG_LV_USE_RTTHREAD_MALLOC is not set # CONFIG_LV_USE_RTTHREAD_MALLOC is not set
# CONFIG_LV_USE_CUSTOM_MALLOC is not set # CONFIG_LV_USE_CUSTOM_MALLOC is not set
@ -2856,15 +2856,12 @@ CONFIG_LV_USE_BUILTIN_STRING=y
CONFIG_LV_USE_BUILTIN_SPRINTF=y CONFIG_LV_USE_BUILTIN_SPRINTF=y
# CONFIG_LV_USE_CLIB_SPRINTF is not set # CONFIG_LV_USE_CLIB_SPRINTF is not set
# CONFIG_LV_USE_CUSTOM_SPRINTF is not set # CONFIG_LV_USE_CUSTOM_SPRINTF is not set
CONFIG_LV_MEM_SIZE_KILOBYTES=128
CONFIG_LV_MEM_POOL_EXPAND_SIZE_KILOBYTES=0
CONFIG_LV_MEM_ADR=0x0
# end of Memory Settings # end of Memory Settings
# #
# HAL Settings # HAL Settings
# #
CONFIG_LV_DEF_REFR_PERIOD=33 CONFIG_LV_DEF_REFR_PERIOD=16
CONFIG_LV_DPI_DEF=130 CONFIG_LV_DPI_DEF=130
# end of HAL Settings # end of HAL Settings
@ -2936,12 +2933,12 @@ CONFIG_LV_USE_DRAW_SW_ASM=0
# #
CONFIG_LV_USE_LOG=y CONFIG_LV_USE_LOG=y
# CONFIG_LV_LOG_LEVEL_TRACE is not set # CONFIG_LV_LOG_LEVEL_TRACE is not set
CONFIG_LV_LOG_LEVEL_INFO=y # CONFIG_LV_LOG_LEVEL_INFO is not set
# CONFIG_LV_LOG_LEVEL_WARN is not set # CONFIG_LV_LOG_LEVEL_WARN is not set
# CONFIG_LV_LOG_LEVEL_ERROR is not set CONFIG_LV_LOG_LEVEL_ERROR=y
# CONFIG_LV_LOG_LEVEL_USER is not set # CONFIG_LV_LOG_LEVEL_USER is not set
# CONFIG_LV_LOG_LEVEL_NONE is not set # CONFIG_LV_LOG_LEVEL_NONE is not set
CONFIG_LV_LOG_LEVEL=1 CONFIG_LV_LOG_LEVEL=3
CONFIG_LV_LOG_PRINTF=y CONFIG_LV_LOG_PRINTF=y
CONFIG_LV_LOG_USE_TIMESTAMP=y CONFIG_LV_LOG_USE_TIMESTAMP=y
CONFIG_LV_LOG_USE_FILE_LINE=y CONFIG_LV_LOG_USE_FILE_LINE=y

View File

@ -30,6 +30,28 @@
<input class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" type="number" min="0" v-model.number="store.config.standby.minutes"> <input class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" type="number" min="0" v-model.number="store.config.standby.minutes">
</div> </div>
</div> </div>
<div class="flex flex-col gap-2.5">
<div class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">Screen-Animation</div>
<div class="flex items-center justify-between gap-2.5">
<label class="text-[12px] text-muted">Typ</label>
<select class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" v-model.number="store.config.screenAnim.type">
<option :value="0">Keine</option>
<option :value="1">Einblenden</option>
<option :value="2">Schieben Links</option>
<option :value="3">Schieben Rechts</option>
<option :value="4">Schieben Hoch</option>
<option :value="5">Schieben Runter</option>
<option :value="6">Ueberlagern Links</option>
<option :value="7">Ueberlagern Rechts</option>
<option :value="8">Ueberlagern Hoch</option>
<option :value="9">Ueberlagern Runter</option>
</select>
</div>
<div class="flex items-center justify-between gap-2.5">
<label class="text-[12px] text-muted">Dauer (ms)</label>
<input class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" type="number" min="0" max="2000" v-model.number="store.config.screenAnim.duration">
</div>
</div>
<div class="flex flex-col gap-2.5"> <div class="flex flex-col gap-2.5">
<div class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">KNX Zeit</div> <div class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">KNX Zeit</div>
<div class="flex items-center justify-between gap-2.5"> <div class="flex items-center justify-between gap-2.5">

View File

@ -71,6 +71,7 @@ const key = computed(() => w.value ? typeKeyFor(w.value.type) : 'label');
const showIconPicker = ref(false); const showIconPicker = ref(false);
const conditionIconPickerIdx = ref(-1); const conditionIconPickerIdx = ref(-1);
const subButtonIconPickerIdx = ref(-1); const subButtonIconPickerIdx = ref(-1);
const subButtonIconType = ref('off'); // 'off' or 'on'
const textLineIconPickerIdx = ref(-1); const textLineIconPickerIdx = ref(-1);
// Map widget types to settings components // Map widget types to settings components
@ -110,9 +111,10 @@ function openConditionIconPicker(idx) {
showIconPicker.value = true; showIconPicker.value = true;
} }
function openSubButtonIconPicker(idx) { function openSubButtonIconPicker(idx, type = 'off') {
conditionIconPickerIdx.value = -1; conditionIconPickerIdx.value = -1;
subButtonIconPickerIdx.value = idx; subButtonIconPickerIdx.value = idx;
subButtonIconType.value = type;
textLineIconPickerIdx.value = -1; textLineIconPickerIdx.value = -1;
showIconPicker.value = true; showIconPicker.value = true;
} }
@ -131,7 +133,8 @@ const activeIconCodepoint = computed({
return w.value.conditions[conditionIconPickerIdx.value].icon || 0; return w.value.conditions[conditionIconPickerIdx.value].icon || 0;
} }
if (subButtonIconPickerIdx.value >= 0 && w.value?.subButtons?.[subButtonIconPickerIdx.value]) { if (subButtonIconPickerIdx.value >= 0 && w.value?.subButtons?.[subButtonIconPickerIdx.value]) {
return w.value.subButtons[subButtonIconPickerIdx.value].icon || 0; const sb = w.value.subButtons[subButtonIconPickerIdx.value];
return subButtonIconType.value === 'on' ? (sb.iconOn || 0) : (sb.iconOff || 0);
} }
if (textLineIconPickerIdx.value >= 0 && w.value?.textLines?.[textLineIconPickerIdx.value]) { if (textLineIconPickerIdx.value >= 0 && w.value?.textLines?.[textLineIconPickerIdx.value]) {
return w.value.textLines[textLineIconPickerIdx.value].icon || 0; return w.value.textLines[textLineIconPickerIdx.value].icon || 0;
@ -142,7 +145,12 @@ const activeIconCodepoint = computed({
if (conditionIconPickerIdx.value >= 0 && w.value?.conditions?.[conditionIconPickerIdx.value]) { if (conditionIconPickerIdx.value >= 0 && w.value?.conditions?.[conditionIconPickerIdx.value]) {
w.value.conditions[conditionIconPickerIdx.value].icon = value; w.value.conditions[conditionIconPickerIdx.value].icon = value;
} else if (subButtonIconPickerIdx.value >= 0 && w.value?.subButtons?.[subButtonIconPickerIdx.value]) { } else if (subButtonIconPickerIdx.value >= 0 && w.value?.subButtons?.[subButtonIconPickerIdx.value]) {
w.value.subButtons[subButtonIconPickerIdx.value].icon = value; const sb = w.value.subButtons[subButtonIconPickerIdx.value];
if (subButtonIconType.value === 'on') {
sb.iconOn = value;
} else {
sb.iconOff = value;
}
} else if (textLineIconPickerIdx.value >= 0 && w.value?.textLines?.[textLineIconPickerIdx.value]) { } else if (textLineIconPickerIdx.value >= 0 && w.value?.textLines?.[textLineIconPickerIdx.value]) {
w.value.textLines[textLineIconPickerIdx.value].icon = value; w.value.textLines[textLineIconPickerIdx.value].icon = value;
} else if (w.value) { } else if (w.value) {
@ -155,6 +163,7 @@ function handleIconPickerClose() {
showIconPicker.value = false; showIconPicker.value = false;
conditionIconPickerIdx.value = -1; conditionIconPickerIdx.value = -1;
subButtonIconPickerIdx.value = -1; subButtonIconPickerIdx.value = -1;
subButtonIconType.value = 'off';
textLineIconPickerIdx.value = -1; textLineIconPickerIdx.value = -1;
} }
</script> </script>

View File

@ -24,8 +24,8 @@
class="absolute rounded-full flex items-center justify-center shadow-md" class="absolute rounded-full flex items-center justify-center shadow-md"
:style="getSubButtonStyle(sb, idx)" :style="getSubButtonStyle(sb, idx)"
> >
<span v-if="sb.icon" class="material-symbols-outlined" :style="getSubButtonIconStyle(sb)"> <span v-if="getSubButtonIcon(sb)" class="material-symbols-outlined" :style="getSubButtonIconStyle(sb)">
{{ String.fromCodePoint(sb.icon) }} {{ String.fromCodePoint(getSubButtonIcon(sb)) }}
</span> </span>
</div> </div>
</template> </template>
@ -56,8 +56,8 @@
class="absolute rounded-full flex items-center justify-center shadow-md" class="absolute rounded-full flex items-center justify-center shadow-md"
:style="getSubButtonStyleTile(sb, idx)" :style="getSubButtonStyleTile(sb, idx)"
> >
<span v-if="sb.icon" class="material-symbols-outlined" :style="getSubButtonIconStyle(sb)"> <span v-if="getSubButtonIcon(sb)" class="material-symbols-outlined" :style="getSubButtonIconStyle(sb)">
{{ String.fromCodePoint(sb.icon) }} {{ String.fromCodePoint(getSubButtonIcon(sb)) }}
</span> </span>
</div> </div>
</template> </template>
@ -162,13 +162,15 @@ const getSubButtonStyleTile = (sb, idx) => {
const btnSize = (props.widget.subButtonSize || 40) * s; const btnSize = (props.widget.subButtonSize || 40) * s;
const gap = 10 * s; const gap = 10 * s;
const padding = 12 * s; const padding = 12 * s;
// Use bgColorOff with fallback to old colorOff for compatibility
const bgColor = sb.bgColorOff || sb.colorOff || '#666666';
return { return {
width: btnSize + 'px', width: btnSize + 'px',
height: btnSize + 'px', height: btnSize + 'px',
right: padding + 'px', right: padding + 'px',
top: (padding + idx * (btnSize + gap)) + 'px', top: (padding + idx * (btnSize + gap)) + 'px',
backgroundColor: sb.colorOff || '#666666', backgroundColor: bgColor,
}; };
}; };
@ -241,7 +243,8 @@ function getSubButtonStyle(sb, idx) {
const angle = (pos * (Math.PI / 4)) - (Math.PI / 2); const angle = (pos * (Math.PI / 4)) - (Math.PI / 2);
const x = centerX + orbitRadius * Math.cos(angle) - subBtnSize / 2; const x = centerX + orbitRadius * Math.cos(angle) - subBtnSize / 2;
const y = centerY + orbitRadius * Math.sin(angle) - subBtnSize / 2; const y = centerY + orbitRadius * Math.sin(angle) - subBtnSize / 2;
const bgColor = sb.colorOff || '#666666'; // Use bgColorOff with fallback to old colorOff for compatibility
const bgColor = sb.bgColorOff || sb.colorOff || '#666666';
return { return {
width: `${subBtnSize}px`, width: `${subBtnSize}px`,
@ -257,12 +260,19 @@ function getSubButtonIconStyle(sb) {
const s = props.scale; const s = props.scale;
const subBtnSize = (props.widget.subButtonSize || 40) * s; const subBtnSize = (props.widget.subButtonSize || 40) * s;
const iconSize = subBtnSize * 0.5; const iconSize = subBtnSize * 0.5;
// Use iconColorOff with fallback for compatibility
const iconColor = sb.iconColorOff || '#ffffff';
return { return {
fontSize: `${iconSize}px`, fontSize: `${iconSize}px`,
color: '#ffffff' color: iconColor
}; };
} }
function getSubButtonIcon(sb) {
// Use iconOff with fallback to old icon field for compatibility
return sb.iconOff || sb.icon || 0;
}
const computedStyle = computed(() => { const computedStyle = computed(() => {
const w = props.widget; const w = props.widget;
const s = props.scale; const s = props.scale;

View File

@ -147,10 +147,14 @@
</button> </button>
<button v-if="cond.icon" class="w-6 h-6 rounded-md border border-red-200 bg-[#f7dede] text-[#b3261e] grid place-items-center text-[11px] cursor-pointer hover:bg-[#f2cfcf]" @click="cond.icon = 0">x</button> <button v-if="cond.icon" class="w-6 h-6 rounded-md border border-red-200 bg-[#f7dede] text-[#b3261e] grid place-items-center text-[11px] cursor-pointer hover:bg-[#f2cfcf]" @click="cond.icon = 0">x</button>
</div> </div>
<div class="flex items-center gap-2 text-[11px] text-muted"> <div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
<label class="w-[50px]">Farbe</label> <label class="w-[50px]">Textfarbe</label>
<input class="h-[22px] w-[32px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="cond.textColor"> <input class="h-[22px] w-[32px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="cond.textColor">
</div> </div>
<div class="flex items-center gap-2 text-[11px] text-muted">
<label class="w-[50px]">Hintergr.</label>
<input class="h-[22px] w-[32px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="cond.bgColor">
</div>
</div> </div>
</div> </div>
</template> </template>
@ -190,7 +194,8 @@ const conditionCount = computed({
op: 'eq', op: 'eq',
priority: props.widget.conditions.length, priority: props.widget.conditions.length,
icon: 0, icon: 0,
textColor: '#FFFFFF' textColor: '#FFFFFF',
bgColor: ''
}); });
} }
if (props.widget.conditions.length > target) { if (props.widget.conditions.length > target) {

View File

@ -166,11 +166,20 @@
</select> </select>
</div> </div>
<div class="flex items-center gap-2 mb-2 text-[11px] text-muted"> <div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
<label class="w-[50px]">Icon</label> <label class="w-[50px]">Icon Aus</label>
<button class="flex-1 bg-white border border-border rounded-md px-2.5 py-1 text-[11px] flex items-center justify-center gap-1 cursor-pointer hover:bg-[#e4ebf2]" @click="$emit('open-subbutton-icon-picker', idx)"> <button class="flex-1 bg-white border border-border rounded-md px-2.5 py-1 text-[11px] flex items-center justify-center gap-1 cursor-pointer hover:bg-[#e4ebf2]" @click="$emit('open-subbutton-icon-picker', idx, 'off')">
<span v-if="sb.icon" class="material-symbols-outlined text-[16px]">{{ String.fromCodePoint(sb.icon) }}</span> <span v-if="sb.iconOff" class="material-symbols-outlined text-[16px]">{{ String.fromCodePoint(sb.iconOff) }}</span>
<span v-else>Kein Icon</span> <span v-else>Kein Icon</span>
</button> </button>
<button v-if="sb.iconOff" class="w-6 h-6 rounded-md border border-red-200 bg-[#f7dede] text-[#b3261e] grid place-items-center text-[11px] cursor-pointer hover:bg-[#f2cfcf]" @click="sb.iconOff = 0">x</button>
</div>
<div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
<label class="w-[50px]">Icon An</label>
<button class="flex-1 bg-white border border-border rounded-md px-2.5 py-1 text-[11px] flex items-center justify-center gap-1 cursor-pointer hover:bg-[#e4ebf2]" @click="$emit('open-subbutton-icon-picker', idx, 'on')">
<span v-if="sb.iconOn" class="material-symbols-outlined text-[16px]">{{ String.fromCodePoint(sb.iconOn) }}</span>
<span v-else>Wie Aus</span>
</button>
<button v-if="sb.iconOn" class="w-6 h-6 rounded-md border border-red-200 bg-[#f7dede] text-[#b3261e] grid place-items-center text-[11px] cursor-pointer hover:bg-[#f2cfcf]" @click="sb.iconOn = 0">x</button>
</div> </div>
<div class="flex items-center gap-2 mb-2 text-[11px] text-muted"> <div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
<label class="w-[50px]">Aktion</label> <label class="w-[50px]">Aktion</label>
@ -184,7 +193,7 @@
<select class="flex-1 bg-white border border-border rounded-md px-2 py-1 text-[11px]" v-model.number="sb.knxRead"> <select class="flex-1 bg-white border border-border rounded-md px-2 py-1 text-[11px]" v-model.number="sb.knxRead">
<option :value="0">-- Keine --</option> <option :value="0">-- Keine --</option>
<option v-for="addr in store.knxAddresses" :key="`${addr.addr}-${addr.index}`" :value="addr.addr"> <option v-for="addr in store.knxAddresses" :key="`${addr.addr}-${addr.index}`" :value="addr.addr">
GA {{ addr.addrStr }} GA {{ addr.addrStr }} (GO{{ addr.index }})
</option> </option>
</select> </select>
</div> </div>
@ -193,7 +202,7 @@
<select class="flex-1 bg-white border border-border rounded-md px-2 py-1 text-[11px]" v-model.number="sb.knxWrite"> <select class="flex-1 bg-white border border-border rounded-md px-2 py-1 text-[11px]" v-model.number="sb.knxWrite">
<option :value="0">-- Keine --</option> <option :value="0">-- Keine --</option>
<option v-for="addr in writeableAddresses" :key="`${addr.addr}-${addr.index}`" :value="addr.addr"> <option v-for="addr in writeableAddresses" :key="`${addr.addr}-${addr.index}`" :value="addr.addr">
GA {{ addr.addrStr }} GA {{ addr.addrStr }} (GO{{ addr.index }})
</option> </option>
</select> </select>
</div> </div>
@ -203,12 +212,19 @@
<option v-for="s in store.config.screens" :key="s.id" :value="s.id">{{ s.name }}</option> <option v-for="s in store.config.screens" :key="s.id" :value="s.id">{{ s.name }}</option>
</select> </select>
</div> </div>
<div class="flex items-center gap-2 text-[11px] text-muted"> <div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
<label class="w-[50px]">Farben</label> <label class="w-[50px]">Hintergr.</label>
<span class="text-[10px]">An:</span> <span class="text-[10px]">An:</span>
<input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.colorOn"> <input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.bgColorOn">
<span class="text-[10px]">Aus:</span> <span class="text-[10px]">Aus:</span>
<input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.colorOff"> <input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.bgColorOff">
</div>
<div class="flex items-center gap-2 text-[11px] text-muted">
<label class="w-[50px]">Icon</label>
<span class="text-[10px]">An:</span>
<input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.iconColorOn">
<span class="text-[10px]">Aus:</span>
<input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.iconColorOff">
</div> </div>
</div> </div>
@ -316,13 +332,16 @@ const subButtonCount = computed({
while (props.widget.subButtons.length < target) { while (props.widget.subButtons.length < target) {
props.widget.subButtons.push({ props.widget.subButtons.push({
pos: props.widget.subButtons.length, pos: props.widget.subButtons.length,
icon: 0, iconOff: 0,
iconOn: 0,
knxRead: 0, knxRead: 0,
knxWrite: 0, knxWrite: 0,
action: 0, action: 0,
target: 0, target: 0,
colorOn: '#FFCC00', bgColorOn: '#FFCC00',
colorOff: '#666666' bgColorOff: '#666666',
iconColorOn: '#FFFFFF',
iconColorOff: '#FFFFFF'
}); });
} }
if (props.widget.subButtons.length > target) { if (props.widget.subButtons.length > target) {

View File

@ -8,6 +8,7 @@ export const useEditorStore = defineStore('editor', () => {
startScreen: 0, startScreen: 0,
standby: { enabled: false, screen: -1, minutes: 5 }, standby: { enabled: false, screen: -1, minutes: 5 },
knx: { time: 0, date: 0, dateTime: 0, night: 0 }, knx: { time: 0, date: 0, dateTime: 0, night: 0 },
screenAnim: { type: 1, duration: 300 }, // 1 = Fade, 300ms
screens: [] screens: []
}); });
@ -209,6 +210,12 @@ export const useEditorStore = defineStore('editor', () => {
if (config.knx.dateTime === undefined) config.knx.dateTime = 0; if (config.knx.dateTime === undefined) config.knx.dateTime = 0;
if (config.knx.night === undefined) config.knx.night = 0; if (config.knx.night === undefined) config.knx.night = 0;
} }
if (!config.screenAnim) {
config.screenAnim = { type: 1, duration: 300 };
} else {
if (config.screenAnim.type === undefined) config.screenAnim.type = 1;
if (config.screenAnim.duration === undefined) config.screenAnim.duration = 300;
}
mapLegacyKnxAddresses(); mapLegacyKnxAddresses();
// Recalculate IDs // Recalculate IDs