From 55b8c2d04cc7eb22c8d14033d0f108ae54b792ea Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 11 Dec 2025 23:52:44 +0100 Subject: [PATCH] fix standalone/make, fix HA avty, fix deprecated arduinojson, update packages --- Makefile | 4 +- cspell.json | 3 +- interface/package.json | 12 +- interface/pnpm-lock.yaml | 388 ++++++++++++++--------------- mock-api/package.json | 2 +- platformio.ini | 1 + src/core/analogsensor.cpp | 11 +- src/core/command.cpp | 18 +- src/core/emsesp.cpp | 55 +++- src/core/helpers.cpp | 82 +++--- src/core/mqtt.cpp | 97 ++++---- src/core/mqtt.h | 11 +- src/core/shower.cpp | 4 +- src/core/system.cpp | 257 ++++++++++--------- src/core/temperaturesensor.cpp | 13 +- src/web/WebCustomEntityService.cpp | 57 +++-- src/web/WebSchedulerService.cpp | 6 +- src/web/WebSchedulerService.h | 2 +- 18 files changed, 535 insertions(+), 488 deletions(-) diff --git a/Makefile b/Makefile index 324f20025..7bbdc2383 100644 --- a/Makefile +++ b/Makefile @@ -47,8 +47,8 @@ MAKEFLAGS += -j$(JOBS) -l$(shell echo $$(($(JOBS) * 2))) #---------------------------------------------------------------------- TARGET := emsesp BUILD := build -SOURCES := src/core src/devices src/web src/test lib_standalone lib/semver lib/espMqttClient/src lib/espMqttClient/src/* lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/PButton -INCLUDES := src/core src/devices src/web src/test lib/* lib_standalone lib/semver lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src +SOURCES := src/core src/devices src/web src/test lib_standalone lib/semver lib/espMqttClient/src lib/espMqttClient/src/* lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/PButton +INCLUDES := src/core src/devices src/web src/test lib_standalone lib/* lib/semver lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src LIBRARIES := CPPCHECK = cppcheck diff --git a/cspell.json b/cspell.json index 7e1f3c11b..24df974df 100644 --- a/cspell.json +++ b/cspell.json @@ -34,6 +34,7 @@ "sdkconfig.*", "managed_components/**", "pnpm-*.yaml", - "vite.config.ts" + "vite.config.ts", + "lib/esp32-psram/**" ] } \ No newline at end of file diff --git a/interface/package.json b/interface/package.json index 410927bd7..35a3bc339 100644 --- a/interface/package.json +++ b/interface/package.json @@ -38,8 +38,8 @@ "magic-string": "^0.30.21", "mime-types": "^3.0.2", "preact": "^10.28.0", - "react": "^19.2.1", - "react-dom": "^19.2.1", + "react": "^19.2.2", + "react-dom": "^19.2.2", "react-icons": "^5.5.0", "react-router": "^7.10.1", "react-toastify": "^11.0.5", @@ -52,7 +52,7 @@ "@preact/compat": "^18.3.1", "@preact/preset-vite": "^2.10.2", "@trivago/prettier-plugin-sort-imports": "^6.0.0", - "@types/node": "^24.10.1", + "@types/node": "^25.0.0", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "axe-core": "^4.11.0", @@ -62,10 +62,10 @@ "prettier": "^3.7.4", "rollup-plugin-visualizer": "^6.0.5", "terser": "^5.44.1", - "typescript-eslint": "^8.48.1", - "vite": "^7.2.6", + "typescript-eslint": "^8.49.0", + "vite": "^7.2.7", "vite-plugin-imagemin": "^0.6.1", "vite-tsconfig-paths": "^5.1.4" }, - "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a" + "packageManager": "pnpm@10.25.0+sha512.5e82639027af37cf832061bcc6d639c219634488e0f2baebe785028a793de7b525ffcd3f7ff574f5e9860654e098fe852ba8ac5dd5cefe1767d23a020a92f501" } diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index 9f4300c11..767321011 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -13,22 +13,22 @@ importers: version: 2.3.0(alova@3.4.0) '@emotion/react': specifier: ^11.14.0 - version: 11.14.0(@types/react@19.2.7)(react@19.2.1) + version: 11.14.0(@types/react@19.2.7)(react@19.2.2) '@emotion/styled': specifier: ^11.14.1 - version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1) + version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2) '@mui/icons-material': specifier: ^7.3.6 - version: 7.3.6(@mui/material@7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@types/react@19.2.7)(react@19.2.1) + version: 7.3.6(@mui/material@7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react-dom@19.2.2(react@19.2.2))(react@19.2.2))(@types/react@19.2.7)(react@19.2.2) '@mui/material': specifier: ^7.3.6 - version: 7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react-dom@19.2.2(react@19.2.2))(react@19.2.2) '@preact/compat': specifier: ^18.3.1 version: 18.3.1(preact@10.28.0) '@table-library/react-table-library': specifier: 4.1.15 - version: 4.1.15(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 4.1.15(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(react-dom@19.2.2(react@19.2.2))(react@19.2.2) alova: specifier: 3.4.0 version: 3.4.0 @@ -54,20 +54,20 @@ importers: specifier: ^10.28.0 version: 10.28.0 react: - specifier: ^19.2.1 - version: 19.2.1 + specifier: ^19.2.2 + version: 19.2.2 react-dom: - specifier: ^19.2.1 - version: 19.2.1(react@19.2.1) + specifier: ^19.2.2 + version: 19.2.2(react@19.2.2) react-icons: specifier: ^5.5.0 - version: 5.5.0(react@19.2.1) + version: 5.5.0(react@19.2.2) react-router: specifier: ^7.10.1 - version: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 7.10.1(react-dom@19.2.2(react@19.2.2))(react@19.2.2) react-toastify: specifier: ^11.0.5 - version: 11.0.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 11.0.5(react-dom@19.2.2(react@19.2.2))(react@19.2.2) typesafe-i18n: specifier: ^5.26.2 version: 5.26.2(typescript@5.9.3) @@ -83,13 +83,13 @@ importers: version: 9.39.1 '@preact/preset-vite': specifier: ^2.10.2 - version: 2.10.2(@babel/core@7.28.5)(preact@10.28.0)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) + version: 2.10.2(@babel/core@7.28.5)(preact@10.28.0)(vite@7.2.7(@types/node@25.0.0)(terser@5.44.1)) '@trivago/prettier-plugin-sort-imports': specifier: ^6.0.0 version: 6.0.0(prettier@3.7.4) '@types/node': - specifier: ^24.10.1 - version: 24.10.1 + specifier: ^25.0.0 + version: 25.0.0 '@types/react': specifier: ^19.2.7 version: 19.2.7 @@ -118,17 +118,17 @@ importers: specifier: ^5.44.1 version: 5.44.1 typescript-eslint: - specifier: ^8.48.1 - version: 8.48.1(eslint@9.39.1)(typescript@5.9.3) + specifier: ^8.49.0 + version: 8.49.0(eslint@9.39.1)(typescript@5.9.3) vite: - specifier: ^7.2.6 - version: 7.2.6(@types/node@24.10.1)(terser@5.44.1) + specifier: ^7.2.7 + version: 7.2.7(@types/node@25.0.0)(terser@5.44.1) vite-plugin-imagemin: specifier: ^0.6.1 - version: 0.6.1(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) + version: 0.6.1(vite@7.2.7(@types/node@25.0.0)(terser@5.44.1)) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@25.0.0)(terser@5.44.1)) packages: @@ -860,8 +860,8 @@ packages: resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. - '@types/node@24.10.1': - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/node@25.0.0': + resolution: {integrity: sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -888,63 +888,63 @@ packages: '@types/svgo@2.6.4': resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} - '@typescript-eslint/eslint-plugin@8.48.1': - resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} + '@typescript-eslint/eslint-plugin@8.49.0': + resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.48.1 + '@typescript-eslint/parser': ^8.49.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.48.1': - resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} + '@typescript-eslint/parser@8.49.0': + resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.48.1': - resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} + '@typescript-eslint/project-service@8.49.0': + resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.48.1': - resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} + '@typescript-eslint/scope-manager@8.49.0': + resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.48.1': - resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} + '@typescript-eslint/tsconfig-utils@8.49.0': + resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.48.1': - resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} + '@typescript-eslint/type-utils@8.49.0': + resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.48.1': - resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} + '@typescript-eslint/types@8.49.0': + resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.48.1': - resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} + '@typescript-eslint/typescript-estree@8.49.0': + resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.48.1': - resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} + '@typescript-eslint/utils@8.49.0': + resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.48.1': - resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} + '@typescript-eslint/visitor-keys@8.49.0': + resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} acorn-jsx@5.3.2: @@ -1027,8 +1027,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.2: - resolution: {integrity: sha512-PxSsosKQjI38iXkmb3d0Y32efqyA0uW4s41u4IVBsLlWLhCiYNpH/AfNOVWRqCQBlD8TFJTz6OUWNd4DFJCnmw==} + baseline-browser-mapping@2.9.6: + resolution: {integrity: sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==} 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.30001759: - resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} + caniuse-lite@1.0.30001760: + resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} 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.265: - resolution: {integrity: sha512-B7IkLR1/AE+9jR2LtVF/1/6PFhY5TlnEHnlrKmGk7PvkJibg5jr+mLXLLzq3QYl6PA1T/vLDthQPqIPAlS/PPA==} + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1815,9 +1815,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - has-ansi@2.0.0: resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} engines: {node: '>=0.10.0'} @@ -2512,10 +2509,10 @@ packages: rate-limiter-flexible@5.0.5: resolution: {integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==} - react-dom@19.2.1: - resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} + react-dom@19.2.2: + resolution: {integrity: sha512-fhyD2BLrew6qYf4NNtHff1rLXvzR25rq49p+FeqByOazc6TcSi2n8EYulo5C1PbH+1uBW++5S1SG7FcUU6mlDg==} peerDependencies: - react: ^19.2.1 + react: ^19.2.2 react-icons@5.5.0: resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} @@ -2525,8 +2522,8 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-is@19.2.1: - resolution: {integrity: sha512-L7BnWgRbMwzMAubQcS7sXdPdNLmKlucPlopgAzx7FtYbksWZgEWiuYM5x9T6UqS2Ne0rsgQTq5kY2SGqpzUkYA==} + react-is@19.2.2: + resolution: {integrity: sha512-ADrk8mxPwhyj+IB8EnMMRzxnon5hJrOdEZl+mCrrHXfPGGPYHdRm1962fvXUiWAu/ZC1G+OTgBN3Puq57iS4Yg==} react-router@7.10.1: resolution: {integrity: sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==} @@ -2563,8 +2560,8 @@ packages: react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react@19.2.1: - resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} + react@19.2.2: + resolution: {integrity: sha512-BdOGOY8OKRBcgoDkwqA8Q5XvOIhoNx/Sh6BnGJlet2Abt0X5BK0BDrqGyQgLhAVjD2nAg5f6o01u/OPUhG022Q==} engines: {node: '>=0.10.0'} read-pkg-up@1.0.1: @@ -2927,8 +2924,8 @@ packages: peerDependencies: typescript: '>=3.5.1' - typescript-eslint@8.48.1: - resolution: {integrity: sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==} + typescript-eslint@8.49.0: + resolution: {integrity: sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2999,8 +2996,8 @@ packages: vite: optional: true - vite@7.2.6: - resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} + vite@7.2.7: + resolution: {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3268,17 +3265,17 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1)': + '@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2)': dependencies: '@babel/runtime': 7.28.4 '@emotion/babel-plugin': 11.13.5 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.1) + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.2) '@emotion/utils': 1.4.2 '@emotion/weak-memoize': 0.4.0 hoist-non-react-statics: 3.3.2 - react: 19.2.1 + react: 19.2.2 optionalDependencies: '@types/react': 19.2.7 transitivePeerDependencies: @@ -3294,16 +3291,16 @@ snapshots: '@emotion/sheet@1.4.0': {} - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1)': + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2)': dependencies: '@babel/runtime': 7.28.4 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.4.0 - '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.1) + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.2) '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.1) + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.2) '@emotion/utils': 1.4.2 - react: 19.2.1 + react: 19.2.2 optionalDependencies: '@types/react': 19.2.7 transitivePeerDependencies: @@ -3311,9 +3308,9 @@ snapshots: '@emotion/unitless@0.10.0': {} - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.1)': + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.2)': dependencies: - react: 19.2.1 + react: 19.2.2 '@emotion/utils@1.4.2': {} @@ -3489,45 +3486,45 @@ snapshots: '@mui/core-downloads-tracker@7.3.6': {} - '@mui/icons-material@7.3.6(@mui/material@7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@types/react@19.2.7)(react@19.2.1)': + '@mui/icons-material@7.3.6(@mui/material@7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react-dom@19.2.2(react@19.2.2))(react@19.2.2))(@types/react@19.2.7)(react@19.2.2)': dependencies: '@babel/runtime': 7.28.4 - '@mui/material': 7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 + '@mui/material': 7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react-dom@19.2.2(react@19.2.2))(react@19.2.2) + react: 19.2.2 optionalDependencies: '@types/react': 19.2.7 - '@mui/material@7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@mui/material@7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react-dom@19.2.2(react@19.2.2))(react@19.2.2)': dependencies: '@babel/runtime': 7.28.4 '@mui/core-downloads-tracker': 7.3.6 - '@mui/system': 7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1) + '@mui/system': 7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2) '@mui/types': 7.4.9(@types/react@19.2.7) - '@mui/utils': 7.3.6(@types/react@19.2.7)(react@19.2.1) + '@mui/utils': 7.3.6(@types/react@19.2.7)(react@19.2.2) '@popperjs/core': 2.11.8 '@types/react-transition-group': 4.4.12(@types/react@19.2.7) clsx: 2.1.1 csstype: 3.2.3 prop-types: 15.8.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - react-is: 19.2.1 - react-transition-group: 4.4.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.2 + react-dom: 19.2.2(react@19.2.2) + react-is: 19.2.2 + react-transition-group: 4.4.5(react-dom@19.2.2(react@19.2.2))(react@19.2.2) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1) + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.2) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2) '@types/react': 19.2.7 - '@mui/private-theming@7.3.6(@types/react@19.2.7)(react@19.2.1)': + '@mui/private-theming@7.3.6(@types/react@19.2.7)(react@19.2.2)': dependencies: '@babel/runtime': 7.28.4 - '@mui/utils': 7.3.6(@types/react@19.2.7)(react@19.2.1) + '@mui/utils': 7.3.6(@types/react@19.2.7)(react@19.2.2) prop-types: 15.8.1 - react: 19.2.1 + react: 19.2.2 optionalDependencies: '@types/react': 19.2.7 - '@mui/styled-engine@7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1))(react@19.2.1)': + '@mui/styled-engine@7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(react@19.2.2)': dependencies: '@babel/runtime': 7.28.4 '@emotion/cache': 11.14.0 @@ -3535,25 +3532,25 @@ snapshots: '@emotion/sheet': 1.4.0 csstype: 3.2.3 prop-types: 15.8.1 - react: 19.2.1 + react: 19.2.2 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1) + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.2) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2) - '@mui/system@7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1)': + '@mui/system@7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2)': dependencies: '@babel/runtime': 7.28.4 - '@mui/private-theming': 7.3.6(@types/react@19.2.7)(react@19.2.1) - '@mui/styled-engine': 7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1))(react@19.2.1) + '@mui/private-theming': 7.3.6(@types/react@19.2.7)(react@19.2.2) + '@mui/styled-engine': 7.3.6(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2))(react@19.2.2) '@mui/types': 7.4.9(@types/react@19.2.7) - '@mui/utils': 7.3.6(@types/react@19.2.7)(react@19.2.1) + '@mui/utils': 7.3.6(@types/react@19.2.7)(react@19.2.2) clsx: 2.1.1 csstype: 3.2.3 prop-types: 15.8.1 - react: 19.2.1 + react: 19.2.2 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(@types/react@19.2.7)(react@19.2.1) + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.2) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(@types/react@19.2.7)(react@19.2.2) '@types/react': 19.2.7 '@mui/types@7.4.9(@types/react@19.2.7)': @@ -3562,15 +3559,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - '@mui/utils@7.3.6(@types/react@19.2.7)(react@19.2.1)': + '@mui/utils@7.3.6(@types/react@19.2.7)(react@19.2.2)': dependencies: '@babel/runtime': 7.28.4 '@mui/types': 7.4.9(@types/react@19.2.7) '@types/prop-types': 15.7.15 clsx: 2.1.1 prop-types: 15.8.1 - react: 19.2.1 - react-is: 19.2.1 + react: 19.2.2 + react-is: 19.2.2 optionalDependencies: '@types/react': 19.2.7 @@ -3598,18 +3595,18 @@ snapshots: dependencies: preact: 10.28.0 - '@preact/preset-vite@2.10.2(@babel/core@7.28.5)(preact@10.28.0)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1))': + '@preact/preset-vite@2.10.2(@babel/core@7.28.5)(preact@10.28.0)(vite@7.2.7(@types/node@25.0.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.28.0)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) + '@prefresh/vite': 2.4.11(preact@10.28.0)(vite@7.2.7(@types/node@25.0.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.6(@types/node@24.10.1)(terser@5.44.1) - vite-prerender-plugin: 0.5.12(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) + vite: 7.2.7(@types/node@25.0.0)(terser@5.44.1) + vite-prerender-plugin: 0.5.12(vite@7.2.7(@types/node@25.0.0)(terser@5.44.1)) transitivePeerDependencies: - preact - supports-color @@ -3622,7 +3619,7 @@ snapshots: '@prefresh/utils@1.2.1': {} - '@prefresh/vite@2.4.11(preact@10.28.0)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1))': + '@prefresh/vite@2.4.11(preact@10.28.0)(vite@7.2.7(@types/node@25.0.0)(terser@5.44.1))': dependencies: '@babel/core': 7.28.5 '@prefresh/babel-plugin': 0.5.2 @@ -3630,7 +3627,7 @@ snapshots: '@prefresh/utils': 1.2.1 '@rollup/pluginutils': 4.2.1 preact: 10.28.0 - vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1) + vite: 7.2.7(@types/node@25.0.0)(terser@5.44.1) transitivePeerDependencies: - supports-color @@ -3707,14 +3704,14 @@ snapshots: '@sindresorhus/is@0.7.0': {} - '@table-library/react-table-library@4.1.15(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@table-library/react-table-library@4.1.15(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.2))(react-dom@19.2.2(react@19.2.2))(react@19.2.2)': dependencies: - '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.1) + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.2) clsx: 1.1.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - react-virtualized-auto-sizer: 1.0.26(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react-window: 1.8.11(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.2 + react-dom: 19.2.2(react@19.2.2) + react-virtualized-auto-sizer: 1.0.26(react-dom@19.2.2(react@19.2.2))(react@19.2.2) + react-window: 1.8.11(react-dom@19.2.2(react@19.2.2))(react@19.2.2) '@trivago/prettier-plugin-sort-imports@6.0.0(prettier@3.7.4)': dependencies: @@ -3737,7 +3734,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 - '@types/node': 24.10.1 + '@types/node': 25.0.0 '@types/imagemin-gifsicle@7.0.4': dependencies: @@ -3766,19 +3763,19 @@ snapshots: '@types/imagemin@7.0.1': dependencies: - '@types/node': 24.10.1 + '@types/node': 25.0.0 '@types/json-schema@7.0.15': {} '@types/keyv@3.1.4': dependencies: - '@types/node': 24.10.1 + '@types/node': 25.0.0 '@types/minimatch@6.0.0': dependencies: minimatch: 10.1.1 - '@types/node@24.10.1': + '@types/node@25.0.0': dependencies: undici-types: 7.16.0 @@ -3800,22 +3797,21 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 24.10.1 + '@types/node': 25.0.0 '@types/svgo@2.6.4': dependencies: - '@types/node': 24.10.1 + '@types/node': 25.0.0 - '@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 eslint: 9.39.1 - graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -3823,41 +3819,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.48.1': + '@typescript-eslint/scope-manager@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 - '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -3865,14 +3861,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.48.1': {} + '@typescript-eslint/types@8.49.0': {} - '@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/visitor-keys': 8.48.1 + '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.3 @@ -3882,20 +3878,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.48.1(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) - '@typescript-eslint/scope-manager': 8.48.1 - '@typescript-eslint/types': 8.48.1 - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.48.1': + '@typescript-eslint/visitor-keys@8.49.0': dependencies: - '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 acorn-jsx@5.3.2(acorn@8.15.0): @@ -3962,7 +3958,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.9.2: {} + baseline-browser-mapping@2.9.6: {} bin-build@3.0.0: dependencies: @@ -4019,9 +4015,9 @@ snapshots: browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.2 - caniuse-lite: 1.0.30001759 - electron-to-chromium: 1.5.265 + baseline-browser-mapping: 2.9.6 + caniuse-lite: 1.0.30001760 + electron-to-chromium: 1.5.267 node-releases: 2.0.27 update-browserslist-db: 1.2.2(browserslist@4.28.1) @@ -4079,7 +4075,7 @@ snapshots: camelcase@2.1.1: {} - caniuse-lite@1.0.30001759: {} + caniuse-lite@1.0.30001760: {} caw@2.0.1: dependencies: @@ -4366,7 +4362,7 @@ snapshots: duplexer3@0.1.5: {} - electron-to-chromium@1.5.265: {} + electron-to-chromium@1.5.267: {} emoji-regex@8.0.0: {} @@ -4895,8 +4891,6 @@ snapshots: graceful-fs@4.2.11: {} - graphemer@1.4.0: {} - has-ansi@2.0.0: dependencies: ansi-regex: 2.1.1 @@ -5518,55 +5512,55 @@ snapshots: rate-limiter-flexible@5.0.5: {} - react-dom@19.2.1(react@19.2.1): + react-dom@19.2.2(react@19.2.2): dependencies: - react: 19.2.1 + react: 19.2.2 scheduler: 0.27.0 - react-icons@5.5.0(react@19.2.1): + react-icons@5.5.0(react@19.2.2): dependencies: - react: 19.2.1 + react: 19.2.2 react-is@16.13.1: {} - react-is@19.2.1: {} + react-is@19.2.2: {} - react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-router@7.10.1(react-dom@19.2.2(react@19.2.2))(react@19.2.2): dependencies: cookie: 1.1.1 - react: 19.2.1 + react: 19.2.2 set-cookie-parser: 2.7.2 optionalDependencies: - react-dom: 19.2.1(react@19.2.1) + react-dom: 19.2.2(react@19.2.2) - react-toastify@11.0.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-toastify@11.0.5(react-dom@19.2.2(react@19.2.2))(react@19.2.2): dependencies: clsx: 2.1.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.2 + react-dom: 19.2.2(react@19.2.2) - react-transition-group@4.4.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-transition-group@4.4.5(react-dom@19.2.2(react@19.2.2))(react@19.2.2): dependencies: '@babel/runtime': 7.28.4 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.2 + react-dom: 19.2.2(react@19.2.2) - react-virtualized-auto-sizer@1.0.26(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-virtualized-auto-sizer@1.0.26(react-dom@19.2.2(react@19.2.2))(react@19.2.2): dependencies: - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.2 + react-dom: 19.2.2(react@19.2.2) - react-window@1.8.11(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-window@1.8.11(react-dom@19.2.2(react@19.2.2))(react@19.2.2): dependencies: '@babel/runtime': 7.28.4 memoize-one: 5.2.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.2 + react-dom: 19.2.2(react@19.2.2) - react@19.2.1: {} + react@19.2.2: {} read-pkg-up@1.0.1: dependencies: @@ -5918,12 +5912,12 @@ snapshots: dependencies: typescript: 5.9.3 - typescript-eslint@8.48.1(eslint@9.39.1)(typescript@5.9.3): + typescript-eslint@8.49.0(eslint@9.39.1)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/parser': 8.48.1(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: @@ -5969,7 +5963,7 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite-plugin-imagemin@0.6.1(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)): + vite-plugin-imagemin@0.6.1(vite@7.2.7(@types/node@25.0.0)(terser@5.44.1)): dependencies: '@types/imagemin': 7.0.1 '@types/imagemin-gifsicle': 7.0.4 @@ -5994,11 +5988,11 @@ snapshots: imagemin-webp: 6.1.0 jpegtran-bin: 6.0.1 pathe: 0.2.0 - vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1) + vite: 7.2.7(@types/node@25.0.0)(terser@5.44.1) transitivePeerDependencies: - supports-color - vite-prerender-plugin@0.5.12(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)): + vite-prerender-plugin@0.5.12(vite@7.2.7(@types/node@25.0.0)(terser@5.44.1)): dependencies: kolorist: 1.8.0 magic-string: 0.30.21 @@ -6006,20 +6000,20 @@ snapshots: simple-code-frame: 1.3.0 source-map: 0.7.6 stack-trace: 1.0.0-pre2 - vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1) + vite: 7.2.7(@types/node@25.0.0)(terser@5.44.1) - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@25.0.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.6(@types/node@24.10.1)(terser@5.44.1) + vite: 7.2.7(@types/node@25.0.0)(terser@5.44.1) transitivePeerDependencies: - supports-color - typescript - vite@7.2.6(@types/node@24.10.1)(terser@5.44.1): + vite@7.2.7(@types/node@25.0.0)(terser@5.44.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -6028,7 +6022,7 @@ snapshots: rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 25.0.0 fsevents: 2.3.3 terser: 5.44.1 diff --git a/mock-api/package.json b/mock-api/package.json index 3c976f094..61ee7754c 100644 --- a/mock-api/package.json +++ b/mock-api/package.json @@ -15,5 +15,5 @@ "itty-router": "^5.0.22", "prettier": "^3.7.4" }, - "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a" + "packageManager": "pnpm@10.25.0+sha512.5e82639027af37cf832061bcc6d639c219634488e0f2baebe785028a793de7b525ffcd3f7ff574f5e9860654e098fe852ba8ac5dd5cefe1767d23a020a92f501" } diff --git a/platformio.ini b/platformio.ini index 3ad7826df..859950c63 100644 --- a/platformio.ini +++ b/platformio.ini @@ -192,6 +192,7 @@ build_src_flags = -std=gnu++17 -Og -ggdb -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces + -Wno-vla-cxx-extension -Wno-tautological-constant-out-of-range-compare -I./src/core -I./lib_standalone -I./lib/uuid-common/src diff --git a/src/core/analogsensor.cpp b/src/core/analogsensor.cpp index 9d4919f3a..f70e72f24 100644 --- a/src/core/analogsensor.cpp +++ b/src/core/analogsensor.cpp @@ -792,20 +792,15 @@ void AnalogSensor::publish_values(const bool force) { } // see if we need to create the [devs] discovery section, as this needs only to be done once for all sensors - bool is_ha_device_created = false; - for (auto const & sensor : sensors_) { - if (sensor.ha_registered) { - is_ha_device_created = true; - break; - } + if (std::none_of(sensors_.begin(), sensors_.end(), [](const auto & sensor) { return sensor.ha_registered; })) { + Mqtt::add_ha_dev_section(config.as(), "Analog Sensors", nullptr, nullptr, nullptr, false); } // add default_entity_id std::string topic_str(topic); doc["def_ent_id"] = topic_str.substr(0, topic_str.find("/")) + "." + uniq_s; - Mqtt::add_ha_dev_section(config.as(), "Analog Sensors", nullptr, nullptr, nullptr, false); - Mqtt::add_ha_avail_section(config.as(), stat_t, !is_ha_device_created, val_cond); + Mqtt::add_ha_avty_section(config.as(), stat_t, val_cond); sensor.ha_registered = Mqtt::queue_ha(topic, config.as()); } diff --git a/src/core/command.cpp b/src/core/command.cpp index 7db7dbacf..4cea1211c 100644 --- a/src/core/command.cpp +++ b/src/core/command.cpp @@ -237,12 +237,20 @@ const char * Command::parse_command_string(const char * command, int8_t & id) { const char * cmd_org = command; int8_t id_org = id; - // convert cmd to lowercase and compare - char * lowerCmd = strdup(command); - for (char * p = lowerCmd; *p; p++) { - *p = tolower(*p); + // Optimized: Use stack buffer instead of strdup() to avoid heap allocation + // Most command strings are short, 64 bytes is more than enough + char lowerCmd[64]; + size_t len = strlen(command); + if (len >= sizeof(lowerCmd)) { + len = sizeof(lowerCmd) - 1; // truncate if too long (rare case) } + // Convert to lowercase in place using stack buffer + for (size_t i = 0; i < len; i++) { + lowerCmd[i] = tolower(command[i]); + } + lowerCmd[len] = '\0'; + // check prefix and valid number range, also check 'id' if (!strncmp(lowerCmd, "hc", 2) && command[2] >= '1' && command[2] <= '8') { id = command[2] - '0'; @@ -279,7 +287,7 @@ const char * Command::parse_command_string(const char * command, int8_t & id) { command += 3; } - free(lowerCmd); + // No free() needed - stack buffer is automatically cleaned up // return original if no seperator if (command[0] != '/' && command[0] != '.') { diff --git a/src/core/emsesp.cpp b/src/core/emsesp.cpp index a125689b4..564c55e10 100644 --- a/src/core/emsesp.cpp +++ b/src/core/emsesp.cpp @@ -848,7 +848,7 @@ std::string EMSESP::device_tostring(const uint8_t device_id) { } } -// created a pretty print telegram as a text string +// create a pretty print telegram as a text string // e.g. Boiler(0x08) -> Me(0x0B), Version(0x02), data: 7B 06 01 00 00 00 00 00 00 04 (offset 1) std::string EMSESP::pretty_telegram(std::shared_ptr telegram) { uint8_t src = telegram->src & 0x7F; @@ -950,27 +950,58 @@ std::string EMSESP::pretty_telegram(std::shared_ptr telegram) { } } - std::string str; - str.reserve(200); + // Optimized: Use stack buffer and build string once to avoid multiple temporary allocations + char buf[250]; if (telegram->operation == Telegram::Operation::RX_READ) { - str = src_name + "(" + Helpers::hextoa(src) + ") R " + dest_name + "(" + Helpers::hextoa(dest) + "), " + 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) : ""); + auto pos = snprintf(buf, + sizeof(buf), + "%s(%s) R %s(%s), %s(%s), length: %d", + src_name.c_str(), + Helpers::hextoa(src).c_str(), + dest_name.c_str(), + Helpers::hextoa(dest).c_str(), + type_name.c_str(), + Helpers::hextoa(telegram->type_id).c_str(), + telegram->message_data[0]); + if (telegram->message_length > 1 && pos > 0 && pos < (int)sizeof(buf)) { + std::string data_hex = Helpers::data_to_hex(telegram->message_data + 1, telegram->message_length - 1); + snprintf(buf + pos, sizeof(buf) - pos, ", data: %s", data_hex.c_str()); + } } else if (telegram->dest == 0) { - str = src_name + "(" + Helpers::hextoa(src) + ") B " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "(" - + Helpers::hextoa(telegram->type_id) + "), data: " + telegram->to_string_message(); + snprintf(buf, + sizeof(buf), + "%s(%s) B %s(%s), %s(%s), data: %s", + src_name.c_str(), + Helpers::hextoa(src).c_str(), + dest_name.c_str(), + Helpers::hextoa(dest).c_str(), + type_name.c_str(), + Helpers::hextoa(telegram->type_id).c_str(), + telegram->to_string_message().c_str()); } else { - str = src_name + "(" + Helpers::hextoa(src) + ") W " + dest_name + "(" + Helpers::hextoa(dest) + "), " + type_name + "(" - + Helpers::hextoa(telegram->type_id) + "), data: " + telegram->to_string_message(); + snprintf(buf, + sizeof(buf), + "%s(%s) W %s(%s), %s(%s), data: %s", + src_name.c_str(), + Helpers::hextoa(src).c_str(), + dest_name.c_str(), + Helpers::hextoa(dest).c_str(), + type_name.c_str(), + Helpers::hextoa(telegram->type_id).c_str(), + telegram->to_string_message().c_str()); } if (offset) { - str += " (offset " + Helpers::itoa(offset) + ")"; + size_t len = strlen(buf); + if (len < sizeof(buf) - 20) { + snprintf(buf + len, sizeof(buf) - len, " (offset %d)", offset); + } } - return str; + return std::string(buf); } + /* * Type 0x07 - UBADevices - shows us the connected EMS devices * e.g. 08 00 07 00 0B 80 00 00 00 00 00 00 00 00 00 00 00 diff --git a/src/core/helpers.cpp b/src/core/helpers.cpp index 9e0d95fef..9c14b82f3 100644 --- a/src/core/helpers.cpp +++ b/src/core/helpers.cpp @@ -34,12 +34,19 @@ char * Helpers::hextoa(char * result, const uint8_t value) { } // same as hextoa but uses to a hex std::string +// Optimized: Avoid string concatenation to reduce temporary allocations std::string Helpers::hextoa(const uint8_t value, bool prefix) { - char buf[3]; if (prefix) { - return std::string("0x") + hextoa(buf, value); + char buf[5]; // "0x" + 2 hex chars + null + buf[0] = '0'; + buf[1] = 'x'; + hextoa(&buf[2], value); + return std::string(buf); + } else { + char buf[3]; + hextoa(buf, value); + return std::string(buf); } - return std::string(hextoa(buf, value)); } // same for 16 bit values @@ -53,12 +60,19 @@ char * Helpers::hextoa(char * result, const uint16_t value) { } // same as above but to a hex string +// Optimized: Avoid string concatenation to reduce temporary allocations std::string Helpers::hextoa(const uint16_t value, bool prefix) { - char buf[5]; if (prefix) { - return std::string("0x") + hextoa(buf, value); + char buf[7]; // "0x" + 4 hex chars + null + buf[0] = '0'; + buf[1] = 'x'; + hextoa(&buf[2], value); + return std::string(buf); + } else { + char buf[5]; + hextoa(buf, value); + return std::string(buf); } - return std::string(hextoa(buf, value)); } #ifdef EMSESP_STANDALONE @@ -100,29 +114,34 @@ char * Helpers::ultostr(char * ptr, uint32_t value, const uint8_t base) { // fast itoa returning a std::string // http://www.strudel.org.uk/itoa/ +// Optimized: Use stack buffer to avoid heap allocation, then create string once std::string Helpers::itoa(int16_t value) { - std::string buf; - buf.reserve(25); // Pre-allocate enough space. - int quotient = value; + // int16_t max: -32768 to 32767 = max 6 chars + null + char buf[8]; + char * p = buf + sizeof(buf) - 1; + *p = '\0'; + bool negative = value < 0; + int32_t abs_val = negative ? -(int32_t)value : value; // cast to int32 to handle -32768 + + // Build string in reverse do { - buf += "0123456789abcdef"[std::abs(quotient % 10)]; - quotient /= 10; - } while (quotient); + *--p = '0' + (abs_val % 10); + abs_val /= 10; + } while (abs_val > 0); - // Append the negative sign - if (value < 0) - buf += '-'; + if (negative) { + *--p = '-'; + } - std::reverse(buf.begin(), buf.end()); - return buf; + return std::string(p); } /* - * fast itoa - * written by Lukás Chmela, Released under GPLv3. http://www.strudel.org.uk/itoa/ version 0.4 - * optimized for ESP32 - */ + * fast itoa + * written by Lukás Chmela, Released under GPLv3. http://www.strudel.org.uk/itoa/ version 0.4 + * optimized for ESP32 + */ char * Helpers::itoa(int32_t value, char * result, const uint8_t base) { // check that the base if valid if (base < 2 || base > 36) { @@ -470,25 +489,26 @@ char * Helpers::utf8tolatin1(char * result, const char * c, const uint8_t len) { *p = '\0'; // terminate result return result; } - // creates string of hex values from an array of bytes std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) { if (length == 0) { return ""; } - std::string str; - str.reserve(length * 3 + 1); + char str[length * 3]; + memset(str, 0, sizeof(str)); - char buffer[4]; + char buffer[4]; + char * p = &str[0]; for (uint8_t i = 0; i < length; i++) { - str.append(Helpers::hextoa(buffer, data[i])); - str.push_back(' '); + Helpers::hextoa(buffer, data[i]); + *p++ = buffer[0]; + *p++ = buffer[1]; + *p++ = ' '; // space } - if (!str.empty()) { - str.pop_back(); - } - return str; + *--p = '\0'; // null terminate just in case, loosing the trailing space + + return std::string(str); } // takes a hex string and convert it to an unsigned 32bit number (max 8 hex digits) diff --git a/src/core/mqtt.cpp b/src/core/mqtt.cpp index 301c0c239..b2090121a 100644 --- a/src/core/mqtt.cpp +++ b/src/core/mqtt.cpp @@ -596,15 +596,17 @@ void Mqtt::ha_status() { // add sub or pub task to the queue. // the base is not included in the topic bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain) { + if (!mqtt_enabled_ || topic.empty() || !connected()) { + return false; // quit, not using MQTT + } + if (topic == "response" && operation == Operation::PUBLISH) { lastresponse_ = payload; if (!send_response_) { return true; } } - if (!mqtt_enabled_ || topic.empty() || !connected()) { - return false; // quit, not using MQTT - } + // check free mem #ifndef EMSESP_STANDALONE // if (ESP.getFreeHeap() < 60 * 1024 || ESP.getMaxAllocHeap() < 40 * 1024) { @@ -1094,10 +1096,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev // don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery if (discovery_type() == discoveryType::HOMEASSISTANT) { doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else " + sample_val + "}}"; - - // adds availability, dev, ids to the config section to HA Discovery config - // except for commands - add_ha_avail_section(doc.as(), stat_t, false, val_cond); + add_ha_avty_section(doc.as(), stat_t, val_cond); // adds availability section } else { // Domoticz doesn't support value templates, so we just use the value directly // Also omit the uom and other state classes @@ -1367,8 +1366,8 @@ bool Mqtt::publish_ha_climate_config(const int8_t tag, const bool has_roomtemp, modes.add("heat"); modes.add("off"); - add_ha_dev_section(doc.as(), devicename, nullptr, nullptr, nullptr, false); // add dev section - add_ha_avail_section(doc.as(), topic_t, false, seltemp_cond, has_roomtemp ? currtemp_cond : nullptr, hc_mode_cond); // add availability section + add_ha_dev_section(doc.as(), devicename, nullptr, nullptr, nullptr, false); // add dev section + add_ha_avty_section(doc.as(), topic_t, seltemp_cond, has_roomtemp ? currtemp_cond : nullptr, hc_mode_cond); // add availability section return queue_ha(topic, doc.as()); // publish the config payload with retain flag } @@ -1434,59 +1433,59 @@ void Mqtt::add_ha_dev_section(JsonObject doc, const char * name, const char * mo } } -// adds sections for HA Discovery to an existing JSON doc -// adds dev section with ids, name, mf, mdl, via_device -// adds optional availability section -void Mqtt::add_ha_avail_section(JsonObject doc, const char * state_t, const bool is_first, const char * cond1, const char * cond2, const char * negcond) { +// adds avty section for HA Discovery to an existing JSON doc +void Mqtt::add_ha_avty_section(JsonObject doc, const char * state_t, const char * cond1, const char * cond2, const char * negcond) { // only works for HA if (discovery_type() != discoveryType::HOMEASSISTANT) { return; } - // skip availability section if no conditions set - if (!cond1 && !cond2 && !negcond) { - return; - } - // adds "availability" section to HA Discovery config JsonArray avty = doc["avty"].to(); JsonDocument avty_json; - // make local copy of state, as the pointer will get de-referenced - char state[50]; - strlcpy(state, state_t, sizeof(state)); + if (state_t != nullptr) { + // make local copy of state, as the pointer will get de-referenced + char state[40]; + strlcpy(state, state_t, sizeof(state)); - char tpl[150]; - const char * tpl_draft = "{{'online' if %s else 'offline'}}"; + char tpl[150]; - // condition 1 - if (cond1 != nullptr) { - avty_json.clear(); - avty_json["t"] = state; - snprintf(tpl, sizeof(tpl), tpl_draft, cond1); - avty_json["val_tpl"] = tpl; - avty.add(avty_json); // returns 0 if no mem + // condition 1 + if (cond1 != nullptr) { + avty_json.clear(); + avty_json["t"] = state; + snprintf(tpl, sizeof(tpl), "{{'online' if %s else 'offline'}}", cond1); + avty_json["val_tpl"] = tpl; + if (!avty.add(avty_json)) { + LOG_WARNING("Failed to add availability condition 1 (low memory)"); + } + } + + // condition 2 + if (cond2 != nullptr) { + avty_json.clear(); + avty_json["t"] = state; + snprintf(tpl, sizeof(tpl), "{{'online' if %s else 'offline'}}", cond2); + avty_json["val_tpl"] = tpl; + if (!avty.add(avty_json)) { + LOG_WARNING("Failed to add availability condition 2 (low memory)"); + } + } + + // negative condition + if (negcond != nullptr) { + avty_json.clear(); + avty_json["t"] = state; + snprintf(tpl, sizeof(tpl), "{{'offline' if %s else 'online'}}", negcond); + avty_json["val_tpl"] = tpl; + if (!avty.add(avty_json)) { + LOG_WARNING("Failed to add negative availability condition (low memory)"); + } + } } - // condition 2 - if (cond2 != nullptr) { - avty_json.clear(); - avty_json["t"] = state; - snprintf(tpl, sizeof(tpl), tpl_draft, cond2); - avty_json["val_tpl"] = tpl; - avty.add(avty_json); // returns 0 if no mem - } - - // negative condition - if (negcond != nullptr) { - avty_json.clear(); - avty_json["t"] = state; - snprintf(tpl, sizeof(tpl), "{{'offline' if %s else 'online'}}", negcond); - avty_json["val_tpl"] = tpl; - avty.add(avty_json); // returns 0 if no mem - } - - // add LWT (Last Will and Testament) + // always add LWT (Last Will and Testament) avty_json.clear(); avty_json["t"] = "~/status"; // as a topic avty.add(avty_json); diff --git a/src/core/mqtt.h b/src/core/mqtt.h index 425919aa4..8127410d8 100644 --- a/src/core/mqtt.h +++ b/src/core/mqtt.h @@ -256,12 +256,11 @@ class Mqtt { static void add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8_t type, const uint8_t uom, const char * entity = nullptr, bool is_discovery = true); static void add_ha_dev_section(JsonObject doc, const char * name, const char * model, const char * brand, const char * version, const bool create_model); - static void add_ha_avail_section(JsonObject doc, - const char * state_t, - const bool is_first, - const char * cond1 = nullptr, - const char * cond2 = nullptr, - const char * negcond = nullptr); + static void add_ha_avty_section(JsonObject doc, + const char * state_t = nullptr, + const char * cond1 = nullptr, + const char * cond2 = nullptr, + const char * negcond = nullptr); static void add_ha_bool(JsonObject doc); static void add_value_bool(JsonObject doc, const char * name, bool value); diff --git a/src/core/shower.cpp b/src/core/shower.cpp index a1da099b0..bb3b0f29c 100644 --- a/src/core/shower.cpp +++ b/src/core/shower.cpp @@ -212,7 +212,7 @@ void Shower::create_ha_discovery() { Mqtt::add_ha_bool(doc.as()); Mqtt::add_ha_dev_section(doc.as(), "Shower Sensor", nullptr, nullptr, nullptr, false); - Mqtt::add_ha_avail_section(doc.as(), "~/shower_active", true); // no conditions + Mqtt::add_ha_avty_section(doc.as()); // no conditions snprintf(topic, sizeof(topic), "binary_sensor/%s/shower_active/config", Mqtt::basename().c_str()); ha_configdone_ = Mqtt::queue_ha(topic, doc.as()); // publish the config payload with retain flag @@ -240,7 +240,7 @@ void Shower::create_ha_discovery() { // doc["ent_cat"] = "diagnostic"; Mqtt::add_ha_dev_section(doc.as(), "Shower Sensor", nullptr, nullptr, nullptr, false); - Mqtt::add_ha_avail_section(doc.as(), "~/shower_data", false, "value_json.duration is defined"); + Mqtt::add_ha_avty_section(doc.as(), "~/shower_data", "value_json.duration is defined"); snprintf(topic, sizeof(topic), "sensor/%s/shower_duration/config", Mqtt::basename().c_str()); Mqtt::queue_ha(topic, doc.as()); // publish the config payload with retain flag diff --git a/src/core/system.cpp b/src/core/system.cpp index 3884b859a..aa39b3b34 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1565,7 +1565,7 @@ void System::get_value_json(JsonObject output, const std::string & circuit, cons // generate Prometheus metrics format from system values std::string System::get_metrics_prometheus() { - std::string result; + std::string result; std::unordered_map seen_metrics; // get system data @@ -1633,156 +1633,155 @@ std::string System::get_metrics_prometheus() { }; // helper function to process a JSON object recursively - std::function process_object = - [&](const JsonObject & obj, const std::string & prefix) { - std::vector> local_info_labels; - bool has_nested_objects = false; + std::function process_object = [&](const JsonObject & obj, const std::string & prefix) { + std::vector> local_info_labels; + bool has_nested_objects = false; - for (JsonPair p : obj) { - std::string key = p.key().c_str(); - std::string path = prefix.empty() ? key : prefix + "." + key; - std::string metric_name = prefix.empty() ? key : prefix + "_" + key; + for (JsonPair p : obj) { + std::string key = p.key().c_str(); + std::string path = prefix.empty() ? key : prefix + "." + key; + std::string metric_name = prefix.empty() ? key : prefix + "_" + key; - if (should_ignore(prefix, key)) { - continue; - } + if (should_ignore(prefix, key)) { + continue; + } - if (p.value().is()) { - // recursive call for nested objects - has_nested_objects = true; - process_object(p.value().as(), metric_name); - } else if (p.value().is()) { - // handle arrays (devices) - if (key == "devices") { - JsonArray devices = p.value().as(); - for (JsonObject device : devices) { - std::vector> device_labels; + if (p.value().is()) { + // recursive call for nested objects + has_nested_objects = true; + process_object(p.value().as(), metric_name); + } else if (p.value().is()) { + // handle arrays (devices) + if (key == "devices") { + JsonArray devices = p.value().as(); + for (JsonObject device : devices) { + std::vector> device_labels; - // collect labels from device object - for (JsonPair dp : device) { - std::string dkey = dp.key().c_str(); - if (dkey == "type" || dkey == "name" || dkey == "deviceID" || dkey == "brand" || dkey == "version") { - if (dp.value().is()) { - std::string val = dp.value().as(); - if (!val.empty()) { - device_labels.push_back({to_lowercase(dkey), val}); - } + // collect labels from device object + for (JsonPair dp : device) { + std::string dkey = dp.key().c_str(); + if (dkey == "type" || dkey == "name" || dkey == "deviceID" || dkey == "brand" || dkey == "version") { + if (dp.value().is()) { + std::string val = dp.value().as(); + if (!val.empty()) { + device_labels.push_back({to_lowercase(dkey), val}); } } } + } - // create productID metric - if (device.containsKey("productID") && device["productID"].is()) { - std::string metric = "emsesp_device_productid"; - if (seen_metrics.find(metric) == seen_metrics.end()) { - result += "# HELP emsesp_device_productid productID\n"; - result += "# TYPE emsesp_device_productid gauge\n"; - seen_metrics[metric] = true; - } - - result += metric; - if (!device_labels.empty()) { - result += "{"; - bool first = true; - for (const auto & label : device_labels) { - if (!first) { - result += ", "; - } - result += label.first + "=\"" + escape_label(label.second) + "\""; - first = false; - } - result += "}"; - } - result += " " + std::to_string(device["productID"].as()) + "\n"; + // create productID metric + if (device["productID"].is()) { + std::string metric = "emsesp_device_productid"; + if (seen_metrics.find(metric) == seen_metrics.end()) { + result += "# HELP emsesp_device_productid productID\n"; + result += "# TYPE emsesp_device_productid gauge\n"; + seen_metrics[metric] = true; } - // create entities metric - if (device.containsKey("entities") && device["entities"].is()) { - std::string metric = "emsesp_device_entities"; - if (seen_metrics.find(metric) == seen_metrics.end()) { - result += "# HELP emsesp_device_entities entities\n"; - result += "# TYPE emsesp_device_entities gauge\n"; - seen_metrics[metric] = true; - } - - result += metric; - if (!device_labels.empty()) { - result += "{"; - bool first = true; - for (const auto & label : device_labels) { - if (!first) { - result += ", "; - } - result += label.first + "=\"" + escape_label(label.second) + "\""; - first = false; + result += metric; + if (!device_labels.empty()) { + result += "{"; + bool first = true; + for (const auto & label : device_labels) { + if (!first) { + result += ", "; } - result += "}"; + result += label.first + "=\"" + escape_label(label.second) + "\""; + first = false; } - result += " " + std::to_string(device["entities"].as()) + "\n"; + result += "}"; } + result += " " + std::to_string(device["productID"].as()) + "\n"; + } + + // create entities metric + if (device["entities"].is()) { + std::string metric = "emsesp_device_entities"; + if (seen_metrics.find(metric) == seen_metrics.end()) { + result += "# HELP emsesp_device_entities entities\n"; + result += "# TYPE emsesp_device_entities gauge\n"; + seen_metrics[metric] = true; + } + + result += metric; + if (!device_labels.empty()) { + result += "{"; + bool first = true; + for (const auto & label : device_labels) { + if (!first) { + result += ", "; + } + result += label.first + "=\"" + escape_label(label.second) + "\""; + first = false; + } + result += "}"; + } + result += " " + std::to_string(device["entities"].as()) + "\n"; } } - } else { - // handle primitive values - bool is_number = p.value().is() || p.value().is(); - bool is_bool = p.value().is(); - bool is_string = p.value().is(); + } + } else { + // handle primitive values + bool is_number = p.value().is() || p.value().is(); + bool is_bool = p.value().is(); + bool is_string = p.value().is(); - if (is_number || is_bool) { - // add metric - std::string full_metric_name = "emsesp_" + sanitize_name(metric_name); - if (seen_metrics.find(full_metric_name) == seen_metrics.end()) { - result += "# HELP emsesp_" + sanitize_name(metric_name) + " " + key + "\n"; - result += "# TYPE emsesp_" + sanitize_name(metric_name) + " gauge\n"; - seen_metrics[full_metric_name] = true; - } + if (is_number || is_bool) { + // add metric + std::string full_metric_name = "emsesp_" + sanitize_name(metric_name); + if (seen_metrics.find(full_metric_name) == seen_metrics.end()) { + result += "# HELP emsesp_" + sanitize_name(metric_name) + " " + key + "\n"; + result += "# TYPE emsesp_" + sanitize_name(metric_name) + " gauge\n"; + seen_metrics[full_metric_name] = true; + } - result += full_metric_name + " "; - if (is_bool) { - result += p.value().as() ? "1" : "0"; - } else if (p.value().is()) { - result += std::to_string(p.value().as()); - } else { - char val_str[30]; - snprintf(val_str, sizeof(val_str), "%.2f", p.value().as()); - result += val_str; - } - result += "\n"; - } else if (is_string) { - // collect string for info metric (skip dynamic strings like uptime and timestamp) - std::string val = p.value().as(); - if (!val.empty() && key != "uptime" && key != "timestamp") { - local_info_labels.push_back({to_lowercase(key), val}); - } + result += full_metric_name + " "; + if (is_bool) { + result += p.value().as() ? "1" : "0"; + } else if (p.value().is()) { + result += std::to_string(p.value().as()); + } else { + char val_str[30]; + snprintf(val_str, sizeof(val_str), "%.2f", p.value().as()); + result += val_str; + } + result += "\n"; + } else if (is_string) { + // collect string for info metric (skip dynamic strings like uptime and timestamp) + std::string val = p.value().as(); + if (!val.empty() && key != "uptime" && key != "timestamp") { + local_info_labels.push_back({to_lowercase(key), val}); } } } + } - // create _info metric for this object level if we have labels and this is a leaf node (no nested objects) - if (!local_info_labels.empty() && !prefix.empty() && !has_nested_objects) { - std::string info_metric = "emsesp_" + sanitize_name(prefix) + "_info"; - if (seen_metrics.find(info_metric) == seen_metrics.end()) { - result += "# HELP " + info_metric + " info\n"; - result += "# TYPE " + info_metric + " gauge\n"; - seen_metrics[info_metric] = true; - } - - result += info_metric; - if (!local_info_labels.empty()) { - result += "{"; - bool first = true; - for (const auto & label : local_info_labels) { - if (!first) { - result += ", "; - } - result += label.first + "=\"" + escape_label(label.second) + "\""; - first = false; - } - result += "}"; - } - result += " 1\n"; + // create _info metric for this object level if we have labels and this is a leaf node (no nested objects) + if (!local_info_labels.empty() && !prefix.empty() && !has_nested_objects) { + std::string info_metric = "emsesp_" + sanitize_name(prefix) + "_info"; + if (seen_metrics.find(info_metric) == seen_metrics.end()) { + result += "# HELP " + info_metric + " info\n"; + result += "# TYPE " + info_metric + " gauge\n"; + seen_metrics[info_metric] = true; } - }; + + result += info_metric; + if (!local_info_labels.empty()) { + result += "{"; + bool first = true; + for (const auto & label : local_info_labels) { + if (!first) { + result += ", "; + } + result += label.first + "=\"" + escape_label(label.second) + "\""; + first = false; + } + result += "}"; + } + result += " 1\n"; + } + }; // process root object process_object(root, ""); diff --git a/src/core/temperaturesensor.cpp b/src/core/temperaturesensor.cpp index 0dcf0a8db..e50e0defc 100644 --- a/src/core/temperaturesensor.cpp +++ b/src/core/temperaturesensor.cpp @@ -336,7 +336,7 @@ bool TemperatureSensor::update(const char * id, const char * name, int16_t offse sensor.set_is_system(is_system); // store the new name and offset in our configuration - EMSESP::webCustomizationService.update([&id, &name, &offset, &sensor, &hide, &is_system](WebCustomization & settings) { + EMSESP::webCustomizationService.update([&id, &name, &offset, &sensor, &is_system](WebCustomization & settings) { // look it up to see if it exists bool found = false; for (auto & SensorCustomization : settings.sensorCustomizations) { @@ -544,16 +544,11 @@ void TemperatureSensor::publish_values(const bool force) { config["name"] = sensor.name(); // see if we need to create the [devs] discovery section, as this needs only to be done once for all sensors - bool is_ha_device_created = false; - for (const auto & sensor : sensors_) { - if (sensor.ha_registered) { - is_ha_device_created = true; - break; - } + if (std::none_of(sensors_.begin(), sensors_.end(), [](const auto & sensor) { return sensor.ha_registered; })) { + Mqtt::add_ha_dev_section(config.as(), "Temperature Sensors", nullptr, nullptr, nullptr, false); } - Mqtt::add_ha_dev_section(config.as(), "Temperature Sensors", nullptr, nullptr, nullptr, false); - Mqtt::add_ha_avail_section(config.as(), stat_t, !is_ha_device_created, val_cond); + Mqtt::add_ha_avty_section(config.as(), stat_t, val_cond); char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(temperaturesensor), sensor.id()); diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index 3136c8b5c..807e7f3ba 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -464,8 +464,11 @@ void WebCustomEntityService::publish(const bool force) { config["def_ent_id"] = topic_str.substr(0, topic_str.find("/")) + "." + uniq_s; Mqtt::add_ha_classes(config.as(), EMSdevice::DeviceType::SYSTEM, entityItem.value_type, entityItem.uom); - Mqtt::add_ha_dev_section(config.as(), "Custom Entities", nullptr, nullptr, nullptr, false); - Mqtt::add_ha_avail_section(config.as(), stat_t, !ha_created, val_cond); + + if (!ha_created) { + Mqtt::add_ha_dev_section(config.as(), "Custom Entities", nullptr, nullptr, nullptr, false); + } + Mqtt::add_ha_avty_section(config.as(), stat_t, val_cond); ha_created |= Mqtt::queue_ha(topic, config.as()); } @@ -700,13 +703,13 @@ void WebCustomEntityService::load_test_data() { auto entityItem = CustomEntityItem(); // test 1 - entityItem.id = 1; - entityItem.ram = 0; - entityItem.device_id = 8; - entityItem.type_id = 24; - entityItem.offset = 0; - entityItem.factor = 1; - strcpy(entityItem.name,"test_custom"); + entityItem.id = 1; + entityItem.ram = 0; + entityItem.device_id = 8; + entityItem.type_id = 24; + entityItem.offset = 0; + entityItem.factor = 1; + strcpy(entityItem.name, "test_custom"); entityItem.uom = 1; entityItem.value_type = 1; entityItem.writeable = true; @@ -723,12 +726,12 @@ void WebCustomEntityService::load_test_data() { CommandFlag::ADMIN_ONLY); // test 2 - entityItem.id = 2; - entityItem.ram = 0; - entityItem.device_id = 24; - entityItem.type_id = 677; - entityItem.offset = 3; - entityItem.factor = 1; + entityItem.id = 2; + entityItem.ram = 0; + entityItem.device_id = 24; + entityItem.type_id = 677; + entityItem.offset = 3; + entityItem.factor = 1; strcpy(entityItem.name, "test_read_only"); entityItem.uom = 0; entityItem.value_type = 2; @@ -737,12 +740,12 @@ void WebCustomEntityService::load_test_data() { webCustomEntity.customEntityItems.push_back(entityItem); // test 3 - entityItem.id = 3; - entityItem.ram = 1; - entityItem.device_id = 0; - entityItem.type_id = 0; - entityItem.offset = 0; - entityItem.factor = 1; + entityItem.id = 3; + entityItem.ram = 1; + entityItem.device_id = 0; + entityItem.type_id = 0; + entityItem.offset = 0; + entityItem.factor = 1; strcpy(entityItem.name, "test_ram"); entityItem.uom = 0; entityItem.value_type = 8; @@ -759,12 +762,12 @@ void WebCustomEntityService::load_test_data() { CommandFlag::ADMIN_ONLY); // test 4 - entityItem.id = 4; - entityItem.ram = 1; - entityItem.device_id = 0; - entityItem.type_id = 0; - entityItem.offset = 0; - entityItem.factor = 1; + entityItem.id = 4; + entityItem.ram = 1; + entityItem.device_id = 0; + entityItem.type_id = 0; + entityItem.offset = 0; + entityItem.factor = 1; strcpy(entityItem.name, "test_seltemp"); entityItem.uom = 0; entityItem.value_type = 8; diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index 0c152270e..ac7dc7752 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -275,8 +275,10 @@ void WebSchedulerService::publish(const bool force) { config["cmd_t"] = command_topic; Mqtt::add_ha_bool(config.as()); - Mqtt::add_ha_dev_section(config.as(), F_(scheduler), nullptr, nullptr, nullptr, false); - Mqtt::add_ha_avail_section(config.as(), stat_t, !ha_created, val_cond); + if (!ha_created) { + Mqtt::add_ha_dev_section(config.as(), F_(scheduler), nullptr, nullptr, nullptr, false); + } + Mqtt::add_ha_avty_section(config.as(), stat_t, val_cond); ha_created |= Mqtt::queue_ha(topic, config.as()); } diff --git a/src/web/WebSchedulerService.h b/src/web/WebSchedulerService.h index 6978a64b0..4a485e35d 100644 --- a/src/web/WebSchedulerService.h +++ b/src/web/WebSchedulerService.h @@ -109,7 +109,7 @@ class WebSchedulerService : public StatefulService { bool ha_registered_ = false; std::list> * scheduleItems_; // pointer to the list of schedule events - std::list> cmd_changed_; // pointer to commands in list that are triggert by change + std::list> cmd_changed_; // pointer to commands in list that are triggered by change }; } // namespace emsesp