diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml new file mode 100644 index 000000000..003969d14 --- /dev/null +++ b/.github/workflows/pr_check.yml @@ -0,0 +1,37 @@ +name: 'pr_check' + +on: + workflow_dispatch: + pull_request: + branches: dev + paths: + - '**.c' + - '**.cpp' + - '**.h' + - '**.hpp' + - '**.json' + - '**.py' + - '**.md' + - '.github/workflows/pr_check.yml' + +jobs: + pre-release: + name: 'Automatic pre-release build' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install PlatformIO + run: | + pip install wheel + pip install -U platformio + + - name: Build native + run: | + platformio run -e native diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 235d65aeb..891f62694 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -10,12 +10,13 @@ For more details go to [www.emsesp.org](https://www.emsesp.org/). - include HA "unit_of_meas", "stat_cla" and "dev_cla" attributes for Number sensors [#2149](https://github.com/emsesp/EMS-ESP32/issues/2149) - Bosch CS6800i AW - Silent Mode + Electrical Power Reduction (HP) [#2147](https://github.com/emsesp/EMS-ESP32/issues/2147) +- /api/system/showeralert and /api/system/showertimer [#2182](https://github.com/emsesp/EMS-ESP32/issues/2182) ## Fixed - Modbus integration in 3.7.0 missing offset [#2148](https://github.com/emsesp/EMS-ESP32/issues/2148) - fix changing TZ in NTPsettings without clearing enable+server, added DST support [#2142](https://github.com/emsesp/EMS-ESP32/issues/2142) +- Support MQTT Discovery (AD) with Domoticz [#2177](https://github.com/emsesp/EMS-ESP32/issues/2177) +- wwExtra (dhw extra) changed from temperature reading to number ## Changed - -- MQTT discovery template to support Domoticz [#2138](https://github.com/emsesp/EMS-ESP32/discussions/2138) diff --git a/Makefile b/Makefile index 27669c141..8f6fc27a0 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ DEFINES += -DARDUINOJSON_ENABLE -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSO DEFINES += -DEMSESP_STANDALONE -DEMSESP_TEST -DEMSESP_DEBUG -DEMC_RX_BUFFER_SIZE=1500 DEFINES += $(ARGS) -DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.0-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" +DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.1-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S3\" #---------------------------------------------------------------------- # Sources & Files diff --git a/README.md b/README.md index 0085b5232..e4f09ff2d 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,6 @@ For a live demo go to [demo.emsesp.org](https://demo.emsesp.org). Pick a languag EMS-ESP is a project created by [proddy](https://github.com/proddy) and owned and maintained by both [proddy](https://github.com/proddy) and [MichaelDvP](https://github.com/MichaelDvP) with support from [BBQKees Electronics](https://bbqkees-electronics.nl). -You can contact us using [this form](https://emsesp.org/Contact/). - If you like **EMS-ESP**, please give it a ✨ on GitHub, or even better fork it and contribute. You can also offer a small donation. This is an open-source project maintained by volunteers, and your support is greatly appreciated. ## **Libraries used** diff --git a/docs/Modbus-Entity-Registers.md b/docs/Modbus-Entity-Registers.md index 55e513ce1..c416151dd 100644 --- a/docs/Modbus-Entity-Registers.md +++ b/docs/Modbus-Entity-Registers.md @@ -4779,7 +4779,7 @@ | circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 3 | 1 | 1 | | chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 4 | 1 | 15 | | charge | charge | boolean | | true | DHW | 5 | 1 | 1 | -| extra | extra | uint8 (>=0<=254) | C | false | DHW | 6 | 1 | 1 | +| extra | extra | uint8 (>=0<=254) | | false | DHW | 6 | 1 | 1 | | disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | | disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | | disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 9 | 1 | 15 | @@ -4895,7 +4895,7 @@ | circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 3 | 1 | 1 | | chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 4 | 1 | 15 | | charge | charge | boolean | | true | DHW | 5 | 1 | 1 | -| extra | extra | uint8 (>=0<=254) | C | false | DHW | 6 | 1 | 1 | +| extra | extra | uint8 (>=0<=254) | | false | DHW | 6 | 1 | 1 | | disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | | disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | | disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 9 | 1 | 15 | @@ -5023,7 +5023,7 @@ | circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 3 | 1 | 1 | | chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 4 | 1 | 15 | | charge | charge | boolean | | true | DHW | 5 | 1 | 1 | -| extra | extra | uint8 (>=0<=254) | C | false | DHW | 6 | 1 | 1 | +| extra | extra | uint8 (>=0<=254) | | false | DHW | 6 | 1 | 1 | | disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | | disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | | disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 9 | 1 | 15 | @@ -5267,7 +5267,7 @@ | circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 3 | 1 | 1 | | chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 4 | 1 | 15 | | charge | charge | boolean | | true | DHW | 5 | 1 | 1 | -| extra | extra | uint8 (>=0<=254) | C | false | DHW | 6 | 1 | 1 | +| extra | extra | uint8 (>=0<=254) | | false | DHW | 6 | 1 | 1 | | disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | | disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | | disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 9 | 1 | 15 | @@ -5355,7 +5355,7 @@ | circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 3 | 1 | 1 | | chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 4 | 1 | 15 | | charge | charge | boolean | | true | DHW | 5 | 1 | 1 | -| extra | extra | uint8 (>=0<=254) | C | false | DHW | 6 | 1 | 1 | +| extra | extra | uint8 (>=0<=254) | | false | DHW | 6 | 1 | 1 | | disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | | disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | | disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 9 | 1 | 15 | @@ -5453,7 +5453,7 @@ | circmode | circulation pump mode | enum [off\|on\|auto\|own prog] | | true | DHW | 3 | 1 | 1 | | chargeduration | charge duration | uint8 (>=0<=3810) | minutes | true | DHW | 4 | 1 | 15 | | charge | charge | boolean | | true | DHW | 5 | 1 | 1 | -| extra | extra | uint8 (>=0<=254) | C | false | DHW | 6 | 1 | 1 | +| extra | extra | uint8 (>=0<=254) | | false | DHW | 6 | 1 | 1 | | disinfecting | disinfecting | boolean | | true | DHW | 7 | 1 | 1 | | disinfectday | disinfection day | enum [mo\|tu\|we\|th\|fr\|sa\|su\|all] | | true | DHW | 8 | 1 | 1 | | disinfecttime | disinfection time | uint8 (>=0<=1431) | minutes | true | DHW | 9 | 1 | 15 | diff --git a/docs/dump_entities.csv b/docs/dump_entities.csv index fcf0a5fe5..10ec99089 100644 --- a/docs/dump_entities.csv +++ b/docs/dump_entities.csv @@ -3438,7 +3438,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "UI800, BC400",thermostat,4,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,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,charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 -"UI800, BC400",thermostat,4,extra,extra,uint8 (>=0<=254),C,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 +"UI800, BC400",thermostat,4,extra,extra,uint8 (>=0<=254), ,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 "UI800, BC400",thermostat,4,disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 "UI800, BC400",thermostat,4,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,disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,9,1 @@ -3807,7 +3807,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "RC200, CW100, CR120, CR50",thermostat,157,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,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,charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 -"RC200, CW100, CR120, CR50",thermostat,157,extra,extra,uint8 (>=0<=254),C,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 +"RC200, CW100, CR120, CR50",thermostat,157,extra,extra,uint8 (>=0<=254), ,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 "RC200, CW100, CR120, CR50",thermostat,157,disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 "RC200, CW100, CR120, CR50",thermostat,157,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,disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,9,1 @@ -3891,7 +3891,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,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,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,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,extra,extra,uint8 (>=0<=254),C,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 +"RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,extra,extra,uint8 (>=0<=254), ,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 "RC3*0, Moduline 3000/1010H, CW400, Sense II, HPC410",thermostat,158,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,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,disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,9,1 @@ -3975,7 +3975,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "RC100, CR10, Moduline 1000/1010",thermostat,165,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 "RC100, CR10, Moduline 1000/1010",thermostat,165,chargeduration,charge duration,uint8 (>=0<=3810),minutes,true,number.thermostat_dhw_charge_duration,number.thermostat_dhw_chargeduration,6,9,15,4,1 "RC100, CR10, Moduline 1000/1010",thermostat,165,charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 -"RC100, CR10, Moduline 1000/1010",thermostat,165,extra,extra,uint8 (>=0<=254),C,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 +"RC100, CR10, Moduline 1000/1010",thermostat,165,extra,extra,uint8 (>=0<=254), ,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 "RC100, CR10, Moduline 1000/1010",thermostat,165,disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 "RC100, CR10, Moduline 1000/1010",thermostat,165,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 "RC100, CR10, Moduline 1000/1010",thermostat,165,disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,9,1 @@ -4060,7 +4060,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "Rego 2000/3000",thermostat,172,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,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,charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 -"Rego 2000/3000",thermostat,172,extra,extra,uint8 (>=0<=254),C,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 +"Rego 2000/3000",thermostat,172,extra,extra,uint8 (>=0<=254), ,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 "Rego 2000/3000",thermostat,172,disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 "Rego 2000/3000",thermostat,172,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,disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,9,1 @@ -4171,7 +4171,7 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/ "Rego 3000, UI800, Logamatic BC400",thermostat,253,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,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,charge,charge,boolean, ,true,switch.thermostat_dhw_charge,switch.thermostat_dhw_charge,6,9,1,5,1 -"Rego 3000, UI800, Logamatic BC400",thermostat,253,extra,extra,uint8 (>=0<=254),C,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 +"Rego 3000, UI800, Logamatic BC400",thermostat,253,extra,extra,uint8 (>=0<=254), ,false,sensor.thermostat_dhw_extra,sensor.thermostat_dhw_extra,6,9,1,6,1 "Rego 3000, UI800, Logamatic BC400",thermostat,253,disinfecting,disinfecting,boolean, ,true,switch.thermostat_dhw_disinfecting,switch.thermostat_dhw_disinfecting,6,9,1,7,1 "Rego 3000, UI800, Logamatic BC400",thermostat,253,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,disinfecttime,disinfection time,uint8 (>=0<=1431),minutes,true,number.thermostat_dhw_disinfection_time,number.thermostat_dhw_disinfecttime,6,9,15,9,1 diff --git a/interface/package.json b/interface/package.json index 176eb165f..0108eaa32 100644 --- a/interface/package.json +++ b/interface/package.json @@ -27,7 +27,7 @@ "@mui/icons-material": "^6.1.6", "@mui/material": "^6.1.6", "@table-library/react-table-library": "4.1.7", - "alova": "3.2.1", + "alova": "3.2.2", "async-validator": "^4.2.5", "jwt-decode": "^4.0.0", "mime-types": "^2.1.35", @@ -47,21 +47,21 @@ "@preact/preset-vite": "^2.9.1", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/formidable": "^3", - "@types/node": "^22.8.7", + "@types/node": "^22.9.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@types/react-router-dom": "^5.3.3", - "concurrently": "^9.0.1", + "concurrently": "^9.1.0", "eslint": "^9.14.0", "eslint-config-prettier": "^9.1.0", "formidable": "^3.5.2", "prettier": "^3.3.3", "rollup-plugin-visualizer": "^5.12.0", "terser": "^5.36.0", - "typescript-eslint": "8.12.2", + "typescript-eslint": "8.13.0", "vite": "^5.4.10", "vite-plugin-imagemin": "^0.6.1", - "vite-tsconfig-paths": "^5.0.1" + "vite-tsconfig-paths": "^5.1.0" }, "packageManager": "yarn@4.5.1" } diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index eb89f925b..52030b152 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -107,7 +107,7 @@ const Dashboard = () => { }, &:hover .td { background-color: #177ac9; - } + }, `, BaseCell: ` &:nth-of-type(2) { @@ -185,18 +185,16 @@ const Dashboard = () => { // if its a device (parent node) and has entities if (di.nodes?.length) { return ( - <> - - -   {showType(di.n, di.t)} - + + +   {showType(di.n, di.t)}  ({di.nodes?.length}) - + ); } } if (di.dv) { - return {di.dv.id.slice(2)}; + return {di.dv.id.slice(2)}; } }; @@ -250,10 +248,10 @@ const Dashboard = () => { onChange={handleShowAll} > - + - + @@ -304,9 +302,7 @@ const Dashboard = () => { title={formatValue(LL, di.dv?.v, di.dv?.u)} arrow > - - {formatValue(LL, di.dv?.v, di.dv?.u)} - + {formatValue(LL, di.dv?.v, di.dv?.u)} @@ -314,10 +310,7 @@ const Dashboard = () => { {me.admin && di.dv?.c && !hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && ( - editDashboardValue(di)} - > + editDashboardValue(di)}> { } &.tr.tr-body.row-select.row-select-single-selected { background-color: #177ac9; - font-weight: normal; } ` }); @@ -169,11 +168,11 @@ const Devices = () => { HeaderRow: ` .th { padding: 8px; - height: 36px; `, Row: ` + font-weight: bold; &:hover .td { - background-color: #177ac9; + background-color: #177ac9; ` } ]); @@ -216,7 +215,7 @@ const Devices = () => { background-color: #303030; }, &:hover .td { - background-color: #177ac9; + background-color: #177ac9; } ` } @@ -523,7 +522,7 @@ const Devices = () => { @@ -574,7 +573,9 @@ const Devices = () => { const deviceValueDialogClose = () => { setDeviceValueDialogOpen(false); - void sendDeviceData(selectedDevice); + if (selectedDevice !== undefined) { + void sendDeviceData(selectedDevice); + } }; const renderDeviceData = () => { diff --git a/interface/src/app/status/SystemLog.tsx b/interface/src/app/status/SystemLog.tsx index b644b48f5..35b11dbf7 100644 --- a/interface/src/app/status/SystemLog.tsx +++ b/interface/src/app/status/SystemLog.tsx @@ -261,16 +261,6 @@ const SystemLog = () => { > {LL.EXPORT()} - {dirtyFlags && dirtyFlags.length !== 0 && ( - - )} {readOpen ? ( @@ -315,6 +305,19 @@ const SystemLog = () => { )} )} + + {dirtyFlags && dirtyFlags.length !== 0 && ( + + + + )} description_); - char info_s[100]; - if (strlen(description)) { - snprintf(info_s, sizeof(info_s), "%s/%s (%s)", dname, cmd, description); - } else { - snprintf(info_s, sizeof(info_s), "%s/%s", dname, cmd); - } - // call the function based on command function type // commands return true or false only (bool) uint8_t return_code = CommandRet::OK; @@ -406,7 +395,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha } } - // report back. If not OK show output from error, otherwise return the HTTP code + // report back. If not OK show output from error, otherwise return the error code if (return_code != CommandRet::OK) { char error[100]; if (single_command) { @@ -418,6 +407,16 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha output["message"] = error; LOG_WARNING(error); } else { + // build up the log string for reporting back + // We send the log message as Warning so it appears in the log (debug is only enabled when compiling with DEBUG) + std::string ro = EMSESP::system_.readonly_mode() ? "[readonly] " : ""; + auto description = Helpers::translated_word(cf->description_); + char info_s[100]; + if (strlen(description)) { + snprintf(info_s, sizeof(info_s), "%s/%s (%s)", dname, cmd, description); + } else { + snprintf(info_s, sizeof(info_s), "%s/%s", dname, cmd); + } if (single_command) { // log as DEBUG (TRACE) regardless if compiled with EMSESP_DEBUG logger_.debug("%sCalled command %s", ro.c_str(), info_s); diff --git a/src/common.h b/src/common.h index 696fcc644..608527620 100644 --- a/src/common.h +++ b/src/common.h @@ -52,11 +52,16 @@ using string_vector = std::vector; #define F_(string_name) (__pstr__##string_name) #define FL_(list_name) (__pstr__L_##list_name) -#if defined(EMSESP_TEST) || defined(EMSESP_EN_ONLY) -// In testing just take one language (en) to save on Flash space +#if defined(EMSESP_TEST) +// in Test mode use two languages (en & de) to save flash memory needed for the tests +#define MAKE_WORD_TRANSLATION(list_name, en, de, ...) static const char * const __pstr__L_##list_name[] = {de, nullptr}; +#define MAKE_TRANSLATION(list_name, shortname, en, de, ...) static const char * const __pstr__L_##list_name[] = {shortname, de, nullptr}; +#elif defined(EMSESP_EN_ONLY) +// EN only #define MAKE_WORD_TRANSLATION(list_name, en, ...) static const char * const __pstr__L_##list_name[] = {en, nullptr}; #define MAKE_TRANSLATION(list_name, shortname, en, ...) static const char * const __pstr__L_##list_name[] = {shortname, en, nullptr}; #elif defined(EMSESP_DE_ONLY) +// EN + DE #define MAKE_WORD_TRANSLATION(list_name, en, de, ...) static const char * const __pstr__L_##list_name[] = {de, nullptr}; #define MAKE_TRANSLATION(list_name, shortname, en, de, ...) static const char * const __pstr__L_##list_name[] = {shortname, de, nullptr}; #else diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 7e93f60f2..ca05d4b84 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -4912,7 +4912,7 @@ void Thermostat::register_device_values_dhw(std::shared_ptrwwCharge_, DeviceValueType::BOOL, FL_(wwCharge), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwcharge)); - register_device_value(tag, &dhw->wwExtra_, DeviceValueType::UINT8, FL_(wwExtra), DeviceValueUOM::DEGREES); + register_device_value(tag, &dhw->wwExtra_, DeviceValueType::BOOL, FL_(wwExtra), DeviceValueUOM::NONE); register_device_value(tag, &dhw->wwDisinfecting_, DeviceValueType::BOOL, FL_(wwDisinfecting), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfect)); register_device_value( tag, &dhw->wwDisinfectDay_, DeviceValueType::ENUM, FL_(enum_dayOfWeek), FL_(wwDisinfectDay), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwDisinfectDay)); diff --git a/src/emsesp.cpp b/src/emsesp.cpp index a1c69ac4a..b2c826353 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -910,14 +910,14 @@ std::string EMSESP::pretty_telegram(std::shared_ptr telegram) { std::string str; str.reserve(200); if (telegram->operation == Telegram::Operation::RX_READ) { - str = src_name + "(" + Helpers::hextoa(src) + ") -R-> " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "(" + str = src_name + "(" + Helpers::hextoa(src) + ") -> " + dest_name + "(" + Helpers::hextoa(dest) + "), R, " + type_name + "(" + Helpers::hextoa(telegram->type_id) + "), length: " + Helpers::itoa(telegram->message_data[0]) + ((telegram->message_length > 1) ? ", data: " + Helpers::data_to_hex(telegram->message_data + 1, telegram->message_length - 1) : ""); } else if (telegram->dest == 0) { - str = src_name + "(" + Helpers::hextoa(src) + ") -B-> " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "(" + str = src_name + "(" + Helpers::hextoa(src) + ") -> " + dest_name + "(" + Helpers::hextoa(dest) + "), B, " + type_name + "(" + Helpers::hextoa(telegram->type_id) + "), data: " + telegram->to_string_message(); } else { - str = src_name + "(" + Helpers::hextoa(src) + ") -W-> " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "(" + str = src_name + "(" + Helpers::hextoa(src) + ") -> " + dest_name + "(" + Helpers::hextoa(dest) + "), W, " + type_name + "(" + Helpers::hextoa(telegram->type_id) + "), data: " + telegram->to_string_message(); } @@ -1016,13 +1016,14 @@ void EMSESP::process_version(std::shared_ptr telegram) { // some devices store the protocol type (HT3, Buderus) in the last byte uint8_t brand; if (telegram->message_length >= 10) { - brand = EMSdevice::decode_brand(telegram->message_data[9]); // TODO should be offset + 9? + brand = EMSdevice::decode_brand(telegram->message_data[9]); } else { brand = EMSdevice::Brand::NO_BRAND; // unknown } // add it - will be overwritten if device already exists (void)add_device(device_id, product_id, version, brand); + // request the deviceName from telegram 0x01 send_read_request(EMSdevice::EMS_TYPE_NAME, device_id, 27); } diff --git a/src/helpers.cpp b/src/helpers.cpp index dcef19c3b..5429dc5f1 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -603,6 +603,10 @@ bool Helpers::value2bool(const char * value, bool & value_b) { return true; // is a bool } +#ifdef EMSESP_STANDALONE + emsesp::EMSESP::logger().debug("Error. value2bool: %s is not a boolean", value); +#endif + return false; // not a bool } @@ -764,7 +768,7 @@ uint8_t Helpers::count_items(const char * const ** list) { // if force_en is true always take the EN non-translated word const char * Helpers::translated_word(const char * const * strings, const bool force_en) { uint8_t language_index = EMSESP::system_.language_index(); - uint8_t index = 0; + uint8_t index = 0; // default en if (!strings) { return ""; // no translations @@ -774,6 +778,7 @@ const char * Helpers::translated_word(const char * const * strings, const bool f if (!force_en && (Helpers::count_items(strings) >= language_index + 1 && strlen(strings[language_index]))) { index = language_index; } + return strings[index]; } diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 86c8db03d..b6140f19c 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -934,14 +934,12 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev char config_topic[70]; snprintf(config_topic, sizeof(config_topic), "%s/%s_%s/config", mqtt_basename_.c_str(), device_name, entity_with_tag); - bool add_ha_classes = true; // default we'll add the "unit_of_meas", "stat_cla" and "dev_cla" attributes - // create the topic - // depending on the type and whether the device entity is writable (a command) + // depending on the type and whether the device entity is writable (i.e. a command) // https://developers.home-assistant.io/docs/core/entity - char topic[MQTT_TOPIC_MAX_SIZE]; - // if it's a command then we can use Number, Switch, Select or Text. Otherwise stick to Sensor + char topic[MQTT_TOPIC_MAX_SIZE] = {0}; if (has_cmd) { + // if it's a command then we can use Number, Switch, Select or Text. Otherwise stick to Sensor switch (type) { case DeviceValueType::INT8: case DeviceValueType::UINT8: @@ -950,45 +948,40 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev case DeviceValueType::UINT24: case DeviceValueType::UINT32: // number - https://www.home-assistant.io/integrations/number.mqtt - // older Domoticz does not support number, use sensor + // older Domoticz does not support number, will default to Sensor if (discovery_type() == discoveryType::HOMEASSISTANT || discovery_type() == discoveryType::DOMOTICZ_LATEST) { snprintf(topic, sizeof(topic), "number/%s", config_topic); - } else { - snprintf(topic, sizeof(topic), "sensor/%s", config_topic); } break; case DeviceValueType::BOOL: // switch - https://www.home-assistant.io/integrations/switch.mqtt snprintf(topic, sizeof(topic), "switch/%s", config_topic); - add_ha_classes = false; break; case DeviceValueType::ENUM: - snprintf(topic, sizeof(topic), "select/%s", config_topic); - add_ha_classes = false; - break; - case DeviceValueType::CMD: // hardcoded commands are always ENUMS // select - https://www.home-assistant.io/integrations/select.mqtt + snprintf(topic, sizeof(topic), "select/%s", config_topic); + break; + case DeviceValueType::CMD: if (uom == DeviceValueUOM::NONE) { - snprintf(topic, sizeof(topic), "select/%s", config_topic); + snprintf(topic, sizeof(topic), "select/%s", config_topic); // hardcoded commands are always ENUMS } else if (discovery_type() == discoveryType::HOMEASSISTANT || discovery_type() == discoveryType::DOMOTICZ_LATEST) { snprintf(topic, sizeof(topic), "number/%s", config_topic); - } else { - snprintf(topic, sizeof(topic), "sensor/%s", config_topic); } - add_ha_classes = false; break; case DeviceValueType::STRING: // text - https://www.home-assistant.io/integrations/text.mqtt - snprintf(topic, sizeof(topic), "text/%s", config_topic); // e.g. set_datetime, set_holiday, set_wwswitchtime - add_ha_classes = false; + // Domoticz does not support text, will default to Sensor + if (discovery_type() == discoveryType::HOMEASSISTANT) { + snprintf(topic, sizeof(topic), "text/%s", config_topic); // e.g. set_datetime, set_holiday, set_wwswitchtime + } break; default: - // plain old sensor - snprintf(topic, sizeof(topic), "sensor/%s", config_topic); break; } - } else { - // it is not a command and a read-only sensor. Use then either sensor or binary_sensor + } + + // if at this point we don't have a topic created yet, create a default sensor one. We always need a topic. + if (strlen(topic) == 0) { snprintf(topic, sizeof(topic), (type == DeviceValueType::BOOL) ? "binary_sensor/%s" : "sensor/%s", config_topic); // binary sensor (for booleans) } @@ -1054,21 +1047,6 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev doc["max"] = dv_set_max; snprintf(sample_val, sizeof(sample_val), "%i", dv_set_min); } - - // set icons - // since these don't have a device class we need to add the icon ourselves - switch (uom) { - case DeviceValueUOM::DEGREES: - case DeviceValueUOM::DEGREES_R: - case DeviceValueUOM::K: - doc["ic"] = F_(icondegrees); - break; - case DeviceValueUOM::PERCENT: - doc["ic"] = F_(iconpercent); - break; - default: - break; - } } // friendly name = @@ -1129,26 +1107,23 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev // Domoticz doesn't support value templates, so we just use the value directly // Also omit the uom and other state classes doc["val_tpl"] = (std::string) "{{" + val_obj + "}}"; - // add_ha_classes = false; // don't add the classes, categories of uom (dev_cla, stat_cla) } } - // Add the state class, device class and sometimes the icon. - // Used only for read-only sensors like Sensor and Binary Sensor but also Numbers - if (add_ha_classes) { - // first set the catagory for System entities - // https://github.com/emsesp/EMS-ESP32/discussions/1459#discussioncomment-7694873 - if (device_type == EMSdevice::DeviceType::SYSTEM) { - doc["ent_cat"] = "diagnostic"; - } - add_ha_uom(doc.as(), type, uom, entity); // add the UoM, device and state class + // Add the state class, device class and an optional icon based on the uom + // first set the catagory for System entities + // https://github.com/emsesp/EMS-ESP32/discussions/1459#discussioncomment-7694873 + if (device_type == EMSdevice::DeviceType::SYSTEM) { + doc["ent_cat"] = "diagnostic"; // instead of config } + add_ha_uom(doc.as(), type, uom, entity); doc["dev"] = dev_json; return queue_ha(topic, doc.as()); } +// Add the state class, device class and an optional icon based on the uom void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, const char * entity) { const char * dc_ha = "dev_cla"; // device class const char * sc_ha = "stat_cla"; // state class @@ -1169,16 +1144,19 @@ void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, con } // set state and device class + // also icon, when there is no device class that sets one switch (uom) { case DeviceValueUOM::DEGREES: case DeviceValueUOM::DEGREES_R: case DeviceValueUOM::K: doc[sc_ha] = F_(measurement); doc[dc_ha] = "temperature"; + doc["ic"] = F_(icondegrees); // icon break; case DeviceValueUOM::PERCENT: doc[sc_ha] = F_(measurement); doc[dc_ha] = "power_factor"; + doc["ic"] = F_(iconpercent); // icon break; case DeviceValueUOM::SECONDS: case DeviceValueUOM::MINUTES: @@ -1256,8 +1234,6 @@ void Mqtt::add_ha_uom(JsonObject doc, const uint8_t type, const uint8_t uom, con } bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp, const bool remove, const int16_t min, const uint32_t max) { - // TODO: check if Domoticz supports climate via MQTT discovery, otherwise exit this function if (discovery_type() != discoveryType::HOMEASSISTANT - uint8_t hc_num = tag; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; diff --git a/src/system.cpp b/src/system.cpp index 7738caf43..fdc19ad6a 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -52,12 +52,17 @@ namespace emsesp { -// Languages supported. Note: the order is important and must match locale_translations.h -#if defined(EMSESP_TEST) || defined(EMSESP_EN_ONLY) -// in Debug mode use one language (en) to save flash memory needed for the tests +// Languages supported. Note: the order is important +// and must match locale_translations.h and common.h +#if defined(EMSESP_TEST) +// in Test mode use two languages (en & de) to save flash memory needed for the tests +const char * const languages[] = {EMSESP_LOCALE_EN, EMSESP_LOCALE_DE}; +#elif defined(EMSESP_EN_ONLY) +// EN only const char * const languages[] = {EMSESP_LOCALE_EN}; #elif defined(EMSESP_DE_ONLY) -const char * const languages[] = {EMSESP_LOCALE_DE}; +// EN + DE +const char * const languages[] = {EMSESP_LOCALE_EN, EMSESP_LOCALE_DE}; #else const char * const languages[] = {EMSESP_LOCALE_EN, EMSESP_LOCALE_DE, @@ -92,7 +97,7 @@ uint8_t System::language_index() { return i; } } - return 0; // EN + return 0; // EN only } // send raw to ems diff --git a/src/temperaturesensor.cpp b/src/temperaturesensor.cpp index 658f55443..0e67cf291 100644 --- a/src/temperaturesensor.cpp +++ b/src/temperaturesensor.cpp @@ -482,7 +482,7 @@ void TemperatureSensor::publish_values(const bool force) { char val_obj[70]; char val_cond[170]; if (Mqtt::is_nested()) { - snprintf(val_obj, sizeof(val_obj), "value_json['%s'].temp", sensor.id().c_str()); // TODO change for Domoticz + snprintf(val_obj, sizeof(val_obj), "value_json['%s']['temp']", sensor.id().c_str()); snprintf(val_cond, sizeof(val_cond), "value_json['%s'] is defined and %s is defined", sensor.id().c_str(), val_obj); } else { snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str()); diff --git a/src/test/test.cpp b/src/test/test.cpp index c71d0bd12..03854c796 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -307,7 +307,15 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const } shell.printfln("Testing Adding a device (product_id %d), with all values...", id2); test("add", id1, id2); - shell.invoke_command("show values"); + shell.invoke_command("show devices"); + ok = true; + } + + // set the language + if (command == "locale") { + shell.printfln("Testing setting locale to %s", id1_s.c_str()); + EMSESP::system_.locale(id1_s.c_str()); + shell.invoke_command("show"); ok = true; } diff --git a/src/version.h b/src/version.h index 8117ed7a7..bc1eb86f2 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.7.1-dev.3" \ No newline at end of file +#define EMSESP_APP_VERSION "3.7.1-dev.4" \ No newline at end of file