diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index e58094e3d..3b228373f 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -49,6 +49,7 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/). - ventilation bypass state from telegram 0x55C [#1197](https://github.com/emsesp/EMS-ESP32/issues/1197) - set selflowtemp for ems+ boilers [#2641](https://github.com/emsesp/EMS-ESP32/discussions/2641) - syslog timestamp [#2704](https://github.com/emsesp/EMS-ESP32/issues/2704) +- fixed FS format command [#2720](https://github.com/emsesp/EMS-ESP32/discussions/2720) ## Changed @@ -57,4 +58,7 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/). - updated core libraries like AsyncTCP, AsyncWebServer and Modbus - remove command `scan deep` - ignore repeated `forceheatingoff` commands [#2641](https://github.com/emsesp/EMS-ESP32/discussions/2641) -- optimized web for performance +- optimized web for better performance by adding lazy loading and caching +- internal system analog sensors (core_voltage, supply_voltage and gateway_temperature) cannot be accidentally removed +- double click button reconnects EMS-ESP to AP +- place system message command in side scheduler loop to reduce stack memory usage by 2KB diff --git a/interface/package.json b/interface/package.json index 5ddce35a0..cc5c7e633 100644 --- a/interface/package.json +++ b/interface/package.json @@ -63,7 +63,7 @@ "rollup-plugin-visualizer": "^6.0.5", "terser": "^5.44.1", "typescript-eslint": "^8.46.3", - "vite": "^7.2.0", + "vite": "^7.2.2", "vite-plugin-imagemin": "^0.6.1", "vite-tsconfig-paths": "^5.1.4" }, diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index c177c8343..6a19b3e35 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -83,7 +83,7 @@ importers: version: 9.39.1 '@preact/preset-vite': specifier: ^2.10.2 - version: 2.10.2(@babel/core@7.28.5)(preact@10.27.2)(vite@7.2.0(@types/node@24.10.0)(terser@5.44.1)) + version: 2.10.2(@babel/core@7.28.5)(preact@10.27.2)(vite@7.2.2(@types/node@24.10.0)(terser@5.44.1)) '@trivago/prettier-plugin-sort-imports': specifier: ^6.0.0 version: 6.0.0(prettier@3.6.2) @@ -113,7 +113,7 @@ importers: version: 3.6.2 rollup-plugin-visualizer: specifier: ^6.0.5 - version: 6.0.5(rollup@4.52.5) + version: 6.0.5(rollup@4.53.1) terser: specifier: ^5.44.1 version: 5.44.1 @@ -121,14 +121,14 @@ importers: specifier: ^8.46.3 version: 8.46.3(eslint@9.39.1)(typescript@5.9.3) vite: - specifier: ^7.2.0 - version: 7.2.0(@types/node@24.10.0)(terser@5.44.1) + specifier: ^7.2.2 + version: 7.2.2(@types/node@24.10.0)(terser@5.44.1) vite-plugin-imagemin: specifier: ^0.6.1 - version: 0.6.1(vite@7.2.0(@types/node@24.10.0)(terser@5.44.1)) + version: 0.6.1(vite@7.2.2(@types/node@24.10.0)(terser@5.44.1)) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.0(@types/node@24.10.0)(terser@5.44.1)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.0)(terser@5.44.1)) packages: @@ -679,113 +679,113 @@ packages: resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} - '@rollup/rollup-android-arm-eabi@4.52.5': - resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} + '@rollup/rollup-android-arm-eabi@4.53.1': + resolution: {integrity: sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.52.5': - resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} + '@rollup/rollup-android-arm64@4.53.1': + resolution: {integrity: sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.52.5': - resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} + '@rollup/rollup-darwin-arm64@4.53.1': + resolution: {integrity: sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.52.5': - resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} + '@rollup/rollup-darwin-x64@4.53.1': + resolution: {integrity: sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.52.5': - resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} + '@rollup/rollup-freebsd-arm64@4.53.1': + resolution: {integrity: sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.5': - resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} + '@rollup/rollup-freebsd-x64@4.53.1': + resolution: {integrity: sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': - resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.53.1': + resolution: {integrity: sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.52.5': - resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} + '@rollup/rollup-linux-arm-musleabihf@4.53.1': + resolution: {integrity: sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.52.5': - resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} + '@rollup/rollup-linux-arm64-gnu@4.53.1': + resolution: {integrity: sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.52.5': - resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} + '@rollup/rollup-linux-arm64-musl@4.53.1': + resolution: {integrity: sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.52.5': - resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} + '@rollup/rollup-linux-loong64-gnu@4.53.1': + resolution: {integrity: sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.52.5': - resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} + '@rollup/rollup-linux-ppc64-gnu@4.53.1': + resolution: {integrity: sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.52.5': - resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} + '@rollup/rollup-linux-riscv64-gnu@4.53.1': + resolution: {integrity: sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.52.5': - resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} + '@rollup/rollup-linux-riscv64-musl@4.53.1': + resolution: {integrity: sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.52.5': - resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} + '@rollup/rollup-linux-s390x-gnu@4.53.1': + resolution: {integrity: sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.52.5': - resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} + '@rollup/rollup-linux-x64-gnu@4.53.1': + resolution: {integrity: sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.52.5': - resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} + '@rollup/rollup-linux-x64-musl@4.53.1': + resolution: {integrity: sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.52.5': - resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} + '@rollup/rollup-openharmony-arm64@4.53.1': + resolution: {integrity: sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.52.5': - resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} + '@rollup/rollup-win32-arm64-msvc@4.53.1': + resolution: {integrity: sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.5': - resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} + '@rollup/rollup-win32-ia32-msvc@4.53.1': + resolution: {integrity: sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.52.5': - resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + '@rollup/rollup-win32-x64-gnu@4.53.1': + resolution: {integrity: sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.5': - resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} + '@rollup/rollup-win32-x64-msvc@4.53.1': + resolution: {integrity: sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg==} cpu: [x64] os: [win32] @@ -1027,8 +1027,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.24: - resolution: {integrity: sha512-uUhTRDPXamakPyghwrUcjaGvvBqGrWvBHReoiULMIpOJVM9IYzQh83Xk2Onx5HlGI2o10NNCzcs9TG/S3TkwrQ==} + baseline-browser-mapping@2.8.25: + resolution: {integrity: sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==} hasBin: true bin-build@3.0.0: @@ -1117,8 +1117,8 @@ packages: resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==} engines: {node: '>=0.10.0'} - caniuse-lite@1.0.30001753: - resolution: {integrity: sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==} + caniuse-lite@1.0.30001754: + resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} caw@2.0.1: resolution: {integrity: sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==} @@ -1337,8 +1337,8 @@ packages: duplexer3@0.1.5: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - electron-to-chromium@1.5.245: - resolution: {integrity: sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ==} + electron-to-chromium@1.5.249: + resolution: {integrity: sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2628,8 +2628,8 @@ packages: rollup: optional: true - rollup@4.52.5: - resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} + rollup@4.53.1: + resolution: {integrity: sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2999,8 +2999,8 @@ packages: vite: optional: true - vite@7.2.0: - resolution: {integrity: sha512-C/Naxf8H0pBx1PA4BdpT+c/5wdqI9ILMdwjSMILw7tVIh3JsxzZqdeTLmmdaoh5MYUEOyBnM9K3o0DzoZ/fe+w==} + vite@7.2.2: + resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3598,18 +3598,18 @@ snapshots: dependencies: preact: 10.27.2 - '@preact/preset-vite@2.10.2(@babel/core@7.28.5)(preact@10.27.2)(vite@7.2.0(@types/node@24.10.0)(terser@5.44.1))': + '@preact/preset-vite@2.10.2(@babel/core@7.28.5)(preact@10.27.2)(vite@7.2.2(@types/node@24.10.0)(terser@5.44.1))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.5) - '@prefresh/vite': 2.4.11(preact@10.27.2)(vite@7.2.0(@types/node@24.10.0)(terser@5.44.1)) + '@prefresh/vite': 2.4.11(preact@10.27.2)(vite@7.2.2(@types/node@24.10.0)(terser@5.44.1)) '@rollup/pluginutils': 4.2.1 babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.28.5) debug: 4.4.3 picocolors: 1.1.1 - vite: 7.2.0(@types/node@24.10.0)(terser@5.44.1) - vite-prerender-plugin: 0.5.12(vite@7.2.0(@types/node@24.10.0)(terser@5.44.1)) + vite: 7.2.2(@types/node@24.10.0)(terser@5.44.1) + vite-prerender-plugin: 0.5.12(vite@7.2.2(@types/node@24.10.0)(terser@5.44.1)) transitivePeerDependencies: - preact - supports-color @@ -3622,7 +3622,7 @@ snapshots: '@prefresh/utils@1.2.1': {} - '@prefresh/vite@2.4.11(preact@10.27.2)(vite@7.2.0(@types/node@24.10.0)(terser@5.44.1))': + '@prefresh/vite@2.4.11(preact@10.27.2)(vite@7.2.2(@types/node@24.10.0)(terser@5.44.1))': dependencies: '@babel/core': 7.28.5 '@prefresh/babel-plugin': 0.5.2 @@ -3630,7 +3630,7 @@ snapshots: '@prefresh/utils': 1.2.1 '@rollup/pluginutils': 4.2.1 preact: 10.27.2 - vite: 7.2.0(@types/node@24.10.0)(terser@5.44.1) + vite: 7.2.2(@types/node@24.10.0)(terser@5.44.1) transitivePeerDependencies: - supports-color @@ -3639,70 +3639,70 @@ snapshots: estree-walker: 2.0.2 picomatch: 2.3.1 - '@rollup/rollup-android-arm-eabi@4.52.5': + '@rollup/rollup-android-arm-eabi@4.53.1': optional: true - '@rollup/rollup-android-arm64@4.52.5': + '@rollup/rollup-android-arm64@4.53.1': optional: true - '@rollup/rollup-darwin-arm64@4.52.5': + '@rollup/rollup-darwin-arm64@4.53.1': optional: true - '@rollup/rollup-darwin-x64@4.52.5': + '@rollup/rollup-darwin-x64@4.53.1': optional: true - '@rollup/rollup-freebsd-arm64@4.52.5': + '@rollup/rollup-freebsd-arm64@4.53.1': optional: true - '@rollup/rollup-freebsd-x64@4.52.5': + '@rollup/rollup-freebsd-x64@4.53.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + '@rollup/rollup-linux-arm-gnueabihf@4.53.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.52.5': + '@rollup/rollup-linux-arm-musleabihf@4.53.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.52.5': + '@rollup/rollup-linux-arm64-gnu@4.53.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.52.5': + '@rollup/rollup-linux-arm64-musl@4.53.1': optional: true - '@rollup/rollup-linux-loong64-gnu@4.52.5': + '@rollup/rollup-linux-loong64-gnu@4.53.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.5': + '@rollup/rollup-linux-ppc64-gnu@4.53.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.5': + '@rollup/rollup-linux-riscv64-gnu@4.53.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.5': + '@rollup/rollup-linux-riscv64-musl@4.53.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.5': + '@rollup/rollup-linux-s390x-gnu@4.53.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.52.5': + '@rollup/rollup-linux-x64-gnu@4.53.1': optional: true - '@rollup/rollup-linux-x64-musl@4.52.5': + '@rollup/rollup-linux-x64-musl@4.53.1': optional: true - '@rollup/rollup-openharmony-arm64@4.52.5': + '@rollup/rollup-openharmony-arm64@4.53.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.5': + '@rollup/rollup-win32-arm64-msvc@4.53.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.5': + '@rollup/rollup-win32-ia32-msvc@4.53.1': optional: true - '@rollup/rollup-win32-x64-gnu@4.52.5': + '@rollup/rollup-win32-x64-gnu@4.53.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.52.5': + '@rollup/rollup-win32-x64-msvc@4.53.1': optional: true '@sindresorhus/is@0.7.0': {} @@ -3963,7 +3963,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.24: {} + baseline-browser-mapping@2.8.25: {} bin-build@3.0.0: dependencies: @@ -4020,9 +4020,9 @@ snapshots: browserslist@4.27.0: dependencies: - baseline-browser-mapping: 2.8.24 - caniuse-lite: 1.0.30001753 - electron-to-chromium: 1.5.245 + baseline-browser-mapping: 2.8.25 + caniuse-lite: 1.0.30001754 + electron-to-chromium: 1.5.249 node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.27.0) @@ -4080,7 +4080,7 @@ snapshots: camelcase@2.1.1: {} - caniuse-lite@1.0.30001753: {} + caniuse-lite@1.0.30001754: {} caw@2.0.1: dependencies: @@ -4367,7 +4367,7 @@ snapshots: duplexer3@0.1.5: {} - electron-to-chromium@1.5.245: {} + electron-to-chromium@1.5.249: {} emoji-regex@8.0.0: {} @@ -5621,41 +5621,41 @@ snapshots: dependencies: glob: 7.2.3 - rollup-plugin-visualizer@6.0.5(rollup@4.52.5): + rollup-plugin-visualizer@6.0.5(rollup@4.53.1): dependencies: open: 8.4.2 picomatch: 4.0.3 source-map: 0.7.6 yargs: 17.7.2 optionalDependencies: - rollup: 4.52.5 + rollup: 4.53.1 - rollup@4.52.5: + rollup@4.53.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.5 - '@rollup/rollup-android-arm64': 4.52.5 - '@rollup/rollup-darwin-arm64': 4.52.5 - '@rollup/rollup-darwin-x64': 4.52.5 - '@rollup/rollup-freebsd-arm64': 4.52.5 - '@rollup/rollup-freebsd-x64': 4.52.5 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 - '@rollup/rollup-linux-arm-musleabihf': 4.52.5 - '@rollup/rollup-linux-arm64-gnu': 4.52.5 - '@rollup/rollup-linux-arm64-musl': 4.52.5 - '@rollup/rollup-linux-loong64-gnu': 4.52.5 - '@rollup/rollup-linux-ppc64-gnu': 4.52.5 - '@rollup/rollup-linux-riscv64-gnu': 4.52.5 - '@rollup/rollup-linux-riscv64-musl': 4.52.5 - '@rollup/rollup-linux-s390x-gnu': 4.52.5 - '@rollup/rollup-linux-x64-gnu': 4.52.5 - '@rollup/rollup-linux-x64-musl': 4.52.5 - '@rollup/rollup-openharmony-arm64': 4.52.5 - '@rollup/rollup-win32-arm64-msvc': 4.52.5 - '@rollup/rollup-win32-ia32-msvc': 4.52.5 - '@rollup/rollup-win32-x64-gnu': 4.52.5 - '@rollup/rollup-win32-x64-msvc': 4.52.5 + '@rollup/rollup-android-arm-eabi': 4.53.1 + '@rollup/rollup-android-arm64': 4.53.1 + '@rollup/rollup-darwin-arm64': 4.53.1 + '@rollup/rollup-darwin-x64': 4.53.1 + '@rollup/rollup-freebsd-arm64': 4.53.1 + '@rollup/rollup-freebsd-x64': 4.53.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.1 + '@rollup/rollup-linux-arm-musleabihf': 4.53.1 + '@rollup/rollup-linux-arm64-gnu': 4.53.1 + '@rollup/rollup-linux-arm64-musl': 4.53.1 + '@rollup/rollup-linux-loong64-gnu': 4.53.1 + '@rollup/rollup-linux-ppc64-gnu': 4.53.1 + '@rollup/rollup-linux-riscv64-gnu': 4.53.1 + '@rollup/rollup-linux-riscv64-musl': 4.53.1 + '@rollup/rollup-linux-s390x-gnu': 4.53.1 + '@rollup/rollup-linux-x64-gnu': 4.53.1 + '@rollup/rollup-linux-x64-musl': 4.53.1 + '@rollup/rollup-openharmony-arm64': 4.53.1 + '@rollup/rollup-win32-arm64-msvc': 4.53.1 + '@rollup/rollup-win32-ia32-msvc': 4.53.1 + '@rollup/rollup-win32-x64-gnu': 4.53.1 + '@rollup/rollup-win32-x64-msvc': 4.53.1 fsevents: 2.3.3 run-parallel@1.2.0: @@ -5970,7 +5970,7 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite-plugin-imagemin@0.6.1(vite@7.2.0(@types/node@24.10.0)(terser@5.44.1)): + vite-plugin-imagemin@0.6.1(vite@7.2.2(@types/node@24.10.0)(terser@5.44.1)): dependencies: '@types/imagemin': 7.0.1 '@types/imagemin-gifsicle': 7.0.4 @@ -5995,11 +5995,11 @@ snapshots: imagemin-webp: 6.1.0 jpegtran-bin: 6.0.1 pathe: 0.2.0 - vite: 7.2.0(@types/node@24.10.0)(terser@5.44.1) + vite: 7.2.2(@types/node@24.10.0)(terser@5.44.1) transitivePeerDependencies: - supports-color - vite-prerender-plugin@0.5.12(vite@7.2.0(@types/node@24.10.0)(terser@5.44.1)): + vite-prerender-plugin@0.5.12(vite@7.2.2(@types/node@24.10.0)(terser@5.44.1)): dependencies: kolorist: 1.8.0 magic-string: 0.30.21 @@ -6007,26 +6007,26 @@ snapshots: simple-code-frame: 1.3.0 source-map: 0.7.6 stack-trace: 1.0.0-pre2 - vite: 7.2.0(@types/node@24.10.0)(terser@5.44.1) + vite: 7.2.2(@types/node@24.10.0)(terser@5.44.1) - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.0(@types/node@24.10.0)(terser@5.44.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.0)(terser@5.44.1)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.2.0(@types/node@24.10.0)(terser@5.44.1) + vite: 7.2.2(@types/node@24.10.0)(terser@5.44.1) transitivePeerDependencies: - supports-color - typescript - vite@7.2.0(@types/node@24.10.0)(terser@5.44.1): + vite@7.2.2(@types/node@24.10.0)(terser@5.44.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.5 + rollup: 4.53.1 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.10.0 diff --git a/interface/src/app/main/SensorsAnalogDialog.tsx b/interface/src/app/main/SensorsAnalogDialog.tsx index 2294a1991..fbbd35466 100644 --- a/interface/src/app/main/SensorsAnalogDialog.tsx +++ b/interface/src/app/main/SensorsAnalogDialog.tsx @@ -288,7 +288,7 @@ const SensorsAnalogDialog = ({ name="f" label={LL.FACTOR()} value={numberValue(editItem.f)} - sx={{ width: '11ch' }} + sx={{ width: '14ch' }} type="number" variant="outlined" onChange={updateFormValue} @@ -447,6 +447,7 @@ const SensorsAnalogDialog = ({ + + {LL.FACTORY_RESET()} + {LL.SYSTEM_FACTORY_TEXT_DIALOG()} + + + + + + + + + - - + + + ); + }, [ + LL, + handleFactoryResetClick, + handleFactoryResetClose, + doFormat, + confirmFactoryReset, + restarting + ]); - - - - - - - ); + return {restarting ? : content}; }; export default Settings; diff --git a/interface/src/components/inputs/ValidatedTextField.tsx b/interface/src/components/inputs/ValidatedTextField.tsx index b7ccd3ae6..ad1b87f37 100644 --- a/interface/src/components/inputs/ValidatedTextField.tsx +++ b/interface/src/components/inputs/ValidatedTextField.tsx @@ -23,7 +23,9 @@ const ValidatedTextField: FC = ({ <> {errors?.map((e) => ( - {e.message} + + {e.message} + ))} ); diff --git a/interface/src/i18n/cz/index.ts b/interface/src/i18n/cz/index.ts index b7131532c..85f6f6f37 100644 --- a/interface/src/i18n/cz/index.ts +++ b/interface/src/i18n/cz/index.ts @@ -337,7 +337,7 @@ const cz: Translation = { UPDATE_AVAILABLE: 'aktualizace dostupná', LATEST_VERSION: 'Používáte nejnovější verzi {0}firmwaru', PLEASE_WAIT: 'Prosím čekejte', - RESTARTING_PRE: 'Inicializace', + RESTARTING_PRE: 'Bootování', RESTARTING_POST: 'Příprava', AUTO_SCROLL: 'Automatické rolování', DASHBOARD: 'Dashboard', diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index f77904b6b..6cd8beb8e 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -337,7 +337,7 @@ const de: Translation = { UPDATE_AVAILABLE: 'Firmware-Update verfügbar', LATEST_VERSION: 'Sie verwenden die neueste {0} Firmware-Version', PLEASE_WAIT: 'Bitte warten', - RESTARTING_PRE: 'Initialisierung', + RESTARTING_PRE: 'Booten', RESTARTING_POST: 'Vorbereitung', AUTO_SCROLL: 'Automatisches Scrollen', DASHBOARD: 'Dashboard', diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index d9d18b7d5..08cffa3df 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -337,7 +337,7 @@ const en: Translation = { UPDATE_AVAILABLE: 'update available', LATEST_VERSION: 'You are using the latest {0} firmware version', PLEASE_WAIT: 'Please wait', - RESTARTING_PRE: 'Initializing', + RESTARTING_PRE: 'Booting', RESTARTING_POST: 'Preparing', AUTO_SCROLL: 'Auto Scroll', DASHBOARD: 'Dashboard', diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index f5a123b60..752f660fb 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -337,7 +337,7 @@ const fr: Translation = { UPDATE_AVAILABLE: 'mise à jour disponible', LATEST_VERSION: 'Vous utilisez la dernière version {0} du firmware', PLEASE_WAIT: 'Veuillez patienter', - RESTARTING_PRE: 'Initialisation', + RESTARTING_PRE: 'Démarrage', RESTARTING_POST: 'Préparation', AUTO_SCROLL: 'Défilement automatique', DASHBOARD: 'Tableau de bord', diff --git a/interface/src/i18n/it/index.ts b/interface/src/i18n/it/index.ts index 44f60c691..279870901 100644 --- a/interface/src/i18n/it/index.ts +++ b/interface/src/i18n/it/index.ts @@ -337,7 +337,7 @@ const it: Translation = { UPDATE_AVAILABLE: 'aggiornamento disponibile', LATEST_VERSION: 'Stai usando la versione più recente del firmware {0}', PLEASE_WAIT: 'Attendere', - RESTARTING_PRE: 'Inizializzazione', + RESTARTING_PRE: 'Avviamento', RESTARTING_POST: 'Preparazione', AUTO_SCROLL: 'Scorrimento automatico', DASHBOARD: 'Pannello di controllo', diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index acdf83c03..fbceaf4ee 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -337,7 +337,7 @@ const nl: Translation = { UPDATE_AVAILABLE: 'update beschikbaar', LATEST_VERSION: 'U gebruikt de nieuwste {0} firmwareversie', PLEASE_WAIT: 'Een ogenblik geduld', - RESTARTING_PRE: 'Initialiseren', + RESTARTING_PRE: 'Booten', RESTARTING_POST: 'Voorbereiding', AUTO_SCROLL: 'Automatisch Scrollen', DASHBOARD: 'Dashboard', diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index e27d38089..1eb1dd3ba 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -337,7 +337,7 @@ const no: Translation = { UPDATE_AVAILABLE: 'oppdatering tilgjengelig', LATEST_VERSION: 'Du bruker den nyeste {0} firmware versjonen', PLEASE_WAIT: 'Vennligst vent', - RESTARTING_PRE: 'Initialiserer', + RESTARTING_PRE: 'Starte', RESTARTING_POST: 'Forbereder', AUTO_SCROLL: 'Automatisk rulling', DASHBOARD: 'Dashboard', diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index 9fd1cbc02..8520e86bc 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -337,7 +337,7 @@ const pl: BaseTranslation = { UPDATE_AVAILABLE: 'aktualizacja dostępna', LATEST_VERSION: 'Jesteś używając najnowszej wersji firmware {0}', PLEASE_WAIT: 'Proszę czekać', - RESTARTING_PRE: 'Inicjalizacja', + RESTARTING_PRE: 'Uruchamianie', RESTARTING_POST: 'Przygotowanie', AUTO_SCROLL: 'Auto Scroll', DASHBOARD: 'Pulpit', diff --git a/interface/src/i18n/sk/index.ts b/interface/src/i18n/sk/index.ts index 737113010..d9349bc2e 100644 --- a/interface/src/i18n/sk/index.ts +++ b/interface/src/i18n/sk/index.ts @@ -337,7 +337,7 @@ const sk: Translation = { UPDATE_AVAILABLE: 'dostupná aktualizácia', LATEST_VERSION: 'Používate poslednú {0} verziu firmvéru', PLEASE_WAIT: 'Čakajte prosím', - RESTARTING_PRE: 'Prebieha inicializácia', + RESTARTING_PRE: 'Bootovanie', RESTARTING_POST: 'Príprava', AUTO_SCROLL: 'Automatické rolovanie', DASHBOARD: 'Panel', diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 48f513017..af1681f5f 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -337,7 +337,7 @@ const sv: Translation = { UPDATE_AVAILABLE: 'uppdatering tillgänglig', LATEST_VERSION: 'Du använder den senaste {0} firmwareversionen.', PLEASE_WAIT: 'Var god vänta', - RESTARTING_PRE: 'Initialiserar', + RESTARTING_PRE: 'Bootar', RESTARTING_POST: 'Förbereder', AUTO_SCROLL: 'Autoskrolla', DASHBOARD: 'Kontrollpanel', diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index d263176bb..2414a867d 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -337,7 +337,7 @@ const tr: Translation = { UPDATE_AVAILABLE: 'güncellendi!', LATEST_VERSION: 'En son {0} firmware sürümünü kullanıyorsunuz.', PLEASE_WAIT: 'Lütfen bekleyin', - RESTARTING_PRE: 'Başlatılıyor', + RESTARTING_PRE: 'Yükleniyor', RESTARTING_POST: 'Hazırlanıyor', AUTO_SCROLL: 'Otomatik kaydırma', DASHBOARD: 'Kontrol Paneli', diff --git a/lib/PButton/PButon.cpp b/lib/PButton/PButon.cpp index 8324b7728..0b0ece9f1 100644 --- a/lib/PButton/PButon.cpp +++ b/lib/PButton/PButon.cpp @@ -23,10 +23,10 @@ // Constructor PButton::PButton() { // Initialization of default properties - Debounce_ = 40; // Debounce period to prevent flickering when pressing or releasing the button (in ms) - DblClickDelay_ = 250; // Max period between clicks for a double click event (in ms) - LongPressDelay_ = 750; // Hold period for a long press event (in ms) - VLongPressDelay_ = 3000; // Hold period for a very long press event (in ms) + Debounce_ = 40; // Debounce period to prevent flickering when pressing or releasing the button (in ms) + DblClickDelay_ = 250; // Max period between clicks for a double click event (in ms) + LongPressDelay_ = 9500; // Hold period for a long press event (in ms) + VLongPressDelay_ = 20000; // Hold period for a very long press event (in ms) cb_onClick = nullptr; cb_onDblClick = nullptr; diff --git a/lib_standalone/ESP32React.h b/lib_standalone/ESP32React.h index 115a42aae..4da6036a6 100644 --- a/lib_standalone/ESP32React.h +++ b/lib_standalone/ESP32React.h @@ -21,6 +21,7 @@ #define NTP_SETTINGS_FILE "/config/ntpSettings.json" #define EMSESP_SETTINGS_FILE "/config/emsespSettings.json" +#define AP_MODE_ALWAYS 0 class DummySettings { public: // SYSTEM @@ -55,6 +56,7 @@ class DummySettings { uint16_t publish_time_other = 10; uint16_t publish_time_sensor = 10; uint16_t publish_time_heartbeat = 60; + uint32_t publish_time_water = 0; String hostname = "ems-esp"; String jwtSecret = "ems-esp"; @@ -72,11 +74,15 @@ class DummySettings { String CORSOrigin = "*"; uint8_t tx_power = 0; - uint8_t provisionMode = 0; - uint32_t publish_time_water = 0; + // AP + uint8_t provisionMode = 0; - static void read(DummySettings & settings, JsonObject root){}; - static void read(DummySettings & settings){}; + // NTP + String server = "pool.ntp.org"; + String tzLabel = "Europe/London"; + + static void read(DummySettings & settings, JsonObject root) {}; + static void read(DummySettings & settings) {}; static StateUpdateResult update(JsonObject root, DummySettings & settings) { return StateUpdateResult::CHANGED; @@ -85,7 +91,7 @@ class DummySettings { class DummySettingsService : public StatefulService { public: - DummySettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager){}; + DummySettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) {}; void begin(); void loop(); @@ -101,12 +107,12 @@ class ESP32React { public: ESP32React(AsyncWebServer * server, FS * fs) : _settings(server, fs, nullptr) - , _securitySettingsService(server, fs){}; + , _securitySettingsService(server, fs) {}; void begin() { _mqttClient = new espMqttClient(); }; - void loop(){}; + void loop() {}; SecurityManager * getSecurityManager() { return &_securitySettingsService; diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts index cb3627521..c464166d3 100644 --- a/mock-api/restServer.ts +++ b/mock-api/restServer.ts @@ -965,14 +965,72 @@ const emsesp_sensordata = { { id: '28-233D-9497-0C03', n: 'Dallas 1', t: 25.7, o: 1.2, u: 1 }, { id: '28-243D-7437-1E3A', n: 'Dallas 2 outside', t: 26.1, o: 0, u: 1 }, { id: '28-243E-7437-1E3B', n: 'Zolder', t: 27.1, o: 0, u: 1 }, - { id: '28-183D-1892-0C33', n: 'Roof', o: 2, u: 1 } // no temperature + { id: '28-183D-1892-0C33', n: 'Roof', o: 2, u: 1 }, // no temperature + { id: '28_1767_7B13_2502', n: 'gateway_temperature', t: 28.1, o: 0, u: 1 } // internal system temp ], // as: [], as: [ - { id: 1, g: 36, n: 'motor', v: 0, u: 0, o: 17, f: 0, t: 0, d: false }, - { id: 2, g: 37, n: 'External switch', v: 13, u: 0, o: 17, f: 0, t: 1, d: false }, - { id: 3, g: 39, n: 'Pulse count', v: 144, u: 0, o: 0, f: 0, t: 2, d: false }, - { id: 4, g: 40, n: 'Pressure', v: 16, u: 17, o: 0, f: 0, t: 3, d: false } + { id: 1, g: 35, n: 'motor', v: 0, u: 0, o: 17, f: 0, t: 0, d: false, s: false }, + { + id: 2, + g: 37, + n: 'External switch', + v: 13, + u: 0, + o: 17, + f: 0, + t: 1, + d: false, + s: false + }, + { + id: 3, + g: 39, + n: 'Pulse count', + v: 144, + u: 0, + o: 0, + f: 0, + t: 2, + d: false, + s: false + }, + { + id: 4, + g: 40, + n: 'Pressure', + v: 16, + u: 17, + o: 0, + f: 0, + t: 3, + d: false, + s: false + }, + { + id: 6, + g: 39, + n: 'core_voltage', + v: 3.34, + u: 23, + o: 0, + f: 0.003771, + t: 3, + d: false, + s: true + }, + { + id: 7, + g: 36, + n: 'supply_voltage', + v: 12.21, + u: 23, + o: 0, + f: 0.017, + t: 3, + d: false, + s: true + } ], analog_enabled: true }; @@ -4811,6 +4869,7 @@ router u: as.uom, t: as.type, d: as.deleted, + s: as.is_system, v: 0 // must be added for demo only }); } else { diff --git a/project-words.txt b/project-words.txt index feffbe623..f5d4b01b6 100644 --- a/project-words.txt +++ b/project-words.txt @@ -1435,4 +1435,5 @@ teddybear washingmachine switchprogram brotlin -fanspd \ No newline at end of file +fanspd +currhum \ No newline at end of file diff --git a/src/ESP32React/APSettingsService.h b/src/ESP32React/APSettingsService.h index 3c76feeb5..67c9e03bf 100644 --- a/src/ESP32React/APSettingsService.h +++ b/src/ESP32React/APSettingsService.h @@ -58,7 +58,7 @@ enum APNetworkStatus { ACTIVE = 0, INACTIVE, LINGERING }; class APSettings { public: - uint8_t provisionMode; + uint8_t provisionMode; // 0 = on, 2 = off String ssid; String password; uint8_t channel; diff --git a/src/core/analogsensor.cpp b/src/core/analogsensor.cpp index 8aa69c0cc..3b39df74e 100644 --- a/src/core/analogsensor.cpp +++ b/src/core/analogsensor.cpp @@ -52,18 +52,23 @@ void AnalogSensor::start(const bool factory_settings) { // if (factory_settings && EMSESP::nvs_.getString("boot").equals("E32V2_2") && EMSESP::nvs_.getString("hwrevision").equals("3.0")) { if (factory_settings && analogReadMilliVolts(39) > 700) { // core voltage > 2.6V EMSESP::webCustomizationService.update([&](WebCustomization & settings) { - auto newSensor = AnalogCustomization(); - newSensor.gpio = 39; - newSensor.name = "core_voltage"; - newSensor.offset = 0; - newSensor.factor = 0.00377136; // Divider 24k - 8,66k - newSensor.uom = DeviceValueUOM::VOLTS; - newSensor.type = AnalogType::ADC; + auto newSensor = AnalogCustomization(); + + newSensor.gpio = 39; + newSensor.name = "core_voltage"; + newSensor.offset = 0; + newSensor.factor = 0.00377136; // Divider 24k - 8,66k + newSensor.uom = DeviceValueUOM::VOLTS; + newSensor.type = AnalogType::ADC; + newSensor.is_system = true; settings.analogCustomizations.push_back(newSensor); - newSensor.gpio = 36; - newSensor.name = "supply_voltage"; - newSensor.factor = 0.017; // Divider 24k - 1,5k + + newSensor.gpio = 36; + newSensor.name = "supply_voltage"; + newSensor.factor = 0.017; // Divider 24k - 1,5k + newSensor.is_system = true; settings.analogCustomizations.push_back(newSensor); + return StateUpdateResult::CHANGED; // persist the change }); } @@ -469,7 +474,7 @@ void AnalogSensor::loop() { // update analog information name and offset // a type value of -1 is used to delete the sensor -bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted) { +bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system) { // first see if we can find the sensor in our customization list bool found_sensor = false; EMSESP::webCustomizationService.update([&](WebCustomization & settings) { @@ -496,11 +501,12 @@ bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, doubl if (name != AnalogCustomization.name) { EMSESP::nvs_.remove(AnalogCustomization.name.c_str()); } - AnalogCustomization.name = name; - AnalogCustomization.offset = offset; - AnalogCustomization.factor = factor; - AnalogCustomization.uom = uom; - AnalogCustomization.type = type; + AnalogCustomization.name = name; + AnalogCustomization.offset = offset; + AnalogCustomization.factor = factor; + AnalogCustomization.uom = uom; + AnalogCustomization.type = type; + AnalogCustomization.is_system = is_system; LOG_DEBUG("Customizing existing analog GPIO %02d", gpio); } return StateUpdateResult::CHANGED; // persist the change @@ -517,13 +523,14 @@ bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, doubl // we didn't find it, it's new, so create and store it in the customization list if (!found_sensor) { EMSESP::webCustomizationService.update([&](WebCustomization & settings) { - auto newSensor = AnalogCustomization(); - newSensor.gpio = gpio; - newSensor.name = name; - newSensor.offset = offset; - newSensor.factor = factor; - newSensor.uom = uom; - newSensor.type = type; + auto newSensor = AnalogCustomization(); + newSensor.gpio = gpio; + newSensor.name = name; + newSensor.offset = offset; + newSensor.factor = factor; + newSensor.uom = uom; + newSensor.type = type; + newSensor.is_system = false; settings.analogCustomizations.push_back(newSensor); LOG_DEBUG("Adding new customization for analog sensor GPIO %02d", gpio); return StateUpdateResult::CHANGED; // persist the change @@ -836,7 +843,8 @@ void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) { output["readable"] = true; output["writeable"] = sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::RGB || sensor.type() == AnalogType::PULSE || (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2); - output["visible"] = true; + output["visible"] = true; + output["is_system"] = sensor.is_system(); if (sensor.type() == AnalogType::COUNTER) { output["min"] = 0; output["max"] = 4000000; diff --git a/src/core/analogsensor.h b/src/core/analogsensor.h index 7125a2b3b..83e8ed4a9 100644 --- a/src/core/analogsensor.h +++ b/src/core/analogsensor.h @@ -61,6 +61,10 @@ class AnalogSensor { value_ = value; } + bool is_system() const { + return is_system_; + } + double factor() const { return factor_; } @@ -105,8 +109,9 @@ class AnalogSensor { double offset_; double factor_; uint8_t uom_; - double value_; // double because of the factor is a double - int8_t type_; // one of the AnalogType enum + double value_; // double because of the factor is a double + int8_t type_; // one of the AnalogType enum + bool is_system_; // if true, the sensor is a system sensor }; AnalogSensor() = default; @@ -167,7 +172,7 @@ class AnalogSensor { return sensors_.size(); } - bool update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted = false); + bool update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted = false, bool is_system = false); bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1); void store_counters(); diff --git a/src/core/command.cpp b/src/core/command.cpp index f5ab6fded..aee649dd6 100644 --- a/src/core/command.cpp +++ b/src/core/command.cpp @@ -592,6 +592,7 @@ bool Command::list(const uint8_t device_type, JsonObject output) { output["ap/enabled"] = Helpers::translated_word(FL_(system_cmd)); output["syslog/enabled"] = Helpers::translated_word(FL_(system_cmd)); } + // create a list of commands we have registered, and sort them std::list sorted_cmds; for (const auto & cf : cmdfunctions_) { diff --git a/src/core/common.h b/src/core/common.h index aa49be717..89e354f8f 100644 --- a/src/core/common.h +++ b/src/core/common.h @@ -44,39 +44,39 @@ using string_vector = std::vector; #endif // clang-format off + + #define FPSTR(pstr_pointer) pstr_pointer + #define MAKE_WORD_CUSTOM(string_name, string_literal) static const char __pstr__##string_name[] = string_literal; + #define MAKE_WORD(string_name) MAKE_WORD_CUSTOM(string_name, #string_name) + + #define F_(string_name) (__pstr__##string_name) + #define FL_(list_name) (__pstr__L_##list_name) + + // Translation counter - capture baseline before any MAKE_TRANSLATION calls + enum { EMSESP_TRANSLATION_COUNT_START = __COUNTER__ }; -#define FPSTR(pstr_pointer) pstr_pointer -#define MAKE_WORD_CUSTOM(string_name, string_literal) static const char __pstr__##string_name[] = string_literal; -#define MAKE_WORD(string_name) MAKE_WORD_CUSTOM(string_name, #string_name) - -#define F_(string_name) (__pstr__##string_name) -#define FL_(list_name) (__pstr__L_##list_name) - -// The language settings below must match system.cpp -#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[] = {en, de, nullptr}; -#define MAKE_TRANSLATION(list_name, shortname, en, de, ...) static const char * const __pstr__L_##list_name[] = {shortname, en, 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[] = {en, de, nullptr}; -#define MAKE_TRANSLATION(list_name, shortname, en, de, ...) static const char * const __pstr__L_##list_name[] = {shortname, en, de, nullptr}; -#else -#define MAKE_WORD_TRANSLATION(list_name, ...) static const char * const __pstr__L_##list_name[] = {__VA_ARGS__, nullptr}; -#define MAKE_TRANSLATION(list_name, ...) static const char * const __pstr__L_##list_name[] = {__VA_ARGS__, nullptr}; -#endif - -#define MAKE_NOTRANSLATION(list_name, ...) static const char * const __pstr__L_##list_name[] = {__VA_ARGS__, nullptr}; - -// fixed strings, no translations -#define MAKE_ENUM_FIXED(enum_name, ...) static const char * const __pstr__L_##enum_name[] = {__VA_ARGS__, nullptr}; - -// with translations -#define MAKE_ENUM(enum_name, ...) static const char * const * __pstr__L_##enum_name[] = {__VA_ARGS__, nullptr}; + // The language settings below must match system.cpp + #if 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 constexpr int __translation_counter_##list_name = __COUNTER__; static const char * const __pstr__L_##list_name[] = {shortname, en, nullptr}; + #elif defined(EMSESP_TEST) || defined(EMSESP_DE_ONLY) + // EN + DE (Test mode uses two languages to save flash memory) + #define MAKE_WORD_TRANSLATION(list_name, en, de, ...) static const char * const __pstr__L_##list_name[] = {en, de, nullptr}; + #define MAKE_TRANSLATION(list_name, shortname, en, de, ...) static constexpr int __translation_counter_##list_name = __COUNTER__; static const char * const __pstr__L_##list_name[] = {shortname, en, de, nullptr}; + #else + // All languages + #define MAKE_WORD_TRANSLATION(list_name, ...) static const char * const __pstr__L_##list_name[] = {__VA_ARGS__, nullptr}; + #define MAKE_TRANSLATION(list_name, ...) static constexpr int __translation_counter_##list_name = __COUNTER__; static const char * const __pstr__L_##list_name[] = {__VA_ARGS__, nullptr}; + #endif + + #define MAKE_NOTRANSLATION(list_name, ...) static const char * const __pstr__L_##list_name[] = {__VA_ARGS__, nullptr}; + + // fixed strings, no translations + #define MAKE_ENUM_FIXED(enum_name, ...) static const char * const __pstr__L_##enum_name[] = {__VA_ARGS__, nullptr}; + + // with translations + #define MAKE_ENUM(enum_name, ...) static const char * const * __pstr__L_##enum_name[] = {__VA_ARGS__, nullptr}; // clang-format on @@ -84,4 +84,8 @@ using string_vector = std::vector; #include "locale_translations.h" #include "locale_common.h" +// Translation count - dynamically calculated at compile-time +enum { EMSESP_TRANSLATION_COUNT_END = __COUNTER__ }; +static constexpr uint16_t EMSESP_TRANSLATION_COUNT = EMSESP_TRANSLATION_COUNT_END - EMSESP_TRANSLATION_COUNT_START - 1; + #endif diff --git a/src/core/emsesp.cpp b/src/core/emsesp.cpp index 4ee114cea..e37c498c8 100644 --- a/src/core/emsesp.cpp +++ b/src/core/emsesp.cpp @@ -96,18 +96,35 @@ Preferences EMSESP::nvs_; // NV Storage // for a specific EMS device go and request data values // or if device_id is 0 it will fetch from all our known and active devices void EMSESP::fetch_device_values(const uint8_t device_id) { - for (const auto & emsdevice : emsdevices) { - if ((device_id == 0) || emsdevice->is_device_id(device_id)) { + // Early return if no devices + if (emsdevices.empty()) { + return; + } + + // If device_id is 0, fetch all + if (device_id == 0) { + for (const auto & emsdevice : emsdevices) { emsdevice->fetch_values(); - if (device_id != 0) { - return; // quit, we only want to return the selected device - } + } + return; + } + + // Fetch specific device + for (const auto & emsdevice : emsdevices) { + if (emsdevice->is_device_id(device_id)) { + emsdevice->fetch_values(); + return; // quit, we only want to return the selected device } } } // see if the deviceID exists bool EMSESP::valid_device(const uint8_t device_id) { + // Early return if devices list is empty + if (emsdevices.empty()) { + return false; + } + for (const auto & emsdevice : emsdevices) { if (emsdevice && emsdevice->is_device_id(device_id)) { return true; @@ -118,6 +135,10 @@ bool EMSESP::valid_device(const uint8_t device_id) { // for a specific EMS device type go and request data values void EMSESP::fetch_device_values_type(const uint8_t device_type) { + if (emsdevices.empty()) { + return; + } + for (const auto & emsdevice : emsdevices) { if (emsdevice && (emsdevice->device_type() == device_type)) { emsdevice->fetch_values(); @@ -126,6 +147,10 @@ void EMSESP::fetch_device_values_type(const uint8_t device_type) { } bool EMSESP::cmd_is_readonly(const uint8_t device_type, const uint8_t device_id, const char * cmd, const int8_t id) { + if (emsdevices.empty()) { + return false; + } + for (const auto & emsdevice : emsdevices) { if (emsdevice && (emsdevice->device_type() == device_type) && (!device_id || emsdevice->device_id() == device_id)) { return emsdevice->is_readonly(cmd, id); @@ -135,6 +160,10 @@ bool EMSESP::cmd_is_readonly(const uint8_t device_type, const uint8_t device_id, } uint8_t EMSESP::device_id_from_cmd(const uint8_t device_type, const char * cmd, const int8_t id) { + if (emsdevices.empty()) { + return 0; + } + for (const auto & emsdevice : emsdevices) { if (emsdevice && emsdevice->device_type() == device_type && emsdevice->has_cmd(cmd, id)) { return emsdevice->device_id(); @@ -151,10 +180,14 @@ void EMSESP::clear_all_devices() { // return number of devices of a known type uint8_t EMSESP::count_devices(const uint8_t device_type) { + if (emsdevices.empty()) { + return 0; + } + uint8_t count = 0; for (const auto & emsdevice : emsdevices) { - if (emsdevice) { - count += (emsdevice->device_type() == device_type); + if (emsdevice && emsdevice->device_type() == device_type) { + count++; } } return count; @@ -162,10 +195,14 @@ uint8_t EMSESP::count_devices(const uint8_t device_type) { // return total number of devices excluding the Controller uint8_t EMSESP::count_devices() { + if (emsdevices.empty()) { + return 0; + } + uint8_t count = 0; for (const auto & emsdevice : emsdevices) { - if (emsdevice) { - count += (emsdevice->device_type() != EMSdevice::DeviceType::CONTROLLER); + if (emsdevice && emsdevice->device_type() != EMSdevice::DeviceType::CONTROLLER) { + count++; } } return count; @@ -174,20 +211,22 @@ uint8_t EMSESP::count_devices() { // returns the index of a device if there are more of the same type // or 0 if there is only one or none uint8_t EMSESP::device_index(const uint8_t device_type, const uint8_t unique_id) { - if (count_devices(device_type) <= 1) { - return 0; // none or only 1 device exists - } - uint8_t index = 1; + uint8_t count = 0; + uint8_t index = 0; + uint8_t current_index = 1; + for (const auto & emsdevice : emsdevices) { if (emsdevice->device_type() == device_type) { - // did we find it? + count++; if (emsdevice->unique_id() == unique_id) { - return index; + index = current_index; } - index++; + current_index++; } } - return 0; // didn't find it + + // Return 0 if only one device exists or not found + return (count <= 1) ? 0 : index; } // scans for new devices @@ -242,13 +281,8 @@ uint8_t EMSESP::bus_status() { uint32_t total_fail = txservice_.telegram_read_fail_count() + txservice_.telegram_write_fail_count(); // nothing sent and also no errors - must be ok - if ((total_sent == 0) && (total_fail == 0)) { - return BUS_STATUS_CONNECTED; - } - - // nothing sent, but have Tx errors - if ((total_sent == 0) && (total_fail != 0)) { - return BUS_STATUS_TX_ERRORS; + if (total_sent == 0) { + return (total_fail == 0) ? BUS_STATUS_CONNECTED : BUS_STATUS_TX_ERRORS; } // Tx Failure rate > 10% @@ -288,7 +322,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) { shell.printfln(" #read fails (after %d retries): %d", TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_read_fail_count()); shell.printfln(" #write fails (after %d retries): %d", TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_write_fail_count()); shell.printfln(" Rx line quality: %d%%", rxservice_.quality()); - shell.printfln(" Tx line quality: %d%%", (txservice_.read_quality() + txservice_.read_quality()) / 2); + shell.printfln(" Tx line quality: %d%%", (txservice_.read_quality() + txservice_.write_quality()) / 2); shell.println(); } @@ -846,41 +880,58 @@ std::string EMSESP::pretty_telegram(std::shared_ptr telegram) { std::string src_name(""); std::string dest_name(""); std::string type_name(""); + + // Single loop to find all device information + bool src_found = false; + bool dest_found = false; + bool type_found = false; + for (const auto & emsdevice : emsdevices) { - // get src & dest - if (emsdevice->is_device_id(src)) { - src_name = emsdevice->device_type_name(); - } else if (emsdevice->is_device_id(dest)) { - dest_name = emsdevice->device_type_name(); + // get src name + if (!src_found && emsdevice->is_device_id(src)) { + src_name = emsdevice->device_type_name(); + src_found = true; } - // get the type name - if (type_name.empty()) { + + // get dest name + if (!dest_found && emsdevice->is_device_id(dest)) { + dest_name = emsdevice->device_type_name(); + dest_found = true; + } + + // get the type name (try primary conditions first) + if (!type_found) { if ((telegram->operation == Telegram::Operation::RX_READ && emsdevice->is_device_id(dest)) || (telegram->operation != Telegram::Operation::RX_READ && dest == 0 && emsdevice->is_device_id(src)) || (telegram->operation != Telegram::Operation::RX_READ && src == EMSbus::ems_bus_id() && emsdevice->is_device_id(dest))) { type_name = emsdevice->telegram_type_name(telegram); + if (!type_name.empty()) { + type_found = true; + } } } + + // Early exit if we found everything + if (src_found && dest_found && type_found) { + break; + } } - if (type_name.empty()) { - // fallback, get the type name from src - for (const auto & emsdevice : emsdevices) { - if (telegram->operation != Telegram::Operation::RX_READ && emsdevice->is_device_id(src)) { - type_name = emsdevice->telegram_type_name(telegram); - break; + + // Fallback for type name if not found - try src first, then dest + if (!type_found && telegram->operation != Telegram::Operation::RX_READ) { + for (int i = 0; i < 2 && type_name.empty(); ++i) { + uint8_t check_id = (i == 0) ? src : dest; + for (const auto & emsdevice : emsdevices) { + if (emsdevice->is_device_id(check_id)) { + type_name = emsdevice->telegram_type_name(telegram); + if (!type_name.empty()) { + break; + } + } } } } - if (type_name.empty()) { - // 2nd fallback, get the type name from dest - for (const auto & emsdevice : emsdevices) { - if (telegram->operation != Telegram::Operation::RX_READ && emsdevice->is_device_id(dest)) { - type_name = emsdevice->telegram_type_name(telegram); - break; - } - } - } // if we can't find names for the devices, use their hex values if (src_name.empty()) { src_name = device_tostring(src); @@ -1009,11 +1060,16 @@ void EMSESP::process_version(std::shared_ptr telegram) { if (telegram->offset != 0) { return; } + + const uint8_t msg_len = telegram->message_length; + // for empty telegram add device with empty product, version and brand - if (telegram->message_length == 0) { + if (msg_len == 0) { (void)add_device(telegram->src, 0, "00.00", 0); return; - } else if (telegram->message_length < 3) { + } + + if (msg_len < 3) { (void)add_device(telegram->src, telegram->message_data[0], "00.00", 0); send_read_request(EMSdevice::EMS_TYPE_NAME, telegram->src, 27); return; @@ -1023,7 +1079,7 @@ void EMSESP::process_version(std::shared_ptr telegram) { uint8_t offset = 0; if (telegram->message_data[0] == 0x00) { // see if we have a 2nd subscriber - if (telegram->message_length > 5 && telegram->message_data[3] != 0x00) { + if (msg_len > 5 && telegram->message_data[3] != 0x00) { offset = 3; } else { return; // ignore whole telegram @@ -1040,7 +1096,7 @@ 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) { + if (msg_len >= 10) { brand = EMSdevice::decode_brand(telegram->message_data[9]); } else { brand = EMSdevice::Brand::NO_BRAND; // unknown @@ -1116,57 +1172,53 @@ bool EMSESP::process_telegram(std::shared_ptr telegram) { // calls the associated process function for that EMS device // returns false if the device_id doesn't recognize it // after the telegram has been processed, see if there have been values changed and we need to do a MQTT publish - bool telegram_found = false; - uint8_t device_found = 0; - // broadcast or send to us + bool telegram_found = false; + uint8_t device_found = 0; + EMSdevice * found_device = nullptr; + + // Combined loop: check all conditions in a single pass for (const auto & emsdevice : emsdevices) { + // broadcast or send to us if (emsdevice->is_device_id(telegram->src) && (telegram->dest == 0 || telegram->dest == EMSbus::ems_bus_id())) { telegram_found = emsdevice->handle_telegram(telegram); - device_found = emsdevice->unique_id(); + found_device = emsdevice.get(); break; } - } - if (!telegram_found) { // check for command to the device - for (const auto & emsdevice : emsdevices) { - if (emsdevice->is_device_id(telegram->dest) && telegram->src != EMSbus::ems_bus_id()) { - telegram_found = emsdevice->handle_telegram(telegram); - device_found = emsdevice->unique_id(); - break; - } - } - } - if (!telegram_found) { - // check for sends to master thermostat - for (const auto & emsdevice : emsdevices) { - if (emsdevice->is_device_id(telegram->src) && telegram->dest == 0x10) { - telegram_found = emsdevice->handle_telegram(telegram); - device_found = emsdevice->unique_id(); - break; - } - } - } - for (const auto & emsdevice : emsdevices) { - if (emsdevice->unique_id() == device_found) { - if (!telegram_found && telegram->message_length > 0) { - emsdevice->add_handlers_ignored(telegram->type_id); - } - if (wait_validate_ == telegram->type_id) { - wait_validate_ = 0; - } - if (Mqtt::connected() && telegram_found - && ((mqtt_.get_publish_onchange(emsdevice->device_type()) && emsdevice->has_update()) - || (telegram->type_id == publish_id_ && telegram->dest == EMSbus::ems_bus_id()))) { - if (telegram->type_id == publish_id_) { - publish_id_ = 0; - } - emsdevice->has_update(false); // reset flag - if (!Mqtt::publish_single()) { - publish_device_values(emsdevice->device_type()); // publish to MQTT if we explicitly have too - } - } + if (!telegram_found && emsdevice->is_device_id(telegram->dest) && telegram->src != EMSbus::ems_bus_id()) { + telegram_found = emsdevice->handle_telegram(telegram); + found_device = emsdevice.get(); break; } + // check for sends to master thermostat + if (!telegram_found && emsdevice->is_device_id(telegram->src) && telegram->dest == 0x10) { + telegram_found = emsdevice->handle_telegram(telegram); + found_device = emsdevice.get(); + break; + } + } + + if (found_device) { + device_found = found_device->unique_id(); + + // Process the found device directly without another loop + if (!telegram_found && telegram->message_length > 0) { + found_device->add_handlers_ignored(telegram->type_id); + } + if (wait_validate_ == telegram->type_id) { + wait_validate_ = 0; + } + if (Mqtt::connected() && telegram_found + && ((mqtt_.get_publish_onchange(found_device->device_type()) && found_device->has_update()) + || (telegram->type_id == publish_id_ && telegram->dest == EMSbus::ems_bus_id()))) { + if (telegram->type_id == publish_id_) { + publish_id_ = 0; + } + found_device->has_update(false); // reset flag + if (!Mqtt::publish_single()) { + publish_device_values(found_device->device_type()); // publish to MQTT if we explicitly have too + } + } } // handle unknown broadcasted telegrams (or send to us) if (!telegram_found && (telegram->dest == 0 || telegram->dest == EMSbus::ems_bus_id())) { @@ -1184,6 +1236,10 @@ bool EMSESP::process_telegram(std::shared_ptr telegram) { // return true if we have this device already registered bool EMSESP::device_exists(const uint8_t device_id) { + if (emsdevices.empty()) { + return false; + } + for (const auto & emsdevice : emsdevices) { if (emsdevice && emsdevice->is_device_id(device_id)) { return true; @@ -1251,16 +1307,14 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const } // first check to see if we already have it, if so update the record - auto it = emsdevices.begin(); - for (const auto & emsdevice : emsdevices) { - if (emsdevice && emsdevice->is_device_id(device_id)) { - if (product_id == 0 || emsdevice->product_id() != 0) { // update only with valid product_id + for (auto it = emsdevices.begin(); it != emsdevices.end(); ++it) { + if ((*it) && (*it)->is_device_id(device_id)) { + if (product_id == 0 || (*it)->product_id() != 0) { // update only with valid product_id return true; } emsdevices.erase(it); // erase the old device without product_id and re detect break; } - it++; } // look up the rest of the details using the product_id and create the new device object @@ -1691,7 +1745,7 @@ void EMSESP::start() { device_library_ = { #include "device_library.h" }; - LOG_INFO("Loaded EMS device library (%d entries)", device_library_.size()); + LOG_INFO("Library loaded: %d EMS devices, %d device entities, %s", device_library_.size(), EMSESP_TRANSLATION_COUNT, system_.languages_string().c_str()); system_.reload_settings(); // ... and store some of the settings locally diff --git a/src/core/system.cpp b/src/core/system.cpp index 5ca1e9a4d..ac0ce94ef 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -107,6 +107,19 @@ bool System::command_send(const char * value, const int8_t id) { return EMSESP::txservice_.send_raw(value); // ignore id } +// return string of languages and count +std::string System::languages_string() { + std::string languages_string = std::to_string(NUM_LANGUAGES) + " languages ("; + for (uint8_t i = 0; i < NUM_LANGUAGES; i++) { + languages_string += languages[i]; + if (i != NUM_LANGUAGES - 1) { + languages_string += ","; + } + } + languages_string += ")"; + return languages_string; +} + // returns last response from MQTT bool System::command_response(const char * value, const int8_t id, JsonObject output) { JsonDocument doc; @@ -523,8 +536,18 @@ void System::button_OnClick(PButton & b) { // button double click void System::button_OnDblClick(PButton & b) { - LOG_NOTICE("Button pressed - double click - wifi reconnect"); - EMSESP::system_.wifi_reconnect(); + LOG_NOTICE("Button pressed - double click - wifi reconnect to AP"); + // set AP mode to always so will join AP if wifi ssid fails to connect + EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) { + apSettings.provisionMode = AP_MODE_ALWAYS; + return StateUpdateResult::CHANGED; + }); + // remove SSID from network settings + EMSESP::esp32React.getNetworkSettingsService()->update([&](NetworkSettings & networkSettings) { + networkSettings.ssid = ""; + return StateUpdateResult::CHANGED; + }); + EMSESP::esp32React.getNetworkSettingsService()->callUpdateHandlers(); // in case we've changed ssid or password } // button long press @@ -1439,6 +1462,7 @@ bool System::command_service(const char * cmd, const char * value) { ok = true; } } + int n; if (!ok && Helpers::value2number(value, n)) { #ifndef EMSESP_STANDALONE @@ -1647,26 +1671,38 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output } }); -#ifndef EMSESP_STANDALONE - EMSESP::esp32React.getAPSettingsService()->read([&](const APSettings & settings) { - const char * pM[] = {"always", "disconnected", "never"}; - node["APProvisionMode"] = pM[settings.provisionMode]; - node["APSecurity"] = settings.password.length() ? "wpa2" : "open"; - node["APSSID"] = settings.ssid; - }); -#endif - // NTP status node = output["ntp"].to(); -#ifndef EMSESP_STANDALONE - node["NTPStatus"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected"; EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) { +#ifndef EMSESP_STANDALONE node["enabled"] = settings.enabled; +#else + node["enabled"] = true; +#endif node["server"] = settings.server; node["tzLabel"] = settings.tzLabel; }); +#ifndef EMSESP_STANDALONE node["timestamp"] = time(nullptr); #endif + node["NTPStatus"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected"; + + // AP Status + node = output["ap"].to(); + EMSESP::esp32React.getAPSettingsService()->read([&](const APSettings & settings) { + const char * pM[] = {"always", "disconnected", "never"}; + node["provisionMode"] = pM[settings.provisionMode]; + node["ssid"] = settings.ssid; +#ifndef EMSESP_STANDALONE + node["security"] = settings.password.length() ? "wpa2" : "open"; + node["channel"] = settings.channel; + node["ssidHidden"] = settings.ssidHidden; + node["maxClients"] = settings.maxClients; + node["localIP"] = settings.localIP.toString(); + node["gatewayIP"] = settings.gatewayIP.toString(); + node["subnetMask"] = settings.subnetMask.toString(); +#endif + }); // MQTT Status node = output["mqtt"].to(); @@ -1764,7 +1800,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output node["busReadsFailed"] = EMSESP::txservice_.telegram_read_fail_count(); node["busWritesFailed"] = EMSESP::txservice_.telegram_write_fail_count(); node["busRxLineQuality"] = EMSESP::rxservice_.quality(); - node["busTxLineQuality"] = (EMSESP::txservice_.read_quality() + EMSESP::txservice_.read_quality()) / 2; + node["busTxLineQuality"] = (EMSESP::txservice_.read_quality() + EMSESP::txservice_.write_quality()) / 2; // Settings node = output["settings"].to(); @@ -1958,15 +1994,12 @@ bool System::load_board_profile(std::vector & data, const std::string & // format command - factory reset, removing all config files bool System::command_format(const char * value, const int8_t id) { - LOG_INFO("Removing all config files"); + LOG_INFO("Formatting FS, removing all config files"); #ifndef EMSESP_STANDALONE - // TODO To replaced with LittleFS.rmdir(FS_CONFIG_DIRECTORY) now we're using IDF 4.2+ - File root = LittleFS.open(EMSESP_FS_CONFIG_DIRECTORY); - File file; - while ((file = root.openNextFile())) { - String path = file.path(); - file.close(); - LittleFS.remove(path); + if (LittleFS.format()) { + LOG_INFO("FS formatted successfully"); + } else { + LOG_ERROR("Format failed"); } #endif diff --git a/src/core/system.h b/src/core/system.h index c151e2fd5..01bf97f88 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -291,6 +291,8 @@ class System { void wifi_reconnect(); void show_users(uuid::console::Shell & shell); + static std::string languages_string(); + uint32_t FStotal() { return fstotal_; } diff --git a/src/emsesp_version.h b/src/emsesp_version.h index caaa33454..cc360f69c 100644 --- a/src/emsesp_version.h +++ b/src/emsesp_version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.7.3-dev.25" +#define EMSESP_APP_VERSION "3.7.3-dev.26" diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index d1068861d..30a510376 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -63,13 +63,14 @@ void WebCustomization::read(WebCustomization & customizations, JsonObject root) // Analog Sensor customization JsonArray analogJson = root["as"].to(); for (const AnalogCustomization & sensor : customizations.analogCustomizations) { - JsonObject sensorJson = analogJson.add(); - sensorJson["gpio"] = sensor.gpio; // g - sensorJson["name"] = sensor.name; // n - sensorJson["offset"] = sensor.offset; // o - sensorJson["factor"] = sensor.factor; // f - sensorJson["uom"] = sensor.uom; // u - sensorJson["type"] = sensor.type; // t + JsonObject sensorJson = analogJson.add(); + sensorJson["gpio"] = sensor.gpio; // g + sensorJson["name"] = sensor.name; // n + sensorJson["offset"] = sensor.offset; // o + sensorJson["factor"] = sensor.factor; // f + sensorJson["uom"] = sensor.uom; // u + sensorJson["type"] = sensor.type; // t + sensorJson["is_system"] = sensor.is_system; // s } // Masked entities customization and custom device name (optional) @@ -115,13 +116,14 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c auto analogJsons = root["as"].as(); for (const JsonObject analogJson : analogJsons) { // create each of the sensor, overwriting any previous settings - auto analog = AnalogCustomization(); - analog.gpio = analogJson["gpio"]; - analog.name = analogJson["name"].as(); - analog.offset = analogJson["offset"]; - analog.factor = analogJson["factor"]; - analog.uom = analogJson["uom"]; - analog.type = analogJson["type"]; + auto analog = AnalogCustomization(); + analog.gpio = analogJson["gpio"]; + analog.name = analogJson["name"].as(); + analog.offset = analogJson["offset"]; + analog.factor = analogJson["factor"]; + analog.uom = analogJson["uom"]; + analog.type = analogJson["type"]; + analog.is_system = analogJson["is_system"]; if (_start && analog.type == EMSESP::analogsensor_.AnalogType::DIGITAL_OUT && analog.uom > DeviceValue::DeviceValueUOM::NONE) { analog.offset = analog.uom - 1; } diff --git a/src/web/WebCustomizationService.h b/src/web/WebCustomizationService.h index f12e5db33..9b416d213 100644 --- a/src/web/WebCustomizationService.h +++ b/src/web/WebCustomizationService.h @@ -45,8 +45,9 @@ class AnalogCustomization { std::string name; double offset; double factor; - uint8_t uom; // 0 is none - int8_t type; // -1 is for deletion + uint8_t uom; // 0 is none + int8_t type; // -1 is for deletion + bool is_system = false; // if true, the customization is a system customization // used for removing from a list bool operator==(const AnalogCustomization & a) const { diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index 735a35182..df4db1f3f 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -143,6 +143,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) { obj["o"] = sensor.offset(); obj["f"] = sensor.factor(); obj["t"] = sensor.type(); + obj["s"] = sensor.is_system(); if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) { obj["v"] = Helpers::transformNumFloat(sensor.value()); // is optional and is a float diff --git a/test/test_api/test_api.h b/test/test_api/test_api.h index b3c7094c5..d4b3cc60f 100644 --- a/test/test_api/test_api.h +++ b/test/test_api/test_api.h @@ -1,4 +1,3 @@ - // ---------- START - CUT HERE ---------- void test_1() { @@ -162,22 +161,24 @@ void test_21() { auto expected_response = "[{\"system\":{\"version\":\"dev\",\"uptime\":\"000+00:00:00.000\",\"uptimeSec\":0,\"resetReason\":\"Unknown / " "Unknown\"},\"network\":{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":-23,\"TxPowerSetting\":0,\"staticIP\":false,\"lowBandwidth\":false," - "\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{},\"mqtt\":{\"MQTTStatus\":\"disconnected\",\"MQTTPublishes\":0," - "\"MQTTQueued\":0,\"MQTTPublishFails\":0,\"MQTTReconnects\":0,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60,\"cleanSession\":false," - "\"entityFormat\":1,\"base\":\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1,\"haEnabled\":true,\"mqttQos\":0," - "\"mqttRetain\":false,\"publishTimeHeartbeat\":60,\"publishTimeBoiler\":10,\"publishTimeThermostat\":10,\"publishTimeSolar\":10,\"publishTimeMixer\":" - "10,\"publishTimeWater\":0,\"publishTimeOther\":10,\"publishTimeSensor\":10,\"publishSingle\":false,\"publish2command\":false,\"sendResponse\":false}," - "\"syslog\":{\"enabled\":false},\"sensor\":{\"temperatureSensors\":2,\"temperatureSensorReads\":0,\"temperatureSensorFails\":0,\"analogSensors\":4," - "\"analogSensorReads\":0,\"analogSensorFails\":0},\"api\":{\"APICalls\":0,\"APIFails\":0},\"bus\":{\"busStatus\":\"connected\",\"busProtocol\":" - "\"Buderus\",\"busTelegramsReceived\":8,\"busReads\":0,\"busWrites\":0,\"busIncompleteTelegrams\":0,\"busReadsFailed\":0,\"busWritesFailed\":0," - "\"busRxLineQuality\":100,\"busTxLineQuality\":100},\"settings\":{\"boardProfile\":\"S32\",\"locale\":\"en\",\"txMode\":8,\"emsBusID\":11," - "\"showerTimer\":false,\"showerMinDuration\":180,\"showerAlert\":false,\"hideLed\":false,\"noTokenApi\":false,\"readonlyMode\":false,\"fahrenheit\":" - "false,\"dallasParasite\":false,\"boolFormat\":1,\"boolDashboard\":1,\"enumFormat\":1,\"analogEnabled\":true,\"telnetEnabled\":true," - "\"maxWebLogBuffer\":25,\"modbusEnabled\":false,\"forceHeatingOff\":false,\"developerMode\":false},\"devices\":[{\"type\":\"boiler\",\"name\":\"My " - "Custom " + "\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{\"enabled\":true,\"server\":\"pool.ntp.org\",\"tzLabel\":\"Europe/" + "London\",\"NTPStatus\":\"disconnected\"},\"ap\":{\"provisionMode\":\"always\",\"ssid\":\"ems-esp\"},\"mqtt\":{\"MQTTStatus\":\"disconnected\"," + "\"MQTTPublishes\":0,\"MQTTQueued\":0,\"MQTTPublishFails\":0,\"MQTTReconnects\":0,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60," + "\"cleanSession\":false,\"entityFormat\":1,\"base\":\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1," + "\"haEnabled\":true,\"mqttQos\":0,\"mqttRetain\":false,\"publishTimeHeartbeat\":60,\"publishTimeBoiler\":10,\"publishTimeThermostat\":10," + "\"publishTimeSolar\":10,\"publishTimeMixer\":10,\"publishTimeWater\":0,\"publishTimeOther\":10,\"publishTimeSensor\":10,\"publishSingle\":false," + "\"publish2command\":false,\"sendResponse\":false},\"syslog\":{\"enabled\":false},\"sensor\":{\"temperatureSensors\":2,\"temperatureSensorReads\":0," + "\"temperatureSensorFails\":0,\"analogSensors\":4,\"analogSensorReads\":0,\"analogSensorFails\":0},\"api\":{\"APICalls\":0,\"APIFails\":0},\"bus\":{" + "\"busStatus\":\"connected\",\"busProtocol\":\"Buderus\",\"busTelegramsReceived\":8,\"busReads\":0,\"busWrites\":0,\"busIncompleteTelegrams\":0," + "\"busReadsFailed\":0,\"busWritesFailed\":0,\"busRxLineQuality\":100,\"busTxLineQuality\":100},\"settings\":{\"boardProfile\":\"S32\",\"locale\":" + "\"en\",\"txMode\":8,\"emsBusID\":11,\"showerTimer\":false,\"showerMinDuration\":180,\"showerAlert\":false,\"hideLed\":false,\"noTokenApi\":false," + "\"readonlyMode\":false,\"fahrenheit\":false,\"dallasParasite\":false,\"boolFormat\":1,\"boolDashboard\":1,\"enumFormat\":1,\"analogEnabled\":true," + "\"telnetEnabled\":true,\"maxWebLogBuffer\":25,\"modbusEnabled\":false,\"forceHeatingOff\":false,\"developerMode\":false},\"devices\":[{\"type\":" + "\"boiler\",\"name\":\"My Custom " "Boiler\",\"deviceID\":\"0x08\",\"productID\":123,\"brand\":\"\",\"version\":\"01.00\",\"entities\":38,\"handlersReceived\":\"0x18\"," "\"handlersFetched\":\"0x14 0x33\",\"handlersPending\":\"0xBF 0x10 0x11 0xC2 0xC6 0x15 0x1C 0x19 0x1A 0x35 0x34 0x2A 0xD1 0xE3 0xE4 0xE5 0xE9 0x02E0 " - "0x2E 0x3B\"},{\"type\":\"thermostat\",\"name\":\"FW120\",\"deviceID\":\"0x10\",\"productID\":192,\"brand\":\"\",\"version\":\"01.00\",\"entities\":15," + "0x2E " + "0x3B\"},{\"type\":\"thermostat\",\"name\":\"FW120\",\"deviceID\":\"0x10\",\"productID\":192,\"brand\":\"\",\"version\":\"01.00\",\"entities\":15," "\"handlersReceived\":\"0x016F\",\"handlersFetched\":\"0x0170 0x0171\",\"handlersPending\":\"0xA3 0x06 0xA2 0x12 0x13 0x0172 0x0165 " "0x0168\"},{\"type\":\"temperaturesensor\",\"name\":\"temperaturesensor\",\"entities\":2},{\"type\":\"analogsensor\",\"name\":\"analogsensor\"," "\"entities\":4},{\"type\":\"scheduler\",\"name\":\"scheduler\",\"entities\":2},{\"type\":\"custom\",\"name\":\"custom\",\"entities\":4}]}]"; @@ -188,22 +189,24 @@ void test_22() { auto expected_response = "[{\"system\":{\"version\":\"dev\",\"uptime\":\"000+00:00:00.000\",\"uptimeSec\":0,\"resetReason\":\"Unknown / " "Unknown\"},\"network\":{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":-23,\"TxPowerSetting\":0,\"staticIP\":false,\"lowBandwidth\":false," - "\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{},\"mqtt\":{\"MQTTStatus\":\"disconnected\",\"MQTTPublishes\":0," - "\"MQTTQueued\":0,\"MQTTPublishFails\":0,\"MQTTReconnects\":0,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60,\"cleanSession\":false," - "\"entityFormat\":1,\"base\":\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1,\"haEnabled\":true,\"mqttQos\":0," - "\"mqttRetain\":false,\"publishTimeHeartbeat\":60,\"publishTimeBoiler\":10,\"publishTimeThermostat\":10,\"publishTimeSolar\":10,\"publishTimeMixer\":" - "10,\"publishTimeWater\":0,\"publishTimeOther\":10,\"publishTimeSensor\":10,\"publishSingle\":false,\"publish2command\":false,\"sendResponse\":false}," - "\"syslog\":{\"enabled\":false},\"sensor\":{\"temperatureSensors\":2,\"temperatureSensorReads\":0,\"temperatureSensorFails\":0,\"analogSensors\":4," - "\"analogSensorReads\":0,\"analogSensorFails\":0},\"api\":{\"APICalls\":0,\"APIFails\":0},\"bus\":{\"busStatus\":\"connected\",\"busProtocol\":" - "\"Buderus\",\"busTelegramsReceived\":8,\"busReads\":0,\"busWrites\":0,\"busIncompleteTelegrams\":0,\"busReadsFailed\":0,\"busWritesFailed\":0," - "\"busRxLineQuality\":100,\"busTxLineQuality\":100},\"settings\":{\"boardProfile\":\"S32\",\"locale\":\"en\",\"txMode\":8,\"emsBusID\":11," - "\"showerTimer\":false,\"showerMinDuration\":180,\"showerAlert\":false,\"hideLed\":false,\"noTokenApi\":false,\"readonlyMode\":false,\"fahrenheit\":" - "false,\"dallasParasite\":false,\"boolFormat\":1,\"boolDashboard\":1,\"enumFormat\":1,\"analogEnabled\":true,\"telnetEnabled\":true," - "\"maxWebLogBuffer\":25,\"modbusEnabled\":false,\"forceHeatingOff\":false,\"developerMode\":false},\"devices\":[{\"type\":\"boiler\",\"name\":\"My " - "Custom " + "\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{\"enabled\":true,\"server\":\"pool.ntp.org\",\"tzLabel\":\"Europe/" + "London\",\"NTPStatus\":\"disconnected\"},\"ap\":{\"provisionMode\":\"always\",\"ssid\":\"ems-esp\"},\"mqtt\":{\"MQTTStatus\":\"disconnected\"," + "\"MQTTPublishes\":0,\"MQTTQueued\":0,\"MQTTPublishFails\":0,\"MQTTReconnects\":0,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60," + "\"cleanSession\":false,\"entityFormat\":1,\"base\":\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1," + "\"haEnabled\":true,\"mqttQos\":0,\"mqttRetain\":false,\"publishTimeHeartbeat\":60,\"publishTimeBoiler\":10,\"publishTimeThermostat\":10," + "\"publishTimeSolar\":10,\"publishTimeMixer\":10,\"publishTimeWater\":0,\"publishTimeOther\":10,\"publishTimeSensor\":10,\"publishSingle\":false," + "\"publish2command\":false,\"sendResponse\":false},\"syslog\":{\"enabled\":false},\"sensor\":{\"temperatureSensors\":2,\"temperatureSensorReads\":0," + "\"temperatureSensorFails\":0,\"analogSensors\":4,\"analogSensorReads\":0,\"analogSensorFails\":0},\"api\":{\"APICalls\":0,\"APIFails\":0},\"bus\":{" + "\"busStatus\":\"connected\",\"busProtocol\":\"Buderus\",\"busTelegramsReceived\":8,\"busReads\":0,\"busWrites\":0,\"busIncompleteTelegrams\":0," + "\"busReadsFailed\":0,\"busWritesFailed\":0,\"busRxLineQuality\":100,\"busTxLineQuality\":100},\"settings\":{\"boardProfile\":\"S32\",\"locale\":" + "\"en\",\"txMode\":8,\"emsBusID\":11,\"showerTimer\":false,\"showerMinDuration\":180,\"showerAlert\":false,\"hideLed\":false,\"noTokenApi\":false," + "\"readonlyMode\":false,\"fahrenheit\":false,\"dallasParasite\":false,\"boolFormat\":1,\"boolDashboard\":1,\"enumFormat\":1,\"analogEnabled\":true," + "\"telnetEnabled\":true,\"maxWebLogBuffer\":25,\"modbusEnabled\":false,\"forceHeatingOff\":false,\"developerMode\":false},\"devices\":[{\"type\":" + "\"boiler\",\"name\":\"My Custom " "Boiler\",\"deviceID\":\"0x08\",\"productID\":123,\"brand\":\"\",\"version\":\"01.00\",\"entities\":38,\"handlersReceived\":\"0x18\"," "\"handlersFetched\":\"0x14 0x33\",\"handlersPending\":\"0xBF 0x10 0x11 0xC2 0xC6 0x15 0x1C 0x19 0x1A 0x35 0x34 0x2A 0xD1 0xE3 0xE4 0xE5 0xE9 0x02E0 " - "0x2E 0x3B\"},{\"type\":\"thermostat\",\"name\":\"FW120\",\"deviceID\":\"0x10\",\"productID\":192,\"brand\":\"\",\"version\":\"01.00\",\"entities\":15," + "0x2E " + "0x3B\"},{\"type\":\"thermostat\",\"name\":\"FW120\",\"deviceID\":\"0x10\",\"productID\":192,\"brand\":\"\",\"version\":\"01.00\",\"entities\":15," "\"handlersReceived\":\"0x016F\",\"handlersFetched\":\"0x0170 0x0171\",\"handlersPending\":\"0xA3 0x06 0xA2 0x12 0x13 0x0172 0x0165 " "0x0168\"},{\"type\":\"temperaturesensor\",\"name\":\"temperaturesensor\",\"entities\":2},{\"type\":\"analogsensor\",\"name\":\"analogsensor\"," "\"entities\":4},{\"type\":\"scheduler\",\"name\":\"scheduler\",\"entities\":2},{\"type\":\"custom\",\"name\":\"custom\",\"entities\":4}]}]"; @@ -281,8 +284,9 @@ void test_35() { } void test_36() { - auto expected_response = "[{\"name\":\"test_analogsensor1\",\"fullname\":\"test_analogsensor1\",\"gpio\":36,\"type\":\"number\",\"analog\":\"adc\"," - "\"value\":0,\"readable\":true,\"writeable\":false,\"visible\":true,\"offset\":0,\"factor\":0.1,\"uom\":\"mV\"}]"; + auto expected_response = + "[{\"name\":\"test_analogsensor1\",\"fullname\":\"test_analogsensor1\",\"gpio\":36,\"type\":\"number\",\"analog\":\"adc\",\"value\":0,\"readable\":" + "true,\"writeable\":false,\"visible\":true,\"is_system\":true,\"offset\":0,\"factor\":0.1,\"uom\":\"mV\"}]"; TEST_ASSERT_EQUAL_STRING(expected_response, call_url("/api/analogsensor/test_analogsensor1")); }