diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3967b676b..cd584a42e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,7 +69,7 @@ Format: `(): ` ## Example -``` +```text feat: add hat wobble ^--^ ^------------^ | | @@ -96,7 +96,7 @@ References: ## Contributor License Agreement (CLA) -``` +```text By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I diff --git a/README.md b/README.md index 4dfc357dc..8fafc5364 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ [![chat](https://img.shields.io/discord/816637840644505620.svg?style=flat-square&color=blueviolet)](https://discord.gg/3J3GgnzpyT) [![GitHub stars](https://img.shields.io/github/stars/emsesp/EMS-ESP32.svg?style=social&label=Star)](https://github.com/emsesp/EMS-ESP32/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/emsesp/EMS-ESP32.svg?style=social&label=Fork)](https://github.com/emsesp/EMS-ES32P/network) +[![GitHub forks](https://img.shields.io/github/forks/emsesp/EMS-ESP32.svg?style=social&label=Fork)](https://github.com/emsesp/EMS-ESP32/network) [![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/paypalme/prderbyshire/2) **EMS-ESP** is an open-source firmware for the Espressif ESP32 microcontroller to communicate with **EMS** (Energy Management System) compatible equipment from manufacturers such as Bosch, Buderus, Nefit, Junkers, Worcester, Sieger, elm.leblanc and iVT. diff --git a/cspell.json b/cspell.json index 13bd25cab..7e1f3c11b 100644 --- a/cspell.json +++ b/cspell.json @@ -33,6 +33,7 @@ "src/core/modbus_entity_parameters.hpp", "sdkconfig.*", "managed_components/**", - "pnpm-*.yaml" + "pnpm-*.yaml", + "vite.config.ts" ] } \ No newline at end of file diff --git a/docs/Modbus-Entity-Registers.md b/docs/Modbus-Entity-Registers.md index 8aca52eb6..26725ab5c 100644 --- a/docs/Modbus-Entity-Registers.md +++ b/docs/Modbus-Entity-Registers.md @@ -421,8 +421,8 @@ | dhw.comfoff | comfort switch off | uint8 (>=15<=65) | C | true | DHW | 18 | 1 | 1 | | dhw.ecooff | eco switch off | uint8 (>=15<=65) | C | true | DHW | 19 | 1 | 1 | | dhw.ecoplusoff | eco+ switch off | uint8 (>=48<=63) | C | true | DHW | 20 | 1 | 1 | -| dhw.comfdiff | comfort diff | uint8 (>=6<=12) | K | true | DHW | 21 | 1 | 1 | -| dhw.ecodiff | eco diff | uint8 (>=6<=12) | K | true | DHW | 22 | 1 | 1 | +| dhw.comfdiff | comfort diff | uint8 (>=4<=12) | K | true | DHW | 21 | 1 | 1 | +| dhw.ecodiff | eco diff | uint8 (>=4<=12) | K | true | DHW | 22 | 1 | 1 | | dhw.ecoplusdiff | eco+ diff | uint8 (>=6<=12) | K | true | DHW | 23 | 1 | 1 | | dhw.comfstop | comfort stop temp | uint8 (>=0<=254) | C | true | DHW | 24 | 1 | 1 | | dhw.ecostop | eco stop temp | uint8 (>=0<=254) | C | true | DHW | 25 | 1 | 1 | @@ -1704,8 +1704,8 @@ | dhw.comfoff | comfort switch off | uint8 (>=15<=65) | C | true | DHW | 18 | 1 | 1 | | dhw.ecooff | eco switch off | uint8 (>=15<=65) | C | true | DHW | 19 | 1 | 1 | | dhw.ecoplusoff | eco+ switch off | uint8 (>=48<=63) | C | true | DHW | 20 | 1 | 1 | -| dhw.comfdiff | comfort diff | uint8 (>=6<=12) | K | true | DHW | 21 | 1 | 1 | -| dhw.ecodiff | eco diff | uint8 (>=6<=12) | K | true | DHW | 22 | 1 | 1 | +| dhw.comfdiff | comfort diff | uint8 (>=4<=12) | K | true | DHW | 21 | 1 | 1 | +| dhw.ecodiff | eco diff | uint8 (>=4<=12) | K | true | DHW | 22 | 1 | 1 | | dhw.ecoplusdiff | eco+ diff | uint8 (>=6<=12) | K | true | DHW | 23 | 1 | 1 | | dhw.comfstop | comfort stop temp | uint8 (>=0<=254) | C | true | DHW | 24 | 1 | 1 | | dhw.ecostop | eco stop temp | uint8 (>=0<=254) | C | true | DHW | 25 | 1 | 1 | @@ -2273,8 +2273,8 @@ | dhw.comfoff | comfort switch off | uint8 (>=15<=65) | C | true | DHW | 18 | 1 | 1 | | dhw.ecooff | eco switch off | uint8 (>=15<=65) | C | true | DHW | 19 | 1 | 1 | | dhw.ecoplusoff | eco+ switch off | uint8 (>=48<=63) | C | true | DHW | 20 | 1 | 1 | -| dhw.comfdiff | comfort diff | uint8 (>=6<=12) | K | true | DHW | 21 | 1 | 1 | -| dhw.ecodiff | eco diff | uint8 (>=6<=12) | K | true | DHW | 22 | 1 | 1 | +| dhw.comfdiff | comfort diff | uint8 (>=4<=12) | K | true | DHW | 21 | 1 | 1 | +| dhw.ecodiff | eco diff | uint8 (>=4<=12) | K | true | DHW | 22 | 1 | 1 | | dhw.ecoplusdiff | eco+ diff | uint8 (>=6<=12) | K | true | DHW | 23 | 1 | 1 | | dhw.comfstop | comfort stop temp | uint8 (>=0<=254) | C | true | DHW | 24 | 1 | 1 | | dhw.ecostop | eco stop temp | uint8 (>=0<=254) | C | true | DHW | 25 | 1 | 1 | @@ -3967,6 +3967,13 @@ | nrgheat | energy heating | uint24 (>=0<=10000000) | kWh | true | DEVICE_DATA | 85 | 2 | 1/100 | | dhw.nrg | energy | uint24 (>=0<=10000000) | kWh | true | DHW | 0 | 2 | 1/100 | +## Devices of type *connect* +### MX400 +| shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | +|-|-|-|-|-|-|-|-|-| +| datetime | date/time | string | | false | DEVICE_DATA | 0 | 13 | 1 | +| outdoortemp | outside temperature | int16 (>=-3199<=3199) | C | false | DEVICE_DATA | 13 | 1 | 1/10 | + ## Devices of type *controller* ### Rego 3000 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -4704,18 +4711,18 @@ | hc1.switchtime1 | own1 program switchtime | string | | true | HC | 91 | 8 | 1 | | hc1.switchtime2 | own2 program switchtime | string | | true | HC | 99 | 8 | 1 | | dhw.mode | operating mode | enum [off\|on\|auto] | | true | DHW | 0 | 1 | 1 | -| dhw.circmode | circulation pump mode | enum [off\|on\|auto] | | true | DHW | 3 | 1 | 1 | -| dhw.progmode | program | enum [std prog\|own prog] | | true | DHW | 12 | 1 | 1 | -| dhw.circprog | circulation program | enum [std prog\|own prog] | | true | DHW | 13 | 1 | 1 | -| dhw.disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | -| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | -| dhw.disinfecthour | disinfection hour | uint8 (>=0<=23) | | true | DHW | 14 | 1 | 1 | -| dhw.maxtemp | maximum temperature | uint8 (>=0<=254) | C | true | DHW | 15 | 1 | 1 | -| dhw.onetimekey | one time key function | boolean | | true | DHW | 16 | 1 | 1 | -| dhw.switchtime | program switchtime | string | | true | DHW | 17 | 8 | 1 | -| dhw.circswitchtime | circulation program switchtime | string | | true | DHW | 25 | 8 | 1 | -| dhw.holidays | holiday dates | string | | true | DHW | 33 | 13 | 1 | -| dhw.vacations | vacation dates | string | | true | DHW | 46 | 13 | 1 | +| dhw.circmode | circulation pump mode | enum [off\|on\|auto] | | true | DHW | 4 | 1 | 1 | +| dhw.progmode | program | enum [std prog\|own prog] | | true | DHW | 13 | 1 | 1 | +| dhw.circprog | circulation program | enum [std prog\|own prog] | | true | DHW | 14 | 1 | 1 | +| dhw.disinfecting | disinfecting | boolean | | true | DHW | 8 | 1 | 1 | +| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 9 | 1 | 1 | +| dhw.disinfecthour | disinfection hour | uint8 (>=0<=23) | | true | DHW | 15 | 1 | 1 | +| dhw.maxtemp | maximum temperature | uint8 (>=0<=254) | C | true | DHW | 16 | 1 | 1 | +| dhw.onetimekey | one time key function | boolean | | true | DHW | 17 | 1 | 1 | +| dhw.switchtime | program switchtime | string | | true | DHW | 18 | 8 | 1 | +| dhw.circswitchtime | circulation program switchtime | string | | true | DHW | 26 | 8 | 1 | +| dhw.holidays | holiday dates | string | | true | DHW | 34 | 13 | 1 | +| dhw.vacations | vacation dates | string | | true | DHW | 47 | 13 | 1 | ### ES79 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -4770,18 +4777,18 @@ | hc1.switchtime1 | own1 program switchtime | string | | true | HC | 91 | 8 | 1 | | hc1.switchtime2 | own2 program switchtime | string | | true | HC | 99 | 8 | 1 | | dhw.mode | operating mode | enum [off\|on\|auto] | | true | DHW | 0 | 1 | 1 | -| dhw.circmode | circulation pump mode | enum [off\|on\|auto] | | true | DHW | 3 | 1 | 1 | -| dhw.progmode | program | enum [std prog\|own prog] | | true | DHW | 12 | 1 | 1 | -| dhw.circprog | circulation program | enum [std prog\|own prog] | | true | DHW | 13 | 1 | 1 | -| dhw.disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | -| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | -| dhw.disinfecthour | disinfection hour | uint8 (>=0<=23) | | true | DHW | 14 | 1 | 1 | -| dhw.maxtemp | maximum temperature | uint8 (>=60<=80) | C | true | DHW | 15 | 1 | 1 | -| dhw.onetimekey | one time key function | boolean | | true | DHW | 16 | 1 | 1 | -| dhw.switchtime | program switchtime | string | | true | DHW | 17 | 8 | 1 | -| dhw.circswitchtime | circulation program switchtime | string | | true | DHW | 25 | 8 | 1 | -| dhw.holidays | holiday dates | string | | true | DHW | 33 | 13 | 1 | -| dhw.vacations | vacation dates | string | | true | DHW | 46 | 13 | 1 | +| dhw.circmode | circulation pump mode | enum [off\|on\|auto] | | true | DHW | 4 | 1 | 1 | +| dhw.progmode | program | enum [std prog\|own prog] | | true | DHW | 13 | 1 | 1 | +| dhw.circprog | circulation program | enum [std prog\|own prog] | | true | DHW | 14 | 1 | 1 | +| dhw.disinfecting | disinfecting | boolean | | true | DHW | 8 | 1 | 1 | +| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 9 | 1 | 1 | +| dhw.disinfecthour | disinfection hour | uint8 (>=0<=23) | | true | DHW | 15 | 1 | 1 | +| dhw.maxtemp | maximum temperature | uint8 (>=60<=80) | C | true | DHW | 16 | 1 | 1 | +| dhw.onetimekey | one time key function | boolean | | true | DHW | 17 | 1 | 1 | +| dhw.switchtime | program switchtime | string | | true | DHW | 18 | 8 | 1 | +| dhw.circswitchtime | circulation program switchtime | string | | true | DHW | 26 | 8 | 1 | +| dhw.holidays | holiday dates | string | | true | DHW | 34 | 13 | 1 | +| dhw.vacations | vacation dates | string | | true | DHW | 47 | 13 | 1 | ### EasyControl, CT200 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -4832,7 +4839,7 @@ | hc1.roominflfactor | room influence factor | uint8 (>=0<=100) | % | true | HC | 14 | 1 | 10 | | hc1.heatingtype | heating type | enum [off\|heatingcurve\|radiator\|convector\|floor] | | true | HC | 19 | 1 | 1 | | hc1.controlmode | control mode | enum [off\|unmixed\|unmixed IPM\|mixed IPM] | | true | HC | 25 | 1 | 1 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | ### FB100 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -4873,7 +4880,7 @@ | hc1.roominflfactor | room influence factor | uint8 (>=0<=100) | % | true | HC | 14 | 1 | 10 | | hc1.heatingtype | heating type | enum [off\|heatingcurve\|radiator\|convector\|floor] | | true | HC | 19 | 1 | 1 | | hc1.controlmode | control mode | enum [off\|unmixed\|unmixed IPM\|mixed IPM] | | true | HC | 25 | 1 | 1 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | ### FR10 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -4912,7 +4919,7 @@ | hc1.roominflfactor | room influence factor | uint8 (>=0<=100) | % | true | HC | 14 | 1 | 10 | | hc1.heatingtype | heating type | enum [off\|heatingcurve\|radiator\|convector\|floor] | | true | HC | 19 | 1 | 1 | | hc1.controlmode | control mode | enum [off\|unmixed\|unmixed IPM\|mixed IPM] | | true | HC | 25 | 1 | 1 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | ### FR100 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -4951,7 +4958,7 @@ | hc1.roominflfactor | room influence factor | uint8 (>=0<=100) | % | true | HC | 14 | 1 | 10 | | hc1.heatingtype | heating type | enum [off\|heatingcurve\|radiator\|convector\|floor] | | true | HC | 19 | 1 | 1 | | hc1.controlmode | control mode | enum [off\|unmixed\|unmixed IPM\|mixed IPM] | | true | HC | 25 | 1 | 1 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | ### FR110 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -4990,7 +4997,7 @@ | hc1.roominflfactor | room influence factor | uint8 (>=0<=100) | % | true | HC | 14 | 1 | 10 | | hc1.heatingtype | heating type | enum [off\|heatingcurve\|radiator\|convector\|floor] | | true | HC | 19 | 1 | 1 | | hc1.controlmode | control mode | enum [off\|unmixed\|unmixed IPM\|mixed IPM] | | true | HC | 25 | 1 | 1 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | ### FR120 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5029,7 +5036,7 @@ | hc1.roominflfactor | room influence factor | uint8 (>=0<=100) | % | true | HC | 14 | 1 | 10 | | hc1.heatingtype | heating type | enum [off\|heatingcurve\|radiator\|convector\|floor] | | true | HC | 19 | 1 | 1 | | hc1.controlmode | control mode | enum [off\|unmixed\|unmixed IPM\|mixed IPM] | | true | HC | 25 | 1 | 1 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | ### FR50 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5068,7 +5075,7 @@ | hc1.roominflfactor | room influence factor | uint8 (>=0<=100) | % | true | HC | 14 | 1 | 10 | | hc1.heatingtype | heating type | enum [off\|heatingcurve\|radiator\|convector\|floor] | | true | HC | 19 | 1 | 1 | | hc1.controlmode | control mode | enum [off\|unmixed\|unmixed IPM\|mixed IPM] | | true | HC | 25 | 1 | 1 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | ### FW100 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5109,7 +5116,7 @@ | hc1.roominflfactor | room influence factor | uint8 (>=0<=100) | % | true | HC | 14 | 1 | 10 | | hc1.heatingtype | heating type | enum [off\|heatingcurve\|radiator\|convector\|floor] | | true | HC | 19 | 1 | 1 | | hc1.controlmode | control mode | enum [off\|unmixed\|unmixed IPM\|mixed IPM] | | true | HC | 25 | 1 | 1 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | ### FW120 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5150,7 +5157,7 @@ | hc1.roominflfactor | room influence factor | uint8 (>=0<=100) | % | true | HC | 14 | 1 | 10 | | hc1.heatingtype | heating type | enum [off\|heatingcurve\|radiator\|convector\|floor] | | true | HC | 19 | 1 | 1 | | hc1.controlmode | control mode | enum [off\|unmixed\|unmixed IPM\|mixed IPM] | | true | HC | 25 | 1 | 1 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | ### FW200 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5191,7 +5198,7 @@ | hc1.roominflfactor | room influence factor | uint8 (>=0<=100) | % | true | HC | 14 | 1 | 10 | | hc1.heatingtype | heating type | enum [off\|heatingcurve\|radiator\|convector\|floor] | | true | HC | 19 | 1 | 1 | | hc1.controlmode | control mode | enum [off\|unmixed\|unmixed IPM\|mixed IPM] | | true | HC | 25 | 1 | 1 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | ### FW500 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5232,7 +5239,7 @@ | hc1.roominflfactor | room influence factor | uint8 (>=0<=100) | % | true | HC | 14 | 1 | 10 | | hc1.heatingtype | heating type | enum [off\|heatingcurve\|radiator\|convector\|floor] | | true | HC | 19 | 1 | 1 | | hc1.controlmode | control mode | enum [off\|unmixed\|unmixed IPM\|mixed IPM] | | true | HC | 25 | 1 | 1 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | ### Logamatic TC100, Moduline Easy | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5303,10 +5310,10 @@ | hc1.manualtemp | manual temperature | uint8 (>=0<=127) | C | true | HC | 6 | 1 | 1/2 | | hc1.offtemp | temperature when mode is off | uint8 (>=0<=127) | C | true | HC | 107 | 1 | 1/2 | | dhw.mode | operating mode | enum [on\|off\|auto] | | true | DHW | 0 | 1 | 1 | -| dhw.whenmodeoff | when thermostat mode off | boolean | | true | DHW | 59 | 1 | 1 | -| dhw.disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | -| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | -| dhw.disinfecthour | disinfection hour | uint8 (>=0<=23) | | true | DHW | 14 | 1 | 1 | +| dhw.whenmodeoff | when thermostat mode off | boolean | | true | DHW | 60 | 1 | 1 | +| dhw.disinfecting | disinfecting | boolean | | true | DHW | 8 | 1 | 1 | +| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 9 | 1 | 1 | +| dhw.disinfecthour | disinfection hour | uint8 (>=0<=23) | | true | DHW | 15 | 1 | 1 | ### RC10 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5487,17 +5494,18 @@ | hc1.solarinfl | solar influence | uint8 (>=-5<=4294967295) | C | true | HC | 54 | 1 | 1 | | hc1.currsolarinfl | curent solar influence | uint8 (>=0<=25) | C | false | HC | 55 | 1 | 1/10 | | dhw.mode | operating mode | enum [off\|on\|auto] | | true | DHW | 0 | 1 | 1 | -| dhw.settemp | set temperature | uint8 (>=0<=254) | C | true | DHW | 1 | 1 | 1 | -| dhw.settemplow | set low temperature | uint8 (>=0<=254) | C | true | DHW | 2 | 1 | 1 | -| dhw.circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 3 | 1 | 1 | -| dhw.chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 4 | 1 | 15 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | -| dhw.extra | extra | boolean | | false | DHW | 6 | 1 | 1 | -| dhw.disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | -| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | -| dhw.disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 9 | 1 | 15 | -| dhw.dailyheating | daily heating | boolean | | true | DHW | 10 | 1 | 1 | -| dhw.dailyheattime | daily heating time | uint8 (>=0<=1431) | minutes | true | DHW | 11 | 1 | 15 | +| dhw.modetype | mode type | enum [off\|eco\|comfort\|eco+] | | false | DHW | 1 | 1 | 1 | +| dhw.settemp | set temperature | uint8 (>=0<=254) | C | true | DHW | 2 | 1 | 1 | +| dhw.settemplow | set low temperature | uint8 (>=0<=254) | C | true | DHW | 3 | 1 | 1 | +| dhw.circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 4 | 1 | 1 | +| dhw.chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 5 | 1 | 15 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | +| dhw.extra | extra | boolean | | false | DHW | 7 | 1 | 1 | +| dhw.disinfecting | disinfecting | boolean | | true | DHW | 8 | 1 | 1 | +| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 9 | 1 | 1 | +| dhw.disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 10 | 1 | 15 | +| dhw.dailyheating | daily heating | boolean | | true | DHW | 11 | 1 | 1 | +| dhw.dailyheattime | daily heating time | uint8 (>=0<=1431) | minutes | true | DHW | 12 | 1 | 15 | ### RC20RF | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5621,17 +5629,18 @@ | hc1.solarinfl | solar influence | uint8 (>=-5<=4294967295) | C | true | HC | 54 | 1 | 1 | | hc1.currsolarinfl | curent solar influence | uint8 (>=0<=25) | C | false | HC | 55 | 1 | 1/10 | | dhw.mode | operating mode | enum [off\|normal\|comfort\|auto\|own prog] | | true | DHW | 0 | 1 | 1 | -| dhw.settemp | set temperature | uint8 (>=0<=254) | C | true | DHW | 1 | 1 | 1 | -| dhw.settemplow | set low temperature | uint8 (>=0<=254) | C | true | DHW | 2 | 1 | 1 | -| dhw.circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 3 | 1 | 1 | -| dhw.chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 4 | 1 | 15 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | -| dhw.extra | extra | boolean | | false | DHW | 6 | 1 | 1 | -| dhw.disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | -| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | -| dhw.disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 9 | 1 | 15 | -| dhw.dailyheating | daily heating | boolean | | true | DHW | 10 | 1 | 1 | -| dhw.dailyheattime | daily heating time | uint8 (>=0<=1431) | minutes | true | DHW | 11 | 1 | 15 | +| dhw.modetype | mode type | enum [off\|eco\|comfort\|eco+] | | false | DHW | 1 | 1 | 1 | +| dhw.settemp | set temperature | uint8 (>=0<=254) | C | true | DHW | 2 | 1 | 1 | +| dhw.settemplow | set low temperature | uint8 (>=0<=254) | C | true | DHW | 3 | 1 | 1 | +| dhw.circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 4 | 1 | 1 | +| dhw.chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 5 | 1 | 15 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | +| dhw.extra | extra | boolean | | false | DHW | 7 | 1 | 1 | +| dhw.disinfecting | disinfecting | boolean | | true | DHW | 8 | 1 | 1 | +| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 9 | 1 | 1 | +| dhw.disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 10 | 1 | 15 | +| dhw.dailyheating | daily heating | boolean | | true | DHW | 11 | 1 | 1 | +| dhw.dailyheattime | daily heating time | uint8 (>=0<=1431) | minutes | true | DHW | 12 | 1 | 15 | ### RC30 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5689,18 +5698,18 @@ | hc1.switchtime1 | own1 program switchtime | string | | true | HC | 91 | 8 | 1 | | hc1.switchtime2 | own2 program switchtime | string | | true | HC | 99 | 8 | 1 | | dhw.mode | operating mode | enum [off\|on\|auto] | | true | DHW | 0 | 1 | 1 | -| dhw.circmode | circulation pump mode | enum [off\|on\|auto] | | true | DHW | 3 | 1 | 1 | -| dhw.progmode | program | enum [std prog\|own prog] | | true | DHW | 12 | 1 | 1 | -| dhw.circprog | circulation program | enum [std prog\|own prog] | | true | DHW | 13 | 1 | 1 | -| dhw.disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | -| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | -| dhw.disinfecthour | disinfection hour | uint8 (>=0<=23) | | true | DHW | 14 | 1 | 1 | -| dhw.maxtemp | maximum temperature | uint8 (>=0<=254) | C | true | DHW | 15 | 1 | 1 | -| dhw.onetimekey | one time key function | boolean | | true | DHW | 16 | 1 | 1 | -| dhw.switchtime | program switchtime | string | | true | DHW | 17 | 8 | 1 | -| dhw.circswitchtime | circulation program switchtime | string | | true | DHW | 25 | 8 | 1 | -| dhw.holidays | holiday dates | string | | true | DHW | 33 | 13 | 1 | -| dhw.vacations | vacation dates | string | | true | DHW | 46 | 13 | 1 | +| dhw.circmode | circulation pump mode | enum [off\|on\|auto] | | true | DHW | 4 | 1 | 1 | +| dhw.progmode | program | enum [std prog\|own prog] | | true | DHW | 13 | 1 | 1 | +| dhw.circprog | circulation program | enum [std prog\|own prog] | | true | DHW | 14 | 1 | 1 | +| dhw.disinfecting | disinfecting | boolean | | true | DHW | 8 | 1 | 1 | +| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 9 | 1 | 1 | +| dhw.disinfecthour | disinfection hour | uint8 (>=0<=23) | | true | DHW | 15 | 1 | 1 | +| dhw.maxtemp | maximum temperature | uint8 (>=0<=254) | C | true | DHW | 16 | 1 | 1 | +| dhw.onetimekey | one time key function | boolean | | true | DHW | 17 | 1 | 1 | +| dhw.switchtime | program switchtime | string | | true | DHW | 18 | 8 | 1 | +| dhw.circswitchtime | circulation program switchtime | string | | true | DHW | 26 | 8 | 1 | +| dhw.holidays | holiday dates | string | | true | DHW | 34 | 13 | 1 | +| dhw.vacations | vacation dates | string | | true | DHW | 47 | 13 | 1 | ### RC35 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5755,18 +5764,18 @@ | hc1.switchtime1 | own1 program switchtime | string | | true | HC | 91 | 8 | 1 | | hc1.switchtime2 | own2 program switchtime | string | | true | HC | 99 | 8 | 1 | | dhw.mode | operating mode | enum [off\|on\|auto] | | true | DHW | 0 | 1 | 1 | -| dhw.circmode | circulation pump mode | enum [off\|on\|auto] | | true | DHW | 3 | 1 | 1 | -| dhw.progmode | program | enum [std prog\|own prog] | | true | DHW | 12 | 1 | 1 | -| dhw.circprog | circulation program | enum [std prog\|own prog] | | true | DHW | 13 | 1 | 1 | -| dhw.disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | -| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | -| dhw.disinfecthour | disinfection hour | uint8 (>=0<=23) | | true | DHW | 14 | 1 | 1 | -| dhw.maxtemp | maximum temperature | uint8 (>=60<=80) | C | true | DHW | 15 | 1 | 1 | -| dhw.onetimekey | one time key function | boolean | | true | DHW | 16 | 1 | 1 | -| dhw.switchtime | program switchtime | string | | true | DHW | 17 | 8 | 1 | -| dhw.circswitchtime | circulation program switchtime | string | | true | DHW | 25 | 8 | 1 | -| dhw.holidays | holiday dates | string | | true | DHW | 33 | 13 | 1 | -| dhw.vacations | vacation dates | string | | true | DHW | 46 | 13 | 1 | +| dhw.circmode | circulation pump mode | enum [off\|on\|auto] | | true | DHW | 4 | 1 | 1 | +| dhw.progmode | program | enum [std prog\|own prog] | | true | DHW | 13 | 1 | 1 | +| dhw.circprog | circulation program | enum [std prog\|own prog] | | true | DHW | 14 | 1 | 1 | +| dhw.disinfecting | disinfecting | boolean | | true | DHW | 8 | 1 | 1 | +| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 9 | 1 | 1 | +| dhw.disinfecthour | disinfection hour | uint8 (>=0<=23) | | true | DHW | 15 | 1 | 1 | +| dhw.maxtemp | maximum temperature | uint8 (>=60<=80) | C | true | DHW | 16 | 1 | 1 | +| dhw.onetimekey | one time key function | boolean | | true | DHW | 17 | 1 | 1 | +| dhw.switchtime | program switchtime | string | | true | DHW | 18 | 8 | 1 | +| dhw.circswitchtime | circulation program switchtime | string | | true | DHW | 26 | 8 | 1 | +| dhw.holidays | holiday dates | string | | true | DHW | 34 | 13 | 1 | +| dhw.vacations | vacation dates | string | | true | DHW | 47 | 13 | 1 | ### RFM20 Remote | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5871,17 +5880,18 @@ | hc1.solarinfl | solar influence | uint8 (>=-5<=4294967295) | C | true | HC | 54 | 1 | 1 | | hc1.currsolarinfl | curent solar influence | uint8 (>=0<=25) | C | false | HC | 55 | 1 | 1/10 | | dhw.mode | operating mode | enum [normal\|comfort\|eco+] | | true | DHW | 0 | 1 | 1 | -| dhw.settemp | set temperature | uint8 (>=0<=254) | C | true | DHW | 1 | 1 | 1 | -| dhw.settemplow | set low temperature | uint8 (>=0<=254) | C | true | DHW | 2 | 1 | 1 | -| dhw.circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 3 | 1 | 1 | -| dhw.chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 4 | 1 | 15 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | -| dhw.extra | extra | boolean | | false | DHW | 6 | 1 | 1 | -| dhw.disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | -| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | -| dhw.disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 9 | 1 | 15 | -| dhw.dailyheating | daily heating | boolean | | true | DHW | 10 | 1 | 1 | -| dhw.dailyheattime | daily heating time | uint8 (>=0<=1431) | minutes | true | DHW | 11 | 1 | 15 | +| dhw.modetype | mode type | enum [off\|eco\|comfort\|eco+] | | false | DHW | 1 | 1 | 1 | +| dhw.settemp | set temperature | uint8 (>=0<=254) | C | true | DHW | 2 | 1 | 1 | +| dhw.settemplow | set low temperature | uint8 (>=0<=254) | C | true | DHW | 3 | 1 | 1 | +| dhw.circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 4 | 1 | 1 | +| dhw.chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 5 | 1 | 15 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | +| dhw.extra | extra | boolean | | false | DHW | 7 | 1 | 1 | +| dhw.disinfecting | disinfecting | boolean | | true | DHW | 8 | 1 | 1 | +| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 9 | 1 | 1 | +| dhw.disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 10 | 1 | 15 | +| dhw.dailyheating | daily heating | boolean | | true | DHW | 11 | 1 | 1 | +| dhw.dailyheattime | daily heating time | uint8 (>=0<=1431) | minutes | true | DHW | 12 | 1 | 15 | ### Rego 3000, UI800, Logamatic BC400 | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -5965,17 +5975,18 @@ | hc1.solarinfl | solar influence | uint8 (>=-5<=4294967295) | C | true | HC | 54 | 1 | 1 | | hc1.currsolarinfl | curent solar influence | uint8 (>=0<=25) | C | false | HC | 55 | 1 | 1/10 | | dhw.mode | operating mode | enum [off\|eco+\|eco\|comfort\|auto] | | true | DHW | 0 | 1 | 1 | -| dhw.settemp | set temperature | uint8 (>=0<=254) | C | true | DHW | 1 | 1 | 1 | -| dhw.settemplow | set low temperature | uint8 (>=0<=254) | C | true | DHW | 2 | 1 | 1 | -| dhw.circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 3 | 1 | 1 | -| dhw.chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 4 | 1 | 15 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | -| dhw.extra | extra | boolean | | false | DHW | 6 | 1 | 1 | -| dhw.disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | -| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | -| dhw.disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 9 | 1 | 15 | -| dhw.dailyheating | daily heating | boolean | | true | DHW | 10 | 1 | 1 | -| dhw.dailyheattime | daily heating time | uint8 (>=0<=1431) | minutes | true | DHW | 11 | 1 | 15 | +| dhw.modetype | mode type | enum [off\|eco\|comfort\|eco+] | | false | DHW | 1 | 1 | 1 | +| dhw.settemp | set temperature | uint8 (>=0<=254) | C | true | DHW | 2 | 1 | 1 | +| dhw.settemplow | set low temperature | uint8 (>=0<=254) | C | true | DHW | 3 | 1 | 1 | +| dhw.circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 4 | 1 | 1 | +| dhw.chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 5 | 1 | 15 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | +| dhw.extra | extra | boolean | | false | DHW | 7 | 1 | 1 | +| dhw.disinfecting | disinfecting | boolean | | true | DHW | 8 | 1 | 1 | +| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 9 | 1 | 1 | +| dhw.disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 10 | 1 | 15 | +| dhw.dailyheating | daily heating | boolean | | true | DHW | 11 | 1 | 1 | +| dhw.dailyheattime | daily heating time | uint8 (>=0<=1431) | minutes | true | DHW | 12 | 1 | 15 | ### TR120RF, CR20RF | shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor | @@ -6069,17 +6080,18 @@ | hc1.solarinfl | solar influence | uint8 (>=-5<=4294967295) | C | true | HC | 54 | 1 | 1 | | hc1.currsolarinfl | curent solar influence | uint8 (>=0<=25) | C | false | HC | 55 | 1 | 1/10 | | dhw.mode | operating mode | enum [off\|eco+\|eco\|comfort\|auto] | | true | DHW | 0 | 1 | 1 | -| dhw.settemp | set temperature | uint8 (>=0<=254) | C | true | DHW | 1 | 1 | 1 | -| dhw.settemplow | set low temperature | uint8 (>=0<=254) | C | true | DHW | 2 | 1 | 1 | -| dhw.circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 3 | 1 | 1 | -| dhw.chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 4 | 1 | 15 | -| dhw.charge | charge | boolean | | true | DHW | 5 | 1 | 1 | -| dhw.extra | extra | boolean | | false | DHW | 6 | 1 | 1 | -| dhw.disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | -| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | -| dhw.disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 9 | 1 | 15 | -| dhw.dailyheating | daily heating | boolean | | true | DHW | 10 | 1 | 1 | -| dhw.dailyheattime | daily heating time | uint8 (>=0<=1431) | minutes | true | DHW | 11 | 1 | 15 | +| dhw.modetype | mode type | enum [off\|eco\|comfort\|eco+] | | false | DHW | 1 | 1 | 1 | +| dhw.settemp | set temperature | uint8 (>=0<=254) | C | true | DHW | 2 | 1 | 1 | +| dhw.settemplow | set low temperature | uint8 (>=0<=254) | C | true | DHW | 3 | 1 | 1 | +| dhw.circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 4 | 1 | 1 | +| dhw.chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 5 | 1 | 15 | +| dhw.charge | charge | boolean | | true | DHW | 6 | 1 | 1 | +| dhw.extra | extra | boolean | | false | DHW | 7 | 1 | 1 | +| dhw.disinfecting | disinfecting | boolean | | true | DHW | 8 | 1 | 1 | +| dhw.disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 9 | 1 | 1 | +| dhw.disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 10 | 1 | 15 | +| dhw.dailyheating | daily heating | boolean | | true | DHW | 11 | 1 | 1 | +| dhw.dailyheattime | daily heating time | uint8 (>=0<=1431) | minutes | true | DHW | 12 | 1 | 15 | ## Devices of type *ventilation* ### HRV176, HRV156, 5000c, MV200 diff --git a/docs/dump_entities.csv b/docs/dump_entities.csv index b3a051dd8..eeff2c37f 100644 --- a/docs/dump_entities.csv +++ b/docs/dump_entities.csv @@ -160,8 +160,8 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfoff,comfort switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_comfort_switch_off,number.boiler_dhw_comfoff,5,9,1,18,1 "CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecooff,eco switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_eco_switch_off,number.boiler_dhw_ecooff,5,9,1,19,1 "CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecoplusoff,eco+ switch off,uint8 (>=48<=63),C,true,number.boiler_dhw_eco+_switch_off,number.boiler_dhw_ecoplusoff,5,9,1,20,1 -"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfdiff,comfort diff,uint8 (>=6<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1 -"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecodiff,eco diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1 +"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfdiff,comfort diff,uint8 (>=4<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1 +"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecodiff,eco diff,uint8 (>=4<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1 "CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecoplusdiff,eco+ diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1 "CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfstop,comfort stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_comfort_stop_temp,number.boiler_dhw_comfstop,5,9,1,24,1 "CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecostop,eco stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco_stop_temp,number.boiler_dhw_ecostop,5,9,1,25,1 @@ -2668,8 +2668,8 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfoff,comfort switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_comfort_switch_off,number.boiler_dhw_comfoff,5,9,1,18,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecooff,eco switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_eco_switch_off,number.boiler_dhw_ecooff,5,9,1,19,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecoplusoff,eco+ switch off,uint8 (>=48<=63),C,true,number.boiler_dhw_eco+_switch_off,number.boiler_dhw_ecoplusoff,5,9,1,20,1 -"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfdiff,comfort diff,uint8 (>=6<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1 -"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecodiff,eco diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1 +"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfdiff,comfort diff,uint8 (>=4<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1 +"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecodiff,eco diff,uint8 (>=4<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecoplusdiff,eco+ diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfstop,comfort stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_comfort_stop_temp,number.boiler_dhw_comfstop,5,9,1,24,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecostop,eco stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco_stop_temp,number.boiler_dhw_ecostop,5,9,1,25,1 @@ -2876,8 +2876,8 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "Geo 5xx",boiler,173,dhw.comfoff,comfort switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_comfort_switch_off,number.boiler_dhw_comfoff,5,9,1,18,1 "Geo 5xx",boiler,173,dhw.ecooff,eco switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_eco_switch_off,number.boiler_dhw_ecooff,5,9,1,19,1 "Geo 5xx",boiler,173,dhw.ecoplusoff,eco+ switch off,uint8 (>=48<=63),C,true,number.boiler_dhw_eco+_switch_off,number.boiler_dhw_ecoplusoff,5,9,1,20,1 -"Geo 5xx",boiler,173,dhw.comfdiff,comfort diff,uint8 (>=6<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1 -"Geo 5xx",boiler,173,dhw.ecodiff,eco diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1 +"Geo 5xx",boiler,173,dhw.comfdiff,comfort diff,uint8 (>=4<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1 +"Geo 5xx",boiler,173,dhw.ecodiff,eco diff,uint8 (>=4<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1 "Geo 5xx",boiler,173,dhw.ecoplusdiff,eco+ diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1 "Geo 5xx",boiler,173,dhw.comfstop,comfort stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_comfort_stop_temp,number.boiler_dhw_comfstop,5,9,1,24,1 "Geo 5xx",boiler,173,dhw.ecostop,eco stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco_stop_temp,number.boiler_dhw_ecostop,5,9,1,25,1 @@ -3914,17 +3914,18 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "UI800, BC400",thermostat,4,hc1.solarinfl,solar influence,uint8 (>=-5<=4294967295),C,true,number.thermostat_hc1_solar_influence,number.thermostat_hc1_solarinfl,6,1,1,54,1 "UI800, BC400",thermostat,4,hc1.currsolarinfl,curent solar influence,uint8 (>=0<=25),C,false,sensor.thermostat_hc1_curent_solar_influence,sensor.thermostat_hc1_currsolarinfl,6,1,1/10,55,1 "UI800, BC400",thermostat,4,dhw.mode,operating mode,enum [off\|eco+\|eco\|comfort\|auto], ,true,select.thermostat_dhw_operating_mode,select.thermostat_dhw_mode,6,9,1,0,1 -"UI800, BC400",thermostat,4,dhw.settemp,set temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_temperature,number.thermostat_dhw_settemp,6,9,1,1,1 -"UI800, BC400",thermostat,4,dhw.settemplow,set low temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_low_temperature,number.thermostat_dhw_settemplow,6,9,1,2,1 -"UI800, BC400",thermostat,4,dhw.circmode,circulation pump mode,enum [off\|on\|auto\|own prog], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,3,1 -"UI800, BC400",thermostat,4,dhw.chargeduration,charge duration,uint8 (>=0<=3810),minutes,true,number.thermostat_dhw_charge_duration,number.thermostat_dhw_chargeduration,6,9,15,4,1 -"UI800, BC400",thermostat,4,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 -"UI800, BC400",thermostat,4,dhw.extra,extra,boolean, ,false,binary_sensor.thermostat_dhw_extra,binary_sensor.thermostat_dhw_extra,6,9,1,6,1 -"UI800, BC400",thermostat,4,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 -"UI800, BC400",thermostat,4,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,8,1 -"UI800, BC400",thermostat,4,dhw.disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,9,1 -"UI800, BC400",thermostat,4,dhw.dailyheating,daily heating,boolean, ,true,switch.thermostat_dhw_daily_heating,switch.thermostat_dhw_dailyheating,6,9,1,10,1 -"UI800, BC400",thermostat,4,dhw.dailyheattime,daily heating time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_daily_heating_time,number.thermostat_dhw_dailyheattime,6,9,15,11,1 +"UI800, BC400",thermostat,4,dhw.modetype,mode type,enum [off\|eco\|comfort\|eco+], ,false,sensor.thermostat_dhw_mode_type,sensor.thermostat_dhw_modetype,6,9,1,1,1 +"UI800, BC400",thermostat,4,dhw.settemp,set temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_temperature,number.thermostat_dhw_settemp,6,9,1,2,1 +"UI800, BC400",thermostat,4,dhw.settemplow,set low temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_low_temperature,number.thermostat_dhw_settemplow,6,9,1,3,1 +"UI800, BC400",thermostat,4,dhw.circmode,circulation pump mode,enum [off\|on\|auto\|own prog], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,4,1 +"UI800, BC400",thermostat,4,dhw.chargeduration,charge duration,uint8 (>=0<=3810),minutes,true,number.thermostat_dhw_charge_duration,number.thermostat_dhw_chargeduration,6,9,15,5,1 +"UI800, BC400",thermostat,4,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 +"UI800, BC400",thermostat,4,dhw.extra,extra,boolean, ,false,binary_sensor.thermostat_dhw_extra,binary_sensor.thermostat_dhw_extra,6,9,1,7,1 +"UI800, BC400",thermostat,4,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,8,1 +"UI800, BC400",thermostat,4,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,9,1 +"UI800, BC400",thermostat,4,dhw.disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,10,1 +"UI800, BC400",thermostat,4,dhw.dailyheating,daily heating,boolean, ,true,switch.thermostat_dhw_daily_heating,switch.thermostat_dhw_dailyheating,6,9,1,11,1 +"UI800, BC400",thermostat,4,dhw.dailyheattime,daily heating time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_daily_heating_time,number.thermostat_dhw_dailyheattime,6,9,15,12,1 "CR11",thermostat,10,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "CR11",thermostat,10,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "CR11",thermostat,10,hc1.seltemp,selected room temperature,int16 (>=0<=30),C,true,number.thermostat_hc1_selected_room_temperature,number.thermostat_hc1_seltemp,6,1,1/2,0,1 @@ -4004,18 +4005,18 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "RC30",thermostat,67,hc1.switchtime1,own1 program switchtime,string, ,true,sensor.thermostat_hc1_own1_program_switchtime,sensor.thermostat_hc1_switchtime1,6,1,1,91,8 "RC30",thermostat,67,hc1.switchtime2,own2 program switchtime,string, ,true,sensor.thermostat_hc1_own2_program_switchtime,sensor.thermostat_hc1_switchtime2,6,1,1,99,8 "RC30",thermostat,67,dhw.mode,operating mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_operating_mode,select.thermostat_dhw_mode,6,9,1,0,1 -"RC30",thermostat,67,dhw.circmode,circulation pump mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,3,1 -"RC30",thermostat,67,dhw.progmode,program,enum [std prog\|own prog], ,true,select.thermostat_dhw_program,select.thermostat_dhw_progmode,6,9,1,12,1 -"RC30",thermostat,67,dhw.circprog,circulation program,enum [std prog\|own prog], ,true,select.thermostat_dhw_circulation_program,select.thermostat_dhw_circprog,6,9,1,13,1 -"RC30",thermostat,67,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 -"RC30",thermostat,67,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,8,1 -"RC30",thermostat,67,dhw.disinfecthour,disinfection hour,uint8 (>=0<=23), ,true,number.thermostat_dhw_disinfection_hour,number.thermostat_dhw_disinfecthour,6,9,1,14,1 -"RC30",thermostat,67,dhw.maxtemp,maximum temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_maximum_temperature,number.thermostat_dhw_maxtemp,6,9,1,15,1 -"RC30",thermostat,67,dhw.onetimekey,one time key function,boolean, ,true,switch.thermostat_dhw_one_time_key_function,switch.thermostat_dhw_onetimekey,6,9,1,16,1 -"RC30",thermostat,67,dhw.switchtime,program switchtime,string, ,true,sensor.thermostat_dhw_program_switchtime,sensor.thermostat_dhw_switchtime,6,9,1,17,8 -"RC30",thermostat,67,dhw.circswitchtime,circulation program switchtime,string, ,true,sensor.thermostat_dhw_circulation_program_switchtime,sensor.thermostat_dhw_circswitchtime,6,9,1,25,8 -"RC30",thermostat,67,dhw.holidays,holiday dates,string, ,true,sensor.thermostat_dhw_holiday_dates,sensor.thermostat_dhw_holidays,6,9,1,33,13 -"RC30",thermostat,67,dhw.vacations,vacation dates,string, ,true,sensor.thermostat_dhw_vacation_dates,sensor.thermostat_dhw_vacations,6,9,1,46,13 +"RC30",thermostat,67,dhw.circmode,circulation pump mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,4,1 +"RC30",thermostat,67,dhw.progmode,program,enum [std prog\|own prog], ,true,select.thermostat_dhw_program,select.thermostat_dhw_progmode,6,9,1,13,1 +"RC30",thermostat,67,dhw.circprog,circulation program,enum [std prog\|own prog], ,true,select.thermostat_dhw_circulation_program,select.thermostat_dhw_circprog,6,9,1,14,1 +"RC30",thermostat,67,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,8,1 +"RC30",thermostat,67,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,9,1 +"RC30",thermostat,67,dhw.disinfecthour,disinfection hour,uint8 (>=0<=23), ,true,number.thermostat_dhw_disinfection_hour,number.thermostat_dhw_disinfecthour,6,9,1,15,1 +"RC30",thermostat,67,dhw.maxtemp,maximum temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_maximum_temperature,number.thermostat_dhw_maxtemp,6,9,1,16,1 +"RC30",thermostat,67,dhw.onetimekey,one time key function,boolean, ,true,switch.thermostat_dhw_one_time_key_function,switch.thermostat_dhw_onetimekey,6,9,1,17,1 +"RC30",thermostat,67,dhw.switchtime,program switchtime,string, ,true,sensor.thermostat_dhw_program_switchtime,sensor.thermostat_dhw_switchtime,6,9,1,18,8 +"RC30",thermostat,67,dhw.circswitchtime,circulation program switchtime,string, ,true,sensor.thermostat_dhw_circulation_program_switchtime,sensor.thermostat_dhw_circswitchtime,6,9,1,26,8 +"RC30",thermostat,67,dhw.holidays,holiday dates,string, ,true,sensor.thermostat_dhw_holiday_dates,sensor.thermostat_dhw_holidays,6,9,1,34,13 +"RC30",thermostat,67,dhw.vacations,vacation dates,string, ,true,sensor.thermostat_dhw_vacation_dates,sensor.thermostat_dhw_vacations,6,9,1,47,13 "RC20, Moduline 300",thermostat,77,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "RC20, Moduline 300",thermostat,77,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "RC20, Moduline 300",thermostat,77,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4068,10 +4069,10 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "Moduline 400",thermostat,78,hc1.manualtemp,manual temperature,uint8 (>=0<=127),C,true,number.thermostat_hc1_manual_temperature,number.thermostat_hc1_manualtemp,6,1,1/2,6,1 "Moduline 400",thermostat,78,hc1.offtemp,temperature when mode is off,uint8 (>=0<=127),C,true,number.thermostat_hc1_temperature_when_mode_is_off,number.thermostat_hc1_offtemp,6,1,1/2,107,1 "Moduline 400",thermostat,78,dhw.mode,operating mode,enum [on\|off\|auto], ,true,select.thermostat_dhw_operating_mode,select.thermostat_dhw_mode,6,9,1,0,1 -"Moduline 400",thermostat,78,dhw.whenmodeoff,when thermostat mode off,boolean, ,true,switch.thermostat_dhw_when_thermostat_mode_off,switch.thermostat_dhw_whenmodeoff,6,9,1,59,1 -"Moduline 400",thermostat,78,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 -"Moduline 400",thermostat,78,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,8,1 -"Moduline 400",thermostat,78,dhw.disinfecthour,disinfection hour,uint8 (>=0<=23), ,true,number.thermostat_dhw_disinfection_hour,number.thermostat_dhw_disinfecthour,6,9,1,14,1 +"Moduline 400",thermostat,78,dhw.whenmodeoff,when thermostat mode off,boolean, ,true,switch.thermostat_dhw_when_thermostat_mode_off,switch.thermostat_dhw_whenmodeoff,6,9,1,60,1 +"Moduline 400",thermostat,78,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,8,1 +"Moduline 400",thermostat,78,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,9,1 +"Moduline 400",thermostat,78,dhw.disinfecthour,disinfection hour,uint8 (>=0<=23), ,true,number.thermostat_dhw_disinfection_hour,number.thermostat_dhw_disinfecthour,6,9,1,15,1 "RC10, Moduline 100",thermostat,79,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "RC10, Moduline 100",thermostat,79,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "RC10, Moduline 100",thermostat,79,intoffset,internal temperature offset,int8 (>=-12<=12),C,true,number.thermostat_internal_temperature_offset,number.thermostat_intoffset,6,0,1/10,46,1 @@ -4150,18 +4151,18 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "RC35",thermostat,86,hc1.switchtime1,own1 program switchtime,string, ,true,sensor.thermostat_hc1_own1_program_switchtime,sensor.thermostat_hc1_switchtime1,6,1,1,91,8 "RC35",thermostat,86,hc1.switchtime2,own2 program switchtime,string, ,true,sensor.thermostat_hc1_own2_program_switchtime,sensor.thermostat_hc1_switchtime2,6,1,1,99,8 "RC35",thermostat,86,dhw.mode,operating mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_operating_mode,select.thermostat_dhw_mode,6,9,1,0,1 -"RC35",thermostat,86,dhw.circmode,circulation pump mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,3,1 -"RC35",thermostat,86,dhw.progmode,program,enum [std prog\|own prog], ,true,select.thermostat_dhw_program,select.thermostat_dhw_progmode,6,9,1,12,1 -"RC35",thermostat,86,dhw.circprog,circulation program,enum [std prog\|own prog], ,true,select.thermostat_dhw_circulation_program,select.thermostat_dhw_circprog,6,9,1,13,1 -"RC35",thermostat,86,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 -"RC35",thermostat,86,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,8,1 -"RC35",thermostat,86,dhw.disinfecthour,disinfection hour,uint8 (>=0<=23), ,true,number.thermostat_dhw_disinfection_hour,number.thermostat_dhw_disinfecthour,6,9,1,14,1 -"RC35",thermostat,86,dhw.maxtemp,maximum temperature,uint8 (>=60<=80),C,true,number.thermostat_dhw_maximum_temperature,number.thermostat_dhw_maxtemp,6,9,1,15,1 -"RC35",thermostat,86,dhw.onetimekey,one time key function,boolean, ,true,switch.thermostat_dhw_one_time_key_function,switch.thermostat_dhw_onetimekey,6,9,1,16,1 -"RC35",thermostat,86,dhw.switchtime,program switchtime,string, ,true,sensor.thermostat_dhw_program_switchtime,sensor.thermostat_dhw_switchtime,6,9,1,17,8 -"RC35",thermostat,86,dhw.circswitchtime,circulation program switchtime,string, ,true,sensor.thermostat_dhw_circulation_program_switchtime,sensor.thermostat_dhw_circswitchtime,6,9,1,25,8 -"RC35",thermostat,86,dhw.holidays,holiday dates,string, ,true,sensor.thermostat_dhw_holiday_dates,sensor.thermostat_dhw_holidays,6,9,1,33,13 -"RC35",thermostat,86,dhw.vacations,vacation dates,string, ,true,sensor.thermostat_dhw_vacation_dates,sensor.thermostat_dhw_vacations,6,9,1,46,13 +"RC35",thermostat,86,dhw.circmode,circulation pump mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,4,1 +"RC35",thermostat,86,dhw.progmode,program,enum [std prog\|own prog], ,true,select.thermostat_dhw_program,select.thermostat_dhw_progmode,6,9,1,13,1 +"RC35",thermostat,86,dhw.circprog,circulation program,enum [std prog\|own prog], ,true,select.thermostat_dhw_circulation_program,select.thermostat_dhw_circprog,6,9,1,14,1 +"RC35",thermostat,86,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,8,1 +"RC35",thermostat,86,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,9,1 +"RC35",thermostat,86,dhw.disinfecthour,disinfection hour,uint8 (>=0<=23), ,true,number.thermostat_dhw_disinfection_hour,number.thermostat_dhw_disinfecthour,6,9,1,15,1 +"RC35",thermostat,86,dhw.maxtemp,maximum temperature,uint8 (>=60<=80),C,true,number.thermostat_dhw_maximum_temperature,number.thermostat_dhw_maxtemp,6,9,1,16,1 +"RC35",thermostat,86,dhw.onetimekey,one time key function,boolean, ,true,switch.thermostat_dhw_one_time_key_function,switch.thermostat_dhw_onetimekey,6,9,1,17,1 +"RC35",thermostat,86,dhw.switchtime,program switchtime,string, ,true,sensor.thermostat_dhw_program_switchtime,sensor.thermostat_dhw_switchtime,6,9,1,18,8 +"RC35",thermostat,86,dhw.circswitchtime,circulation program switchtime,string, ,true,sensor.thermostat_dhw_circulation_program_switchtime,sensor.thermostat_dhw_circswitchtime,6,9,1,26,8 +"RC35",thermostat,86,dhw.holidays,holiday dates,string, ,true,sensor.thermostat_dhw_holiday_dates,sensor.thermostat_dhw_holidays,6,9,1,34,13 +"RC35",thermostat,86,dhw.vacations,vacation dates,string, ,true,sensor.thermostat_dhw_vacation_dates,sensor.thermostat_dhw_vacations,6,9,1,47,13 "RC10, Moduline 100",thermostat,90,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "RC10, Moduline 100",thermostat,90,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "RC10, Moduline 100",thermostat,90,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4296,17 +4297,18 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "RC200, CW100, CR120, CR50",thermostat,157,hc1.solarinfl,solar influence,uint8 (>=-5<=4294967295),C,true,number.thermostat_hc1_solar_influence,number.thermostat_hc1_solarinfl,6,1,1,54,1 "RC200, CW100, CR120, CR50",thermostat,157,hc1.currsolarinfl,curent solar influence,uint8 (>=0<=25),C,false,sensor.thermostat_hc1_curent_solar_influence,sensor.thermostat_hc1_currsolarinfl,6,1,1/10,55,1 "RC200, CW100, CR120, CR50",thermostat,157,dhw.mode,operating mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_operating_mode,select.thermostat_dhw_mode,6,9,1,0,1 -"RC200, CW100, CR120, CR50",thermostat,157,dhw.settemp,set temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_temperature,number.thermostat_dhw_settemp,6,9,1,1,1 -"RC200, CW100, CR120, CR50",thermostat,157,dhw.settemplow,set low temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_low_temperature,number.thermostat_dhw_settemplow,6,9,1,2,1 -"RC200, CW100, CR120, CR50",thermostat,157,dhw.circmode,circulation pump mode,enum [off\|on\|auto\|own prog], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,3,1 -"RC200, CW100, CR120, CR50",thermostat,157,dhw.chargeduration,charge duration,uint8 (>=0<=3810),minutes,true,number.thermostat_dhw_charge_duration,number.thermostat_dhw_chargeduration,6,9,15,4,1 -"RC200, CW100, CR120, CR50",thermostat,157,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 -"RC200, CW100, CR120, CR50",thermostat,157,dhw.extra,extra,boolean, ,false,binary_sensor.thermostat_dhw_extra,binary_sensor.thermostat_dhw_extra,6,9,1,6,1 -"RC200, CW100, CR120, CR50",thermostat,157,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 -"RC200, CW100, CR120, CR50",thermostat,157,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,8,1 -"RC200, CW100, CR120, CR50",thermostat,157,dhw.disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,9,1 -"RC200, CW100, CR120, CR50",thermostat,157,dhw.dailyheating,daily heating,boolean, ,true,switch.thermostat_dhw_daily_heating,switch.thermostat_dhw_dailyheating,6,9,1,10,1 -"RC200, CW100, CR120, CR50",thermostat,157,dhw.dailyheattime,daily heating time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_daily_heating_time,number.thermostat_dhw_dailyheattime,6,9,15,11,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.modetype,mode type,enum [off\|eco\|comfort\|eco+], ,false,sensor.thermostat_dhw_mode_type,sensor.thermostat_dhw_modetype,6,9,1,1,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.settemp,set temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_temperature,number.thermostat_dhw_settemp,6,9,1,2,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.settemplow,set low temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_low_temperature,number.thermostat_dhw_settemplow,6,9,1,3,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.circmode,circulation pump mode,enum [off\|on\|auto\|own prog], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,4,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.chargeduration,charge duration,uint8 (>=0<=3810),minutes,true,number.thermostat_dhw_charge_duration,number.thermostat_dhw_chargeduration,6,9,15,5,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.extra,extra,boolean, ,false,binary_sensor.thermostat_dhw_extra,binary_sensor.thermostat_dhw_extra,6,9,1,7,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,8,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,9,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,10,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.dailyheating,daily heating,boolean, ,true,switch.thermostat_dhw_daily_heating,switch.thermostat_dhw_dailyheating,6,9,1,11,1 +"RC200, CW100, CR120, CR50",thermostat,157,dhw.dailyheattime,daily heating time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_daily_heating_time,number.thermostat_dhw_dailyheattime,6,9,15,12,1 "RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,datetime,date/time,string, ,true,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4386,17 +4388,18 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,hc1.solarinfl,solar influence,uint8 (>=-5<=4294967295),C,true,number.thermostat_hc1_solar_influence,number.thermostat_hc1_solarinfl,6,1,1,54,1 "RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,hc1.currsolarinfl,curent solar influence,uint8 (>=0<=25),C,false,sensor.thermostat_hc1_curent_solar_influence,sensor.thermostat_hc1_currsolarinfl,6,1,1/10,55,1 "RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.mode,operating mode,enum [off\|normal\|comfort\|auto\|own prog], ,true,select.thermostat_dhw_operating_mode,select.thermostat_dhw_mode,6,9,1,0,1 -"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.settemp,set temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_temperature,number.thermostat_dhw_settemp,6,9,1,1,1 -"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.settemplow,set low temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_low_temperature,number.thermostat_dhw_settemplow,6,9,1,2,1 -"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.circmode,circulation pump mode,enum [off\|on\|auto\|own prog], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,3,1 -"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.chargeduration,charge duration,uint8 (>=0<=3810),minutes,true,number.thermostat_dhw_charge_duration,number.thermostat_dhw_chargeduration,6,9,15,4,1 -"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 -"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.extra,extra,boolean, ,false,binary_sensor.thermostat_dhw_extra,binary_sensor.thermostat_dhw_extra,6,9,1,6,1 -"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 -"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,8,1 -"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,9,1 -"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.dailyheating,daily heating,boolean, ,true,switch.thermostat_dhw_daily_heating,switch.thermostat_dhw_dailyheating,6,9,1,10,1 -"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.dailyheattime,daily heating time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_daily_heating_time,number.thermostat_dhw_dailyheattime,6,9,15,11,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.modetype,mode type,enum [off\|eco\|comfort\|eco+], ,false,sensor.thermostat_dhw_mode_type,sensor.thermostat_dhw_modetype,6,9,1,1,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.settemp,set temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_temperature,number.thermostat_dhw_settemp,6,9,1,2,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.settemplow,set low temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_low_temperature,number.thermostat_dhw_settemplow,6,9,1,3,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.circmode,circulation pump mode,enum [off\|on\|auto\|own prog], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,4,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.chargeduration,charge duration,uint8 (>=0<=3810),minutes,true,number.thermostat_dhw_charge_duration,number.thermostat_dhw_chargeduration,6,9,15,5,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.extra,extra,boolean, ,false,binary_sensor.thermostat_dhw_extra,binary_sensor.thermostat_dhw_extra,6,9,1,7,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,8,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,9,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,10,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.dailyheating,daily heating,boolean, ,true,switch.thermostat_dhw_daily_heating,switch.thermostat_dhw_dailyheating,6,9,1,11,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,dhw.dailyheattime,daily heating time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_daily_heating_time,number.thermostat_dhw_dailyheattime,6,9,15,12,1 "RC100, CR10, Moduline 1000/1010",thermostat,165,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "RC100, CR10, Moduline 1000/1010",thermostat,165,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "RC100, CR10, Moduline 1000/1010",thermostat,165,hc1.seltemp,selected room temperature,int16 (>=0<=30),C,true,number.thermostat_hc1_selected_room_temperature,number.thermostat_hc1_seltemp,6,1,1/2,0,1 @@ -4485,17 +4488,18 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "Rego 2000/3000",thermostat,172,hc1.solarinfl,solar influence,uint8 (>=-5<=4294967295),C,true,number.thermostat_hc1_solar_influence,number.thermostat_hc1_solarinfl,6,1,1,54,1 "Rego 2000/3000",thermostat,172,hc1.currsolarinfl,curent solar influence,uint8 (>=0<=25),C,false,sensor.thermostat_hc1_curent_solar_influence,sensor.thermostat_hc1_currsolarinfl,6,1,1/10,55,1 "Rego 2000/3000",thermostat,172,dhw.mode,operating mode,enum [normal\|comfort\|eco+], ,true,select.thermostat_dhw_operating_mode,select.thermostat_dhw_mode,6,9,1,0,1 -"Rego 2000/3000",thermostat,172,dhw.settemp,set temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_temperature,number.thermostat_dhw_settemp,6,9,1,1,1 -"Rego 2000/3000",thermostat,172,dhw.settemplow,set low temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_low_temperature,number.thermostat_dhw_settemplow,6,9,1,2,1 -"Rego 2000/3000",thermostat,172,dhw.circmode,circulation pump mode,enum [off\|on\|auto\|own prog], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,3,1 -"Rego 2000/3000",thermostat,172,dhw.chargeduration,charge duration,uint8 (>=0<=3810),minutes,true,number.thermostat_dhw_charge_duration,number.thermostat_dhw_chargeduration,6,9,15,4,1 -"Rego 2000/3000",thermostat,172,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 -"Rego 2000/3000",thermostat,172,dhw.extra,extra,boolean, ,false,binary_sensor.thermostat_dhw_extra,binary_sensor.thermostat_dhw_extra,6,9,1,6,1 -"Rego 2000/3000",thermostat,172,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 -"Rego 2000/3000",thermostat,172,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,8,1 -"Rego 2000/3000",thermostat,172,dhw.disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,9,1 -"Rego 2000/3000",thermostat,172,dhw.dailyheating,daily heating,boolean, ,true,switch.thermostat_dhw_daily_heating,switch.thermostat_dhw_dailyheating,6,9,1,10,1 -"Rego 2000/3000",thermostat,172,dhw.dailyheattime,daily heating time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_daily_heating_time,number.thermostat_dhw_dailyheattime,6,9,15,11,1 +"Rego 2000/3000",thermostat,172,dhw.modetype,mode type,enum [off\|eco\|comfort\|eco+], ,false,sensor.thermostat_dhw_mode_type,sensor.thermostat_dhw_modetype,6,9,1,1,1 +"Rego 2000/3000",thermostat,172,dhw.settemp,set temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_temperature,number.thermostat_dhw_settemp,6,9,1,2,1 +"Rego 2000/3000",thermostat,172,dhw.settemplow,set low temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_low_temperature,number.thermostat_dhw_settemplow,6,9,1,3,1 +"Rego 2000/3000",thermostat,172,dhw.circmode,circulation pump mode,enum [off\|on\|auto\|own prog], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,4,1 +"Rego 2000/3000",thermostat,172,dhw.chargeduration,charge duration,uint8 (>=0<=3810),minutes,true,number.thermostat_dhw_charge_duration,number.thermostat_dhw_chargeduration,6,9,15,5,1 +"Rego 2000/3000",thermostat,172,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 +"Rego 2000/3000",thermostat,172,dhw.extra,extra,boolean, ,false,binary_sensor.thermostat_dhw_extra,binary_sensor.thermostat_dhw_extra,6,9,1,7,1 +"Rego 2000/3000",thermostat,172,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,8,1 +"Rego 2000/3000",thermostat,172,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,9,1 +"Rego 2000/3000",thermostat,172,dhw.disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,10,1 +"Rego 2000/3000",thermostat,172,dhw.dailyheating,daily heating,boolean, ,true,switch.thermostat_dhw_daily_heating,switch.thermostat_dhw_dailyheating,6,9,1,11,1 +"Rego 2000/3000",thermostat,172,dhw.dailyheattime,daily heating time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_daily_heating_time,number.thermostat_dhw_dailyheattime,6,9,15,12,1 "Comfort RF",thermostat,215,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "Comfort RF",thermostat,215,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "Comfort RF",thermostat,215,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4602,17 +4606,18 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "Rego 3000, UI800, Logamatic BC400",thermostat,253,hc1.solarinfl,solar influence,uint8 (>=-5<=4294967295),C,true,number.thermostat_hc1_solar_influence,number.thermostat_hc1_solarinfl,6,1,1,54,1 "Rego 3000, UI800, Logamatic BC400",thermostat,253,hc1.currsolarinfl,curent solar influence,uint8 (>=0<=25),C,false,sensor.thermostat_hc1_curent_solar_influence,sensor.thermostat_hc1_currsolarinfl,6,1,1/10,55,1 "Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.mode,operating mode,enum [off\|eco+\|eco\|comfort\|auto], ,true,select.thermostat_dhw_operating_mode,select.thermostat_dhw_mode,6,9,1,0,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.settemp,set temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_temperature,number.thermostat_dhw_settemp,6,9,1,1,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.settemplow,set low temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_low_temperature,number.thermostat_dhw_settemplow,6,9,1,2,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.circmode,circulation pump mode,enum [off\|on\|auto\|own prog], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,3,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.chargeduration,charge duration,uint8 (>=0<=3810),minutes,true,number.thermostat_dhw_charge_duration,number.thermostat_dhw_chargeduration,6,9,15,4,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.extra,extra,boolean, ,false,binary_sensor.thermostat_dhw_extra,binary_sensor.thermostat_dhw_extra,6,9,1,6,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,8,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,9,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.dailyheating,daily heating,boolean, ,true,switch.thermostat_dhw_daily_heating,switch.thermostat_dhw_dailyheating,6,9,1,10,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.dailyheattime,daily heating time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_daily_heating_time,number.thermostat_dhw_dailyheattime,6,9,15,11,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.modetype,mode type,enum [off\|eco\|comfort\|eco+], ,false,sensor.thermostat_dhw_mode_type,sensor.thermostat_dhw_modetype,6,9,1,1,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.settemp,set temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_temperature,number.thermostat_dhw_settemp,6,9,1,2,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.settemplow,set low temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_set_low_temperature,number.thermostat_dhw_settemplow,6,9,1,3,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.circmode,circulation pump mode,enum [off\|on\|auto\|own prog], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,4,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.chargeduration,charge duration,uint8 (>=0<=3810),minutes,true,number.thermostat_dhw_charge_duration,number.thermostat_dhw_chargeduration,6,9,15,5,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.extra,extra,boolean, ,false,binary_sensor.thermostat_dhw_extra,binary_sensor.thermostat_dhw_extra,6,9,1,7,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,8,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,9,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,10,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.dailyheating,daily heating,boolean, ,true,switch.thermostat_dhw_daily_heating,switch.thermostat_dhw_dailyheating,6,9,1,11,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,dhw.dailyheattime,daily heating time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_daily_heating_time,number.thermostat_dhw_dailyheattime,6,9,15,12,1 "ES72, RC20",thermostat,66,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "ES72, RC20",thermostat,66,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "ES72, RC20",thermostat,66,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4684,18 +4689,18 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "ES73",thermostat,76,hc1.switchtime1,own1 program switchtime,string, ,true,sensor.thermostat_hc1_own1_program_switchtime,sensor.thermostat_hc1_switchtime1,6,1,1,91,8 "ES73",thermostat,76,hc1.switchtime2,own2 program switchtime,string, ,true,sensor.thermostat_hc1_own2_program_switchtime,sensor.thermostat_hc1_switchtime2,6,1,1,99,8 "ES73",thermostat,76,dhw.mode,operating mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_operating_mode,select.thermostat_dhw_mode,6,9,1,0,1 -"ES73",thermostat,76,dhw.circmode,circulation pump mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,3,1 -"ES73",thermostat,76,dhw.progmode,program,enum [std prog\|own prog], ,true,select.thermostat_dhw_program,select.thermostat_dhw_progmode,6,9,1,12,1 -"ES73",thermostat,76,dhw.circprog,circulation program,enum [std prog\|own prog], ,true,select.thermostat_dhw_circulation_program,select.thermostat_dhw_circprog,6,9,1,13,1 -"ES73",thermostat,76,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 -"ES73",thermostat,76,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,8,1 -"ES73",thermostat,76,dhw.disinfecthour,disinfection hour,uint8 (>=0<=23), ,true,number.thermostat_dhw_disinfection_hour,number.thermostat_dhw_disinfecthour,6,9,1,14,1 -"ES73",thermostat,76,dhw.maxtemp,maximum temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_maximum_temperature,number.thermostat_dhw_maxtemp,6,9,1,15,1 -"ES73",thermostat,76,dhw.onetimekey,one time key function,boolean, ,true,switch.thermostat_dhw_one_time_key_function,switch.thermostat_dhw_onetimekey,6,9,1,16,1 -"ES73",thermostat,76,dhw.switchtime,program switchtime,string, ,true,sensor.thermostat_dhw_program_switchtime,sensor.thermostat_dhw_switchtime,6,9,1,17,8 -"ES73",thermostat,76,dhw.circswitchtime,circulation program switchtime,string, ,true,sensor.thermostat_dhw_circulation_program_switchtime,sensor.thermostat_dhw_circswitchtime,6,9,1,25,8 -"ES73",thermostat,76,dhw.holidays,holiday dates,string, ,true,sensor.thermostat_dhw_holiday_dates,sensor.thermostat_dhw_holidays,6,9,1,33,13 -"ES73",thermostat,76,dhw.vacations,vacation dates,string, ,true,sensor.thermostat_dhw_vacation_dates,sensor.thermostat_dhw_vacations,6,9,1,46,13 +"ES73",thermostat,76,dhw.circmode,circulation pump mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,4,1 +"ES73",thermostat,76,dhw.progmode,program,enum [std prog\|own prog], ,true,select.thermostat_dhw_program,select.thermostat_dhw_progmode,6,9,1,13,1 +"ES73",thermostat,76,dhw.circprog,circulation program,enum [std prog\|own prog], ,true,select.thermostat_dhw_circulation_program,select.thermostat_dhw_circprog,6,9,1,14,1 +"ES73",thermostat,76,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,8,1 +"ES73",thermostat,76,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,9,1 +"ES73",thermostat,76,dhw.disinfecthour,disinfection hour,uint8 (>=0<=23), ,true,number.thermostat_dhw_disinfection_hour,number.thermostat_dhw_disinfecthour,6,9,1,15,1 +"ES73",thermostat,76,dhw.maxtemp,maximum temperature,uint8 (>=0<=254),C,true,number.thermostat_dhw_maximum_temperature,number.thermostat_dhw_maxtemp,6,9,1,16,1 +"ES73",thermostat,76,dhw.onetimekey,one time key function,boolean, ,true,switch.thermostat_dhw_one_time_key_function,switch.thermostat_dhw_onetimekey,6,9,1,17,1 +"ES73",thermostat,76,dhw.switchtime,program switchtime,string, ,true,sensor.thermostat_dhw_program_switchtime,sensor.thermostat_dhw_switchtime,6,9,1,18,8 +"ES73",thermostat,76,dhw.circswitchtime,circulation program switchtime,string, ,true,sensor.thermostat_dhw_circulation_program_switchtime,sensor.thermostat_dhw_circswitchtime,6,9,1,26,8 +"ES73",thermostat,76,dhw.holidays,holiday dates,string, ,true,sensor.thermostat_dhw_holiday_dates,sensor.thermostat_dhw_holidays,6,9,1,34,13 +"ES73",thermostat,76,dhw.vacations,vacation dates,string, ,true,sensor.thermostat_dhw_vacation_dates,sensor.thermostat_dhw_vacations,6,9,1,47,13 "ES72, RC20",thermostat,113,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "ES72, RC20",thermostat,113,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "ES72, RC20",thermostat,113,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4764,18 +4769,18 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "ES79",thermostat,156,hc1.switchtime1,own1 program switchtime,string, ,true,sensor.thermostat_hc1_own1_program_switchtime,sensor.thermostat_hc1_switchtime1,6,1,1,91,8 "ES79",thermostat,156,hc1.switchtime2,own2 program switchtime,string, ,true,sensor.thermostat_hc1_own2_program_switchtime,sensor.thermostat_hc1_switchtime2,6,1,1,99,8 "ES79",thermostat,156,dhw.mode,operating mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_operating_mode,select.thermostat_dhw_mode,6,9,1,0,1 -"ES79",thermostat,156,dhw.circmode,circulation pump mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,3,1 -"ES79",thermostat,156,dhw.progmode,program,enum [std prog\|own prog], ,true,select.thermostat_dhw_program,select.thermostat_dhw_progmode,6,9,1,12,1 -"ES79",thermostat,156,dhw.circprog,circulation program,enum [std prog\|own prog], ,true,select.thermostat_dhw_circulation_program,select.thermostat_dhw_circprog,6,9,1,13,1 -"ES79",thermostat,156,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 -"ES79",thermostat,156,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,8,1 -"ES79",thermostat,156,dhw.disinfecthour,disinfection hour,uint8 (>=0<=23), ,true,number.thermostat_dhw_disinfection_hour,number.thermostat_dhw_disinfecthour,6,9,1,14,1 -"ES79",thermostat,156,dhw.maxtemp,maximum temperature,uint8 (>=60<=80),C,true,number.thermostat_dhw_maximum_temperature,number.thermostat_dhw_maxtemp,6,9,1,15,1 -"ES79",thermostat,156,dhw.onetimekey,one time key function,boolean, ,true,switch.thermostat_dhw_one_time_key_function,switch.thermostat_dhw_onetimekey,6,9,1,16,1 -"ES79",thermostat,156,dhw.switchtime,program switchtime,string, ,true,sensor.thermostat_dhw_program_switchtime,sensor.thermostat_dhw_switchtime,6,9,1,17,8 -"ES79",thermostat,156,dhw.circswitchtime,circulation program switchtime,string, ,true,sensor.thermostat_dhw_circulation_program_switchtime,sensor.thermostat_dhw_circswitchtime,6,9,1,25,8 -"ES79",thermostat,156,dhw.holidays,holiday dates,string, ,true,sensor.thermostat_dhw_holiday_dates,sensor.thermostat_dhw_holidays,6,9,1,33,13 -"ES79",thermostat,156,dhw.vacations,vacation dates,string, ,true,sensor.thermostat_dhw_vacation_dates,sensor.thermostat_dhw_vacations,6,9,1,46,13 +"ES79",thermostat,156,dhw.circmode,circulation pump mode,enum [off\|on\|auto], ,true,select.thermostat_dhw_circulation_pump_mode,select.thermostat_dhw_circmode,6,9,1,4,1 +"ES79",thermostat,156,dhw.progmode,program,enum [std prog\|own prog], ,true,select.thermostat_dhw_program,select.thermostat_dhw_progmode,6,9,1,13,1 +"ES79",thermostat,156,dhw.circprog,circulation program,enum [std prog\|own prog], ,true,select.thermostat_dhw_circulation_program,select.thermostat_dhw_circprog,6,9,1,14,1 +"ES79",thermostat,156,dhw.disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,8,1 +"ES79",thermostat,156,dhw.disinfectday,disinfection day,enum [mo\|tu\|we\|th\|fr\|sa\|su\|all], ,true,select.thermostat_dhw_disinfection_day,select.thermostat_dhw_disinfectday,6,9,1,9,1 +"ES79",thermostat,156,dhw.disinfecthour,disinfection hour,uint8 (>=0<=23), ,true,number.thermostat_dhw_disinfection_hour,number.thermostat_dhw_disinfecthour,6,9,1,15,1 +"ES79",thermostat,156,dhw.maxtemp,maximum temperature,uint8 (>=60<=80),C,true,number.thermostat_dhw_maximum_temperature,number.thermostat_dhw_maxtemp,6,9,1,16,1 +"ES79",thermostat,156,dhw.onetimekey,one time key function,boolean, ,true,switch.thermostat_dhw_one_time_key_function,switch.thermostat_dhw_onetimekey,6,9,1,17,1 +"ES79",thermostat,156,dhw.switchtime,program switchtime,string, ,true,sensor.thermostat_dhw_program_switchtime,sensor.thermostat_dhw_switchtime,6,9,1,18,8 +"ES79",thermostat,156,dhw.circswitchtime,circulation program switchtime,string, ,true,sensor.thermostat_dhw_circulation_program_switchtime,sensor.thermostat_dhw_circswitchtime,6,9,1,26,8 +"ES79",thermostat,156,dhw.holidays,holiday dates,string, ,true,sensor.thermostat_dhw_holiday_dates,sensor.thermostat_dhw_holidays,6,9,1,34,13 +"ES79",thermostat,156,dhw.vacations,vacation dates,string, ,true,sensor.thermostat_dhw_vacation_dates,sensor.thermostat_dhw_vacations,6,9,1,47,13 "FW100",thermostat,105,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "FW100",thermostat,105,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "FW100",thermostat,105,datetime,date/time,string, ,true,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4812,7 +4817,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "FW100",thermostat,105,hc1.roominflfactor,room influence factor,uint8 (>=0<=100),%,true,number.thermostat_hc1_room_influence_factor,number.thermostat_hc1_roominflfactor,6,1,10,14,1 "FW100",thermostat,105,hc1.heatingtype,heating type,enum [off\|heatingcurve\|radiator\|convector\|floor], ,true,select.thermostat_hc1_heating_type,select.thermostat_hc1_heatingtype,6,1,1,19,1 "FW100",thermostat,105,hc1.controlmode,control mode,enum [off\|unmixed\|unmixed IPM\|mixed IPM], ,true,select.thermostat_hc1_control_mode,select.thermostat_hc1_controlmode,6,1,1,25,1 -"FW100",thermostat,105,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 +"FW100",thermostat,105,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 "FW200",thermostat,106,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "FW200",thermostat,106,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "FW200",thermostat,106,datetime,date/time,string, ,true,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4849,7 +4854,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "FW200",thermostat,106,hc1.roominflfactor,room influence factor,uint8 (>=0<=100),%,true,number.thermostat_hc1_room_influence_factor,number.thermostat_hc1_roominflfactor,6,1,10,14,1 "FW200",thermostat,106,hc1.heatingtype,heating type,enum [off\|heatingcurve\|radiator\|convector\|floor], ,true,select.thermostat_hc1_heating_type,select.thermostat_hc1_heatingtype,6,1,1,19,1 "FW200",thermostat,106,hc1.controlmode,control mode,enum [off\|unmixed\|unmixed IPM\|mixed IPM], ,true,select.thermostat_hc1_control_mode,select.thermostat_hc1_controlmode,6,1,1,25,1 -"FW200",thermostat,106,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 +"FW200",thermostat,106,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 "FR100",thermostat,107,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "FR100",thermostat,107,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "FR100",thermostat,107,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4884,7 +4889,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "FR100",thermostat,107,hc1.roominflfactor,room influence factor,uint8 (>=0<=100),%,true,number.thermostat_hc1_room_influence_factor,number.thermostat_hc1_roominflfactor,6,1,10,14,1 "FR100",thermostat,107,hc1.heatingtype,heating type,enum [off\|heatingcurve\|radiator\|convector\|floor], ,true,select.thermostat_hc1_heating_type,select.thermostat_hc1_heatingtype,6,1,1,19,1 "FR100",thermostat,107,hc1.controlmode,control mode,enum [off\|unmixed\|unmixed IPM\|mixed IPM], ,true,select.thermostat_hc1_control_mode,select.thermostat_hc1_controlmode,6,1,1,25,1 -"FR100",thermostat,107,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 +"FR100",thermostat,107,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 "FR110",thermostat,108,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "FR110",thermostat,108,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "FR110",thermostat,108,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4919,7 +4924,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "FR110",thermostat,108,hc1.roominflfactor,room influence factor,uint8 (>=0<=100),%,true,number.thermostat_hc1_room_influence_factor,number.thermostat_hc1_roominflfactor,6,1,10,14,1 "FR110",thermostat,108,hc1.heatingtype,heating type,enum [off\|heatingcurve\|radiator\|convector\|floor], ,true,select.thermostat_hc1_heating_type,select.thermostat_hc1_heatingtype,6,1,1,19,1 "FR110",thermostat,108,hc1.controlmode,control mode,enum [off\|unmixed\|unmixed IPM\|mixed IPM], ,true,select.thermostat_hc1_control_mode,select.thermostat_hc1_controlmode,6,1,1,25,1 -"FR110",thermostat,108,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 +"FR110",thermostat,108,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 "FB10",thermostat,109,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "FB10",thermostat,109,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "FB10",thermostat,109,datetime,date/time,string, ,true,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4956,7 +4961,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "FB10",thermostat,109,hc1.roominflfactor,room influence factor,uint8 (>=0<=100),%,true,number.thermostat_hc1_room_influence_factor,number.thermostat_hc1_roominflfactor,6,1,10,14,1 "FB10",thermostat,109,hc1.heatingtype,heating type,enum [off\|heatingcurve\|radiator\|convector\|floor], ,true,select.thermostat_hc1_heating_type,select.thermostat_hc1_heatingtype,6,1,1,19,1 "FB10",thermostat,109,hc1.controlmode,control mode,enum [off\|unmixed\|unmixed IPM\|mixed IPM], ,true,select.thermostat_hc1_control_mode,select.thermostat_hc1_controlmode,6,1,1,25,1 -"FB10",thermostat,109,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 +"FB10",thermostat,109,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 "FB100",thermostat,110,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "FB100",thermostat,110,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "FB100",thermostat,110,datetime,date/time,string, ,true,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -4993,7 +4998,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "FB100",thermostat,110,hc1.roominflfactor,room influence factor,uint8 (>=0<=100),%,true,number.thermostat_hc1_room_influence_factor,number.thermostat_hc1_roominflfactor,6,1,10,14,1 "FB100",thermostat,110,hc1.heatingtype,heating type,enum [off\|heatingcurve\|radiator\|convector\|floor], ,true,select.thermostat_hc1_heating_type,select.thermostat_hc1_heatingtype,6,1,1,19,1 "FB100",thermostat,110,hc1.controlmode,control mode,enum [off\|unmixed\|unmixed IPM\|mixed IPM], ,true,select.thermostat_hc1_control_mode,select.thermostat_hc1_controlmode,6,1,1,25,1 -"FB100",thermostat,110,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 +"FB100",thermostat,110,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 "FR10",thermostat,111,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "FR10",thermostat,111,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "FR10",thermostat,111,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -5028,7 +5033,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "FR10",thermostat,111,hc1.roominflfactor,room influence factor,uint8 (>=0<=100),%,true,number.thermostat_hc1_room_influence_factor,number.thermostat_hc1_roominflfactor,6,1,10,14,1 "FR10",thermostat,111,hc1.heatingtype,heating type,enum [off\|heatingcurve\|radiator\|convector\|floor], ,true,select.thermostat_hc1_heating_type,select.thermostat_hc1_heatingtype,6,1,1,19,1 "FR10",thermostat,111,hc1.controlmode,control mode,enum [off\|unmixed\|unmixed IPM\|mixed IPM], ,true,select.thermostat_hc1_control_mode,select.thermostat_hc1_controlmode,6,1,1,25,1 -"FR10",thermostat,111,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 +"FR10",thermostat,111,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 "FW500",thermostat,116,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "FW500",thermostat,116,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "FW500",thermostat,116,datetime,date/time,string, ,true,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -5065,7 +5070,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "FW500",thermostat,116,hc1.roominflfactor,room influence factor,uint8 (>=0<=100),%,true,number.thermostat_hc1_room_influence_factor,number.thermostat_hc1_roominflfactor,6,1,10,14,1 "FW500",thermostat,116,hc1.heatingtype,heating type,enum [off\|heatingcurve\|radiator\|convector\|floor], ,true,select.thermostat_hc1_heating_type,select.thermostat_hc1_heatingtype,6,1,1,19,1 "FW500",thermostat,116,hc1.controlmode,control mode,enum [off\|unmixed\|unmixed IPM\|mixed IPM], ,true,select.thermostat_hc1_control_mode,select.thermostat_hc1_controlmode,6,1,1,25,1 -"FW500",thermostat,116,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 +"FW500",thermostat,116,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 "FR50",thermostat,147,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "FR50",thermostat,147,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "FR50",thermostat,147,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -5100,7 +5105,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "FR50",thermostat,147,hc1.roominflfactor,room influence factor,uint8 (>=0<=100),%,true,number.thermostat_hc1_room_influence_factor,number.thermostat_hc1_roominflfactor,6,1,10,14,1 "FR50",thermostat,147,hc1.heatingtype,heating type,enum [off\|heatingcurve\|radiator\|convector\|floor], ,true,select.thermostat_hc1_heating_type,select.thermostat_hc1_heatingtype,6,1,1,19,1 "FR50",thermostat,147,hc1.controlmode,control mode,enum [off\|unmixed\|unmixed IPM\|mixed IPM], ,true,select.thermostat_hc1_control_mode,select.thermostat_hc1_controlmode,6,1,1,25,1 -"FR50",thermostat,147,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 +"FR50",thermostat,147,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 "FR120",thermostat,191,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "FR120",thermostat,191,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "FR120",thermostat,191,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -5135,7 +5140,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "FR120",thermostat,191,hc1.roominflfactor,room influence factor,uint8 (>=0<=100),%,true,number.thermostat_hc1_room_influence_factor,number.thermostat_hc1_roominflfactor,6,1,10,14,1 "FR120",thermostat,191,hc1.heatingtype,heating type,enum [off\|heatingcurve\|radiator\|convector\|floor], ,true,select.thermostat_hc1_heating_type,select.thermostat_hc1_heatingtype,6,1,1,19,1 "FR120",thermostat,191,hc1.controlmode,control mode,enum [off\|unmixed\|unmixed IPM\|mixed IPM], ,true,select.thermostat_hc1_control_mode,select.thermostat_hc1_controlmode,6,1,1,25,1 -"FR120",thermostat,191,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 +"FR120",thermostat,191,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 "FW120",thermostat,192,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "FW120",thermostat,192,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "FW120",thermostat,192,datetime,date/time,string, ,true,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -5172,7 +5177,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "FW120",thermostat,192,hc1.roominflfactor,room influence factor,uint8 (>=0<=100),%,true,number.thermostat_hc1_room_influence_factor,number.thermostat_hc1_roominflfactor,6,1,10,14,1 "FW120",thermostat,192,hc1.heatingtype,heating type,enum [off\|heatingcurve\|radiator\|convector\|floor], ,true,select.thermostat_hc1_heating_type,select.thermostat_hc1_heatingtype,6,1,1,19,1 "FW120",thermostat,192,hc1.controlmode,control mode,enum [off\|unmixed\|unmixed IPM\|mixed IPM], ,true,select.thermostat_hc1_control_mode,select.thermostat_hc1_controlmode,6,1,1,25,1 -"FW120",thermostat,192,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 +"FW120",thermostat,192,dhw.charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,6,1 "RT800, RC220",thermostat,3,errorcode,error code,string, ,false,sensor.thermostat_error_code,sensor.thermostat_errorcode,6,0,1,0,8 "RT800, RC220",thermostat,3,lastcode,last error code,string, ,false,sensor.thermostat_last_error_code,sensor.thermostat_lastcode,6,0,1,8,25 "RT800, RC220",thermostat,3,datetime,date/time,string, ,false,sensor.thermostat_date/time,sensor.thermostat_datetime,6,0,1,33,13 @@ -5621,6 +5626,8 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "WM10",switch,71,flowtemphc,flow temperature (TC1),uint16 (>=0<=3199),C,false,sensor.switch_flow_temperature_(TC1),sensor.switch_flowtemphc,11,0,1/10,1,1 "WM10",switch,71,status,status,int8 (>=-126<=126), ,false,sensor.switch_status,sensor.switch_status,11,0,1,2,1 "Rego 3000",controller,240,datetime,date/time,string, ,false,sensor.controller_date/time,sensor.controller_datetime,12,0,1,0,13 +"MX400",connect,17,datetime,date/time,string, ,false,sensor.connect_date/time,sensor.connect_datetime,13,0,1,0,13 +"MX400",connect,17,outdoortemp,outside temperature,int16 (>=-3199<=3199),C,false,sensor.connect_outside_temperature,sensor.connect_outdoortemp,13,0,1/10,13,1 "EM10",alert,74,setflowtemp,set flow temperature,uint8 (>=0<=254),C,false,sensor.alert_set_flow_temperature,sensor.alert_setflowtemp,14,0,1,0,1 "EM10",alert,74,setburnpow,burner set power,uint8 (>=0<=100),%,false,sensor.alert_burner_set_power,sensor.alert_setburnpow,14,0,1,1,1 "EM10, EM100",extension,243,flowtempvf,flow temperature in header (T0/Vf),int16 (>=-3199<=3199),C,false,sensor.extension_flow_temperature_in_header_(T0/Vf),sensor.extension_flowtempvf,15,0,1/10,0,1 diff --git a/interface/eslint.config.js b/interface/eslint.config.js index 4ccfc79ac..4eca2f5e5 100644 --- a/interface/eslint.config.js +++ b/interface/eslint.config.js @@ -10,8 +10,7 @@ export default tseslint.config( { languageOptions: { parserOptions: { - project: true, - tsconfigRootDir: import.meta.dirname + project: true } } }, diff --git a/interface/package.json b/interface/package.json index 953acc1b2..b276e3601 100644 --- a/interface/package.json +++ b/interface/package.json @@ -1,6 +1,6 @@ { "name": "EMS-ESP", - "version": "3.7.2", + "version": "3.7.3", "description": "EMS-ESP WebUI", "homepage": "https://emsesp.org", "author": "proddy, emsesp.org", @@ -63,5 +63,5 @@ "vite-plugin-imagemin": "^0.6.1", "vite-tsconfig-paths": "^5.1.4" }, - "packageManager": "pnpm@10.19.0" + "packageManager": "pnpm@10.19.0+sha512.c9fc7236e92adf5c8af42fd5bf1612df99c2ceb62f27047032f4720b33f8eacdde311865e91c411f2774f618d82f320808ecb51718bfa82c060c4ba7c76a32b8" } diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index 86ee614ca..3c8c7d330 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -1324,8 +1324,8 @@ packages: duplexer3@0.1.5: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - electron-to-chromium@1.5.238: - resolution: {integrity: sha512-khBdc+w/Gv+cS8e/Pbnaw/FXcBUeKrRVik9IxfXtgREOWyJhR4tj43n3amkVogJ/yeQUqzkrZcFhtIxIdqmmcQ==} + electron-to-chromium@1.5.239: + resolution: {integrity: sha512-1y5w0Zsq39MSPmEjHjbizvhYoTaulVtivpxkp5q5kaPmQtsK6/2nvAzGRxNMS9DoYySp9PkW0MAQDwU1m764mg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3995,7 +3995,7 @@ snapshots: dependencies: baseline-browser-mapping: 2.8.19 caniuse-lite: 1.0.30001751 - electron-to-chromium: 1.5.238 + electron-to-chromium: 1.5.239 node-releases: 2.0.26 update-browserslist-db: 1.1.4(browserslist@4.27.0) @@ -4340,7 +4340,7 @@ snapshots: duplexer3@0.1.5: {} - electron-to-chromium@1.5.238: {} + electron-to-chromium@1.5.239: {} emoji-regex@8.0.0: {} diff --git a/interface/progmem-generator.js b/interface/progmem-generator.js index 52ec26625..52c304b1d 100644 --- a/interface/progmem-generator.js +++ b/interface/progmem-generator.js @@ -4,6 +4,7 @@ import { existsSync, readFileSync, readdirSync, + statSync, unlinkSync } from 'fs'; import mime from 'mime-types'; @@ -15,67 +16,79 @@ const INDENT = ' '; const outputPath = '../src/ESP32React/WWWData.h'; const sourcePath = './dist'; const bytesPerLine = 20; -var totalSize = 0; +let totalSize = 0; +let bundleStats = { + js: { count: 0, uncompressed: 0, compressed: 0 }, + css: { count: 0, uncompressed: 0, compressed: 0 }, + html: { count: 0, uncompressed: 0, compressed: 0 }, + svg: { count: 0, uncompressed: 0, compressed: 0 }, + other: { count: 0, uncompressed: 0, compressed: 0 } +}; -const generateWWWClass = () => - `typedef std::function RouteRegistrationHandler; -// Total size is ${totalSize} bytes +const generateWWWClass = + () => `typedef std::function RouteRegistrationHandler; +// Bundle Statistics: +// - Total compressed size: ${(totalSize / 1000).toFixed(1)} KB +// - Total uncompressed size: ${(Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) / 1000).toFixed(1)} KB +// - Compression ratio: ${(((Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) - totalSize) / Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0)) * 100).toFixed(1)}% +// - Generated on: ${new Date().toISOString()} class WWWData { -${indent}public: -${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) { -${fileInfo.map((file) => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size}, "${file.hash}");`).join('\n')} -${indent.repeat(2)}} +${INDENT}public: +${INDENT.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) { +${fileInfo.map((f) => `${INDENT.repeat(3)}handler("${f.uri}", "${f.mimeType}", ${f.variable}, ${f.size}, "${f.hash}");`).join('\n')} +${INDENT.repeat(2)}} }; `; -function getFilesSync(dir, files = []) { +const getFilesSync = (dir, files = []) => { readdirSync(dir, { withFileTypes: true }).forEach((entry) => { const entryPath = resolve(dir, entry.name); - if (entry.isDirectory()) { - getFilesSync(entryPath, files); - } else { - files.push(entryPath); - } + entry.isDirectory() ? getFilesSync(entryPath, files) : files.push(entryPath); }); return files; -} +}; -function cleanAndOpen(path) { - if (existsSync(path)) { - unlinkSync(path); - } +const cleanAndOpen = (path) => { + existsSync(path) && unlinkSync(path); return createWriteStream(path, { flags: 'w+' }); -} +}; + +const getFileType = (filePath) => { + const ext = filePath.split('.').pop().toLowerCase(); + if (ext === 'js') return 'js'; + if (ext === 'css') return 'css'; + if (ext === 'html') return 'html'; + if (ext === 'svg') return 'svg'; + return 'other'; +}; const writeFile = (relativeFilePath, buffer) => { - const variable = 'ESP_REACT_DATA_' + fileInfo.length; + const variable = `ESP_REACT_DATA_${fileInfo.length}`; const mimeType = mime.lookup(relativeFilePath); - var size = 0; - writeStream.write('const uint8_t ' + variable + '[] = {'); - // const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 }); - const zipBuffer = zlib.gzipSync(buffer, { level: 9 }); + const fileType = getFileType(relativeFilePath); + let size = 0; + writeStream.write(`const uint8_t ${variable}[] = {`); - // create sha - const hashSum = crypto.createHash('sha256'); - hashSum.update(zipBuffer); - const hash = hashSum.digest('hex'); + const zipBuffer = zlib.gzipSync(buffer, { level: 9 }); + const hash = crypto.createHash('sha256').update(zipBuffer).digest('hex'); zipBuffer.forEach((b) => { if (!(size % bytesPerLine)) { - writeStream.write('\n'); - writeStream.write(indent); + writeStream.write('\n' + INDENT); } - writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).slice(-2) + ','); + writeStream.write('0x' + b.toString(16).toUpperCase().padStart(2, '0') + ','); size++; }); - if (size % bytesPerLine) { - writeStream.write('\n'); - } - + size % bytesPerLine && writeStream.write('\n'); writeStream.write('};\n\n'); + // Update bundle statistics + bundleStats[fileType].count++; + bundleStats[fileType].uncompressed += buffer.length; + bundleStats[fileType].compressed += zipBuffer.length; + fileInfo.push({ uri: '/' + relativeFilePath.replace(sep, '/'), mimeType, @@ -84,32 +97,52 @@ const writeFile = (relativeFilePath, buffer) => { hash }); - // console.log(relativeFilePath + ' (size ' + size + ' bytes)'); totalSize += size; }; -// start -console.log('Generating ' + outputPath + ' from ' + sourcePath); -const includes = ARDUINO_INCLUDES; -const indent = INDENT; +console.log(`Generating ${outputPath} from ${sourcePath}`); const fileInfo = []; const writeStream = cleanAndOpen(resolve(outputPath)); -// includes -writeStream.write(includes); +writeStream.write(ARDUINO_INCLUDES); -// process static files const buildPath = resolve(sourcePath); for (const filePath of getFilesSync(buildPath)) { - const readStream = readFileSync(filePath); - const relativeFilePath = relative(buildPath, filePath); - writeFile(relativeFilePath, readStream); + writeFile(relative(buildPath, filePath), readFileSync(filePath)); } -// add class writeStream.write(generateWWWClass()); - -// end writeStream.end(); -console.log('Total size: ' + totalSize / 1000 + ' KB'); +// Calculate and display bundle statistics +const totalUncompressed = Object.values(bundleStats).reduce( + (sum, stat) => sum + stat.uncompressed, + 0 +); +const totalCompressed = Object.values(bundleStats).reduce( + (sum, stat) => sum + stat.compressed, + 0 +); +const compressionRatio = ( + ((totalUncompressed - totalCompressed) / totalUncompressed) * + 100 +).toFixed(1); + +console.log('\n📊 Bundle Size Analysis:'); +console.log('='.repeat(50)); +console.log(`Total compressed size: ${(totalSize / 1000).toFixed(1)} KB`); +console.log(`Total uncompressed size: ${(totalUncompressed / 1000).toFixed(1)} KB`); +console.log(`Compression ratio: ${compressionRatio}%`); +console.log('\n📁 File Type Breakdown:'); +Object.entries(bundleStats).forEach(([type, stats]) => { + if (stats.count > 0) { + const ratio = ( + ((stats.uncompressed - stats.compressed) / stats.uncompressed) * + 100 + ).toFixed(1); + console.log( + `${type.toUpperCase().padEnd(4)}: ${stats.count} files, ${(stats.uncompressed / 1000).toFixed(1)} KB → ${(stats.compressed / 1000).toFixed(1)} KB (${ratio}% compression)` + ); + } +}); +console.log('='.repeat(50)); diff --git a/interface/src/AuthenticatedRouting.tsx b/interface/src/AuthenticatedRouting.tsx index 9777ed50a..df7e9cd5c 100644 --- a/interface/src/AuthenticatedRouting.tsx +++ b/interface/src/AuthenticatedRouting.tsx @@ -37,10 +37,9 @@ const AuthenticatedRouting = () => { } /> } /> } /> - } /> } /> - } /> + } /> } /> } /> } /> @@ -68,6 +67,8 @@ const AuthenticatedRouting = () => { } /> )} + + } /> ); diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index c547be43d..5d15d4fa5 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -98,7 +98,7 @@ const SignIn = () => { { }} /> alovaInstance.Get(`/rest/deviceEntities`, { params: { id }, responseType: 'arraybuffer', + // @ts-expect-error - exactOptionalPropertyTypes compatibility issue transform(data) { return (data as DeviceEntity[]).map((de: DeviceEntity) => ({ ...de, @@ -92,6 +93,7 @@ export const writeDeviceName = (data: { id: number; name: string }) => // SettingsScheduler export const readSchedule = () => alovaInstance.Get('/rest/schedule', { + // @ts-expect-error - exactOptionalPropertyTypes compatibility issue transform(data) { return (data as Schedule).schedule.map((si: ScheduleItem) => ({ ...si, @@ -129,6 +131,7 @@ export const writeModules = (data: { // CustomEntities export const readCustomEntities = () => alovaInstance.Get('/rest/customEntities', { + // @ts-expect-error - exactOptionalPropertyTypes compatibility issue transform(data) { return (data as Entities).entities.map((ei: EntityItem) => ({ ...ei, diff --git a/interface/src/api/system.ts b/interface/src/api/system.ts index 99162e20f..492661212 100644 --- a/interface/src/api/system.ts +++ b/interface/src/api/system.ts @@ -30,7 +30,7 @@ export const getDevVersion = () => cacheFor: 60 * 10 * 1000, transform(response: { data: { name: string; published_at: string } }) { return { - name: response.data.name.split(/\s+/).splice(-1)[0].substring(1), + name: response.data.name.split(/\s+/).splice(-1)[0]?.substring(1) || '', published_at: response.data.published_at }; } diff --git a/interface/src/api/unpack.ts b/interface/src/api/unpack.ts index 0f702c3a3..0b40f7aa0 100644 --- a/interface/src/api/unpack.ts +++ b/interface/src/api/unpack.ts @@ -1,40 +1,37 @@ -let decoder; +// @ts-nocheck - Optimized MessagePack unpacking library for EMS-ESP32 +let decoder, + src, + srcEnd, + position = 0, + strings = [], + stringPosition = 0, + currentUnpackr = {}, + currentStructures, + srcString, + srcStringStart = 0, + srcStringEnd = 0, + bundledStrings, + referenceMap, + dataView; +const EMPTY_ARRAY = [], + currentExtensions = []; +const defaultOptions = { useRecords: false, mapsAsObjects: true }; try { decoder = new TextDecoder(); } catch (error) {} -let src; -let srcEnd; -let position = 0; -const EMPTY_ARRAY = []; -let strings = EMPTY_ARRAY; -let stringPosition = 0; -let currentUnpackr = {}; -let currentStructures; -let srcString; -let srcStringStart = 0; -let srcStringEnd = 0; -let bundledStrings; -let referenceMap; -const currentExtensions = []; -let dataView; -const defaultOptions = { - useRecords: false, - mapsAsObjects: true -}; -export class C1Type {} -export const C1 = new C1Type(); +class C1Type {} +const C1 = new C1Type(); C1.name = 'MessagePack 0xC1'; -let sequentialMode = false; -let inlineObjectReadThreshold = 2; -let readStruct, onLoadedStructures, onSaveState; -// no-eval build +let sequentialMode = false, + inlineObjectReadThreshold = 2, + readStruct, + onLoadedStructures, + onSaveState; try { new Function(''); } catch (error) { - // if eval variants are not supported, do not create inline object readers ever inlineObjectReadThreshold = Infinity; } - export class Unpackr { constructor(options) { if (options) { @@ -50,19 +47,15 @@ export class Unpackr { if (options.structures) options.structures.sharedLength = options.structures.length; else if (options.getStructures) { - (options.structures = []).uninitialized = true; // this is what we use to denote an uninitialized structures + (options.structures = []).uninitialized = true; options.structures.sharedLength = 0; } - if (options.int64AsNumber) { - options.int64AsType = 'number'; - } + if (options.int64AsNumber) options.int64AsType = 'number'; } Object.assign(this, options); } - unpack(source, options?: any) { if (src) { - // re-entrant execution, save the state and restore it after we do this unpack return saveState(() => { clearSource(); return this @@ -86,9 +79,6 @@ export class Unpackr { strings = EMPTY_ARRAY; bundledStrings = null; src = source; - // this provides cached access to the data view for a buffer if it is getting reused, which is a recommend - // technique for getting data from a database where it can be copied into an existing buffer instead of creating - // new ones try { dataView = source.dataView || @@ -191,10 +181,10 @@ export class Unpackr { return this.unpack(source, end); } } -export function getPosition() { +function getPosition() { return position; } -export function checkedRead(options: any) { +function checkedRead(options: any) { try { if (!currentUnpackr.trusted && !sequentialMode) { const sharedLength = currentStructures.sharedLength || 0; @@ -264,7 +254,7 @@ function restoreStructures() { currentStructures.restoreStructures = null; } -export function read() { +function read() { let token = src[position++]; if (token < 0xa0) { if (token < 0x80) { @@ -589,7 +579,7 @@ const createSecondByteReader = (firstId, read0) => return structure.read(); }; -export function loadStructures() { +function loadStructures() { const loadedStructures = saveState(() => { // save the state in case getStructures modifies our buffer src = null; @@ -605,9 +595,8 @@ var readFixedString = readStringJS; var readString8 = readStringJS; var readString16 = readStringJS; var readString32 = readStringJS; -export let isNativeAccelerationEnabled = false; - -export function setExtractor(extractStrings) { +let isNativeAccelerationEnabled = false; +function setExtractor(extractStrings) { isNativeAccelerationEnabled = true; readFixedString = readString(1); readString8 = readString(2); @@ -701,7 +690,7 @@ function readStringJS(length) { return result; } -export function readString(source, start, length) { +function readString(source, start, length) { const existingSrc = src; src = source; position = start; @@ -1065,7 +1054,7 @@ currentExtensions[0x70] = (data) => { currentExtensions[0x73] = () => new Set(read()); -export const typedArrays = [ +const typedArrays = [ 'Int8', 'Uint8', 'Uint8Clamped', @@ -1177,44 +1166,20 @@ function saveState(callback) { dataView = new DataView(src.buffer, src.byteOffset, src.byteLength); return value; } -export function clearSource() { +function clearSource() { src = null; referenceMap = null; currentStructures = null; } -export function addExtension(extension) { +function addExtension(extension) { if (extension.unpack) currentExtensions[extension.type] = extension.unpack; else currentExtensions[extension.type] = extension; } -export const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding +const mult10 = new Array(147); for (let i = 0; i < 256; i++) { mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103)); } -export const Decoder = Unpackr; -var defaultUnpackr = new Unpackr({ useRecords: false }); +const defaultUnpackr = new Unpackr({ useRecords: false }); export const unpack = defaultUnpackr.unpack; -export const unpackMultiple = defaultUnpackr.unpackMultiple; -export const decode = defaultUnpackr.unpack; -export const FLOAT32_OPTIONS = { - NEVER: 0, - ALWAYS: 1, - DECIMAL_ROUND: 3, - DECIMAL_FIT: 4 -}; -const f32Array = new Float32Array(1); -const u8Array = new Uint8Array(f32Array.buffer, 0, 4); -export function roundFloat32(float32Number) { - f32Array[0] = float32Number; - const multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)]; - return ( - ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / - multiplier - ); -} -export function setReadStruct(updatedReadStruct, loadedStructs, saveState) { - readStruct = updatedReadStruct; - onLoadedStructures = loadedStructs; - onSaveState = saveState; -} diff --git a/interface/src/app/main/CustomEntities.tsx b/interface/src/app/main/CustomEntities.tsx index fac37a376..26e66041f 100644 --- a/interface/src/app/main/CustomEntities.tsx +++ b/interface/src/app/main/CustomEntities.tsx @@ -137,8 +137,8 @@ const CustomEntities = () => { const saveEntities = async () => { await writeEntities({ entities: entities - .filter((ei) => !ei.deleted) - .map((condensed_ei) => ({ + .filter((ei: EntityItem) => !ei.deleted) + .map((condensed_ei: EntityItem) => ({ id: condensed_ei.id, ram: condensed_ei.ram, name: condensed_ei.name, @@ -231,6 +231,7 @@ const CustomEntities = () => { value_type: 0, writeable: false, deleted: false, + hide: false, value: '' }); setDialogOpen(true); @@ -251,15 +252,17 @@ const CustomEntities = () => { const renderEntity = () => { if (!entities) { - return ; + return ( + + ); } return ( !ei.deleted) - .sort((a, b) => a.name.localeCompare(b.name)) + .filter((ei: EntityItem) => !ei.deleted) + .sort((a: EntityItem, b: EntityItem) => a.name.localeCompare(b.name)) }} theme={entity_theme} layout={{ custom: true }} diff --git a/interface/src/app/main/CustomEntitiesDialog.tsx b/interface/src/app/main/CustomEntitiesDialog.tsx index 05d1ab816..bf10b55fc 100644 --- a/interface/src/app/main/CustomEntitiesDialog.tsx +++ b/interface/src/app/main/CustomEntitiesDialog.tsx @@ -74,7 +74,10 @@ const CustomEntitiesDialog = ({ } }, [open, selectedItem]); - const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => { + const handleClose = ( + _event: React.SyntheticEvent, + reason: 'backdropClick' | 'escapeKeyDown' + ) => { if (reason !== 'backdropClick') { onClose(); } @@ -123,7 +126,7 @@ const CustomEntitiesDialog = ({ { const setOriginalSettings = (data: DeviceEntity[]) => { setDeviceEntities( - data.map((de) => ({ - ...de, - o_m: de.m, - o_cn: de.cn, - o_mi: de.mi, - o_ma: de.ma - })) + data.map((de) => { + const result: DeviceEntity = { + ...de, + o_m: de.m + }; + if (de.cn !== undefined) { + result.o_cn = de.cn; + } + if (de.mi !== undefined) { + result.o_mi = de.mi; + } + if (de.ma !== undefined) { + result.o_ma = de.ma; + } + return result; + }) ); }; @@ -244,8 +253,11 @@ const Customizations = () => { setSelectedDevice(-1); setSelectedDeviceTypeNameURL(''); } else { - setSelectedDeviceTypeNameURL(devices.devices[index].url || ''); - setSelectedDeviceName(devices.devices[index].n); + const device = devices.devices[index]; + if (device) { + setSelectedDeviceTypeNameURL(device.url || ''); + setSelectedDeviceName(device.n); + } setNumChanges(0); setRestartNeeded(false); } @@ -396,14 +408,20 @@ const Customizations = () => { await sendCustomizationEntities({ id: selectedDevice, entity_ids: masked_entities - }).catch((error: Error) => { - if (error.message === 'Reboot required') { - setRestartNeeded(true); - } else { - toast.error(error.message); - } - }); - setOriginalSettings(deviceEntities); + }) + .then(() => { + toast.success(LL.CUSTOMIZATIONS_SAVED()); + }) + .catch((error: Error) => { + if (error.message === 'Reboot required') { + setRestartNeeded(true); + } else { + toast.error(error.message); + } + }) + .finally(() => { + setOriginalSettings(deviceEntities); + }); } }; @@ -545,7 +563,7 @@ const Customizations = () => { size="small" color="secondary" value={getMaskString(selectedFilters)} - onChange={(event, mask: string[]) => { + onChange={(_, mask: string[]) => { setSelectedFilters(getMaskNumber(mask)); }} > diff --git a/interface/src/app/main/CustomizationsDialog.tsx b/interface/src/app/main/CustomizationsDialog.tsx index d738594b1..f303634a0 100644 --- a/interface/src/app/main/CustomizationsDialog.tsx +++ b/interface/src/app/main/CustomizationsDialog.tsx @@ -54,7 +54,10 @@ const CustomizationsDialog = ({ } }, [open, selectedItem]); - const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => { + const handleClose = ( + _event: React.SyntheticEvent, + reason: 'backdropClick' | 'escapeKeyDown' + ) => { if (reason !== 'backdropClick') { onClose(); } diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index 450727310..8198a86fd 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState } from 'react'; +import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { IconContext } from 'react-icons/lib'; import { Link } from 'react-router'; import { toast } from 'react-toastify'; @@ -76,35 +76,40 @@ const Dashboard = () => { } ); - const deviceValueDialogSave = async (devicevalue: DeviceValue) => { - if (!selectedDashboardItem) { - return; - } - const id = selectedDashboardItem.parentNode.id; // this is the parent ID - await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v }) - .then(() => { - toast.success(LL.WRITE_CMD_SENT()); - }) - .catch((error: Error) => { - toast.error(error.message); - }) - .finally(() => { - setDeviceValueDialogOpen(false); - setSelectedDashboardItem(undefined); - }); - }; + const deviceValueDialogSave = useCallback( + async (devicevalue: DeviceValue) => { + if (!selectedDashboardItem) { + return; + } + const id = selectedDashboardItem.id; // this is the parent ID + await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v }) + .then(() => { + toast.success(LL.WRITE_CMD_SENT()); + }) + .catch((error: Error) => { + toast.error(error.message); + }) + .finally(() => { + setDeviceValueDialogOpen(false); + setSelectedDashboardItem(undefined); + }); + }, + [selectedDashboardItem, sendDeviceValue, LL] + ); - const dashboard_theme = useTheme({ - Table: ` + const dashboard_theme = useMemo( + () => + useTheme({ + Table: ` --data-table-library_grid-template-columns: minmax(80px, auto) 120px 32px; `, - BaseRow: ` + BaseRow: ` font-size: 14px; .td { height: 28px; } `, - Row: ` + Row: ` cursor: pointer; background-color: #1e1e1e; &:nth-of-type(odd) .td { @@ -114,7 +119,7 @@ const Dashboard = () => { background-color: #177ac9; }, `, - BaseCell: ` + BaseCell: ` &:nth-of-type(2) { text-align: right; } @@ -122,12 +127,14 @@ const Dashboard = () => { text-align: right; } ` - }); + }), + [] + ); const tree = useTree( { nodes: data.nodes }, { - onChange: undefined // not used but needed + onChange: () => {} // not used but needed }, { treeIcon: { @@ -162,28 +169,31 @@ const Dashboard = () => { : tree.fns.onRemoveAll(); // collapse tree }, [parentNodes]); - const showType = (n?: string, t?: number) => { - // if we have a name show it - if (n) { - return n; - } - if (t) { - // otherwise pick translation based on type - switch (t) { - case DeviceType.CUSTOM: - return LL.CUSTOM_ENTITIES(0); - case DeviceType.ANALOGSENSOR: - return LL.ANALOG_SENSORS(); - case DeviceType.TEMPERATURESENSOR: - return LL.TEMP_SENSORS(); - case DeviceType.SCHEDULER: - return LL.SCHEDULER(); - default: - break; + const showType = useCallback( + (n?: string, t?: number) => { + // if we have a name show it + if (n) { + return n; } - } - return ''; - }; + if (t) { + // otherwise pick translation based on type + switch (t) { + case DeviceType.CUSTOM: + return LL.CUSTOM_ENTITIES(0); + case DeviceType.ANALOGSENSOR: + return LL.ANALOG_SENSORS(); + case DeviceType.TEMPERATURESENSOR: + return LL.TEMP_SENSORS(); + case DeviceType.SCHEDULER: + return LL.SCHEDULER(); + default: + break; + } + } + return ''; + }, + [LL] + ); const showName = (di: DashboardItem) => { if (di.id < 100) { @@ -201,20 +211,24 @@ const Dashboard = () => { if (di.dv) { return {di.dv.id.slice(2)}; } + return null; }; const hasMask = (id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask; - const editDashboardValue = (di: DashboardItem) => { - if (me.admin && di.dv?.c) { - setSelectedDashboardItem(di); - setDeviceValueDialogOpen(true); - } - }; + const editDashboardValue = useCallback( + (di: DashboardItem) => { + if (me.admin && di.dv?.c) { + setSelectedDashboardItem(di); + setDeviceValueDialogOpen(true); + } + }, + [me.admin] + ); const handleShowAll = ( - event: React.MouseEvent, + _event: React.MouseEvent, toggle: boolean | null ) => { if (toggle !== null) { @@ -225,7 +239,9 @@ const Dashboard = () => { const renderContent = () => { if (!data) { - return ; + return ( + + ); } const hasFavEntities = data.nodes.filter( diff --git a/interface/src/app/main/Devices.tsx b/interface/src/app/main/Devices.tsx index 5d6affdc0..69f30d297 100644 --- a/interface/src/app/main/Devices.tsx +++ b/interface/src/app/main/Devices.tsx @@ -329,13 +329,16 @@ const Devices = () => { const handleDownloadCsv = () => { const deviceIndex = coreData.devices.findIndex( - (d) => d.id === device_select.state.id + (d: Device) => d.id === device_select.state.id ); if (deviceIndex === -1) { return; } - const filename = - coreData.devices[deviceIndex].tn + '_' + coreData.devices[deviceIndex].n; + const selectedDevice = coreData.devices[deviceIndex]; + if (!selectedDevice) { + return; + } + const filename = selectedDevice.tn + '_' + selectedDevice.n; const columns = [ { @@ -350,7 +353,7 @@ const Devices = () => { { accessor: (dv: DeviceValue) => dv.u !== undefined && DeviceValueUOM_s[dv.u] - ? DeviceValueUOM_s[dv.u].replace(/[^a-zA-Z0-9]/g, '') + ? DeviceValueUOM_s[dv.u]?.replace(/[^a-zA-Z0-9]/g, '') : '', name: 'UoM' }, @@ -373,7 +376,9 @@ const Devices = () => { ]; const data = onlyFav - ? deviceData.nodes.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE)) + ? deviceData.nodes.filter((dv: DeviceValue) => + hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) + ) : deviceData.nodes; const csvData = data.reduce( @@ -433,10 +438,14 @@ const Devices = () => { const renderDeviceDetails = () => { if (showDeviceInfo) { const deviceIndex = coreData.devices.findIndex( - (d) => d.id === device_select.state.id + (d: Device) => d.id === device_select.state.id ); if (deviceIndex === -1) { - return; + return null; + } + const deviceDetails = coreData.devices[deviceIndex]; + if (!deviceDetails) { + return null; } return ( @@ -449,47 +458,35 @@ const Devices = () => { - + - + - {coreData.devices[deviceIndex].t !== DeviceType.CUSTOM && ( + {deviceDetails.t !== DeviceType.CUSTOM && ( <> - + @@ -508,6 +505,7 @@ const Devices = () => { ); } + return null; }; const renderCoreData = () => ( @@ -598,21 +596,26 @@ const Devices = () => { const shown_data = onlyFav ? deviceData.nodes.filter( - (dv) => + (dv: DeviceValue) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) && dv.id.slice(2).toLowerCase().includes(search.toLowerCase()) ) - : deviceData.nodes.filter((dv) => + : deviceData.nodes.filter((dv: DeviceValue) => dv.id.slice(2).toLowerCase().includes(search.toLowerCase()) ); const deviceIndex = coreData.devices.findIndex( - (d) => d.id === device_select.state.id + (d: Device) => d.id === device_select.state.id ); if (deviceIndex === -1) { return; } + const deviceInfo = coreData.devices[deviceIndex]; + if (!deviceInfo) { + return; + } + const [, height] = size; return ( { bottom: 0, top: 64, zIndex: 'modal', - maxHeight: () => size[1] - 126, + maxHeight: () => (height || 0) - 126, border: '1px solid #177ac9' }} > - {coreData.devices[deviceIndex].n} ( - {coreData.devices[deviceIndex].tn}) + {deviceInfo.n} ( + {deviceInfo.tn}) @@ -701,7 +704,7 @@ const Devices = () => { ' ' + shown_data.length + '/' + - coreData.devices[deviceIndex].e + + deviceInfo.e + ' ' + LL.ENTITIES(shown_data.length)} diff --git a/interface/src/app/main/DevicesDialog.tsx b/interface/src/app/main/DevicesDialog.tsx index 35054b31e..a7aa1f184 100644 --- a/interface/src/app/main/DevicesDialog.tsx +++ b/interface/src/app/main/DevicesDialog.tsx @@ -120,7 +120,7 @@ const DevicesDialog = ({ {editItem.l ? ( ) : editItem.s || editItem.u !== DeviceValueUOM.NONE ? ( ) : ( { size="small" color="secondary" value={getMaskString(de.m)} - onChange={(event, mask: string[]) => { + onChange={(_event, mask: string[]) => { de.m = getMaskNumber(mask); if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) { de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE; diff --git a/interface/src/app/main/Modules.tsx b/interface/src/app/main/Modules.tsx index c5fa4ad88..d9ee062c8 100644 --- a/interface/src/app/main/Modules.tsx +++ b/interface/src/app/main/Modules.tsx @@ -133,13 +133,15 @@ const Modules = () => { }; const saveModules = async () => { - await updateModules({ - modules: modules.map((condensed_mi) => ({ - key: condensed_mi.key, - enabled: condensed_mi.enabled, - license: condensed_mi.license - })) - }) + await Promise.all( + modules.map((condensed_mi: ModuleItem) => + updateModules({ + key: condensed_mi.key, + enabled: condensed_mi.enabled, + license: condensed_mi.license + }) + ) + ) .then(() => { toast.success(LL.MODULES_UPDATED()); }) @@ -154,7 +156,9 @@ const Modules = () => { const renderContent = () => { if (!modules) { - return ; + return ( + + ); } if (modules.length === 0) { diff --git a/interface/src/app/main/Scheduler.tsx b/interface/src/app/main/Scheduler.tsx index 0029a8eaa..eeb47b3e0 100644 --- a/interface/src/app/main/Scheduler.tsx +++ b/interface/src/app/main/Scheduler.tsx @@ -135,8 +135,8 @@ const Scheduler = () => { const saveSchedule = async () => { await updateSchedule({ schedule: schedule - .filter((si) => !si.deleted) - .map((condensed_si) => ({ + .filter((si: ScheduleItem) => !si.deleted) + .map((condensed_si: ScheduleItem) => ({ id: condensed_si.id, active: condensed_si.active, flags: condensed_si.flags, @@ -212,7 +212,9 @@ const Scheduler = () => { const renderSchedule = () => { if (!schedule) { - return ; + return ( + + ); } const dayBox = (si: ScheduleItem, flag: number) => ( @@ -251,8 +253,8 @@ const Scheduler = () => {
!si.deleted) - .sort((a, b) => a.flags - b.flags) + .filter((si: ScheduleItem) => !si.deleted) + .sort((a: ScheduleItem, b: ScheduleItem) => a.flags - b.flags) }} theme={schedule_theme} layout={{ custom: true }} diff --git a/interface/src/app/main/SchedulerDialog.tsx b/interface/src/app/main/SchedulerDialog.tsx index 89838c246..812a99a81 100644 --- a/interface/src/app/main/SchedulerDialog.tsx +++ b/interface/src/app/main/SchedulerDialog.tsx @@ -144,7 +144,10 @@ const SchedulerDialog = ({ ); - const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => { + const handleClose = ( + _event: React.SyntheticEvent, + reason: 'backdropClick' | 'escapeKeyDown' + ) => { if (reason !== 'backdropClick') { onClose(); } @@ -325,7 +328,7 @@ const SchedulerDialog = ({ )} { + const handleClose = ( + _event: React.SyntheticEvent, + reason: 'backdropClick' | 'escapeKeyDown' + ) => { if (reason !== 'backdropClick') { onClose(); } @@ -88,7 +91,7 @@ const SensorsAnalogDialog = ({ { + const handleClose = ( + _event: React.SyntheticEvent, + reason: 'backdropClick' | 'escapeKeyDown' + ) => { if (reason !== 'backdropClick') { onClose(); } @@ -82,7 +85,7 @@ const SensorsTemperatureDialog = ({ void ) { @@ -36,7 +36,7 @@ export const GPIO_VALIDATOR = { export const GPIO_VALIDATORR = { validator( - rule: InternalRuleItem, + _rule: InternalRuleItem, value: number, callback: (error?: string) => void ) { @@ -60,7 +60,7 @@ export const GPIO_VALIDATORR = { export const GPIO_VALIDATORC3 = { validator( - rule: InternalRuleItem, + _rule: InternalRuleItem, value: number, callback: (error?: string) => void ) { @@ -74,7 +74,7 @@ export const GPIO_VALIDATORC3 = { export const GPIO_VALIDATORS2 = { validator( - rule: InternalRuleItem, + _rule: InternalRuleItem, value: number, callback: (error?: string) => void ) { @@ -94,7 +94,7 @@ export const GPIO_VALIDATORS2 = { export const GPIO_VALIDATORS3 = { validator( - rule: InternalRuleItem, + _rule: InternalRuleItem, value: number, callback: (error?: string) => void ) { @@ -279,7 +279,7 @@ export const createSettingsValidator = (settings: Settings) => export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) => ({ validator( - rule: InternalRuleItem, + _rule: InternalRuleItem, name: string, callback: (error?: string) => void ) { @@ -324,7 +324,7 @@ export const uniqueCustomNameValidator = ( o_name?: string ) => ({ validator( - rule: InternalRuleItem, + _rule: InternalRuleItem, name: string, callback: (error?: string) => void ) { @@ -353,7 +353,7 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte device_id: [ { validator( - rule: InternalRuleItem, + _rule: InternalRuleItem, value: string, callback: (error?: string) => void ) { @@ -367,7 +367,7 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte type_id: [ { validator( - rule: InternalRuleItem, + _rule: InternalRuleItem, value: string, callback: (error?: string) => void ) { @@ -389,7 +389,7 @@ export const uniqueTemperatureNameValidator = ( sensors: TemperatureSensor[], o_name?: string ) => ({ - validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) { + validator(_rule: InternalRuleItem, n: string, callback: (error?: string) => void) { if ( (o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) && n !== '' && @@ -419,7 +419,7 @@ export const temperatureSensorItemValidation = ( export const isGPIOUniqueValidator = (sensors: AnalogSensor[]) => ({ validator( - rule: InternalRuleItem, + _rule: InternalRuleItem, gpio: number, callback: (error?: string) => void ) { @@ -435,7 +435,7 @@ export const uniqueAnalogNameValidator = ( sensors: AnalogSensor[], o_name?: string ) => ({ - validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) { + validator(_rule: InternalRuleItem, n: string, callback: (error?: string) => void) { if ( (o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) && n !== '' && @@ -482,7 +482,7 @@ export const deviceValueItemValidation = (dv: DeviceValue) => { required: true, message: 'Value is required' }, { validator( - rule: InternalRuleItem, + _rule: InternalRuleItem, value: unknown, callback: (error?: string) => void ) { diff --git a/interface/src/app/settings/APSettings.tsx b/interface/src/app/settings/APSettings.tsx index 7c9aee614..aa294bd15 100644 --- a/interface/src/app/settings/APSettings.tsx +++ b/interface/src/app/settings/APSettings.tsx @@ -54,12 +54,12 @@ const APSettings = () => { origData, dirtyFlags, setDirtyFlags, - updateDataValue + updateDataValue as (value: unknown) => void ); const content = () => { if (!data) { - return ; + return ; } const validateAndSubmit = async () => { @@ -80,7 +80,7 @@ const APSettings = () => { return ( <> { {isAPEnabled(data) && ( <> { margin="normal" /> { margin="normal" /> { label={LL.AP_HIDE_SSID()} /> { ))} { margin="normal" /> { margin="normal" /> { origData, dirtyFlags, setDirtyFlags, - updateDataValue + updateDataValue as (value: unknown) => void ); const [fieldErrors, setFieldErrors] = useState(); @@ -135,7 +135,7 @@ const ApplicationSettings = () => { const content = () => { if (!data || !hardwareData) { - return ; + return ; } const validateAndSubmit = async () => { @@ -219,7 +219,7 @@ const ApplicationSettings = () => { { { { { { { { { { { { {data.remote_timeout_en && ( { {data.shower_timer && ( { <> { { const content = () => { if (!data) { - return ; + return ; } return ( diff --git a/interface/src/app/settings/MqttSettings.tsx b/interface/src/app/settings/MqttSettings.tsx index 10921a96c..917e9fe90 100644 --- a/interface/src/app/settings/MqttSettings.tsx +++ b/interface/src/app/settings/MqttSettings.tsx @@ -56,7 +56,7 @@ const MqttSettings = () => { origData, dirtyFlags, setDirtyFlags, - updateDataValue + updateDataValue as (value: unknown) => void ); const SecondsInputProps = { @@ -65,7 +65,7 @@ const MqttSettings = () => { const content = () => { if (!data) { - return ; + return ; } const validateAndSubmit = async () => { @@ -93,7 +93,7 @@ const MqttSettings = () => { { { { { { origData, dirtyFlags, setDirtyFlags, - updateDataValue + updateDataValue as (value: unknown) => void ); const [fieldErrors, setFieldErrors] = useState(); @@ -155,7 +155,7 @@ const NTPSettings = () => { const content = () => { if (!data) { - return ; + return ; } const validateAndSubmit = async () => { @@ -190,7 +190,7 @@ const NTPSettings = () => { label={LL.ENABLE_NTP()} /> { margin="normal" /> { ], useLocation() ); - const routerTab = matchedRoutes?.[0].route.path || false; + const routerTab = matchedRoutes?.[0]?.route.path || false; const navigate = useNavigate(); @@ -56,7 +56,7 @@ const Network = () => { return ( { origData, dirtyFlags, setDirtyFlags, - updateDataValue + updateDataValue as (value: unknown) => void ); const [fieldErrors, setFieldErrors] = useState(); @@ -113,7 +113,7 @@ const NetworkSettings = () => { const content = () => { if (!data) { - return ; + return ; } const validateAndSubmit = async () => { @@ -172,7 +172,7 @@ const NetworkSettings = () => { ) : ( { /> )} { /> {(!selectedNetwork || !isNetworkOpen(selectedNetwork)) && ( { {LL.GENERAL_OPTIONS()} { {data.static_ip_config && ( <> { margin="normal" /> { margin="normal" /> { margin="normal" /> { margin="normal" /> { const renderNetworkScanner = () => { if (!networkList) { - return ( - - ); + return ; } return ; }; diff --git a/interface/src/app/settings/security/ManageUsers.tsx b/interface/src/app/settings/security/ManageUsers.tsx index f4724ece9..991fa790d 100644 --- a/interface/src/app/settings/security/ManageUsers.tsx +++ b/interface/src/app/settings/security/ManageUsers.tsx @@ -97,7 +97,7 @@ const ManageUsers = () => { const content = () => { if (!data) { - return ; + return ; } const noAdminConfigured = () => !data.users.find((u) => u.admin); @@ -260,15 +260,20 @@ const ManageUsers = () => { - - + {user && ( + + )} ); }; diff --git a/interface/src/app/settings/security/Security.tsx b/interface/src/app/settings/security/Security.tsx index cbbc5716b..7d5d56827 100644 --- a/interface/src/app/settings/security/Security.tsx +++ b/interface/src/app/settings/security/Security.tsx @@ -19,7 +19,7 @@ const Security = () => { ], useLocation() ); - const routerTab = matchedRoutes?.[0].route.path || false; + const routerTab = matchedRoutes?.[0]?.route.path || false; return ( <> diff --git a/interface/src/app/settings/security/SecuritySettings.tsx b/interface/src/app/settings/security/SecuritySettings.tsx index b27128dfb..04f332335 100644 --- a/interface/src/app/settings/security/SecuritySettings.tsx +++ b/interface/src/app/settings/security/SecuritySettings.tsx @@ -47,12 +47,12 @@ const SecuritySettings = () => { origData, dirtyFlags, setDirtyFlags, - updateDataValue + updateDataValue as (value: unknown) => void ); const content = () => { if (!data) { - return ; + return ; } const validateAndSubmit = async () => { @@ -69,7 +69,7 @@ const SecuritySettings = () => { return ( <> = ({ = ({ margin="normal" /> { const content = () => { if (!data) { - return ; + return ; } return ( diff --git a/interface/src/app/status/Activity.tsx b/interface/src/app/status/Activity.tsx index 7c6b826e7..5be73a81d 100644 --- a/interface/src/app/status/Activity.tsx +++ b/interface/src/app/status/Activity.tsx @@ -67,7 +67,8 @@ const SystemActivity = () => { }); const showName = (id: number) => { - const name: keyof Translation['STATUS_NAMES'] = id; + const name: keyof Translation['STATUS_NAMES'] = + id.toString() as keyof Translation['STATUS_NAMES']; return LL.STATUS_NAMES[name](); }; @@ -87,7 +88,7 @@ const SystemActivity = () => { const content = () => { if (!data) { - return ; + return ; } return ( diff --git a/interface/src/app/status/HardwareStatus.tsx b/interface/src/app/status/HardwareStatus.tsx index 681ac0a47..fdef8bcd2 100644 --- a/interface/src/app/status/HardwareStatus.tsx +++ b/interface/src/app/status/HardwareStatus.tsx @@ -41,7 +41,7 @@ const HardwareStatus = () => { const content = () => { if (!data) { - return ; + return ; } return ( diff --git a/interface/src/app/status/MqttStatus.tsx b/interface/src/app/status/MqttStatus.tsx index 911c3c5d6..210ac02d1 100644 --- a/interface/src/app/status/MqttStatus.tsx +++ b/interface/src/app/status/MqttStatus.tsx @@ -99,7 +99,7 @@ const MqttStatus = () => { const content = () => { if (!data) { - return ; + return ; } const renderConnectionStatus = () => ( diff --git a/interface/src/app/status/NTPStatus.tsx b/interface/src/app/status/NTPStatus.tsx index cf2e456d0..2153b61a5 100644 --- a/interface/src/app/status/NTPStatus.tsx +++ b/interface/src/app/status/NTPStatus.tsx @@ -68,7 +68,7 @@ const NTPStatus = () => { const content = () => { if (!data) { - return ; + return ; } return ( diff --git a/interface/src/app/status/NetworkStatus.tsx b/interface/src/app/status/NetworkStatus.tsx index 7185a947b..3a9ea09b2 100644 --- a/interface/src/app/status/NetworkStatus.tsx +++ b/interface/src/app/status/NetworkStatus.tsx @@ -120,7 +120,7 @@ const NetworkStatus = () => { const content = () => { if (!data) { - return ; + return ; } return ( diff --git a/interface/src/app/status/Status.tsx b/interface/src/app/status/Status.tsx index 1b0836901..286901424 100644 --- a/interface/src/app/status/Status.tsx +++ b/interface/src/app/status/Status.tsx @@ -248,7 +248,7 @@ const SystemStatus = () => { const content = () => { if (!data || !LL) { - return ; + return ; } return ( diff --git a/interface/src/app/status/SystemLog.tsx b/interface/src/app/status/SystemLog.tsx index 730881ff2..cafe73755 100644 --- a/interface/src/app/status/SystemLog.tsx +++ b/interface/src/app/status/SystemLog.tsx @@ -31,13 +31,14 @@ import type { LogEntry, LogSettings } from 'types'; import { LogLevel } from 'types'; import { updateValueDirty, useRest } from 'utils'; -const TextColors = { +const TextColors: Record = { [LogLevel.ERROR]: '#ff0000', // red [LogLevel.WARNING]: '#ff0000', // red [LogLevel.NOTICE]: '#ffffff', // white [LogLevel.INFO]: '#ffcc00', // yellow [LogLevel.DEBUG]: '#00ffff', // cyan - [LogLevel.TRACE]: '#00ffff' // cyan + [LogLevel.TRACE]: '#00ffff', // cyan + [LogLevel.ALL]: '#ffffff' // white }; const LogEntryLine = styled('span')( @@ -109,7 +110,7 @@ const SystemLog = () => { origData, dirtyFlags, setDirtyFlags, - updateDataValue + updateDataValue as (value: unknown) => void ); useSSE(fetchLogES, { @@ -190,7 +191,7 @@ const SystemLog = () => { const content = () => { if (!data) { - return ; + return ; } return ( diff --git a/interface/src/app/status/Version.tsx b/interface/src/app/status/Version.tsx index f3e3d1a20..0178cac27 100644 --- a/interface/src/app/status/Version.tsx +++ b/interface/src/app/status/Version.tsx @@ -110,7 +110,7 @@ const Version = () => { }, [latestVersion, latestDevVersion]); const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }); - const DIVISIONS = [ + const DIVISIONS: Array<{ amount: number; name: string }> = [ { amount: 60, name: 'seconds' }, { amount: 60, name: 'minutes' }, { amount: 24, name: 'hours' }, @@ -119,18 +119,21 @@ const Version = () => { { amount: 12, name: 'months' }, { amount: Number.POSITIVE_INFINITY, name: 'years' } ]; - function formatTimeAgo(date) { + function formatTimeAgo(date: Date) { let duration = (date.getTime() - new Date().getTime()) / 1000; for (let i = 0; i < DIVISIONS.length; i++) { const division = DIVISIONS[i]; - if (Math.abs(duration) < division.amount) { + if (division && Math.abs(duration) < division.amount) { return rtf.format( Math.round(duration), division.name as Intl.RelativeTimeFormatUnit ); } - duration /= division.amount; + if (division) { + duration /= division.amount; + } } + return rtf.format(0, 'seconds'); } const { send: sendAPI } = useRequest((data: APIcall) => API(data), { @@ -270,6 +273,14 @@ const Version = () => { {LL.LATEST_VERSION(usingDevVersion ? LL.DEVELOPMENT() : LL.STABLE())} + ); } @@ -293,7 +304,7 @@ const Version = () => { const content = () => { if (!data) { - return ; + return ; } const isDev = data.emsesp_version.includes('dev'); diff --git a/interface/src/components/ButtonRow.tsx b/interface/src/components/ButtonRow.tsx index 01c7aafb7..f8f9a0002 100644 --- a/interface/src/components/ButtonRow.tsx +++ b/interface/src/components/ButtonRow.tsx @@ -1,26 +1,24 @@ -import type { FC } from 'react'; +import { memo } from 'react'; import { Box } from '@mui/material'; import type { BoxProps } from '@mui/material'; -const ButtonRow: FC = ({ children, ...rest }) => ( +const ButtonRow = memo(({ children, ...rest }) => ( {children} -); +)); + +ButtonRow.displayName = 'ButtonRow'; export default ButtonRow; diff --git a/interface/src/components/ButtonTooltip.tsx b/interface/src/components/ButtonTooltip.tsx index 8498a6860..3ffad081d 100644 --- a/interface/src/components/ButtonTooltip.tsx +++ b/interface/src/components/ButtonTooltip.tsx @@ -1,7 +1,12 @@ import { Tooltip, type TooltipProps, styled, tooltipClasses } from '@mui/material'; export const ButtonTooltip = styled(({ className, ...props }: TooltipProps) => ( - + ))(({ theme }) => ({ [`& .${tooltipClasses.arrow}`]: { color: theme.palette.success.main diff --git a/interface/src/components/index.ts b/interface/src/components/index.ts index 4d47f6d2d..74de4154b 100644 --- a/interface/src/components/index.ts +++ b/interface/src/components/index.ts @@ -1,10 +1,15 @@ +// Optimized exports - use direct exports to reduce bundle size +export { default as SectionContent } from './SectionContent'; +export { default as ButtonRow } from './ButtonRow'; +export { default as MessageBox } from './MessageBox'; +export { default as ButtonTooltip } from './ButtonTooltip'; + +// Re-export sub-modules export * from './inputs'; export * from './layout'; export * from './loading'; export * from './routing'; export * from './upload'; -export { default as SectionContent } from './SectionContent'; -export { default as ButtonRow } from './ButtonRow'; -export { default as MessageBox } from './MessageBox'; + +// Specific routing exports export { default as BlockNavigation } from './routing/BlockNavigation'; -export { default as ButtonTooltip } from './ButtonTooltip'; diff --git a/interface/src/components/inputs/ValidatedTextField.tsx b/interface/src/components/inputs/ValidatedTextField.tsx index 9fdcdab5d..c52fd3d09 100644 --- a/interface/src/components/inputs/ValidatedTextField.tsx +++ b/interface/src/components/inputs/ValidatedTextField.tsx @@ -16,14 +16,14 @@ const ValidatedTextField: FC = ({ fieldErrors, ...rest }) => { - const errors = fieldErrors && fieldErrors[rest.name]; - const renderErrors = () => - errors && - errors.map((e) => {e.message}); + const errors = fieldErrors?.[rest.name]; + return ( <> - {renderErrors()} + {errors?.map((e) => ( + {e.message} + ))} ); }; diff --git a/interface/src/components/layout/LayoutMenuItem.tsx b/interface/src/components/layout/LayoutMenuItem.tsx index 99b6278bd..ba4c52d63 100644 --- a/interface/src/components/layout/LayoutMenuItem.tsx +++ b/interface/src/components/layout/LayoutMenuItem.tsx @@ -23,7 +23,12 @@ const LayoutMenuItem = ({ const selected = routeMatches(to, pathname); return ( - + diff --git a/interface/src/components/layout/ListMenuItem.tsx b/interface/src/components/layout/ListMenuItem.tsx index 0a8df795b..4d957b595 100644 --- a/interface/src/components/layout/ListMenuItem.tsx +++ b/interface/src/components/layout/ListMenuItem.tsx @@ -58,12 +58,22 @@ const LayoutMenuItem = ({ } > - + ) : ( - + )} diff --git a/interface/src/components/loading/LazyLoader.tsx b/interface/src/components/loading/LazyLoader.tsx new file mode 100644 index 000000000..d21e0a147 --- /dev/null +++ b/interface/src/components/loading/LazyLoader.tsx @@ -0,0 +1,20 @@ +import { Box, CircularProgress } from '@mui/material'; + +const LazyLoader = () => ( + + + +); + +export default LazyLoader; diff --git a/interface/src/components/loading/index.ts b/interface/src/components/loading/index.ts index 76041e14b..83aa1ddf0 100644 --- a/interface/src/components/loading/index.ts +++ b/interface/src/components/loading/index.ts @@ -1,2 +1,3 @@ export { default as LoadingSpinner } from './LoadingSpinner'; export { default as FormLoader } from './FormLoader'; +export { default as LazyLoader } from './LazyLoader'; diff --git a/interface/src/components/routing/authentication.ts b/interface/src/components/routing/authentication.ts index ac79aaccb..fb660fe71 100644 --- a/interface/src/components/routing/authentication.ts +++ b/interface/src/components/routing/authentication.ts @@ -1,6 +1,5 @@ import type { Path } from 'react-router'; -import type * as H from 'history'; import { jwtDecode } from 'jwt-decode'; import type { Me, SignInRequest, SignInResponse } from 'types'; @@ -18,10 +17,10 @@ export function getStorage() { return localStorage || sessionStorage; } -export function storeLoginRedirect(location?: H.Location) { +export function storeLoginRedirect(location?: { pathname: string; search: string }) { if (location) { - getStorage().setItem(SIGN_IN_PATHNAME, location.pathname as string); - getStorage().setItem(SIGN_IN_SEARCH, location.search as string); + getStorage().setItem(SIGN_IN_PATHNAME, location.pathname); + getStorage().setItem(SIGN_IN_SEARCH, location.search); } } @@ -36,7 +35,7 @@ export function fetchLoginRedirect(): Partial { clearLoginRedirect(); return { pathname: signInPathname || `/dashboard`, - search: (signInPathname && signInSearch) || undefined + ...(signInPathname && signInSearch && { search: signInSearch }) }; } diff --git a/interface/src/components/upload/DragNdrop.tsx b/interface/src/components/upload/DragNdrop.tsx index e2cfa2abc..1449f5e6c 100644 --- a/interface/src/components/upload/DragNdrop.tsx +++ b/interface/src/components/upload/DragNdrop.tsx @@ -1,16 +1,59 @@ // Code inspired by Prince Azubuike from https://medium.com/@dprincecoder/creating-a-drag-and-drop-file-upload-component-in-react-a-step-by-step-guide-4d93b6cc21e0 -import { type ChangeEvent, useRef, useState } from 'react'; +import { + type ChangeEvent, + type DragEvent, + type MouseEvent, + useRef, + useState +} from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import CloudUploadIcon from '@mui/icons-material/CloudUpload'; import UploadIcon from '@mui/icons-material/Upload'; -import { Box, Button } from '@mui/material'; +import { Box, Button, Typography, styled } from '@mui/material'; import { useI18nContext } from 'i18n/i18n-react'; -import './dragNdrop.css'; +const DocumentUploader = styled(Box)<{ active?: boolean }>(({ theme, active }) => ({ + border: `2px dashed ${active ? '#6dc24b' : '#4282fe'}`, + backgroundColor: '#2e3339', + padding: theme.spacing(1.25), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + position: 'relative', + borderRadius: theme.spacing(1), + cursor: 'pointer', + minHeight: '120px', + transition: 'border-color 0.2s ease-in-out' +})); -const DragNdrop = ({ text, onFileSelected }) => { +const UploadInfo = styled(Box)({ + display: 'flex', + alignItems: 'center' +}); + +const FileInfo = styled(Box)({ + display: 'flex', + flexDirection: 'column', + width: '100%', + justifyContent: 'space-between', + alignItems: 'center' +}); + +const FileName = styled(Typography)(({ theme }) => ({ + fontSize: '14px', + color: '#6dc24b', + margin: theme.spacing(1, 0) +})); + +interface DragNdropProps { + text: string; + onFileSelected: (file: File) => void; +} + +const DragNdrop = ({ text, onFileSelected }: DragNdropProps) => { const [file, setFile] = useState(); const [dragged, setDragged] = useState(false); const inputRef = useRef(null); @@ -28,14 +71,17 @@ const DragNdrop = ({ text, onFileSelected }) => { }; const handleFileChange = (e: ChangeEvent) => { - if (!e.target.files) { + if (!e.target.files || e.target.files.length === 0) { return; } - checkFileExtension(e.target.files[0]); + const selectedFile = e.target.files[0]; + if (selectedFile) { + checkFileExtension(selectedFile); + } e.target.value = ''; // this is to allow the same file to be selected again }; - const handleDrop = (event) => { + const handleDrop = (event: DragEvent) => { event.preventDefault(); const droppedFiles = event.dataTransfer.files; if (droppedFiles.length > 0) { @@ -43,38 +89,40 @@ const DragNdrop = ({ text, onFileSelected }) => { } }; - const handleRemoveFile = (event) => { + const handleRemoveFile = (event: MouseEvent) => { event.stopPropagation(); setFile(undefined); setDragged(false); }; - const handleUploadClick = (event) => { + const handleUploadClick = (event: MouseEvent) => { event.stopPropagation(); - onFileSelected(file); + if (file) { + onFileSelected(file); + } }; const handleBrowseClick = () => { inputRef.current?.click(); }; - const handleDragOver = (event) => { + const handleDragOver = (event: DragEvent) => { event.preventDefault(); // prevent file from being opened setDragged(true); }; return ( -
setDragged(false)} onClick={handleBrowseClick} > -
+ -

{text}

-
+ {text} + { {file && ( <> -
-

{file.name}

-
+ + {file.name} +
+ ); }; diff --git a/interface/src/components/upload/SingleUpload.tsx b/interface/src/components/upload/SingleUpload.tsx index 912e4aa17..5ad6941f2 100644 --- a/interface/src/components/upload/SingleUpload.tsx +++ b/interface/src/components/upload/SingleUpload.tsx @@ -12,7 +12,12 @@ import { useI18nContext } from 'i18n/i18n-react'; import DragNdrop from './DragNdrop'; import { LinearProgressWithLabel } from './LinearProgressWithLabel'; -const SingleUpload = ({ text, doRestart }) => { +interface SingleUploadProps { + text: string; + doRestart: () => void; +} + +const SingleUpload = ({ text, doRestart }: SingleUploadProps) => { const [md5, setMd5] = useState(); const [file, setFile] = useState(); const { LL } = useI18nContext(); @@ -25,8 +30,8 @@ const SingleUpload = ({ text, doRestart }) => { } = useRequest(SystemApi.uploadFile, { immediate: false }).onSuccess(({ data }) => { - if (data) { - setMd5(data.md5 as string); + if (data && typeof data === 'object' && 'md5' in data) { + setMd5((data as { md5: string }).md5); toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL()); setFile(undefined); } else { @@ -34,16 +39,19 @@ const SingleUpload = ({ text, doRestart }) => { } }); - useEffect(async () => { - if (file) { - await sendUpload(file).catch((error: Error) => { - if (error.message === 'The user aborted a request') { - toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED()); - } else { - toast.warning('Invalid file extension or incompatible bin file'); - } - }); - } + useEffect(() => { + const uploadFile = async () => { + if (file) { + await sendUpload(file).catch((error: Error) => { + if (error.message.includes('The user aborted a request')) { + toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED()); + } else { + toast.warning('Invalid file extension or incompatible bin file'); + } + }); + } + }; + void uploadFile(); }, [file]); return ( diff --git a/interface/src/components/upload/dragNdrop.css b/interface/src/components/upload/dragNdrop.css deleted file mode 100644 index f239b9831..000000000 --- a/interface/src/components/upload/dragNdrop.css +++ /dev/null @@ -1,33 +0,0 @@ -.document-uploader { - border: 2px dashed #4282fe; - background-color: #2e3339; - padding: 10px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - position: relative; - border-radius: 8px; - cursor: pointer; - - &.active { - border-color: #6dc24b; - } - - .upload-info { - display: flex; - align-items: center; - } - - .file-info { - display: flex; - flex-direction: column; - width: 100%; - justify-content: space-between; - align-items: center; - p { - font-size: 14px; - color: #6dc24b; - } - } -} diff --git a/interface/src/contexts/authentication/Authentication.tsx b/interface/src/contexts/authentication/Authentication.tsx index d3aef600d..dba45cad7 100644 --- a/interface/src/contexts/authentication/Authentication.tsx +++ b/interface/src/contexts/authentication/Authentication.tsx @@ -69,7 +69,12 @@ const Authentication: FC = ({ children }) => { // cache object to prevent re-renders const obj = useMemo( - () => ({ signIn, signOut, me, refresh }), + () => ({ + signIn, + signOut, + refresh, + ...(me && { me }) + }), [signIn, signOut, me, refresh] ); diff --git a/interface/src/i18n/cz/index.ts b/interface/src/i18n/cz/index.ts index 982e0c650..7455a8e0d 100644 --- a/interface/src/i18n/cz/index.ts +++ b/interface/src/i18n/cz/index.ts @@ -162,6 +162,7 @@ const cz: Translation = { UPLOAD: 'Nahrát', DOWNLOAD: '{{S|s|s}}táhnout', INSTALL: 'Instalovat', + REINSTALL: 'Znovu instalovat', ABORTED: 'přerušeno', FAILED: 'neúspěšné', SUCCESSFUL: 'úspěšné', diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 924d76dc6..4e321b996 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -162,6 +162,7 @@ const de: Translation = { UPLOAD: 'Hochladen', DOWNLOAD: '{{Herunterladen|heruntergeladen|}}', INSTALL: 'Installieren', + REINSTALL: 'Neu installieren', ABORTED: 'abgebrochen', FAILED: 'gescheitert', SUCCESSFUL: 'erfolgreich', diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 9a1ecce64..6c7caf8f4 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -162,6 +162,7 @@ const en: Translation = { UPLOAD: 'Upload', DOWNLOAD: '{{D|d|d}}ownload', INSTALL: 'Install', + REINSTALL: 'Reinstall', ABORTED: 'aborted', FAILED: 'failed', SUCCESSFUL: 'successful', diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index 9970aefaa..d5fd3cece 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -162,6 +162,7 @@ const fr: Translation = { UPLOAD: 'Upload', DOWNLOAD: '{{D|d|d}}ownload', INSTALL: 'Installer', + REINSTALL: 'Réinstaller', ABORTED: 'annulé', FAILED: 'échoué', SUCCESSFUL: 'réussi', diff --git a/interface/src/i18n/it/index.ts b/interface/src/i18n/it/index.ts index 2ad76c4d3..946cff16a 100644 --- a/interface/src/i18n/it/index.ts +++ b/interface/src/i18n/it/index.ts @@ -162,6 +162,7 @@ const it: Translation = { UPLOAD: 'Carica', DOWNLOAD: 'Scarica', INSTALL: 'Installare {0}', + REINSTALL: 'Riavviare', ABORTED: 'Annullato', FAILED: 'Fallito', SUCCESSFUL: 'Riuscito', diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index 2962fdaf0..978ba54e9 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -162,6 +162,7 @@ const nl: Translation = { UPLOAD: 'Upload', DOWNLOAD: '{{D|d|d}}ownload', INSTALL: 'Installeren', + REINSTALL: 'Opnieuw installeren', ABORTED: 'afgebroken', FAILED: 'mislukt', SUCCESSFUL: 'successvol', diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index da6a96cfe..30f8be0b1 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -162,6 +162,7 @@ const no: Translation = { UPLOAD: 'Opplasning', DOWNLOAD: '{{N|n|n}}edlasting', INSTALL: 'Installer', + REINSTALL: 'Ominstaller', ABORTED: 'avbrutt', FAILED: 'feilet', SUCCESSFUL: 'vellykket', diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index fa59238e8..5761c5009 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -162,6 +162,7 @@ const pl: BaseTranslation = { UPLOAD: 'Wysyłanie', DOWNLOAD: '{{P|p||P}}obier{{anie|z||z}}', INSTALL: 'Zainstalować', + REINSTALL: 'Zainstalować ponownie', ABORTED: 'zostało przerwane!', FAILED: 'nie powiodł{{o|a|}} się!', SUCCESSFUL: 'powiodło się.', diff --git a/interface/src/i18n/sk/index.ts b/interface/src/i18n/sk/index.ts index 554b4c5bd..e35a4ef09 100644 --- a/interface/src/i18n/sk/index.ts +++ b/interface/src/i18n/sk/index.ts @@ -162,6 +162,7 @@ const sk: Translation = { UPLOAD: 'Nahrať', DOWNLOAD: '{{S|s|s}}tiahnuť', INSTALL: 'Inštalovať', + REINSTALL: 'Inštalovať znova', ABORTED: 'zrušené', FAILED: 'chybné', SUCCESSFUL: 'úspešné', diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index a46a8bdb8..4c695148c 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -162,6 +162,7 @@ const sv: Translation = { UPLOAD: 'Uppladdning', DOWNLOAD: '{{N|n|n}}edladdning', INSTALL: 'Installera', + REINSTALL: 'Återinstallera', ABORTED: 'Avbruten', FAILED: 'Misslyckades', SUCCESSFUL: 'Lyckades', diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index b6b1556be..9e8868d78 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -162,6 +162,7 @@ const tr: Translation = { UPLOAD: 'Yükleme', DOWNLOAD: '{{İ|i|i}}İndirme', INSTALL: 'Düzenlemek', + REINSTALL: 'Yeniden düzenlemek', ABORTED: 'iptal edildi', FAILED: 'başarısız', SUCCESSFUL: 'başarılı', diff --git a/interface/src/index.tsx b/interface/src/index.tsx index 57ae0fb7d..0b319cdab 100644 --- a/interface/src/index.tsx +++ b/interface/src/index.tsx @@ -13,7 +13,7 @@ const router = createBrowserRouter( createRoutesFromElements(} />) ); -createRoot(document.getElementById('root') as HTMLElement).render( +createRoot(document.getElementById('root')!).render( diff --git a/interface/src/utils/binding.ts b/interface/src/utils/binding.ts index 54c52e38e..443f5a0f9 100644 --- a/interface/src/utils/binding.ts +++ b/interface/src/utils/binding.ts @@ -29,10 +29,10 @@ export const updateValue = export const updateValueDirty = ( - origData, + origData: unknown, dirtyFlags: string[], setDirtyFlags: React.Dispatch>, - updateDataValue: (unknown) => void + updateDataValue: (value: unknown) => void ) => (event: React.ChangeEvent) => { const updated_value = extractEventValue(event); diff --git a/interface/tsconfig.json b/interface/tsconfig.json index 3e8dfeb47..ec73ad764 100644 --- a/interface/tsconfig.json +++ b/interface/tsconfig.json @@ -1,31 +1,108 @@ { "compilerOptions": { - "target": "ESNext", + // Target modern browsers for better performance + "target": "ES2022", "useDefineForClassFields": true, - "lib": ["DOM", "DOM.Iterable", "ESNext"], - "types": ["node"], + + // Optimized library selection + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["node", "vite/client"], + + // JavaScript handling "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "composite": true, + "checkJs": false, + + // Module system optimized for Vite "module": "ESNext", "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, "resolveJsonModule": true, "isolatedModules": true, + + // Emit configuration "noEmit": true, - "useUnknownInCatchVariables": false, + "declaration": false, + "declarationMap": false, + "sourceMap": false, + + // React/JSX configuration "jsx": "react-jsx", - "noImplicitAny": false, - "baseUrl": "src", + "jsxImportSource": "react", + + // Strict type checking for better code quality + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "exactOptionalPropertyTypes": true, + + // Additional checks + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + + // Performance optimizations + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "incremental": true, + "tsBuildInfoFile": ".tsbuildinfo", + + // Path mapping for cleaner imports + "baseUrl": ".", "paths": { - "@": ["src"], - "@/*": ["src/*"] + "@/*": ["src/*"], + "@/components/*": ["src/components/*"], + "@/utils/*": ["src/utils/*"], + "@/types/*": ["src/types/*"], + "@/hooks/*": ["src/hooks/*"], + "@/services/*": ["src/services/*"], + "@/assets/*": ["src/assets/*"], + // Support for bare imports from src directory + "App": ["src/App"], + "AppRouting": ["src/AppRouting"], + "CustomTheme": ["src/CustomTheme"], + "SignIn": ["src/SignIn"], + "AuthenticatedRouting": ["src/AuthenticatedRouting"], + "env": ["src/env"], + "components": ["src/components"], + "contexts": ["src/contexts"], + "i18n": ["src/i18n"], + "utils": ["src/utils"], + "validators": ["src/validators"], + "types": ["src/types"], + "api": ["src/api"], + "app": ["src/app"], + // Wildcard patterns for subdirectories + "components/*": ["src/components/*"], + "contexts/*": ["src/contexts/*"], + "i18n/*": ["src/i18n/*"], + "utils/*": ["src/utils/*"], + "validators/*": ["src/validators/*"], + "types/*": ["src/types/*"], + "api/*": ["src/api/*"], + "app/*": ["src/app/*"] } }, - "include": ["src/**/*", "vite.config.ts"], - "exclude": ["node_modules", "dist"] + "include": ["src/**/*", "vite.config.ts", "progmem-generator.js"], + "exclude": [ + "node_modules", + "dist", + "build", + ".tsbuildinfo", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.spec.ts", + "**/*.spec.tsx" + ], + "ts-node": { + "esm": true + } } diff --git a/interface/vite.config.ts b/interface/vite.config.ts index 8c7b336a4..d05a2bd40 100644 --- a/interface/vite.config.ts +++ b/interface/vite.config.ts @@ -1,130 +1,331 @@ import preact from '@preact/preset-vite'; +import fs from 'fs'; +import path from 'path'; import { visualizer } from 'rollup-plugin-visualizer'; import { defineConfig } from 'vite'; -// import viteImagemin from 'vite-plugin-imagemin'; +import { Plugin } from 'vite'; +import viteImagemin from 'vite-plugin-imagemin'; import viteTsconfigPaths from 'vite-tsconfig-paths'; +import zlib from 'zlib'; +// @ts-expect-error - mock server doesn't have type declarations import mockServer from '../mock-api/mockServer.js'; -export default defineConfig(({ command, mode }) => { - if (command === 'serve') { - console.log('Preparing for standalone build with server, mode=' + mode); - return { - plugins: [preact(), viteTsconfigPaths(), mockServer()], - server: { - open: true, - port: mode == 'production' ? 4173 : 3000, - proxy: { - '/api': { - target: 'http://localhost:3080', - changeOrigin: true, - secure: false - }, - '/rest': 'http://localhost:3080', - '/gh': 'http://localhost:3080' // mock for GitHub API - } - } - }; - } - - if (mode === 'hosted') { - console.log('Preparing for hosted build'); - return { - plugins: [preact(), viteTsconfigPaths()], - build: { - chunkSizeWarningLimit: 1024 - } - }; - } - - console.log('Preparing for production, optimized build'); - +// Plugin to display bundle size information +const bundleSizeReporter = (): Plugin => { return { - plugins: [ - preact(), - viteTsconfigPaths(), - // { - // ...viteImagemin({ - // verbose: false, - // gifsicle: { - // optimizationLevel: 7, - // interlaced: false - // }, - // optipng: { - // optimizationLevel: 7 - // }, - // mozjpeg: { - // quality: 20 - // }, - // pngquant: { - // quality: [0.8, 0.9], - // speed: 4 - // }, - // svgo: { - // plugins: [ - // { - // name: 'removeViewBox' - // }, - // { - // name: 'removeEmptyAttrs', - // active: false - // } - // ] - // } - // }), - // enforce: 'pre' - // }, - visualizer({ - template: 'treemap', // or sunburst - open: false, - gzipSize: true, - brotliSize: true, - filename: '../analyse.html' // will be saved in project's root - }) - ], + name: 'bundle-size-reporter', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + writeBundle(options: any, bundle: any) { + console.log('\n📦 Bundle Size Report:'); + console.log('='.repeat(50)); - build: { - // target: 'es2022', - chunkSizeWarningLimit: 1024, - minify: 'terser', - terserOptions: { - compress: { - passes: 4, - arrows: true, - drop_console: true, - drop_debugger: true, - sequences: true - }, - mangle: { - // toplevel: true - // module: true - // properties: { - // regex: /^_/ - // } - }, - ecma: 5, - enclose: false, - keep_classnames: false, - keep_fnames: false, - ie8: false, - module: false, - safari10: false, - toplevel: false - }, + let totalSize = 0; + const files: Array<{ name: string; size: number; gzipSize?: number }> = []; - rollupOptions: { - output: { - manualChunks(id: string) { - if (id.includes('node_modules')) { - // creating a chunk to react routes deps. Reducing the vendor chunk size - if (id.includes('react-router')) { - return '@react-router'; - } - return 'vendor'; - } + for (const [fileName, chunk] of Object.entries( + bundle as Record + )) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((chunk as any).type === 'chunk' || (chunk as any).type === 'asset') { + const filePath = path.join((options.dir as string) || 'dist', fileName); + let size = 0; + let gzipSize = 0; + + try { + const stats = fs.statSync(filePath); + size = stats.size; + totalSize += size; + + // Calculate gzip size + const fileContent = fs.readFileSync(filePath); + gzipSize = zlib.gzipSync(fileContent).length; + + files.push({ + name: fileName, + size, + gzipSize + }); + } catch (error) { + console.warn(`Could not read file ${fileName}:`, error); } } } + + // Sort files by size (largest first) + files.sort((a, b) => b.size - a.size); + + // Display individual file sizes + files.forEach((file) => { + const sizeKB = (file.size / 1024).toFixed(2); + const gzipKB = file.gzipSize ? (file.gzipSize / 1024).toFixed(2) : 'N/A'; + console.log( + `📄 ${file.name.padEnd(30)} ${sizeKB.padStart(8)} KB (${gzipKB} KB gzipped)` + ); + }); + + console.log('='.repeat(50)); + console.log(`📊 Total Bundle Size: ${(totalSize / 1024).toFixed(2)} KB`); + + // Calculate and display gzip total + const totalGzipSize = files.reduce( + (sum, file) => sum + (file.gzipSize || 0), + 0 + ); + console.log(`🗜️ Total Gzipped Size: ${(totalGzipSize / 1024).toFixed(2)} KB`); + + // Show compression ratio + const compressionRatio = ( + ((totalSize - totalGzipSize) / totalSize) * + 100 + ).toFixed(1); + console.log(`📈 Compression Ratio: ${compressionRatio}%`); + + console.log('='.repeat(50)); } }; -}); +}; + +export default defineConfig( + ({ command, mode }: { command: string; mode: string }) => { + if (command === 'serve') { + console.log('Preparing for standalone build with server, mode=' + mode); + return { + plugins: [ + preact({ + // Keep dev tools enabled for development + devToolsEnabled: true, + prefreshEnabled: true + }), + viteTsconfigPaths(), + bundleSizeReporter(), // Add bundle size reporting + mockServer() + ], + server: { + open: true, + port: mode == 'production' ? 4173 : 3000, + proxy: { + '/api': { + target: 'http://localhost:3080', + changeOrigin: true, + secure: false + }, + '/rest': 'http://localhost:3080', + '/gh': 'http://localhost:3080' // mock for GitHub API + } + }, + // Optimize development builds + build: { + target: 'es2020', + minify: false, // Disable minification for faster dev builds + sourcemap: true // Enable source maps for debugging + } + }; + } + + if (mode === 'hosted') { + console.log('Preparing for hosted build'); + return { + plugins: [ + preact({ + // Enable Preact optimizations for hosted build + devToolsEnabled: false, + prefreshEnabled: false + }), + viteTsconfigPaths(), + bundleSizeReporter() // Add bundle size reporting + ], + build: { + target: 'es2020', + chunkSizeWarningLimit: 512, + minify: 'terser', + cssMinify: true, + assetsInlineLimit: 4096, + terserOptions: { + compress: { + passes: 3, + drop_console: true, + drop_debugger: true, + dead_code: true, + unused: true + }, + mangle: { + toplevel: true + }, + ecma: 2020 + }, + rollupOptions: { + treeshake: { + moduleSideEffects: false + }, + output: { + manualChunks(id: string) { + if (id.includes('node_modules')) { + if (id.includes('preact')) { + return '@preact'; + } + return 'vendor'; + } + return undefined; + } + } + } + } + }; + } + + console.log('Preparing for production, optimized build'); + + return { + plugins: [ + preact({ + // Enable Preact optimizations + devToolsEnabled: false, + prefreshEnabled: false + }), + viteTsconfigPaths(), + // Enable image optimization for size reduction + { + ...viteImagemin({ + verbose: false, + gifsicle: { + optimizationLevel: 7, + interlaced: false + }, + optipng: { + optimizationLevel: 7 + }, + mozjpeg: { + quality: 20 + }, + pngquant: { + quality: [0.8, 0.9], + speed: 4 + }, + svgo: { + plugins: [ + { + name: 'removeViewBox' + }, + { + name: 'removeEmptyAttrs', + active: false + } + ] + } + }), + enforce: 'pre' + }, + visualizer({ + template: 'treemap', // or sunburst + open: false, + gzipSize: true, + brotliSize: true, + filename: '../analyse.html' // will be saved in project's root + }), + bundleSizeReporter() // Add bundle size reporting + ], + + build: { + // Target modern browsers for smaller bundles + target: 'es2020', + chunkSizeWarningLimit: 512, + minify: 'terser', + // Enable CSS minification + cssMinify: true, + // Optimize asset handling + assetsInlineLimit: 4096, // Inline small assets + terserOptions: { + compress: { + passes: 6, + arrows: true, + drop_console: true, + drop_debugger: true, + sequences: true + // Additional aggressive compression options + // dead_code: true, + // hoist_funs: true, + // hoist_vars: true, + // if_return: true, + // join_vars: true, + // loops: true, + // pure_getters: true, + // reduce_vars: true, + // side_effects: false, + // switches: true, + // unsafe: true, + // unsafe_arrows: true, + // unsafe_comps: true, + // unsafe_Function: true, + // unsafe_math: true, + // unsafe_proto: true, + // unsafe_regexp: true, + // unsafe_undefined: true, + // unused: true + }, + mangle: { + toplevel: true, // Enable top-level mangling + module: true // Enable module mangling + // properties: { + // regex: /^_/ // Mangle properties starting with _ + // } + }, + ecma: 2020, // Updated to modern ECMAScript + enclose: false, + keep_classnames: false, + keep_fnames: false, + ie8: false, + module: false, + safari10: false, + toplevel: true // Enable top-level optimization + }, + + rollupOptions: { + // Enable tree shaking + treeshake: { + moduleSideEffects: false, + propertyReadSideEffects: false, + tryCatchDeoptimization: false + }, + output: { + // Optimize chunk naming for better caching + chunkFileNames: 'assets/[name]-[hash].js', + entryFileNames: 'assets/[name]-[hash].js', + assetFileNames: 'assets/[name]-[hash].[ext]', + manualChunks(id: string) { + if (id.includes('node_modules')) { + // More granular chunk splitting for better caching + if (id.includes('react-router')) { + return '@react-router'; + } + if (id.includes('preact')) { + return '@preact'; + } + if (id.includes('uuid')) { + return '@uuid'; + } + if (id.includes('axios') || id.includes('fetch')) { + return '@http'; + } + if (id.includes('lodash') || id.includes('ramda')) { + return '@utils'; + } + return 'vendor'; + } + // Split large application modules + if (id.includes('components/')) { + return 'components'; + } + if (id.includes('pages/') || id.includes('routes/')) { + return 'pages'; + } + return undefined; + }, + // Enable source maps for debugging (optional) + sourcemap: false // Disable for production to save space + } + } + } + }; + } +); diff --git a/mock-api/mockServer.js b/mock-api/mockServer.js index 9951d31de..1f9245bd6 100644 --- a/mock-api/mockServer.js +++ b/mock-api/mockServer.js @@ -1,33 +1,24 @@ -// used to simulate -// - file uploads -// - EventSource (SSE) for log messages +// Mock server for development +// Simulates file uploads and EventSource (SSE) for log messages import formidable from 'formidable'; -function pad(number) { - let r = String(number); - if (r.length === 1) { - r = '0' + r; - } - return r; -} +// Optimized padding function +const pad = (number) => String(number).padStart(2, '0'); -// e.g. 2024-03-29 07:02:37.856 -Date.prototype.toISOString = function () { - return ( - this.getUTCFullYear() + - '-' + - pad(this.getUTCMonth() + 1) + - '-' + - pad(this.getUTCDate()) + - ' ' + - pad(this.getUTCHours()) + - ':' + - pad(this.getUTCMinutes()) + - ':' + - pad(this.getUTCSeconds()) + - '.' + - String((this.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5) +// Cached date formatter to avoid prototype pollution +const formatDate = (date) => { + const year = date.getUTCFullYear(); + const month = pad(date.getUTCMonth() + 1); + const day = pad(date.getUTCDate()); + const hours = pad(date.getUTCHours()); + const minutes = pad(date.getUTCMinutes()); + const seconds = pad(date.getUTCSeconds()); + const milliseconds = String((date.getUTCMilliseconds() / 1000).toFixed(3)).slice( + 2, + 5 ); + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`; }; export default () => { @@ -35,97 +26,129 @@ export default () => { name: 'vite:mockserver', configureServer: async (server) => { server.middlewares.use(async (req, res, next) => { - // catch any file uploads + // Handle file uploads if (req.url.startsWith('/rest/uploadFile')) { - // show progress + const fileSize = parseInt(req.headers['content-length'] || '0', 10); let progress = 0; - const file_size = req.headers['content-length']; - console.log('File size: ' + file_size); - req.on('data', async (chunk) => { + + // Track upload progress + req.on('data', (chunk) => { progress += chunk.length; - const percentage = (progress / file_size) * 100; - console.log(`Progress: ${Math.round(percentage)}%`); - // await new Promise((resolve) => setTimeout(() => resolve(), 3000)); // slow it down + if (fileSize > 0) { + const percentage = Math.round((progress / fileSize) * 100); + console.log(`Upload progress: ${percentage}%`); + } }); - const form = formidable({}); - let fields; - let files; try { - [fields, files] = await form.parse(req); - } catch (err) { - console.error('Not json form content'); - res.writeHead(err.httpCode || 400, { - 'Content-Type': 'text/plain' + const form = formidable({ + maxFileSize: 50 * 1024 * 1024, // 50MB limit + keepExtensions: true }); - res.end(String(err)); - return; - } - // only process when we have a file - if (Object.keys(files).length > 0) { - const uploaded_file = files.file[0]; - const file_name = uploaded_file.originalFilename; - const file_extension = file_name.substring( - file_name.lastIndexOf('.') + 1 + const [fields, files] = await form.parse(req); + + if (Object.keys(files).length === 0) { + res.statusCode = 400; + res.end('No file uploaded'); + return; + } + + const uploadedFile = files.file[0]; + const fileName = uploadedFile.originalFilename; + const fileExtension = fileName + .substring(fileName.lastIndexOf('.') + 1) + .toLowerCase(); + + console.log( + `File uploaded: ${fileName} (${fileExtension}, ${fileSize} bytes)` ); - console.log('Filename: ' + file_name); - console.log('Extension: ' + file_extension); - console.log('File size: ' + file_size); + // Validate file extension + const validExtensions = new Set(['bin', 'json', 'md5']); + if (!validExtensions.has(fileExtension)) { + res.statusCode = 406; + res.end('Invalid file extension'); + return; + } - if (file_extension === 'bin' || file_extension === 'json') { - console.log('File uploaded successfully!'); - } else if (file_extension === 'md5') { - console.log('MD5 hash generated successfully!'); + // Handle different file types + if (fileExtension === 'md5') { + res.setHeader('Content-Type', 'application/json'); res.end( JSON.stringify({ md5: 'ef4304fc4d9025a58dcf25d71c882d2c' }) ); } else { - res.statusCode = 406; - console.log('Invalid file extension!'); + console.log('File uploaded successfully!'); + res.end(); } + } catch (err) { + console.error('Upload error:', err.message); + res.statusCode = err.httpCode || 400; + res.setHeader('Content-Type', 'text/plain'); + res.end(err.message); } - - res.end(); } - // SSE Eventsource + // Handle Server-Sent Events (SSE) for log streaming else if (req.url.startsWith('/es/log')) { + // Set SSE headers res.writeHead(200, { Connection: 'keep-alive', 'Cache-Control': 'no-cache', - 'Content-Type': 'text/event-stream' + 'Content-Type': 'text/event-stream', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Cache-Control' }); - let count = 0; - const interval = setInterval(() => { - let message = 'message #' + count; - if (count % 6 === 1) { + let messageCount = 0; + const logLevels = [3, 4, 5, 6, 7, 8]; // Different log levels + const logNames = ['system', 'ems', 'wifi', 'mqtt', 'ntp', 'api']; + + const sendLogMessage = () => { + const level = logLevels[messageCount % logLevels.length]; + const name = logNames[messageCount % logNames.length]; + let message = `Log message #${messageCount}`; + + // Add long message every 6th message + if (messageCount % 6 === 1) { message += - ' that is a long message that will be wrapped, to test if it gets truncated'; + ' - This is a longer message to test text wrapping and truncation behavior in the UI'; } - const data = { - t: new Date().toISOString(), - l: 3 + (count % 6), - i: count, - n: 'system', + + const logData = { + t: formatDate(new Date()), + l: level, + i: messageCount, + n: name, m: message }; - count++; - res.write(`data: ${JSON.stringify(data)}\n\n`); - }, 1000); - // if client closes connection - res.on('close', () => { - console.log('Closing ES connection'); + res.write(`data: ${JSON.stringify(logData)}\n\n`); + messageCount++; + }; + + // Send initial message + sendLogMessage(); + + // Set up interval for periodic messages + const interval = setInterval(sendLogMessage, 1000); + + // Clean up on connection close + const cleanup = () => { + console.log('SSE connection closed'); clearInterval(interval); - res.end(); - }); + if (!res.destroyed) { + res.end(); + } + }; + + res.on('close', cleanup); + res.on('error', cleanup); } else { - next(); // move on to the next middleware function in chain + next(); // Continue to next middleware } }); } diff --git a/mock-api/package.json b/mock-api/package.json index 69ae8ede5..33ef45bef 100644 --- a/mock-api/package.json +++ b/mock-api/package.json @@ -1,6 +1,6 @@ { "name": "mock-api", - "version": "3.7.2", + "version": "3.7.3", "description": "mock api for EMS-ESP", "author": "proddy, emsesp.org", "license": "MIT", @@ -15,5 +15,5 @@ "itty-router": "^5.0.22", "prettier": "^3.6.2" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.19.0+sha512.c9fc7236e92adf5c8af42fd5bf1612df99c2ceb62f27047032f4720b33f8eacdde311865e91c411f2774f618d82f320808ecb51718bfa82c060c4ba7c76a32b8" } diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts index 0ea13ac1c..f760dc3e5 100644 --- a/mock-api/restServer.ts +++ b/mock-api/restServer.ts @@ -116,17 +116,15 @@ let system_status = { let DEV_VERSION_IS_UPGRADEABLE: boolean; let STABLE_VERSION_IS_UPGRADEABLE: boolean; let THIS_VERSION: string; -let version_test: number; - let LATEST_STABLE_VERSION = '3.7.2'; let LATEST_DEV_VERSION = '3.7.3-dev.6'; // scenarios for testing versioning -version_test = 0; // on latest stable, or switch to dev -// version_test = 1; // on latest dev, or switch back to stable -// version_test = 2; // upgrade an older stable to latest stable or switch to latest dev -// version_test = 3; // upgrade dev to latest, or switch to stable -// version_test = 4; // downgrade to an older dev, or switch back to stable +let version_test = 0; // on latest stable, or switch to dev +// let version_test = 1; // on latest dev, or switch back to stable +// let version_test = 2; // upgrade an older stable to latest stable or switch to latest dev +// let version_test = 3; // upgrade dev to latest, or switch to stable +// let version_test = 4; // downgrade to an older dev, or switch back to stable switch (version_test as number) { case 0: @@ -4380,78 +4378,124 @@ router function deviceData(id: number) { if (id == 1) { - return new Response(encoder.encode(emsesp_devicedata_1), { headers }); + return new Response(encoder.encode(emsesp_devicedata_1) as BodyInit, { + headers + }); } if (id == 2) { - return new Response(encoder.encode(emsesp_devicedata_2), { headers }); + return new Response(encoder.encode(emsesp_devicedata_2) as BodyInit, { + headers + }); } if (id == 3) { - return new Response(encoder.encode(emsesp_devicedata_3), { headers }); + return new Response(encoder.encode(emsesp_devicedata_3) as BodyInit, { + headers + }); } if (id == 4) { - return new Response(encoder.encode(emsesp_devicedata_4), { headers }); + return new Response(encoder.encode(emsesp_devicedata_4) as BodyInit, { + headers + }); } if (id == 5) { - return new Response(encoder.encode(emsesp_devicedata_5), { headers }); + return new Response(encoder.encode(emsesp_devicedata_5) as BodyInit, { + headers + }); } if (id == 6) { - return new Response(encoder.encode(emsesp_devicedata_6), { headers }); + return new Response(encoder.encode(emsesp_devicedata_6) as BodyInit, { + headers + }); } if (id == 7) { - return new Response(encoder.encode(emsesp_devicedata_7), { headers }); + return new Response(encoder.encode(emsesp_devicedata_7) as BodyInit, { + headers + }); } if (id == 8) { // test changing the selected flow temp on a Bosch Compress 7000i AW Heat Pump (Boiler/HP) emsesp_devicedata_8.nodes[4].v = Math.floor(Math.random() * 100); - return new Response(encoder.encode(emsesp_devicedata_8), { headers }); + return new Response(encoder.encode(emsesp_devicedata_8) as BodyInit, { + headers + }); } if (id == 9) { - return new Response(encoder.encode(emsesp_devicedata_9), { headers }); + return new Response(encoder.encode(emsesp_devicedata_9) as BodyInit, { + headers + }); } if (id == 10) { - return new Response(encoder.encode(emsesp_devicedata_10), { headers }); + return new Response(encoder.encode(emsesp_devicedata_10) as BodyInit, { + headers + }); } if (id == 11) { - return new Response(encoder.encode(emsesp_devicedata_11), { headers }); + return new Response(encoder.encode(emsesp_devicedata_11) as BodyInit, { + headers + }); } if (id == 99) { - return new Response(encoder.encode(emsesp_devicedata_99), { headers }); + return new Response(encoder.encode(emsesp_devicedata_99) as BodyInit, { + headers + }); } } function deviceEntities(id: number) { if (id == 1) { - return new Response(encoder.encode(emsesp_deviceentities_1), { headers }); + return new Response(encoder.encode(emsesp_deviceentities_1) as BodyInit, { + headers + }); } if (id == 2) { - return new Response(encoder.encode(emsesp_deviceentities_2), { headers }); + return new Response(encoder.encode(emsesp_deviceentities_2) as BodyInit, { + headers + }); } if (id == 3) { - return new Response(encoder.encode(emsesp_deviceentities_3), { headers }); + return new Response(encoder.encode(emsesp_deviceentities_3) as BodyInit, { + headers + }); } if (id == 4) { - return new Response(encoder.encode(emsesp_deviceentities_4), { headers }); + return new Response(encoder.encode(emsesp_deviceentities_4) as BodyInit, { + headers + }); } if (id == 5) { - return new Response(encoder.encode(emsesp_deviceentities_5), { headers }); + return new Response(encoder.encode(emsesp_deviceentities_5) as BodyInit, { + headers + }); } if (id == 6) { - return new Response(encoder.encode(emsesp_deviceentities_6), { headers }); + return new Response(encoder.encode(emsesp_deviceentities_6) as BodyInit, { + headers + }); } if (id == 7) { - return new Response(encoder.encode(emsesp_deviceentities_7), { headers }); + return new Response(encoder.encode(emsesp_deviceentities_7) as BodyInit, { + headers + }); } if (id == 8) { - return new Response(encoder.encode(emsesp_deviceentities_8), { headers }); + return new Response(encoder.encode(emsesp_deviceentities_8) as BodyInit, { + headers + }); } if (id == 9) { - return new Response(encoder.encode(emsesp_deviceentities_9), { headers }); + return new Response(encoder.encode(emsesp_deviceentities_9) as BodyInit, { + headers + }); } if (id == 10) { - return new Response(encoder.encode(emsesp_deviceentities_10), { headers }); + return new Response(encoder.encode(emsesp_deviceentities_10) as BodyInit, { + headers + }); } // not found, return empty - return new Response(encoder.encode(emsesp_deviceentities_none), { headers }); + return new Response(encoder.encode(emsesp_deviceentities_none) as BodyInit, { + headers + }); } // prepare dashboard data @@ -4558,65 +4602,63 @@ router } // add temperature sensor data. no command c - let sensor_data: any[] = []; - sensor_data = emsesp_sensordata.ts.map((item, index) => ({ - id: DeviceTypeUniqueID.TEMPERATURESENSOR_UID * 100 + index, - dv: { - id: '00' + item.n, - v: item.t, // value is called t in ts (temperature) - u: item.u - } - })); - dashboard_object = { - id: DeviceTypeUniqueID.TEMPERATURESENSOR_UID, - t: DeviceType.TEMPERATURESENSOR, - nodes: sensor_data - }; - // only add to dashboard if we have values - if ((dashboard_object.nodes ?? []).length > 0) { + if (emsesp_sensordata.ts.length > 0) { + const sensor_data = emsesp_sensordata.ts.map((item, index) => ({ + id: DeviceTypeUniqueID.TEMPERATURESENSOR_UID * 100 + index, + dv: { + id: '00' + item.n, + v: item.t, // value is called t in ts (temperature) + u: item.u + } + })); + dashboard_object = { + id: DeviceTypeUniqueID.TEMPERATURESENSOR_UID, + t: DeviceType.TEMPERATURESENSOR, + nodes: sensor_data + }; dashboard_nodes.push(dashboard_object); } // add analog sensor data. no command c - // remove disabled sensors first (t = 0) - sensor_data = emsesp_sensordata.as.filter((item) => item.t !== 0); - sensor_data = sensor_data.map((item, index) => ({ - id: DeviceTypeUniqueID.ANALOGSENSOR_UID * 100 + index, - dv: { - id: '00' + item.n, - v: item.v, - u: item.u - } - })); - dashboard_object = { - id: DeviceTypeUniqueID.ANALOGSENSOR_UID, - t: DeviceType.ANALOGSENSOR, - nodes: sensor_data - }; - // only add to dashboard if we have values - if ((dashboard_object.nodes ?? []).length > 0) { + // remove disabled sensors first (t = 0) and create data in one pass + const enabledAnalogSensors = emsesp_sensordata.as.filter( + (item) => item.t !== 0 + ); + if (enabledAnalogSensors.length > 0) { + const sensor_data = enabledAnalogSensors.map((item, index) => ({ + id: DeviceTypeUniqueID.ANALOGSENSOR_UID * 100 + index, + dv: { + id: '00' + item.n, + v: item.v, + u: item.u + } + })); + dashboard_object = { + id: DeviceTypeUniqueID.ANALOGSENSOR_UID, + t: DeviceType.ANALOGSENSOR, + nodes: sensor_data + }; dashboard_nodes.push(dashboard_object); } // add the scheduler data - // filter emsesp_schedule with only if it has a name - let scheduler_data = emsesp_schedule.schedule.filter((item) => item.name); - let scheduler_data2 = scheduler_data.map((item, index) => ({ - id: DeviceTypeUniqueID.SCHEDULER_UID * 100 + index, - dv: { - id: '00' + item.name, - v: item.active ? 'on' : 'off', - c: item.name, - l: ['off', 'on'] - } - })); - dashboard_object = { - id: DeviceTypeUniqueID.SCHEDULER_UID, - t: DeviceType.SCHEDULER, - nodes: scheduler_data2 - }; - // only add to dashboard if we have values - if ((dashboard_object.nodes ?? []).length > 0) { + // filter emsesp_schedule with only if it has a name and create data in one pass + const namedSchedules = emsesp_schedule.schedule.filter((item) => item.name); + if (namedSchedules.length > 0) { + const scheduler_data = namedSchedules.map((item, index) => ({ + id: DeviceTypeUniqueID.SCHEDULER_UID * 100 + index, + dv: { + id: '00' + item.name, + v: item.active ? 'on' : 'off', + c: item.name, + l: ['off', 'on'] + } + })); + dashboard_object = { + id: DeviceTypeUniqueID.SCHEDULER_UID, + t: DeviceType.SCHEDULER, + nodes: scheduler_data + }; dashboard_nodes.push(dashboard_object); } } else { @@ -4660,8 +4702,12 @@ router }; // console.log('dashboardData: ', dashboardData); + // Clear references to help with garbage collection + dashboard_nodes = []; + dashboard_object = {}; + // return dashboard_data; // if not using msgpack - return new Response(encoder.encode(dashboardData), { headers }); // msgpack it + return new Response(encoder.encode(dashboardData) as BodyInit, { headers }); // msgpack it }) // Customizations @@ -4852,10 +4898,12 @@ router } else { if (as.deleted) { emsesp_sensordata.as[objIndex].d = true; - var filtered = emsesp_sensordata.as.filter(function (value, index, arr) { - return !value.d; - }); - emsesp_sensordata.as = filtered; + // Remove deleted items in-place to avoid creating new arrays + for (let i = emsesp_sensordata.as.length - 1; i >= 0; i--) { + if (emsesp_sensordata.as[i].d) { + emsesp_sensordata.as.splice(i, 1); + } + } } else { emsesp_sensordata.as[objIndex].n = as.name; emsesp_sensordata.as[objIndex].f = as.factor; diff --git a/platformio.ini b/platformio.ini index 0daf5df98..b320b134b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -197,7 +197,7 @@ extra_scripts = build_flags = -DARDUINOJSON_ENABLE_ARDUINO_STRING=1 -DEMSESP_STANDALONE -DEMSESP_TEST - -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.2-dev.0\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" + -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.3-dev.0\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" -std=gnu++17 -Og -ggdb build_unflags = -std=gnu++11 -std=gnu++14 build_type = debug diff --git a/project-words.txt b/project-words.txt index ac685edb6..ee977b90d 100644 --- a/project-words.txt +++ b/project-words.txt @@ -1426,3 +1426,11 @@ Roomparams Roomdata Roomschedule dewtemp +chefhat +sofasingle +bowlmix +bedsingle +beddouble +teddybear +washingmachine +switchprogram \ No newline at end of file diff --git a/scripts/update_modbus_registers.py b/scripts/update_modbus_registers.py index 94411af84..ded0c7260 100644 --- a/scripts/update_modbus_registers.py +++ b/scripts/update_modbus_registers.py @@ -211,7 +211,7 @@ for entity in entities: if int(entity["modbus count"]) <= 0: raise Exception('Entity "' + entity_dev_name + ' (' + entity_shortname + ')' + - '" does not have a size - string sizes need to be added manually to update_modbus_registers.py/string_sizes') + '" does not have a size - string sizes need to be added manually to update_modbus_registers.py/string_sizes[]') # if entity["modbus count"] == "0": # print("ignoring " + entity_dev_name + " - it has a register length of zero") diff --git a/src/core/device_library.h b/src/core/device_library.h index fa10d6e1d..7d6784676 100644 --- a/src/core/device_library.h +++ b/src/core/device_library.h @@ -203,7 +203,7 @@ {163, DeviceType::WATER, "SM100, MS100", DeviceFlags::EMS_DEVICE_FLAG_SM100}, {164, DeviceType::WATER, "SM200, MS200", DeviceFlags::EMS_DEVICE_FLAG_SM100}, {248, DeviceType::MIXER, "HM210", DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, -{17, DeviceType::CONNECT, "MX400", DeviceFlags::EMS_DEVICE_FLAG_NONE} // 0x50 Wirelss Base +{17, DeviceType::CONNECT, "MX400", DeviceFlags::EMS_DEVICE_FLAG_NONE} // 0x50 Wireless Base // {157, DeviceType::THERMOSTAT, "RC120", DeviceFlags::EMS_DEVICE_FLAG_CR120} #endif diff --git a/src/core/emsesp.cpp b/src/core/emsesp.cpp index 8d69ea6bb..4ee114cea 100644 --- a/src/core/emsesp.cpp +++ b/src/core/emsesp.cpp @@ -29,22 +29,22 @@ static_assert(uuid::console::thread_safe, "uuid-console must be thread-safe"); namespace emsesp { // Static member definitions -std::deque> EMSESP::emsdevices{}; -std::vector EMSESP::device_library_; -uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN}; -uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; -uint8_t EMSESP::watch_ = 0; -uint16_t EMSESP::read_id_ = WATCH_ID_NONE; -bool EMSESP::read_next_ = false; -uint16_t EMSESP::publish_id_ = 0; -uint16_t EMSESP::response_id_ = 0; -bool EMSESP::tap_water_active_ = false; -uint8_t EMSESP::publish_all_idx_ = 0; -uint8_t EMSESP::unique_id_count_ = 0; -bool EMSESP::trace_raw_ = false; -uint16_t EMSESP::wait_validate_ = 0; -bool EMSESP::wait_km_ = false; -uint32_t EMSESP::last_fetch_ = 0; +std::vector> EMSESP::emsdevices{}; +std::vector EMSESP::device_library_; +uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN}; +uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; +uint8_t EMSESP::watch_ = 0; +uint16_t EMSESP::read_id_ = WATCH_ID_NONE; +bool EMSESP::read_next_ = false; +uint16_t EMSESP::publish_id_ = 0; +uint16_t EMSESP::response_id_ = 0; +bool EMSESP::tap_water_active_ = false; +uint8_t EMSESP::publish_all_idx_ = 0; +uint8_t EMSESP::unique_id_count_ = 0; +bool EMSESP::trace_raw_ = false; +uint16_t EMSESP::wait_validate_ = 0; +bool EMSESP::wait_km_ = false; +uint32_t EMSESP::last_fetch_ = 0; AsyncWebServer webServer(80); diff --git a/src/core/emsesp.h b/src/core/emsesp.h index f1a952b53..2821eecc6 100644 --- a/src/core/emsesp.h +++ b/src/core/emsesp.h @@ -222,7 +222,7 @@ class EMSESP { static void scan_devices(); static void clear_all_devices(); - static std::deque> emsdevices; + static std::vector> emsdevices; // services static Mqtt mqtt_; diff --git a/src/core/helpers.cpp b/src/core/helpers.cpp index 9080c21f4..119323d95 100644 --- a/src/core/helpers.cpp +++ b/src/core/helpers.cpp @@ -787,8 +787,8 @@ std::string Helpers::toUpper(std::string const & s) { } // capitalizes one UTF-8 character in char array -// works with Latin1 (1 byte), Polish amd some other (2 bytes) characters -// TODO add special characters that occur in other supported languages +// works with Latin1 (1 byte), Polish and other (2 bytes) characters +// supports special characters for all 11 supported languages: EN, DE, NL, SV, PL, NO, FR, TR, IT, SK, CZ #if defined(EMSESP_STANDALONE) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" @@ -803,23 +803,77 @@ void Helpers::CharToUpperUTF8(char * c) { if ((p_v >= (char)0xA0) && (p_v <= (char)0xBE)) { *p -= 0x20; } + // Additional special characters for supported languages + switch (p_v) { + case (char)0xA0: // à -> À + case (char)0xA1: // á -> Á + case (char)0xA2: // â -> Â + case (char)0xA3: // ã -> Ã + case (char)0xA4: // ä -> Ä (German, Swedish) + case (char)0xA5: // å -> Å (Swedish, Norwegian) + case (char)0xA6: // æ -> Æ (Norwegian) + case (char)0xA7: // ç -> Ç (French, Turkish) + case (char)0xA8: // è -> È (French, Italian) + case (char)0xA9: // é -> É (French, Italian) + case (char)0xAA: // ê -> Ê (French) + case (char)0xAB: // ë -> Ë (French) + case (char)0xAC: // ì -> Ì (Italian) + case (char)0xAD: // í -> Í (Slovak, Czech) + case (char)0xAE: // î -> Î (French) + case (char)0xAF: // ï -> Ï (French) + case (char)0xB0: // ð -> Ð (Icelandic) + case (char)0xB1: // ñ -> Ñ (Spanish) + case (char)0xB2: // ò -> Ò (Italian) + case (char)0xB3: // ó -> Ó (Slovak, Czech) + case (char)0xB4: // ô -> Ô (French, Slovak) + case (char)0xB5: // õ -> Õ (Portuguese) + case (char)0xB6: // ö -> Ö (German, Swedish, Turkish) + case (char)0xB8: // ø -> Ø (Norwegian) + case (char)0xB9: // ù -> Ù (French, Italian) + case (char)0xBA: // ú -> Ú (Slovak, Czech) + case (char)0xBB: // û -> Û (French) + case (char)0xBC: // ü -> Ü (German, French, Turkish) + case (char)0xBD: // ý -> Ý (Slovak, Czech) + case (char)0xBE: // þ -> Þ (Icelandic) + case (char)0xBF: // ÿ -> Ÿ (French) + *p -= 0x20; + break; + } break; case (char)0xC4: switch (p_v) { - case (char)0x85: //ą (0xC4,0x85) -> Ą (0xC4,0x84) - case (char)0x87: //ć (0xC4,0x87) -> Ć (0xC4,0x86) - case (char)0x99: //ę (0xC4,0x99) -> Ę (0xC4,0x98) + case (char)0x85: //ą (0xC4,0x85) -> Ą (0xC4,0x84) (Polish) + case (char)0x87: //ć (0xC4,0x87) -> Ć (0xC4,0x86) (Polish) + case (char)0x8D: //č (0xC4,0x8D) -> Č (0xC4,0x8C) (Slovak, Czech) + case (char)0x8F: //ď (0xC4,0x8F) -> Ď (0xC4,0x8E) (Slovak, Czech) + case (char)0x9F: //ğ (0xC4,0x9F) -> Ğ (0xC4,0x9E) (Turkish) + case (char)0x99: //ę (0xC4,0x99) -> Ę (0xC4,0x98) (Polish) + case (char)0x9B: //ě (0xC4,0x9B) -> Ě (0xC4,0x9A) (Czech) + case (char)0xAF: //ı (0xC4,0xAF) -> I (0xC4,0xAE) (Turkish) + case (char)0xB1: //ı (0xC4,0xB1) -> I (0xC4,0xB0) (Turkish) + case (char)0xB3: //ij (0xC4,0xB3) -> IJ (0xC4,0xB2) (Dutch) *p -= 1; break; } break; case (char)0xC5: switch (p_v) { - case (char)0x82: //ł (0xC5,0x82) -> Ł (0xC5,0x81) - case (char)0x84: //ń (0xC5,0x84) -> Ń (0xC5,0x83) - case (char)0x9B: //ś (0xC5,0x9B) -> Ś (0xC5,0x9A) - case (char)0xBA: //ź (0xC5,0xBA) -> Ź (0xC5,0xB9) - case (char)0xBC: //ż (0xC5,0xBC) -> Ż (0xC5,0xBB) + case (char)0x81: //ł (0xC5,0x81) -> Ł (0xC5,0x80) (Polish) + case (char)0x82: //ł (0xC5,0x82) -> Ł (0xC5,0x81) (Polish) + case (char)0x83: //ń (0xC5,0x83) -> Ń (0xC5,0x82) (Polish) + case (char)0x84: //ń (0xC5,0x84) -> Ń (0xC5,0x83) (Polish) + case (char)0x88: //ň (0xC5,0x88) -> Ň (0xC5,0x87) (Slovak, Czech) + case (char)0x95: //ŕ (0xC5,0x95) -> Ŕ (0xC5,0x94) (Slovak) + case (char)0x99: //ř (0xC5,0x99) -> Ř (0xC5,0x98) (Czech) + case (char)0x9A: //ś (0xC5,0x9A) -> Ś (0xC5,0x99) (Polish) + case (char)0x9B: //ś (0xC5,0x9B) -> Ś (0xC5,0x9A) (Polish) + case (char)0x9F: //ş (0xC5,0x9F) -> Ş (0xC5,0x9E) (Turkish) + case (char)0xA1: //š (0xC5,0xA1) -> Š (0xC5,0xA0) (Slovak, Czech) + case (char)0xA5: //ť (0xC5,0xA5) -> Ť (0xC5,0xA4) (Slovak, Czech) + case (char)0xAF: //ů (0xC5,0xAF) -> Ů (0xC5,0xAE) (Czech) + case (char)0xBA: //ź (0xC5,0xBA) -> Ź (0xC5,0xB9) (Polish) + case (char)0xBC: //ż (0xC5,0xBC) -> Ż (0xC5,0xBB) (Polish) + case (char)0xBE: //ž (0xC5,0xBE) -> Ž (0xC5,0xBD) (Slovak, Czech) *p -= 1; break; } diff --git a/src/core/modbus_entity_parameters.hpp b/src/core/modbus_entity_parameters.hpp index 271113144..d3e83447f 100644 --- a/src/core/modbus_entity_parameters.hpp +++ b/src/core/modbus_entity_parameters.hpp @@ -378,27 +378,28 @@ const std::initializer_list Modbus::modbus_register_ma REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(roomsensor), 199, 1), // roomsensor REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_HC, FL_(heatup), 200, 1), // heatup REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(mode), 0, 1), // mode - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwSetTemp), 1, 1), // settemp - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwSetTempLow), 2, 1), // settemplow - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwCircMode), 3, 1), // circmode - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwChargeDuration), 4, 1), // chargeduration - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwCharge), 5, 1), // charge - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwExtra), 6, 1), // extra - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfecting), 7, 1), // disinfecting - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfectDay), 8, 1), // disinfectday - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfectTime), 9, 1), // disinfecttime - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDailyHeating), 10, 1), // dailyheating - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDailyHeatTime), 11, 1), // dailyheattime - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwProgMode), 12, 1), // progmode - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwCircProg), 13, 1), // circprog - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfectHour), 14, 1), // disinfecthour - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwMaxTemp), 15, 1), // maxtemp - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwOneTimeKey), 16, 1), // onetimekey - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(switchtime), 17, 8), // switchtime - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwcircswitchtime), 25, 8), // circswitchtime - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(holidays), 33, 13), // holidays - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(vacations), 46, 13), // vacations - REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwWhenModeOff), 59, 1), // whenmodeoff + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(modetype), 1, 1), // modetype + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwSetTemp), 2, 1), // settemp + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwSetTempLow), 3, 1), // settemplow + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwCircMode), 4, 1), // circmode + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwChargeDuration), 5, 1), // chargeduration + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwCharge), 6, 1), // charge + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwExtra), 7, 1), // extra + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfecting), 8, 1), // disinfecting + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfectDay), 9, 1), // disinfectday + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfectTime), 10, 1), // disinfecttime + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDailyHeating), 11, 1), // dailyheating + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDailyHeatTime), 12, 1), // dailyheattime + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwProgMode), 13, 1), // progmode + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwCircProg), 14, 1), // circprog + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwDisinfectHour), 15, 1), // disinfecthour + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwMaxTemp), 16, 1), // maxtemp + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwOneTimeKey), 17, 1), // onetimekey + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(switchtime), 18, 8), // switchtime + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwcircswitchtime), 26, 8), // circswitchtime + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(holidays), 34, 13), // holidays + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(vacations), 47, 13), // vacations + REGISTER_MAPPING(dt::THERMOSTAT, TAG_TYPE_DHW, FL_(wwWhenModeOff), 60, 1), // whenmodeoff REGISTER_MAPPING(dt::MIXER, TAG_TYPE_HC, FL_(flowTempHc), 0, 1), // flowtemphc REGISTER_MAPPING(dt::MIXER, TAG_TYPE_HC, FL_(valveStatus), 1, 1), // valvestatus REGISTER_MAPPING(dt::MIXER, TAG_TYPE_HC, FL_(flowSetTemp), 2, 1), // flowsettemp @@ -523,6 +524,8 @@ const std::initializer_list Modbus::modbus_register_ma REGISTER_MAPPING(dt::SWITCH, TAG_TYPE_DEVICE_DATA, FL_(flowTempHc), 1, 1), // flowtemphc REGISTER_MAPPING(dt::SWITCH, TAG_TYPE_DEVICE_DATA, FL_(status), 2, 1), // status REGISTER_MAPPING(dt::CONTROLLER, TAG_TYPE_DEVICE_DATA, FL_(dateTime), 0, 13), // datetime + REGISTER_MAPPING(dt::CONNECT, TAG_TYPE_DEVICE_DATA, FL_(dateTime), 0, 13), // datetime + REGISTER_MAPPING(dt::CONNECT, TAG_TYPE_DEVICE_DATA, FL_(outdoorTemp), 13, 1), // outdoortemp REGISTER_MAPPING(dt::ALERT, TAG_TYPE_DEVICE_DATA, FL_(setFlowTemp), 0, 1), // setflowtemp REGISTER_MAPPING(dt::ALERT, TAG_TYPE_DEVICE_DATA, FL_(setBurnPow), 1, 1), // setburnpow REGISTER_MAPPING(dt::EXTENSION, TAG_TYPE_DEVICE_DATA, FL_(flowTempVf), 0, 1), // flowtempvf diff --git a/src/core/shower.cpp b/src/core/shower.cpp index 372337311..c9e0ad06d 100644 --- a/src/core/shower.cpp +++ b/src/core/shower.cpp @@ -151,7 +151,7 @@ void Shower::loop() { // turn off hot water to send a shot of cold void Shower::shower_alert_start() { LOG_DEBUG("Shower Alert started"); - (void)Command::call(EMSdevice::DeviceType::BOILER, "tapactivated", "false", 9); + (void)Command::call(EMSdevice::DeviceType::BOILER, "tapactivated", "false", DeviceValueTAG::TAG_DHW1); doing_cold_shot_ = true; force_coldshot = false; alert_timer_start_ = uuid::get_uptime_sec(); // timer starts now @@ -161,7 +161,7 @@ void Shower::shower_alert_start() { void Shower::shower_alert_stop() { if (doing_cold_shot_) { LOG_DEBUG("Shower Alert stopped"); - (void)Command::call(EMSdevice::DeviceType::BOILER, "tapactivated", "true", 9); + (void)Command::call(EMSdevice::DeviceType::BOILER, "tapactivated", "true", DeviceValueTAG::TAG_DHW1); doing_cold_shot_ = false; force_coldshot = false; next_alert_ += shower_alert_trigger_; diff --git a/src/core/shuntingYard.cpp b/src/core/shuntingYard.cpp index 5cb262f50..12ef69d41 100644 --- a/src/core/shuntingYard.cpp +++ b/src/core/shuntingYard.cpp @@ -22,7 +22,7 @@ #include "shuntingYard.h" -// find tokens +// find tokens - optimized to reduce string allocations std::deque exprToTokens(const std::string & expr) { std::deque tokens; @@ -40,13 +40,14 @@ std::deque exprToTokens(const std::string & expr) { if (*p) { ++p; } - auto s = std::string(b, p); - auto n = s.find("\"\""); - while (n != std::string::npos) { - s.erase(n, 2); + // Use string_view to avoid unnecessary string copies + std::string_view s(b, p - b); + auto n = s.find("\"\""); + while (n != std::string_view::npos) { + s.remove_prefix(n + 2); n = s.find("\"\""); } - tokens.emplace_back(Token::Type::String, s, -3); + tokens.emplace_back(Token::Type::String, std::string(s), -3); if (*p == '\0') { --p; } @@ -225,11 +226,14 @@ std::deque exprToTokens(const std::string & expr) { return tokens; } -// sort tokens to RPN form +// sort tokens to RPN form - optimized for memory usage std::deque shuntingYard(const std::deque & tokens) { std::deque queue; std::vector stack; + // Reserve space for vector to reduce reallocations + stack.reserve(tokens.size() / 2); + // While there are tokens to be read: for (auto const & token : tokens) { // Read a token diff --git a/src/devices/connect.cpp b/src/devices/connect.cpp index 508088a84..3f234b40b 100644 --- a/src/devices/connect.cpp +++ b/src/devices/connect.cpp @@ -135,7 +135,7 @@ void Connect::process_roomThermostat(std::shared_ptr telegram) { has_update(rc->dewtemp_, dt); } -// gateway(0x48) W gateway(0x50), ?(0x0B42), data: 01 // icon in ofset 0 +// gateway(0x48) W gateway(0x50), ?(0x0B42), data: 01 // icon in offset 0 // gateway(0x48) W gateway(0x50), ?(0x0B42), data: 00 4B 00 FC 00 63 00 68 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (offset 1) void Connect::process_roomThermostatName(std::shared_ptr telegram) { auto rc = room_circuit(telegram->type_id - 0xB3D); diff --git a/src/emsesp_version.h b/src/emsesp_version.h index d234ddd9b..3fc4959ec 100644 --- a/src/emsesp_version.h +++ b/src/emsesp_version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.7.3-dev.20" +#define EMSESP_APP_VERSION "3.7.3-dev.21" diff --git a/src/test/test.cpp b/src/test/test.cpp index 59927e424..a2764901d 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -263,8 +263,8 @@ bool Test::test(const std::string & cmd, int8_t id1, int8_t id2) { if (cmd == "src") { EMSESP::logger().notice("Adding SRC plus thermostat..."); - add_device(0x50, 17); // MX400 module - uart_telegram("50 00 FF 00 0A DD 00 E6 36 2A"); // monitor, temperatures + add_device(0x50, 17); // MX400 module + uart_telegram("50 00 FF 00 0A DD 00 E6 36 2A"); // monitor, temperatures uart_telegram("50 00 FF 00 0A B5 00 FF 00 24 01 FF 24 00"); // mode, childlock // switchprogram uart_telegram("50 00 FF 00 0A 65 2A 00 3C 2A FF FF 2A FF FF 2A FF FF 2A FF FF 2A FF FF"); @@ -764,7 +764,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const test("boiler"); // device type, command, data - Command::call(EMSdevice::DeviceType::BOILER, "tapactivated", "false", 9); + Command::call(EMSdevice::DeviceType::BOILER, "tapactivated", "false", DeviceValueTAG::TAG_DHW1); ok = true; } diff --git a/test/test_api/test_api.cpp b/test/test_api/test_api.cpp index f4ddefd83..acc4563c5 100644 --- a/test/test_api/test_api.cpp +++ b/test/test_api/test_api.cpp @@ -318,7 +318,7 @@ const char * run_console_command(const char * command) { } void console_test1() { - auto expected_response = "Log level: DEBUG\n"; + auto expected_response = "Log level: DEBUG"; TEST_ASSERT_EQUAL_STRING(expected_response, run_console_command("log")); } @@ -329,7 +329,7 @@ void console_test2() { void console_test3() { // test bad command - auto expected_response = "Bad syntax. Check arguments.\n"; + auto expected_response = "Bad syntax. Check arguments."; TEST_ASSERT_EQUAL_STRING(expected_response, run_console_command("call thermostat mode bad")); }