Merge branch 'dev' into dev

This commit is contained in:
pswid
2022-10-11 14:10:30 +02:00
committed by GitHub
110 changed files with 3715 additions and 4216 deletions

View File

@@ -4,13 +4,15 @@
## Added ## Added
- Translations in Web UI and all device entity names to German. [#22](https://github.com/emsesp/EMS-ESP32/issues/22) - Translations in Web UI and all device entity names (DE, NL, SE, PL, NO, ...) [#22](https://github.com/emsesp/EMS-ESP32/issues/22)
- Add support for Lolin C3 mini [#620](https://github.com/emsesp/EMS-ESP32/pull/620) - Add support for Lolin C3 mini [#620](https://github.com/emsesp/EMS-ESP32/pull/620)
- Add Greenstar 30Ri boiler - Add support for ESP32-S2 [#667](https://github.com/emsesp/EMS-ESP32/pull/667)
- Add devices: Greenstar 30Ri boiler, Junkers FW500 thermostat, Buderus BC30 controller
- Add program memory info - Add program memory info
- Add mqtt queue and connection infos
- Add min/max setting to customizations - Add min/max setting to customizations
- Adapt min/max if ems-value is not in this range - Adapt min/max if ems-value is not in this range
- Add heatpump settings for inputs and limits - Add heat pump settings for inputs and limits
## Fixed ## Fixed
@@ -20,8 +22,9 @@
- Discovery in HomeAssistant don't work with custom base topic. [#596](https://github.com/emsesp/EMS-ESP32/issues/596) Base topic containing `/` are changed to `_` - Discovery in HomeAssistant don't work with custom base topic. [#596](https://github.com/emsesp/EMS-ESP32/issues/596) Base topic containing `/` are changed to `_`
- RF room temperature sensor are shown as thermostat - RF room temperature sensor are shown as thermostat
- render mqtt float json values with trailing zero
- removed flash strings
## **BREAKING CHANGES:** ## **BREAKING CHANGES:**
- When upgrading from 3.4.x you may need to erase the flash on the ESP32 before uploading the firmware. Make sure you make a backup of the settings and customizations via the WebUI (System->Upload/Download) - When upgrading from 3.4.x you may need to erase the flash on the ESP32 before uploading the firmware. Make sure you make a backup of the settings and customizations via the WebUI (System->Upload/Download)

View File

@@ -1,5 +1,5 @@
{ {
"adapter": "react", "adapter": "react",
"baseLocale": "en", "baseLocale": "en",
"$schema": "https://unpkg.com/typesafe-i18n@5.13.0/schema/typesafe-i18n.json" "$schema": "https://unpkg.com/typesafe-i18n@5.14.0/schema/typesafe-i18n.json"
} }

View File

@@ -12,15 +12,15 @@
"@emotion/styled": "^11.10.4", "@emotion/styled": "^11.10.4",
"@msgpack/msgpack": "^2.8.0", "@msgpack/msgpack": "^2.8.0",
"@mui/icons-material": "^5.10.6", "@mui/icons-material": "^5.10.6",
"@mui/material": "^5.10.7", "@mui/material": "^5.10.8",
"@table-library/react-table-library": "4.0.18", "@table-library/react-table-library": "4.0.23",
"@types/lodash": "^4.14.185", "@types/lodash": "^4.14.186",
"@types/node": "^18.7.23", "@types/node": "^18.8.3",
"@types/react": "^18.0.21", "@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"axios": "^0.27.2", "axios": "^1.1.2",
"http-proxy-middleware": "^2.0.6", "http-proxy-middleware": "^2.0.6",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@@ -31,10 +31,10 @@
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.2", "react-dropzone": "^14.2.2",
"react-icons": "^4.4.0", "react-icons": "^4.4.0",
"react-router-dom": "^6.4.1", "react-router-dom": "^6.4.2",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"sockette": "^2.0.6", "sockette": "^2.0.6",
"typesafe-i18n": "^5.13.1", "typesafe-i18n": "^5.14.0",
"typescript": "^4.8.4" "typescript": "^4.8.4"
}, },
"devDependencies": { "devDependencies": {
@@ -2316,9 +2316,9 @@
} }
}, },
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.10.5", "version": "0.10.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.5.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz",
"integrity": "sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug==", "integrity": "sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==",
"dependencies": { "dependencies": {
"@humanwhocodes/object-schema": "^1.2.1", "@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1", "debug": "^4.1.1",
@@ -3103,9 +3103,9 @@
} }
}, },
"node_modules/@mui/base": { "node_modules/@mui/base": {
"version": "5.0.0-alpha.99", "version": "5.0.0-alpha.100",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.99.tgz", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.100.tgz",
"integrity": "sha512-D04H6O1c0Jv561yI0SVbpa8MpqpW3G43CwJxV2o6ALfI0DMJ45w07dGafmDchb6aCWTRTdggd3rjgmuzyNwPiQ==", "integrity": "sha512-bSoJEKCENtmJrJDECHUe9PiqztIUACuSskyqw9ypqE7Dz3WxL3e8puFsWBkUsz+WOCjXh4B4Xljn88Ucxxv5HA==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.19.0", "@babel/runtime": "^7.19.0",
"@emotion/is-prop-valid": "^1.2.0", "@emotion/is-prop-valid": "^1.2.0",
@@ -3135,9 +3135,9 @@
} }
}, },
"node_modules/@mui/core-downloads-tracker": { "node_modules/@mui/core-downloads-tracker": {
"version": "5.10.7", "version": "5.10.8",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.7.tgz", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.8.tgz",
"integrity": "sha512-3N0UYVy3MbrHzM3j6f7fIUCZ+bQ1/sSZq143tLxwSssW3Z4AqE83brpr5flEY1Lx+Aowv/cPyQMmZxzRlFCGqw==", "integrity": "sha512-V5D7OInO4P9PdT/JACg7fwjbOORm3GklaMVgdGomjyxiyetgRND5CC9r35e1LK/DqHdoyDuhbFzdfrqWtpmEIw==",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/mui" "url": "https://opencollective.com/mui"
@@ -3169,14 +3169,14 @@
} }
}, },
"node_modules/@mui/material": { "node_modules/@mui/material": {
"version": "5.10.7", "version": "5.10.8",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.10.7.tgz", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.10.8.tgz",
"integrity": "sha512-o1jcQGii+q7ORrXhBiMmGzFDaboc1qTgOOC3zDW+NR9ryVzWzL7qEeqoORbgDB5zk9OBsXCjB91fUH/ls5xMwg==", "integrity": "sha512-sF/Ka0IJjGXV52zoT4xAWEqXVRjNYbIjATo9L4Q5oQC5iJpGrKJFY16uNtWWB0+vp/nayAuPGZHrxtV+t3ecdQ==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.19.0", "@babel/runtime": "^7.19.0",
"@mui/base": "5.0.0-alpha.99", "@mui/base": "5.0.0-alpha.100",
"@mui/core-downloads-tracker": "^5.10.7", "@mui/core-downloads-tracker": "^5.10.8",
"@mui/system": "^5.10.7", "@mui/system": "^5.10.8",
"@mui/types": "^7.2.0", "@mui/types": "^7.2.0",
"@mui/utils": "^5.10.6", "@mui/utils": "^5.10.6",
"@types/react-transition-group": "^4.4.5", "@types/react-transition-group": "^4.4.5",
@@ -3239,9 +3239,9 @@
} }
}, },
"node_modules/@mui/styled-engine": { "node_modules/@mui/styled-engine": {
"version": "5.10.7", "version": "5.10.8",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.10.7.tgz", "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.10.8.tgz",
"integrity": "sha512-CCrtW+vvCKEm6pOE/QcutQ+ORC/iE6D1ghscN4l7LE2JXPvTXO/z0yu8Wxug1JEDlWm4r1Qa0PzJe1P9bjKzNA==", "integrity": "sha512-w+y8WI18EJV6zM/q41ug19cE70JTeO6sWFsQ7tgePQFpy6ToCVPh0YLrtqxUZXSoMStW5FMw0t9fHTFAqPbngw==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.19.0", "@babel/runtime": "^7.19.0",
"@emotion/cache": "^11.10.3", "@emotion/cache": "^11.10.3",
@@ -3270,13 +3270,13 @@
} }
}, },
"node_modules/@mui/system": { "node_modules/@mui/system": {
"version": "5.10.7", "version": "5.10.8",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.10.7.tgz", "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.10.8.tgz",
"integrity": "sha512-kwyhjjKGsgtBRFl6vSqidDZcNKU5S1juTgm4Xi2fyWxaEbIQb9Sh9y0iVP2bNCJzgDr0alLaENOZOEaDWHISAQ==", "integrity": "sha512-hRQ354zcrYP/KHqK8FheICSvE9raQaUgQaV+A3oD4JETaFUCVI9Ytt+RcQYgTqx02xlCXIjl8LK1rPjTneySqw==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.19.0", "@babel/runtime": "^7.19.0",
"@mui/private-theming": "^5.10.6", "@mui/private-theming": "^5.10.6",
"@mui/styled-engine": "^5.10.7", "@mui/styled-engine": "^5.10.8",
"@mui/types": "^7.2.0", "@mui/types": "^7.2.0",
"@mui/utils": "^5.10.6", "@mui/utils": "^5.10.6",
"clsx": "^1.2.1", "clsx": "^1.2.1",
@@ -3470,9 +3470,9 @@
} }
}, },
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.0.1", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz",
"integrity": "sha512-eBV5rvW4dRFOU1eajN7FmYxjAIVz/mRHgUE9En9mBn6m3mulK3WTR5C3iQhL9MZ14rWAq+xOlEaCkDiW0/heOg==", "integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
} }
@@ -3557,9 +3557,9 @@
"integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg=="
}, },
"node_modules/@sinclair/typebox": { "node_modules/@sinclair/typebox": {
"version": "0.24.43", "version": "0.24.44",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.43.tgz", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.44.tgz",
"integrity": "sha512-1orQTvtazZmsPeBroJjysvsOQCYV2yjWlebkSY38pl5vr2tdLjEJ+LoxITlGNZaH2RE19WlAwQMkH/7C14wLfw==" "integrity": "sha512-ka0W0KN5i6LfrSocduwliMMpqVgohtPFidKdMEOUjoOFCHcOOYkKsPRxfs5f15oPNHTm6ERAm0GV/+/LTKeiWg=="
}, },
"node_modules/@sinonjs/commons": { "node_modules/@sinonjs/commons": {
"version": "1.8.3", "version": "1.8.3",
@@ -3796,9 +3796,9 @@
} }
}, },
"node_modules/@table-library/react-table-library": { "node_modules/@table-library/react-table-library": {
"version": "4.0.18", "version": "4.0.23",
"resolved": "https://registry.npmjs.org/@table-library/react-table-library/-/react-table-library-4.0.18.tgz", "resolved": "https://registry.npmjs.org/@table-library/react-table-library/-/react-table-library-4.0.23.tgz",
"integrity": "sha512-3GX5712YuWPFeUilr8UG22SCNR9BylD5Y4xPF+pHhb1QM7WXw6SLgpeGq6UDTQGOtmKtKtwKGWXdkmvVDg8pkA==", "integrity": "sha512-o2L/fqhwQNxsNbbm3LIiyZzEwaTslhG1tY9ArkYdS0xJyRhJxcOLfbJ3+dcnOOn+aIJpmPmQH+gr7RYJC0P8uw==",
"dependencies": { "dependencies": {
"clsx": "1.1.1", "clsx": "1.1.1",
"react-virtualized-auto-sizer": "1.0.6", "react-virtualized-auto-sizer": "1.0.6",
@@ -4019,9 +4019,9 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
}, },
"node_modules/@types/lodash": { "node_modules/@types/lodash": {
"version": "4.14.185", "version": "4.14.186",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.185.tgz", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz",
"integrity": "sha512-evMDG1bC4rgQg4ku9tKpuMh5iBNEwNa3tf9zRHdP1qlv+1WUg44xat4IxCE14gIpZRGUUWAx2VhItCZc25NfMA==" "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw=="
}, },
"node_modules/@types/mime": { "node_modules/@types/mime": {
"version": "3.0.1", "version": "3.0.1",
@@ -4029,9 +4029,9 @@
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA=="
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "18.7.23", "version": "18.8.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.23.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.3.tgz",
"integrity": "sha512-DWNcCHolDq0ZKGizjx2DZjR/PqsYwAcYUJmfMWqtVU2MBMG5Mo+xFZrhGId5r/O5HOuMPyQEcM6KUBp5lBZZBg==" "integrity": "sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w=="
}, },
"node_modules/@types/parse-json": { "node_modules/@types/parse-json": {
"version": "4.0.0", "version": "4.0.0",
@@ -5008,12 +5008,13 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "0.27.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.1.2.tgz",
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "integrity": "sha512-bznQyETwElsXl2RK7HLLwb5GPpOLlycxHCtrpDR/4RqqBzjARaOTo3jz4IgtntWUYee7Ne4S8UHd92VCuzPaWA==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.14.9", "follow-redirects": "^1.15.0",
"form-data": "^4.0.0" "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
} }
}, },
"node_modules/axobject-query": { "node_modules/axobject-query": {
@@ -5561,9 +5562,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001412", "version": "1.0.30001414",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001412.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz",
"integrity": "sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA==", "integrity": "sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -6755,9 +6756,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.266", "version": "1.4.270",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.266.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.270.tgz",
"integrity": "sha512-saJTYECxUSv7eSpnXw0XIEvUkP9x4s/x2mm3TVX7k4rIFS6f5TjBih1B5h437WzIhHQjid+d8ouQzPQskMervQ==" "integrity": "sha512-KNhIzgLiJmDDC444dj9vEOpZEgsV96ult9Iff98Vanumn+ShJHd5se8aX6KeVxdc0YQeqdrezBZv89rleDbvSg=="
}, },
"node_modules/emittery": { "node_modules/emittery": {
"version": "0.8.1", "version": "0.8.1",
@@ -11430,9 +11431,9 @@
} }
}, },
"node_modules/js-sdsl": { "node_modules/js-sdsl": {
"version": "4.1.4", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
"integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==" "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q=="
}, },
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
@@ -12853,9 +12854,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.16", "version": "8.4.17",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz",
"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", "integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -14147,6 +14148,11 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/psl": { "node_modules/psl": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -14545,11 +14551,11 @@
} }
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "6.4.1", "version": "6.4.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz",
"integrity": "sha512-OJASKp5AykDWFewgWUim1vlLr7yfD4vO/h+bSgcP/ix8Md+LMHuAjovA74MQfsfhQJGGN1nHRhwS5qQQbbBt3A==", "integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==",
"dependencies": { "dependencies": {
"@remix-run/router": "1.0.1" "@remix-run/router": "1.0.2"
}, },
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@@ -14559,12 +14565,12 @@
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "6.4.1", "version": "6.4.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.1.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz",
"integrity": "sha512-MY7NJCrGNVJtGp8ODMOBHu20UaIkmwD2V3YsAOUQoCXFk7Ppdwf55RdcGyrSj+ycSL9Uiwrb3gTLYSnzcRoXww==", "integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==",
"dependencies": { "dependencies": {
"@remix-run/router": "1.0.1", "@remix-run/router": "1.0.2",
"react-router": "6.4.1" "react-router": "6.4.2"
}, },
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@@ -16420,9 +16426,9 @@
} }
}, },
"node_modules/typesafe-i18n": { "node_modules/typesafe-i18n": {
"version": "5.13.1", "version": "5.14.0",
"resolved": "https://registry.npmjs.org/typesafe-i18n/-/typesafe-i18n-5.13.1.tgz", "resolved": "https://registry.npmjs.org/typesafe-i18n/-/typesafe-i18n-5.14.0.tgz",
"integrity": "sha512-9Cgikxcj+2LAWQGZ8pOQAf14P+aOOcOel/G1kSAXAObA4Htj8R+qgCx9DqUnxjRfugWzM/19ZqGss7dl8Dw7Gg==", "integrity": "sha512-ZNHysUvZZhmUuMjBvDGtUI8vT3g//4ay5fFOk2sJCsjx4ztippW1Hrhrq59nJ9mV/Q0u4OX80Gyorq8L3rwNLw==",
"bin": { "bin": {
"typesafe-i18n": "cli/typesafe-i18n.mjs" "typesafe-i18n": "cli/typesafe-i18n.mjs"
}, },
@@ -19062,9 +19068,9 @@
} }
}, },
"@humanwhocodes/config-array": { "@humanwhocodes/config-array": {
"version": "0.10.5", "version": "0.10.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.5.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz",
"integrity": "sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug==", "integrity": "sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==",
"requires": { "requires": {
"@humanwhocodes/object-schema": "^1.2.1", "@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1", "debug": "^4.1.1",
@@ -19643,9 +19649,9 @@
"integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==" "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ=="
}, },
"@mui/base": { "@mui/base": {
"version": "5.0.0-alpha.99", "version": "5.0.0-alpha.100",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.99.tgz", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.100.tgz",
"integrity": "sha512-D04H6O1c0Jv561yI0SVbpa8MpqpW3G43CwJxV2o6ALfI0DMJ45w07dGafmDchb6aCWTRTdggd3rjgmuzyNwPiQ==", "integrity": "sha512-bSoJEKCENtmJrJDECHUe9PiqztIUACuSskyqw9ypqE7Dz3WxL3e8puFsWBkUsz+WOCjXh4B4Xljn88Ucxxv5HA==",
"requires": { "requires": {
"@babel/runtime": "^7.19.0", "@babel/runtime": "^7.19.0",
"@emotion/is-prop-valid": "^1.2.0", "@emotion/is-prop-valid": "^1.2.0",
@@ -19658,9 +19664,9 @@
} }
}, },
"@mui/core-downloads-tracker": { "@mui/core-downloads-tracker": {
"version": "5.10.7", "version": "5.10.8",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.7.tgz", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.8.tgz",
"integrity": "sha512-3N0UYVy3MbrHzM3j6f7fIUCZ+bQ1/sSZq143tLxwSssW3Z4AqE83brpr5flEY1Lx+Aowv/cPyQMmZxzRlFCGqw==" "integrity": "sha512-V5D7OInO4P9PdT/JACg7fwjbOORm3GklaMVgdGomjyxiyetgRND5CC9r35e1LK/DqHdoyDuhbFzdfrqWtpmEIw=="
}, },
"@mui/icons-material": { "@mui/icons-material": {
"version": "5.10.6", "version": "5.10.6",
@@ -19671,14 +19677,14 @@
} }
}, },
"@mui/material": { "@mui/material": {
"version": "5.10.7", "version": "5.10.8",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.10.7.tgz", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.10.8.tgz",
"integrity": "sha512-o1jcQGii+q7ORrXhBiMmGzFDaboc1qTgOOC3zDW+NR9ryVzWzL7qEeqoORbgDB5zk9OBsXCjB91fUH/ls5xMwg==", "integrity": "sha512-sF/Ka0IJjGXV52zoT4xAWEqXVRjNYbIjATo9L4Q5oQC5iJpGrKJFY16uNtWWB0+vp/nayAuPGZHrxtV+t3ecdQ==",
"requires": { "requires": {
"@babel/runtime": "^7.19.0", "@babel/runtime": "^7.19.0",
"@mui/base": "5.0.0-alpha.99", "@mui/base": "5.0.0-alpha.100",
"@mui/core-downloads-tracker": "^5.10.7", "@mui/core-downloads-tracker": "^5.10.8",
"@mui/system": "^5.10.7", "@mui/system": "^5.10.8",
"@mui/types": "^7.2.0", "@mui/types": "^7.2.0",
"@mui/utils": "^5.10.6", "@mui/utils": "^5.10.6",
"@types/react-transition-group": "^4.4.5", "@types/react-transition-group": "^4.4.5",
@@ -19700,9 +19706,9 @@
} }
}, },
"@mui/styled-engine": { "@mui/styled-engine": {
"version": "5.10.7", "version": "5.10.8",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.10.7.tgz", "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.10.8.tgz",
"integrity": "sha512-CCrtW+vvCKEm6pOE/QcutQ+ORC/iE6D1ghscN4l7LE2JXPvTXO/z0yu8Wxug1JEDlWm4r1Qa0PzJe1P9bjKzNA==", "integrity": "sha512-w+y8WI18EJV6zM/q41ug19cE70JTeO6sWFsQ7tgePQFpy6ToCVPh0YLrtqxUZXSoMStW5FMw0t9fHTFAqPbngw==",
"requires": { "requires": {
"@babel/runtime": "^7.19.0", "@babel/runtime": "^7.19.0",
"@emotion/cache": "^11.10.3", "@emotion/cache": "^11.10.3",
@@ -19711,13 +19717,13 @@
} }
}, },
"@mui/system": { "@mui/system": {
"version": "5.10.7", "version": "5.10.8",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.10.7.tgz", "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.10.8.tgz",
"integrity": "sha512-kwyhjjKGsgtBRFl6vSqidDZcNKU5S1juTgm4Xi2fyWxaEbIQb9Sh9y0iVP2bNCJzgDr0alLaENOZOEaDWHISAQ==", "integrity": "sha512-hRQ354zcrYP/KHqK8FheICSvE9raQaUgQaV+A3oD4JETaFUCVI9Ytt+RcQYgTqx02xlCXIjl8LK1rPjTneySqw==",
"requires": { "requires": {
"@babel/runtime": "^7.19.0", "@babel/runtime": "^7.19.0",
"@mui/private-theming": "^5.10.6", "@mui/private-theming": "^5.10.6",
"@mui/styled-engine": "^5.10.7", "@mui/styled-engine": "^5.10.8",
"@mui/types": "^7.2.0", "@mui/types": "^7.2.0",
"@mui/utils": "^5.10.6", "@mui/utils": "^5.10.6",
"clsx": "^1.2.1", "clsx": "^1.2.1",
@@ -19819,9 +19825,9 @@
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw=="
}, },
"@remix-run/router": { "@remix-run/router": {
"version": "1.0.1", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz",
"integrity": "sha512-eBV5rvW4dRFOU1eajN7FmYxjAIVz/mRHgUE9En9mBn6m3mulK3WTR5C3iQhL9MZ14rWAq+xOlEaCkDiW0/heOg==" "integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ=="
}, },
"@rollup/plugin-babel": { "@rollup/plugin-babel": {
"version": "5.3.1", "version": "5.3.1",
@@ -19877,9 +19883,9 @@
"integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg=="
}, },
"@sinclair/typebox": { "@sinclair/typebox": {
"version": "0.24.43", "version": "0.24.44",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.43.tgz", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.44.tgz",
"integrity": "sha512-1orQTvtazZmsPeBroJjysvsOQCYV2yjWlebkSY38pl5vr2tdLjEJ+LoxITlGNZaH2RE19WlAwQMkH/7C14wLfw==" "integrity": "sha512-ka0W0KN5i6LfrSocduwliMMpqVgohtPFidKdMEOUjoOFCHcOOYkKsPRxfs5f15oPNHTm6ERAm0GV/+/LTKeiWg=="
}, },
"@sinonjs/commons": { "@sinonjs/commons": {
"version": "1.8.3", "version": "1.8.3",
@@ -20018,9 +20024,9 @@
} }
}, },
"@table-library/react-table-library": { "@table-library/react-table-library": {
"version": "4.0.18", "version": "4.0.23",
"resolved": "https://registry.npmjs.org/@table-library/react-table-library/-/react-table-library-4.0.18.tgz", "resolved": "https://registry.npmjs.org/@table-library/react-table-library/-/react-table-library-4.0.23.tgz",
"integrity": "sha512-3GX5712YuWPFeUilr8UG22SCNR9BylD5Y4xPF+pHhb1QM7WXw6SLgpeGq6UDTQGOtmKtKtwKGWXdkmvVDg8pkA==", "integrity": "sha512-o2L/fqhwQNxsNbbm3LIiyZzEwaTslhG1tY9ArkYdS0xJyRhJxcOLfbJ3+dcnOOn+aIJpmPmQH+gr7RYJC0P8uw==",
"requires": { "requires": {
"clsx": "1.1.1", "clsx": "1.1.1",
"react-virtualized-auto-sizer": "1.0.6", "react-virtualized-auto-sizer": "1.0.6",
@@ -20223,9 +20229,9 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
}, },
"@types/lodash": { "@types/lodash": {
"version": "4.14.185", "version": "4.14.186",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.185.tgz", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz",
"integrity": "sha512-evMDG1bC4rgQg4ku9tKpuMh5iBNEwNa3tf9zRHdP1qlv+1WUg44xat4IxCE14gIpZRGUUWAx2VhItCZc25NfMA==" "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw=="
}, },
"@types/mime": { "@types/mime": {
"version": "3.0.1", "version": "3.0.1",
@@ -20233,9 +20239,9 @@
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA=="
}, },
"@types/node": { "@types/node": {
"version": "18.7.23", "version": "18.8.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.23.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.3.tgz",
"integrity": "sha512-DWNcCHolDq0ZKGizjx2DZjR/PqsYwAcYUJmfMWqtVU2MBMG5Mo+xFZrhGId5r/O5HOuMPyQEcM6KUBp5lBZZBg==" "integrity": "sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w=="
}, },
"@types/parse-json": { "@types/parse-json": {
"version": "4.0.0", "version": "4.0.0",
@@ -20973,12 +20979,13 @@
"integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==" "integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w=="
}, },
"axios": { "axios": {
"version": "0.27.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.1.2.tgz",
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "integrity": "sha512-bznQyETwElsXl2RK7HLLwb5GPpOLlycxHCtrpDR/4RqqBzjARaOTo3jz4IgtntWUYee7Ne4S8UHd92VCuzPaWA==",
"requires": { "requires": {
"follow-redirects": "^1.14.9", "follow-redirects": "^1.15.0",
"form-data": "^4.0.0" "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
} }
}, },
"axobject-query": { "axobject-query": {
@@ -21399,9 +21406,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001412", "version": "1.0.30001414",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001412.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz",
"integrity": "sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA==" "integrity": "sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg=="
}, },
"case-sensitive-paths-webpack-plugin": { "case-sensitive-paths-webpack-plugin": {
"version": "2.4.0", "version": "2.4.0",
@@ -22255,9 +22262,9 @@
} }
}, },
"electron-to-chromium": { "electron-to-chromium": {
"version": "1.4.266", "version": "1.4.270",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.266.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.270.tgz",
"integrity": "sha512-saJTYECxUSv7eSpnXw0XIEvUkP9x4s/x2mm3TVX7k4rIFS6f5TjBih1B5h437WzIhHQjid+d8ouQzPQskMervQ==" "integrity": "sha512-KNhIzgLiJmDDC444dj9vEOpZEgsV96ult9Iff98Vanumn+ShJHd5se8aX6KeVxdc0YQeqdrezBZv89rleDbvSg=="
}, },
"emittery": { "emittery": {
"version": "0.8.1", "version": "0.8.1",
@@ -25614,9 +25621,9 @@
} }
}, },
"js-sdsl": { "js-sdsl": {
"version": "4.1.4", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
"integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==" "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q=="
}, },
"js-tokens": { "js-tokens": {
"version": "4.0.0", "version": "4.0.0",
@@ -26649,9 +26656,9 @@
} }
}, },
"postcss": { "postcss": {
"version": "8.4.16", "version": "8.4.17",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz",
"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", "integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
"requires": { "requires": {
"nanoid": "^3.3.4", "nanoid": "^3.3.4",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
@@ -27407,6 +27414,11 @@
} }
} }
}, },
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"psl": { "psl": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -27692,20 +27704,20 @@
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
}, },
"react-router": { "react-router": {
"version": "6.4.1", "version": "6.4.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz",
"integrity": "sha512-OJASKp5AykDWFewgWUim1vlLr7yfD4vO/h+bSgcP/ix8Md+LMHuAjovA74MQfsfhQJGGN1nHRhwS5qQQbbBt3A==", "integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==",
"requires": { "requires": {
"@remix-run/router": "1.0.1" "@remix-run/router": "1.0.2"
} }
}, },
"react-router-dom": { "react-router-dom": {
"version": "6.4.1", "version": "6.4.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.1.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz",
"integrity": "sha512-MY7NJCrGNVJtGp8ODMOBHu20UaIkmwD2V3YsAOUQoCXFk7Ppdwf55RdcGyrSj+ycSL9Uiwrb3gTLYSnzcRoXww==", "integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==",
"requires": { "requires": {
"@remix-run/router": "1.0.1", "@remix-run/router": "1.0.2",
"react-router": "6.4.1" "react-router": "6.4.2"
} }
}, },
"react-scripts": { "react-scripts": {
@@ -29096,9 +29108,9 @@
} }
}, },
"typesafe-i18n": { "typesafe-i18n": {
"version": "5.13.1", "version": "5.14.0",
"resolved": "https://registry.npmjs.org/typesafe-i18n/-/typesafe-i18n-5.13.1.tgz", "resolved": "https://registry.npmjs.org/typesafe-i18n/-/typesafe-i18n-5.14.0.tgz",
"integrity": "sha512-9Cgikxcj+2LAWQGZ8pOQAf14P+aOOcOel/G1kSAXAObA4Htj8R+qgCx9DqUnxjRfugWzM/19ZqGss7dl8Dw7Gg==", "integrity": "sha512-ZNHysUvZZhmUuMjBvDGtUI8vT3g//4ay5fFOk2sJCsjx4ztippW1Hrhrq59nJ9mV/Q0u4OX80Gyorq8L3rwNLw==",
"requires": {} "requires": {}
}, },
"typescript": { "typescript": {

View File

@@ -8,15 +8,15 @@
"@emotion/styled": "^11.10.4", "@emotion/styled": "^11.10.4",
"@msgpack/msgpack": "^2.8.0", "@msgpack/msgpack": "^2.8.0",
"@mui/icons-material": "^5.10.6", "@mui/icons-material": "^5.10.6",
"@mui/material": "^5.10.7", "@mui/material": "^5.10.8",
"@table-library/react-table-library": "4.0.18", "@table-library/react-table-library": "4.0.23",
"@types/lodash": "^4.14.185", "@types/lodash": "^4.14.186",
"@types/node": "^18.7.23", "@types/node": "^18.8.3",
"@types/react": "^18.0.21", "@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"axios": "^0.27.2", "axios": "^1.1.2",
"http-proxy-middleware": "^2.0.6", "http-proxy-middleware": "^2.0.6",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@@ -27,10 +27,10 @@
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.2", "react-dropzone": "^14.2.2",
"react-icons": "^4.4.0", "react-icons": "^4.4.0",
"react-router-dom": "^6.4.1", "react-router-dom": "^6.4.2",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"sockette": "^2.0.6", "sockette": "^2.0.6",
"typesafe-i18n": "^5.13.1", "typesafe-i18n": "^5.14.0",
"typescript": "^4.8.4" "typescript": "^4.8.4"
}, },
"scripts": { "scripts": {

View File

@@ -9,8 +9,6 @@ import * as AuthenticationApi from './api/authentication';
import { PROJECT_NAME } from './api/env'; import { PROJECT_NAME } from './api/env';
import { AuthenticationContext } from './contexts/authentication'; import { AuthenticationContext } from './contexts/authentication';
import { AxiosError } from 'axios';
import { extractErrorMessage, onEnterCallback, updateValue } from './utils'; import { extractErrorMessage, onEnterCallback, updateValue } from './utils';
import { SignInRequest } from './types'; import { SignInRequest } from './types';
import { ValidatedTextField } from './components'; import { ValidatedTextField } from './components';
@@ -24,6 +22,8 @@ import { ReactComponent as NLflag } from './i18n/NL.svg';
import { ReactComponent as DEflag } from './i18n/DE.svg'; import { ReactComponent as DEflag } from './i18n/DE.svg';
import { ReactComponent as GBflag } from './i18n/GB.svg'; import { ReactComponent as GBflag } from './i18n/GB.svg';
import { ReactComponent as SEflag } from './i18n/SE.svg'; import { ReactComponent as SEflag } from './i18n/SE.svg';
import { ReactComponent as PLflag } from './i18n/PL.svg';
import { ReactComponent as NOflag } from './i18n/NO.svg';
const SignIn: FC = () => { const SignIn: FC = () => {
const authenticationContext = useContext(AuthenticationContext); const authenticationContext = useContext(AuthenticationContext);
@@ -56,8 +56,8 @@ const SignIn: FC = () => {
try { try {
const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest); const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest);
authenticationContext.signIn(loginResponse.access_token); authenticationContext.signIn(loginResponse.access_token);
} catch (error: unknown) { } catch (error) {
if (error instanceof AxiosError) { if (error.response) {
if (error.response?.status === 401) { if (error.response?.status === 401) {
enqueueSnackbar(LL.INVALID_LOGIN(), { variant: 'warning' }); enqueueSnackbar(LL.INVALID_LOGIN(), { variant: 'warning' });
} }
@@ -125,6 +125,14 @@ const SignIn: FC = () => {
<SEflag style={{ width: 24 }} /> <SEflag style={{ width: 24 }} />
&nbsp;SE &nbsp;SE
</Button> </Button>
<Button size="small" variant={locale === 'pl' ? 'contained' : 'outlined'} onClick={() => selectLocale('pl')}>
<PLflag style={{ width: 24 }} />
&nbsp;PL
</Button>
<Button size="small" variant={locale === 'no' ? 'contained' : 'outlined'} onClick={() => selectLocale('no')}>
<NOflag style={{ width: 24 }} />
&nbsp;NO
</Button>
</Box> </Box>
<ValidatedTextField <ValidatedTextField

View File

@@ -1,4 +1,4 @@
import axios, { AxiosPromise, CancelToken } from 'axios'; import axios, { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios';
import { decode } from '@msgpack/msgpack'; import { decode } from '@msgpack/msgpack';
@@ -89,7 +89,7 @@ function calculateEventSourceRoot(endpointPath: string) {
export interface FileUploadConfig { export interface FileUploadConfig {
cancelToken?: CancelToken; cancelToken?: CancelToken;
onUploadProgress?: (progressEvent: ProgressEvent) => void; onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
} }
export const startUploadFile = (url: string, file: File, config?: FileUploadConfig): AxiosPromise<void> => { export const startUploadFile = (url: string, file: File, config?: FileUploadConfig): AxiosPromise<void> => {

View File

@@ -93,7 +93,7 @@ const LayoutAuthMenu: FC = () => {
<PLflag style={{ width: 16, verticalAlign: 'middle' }} /> <PLflag style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;PL &nbsp;PL
</MenuItem> </MenuItem>
<MenuItem key="no" value="no" disabled> <MenuItem key="no" value="no">
<NOflag style={{ width: 16, verticalAlign: 'middle' }} /> <NOflag style={{ width: 16, verticalAlign: 'middle' }} />
&nbsp;NO &nbsp;NO
</MenuItem> </MenuItem>

View File

@@ -1,6 +1,8 @@
import { FC, Fragment } from 'react'; import { FC, Fragment } from 'react';
import { useDropzone, DropzoneState } from 'react-dropzone'; import { useDropzone, DropzoneState } from 'react-dropzone';
import { AxiosProgressEvent } from 'axios';
import { Box, Button, LinearProgress, Theme, Typography, useTheme } from '@mui/material'; import { Box, Button, LinearProgress, Theme, Typography, useTheme } from '@mui/material';
import CloudUploadIcon from '@mui/icons-material/CloudUpload'; import CloudUploadIcon from '@mui/icons-material/CloudUpload';
@@ -8,8 +10,6 @@ import CancelIcon from '@mui/icons-material/Cancel';
import { useI18nContext } from '../../i18n/i18n-react'; import { useI18nContext } from '../../i18n/i18n-react';
const progressPercentage = (progress: ProgressEvent) => Math.round((progress.loaded * 100) / progress.total);
const getBorderColor = (theme: Theme, props: DropzoneState) => { const getBorderColor = (theme: Theme, props: DropzoneState) => {
if (props.isDragAccept) { if (props.isDragAccept) {
return theme.palette.success.main; return theme.palette.success.main;
@@ -27,7 +27,7 @@ export interface SingleUploadProps {
onDrop: (acceptedFiles: File[]) => void; onDrop: (acceptedFiles: File[]) => void;
onCancel: () => void; onCancel: () => void;
uploading: boolean; uploading: boolean;
progress?: ProgressEvent; progress?: AxiosProgressEvent;
} }
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, progress }) => { const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, progress }) => {
@@ -47,8 +47,8 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
const progressText = () => { const progressText = () => {
if (uploading) { if (uploading) {
if (progress?.lengthComputable) { if (progress?.total) {
return LL.UPLOADING() + `: ${progressPercentage(progress)}%`; return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%';
} }
return LL.UPLOADING() + `\u2026`; return LL.UPLOADING() + `\u2026`;
} }
@@ -80,8 +80,8 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
<Fragment> <Fragment>
<Box width="100%" p={2}> <Box width="100%" p={2}>
<LinearProgress <LinearProgress
variant={!progress || progress.lengthComputable ? 'determinate' : 'indeterminate'} variant={!progress || progress.total ? 'determinate' : 'indeterminate'}
value={!progress ? 0 : progress.lengthComputable ? progressPercentage(progress) : 0} value={!progress ? 0 : progress.total ? Math.round((progress.loaded * 100) / progress.total) : 0}
/> />
</Box> </Box>
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}> <Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import axios, { AxiosPromise, CancelTokenSource } from 'axios'; import axios, { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
import { extractErrorMessage } from '../../utils'; import { extractErrorMessage } from '../../utils';
@@ -16,7 +16,7 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const [uploading, setUploading] = useState<boolean>(false); const [uploading, setUploading] = useState<boolean>(false);
const [uploadProgress, setUploadProgress] = useState<ProgressEvent>(); const [uploadProgress, setUploadProgress] = useState<AxiosProgressEvent>();
const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>(); const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>();
const resetUploadingStates = () => { const resetUploadingStates = () => {
@@ -47,7 +47,7 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => {
}); });
resetUploadingStates(); resetUploadingStates();
enqueueSnackbar(LL.UPLOAD() + ' ' + LL.SUCCESSFUL(), { variant: 'success' }); enqueueSnackbar(LL.UPLOAD() + ' ' + LL.SUCCESSFUL(), { variant: 'success' });
} catch (error: unknown) { } catch (error) {
if (axios.isCancel(error)) { if (axios.isCancel(error)) {
enqueueSnackbar(LL.UPLOAD() + ' ' + LL.ABORTED(), { variant: 'warning' }); enqueueSnackbar(LL.UPLOAD() + ' ' + LL.ABORTED(), { variant: 'warning' });
} else { } else {

View File

@@ -28,7 +28,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
const decodedMe = AuthenticationApi.decodeMeJWT(accessToken); const decodedMe = AuthenticationApi.decodeMeJWT(accessToken);
setMe(decodedMe); setMe(decodedMe);
enqueueSnackbar(LL.LOGGED_IN({ name: decodedMe.username }), { variant: 'success' }); enqueueSnackbar(LL.LOGGED_IN({ name: decodedMe.username }), { variant: 'success' });
} catch (error: unknown) { } catch (error) {
setMe(undefined); setMe(undefined);
throw new Error('Failed to parse JWT'); throw new Error('Failed to parse JWT');
} }
@@ -54,7 +54,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
await AuthenticationApi.verifyAuthorization(); await AuthenticationApi.verifyAuthorization();
setMe(AuthenticationApi.decodeMeJWT(accessToken)); setMe(AuthenticationApi.decodeMeJWT(accessToken));
setInitialized(true); setInitialized(true);
} catch (error: unknown) { } catch (error) {
setMe(undefined); setMe(undefined);
setInitialized(true); setInitialized(true);
} }

View File

@@ -16,7 +16,7 @@ const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
try { try {
const response = await FeaturesApi.readFeatures(); const response = await FeaturesApi.readFeatures();
setFeatures(response.data); setFeatures(response.data);
} catch (error: unknown) { } catch (error) {
setErrorMessage(extractErrorMessage(error, 'Failed to fetch application details.')); setErrorMessage(extractErrorMessage(error, 'Failed to fetch application details.'));
} }
}, []); }, []);

View File

@@ -5,6 +5,7 @@ import DeviceHubIcon from '@mui/icons-material/DeviceHub';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import ReportIcon from '@mui/icons-material/Report'; import ReportIcon from '@mui/icons-material/Report';
import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff'; import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff';
import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion';
import { ButtonRow, FormLoader, SectionContent } from '../../components'; import { ButtonRow, FormLoader, SectionContent } from '../../components';
import { MqttStatus, MqttDisconnectReason } from '../../types'; import { MqttStatus, MqttDisconnectReason } from '../../types';
@@ -31,6 +32,12 @@ export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatus, theme: Theme) =
return theme.palette.error.main; return theme.palette.error.main;
}; };
export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatus, theme: Theme) => {
if (mqtt_queued <= 1) return theme.palette.success.main;
return theme.palette.warning.main;
};
const MqttStatusForm: FC = () => { const MqttStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus }); const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus });
@@ -38,14 +45,14 @@ const MqttStatusForm: FC = () => {
const theme = useTheme(); const theme = useTheme();
const mqttStatus = ({ enabled, connected }: MqttStatus) => { const mqttStatus = ({ enabled, connected, connect_count }: MqttStatus) => {
if (!enabled) { if (!enabled) {
return LL.NOT_ENABLED(); return LL.NOT_ENABLED();
} }
if (connected) { if (connected) {
return LL.CONNECTED(); return LL.CONNECTED() + (connect_count > 1 ? ' (' + connect_count + ')' : '');
} }
return LL.DISCONNECTED(); return LL.DISCONNECTED() + (connect_count > 1 ? ' (' + connect_count + ')' : '');
}; };
const disconnectReason = ({ disconnect_reason }: MqttStatus) => { const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
@@ -77,36 +84,44 @@ const MqttStatusForm: FC = () => {
} }
const renderConnectionStatus = () => { const renderConnectionStatus = () => {
if (data.connected) {
return (
<>
<ListItem>
<ListItemAvatar>
<Avatar>#</Avatar>
</ListItemAvatar>
<ListItemText primary="Client ID" secondary={data.client_id} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: mqttPublishHighlight(data, theme) }}>
<SpeakerNotesOffIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={'MQTT Publish ' + LL.ERRORS()} secondary={data.mqtt_fails} />
</ListItem>
</>
);
}
return ( return (
<> <>
{!data.connected && (
<>
<ListItem>
<ListItemAvatar>
<Avatar>
<ReportIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.DISCONNECT_REASON()} secondary={disconnectReason(data)} />
</ListItem>
<Divider variant="inset" component="li" />
</>
)}
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar> <Avatar>#</Avatar>
<ReportIcon /> </ListItemAvatar>
<ListItemText primary="Client ID" secondary={data.client_id} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: mqttQueueHighlight(data, theme) }}>
<AutoAwesomeMotionIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary={LL.DISCONNECT_REASON()} secondary={disconnectReason(data)} /> <ListItemText primary="MQTT Queue" secondary={data.mqtt_queued} />
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<ListItemAvatar>
<Avatar sx={{ bgcolor: mqttPublishHighlight(data, theme) }}>
<SpeakerNotesOffIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={'MQTT ' + LL.ERRORS()} secondary={data.mqtt_fails} />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
</> </>

View File

@@ -1,8 +1,6 @@
import { useEffect, FC, useState, useCallback, useRef } from 'react'; import { useEffect, FC, useState, useCallback, useRef } from 'react';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
import { AxiosError } from 'axios';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
@@ -57,8 +55,8 @@ const WiFiNetworkScanner: FC = () => {
newNetworkList.networks.sort(compareNetworks); newNetworkList.networks.sort(compareNetworks);
setNetworkList(newNetworkList); setNetworkList(newNetworkList);
} }
} catch (error: unknown) { } catch (error) {
if (error instanceof AxiosError) { if (error.response) {
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message); finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
} else { } else {
finishedWithError(LL.PROBLEM_LOADING()); finishedWithError(LL.PROBLEM_LOADING());
@@ -73,8 +71,8 @@ const WiFiNetworkScanner: FC = () => {
try { try {
await NetworkApi.scanNetworks(); await NetworkApi.scanNetworks();
setTimeout(pollNetworkList, POLLING_FREQUENCY); setTimeout(pollNetworkList, POLLING_FREQUENCY);
} catch (error: unknown) { } catch (error) {
if (error instanceof AxiosError) { if (error.response) {
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message); finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
} else { } else {
finishedWithError(LL.PROBLEM_LOADING()); finishedWithError(LL.PROBLEM_LOADING());

View File

@@ -91,7 +91,7 @@ const NTPStatusForm: FC = () => {
enqueueSnackbar(LL.TIME_SET(), { variant: 'success' }); enqueueSnackbar(LL.TIME_SET(), { variant: 'success' });
setSettingTime(false); setSettingTime(false);
loadData(); loadData();
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setProcessing(false); setProcessing(false);

View File

@@ -37,7 +37,7 @@ const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
const getToken = useCallback(async () => { const getToken = useCallback(async () => {
try { try {
setToken((await SecurityApi.generateToken(username)).data); setToken((await SecurityApi.generateToken(username)).data);
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} }
}, [username, enqueueSnackbar, LL]); }, [username, enqueueSnackbar, LL]);

View File

@@ -50,7 +50,7 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
} else { } else {
saveFile(response.data, 'settings'); saveFile(response.data, 'settings');
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} }
}; };
@@ -63,7 +63,7 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
} else { } else {
saveFile(response.data, 'customizations'); saveFile(response.data, 'customizations');
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} }
}; };

View File

@@ -21,7 +21,7 @@ const RestartMonitor: FC = () => {
try { try {
await SystemApi.readSystemStatus(POLL_TIMEOUT); await SystemApi.readSystemStatus(POLL_TIMEOUT);
document.location.href = '/fileUpdated'; document.location.href = '/fileUpdated';
} catch (error: unknown) { } catch (error) {
if (new Date().getTime() < timeoutAt.current) { if (new Date().getTime() < timeoutAt.current) {
setTimeoutId(setTimeout(poll.current, POLL_INTERVAL)); setTimeoutId(setTimeout(poll.current, POLL_INTERVAL));
} else { } else {

View File

@@ -111,7 +111,7 @@ const SystemLog: FC = () => {
if (response.status !== 200) { if (response.status !== 200) {
enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' }); enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} }
} }
@@ -163,7 +163,7 @@ const SystemLog: FC = () => {
const fetchLog = useCallback(async () => { const fetchLog = useCallback(async () => {
try { try {
setLogEntries((await SystemApi.readLogEntries()).data); setLogEntries((await SystemApi.readLogEntries()).data);
} catch (error: unknown) { } catch (error) {
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
} }
}, [LL]); }, [LL]);

View File

@@ -86,7 +86,7 @@ const SystemStatusForm: FC = () => {
try { try {
await SystemApi.restart(); await SystemApi.restart();
enqueueSnackbar(LL.APPLICATION_RESTARTING(), { variant: 'info' }); enqueueSnackbar(LL.APPLICATION_RESTARTING(), { variant: 'info' });
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} finally { } finally {
setConfirmRestart(false); setConfirmRestart(false);
@@ -180,7 +180,7 @@ const SystemStatusForm: FC = () => {
try { try {
await SystemApi.factoryReset(); await SystemApi.factoryReset();
enqueueSnackbar(LL.SYSTEM_FACTORY_TEXT(), { variant: 'info' }); enqueueSnackbar(LL.SYSTEM_FACTORY_TEXT(), { variant: 'info' });
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setConfirmFactoryReset(false); setConfirmFactoryReset(false);

File diff suppressed because it is too large Load Diff

View File

@@ -1,270 +1,269 @@
import type { BaseTranslation } from '../i18n-types'; import type { BaseTranslation } from '../i18n-types';
const no: BaseTranslation = { const no: BaseTranslation = {
LANGUAGE: 'Language', LANGUAGE: 'Språk',
RETRY: 'Retry', RETRY: 'Forsøk igjen',
LOADING: 'Loading', LOADING: 'Laster',
IS_REQUIRED: 'is required', IS_REQUIRED: 'er nødvendig',
SIGN_IN: 'Sign In', SIGN_IN: 'Logg inn',
SIGN_OUT: 'Sign Out', SIGN_OUT: 'Logg ut',
USERNAME: 'Username', USERNAME: 'Brukernavn',
PASSWORD: 'Password', PASSWORD: 'Passord',
DASHBOARD: 'Dashboard', DASHBOARD: 'Dashboard',
SETTINGS: 'Settings', SETTINGS: 'Innstillinger',
SAVED: 'saved', SAVED: 'lagret',
HELP: 'Help', HELP: 'Hjelp',
LOGGED_IN: 'Logged in as {name}', LOGGED_IN: 'Logget in som {name}',
PLEASE_SIGNIN: 'Please sign in to continue', PLEASE_SIGNIN: 'Venligst logge inn for å fortsetta',
UPLOAD_SUCCESSFUL: 'Upload successful', UPLOAD_SUCCESSFUL: 'Opplasting lykkes',
DOWNLOAD_SUCCESSFUL: 'Download successful', DOWNLOAD_SUCCESSFUL: 'Nedlasting lykkes',
INVALID_LOGIN: 'Invalid login details', INVALID_LOGIN: 'Ugyldig innlogging',
NETWORK: 'Network', NETWORK: 'Nettverk',
SECURITY: 'Security', SECURITY: 'Sikkerhet',
ONOFF_CAP: 'ON/OFF', ONOFF_CAP: 'PÅ/AV',
ONOFF: 'on/off', ONOFF: 'på/av',
TYPE: 'Type', TYPE: 'Type',
DESCRIPTION: 'Description', DESCRIPTION: 'Beskrivelse',
ENTITIES: 'Entities', ENTITIES: 'Ojekter',
REFRESH: 'Refresh', REFRESH: 'Oppdater',
EXPORT: 'Export', EXPORT: 'Eksport',
DEVICE_DETAILS: 'Device Details', DEVICE_DETAILS: 'Enhetsdetaljer',
BRAND: 'Brand', BRAND: 'Fabrikat',
ENTITY_NAME: 'Entity Name', ENTITY_NAME: 'Objektsnavn',
VALUE: 'Value', VALUE: 'Verdi',
SHOW_FAV: 'only show favorites', SHOW_FAV: ' Vis kun favoritter',
DEVICE_SENSOR_DATA: 'Device and Sensor Data', DEVICE_SENSOR_DATA: 'Enheter og Sensordata',
DEVICES_SENSORS: 'Devices & Sensors', DEVICES_SENSORS: 'Enheter og Sensorer',
ATTACHED_SENSORS: 'Attached EMS-ESP Sensors', ATTACHED_SENSORS: 'Tilkoblede EMS-ESP Sensorer',
RUN_COMMAND: 'Call Command', RUN_COMMAND: 'Kjør kommando',
CHANGE_VALUE: 'Change Value', CHANGE_VALUE: 'Endre Verdi',
CANCEL: 'Cancel', CANCEL: 'Avbryt',
RESET: 'Reset', RESET: 'Nullstill',
SEND: 'Send', SEND: 'Send',
SAVE: 'Save', SAVE: 'Lagre',
REMOVE: 'Remove', REMOVE: 'Fjern',
PROBLEM_UPDATING: 'Problem updating', PROBLEM_UPDATING: 'Problem med oppdatering',
PROBLEM_LOADING: 'Problem loading', PROBLEM_LOADING: 'Problem med opplasting',
ACCESS_DENIED: 'Access Denied', ACCESS_DENIED: 'Tilgang nektet',
ANALOG_SENSOR: 'Analog Sensor', ANALOG_SENSOR: 'Analog Sensor',
ANALOG_SENSORS: 'Analog Sensors', ANALOG_SENSORS: 'Analoge Sensorer',
UPDATED: 'Updated', UPDATED: 'Oppdatert',
UPDATE: 'Update', UPDATE: 'Oppdater',
REMOVED: 'Removed', REMOVED: 'Slettet',
DELETION: 'Deletion', DELETION: 'Sletting',
OFFSET: 'Offset', OFFSET: 'Kompensering',
FACTOR: 'Factor', FACTOR: 'Faktor',
FREQ: 'Frequency', FREQ: 'Frekvens',
STARTVALUE: 'Start value', STARTVALUE: 'Startverdi',
WARN_GPIO: 'Warning: be careful when assigning a GPIO!', WARN_GPIO: 'Advarsel: vær forsiktig ved aktivering av GPIO!',
EDIT: 'Edit', EDIT: 'Endre',
TEMP_SENSOR: 'Temperature Sensor', TEMP_SENSOR: 'Temperatursensor',
TEMP_SENSORS: 'Temperature Sensors', TEMP_SENSORS: 'Temperaturesensorer',
WRITE_COMMAND: 'Write command {cmd}', WRITE_COMMAND: 'Skriv kommando {cmd}',
EMS_BUS_WARNING: EMS_BUS_WARNING:
'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile', 'EMS bussen koblet ned. Hvis denne advarselen fortsetter etter noen f¨sekunder sjekk instillinger og prosessorkort',
EMS_BUS_SCANNING: 'Scanning for EMS devices...', EMS_BUS_SCANNING: 'Søker etter EMS enheter...',
CONNECTED: 'Connected', CONNECTED: 'Tilkoblet',
TX_ISSUES: 'Tx issues - try a different Tx Mode', TX_ISSUES: 'Tx problemer - prøv en annen Tx Modus',
DISCONNECTED: 'Disconnected', DISCONNECTED: 'Frakoblet',
EMS_SCAN: 'Are you sure you want to initiate a full device scan of the EMS bus?', EMS_SCAN: 'Er du sikker på du vil starte full søking av EMS bussen?',
EMS_BUS_STATUS: 'EMS Bus Status', EMS_BUS_STATUS: 'EMS Buss Status',
ACTIVE_DEVICES: 'Active Devices & Sensors', ACTIVE_DEVICES: 'Aktive Enheter og Sensorer',
DEVICE: 'Device', DEVICE: 'Enhet',
SUCCESS: 'SUCCESS', SUCCESS: 'VELLYKKET',
FAIL: 'FAIL', FAIL: 'MISLYKKET',
QUALITY: 'QUALITY', QUALITY: 'KVALITET',
SCAN_DEVICES: 'Scan for new devices', SCAN_DEVICES: 'Søk etter nye enheter',
EMS_BUS_STATUS_TITLE: 'EMS Bus & Activity Status', EMS_BUS_STATUS_TITLE: 'EMS Buss & Aktivitet Status',
SCAN: 'Scan', SCAN: 'Søk',
STATUS_NAMES: [ STATUS_NAMES: [
'EMS Telegrams Received (Rx)', 'EMS Telegrammer Mottatt (Rx)',
'EMS Reads (Tx)', 'EMS Lest (Tx)',
'EMS Writes (Tx)', 'EMS Skrevet (Tx)',
'Temperature Sensor Reads', 'Temperatur Sensor Lest',
'Analog Sensor Reads', 'Analog Sensor Lest',
'MQTT Publishes', 'MQTT Publiseringer',
'API Calls', 'API Anrop',
'Syslog Messages' 'Syslog Meldinger'
], ],
NUM_DEVICES: '{num} Device{{s}}', NUM_DEVICES: '{num} Enhet{{er}}',
NUM_TEMP_SENSORS: '{num} Temperature Sensor{{s}}', NUM_TEMP_SENSORS: '{num} Temperatursensor{{er}}',
NUM_ANALOG_SENSORS: '{num} Analog Sensor{{s}}', NUM_ANALOG_SENSORS: '{num} Analogsensor{{er}}',
NUM_DAYS: '{num} Day{{s}}', NUM_DAYS: '{num} Dag{{er}}',
NUM_SECONDS: '{num} Second{{s}}', NUM_SECONDS: '{num} Sekund{{er}}',
NUM_HOURS: '{num} Hour{{s}}', NUM_HOURS: '{num} Time{{r}}',
NUM_MINUTES: '{num} Minute{{s}}', NUM_MINUTES: '{num} Minutt{{er}}',
APPLICATION_SETTINGS: 'Application Settings', APPLICATION_SETTINGS: 'Innstillinger',
CUSTOMIZATION: 'Customization', CUSTOMIZATION: 'Tilpasninger',
APPLICATION_RESTARTING: 'EMS-ESP is restarting', APPLICATION_RESTARTING: 'EMS-ESP restarter',
BOARD_PROFILE_TEXT: BOARD_PROFILE_TEXT:
'Select a pre-configured interface board profile from the list below or choose Custom to configure your own hardware settings', 'Velg en pre-konfigurert prosessor profil fra listen under eller velg Tilpasset for å konfigurere dine egne innstillinger',
BOARD_PROFILE: 'Board Profile', BOARD_PROFILE: 'Prosessor Profil',
BUTTON: 'Button', BUTTON: 'Knapp',
TEMPERATURE: 'Temperature', TEMPERATURE: 'Temperatur',
DISABLED: 'disabled', DISABLED: 'avslått',
GENERAL_OPTIONS: 'General Options', GENERAL_OPTIONS: 'Generelle Innstillinger',
LANGUAGE_ENTITIES: 'Language (for device entities)', LANGUAGE_ENTITIES: 'Språk (for objekter)',
HIDE_LED: 'Hide LED', HIDE_LED: 'Skjul LED',
ENABLE_TELNET: 'Enable Telnet Console', ENABLE_TELNET: 'Aktiver Telnet',
ENABLE_ANALOG: 'Enable Analog Sensors', ENABLE_ANALOG: 'Aktiver Analoge Sensorer',
CONVERT_FAHRENHEIT: 'Convert temperature values to Fahrenheit', CONVERT_FAHRENHEIT: 'Konverter temperatur til Fahrenheit',
BYPASS_TOKEN: 'Bypass Access Token authorization on API calls', BYPASS_TOKEN: 'Utelat Aksess Token authorisering av API kall',
READONLY: 'Enable read-only mode (blocks all outgoing EMS Tx Write commands)', READONLY: 'Aktiver read-only modus (blokker all EMS Tx Skriving)',
UNDERCLOCK_CPU: 'Underclock CPU speed', UNDERCLOCK_CPU: 'Underklokking av prosessorhastighet',
ENABLE_SHOWER_TIMER: 'Enable Shower Timer', ENABLE_SHOWER_TIMER: 'Aktiver Dusjtimer',
ENABLE_SHOWER_ALERT: 'Enable Shower Alert', ENABLE_SHOWER_ALERT: 'Aktiver Dusj-varsling',
TRIGGER_TIME: 'Trigger Time', TRIGGER_TIME: 'Aktiveringstid',
COLD_SHOT_DURATION: 'Cold Shot Duration', COLD_SHOT_DURATION: 'Tid på kaldt vann',
FORMATTING_OPTIONS: 'Formatting Options', FORMATTING_OPTIONS: 'Formatteringsalternativs',
BOOLEAN_FORMAT_DASHBOARD: 'Boolean Format Dashboard', BOOLEAN_FORMAT_DASHBOARD: 'Bool Format Dashboard',
BOOLEAN_FORMAT_API: 'Boolean Format API/MQTT', BOOLEAN_FORMAT_API: 'Bool Format API/MQTT',
ENUM_FORMAT: 'Enum Format API/MQTT', ENUM_FORMAT: 'Enum Format API/MQTT',
INDEX: 'Index', INDEX: 'Indeks',
ENABLE_PARASITE: 'Enable parasite power', ENABLE_PARASITE: 'Aktiver parasitt strømforsyning',
LOGGING: 'Logging', LOGGING: 'Logging',
LOG_HEX: 'Log EMS telegrams in hexadecimal', LOG_HEX: 'Logg EMS telegrammer i hexadesimal',
ENABLE_SYSLOG: 'Enable Syslog', ENABLE_SYSLOG: 'Aktiver Syslog',
MARK_INTERVAL: 'Mark Interval', MARK_INTERVAL: 'Oppdateringsintervall',
SECONDS: 'seconds', SECONDS: 'sekunder',
MINUTES: 'minutes', MINUTES: 'minutter',
RESTART: 'Restart', RESTART: 'Omstart',
HOURS: 'hours', HOURS: 'timer',
RESTART_TEXT: 'EMS-ESP needs to be restarted to apply changed system settings', RESTART_TEXT: 'EMS-ESP må omstartes for å iverksette endrede systeminstillinger',
RESTART_CONFIRM: 'Are you sure you want to restart EMS-ESP?', RESTART_CONFIRM: 'Er du sikker på at du vil omstarte EMS-ESP?',
COMMAND: 'Command', COMMAND: 'Kommando',
CUSTOMIZATIONS_RESTART: 'All customizations have been removed. Restarting...', CUSTOMIZATIONS_RESTART: 'Alle tilpasninger har blitt slettet. Restarter...',
CUSTOMIZATIONS_FULL: 'Selected entities exceeded limit. Please save in batches', CUSTOMIZATIONS_FULL: 'Antall valgte objekter for høyt. Largre i mindre antall om gangen',
CUSTOMIZATIONS_SAVED: 'Customizations saved', CUSTOMIZATIONS_SAVED: 'Tilpasninger lagret',
CUSTOMIZATIONS_HELP_1: 'Select a device and customize the entities options or click to rename', CUSTOMIZATIONS_HELP_1: 'Velg en enhet og tilpass underenheter med hjelp av alternativer eller velg å gi nytt navn',
CUSTOMIZATIONS_HELP_2: 'mark as favorite', CUSTOMIZATIONS_HELP_2: 'merk som favoritt',
CUSTOMIZATIONS_HELP_3: 'disable write action', CUSTOMIZATIONS_HELP_3: 'inaktiviser skriving',
CUSTOMIZATIONS_HELP_4: 'exclude from MQTT and API', CUSTOMIZATIONS_HELP_4: 'ekskludere fra MQTT og API',
CUSTOMIZATIONS_HELP_5: 'hide from Dashboard', CUSTOMIZATIONS_HELP_5: 'gjemme fra Dashboard',
SELECT_DEVICE: 'Select a device', SELECT_DEVICE: 'Velg en enhet',
SET_ALL: 'set all', SET_ALL: 'sett alle',
OPTIONS: 'Options', OPTIONS: 'Alternativ',
NAME: 'Name', NAME: 'Navn',
CUSTOMIZATIONS_RESET: CUSTOMIZATIONS_RESET:
'Are you sure you want remove all customizations including the custom settings of the Temperature and Analog sensors?', 'Er du sikker på att du vil fjerne tilpassninger inkludert innstillinger for Temperatur og Analoge sensorer?',
DEVICE_ENTITIES: 'Device Entities', DEVICE_ENTITIES: 'Enhets objekter',
USER_CUSTOMIZATION: 'User Customization', USER_CUSTOMIZATION: 'Brukertilpasninger',
SUPPORT_INFORMATION: 'Support Information', SUPPORT_INFORMATION: 'Supportinformasjon',
CLICK_HERE: 'Click Here', CLICK_HERE: 'Klikk her',
HELP_INFORMATION_1: 'Visit the online wiki to get instructions on how to configure EMS-ESP', HELP_INFORMATION_1: 'Besøk wiki for instruksjoner for å konfigurere EMS-ESP',
HELP_INFORMATION_2: 'For live community chat join our Discord server', HELP_INFORMATION_2: 'For community-support besøk vår Discord-server',
HELP_INFORMATION_3: 'To request a feature or report a bug', HELP_INFORMATION_3: 'For å be om en ny funksjon eller melde feil',
HELP_INFORMATION_4: HELP_INFORMATION_4:
'remember to download and attach your system information for a faster response when reporting an issue', 'husk å laste ned og legg ved din systeminformasjon for en raskere respons når du rapporterer et problem',
HELP_INFORMATION_5: HELP_INFORMATION_5: 'EMS-ESP er gratis og åpen kildekode. Bidra til utviklingen ved å gi oss en stjerne på GitHub!',
'EMS-ESP is a free and open-source project. Please support its future development by giving it a star on Github!', SUPPORT_INFO: 'Supportinfo',
SUPPORT_INFO: 'Support Info', UPLOAD: 'Opplasning',
UPLOAD: 'Upload', DOWNLOAD: 'Nedlasting',
DOWNLOAD: 'Download', ABORTED: 'avbrutt',
ABORTED: 'aborted', FAILED: 'feilet',
FAILED: 'failed', SUCCESSFUL: 'vellykket',
SUCCESSFUL: 'successful',
SYSTEM: 'System', SYSTEM: 'System',
LOG: 'Log', LOG: 'Logg',
STATUS: 'Status', STATUS: 'Status',
UPLOAD_DOWNLOAD: 'Upload/Download', UPLOAD_DOWNLOAD: 'Opp/Nedlasting',
SYSTEM_VERSION_RUNNING: 'You are currently running version', SYSTEM_VERSION_RUNNING: 'Du benytter versjon',
SYSTEM_APPLY_FIRMWARE: 'to apply the new firmware', SYSTEM_APPLY_FIRMWARE: 'for å aktivere ny firmware',
CLOSE: 'Close', CLOSE: 'Steng',
USE: 'Use', USE: 'Bruk',
FACTORY_RESET: 'Factory Reset', FACTORY_RESET: 'Sett tilbake til fabrikkinstilling',
SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart', SYSTEM_FACTORY_TEXT: 'Enhet har blitt satt tilbake til fabrikkinstilling og vil restarte',
SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset the device to its factory defaults?', SYSTEM_FACTORY_TEXT_DIALOG: 'Er du sikker på at du vil resette enheten til fabrikkinstillinger?',
VERSION_CHECK: 'Version Check', VERSION_CHECK: 'Versjonsjekk',
THE_LATEST: 'The latest', THE_LATEST: 'Den nyeste',
VERSION_IS: 'version is', VERSION_IS: 'versjonen er',
PLATFORM: 'Device (Platform / SDK)', PLATFORM: 'Enhet (Platform / SDK)',
UPTIME: 'System Uptime', UPTIME: 'System Oppetid',
CPU_FREQ: 'CPU Frequency', CPU_FREQ: 'CPU Frekvens',
HEAP: 'Heap (Free / Max Alloc)', HEAP: 'Heap (Ledig / Max Allokert)',
PSRAM: 'PSRAM (Size / Free)', PSRAM: 'PSRAM (Størrelse / Ledig)',
FLASH: 'Flash Chip (Size / Speed)', FLASH: 'Flash Chip (Størrelse / Hastighet)',
APPSIZE: 'Application (Used / Free)', APPSIZE: 'Applikasjon (Brukt / Ledig)',
FILESYSTEM: 'File System (Used / Free)', FILESYSTEM: 'File System (Brukt / Ledig)',
BUFFER_SIZE: 'Buffer Size', BUFFER_SIZE: 'Buffer Størrelse',
COMPACT: 'Compact', COMPACT: 'Komprimere',
ENABLE_OTA: 'Enable OTA Updates', ENABLE_OTA: 'Aktiviser OTA oppdateringer',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download the entity customizations', DOWNLOAD_CUSTOMIZATION_TEXT: 'Last ned objektstilpasninger',
DOWNLOAD_SETTINGS_TEXT: DOWNLOAD_SETTINGS_TEXT:
'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information', 'Last ned applikasjonskonfigurasjon. Vær varsom med å dele fila da den inneholder passord og annen sensitiv system informasjon',
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below', UPLOAD_TEXT: 'Last opp en ny firmware (.bin) fil, innstillinger eller tilpassninger (.json) fil nedenfor',
UPLOADING: 'Uploading', UPLOADING: 'Opplasting',
UPLOAD_DROP_TEXT: 'Drop file or click here', UPLOAD_DROP_TEXT: 'Slipp fil eller klikk her',
ERROR: 'Unexpected Error, please try again', ERROR: 'Ukjent feil, prøv igjen',
TIME_SET: 'Time set', TIME_SET: 'Still in tid',
MANAGE_USERS: 'Manage Users', MANAGE_USERS: 'Administrer Brukere',
IS_ADMIN: 'is Admin', IS_ADMIN: 'er Admin',
USER_WARNING: 'You must have at least one admin user configured', USER_WARNING: 'Du må ha minst en admin bruker konfigurert',
ADD: 'Add', ADD: 'Legg til',
ACCESS_TOKEN_FOR: 'Access Token for', ACCESS_TOKEN_FOR: 'Aksess Token for',
ACCESS_TOKEN_TEXT: ACCESS_TOKEN_TEXT:
'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.', 'Token nedenfor benyttes med REST API-kall som krever autorisering. Den kan sendes med enten som en Bearer token i Authorization-headern eller i access_token URL query-parameter.',
GENERATING_TOKEN: 'Generating token', GENERATING_TOKEN: 'Generer token',
USER: 'User', USER: 'Bruker',
MODIFY: 'Modify', MODIFY: 'Endre',
SU_TEXT: SU_TEXT:
'The su (super user) password is used to sign authentication tokens and also enable admin privileges within the Console.', 'su brukeren (super user) passord benyttes for å signere autentiserings token samt å tillate admin privileger i konsoll modus.',
NOT_ENABLED: 'Not enabled', NOT_ENABLED: 'Ikke aktiv',
ERRORS: 'Errors', ERRORS: 'Feil',
DISCONNECT_REASON: 'Disconnect Reason', DISCONNECT_REASON: 'Årsak til nedkobling',
ENABLE_MQTT: 'Enable MQTT', ENABLE_MQTT: 'Aktiver MQTT',
OPTIONAL: 'Optional', OPTIONAL: 'Valgfritt',
FORMATTING: 'Formatting', FORMATTING: 'Formatering',
FORMAT: 'Format', FORMAT: 'Format',
MQTT_NEST_1: 'Nested in a single topic', MQTT_NEST_1: 'Nestet i en topic',
MQTT_NEST_2: 'As individual topics', MQTT_NEST_2: 'Som individuelle topics',
MQTT_RESPONSE: 'Publish command output to a `response` topic', MQTT_RESPONSE: 'Publiser kommandoer til en `response` topic',
MQTT_PUBLISH_TEXT_1: 'Publish single value topics on change', MQTT_PUBLISH_TEXT_1: 'Publiser singel verdi topics ved endringer',
MQTT_PUBLISH_TEXT_2: 'Publish to command topics (ioBroker)', MQTT_PUBLISH_TEXT_2: 'Publiser til kommando topics (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Enable MQTT Discovery (Home Assistant, Domoticz)', MQTT_PUBLISH_TEXT_3: 'Aktiver MQTT Discovery (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'Prefix for the Discovery topics', MQTT_PUBLISH_TEXT_4: 'Prefiks for Discovery topics',
MQTT_PUBLISH_INTERVALS: 'Publish Intervals', MQTT_PUBLISH_INTERVALS: 'Publiseringsintervall',
MQTT_INT_BOILER: 'Boilers and Heat Pumps', MQTT_INT_BOILER: 'Fyr/Varmepumpe',
MQTT_INT_THERMOSTATS: 'Thermostats', MQTT_INT_THERMOSTATS: 'Termostat',
MQTT_INT_SOLAR: 'Solar Modules', MQTT_INT_SOLAR: 'Solpaneler',
MQTT_INT_MIXER: 'Mixer Modules', MQTT_INT_MIXER: 'Blandeventil',
DEFAULT: 'Default', DEFAULT: 'Standard',
MQTT_CLEAN_SESSION: 'Set Clean Session', MQTT_CLEAN_SESSION: 'Benytt Clean Session',
MQTT_RETAIN_FLAG: 'Always set Retain flag', MQTT_RETAIN_FLAG: 'Alltid sett Retain flag',
INACTIVE: 'Inactive', INACTIVE: 'Innaktiv',
ACTIVE: 'Active', ACTIVE: 'Aktiv',
UNKNOWN: 'Unknown', UNKNOWN: 'Ukjent',
SET_TIME: 'Set Time', SET_TIME: 'Sett Tid',
SET_TIME_TEXT: 'Enter local date and time below to set the time', SET_TIME_TEXT: 'Skriv inn dato og klokke nedenfor',
LOCAL_TIME: 'Local Time', LOCAL_TIME: 'Lokaltid',
UTC_TIME: 'UTC Time', UTC_TIME: 'UTC Tid',
ENABLE_NTP: 'Enable NTP', ENABLE_NTP: 'Aktiver NTP',
TIME_ZONE: 'Time Zone', TIME_ZONE: 'Tidssone',
ACCESS_POINT: 'Access Point', ACCESS_POINT: 'Aksesspunkt',
AP_PROVIDE: 'Enable Access Point', AP_PROVIDE: 'Aktiver Aksesspunkt',
AP_PROVIDE_TEXT_1: 'always', AP_PROVIDE_TEXT_1: 'alltid',
AP_PROVIDE_TEXT_2: 'when WiFi is disconnected', AP_PROVIDE_TEXT_2: 'når WiFi er utilgjengelig',
AP_PROVIDE_TEXT_3: 'never', AP_PROVIDE_TEXT_3: 'aldri',
AP_PREFERRED_CHANNEL: 'Preferred Channel', AP_PREFERRED_CHANNEL: 'Foretrukket kanal',
AP_HIDE_SSID: 'Hide SSID', AP_HIDE_SSID: 'Skjul SSID',
NETWORK_SCAN: 'Scan WiFi Networks', NETWORK_SCAN: 'Søk etter trådløst nettverk',
IDLE: 'Idle', IDLE: 'Klar',
LOST: 'Lost', LOST: 'Mistet',
SCANNING: 'Scanning', SCANNING: 'Søker',
SCAN_AGAIN: 'Scan again', SCAN_AGAIN: 'Søk igjen',
NETWORK_SCANNER: 'Network Scanner', NETWORK_SCANNER: 'Nettverk Scanner',
NETWORK_NO_WIFI: 'No WiFi networks found', NETWORK_NO_WIFI: 'Ingen trådløse nett funnet',
NETWORK_BLANK_SSID: 'leave blank to disable WiFi', NETWORK_BLANK_SSID: 'la feltet være blankt for å deaktivisere trådløst nettverk',
POWER: 'Power', POWER: 'Effekt',
NETWORK_DISABLE_SLEEP: 'Disable WiFi Sleep Mode', NETWORK_DISABLE_SLEEP: 'Hindre at trådløst nettverk går i Sleep Mode',
NETWORK_LOW_BAND: 'Use Lower WiFi Bandwidth', NETWORK_LOW_BAND: 'Benytt smalere båndbredde på trådløst nettverk',
NETWORK_USE_DNS: 'Enable mDNS Service', NETWORK_USE_DNS: 'Aktiviser mDNS Service',
NETWORK_ENABLE_IPV6: 'Enable IPv6 support', NETWORK_ENABLE_IPV6: 'Aktiviser IPv6 støtte',
NETWORK_FIXED_IP: 'Use Fixed IP address', NETWORK_FIXED_IP: 'Benytt statisk IP adresse',
ADMIN: 'Admin', ADMIN: 'Admin',
GUEST: 'Guest', GUEST: 'Gjest',
NEW: 'New', NEW: 'Ny',
RENAME: 'Rename', RENAME: 'Bytt navn',
ENTITY: 'Entity' ENTITY: 'Entitet'
}; };
export default no; export default no;

View File

@@ -96,8 +96,7 @@ const pl: BaseTranslation = {
APPLICATION_SETTINGS: 'Ustawienia aplikacji', APPLICATION_SETTINGS: 'Ustawienia aplikacji',
CUSTOMIZATION: 'Personalizacja', CUSTOMIZATION: 'Personalizacja',
APPLICATION_RESTARTING: 'Trwa ponowne uruchamianie', APPLICATION_RESTARTING: 'Trwa ponowne uruchamianie',
BOARD_PROFILE_TEXT: BOARD_PROFILE_TEXT: 'Wybierz z listy wstępną konfigurację płytki interfejsu lub stwórz własną konfigurację.',
'Wybierz z listy wstępną konfigurację płytki interfejsu lub stwórz własną konfigurację.',
BOARD_PROFILE: 'Profil płytki', BOARD_PROFILE: 'Profil płytki',
BUTTON: 'Przycisk', BUTTON: 'Przycisk',
TEMPERATURE: 'Temperatura', TEMPERATURE: 'Temperatura',

View File

@@ -364,7 +364,7 @@ const DashboardData: FC = () => {
const fetchCoreData = useCallback(async () => { const fetchCoreData = useCallback(async () => {
try { try {
setCoreData((await EMSESP.readCoreData()).data); setCoreData((await EMSESP.readCoreData()).data);
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} }
}, [enqueueSnackbar, LL]); }, [enqueueSnackbar, LL]);
@@ -385,7 +385,7 @@ const DashboardData: FC = () => {
const unique_id = parseInt(id); const unique_id = parseInt(id);
try { try {
setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data); setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data);
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} }
}; };
@@ -393,7 +393,7 @@ const DashboardData: FC = () => {
const fetchSensorData = async () => { const fetchSensorData = async () => {
try { try {
setSensorData((await EMSESP.readSensorData()).data); setSensorData((await EMSESP.readSensorData()).data);
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} }
}; };
@@ -474,7 +474,7 @@ const DashboardData: FC = () => {
enqueueSnackbar(LL.WRITE_COMMAND({ cmd: 'send' }), { variant: 'success' }); enqueueSnackbar(LL.WRITE_COMMAND({ cmd: 'send' }), { variant: 'success' });
} }
setDeviceValue(undefined); setDeviceValue(undefined);
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
refreshData(); refreshData();
@@ -508,7 +508,7 @@ const DashboardData: FC = () => {
<ValidatedTextField <ValidatedTextField
name="v" name="v"
label={deviceValue.id.slice(2)} label={deviceValue.id.slice(2)}
value={deviceValue.u ? numberValue(deviceValue.v) : deviceValue.v} value={typeof deviceValue.v === 'number' ? Math.round(deviceValue.v * 10) / 10 : deviceValue.v}
autoFocus autoFocus
multiline={deviceValue.u ? false : true} multiline={deviceValue.u ? false : true}
sx={{ width: '30ch' }} sx={{ width: '30ch' }}
@@ -566,7 +566,7 @@ const DashboardData: FC = () => {
enqueueSnackbar(LL.TEMP_SENSOR() + ' ' + LL.UPDATED(), { variant: 'success' }); enqueueSnackbar(LL.TEMP_SENSOR() + ' ' + LL.UPDATED(), { variant: 'success' });
} }
setSensor(undefined); setSensor(undefined);
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setSensor(undefined); setSensor(undefined);
@@ -992,7 +992,7 @@ const DashboardData: FC = () => {
} else { } else {
enqueueSnackbar(LL.ANALOG_SENSOR() + ' ' + LL.REMOVED(), { variant: 'success' }); enqueueSnackbar(LL.ANALOG_SENSOR() + ' ' + LL.REMOVED(), { variant: 'success' });
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setAnalog(undefined); setAnalog(undefined);
@@ -1020,7 +1020,7 @@ const DashboardData: FC = () => {
} else { } else {
enqueueSnackbar(LL.ANALOG_SENSOR() + ' ' + LL.UPDATED(), { variant: 'success' }); enqueueSnackbar(LL.ANALOG_SENSOR() + ' ' + LL.UPDATED(), { variant: 'success' });
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setAnalog(undefined); setAnalog(undefined);

View File

@@ -149,7 +149,7 @@ const DashboardStatus: FC = () => {
try { try {
await EMSESP.scanDevices(); await EMSESP.scanDevices();
enqueueSnackbar(LL.SCANNING() + '...', { variant: 'info' }); enqueueSnackbar(LL.SCANNING() + '...', { variant: 'info' });
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setConfirmScan(false); setConfirmScan(false);

View File

@@ -50,7 +50,7 @@ const HelpInformation: FC = () => {
} else { } else {
saveFile(response.data, endpoint); saveFile(response.data, endpoint);
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} }
}; };

View File

@@ -68,7 +68,7 @@ const SettingsApplication: FC = () => {
eth_clock_mode: response.data.eth_clock_mode eth_clock_mode: response.data.eth_clock_mode
}); });
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setProcessingBoard(false); setProcessingBoard(false);
@@ -107,7 +107,7 @@ const SettingsApplication: FC = () => {
try { try {
await EMSESP.restart(); await EMSESP.restart();
enqueueSnackbar(LL.APPLICATION_RESTARTING(), { variant: 'info' }); enqueueSnackbar(LL.APPLICATION_RESTARTING(), { variant: 'info' });
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} }
}; };

View File

@@ -131,7 +131,7 @@ const SettingsCustomization: FC = () => {
const fetchDevices = useCallback(async () => { const fetchDevices = useCallback(async () => {
try { try {
setDevices((await EMSESP.readDevices()).data); setDevices((await EMSESP.readDevices()).data);
} catch (error: unknown) { } catch (error) {
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
} }
}, [LL]); }, [LL]);
@@ -144,7 +144,7 @@ const SettingsCustomization: FC = () => {
try { try {
const new_deviceEntities = (await EMSESP.readDeviceEntities({ id: unique_id })).data; const new_deviceEntities = (await EMSESP.readDeviceEntities({ id: unique_id })).data;
setInitialMask(new_deviceEntities); setInitialMask(new_deviceEntities);
} catch (error: unknown) { } catch (error) {
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
} }
}; };
@@ -165,17 +165,21 @@ const SettingsCustomization: FC = () => {
} }
function formatName(de: DeviceEntity) { function formatName(de: DeviceEntity) {
if (de.n === undefined || de.n === de.id) { if (de.n === undefined) {
return de.id; return (
} <>
(
if (de.n[0] === '!') { <Link target="_blank" href={APIURL + devices?.devices[selectedDevice].t + '/' + de.id}>
return LL.COMMAND() + ': ' + de.n.slice(1); {de.id}
</Link>
)
</>
);
} }
return ( return (
<> <>
{de.cn !== undefined && de.cn !== '' ? de.cn : de.n} {de.n[0] === '!' ? LL.COMMAND() + ': ' + de.n.slice(1) : de.cn !== undefined && de.cn !== '' ? de.cn : de.n}
&nbsp;( &nbsp;(
<Link target="_blank" href={APIURL + devices?.devices[selectedDevice].t + '/' + de.id}> <Link target="_blank" href={APIURL + devices?.devices[selectedDevice].t + '/' + de.id}>
{de.id} {de.id}
@@ -239,7 +243,7 @@ const SettingsCustomization: FC = () => {
try { try {
await EMSESP.resetCustomizations(); await EMSESP.resetCustomizations();
enqueueSnackbar(LL.CUSTOMIZATIONS_RESTART(), { variant: 'info' }); enqueueSnackbar(LL.CUSTOMIZATIONS_RESTART(), { variant: 'info' });
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setConfirmReset(false); setConfirmReset(false);
@@ -250,13 +254,15 @@ const SettingsCustomization: FC = () => {
if (devices && deviceEntities && selectedDevice !== -1) { if (devices && deviceEntities && selectedDevice !== -1) {
const masked_entities = deviceEntities const masked_entities = deviceEntities
.filter((de) => de.m !== de.o_m || de.cn !== de.o_cn || de.ma !== de.o_ma || de.mi !== de.o_mi) .filter((de) => de.m !== de.o_m || de.cn !== de.o_cn || de.ma !== de.o_ma || de.mi !== de.o_mi)
.map((new_de) => .map(
new_de.m.toString(16).padStart(2, '0') + (new_de) =>
new_de.id + new_de.m.toString(16).padStart(2, '0') +
((new_de.cn || new_de.mi || new_de.ma) ? '|' : '') + new_de.id +
(new_de.cn ? new_de.cn : '') + (new_de.cn || new_de.mi || new_de.ma ? '|' : '') +
(new_de.mi ? '>' + new_de.mi : '') + (new_de.cn ? new_de.cn : '') +
(new_de.ma ? '<' + new_de.ma : '')); (new_de.mi ? '>' + new_de.mi : '') +
(new_de.ma ? '<' + new_de.ma : '')
);
// check size in bytes to match buffer in CPP, which is 4096 // check size in bytes to match buffer in CPP, which is 4096
const bytes = new TextEncoder().encode(JSON.stringify(masked_entities)).length; const bytes = new TextEncoder().encode(JSON.stringify(masked_entities)).length;
@@ -275,7 +281,7 @@ const SettingsCustomization: FC = () => {
} else { } else {
enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' }); enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
} }
} catch (error: unknown) { } catch (error) {
enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} }
setInitialMask(deviceEntities); setInitialMask(deviceEntities);
@@ -322,7 +328,7 @@ const SettingsCustomization: FC = () => {
}; };
const editEntity = (de: DeviceEntity) => { const editEntity = (de: DeviceEntity) => {
if (de.n && de.n[0] === '!') { if (de.n === undefined || (de.n && de.n[0] === '!')) {
return; return;
} }

View File

@@ -15,6 +15,8 @@ export interface MqttStatus {
client_id: string; client_id: string;
disconnect_reason: MqttDisconnectReason; disconnect_reason: MqttDisconnectReason;
mqtt_fails: number; mqtt_fails: number;
mqtt_queued: number;
connect_count: number;
} }
export interface MqttSettings { export interface MqttSettings {

View File

@@ -1,7 +1,5 @@
import { AxiosError } from 'axios'; export const extractErrorMessage = (error: any, defaultMessage: string) => {
if (error.request) {
export const extractErrorMessage = (error: unknown, defaultMessage: string) => {
if (error instanceof AxiosError) {
return defaultMessage + ' (' + error.request.statusText + ')'; return defaultMessage + ' (' + error.request.statusText + ')';
} else if (error instanceof Error) { } else if (error instanceof Error) {
return defaultMessage + ' (' + error.message + ')'; return defaultMessage + ' (' + error.message + ')';

View File

@@ -26,7 +26,7 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
setErrorMessage(undefined); setErrorMessage(undefined);
try { try {
setData((await read()).data); setData((await read()).data);
} catch (error: unknown) { } catch (error) {
const message = extractErrorMessage(error, LL.PROBLEM_LOADING()); const message = extractErrorMessage(error, LL.PROBLEM_LOADING());
enqueueSnackbar(message, { variant: 'error' }); enqueueSnackbar(message, { variant: 'error' });
setErrorMessage(message); setErrorMessage(message);
@@ -49,7 +49,7 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
} else { } else {
enqueueSnackbar(LL.SETTINGS() + ' ' + LL.SAVED(), { variant: 'success' }); enqueueSnackbar(LL.SETTINGS() + ' ' + LL.SAVED(), { variant: 'success' });
} }
} catch (error: unknown) { } catch (error) {
const message = extractErrorMessage(error, LL.PROBLEM_UPDATING()); const message = extractErrorMessage(error, LL.PROBLEM_UPDATING());
enqueueSnackbar(message, { variant: 'error' }); enqueueSnackbar(message, { variant: 'error' });
setErrorMessage(message); setErrorMessage(message);

View File

@@ -14,6 +14,7 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"useUnknownInCatchVariables": false,
"jsx": "react-jsx" "jsx": "react-jsx"
}, },
"include": ["src"] "include": ["src"]

View File

@@ -49,9 +49,9 @@ void APSettingsService::startAP() {
WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask); WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
esp_wifi_set_bandwidth((wifi_interface_t)ESP_IF_WIFI_AP, WIFI_BW_HT20); esp_wifi_set_bandwidth((wifi_interface_t)ESP_IF_WIFI_AP, WIFI_BW_HT20);
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients); WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients);
#ifdef ARDUINO_LOLIN_C3_MINI #ifdef ARDUINO_LOLIN_C3_MINI
WiFi.setTxPower(WIFI_POWER_8_5dBm); //https://www.wemos.cc/en/latest/c3/c3_mini.html#about-wifi WiFi.setTxPower(WIFI_POWER_8_5dBm); //https://www.wemos.cc/en/latest/c3/c3_mini.html#about-wifi
#endif #endif
if (!_dnsServer) { if (!_dnsServer) {
IPAddress apIp = WiFi.softAPIP(); IPAddress apIp = WiFi.softAPIP();
emsesp::EMSESP::logger().info(F("Starting Access Point with captive portal on %s"), apIp.toString().c_str()); emsesp::EMSESP::logger().info(F("Starting Access Point with captive portal on %s"), apIp.toString().c_str());

View File

@@ -20,7 +20,9 @@ void MqttStatus::mqttStatus(AsyncWebServerRequest * request) {
root["client_id"] = _mqttSettingsService->getClientId(); root["client_id"] = _mqttSettingsService->getClientId();
root["disconnect_reason"] = (uint8_t)_mqttSettingsService->getDisconnectReason(); root["disconnect_reason"] = (uint8_t)_mqttSettingsService->getDisconnectReason();
root["mqtt_fails"] = emsesp::Mqtt::publish_fails(); // proddy added root["mqtt_queued"] = emsesp::Mqtt::publish_queued(); // mdvp added
root["mqtt_fails"] = emsesp::Mqtt::publish_fails(); // proddy added
root["connect_count"] = emsesp::Mqtt::connect_count(); // mdvp added
response->setLength(); response->setLength();
request->send(response); request->send(response);

View File

@@ -56,7 +56,7 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
public: public:
NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin(); void begin();
static void ntp_received(struct timeval * tv); static void ntp_received(struct timeval * tv);
private: private:
@@ -68,7 +68,6 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
void WiFiEvent(WiFiEvent_t event); void WiFiEvent(WiFiEvent_t event);
void configureNTP(); void configureNTP();
void configureTime(AsyncWebServerRequest * request, JsonVariant & json); void configureTime(AsyncWebServerRequest * request, JsonVariant & json);
}; };
#endif #endif

View File

@@ -75,9 +75,9 @@ void NetworkSettingsService::manageSTA() {
}); });
WiFi.begin(_state.ssid.c_str(), _state.password.c_str()); // attempt to connect to the network WiFi.begin(_state.ssid.c_str(), _state.password.c_str()); // attempt to connect to the network
#ifdef ARDUINO_LOLIN_C3_MINI #ifdef ARDUINO_LOLIN_C3_MINI
WiFi.setTxPower(WIFI_POWER_8_5dBm); //https://www.wemos.cc/en/latest/c3/c3_mini.html#about-wifi WiFi.setTxPower(WIFI_POWER_8_5dBm); //https://www.wemos.cc/en/latest/c3/c3_mini.html#about-wifi
#endif #endif
} }
} }

View File

@@ -85,7 +85,8 @@ std::string printable_to_string(const Printable & printable);
* *
* @since 1.0.0 * @since 1.0.0
*/ */
using flash_string_vector = std::vector<const __FlashStringHelper *>;
using string_vector = std::vector<const char *>;
/** /**
* Loop function that must be called regularly to detect a 32-bit * Loop function that must be called regularly to detect a 32-bit

View File

@@ -42,30 +42,36 @@ namespace uuid {
namespace console { namespace console {
void Commands::add_command(const flash_string_vector & name, command_function function) { void Commands::add_command(const string_vector & name, command_function function) {
add_command(0, 0, name, flash_string_vector{}, function, nullptr); add_command(0, 0, name, string_vector{}, function, nullptr);
} }
void Commands::add_command(const flash_string_vector & name, const flash_string_vector & arguments, command_function function) { void Commands::add_command(const string_vector & name, const string_vector & arguments, command_function function) {
add_command(0, 0, name, arguments, function, nullptr); add_command(0, 0, name, arguments, function, nullptr);
} }
void Commands::add_command(const flash_string_vector & name, const flash_string_vector & arguments, command_function function, argument_completion_function arg_function) { void Commands::add_command(const string_vector & name, const string_vector & arguments, command_function function, argument_completion_function arg_function) {
add_command(0, 0, name, arguments, function, arg_function); add_command(0, 0, name, arguments, function, arg_function);
} }
void Commands::add_command(unsigned int context, unsigned int flags, const flash_string_vector & name, command_function function) { void Commands::add_command(unsigned int context, unsigned int flags, const string_vector & name, command_function function) {
add_command(context, flags, name, flash_string_vector{}, function, nullptr); add_command(context, flags, name, string_vector{}, function, nullptr);
} }
void Commands::add_command(unsigned int context, unsigned int flags, const flash_string_vector & name, const flash_string_vector & arguments, command_function function) { void Commands::add_command(unsigned int context, unsigned int flags, const string_vector & name, const string_vector & arguments, command_function function) {
add_command(context, flags, name, arguments, function, nullptr); add_command(context, flags, name, arguments, function, nullptr);
} }
void Commands::add_command(unsigned int context, unsigned int flags, const flash_string_vector & name, const flash_string_vector & arguments, command_function function, argument_completion_function arg_function) { void Commands::add_command(unsigned int context,
unsigned int flags,
const string_vector & name,
const string_vector & arguments,
command_function function,
argument_completion_function arg_function) {
commands_.emplace(std::piecewise_construct, std::forward_as_tuple(context), std::forward_as_tuple(flags, name, arguments, function, arg_function)); commands_.emplace(std::piecewise_construct, std::forward_as_tuple(context), std::forward_as_tuple(flags, name, arguments, function, arg_function));
} }
// added by proddy // added by proddy
// note we should really iterate and free up the lambda code and any flashstrings // note we should really iterate and free up the lambda code and any flashstrings
void Commands::remove_all_commands() { void Commands::remove_all_commands() {
@@ -139,7 +145,7 @@ bool Commands::find_longest_common_prefix(const std::multimap<size_t, const Comm
for (size_t length = 0; all_match && length < shortest_match; length++) { for (size_t length = 0; all_match && length < shortest_match; length++) {
for (auto command_it = std::next(commands.begin()); command_it != commands.end(); command_it++) { for (auto command_it = std::next(commands.begin()); command_it != commands.end(); command_it++) {
if (read_flash_string(*std::next(first.begin(), length)) != read_flash_string(*std::next(command_it->second->name_.begin(), length))) { if ((*std::next(first.begin(), length)) != (*std::next(command_it->second->name_.begin(), length))) {
all_match = false; all_match = false;
break; break;
} }
@@ -152,7 +158,7 @@ bool Commands::find_longest_common_prefix(const std::multimap<size_t, const Comm
auto name_it = first.begin(); auto name_it = first.begin();
for (size_t i = 0; i < component_prefix; i++) { for (size_t i = 0; i < component_prefix; i++) {
longest_name.push_back(std::move(read_flash_string(*name_it))); longest_name.push_back(std::move((*name_it)));
name_it++; name_it++;
} }
} }
@@ -167,7 +173,8 @@ bool Commands::find_longest_common_prefix(const std::multimap<size_t, const Comm
for (auto command_it = std::next(commands.begin()); command_it != commands.end(); command_it++) { for (auto command_it = std::next(commands.begin()); command_it != commands.end(); command_it++) {
// This relies on the null terminator character limiting the // This relies on the null terminator character limiting the
// length before it becomes longer than any of the strings // length before it becomes longer than any of the strings
if (pgm_read_byte(reinterpret_cast<PGM_P>(first) + length) != pgm_read_byte(reinterpret_cast<PGM_P>(*std::next(command_it->second->name_.begin(), component_prefix)) + length)) { if (pgm_read_byte(reinterpret_cast<PGM_P>(first) + length)
!= pgm_read_byte(reinterpret_cast<PGM_P>(*std::next(command_it->second->name_.begin(), component_prefix)) + length)) {
all_match = false; all_match = false;
break; break;
} }
@@ -179,7 +186,8 @@ bool Commands::find_longest_common_prefix(const std::multimap<size_t, const Comm
} }
if (chars_prefix > 0) { if (chars_prefix > 0) {
longest_name.push_back(std::move(read_flash_string(first).substr(0, chars_prefix))); // TODO fix, no more PGM
longest_name.push_back(std::string(first).substr(0, chars_prefix));
return false; return false;
} }
} }
@@ -243,7 +251,7 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine
} }
if (!temp_command_name.empty() && command_line.total_size() <= temp_command_name.size()) { if (!temp_command_name.empty() && command_line.total_size() <= temp_command_name.size()) {
temp_command = std::make_unique<Command>(0, flash_string_vector{}, flash_string_vector{}, nullptr, nullptr); temp_command = std::make_unique<Command>(0, string_vector{}, string_vector{}, nullptr, nullptr);
count = 1; count = 1;
match = commands.partial.end(); match = commands.partial.end();
result.replacement.trailing_space = whole_components; result.replacement.trailing_space = whole_components;
@@ -259,10 +267,11 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine
auto & matching_command = match->second; auto & matching_command = match->second;
for (auto & name : matching_command->name_) { for (auto & name : matching_command->name_) {
result.replacement->push_back(std::move(read_flash_string(name))); result.replacement->push_back(std::move((name)));
} }
if (command_line.total_size() > result.replacement->size() && command_line.total_size() <= matching_command->name_.size() + matching_command->maximum_arguments()) { if (command_line.total_size() > result.replacement->size()
&& command_line.total_size() <= matching_command->name_.size() + matching_command->maximum_arguments()) {
// Try to auto-complete arguments // Try to auto-complete arguments
std::vector<std::string> arguments{std::next(command_line->cbegin(), result.replacement->size()), command_line->cend()}; std::vector<std::string> arguments{std::next(command_line->cbegin(), result.replacement->size()), command_line->cend()};
@@ -332,7 +341,7 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine
remaining_help.escape_initial_parameters(); remaining_help.escape_initial_parameters();
for (auto it = std::next(matching_command->arguments_.cbegin(), current_args_count); it != matching_command->arguments_.cend(); it++) { for (auto it = std::next(matching_command->arguments_.cbegin(), current_args_count); it != matching_command->arguments_.cend(); it++) {
remaining_help->push_back(std::move(read_flash_string(*it))); remaining_help->push_back(std::move((*it)));
} }
} }
@@ -380,7 +389,7 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine
} }
for (; flash_name_it != command_it->second->name_.cend(); flash_name_it++) { for (; flash_name_it != command_it->second->name_.cend(); flash_name_it++) {
std::string name = read_flash_string(*flash_name_it); std::string name = (*flash_name_it);
// Skip parts of the command name that match the command line // Skip parts of the command name that match the command line
if (line_it != command_line->cend()) { if (line_it != command_line->cend()) {
@@ -403,7 +412,7 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine
continue; continue;
} }
help->push_back(std::move(read_flash_string(argument))); help->push_back(std::move((argument)));
} }
result.help.push_back(std::move(help)); result.help.push_back(std::move(help));
@@ -416,7 +425,7 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine
if (commands.exact.count(longest->first) == 1) { if (commands.exact.count(longest->first) == 1) {
for (auto & name : longest->second->name_) { for (auto & name : longest->second->name_) {
result.replacement->push_back(std::move(read_flash_string(name))); result.replacement->push_back(name);
} }
// Add a space because there are sub-commands for a command that has matched exactly // Add a space because there are sub-commands for a command that has matched exactly
@@ -449,7 +458,7 @@ Commands::Match Commands::find_command(Shell & shell, const CommandLine & comman
auto line_it = command_line->cbegin(); auto line_it = command_line->cbegin();
for (; name_it != command.name_.cend() && line_it != command_line->cend(); name_it++, line_it++) { for (; name_it != command.name_.cend() && line_it != command_line->cend(); name_it++, line_it++) {
std::string name = read_flash_string(*name_it); std::string name = (*name_it);
size_t found = name.rfind(*line_it, 0); size_t found = name.rfind(*line_it, 0);
if (found == std::string::npos) { if (found == std::string::npos) {
@@ -499,12 +508,12 @@ void Commands::for_each_available_command(Shell & shell, apply_function f) const
name.reserve(command_it->second.name_.size()); name.reserve(command_it->second.name_.size());
for (auto flash_name : command_it->second.name_) { for (auto flash_name : command_it->second.name_) {
name.push_back(std::move(read_flash_string(flash_name))); name.push_back(std::move((flash_name)));
} }
arguments.reserve(command_it->second.arguments_.size()); arguments.reserve(command_it->second.arguments_.size());
for (auto flash_argument : command_it->second.arguments_) { for (auto flash_argument : command_it->second.arguments_) {
arguments.push_back(std::move(read_flash_string(flash_argument))); arguments.push_back(std::move((flash_argument)));
} }
f(name, arguments); f(name, arguments);
@@ -512,7 +521,11 @@ void Commands::for_each_available_command(Shell & shell, apply_function f) const
} }
} }
Commands::Command::Command(unsigned int flags, const flash_string_vector name, const flash_string_vector arguments, command_function function, argument_completion_function arg_function) Commands::Command::Command(unsigned int flags,
const string_vector name,
const string_vector arguments,
command_function function,
argument_completion_function arg_function)
: flags_(flags) : flags_(flags)
, name_(name) , name_(name)
, arguments_(arguments) , arguments_(arguments)
@@ -524,7 +537,7 @@ Commands::Command::~Command() {
} }
size_t Commands::Command::minimum_arguments() const { size_t Commands::Command::minimum_arguments() const {
return std::count_if(arguments_.cbegin(), arguments_.cend(), [](const __FlashStringHelper * argument) { return pgm_read_byte(argument) == '<'; }); return std::count_if(arguments_.cbegin(), arguments_.cend(), [](const char * argument) { return pgm_read_byte(argument) == '<'; });
} }
} // namespace console } // namespace console

View File

@@ -135,8 +135,8 @@ void Shell::loop_one() {
} }
} }
void Shell::set_command_str(const __FlashStringHelper * str) { void Shell::set_command_str(const char * str) {
line_buffer_ = read_flash_string(str); line_buffer_ = (str);
erase_current_line(); erase_current_line();
prompt_displayed_ = false; prompt_displayed_ = false;
display_prompt(); display_prompt();
@@ -262,30 +262,30 @@ void Shell::loop_normal() {
} else if (esc_ == 1) { // pos1 } else if (esc_ == 1) { // pos1
cursor_ = line_buffer_.length(); cursor_ = line_buffer_.length();
} else if (esc_ == 11) { // F1 } else if (esc_ == 11) { // F1
set_command_str(F("help")); set_command_str("help");
} else if (esc_ == 12) { // F2 } else if (esc_ == 12) { // F2
set_command_str(F("show")); set_command_str("show");
} else if (esc_ == 13) { // F3 } else if (esc_ == 13) { // F3
set_command_str(F("log notice")); set_command_str("log notice");
} else if (esc_ == 14) { // F4 } else if (esc_ == 14) { // F4
set_command_str(F("log info")); set_command_str("log info");
} else if (esc_ == 15) { // F5 } else if (esc_ == 15) { // F5
set_command_str(F("log debug")); set_command_str("log debug");
} else if (esc_ == 17) { // F6 } else if (esc_ == 17) { // F6
set_command_str(F("watch off")); set_command_str("watch off");
} else if (esc_ == 18) { // F7 } else if (esc_ == 18) { // F7
set_command_str(F("watch on")); set_command_str("watch on");
} else if (esc_ == 19) { // F8 } else if (esc_ == 19) { // F8
set_command_str(F("watch raw")); set_command_str("watch raw");
} else if (esc_ == 20) { // F9 } else if (esc_ == 20) { // F9
set_command_str(F("call system info")); set_command_str("call system info");
} else if (esc_ == 21) { // F10 } else if (esc_ == 21) { // F10
set_command_str(F("call system settings")); set_command_str("call system settings");
} else if (esc_ == 23) { // F11 } else if (esc_ == 23) { // F11
line_buffer_ = read_flash_string(F("call send \"0B \"")); line_buffer_ = ("call send \"0B \"");
cursor_ = 1; cursor_ = 1;
} else if (esc_ == 24) { // F12 } else if (esc_ == 24) { // F12
set_command_str(F("log debug; watch raw")); set_command_str("log debug; watch raw");
} }
esc_ = 0; esc_ = 0;
} else if (c >= '0' && (c <= '9')) { // numbers } else if (c >= '0' && (c <= '9')) { // numbers
@@ -320,7 +320,7 @@ void Shell::loop_normal() {
idle_time_ = uuid::get_uptime_ms(); idle_time_ = uuid::get_uptime_ms();
} }
Shell::PasswordData::PasswordData(const __FlashStringHelper * password_prompt, password_function && password_function) Shell::PasswordData::PasswordData(const char * password_prompt, password_function && password_function)
: password_prompt_(password_prompt) : password_prompt_(password_prompt)
, password_function_(std::move(password_function)) { , password_function_(std::move(password_function)) {
} }
@@ -452,7 +452,7 @@ void Shell::loop_blocking() {
} }
} }
void Shell::enter_password(const __FlashStringHelper * prompt, password_function function) { void Shell::enter_password(const char * prompt, password_function function) {
if (mode_ == Mode::NORMAL) { if (mode_ == Mode::NORMAL) {
mode_ = Mode::PASSWORD; mode_ = Mode::PASSWORD;
mode_data_ = std::make_unique<Shell::PasswordData>(prompt, std::move(function)); mode_data_ = std::make_unique<Shell::PasswordData>(prompt, std::move(function));

View File

@@ -30,8 +30,8 @@ namespace uuid {
namespace console { namespace console {
static const char __pstr__logger_name[] __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "shell"; static const char __pstr__logger_name[] = "shell";
const uuid::log::Logger Shell::logger_{reinterpret_cast<const __FlashStringHelper *>(__pstr__logger_name), uuid::log::Facility::LPR}; const uuid::log::Logger Shell::logger_{(__pstr__logger_name), uuid::log::Facility::LPR};
Shell::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content) Shell::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content)
: id_(id) : id_(id)

View File

@@ -335,7 +335,7 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
* execution. * execution.
* @since 0.1.0 * @since 0.1.0
*/ */
void enter_password(const __FlashStringHelper * prompt, password_function function); void enter_password(const char * prompt, password_function function);
/** /**
* Stop executing anything on this shell for a period of time. * Stop executing anything on this shell for a period of time.
@@ -682,11 +682,11 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
* execution. * execution.
* @since 0.1.0 * @since 0.1.0
*/ */
PasswordData(const __FlashStringHelper * password_prompt, password_function && password_function); PasswordData(const char * password_prompt, password_function && password_function);
~PasswordData() override = default; ~PasswordData() override = default;
const __FlashStringHelper * password_prompt_; /*!< Prompt requesting password input. @since 0.1.0 */ const char * password_prompt_; /*!< Prompt requesting password input. @since 0.1.0 */
password_function password_function_; /*!< Function to execute after password entry. @since 0.1.0 */ password_function password_function_; /*!< Function to execute after password entry. @since 0.1.0 */
}; };
/** /**
@@ -761,7 +761,7 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
const std::shared_ptr<const uuid::log::Message> content_; /*!< Log message content. @since 0.1.0 */ const std::shared_ptr<const uuid::log::Message> content_; /*!< Log message content. @since 0.1.0 */
}; };
Shell(const Shell &) = delete; Shell(const Shell &) = delete;
Shell & operator=(const Shell &) = delete; Shell & operator=(const Shell &) = delete;
/** /**
@@ -893,7 +893,7 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
* @since 0.1.0 * @since 0.1.0
*/ */
size_t vprintf(const __FlashStringHelper * format, va_list ap); size_t vprintf(const __FlashStringHelper * format, va_list ap);
void set_command_str(const __FlashStringHelper * str); void set_command_str(const char * str);
static const uuid::log::Logger logger_; /*!< uuid::log::Logger instance for shells. @since 0.1.0 */ static const uuid::log::Logger logger_; /*!< uuid::log::Logger instance for shells. @since 0.1.0 */
static std::set<std::shared_ptr<Shell>> shells_; /*!< Registered running shells to be executed. @since 0.1.0 */ static std::set<std::shared_ptr<Shell>> shells_; /*!< Registered running shells to be executed. @since 0.1.0 */
@@ -906,7 +906,7 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum command line length in bytes. @since 0.6.0 */ size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum command line length in bytes. @since 0.6.0 */
std::string line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */ std::string line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */
std::string line_old_[MAX_LINES]; /*!< old Command line buffer.*/ std::string line_old_[MAX_LINES]; /*!< old Command line buffer.*/
uint8_t line_no_ = 0; uint8_t line_no_ = 0;
size_t maximum_command_line_length_ = MAX_COMMAND_LINE_LENGTH; /*!< Maximum command line length in bytes. @since 0.6.0 */ size_t maximum_command_line_length_ = MAX_COMMAND_LINE_LENGTH; /*!< Maximum command line length in bytes. @since 0.6.0 */
unsigned char previous_ = 0; /*!< Previous character that was entered on the command line. Used to detect CRLF line endings. @since 0.1.0 */ unsigned char previous_ = 0; /*!< Previous character that was entered on the command line. Used to detect CRLF line endings. @since 0.1.0 */
uint8_t cursor_ = 0; /*!< cursor position from end of line */ uint8_t cursor_ = 0; /*!< cursor position from end of line */
@@ -955,9 +955,9 @@ class CommandLine {
~CommandLine() = default; ~CommandLine() = default;
#ifdef UNIT_TEST #ifdef UNIT_TEST
CommandLine(CommandLine &&) = default; CommandLine(CommandLine &&) = default;
CommandLine & operator=(CommandLine &&) = default; CommandLine & operator=(CommandLine &&) = default;
CommandLine(const CommandLine &) __attribute__((deprecated)) = default; CommandLine(const CommandLine &) __attribute__((deprecated)) = default;
CommandLine & operator=(const CommandLine &) __attribute__((deprecated)) = default; CommandLine & operator=(const CommandLine &) __attribute__((deprecated)) = default;
#endif #endif
@@ -1188,7 +1188,7 @@ class Commands {
* executed. * executed.
* @since 0.2.0 * @since 0.2.0
*/ */
void add_command(const flash_string_vector & name, command_function function); void add_command(const string_vector & name, command_function function);
/** /**
* Add a command with arguments to the list of commands in this * Add a command with arguments to the list of commands in this
* container. * container.
@@ -1205,7 +1205,7 @@ class Commands {
* executed. * executed.
* @since 0.2.0 * @since 0.2.0
*/ */
void add_command(const flash_string_vector & name, const flash_string_vector & arguments, command_function function); void add_command(const string_vector & name, const string_vector & arguments, command_function function);
/** /**
* Add a command with arguments and automatic argument completion * Add a command with arguments and automatic argument completion
* to the list of commands in this container. * to the list of commands in this container.
@@ -1224,7 +1224,7 @@ class Commands {
* completions for this command. * completions for this command.
* @since 0.2.0 * @since 0.2.0
*/ */
void add_command(const flash_string_vector & name, const flash_string_vector & arguments, command_function function, argument_completion_function arg_function); void add_command(const string_vector & name, const string_vector & arguments, command_function function, argument_completion_function arg_function);
/** /**
* Add a command with no arguments to the list of commands in this * Add a command with no arguments to the list of commands in this
* container. * container.
@@ -1239,7 +1239,7 @@ class Commands {
* executed. * executed.
* @since 0.1.0 * @since 0.1.0
*/ */
void add_command(unsigned int context, unsigned int flags, const flash_string_vector & name, command_function function); void add_command(unsigned int context, unsigned int flags, const string_vector & name, command_function function);
/** /**
* Add a command with arguments to the list of commands in this * Add a command with arguments to the list of commands in this
* container. * container.
@@ -1257,7 +1257,7 @@ class Commands {
* executed. * executed.
* @since 0.1.0 * @since 0.1.0
*/ */
void add_command(unsigned int context, unsigned int flags, const flash_string_vector & name, const flash_string_vector & arguments, command_function function); void add_command(unsigned int context, unsigned int flags, const string_vector & name, const string_vector & arguments, command_function function);
/** /**
* Add a command with arguments and automatic argument completion * Add a command with arguments and automatic argument completion
* to the list of commands in this container. * to the list of commands in this container.
@@ -1279,8 +1279,8 @@ class Commands {
*/ */
void add_command(unsigned int context, void add_command(unsigned int context,
unsigned int flags, unsigned int flags,
const flash_string_vector & name, const string_vector & name,
const flash_string_vector & arguments, const string_vector & arguments,
command_function function, command_function function,
argument_completion_function arg_function); argument_completion_function arg_function);
@@ -1344,11 +1344,7 @@ class Commands {
* completions for this command. * completions for this command.
* @since 0.1.0 * @since 0.1.0
*/ */
Command(unsigned int flags, Command(unsigned int flags, const string_vector name, const string_vector arguments, command_function function, argument_completion_function arg_function);
const flash_string_vector name,
const flash_string_vector arguments,
command_function function,
argument_completion_function arg_function);
~Command(); ~Command();
/** /**
@@ -1372,13 +1368,13 @@ class Commands {
} }
unsigned int flags_; /*!< Shell flags that must be set for this command to be available. @since 0.1.0 */ unsigned int flags_; /*!< Shell flags that must be set for this command to be available. @since 0.1.0 */
const flash_string_vector name_; /*!< Name of the command as a std::vector of flash strings. @since 0.1.0 */ const string_vector name_; /*!< Name of the command as a std::vector of flash strings. @since 0.1.0 */
const flash_string_vector arguments_; /*!< Help text for arguments that the command accepts as a std::vector of flash strings. @since 0.1.0 */ const string_vector arguments_; /*!< Help text for arguments that the command accepts as a std::vector of flash strings. @since 0.1.0 */
command_function function_; /*!< Function to be used when the command is executed. @since 0.1.0 */ command_function function_; /*!< Function to be used when the command is executed. @since 0.1.0 */
argument_completion_function arg_function_; /*!< Function to be used to perform argument completions for this command. @since 0.1.0 */ argument_completion_function arg_function_; /*!< Function to be used to perform argument completions for this command. @since 0.1.0 */
private: private:
Command(const Command &) = delete; Command(const Command &) = delete;
Command & operator=(const Command &) = delete; Command & operator=(const Command &) = delete;
}; };
@@ -1487,7 +1483,7 @@ class StreamConsole : virtual public Shell {
explicit StreamConsole(Stream & stream); explicit StreamConsole(Stream & stream);
private: private:
StreamConsole(const StreamConsole &) = delete; StreamConsole(const StreamConsole &) = delete;
StreamConsole & operator=(const StreamConsole &) = delete; StreamConsole & operator=(const StreamConsole &) = delete;
/** /**

View File

@@ -26,17 +26,17 @@ namespace uuid {
namespace log { namespace log {
static constexpr const char * pstr_level_lowercase_off __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "off"; static constexpr const char * pstr_level_lowercase_off = "off";
static constexpr const char * pstr_level_lowercase_emerg __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "emerg"; static constexpr const char * pstr_level_lowercase_emerg = "emerg";
static constexpr const char * pstr_level_lowercase_crit __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "crit"; static constexpr const char * pstr_level_lowercase_crit = "crit";
static constexpr const char * pstr_level_lowercase_alert __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "alert"; static constexpr const char * pstr_level_lowercase_alert = "alert";
static constexpr const char * pstr_level_lowercase_err __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "err"; static constexpr const char * pstr_level_lowercase_err = "err";
static constexpr const char * pstr_level_lowercase_warning __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "warning"; static constexpr const char * pstr_level_lowercase_warning = "warning";
static constexpr const char * pstr_level_lowercase_notice __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "notice"; static constexpr const char * pstr_level_lowercase_notice = "notice";
static constexpr const char * pstr_level_lowercase_info __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "info"; static constexpr const char * pstr_level_lowercase_info = "info";
static constexpr const char * pstr_level_lowercase_debug __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "debug"; static constexpr const char * pstr_level_lowercase_debug = "debug";
static constexpr const char * pstr_level_lowercase_trace __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "trace"; static constexpr const char * pstr_level_lowercase_trace = "trace";
static constexpr const char * pstr_level_lowercase_all __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "all"; static constexpr const char * pstr_level_lowercase_all = "all";
static const __FlashStringHelper * log_level_lowercase[(int)Level::ALL - (int)Level::OFF + 1] __attribute__((__aligned__(sizeof(uint32_t)))) static const __FlashStringHelper * log_level_lowercase[(int)Level::ALL - (int)Level::OFF + 1] __attribute__((__aligned__(sizeof(uint32_t))))
PROGMEM = {reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_off), PROGMEM = {reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_off),

View File

@@ -26,17 +26,17 @@ namespace uuid {
namespace log { namespace log {
static constexpr const char * pstr_level_uppercase_off __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "OFF"; static constexpr const char * pstr_level_uppercase_off = "OFF";
static constexpr const char * pstr_level_uppercase_emerg __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "EMERG"; static constexpr const char * pstr_level_uppercase_emerg = "EMERG";
static constexpr const char * pstr_level_uppercase_crit __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "CRIT"; static constexpr const char * pstr_level_uppercase_crit = "CRIT";
static constexpr const char * pstr_level_uppercase_alert __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "ALERT"; static constexpr const char * pstr_level_uppercase_alert = "ALERT";
static constexpr const char * pstr_level_uppercase_err __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "ERR"; static constexpr const char * pstr_level_uppercase_err = "ERR";
static constexpr const char * pstr_level_uppercase_warning __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "WARNING"; static constexpr const char * pstr_level_uppercase_warning = "WARNING";
static constexpr const char * pstr_level_uppercase_notice __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "NOTICE"; static constexpr const char * pstr_level_uppercase_notice = "NOTICE";
static constexpr const char * pstr_level_uppercase_info __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "INFO"; static constexpr const char * pstr_level_uppercase_info = "INFO";
static constexpr const char * pstr_level_uppercase_debug __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "DEBUG"; static constexpr const char * pstr_level_uppercase_debug = "DEBUG";
static constexpr const char * pstr_level_uppercase_trace __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "TRACE"; static constexpr const char * pstr_level_uppercase_trace = "TRACE";
static constexpr const char * pstr_level_uppercase_all __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "ALL"; static constexpr const char * pstr_level_uppercase_all = "ALL";
static const __FlashStringHelper * log_level_uppercase[(int)Level::ALL - (int)Level::OFF + 1] __attribute__((__aligned__(sizeof(uint32_t)))) static const __FlashStringHelper * log_level_uppercase[(int)Level::ALL - (int)Level::OFF + 1] __attribute__((__aligned__(sizeof(uint32_t))))
PROGMEM = {reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_off), PROGMEM = {reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_off),

View File

@@ -47,7 +47,7 @@ std::string format_timestamp_ms(uint64_t timestamp_ms, unsigned int days_width)
static std::vector<char> text(10 + 1 /* days */ + 2 + 1 /* hours */ + 2 + 1 /* minutes */ + 2 + 1 /* seconds */ + 3 /* milliseconds */ + 1); static std::vector<char> text(10 + 1 /* days */ + 2 + 1 /* hours */ + 2 + 1 /* minutes */ + 2 + 1 /* seconds */ + 3 /* milliseconds */ + 1);
snprintf_P(text.data(), text.size(), PSTR("%0*lu+%02u:%02u:%02u.%03u"), std::min(days_width, 10U), days, hours, minutes, seconds, milliseconds); snprintf(text.data(), text.size(), ("%0*lu+%02u:%02u:%02u.%03u"), std::min(days_width, 10U), days, hours, minutes, seconds, milliseconds);
return text.data(); return text.data();
} }

View File

@@ -36,7 +36,7 @@ namespace log {
std::map<Handler *, Level> Logger::handlers_; std::map<Handler *, Level> Logger::handlers_;
Level Logger::level_ = Level::OFF; Level Logger::level_ = Level::OFF;
Message::Message(uint64_t uptime_ms, Level level, Facility facility, const __FlashStringHelper * name, const std::string && text) Message::Message(uint64_t uptime_ms, Level level, Facility facility, const char * name, const std::string && text)
: uptime_ms(uptime_ms) : uptime_ms(uptime_ms)
, level(level) , level(level)
, facility(facility) , facility(facility)
@@ -44,7 +44,7 @@ Message::Message(uint64_t uptime_ms, Level level, Facility facility, const __Fla
, text(std::move(text)) { , text(std::move(text)) {
} }
Logger::Logger(const __FlashStringHelper * name, Facility facility) Logger::Logger(const char * name, Facility facility)
: name_(name) : name_(name)
, facility_(facility){ , facility_(facility){

View File

@@ -231,7 +231,7 @@ struct Message {
* @param[in] text Log message text. * @param[in] text Log message text.
* @since 1.0.0 * @since 1.0.0
*/ */
Message(uint64_t uptime_ms, Level level, Facility facility, const __FlashStringHelper * name, const std::string && text); Message(uint64_t uptime_ms, Level level, Facility facility, const char * name, const std::string && text);
~Message() = default; ~Message() = default;
/** /**
@@ -261,7 +261,7 @@ struct Message {
* *
* @since 1.0.0 * @since 1.0.0
*/ */
const __FlashStringHelper * name; const char * name;
/** /**
* Formatted log message text. * Formatted log message text.
@@ -331,7 +331,7 @@ class Logger {
* *
* @since 1.0.0 * @since 1.0.0
*/ */
Logger(const __FlashStringHelper * name, Facility facility = Facility::LOCAL0); Logger(const char * name, Facility facility = Facility::LOCAL0);
~Logger() = default; ~Logger() = default;
/** /**
@@ -626,8 +626,8 @@ class Logger {
static std::map<Handler *, Level> handlers_; /*!< Registered log handlers. @since 1.0.0 */ static std::map<Handler *, Level> handlers_; /*!< Registered log handlers. @since 1.0.0 */
static Level level_; /*!< Minimum global log level across all handlers. @since 1.0.0 */ static Level level_; /*!< Minimum global log level across all handlers. @since 1.0.0 */
const __FlashStringHelper * name_; /*!< Logger name (flash string). @since 1.0.0 */ const char * name_; /*!< Logger name (flash string). @since 1.0.0 */
const Facility facility_; /*!< Default logging facility for messages. @since 1.0.0 */ const Facility facility_; /*!< Default logging facility for messages. @since 1.0.0 */
}; };
} // namespace log } // namespace log

View File

@@ -82,13 +82,13 @@
#include <uuid/common.h> #include <uuid/common.h>
#include <uuid/log.h> #include <uuid/log.h>
static const char __pstr__logger_name[] __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "syslog"; static const char __pstr__logger_name[] = "syslog";
namespace uuid { namespace uuid {
namespace syslog { namespace syslog {
uuid::log::Logger SyslogService::logger_{FPSTR(__pstr__logger_name), uuid::log::Facility::SYSLOG}; uuid::log::Logger SyslogService::logger_{__pstr__logger_name, uuid::log::Facility::SYSLOG};
bool SyslogService::QueuedLogMessage::time_good_ = false; bool SyslogService::QueuedLogMessage::time_good_ = false;
SyslogService::~SyslogService() { SyslogService::~SyslogService() {
@@ -116,7 +116,7 @@ void SyslogService::remove_queued_messages(uuid::log::Level level) {
} }
} }
log_message_id_ -= offset; log_message_id_ -= offset;
log_message_fails_ += offset; log_message_fails_ += offset;
} }
@@ -269,8 +269,8 @@ void SyslogService::loop() {
operator<<(std::make_shared<uuid::log::Message>(uuid::get_uptime_ms(), operator<<(std::make_shared<uuid::log::Message>(uuid::get_uptime_ms(),
uuid::log::Level::INFO, uuid::log::Level::INFO,
uuid::log::Facility::SYSLOG, uuid::log::Facility::SYSLOG,
reinterpret_cast<const __FlashStringHelper *>(__pstr__logger_name), (__pstr__logger_name),
uuid::read_flash_string(F("-- MARK --")))); (F("-- MARK --"))));
} }
} }
} }
@@ -443,7 +443,7 @@ bool SyslogService::transmit(const QueuedLogMessage & message) {
udp_.print('-'); udp_.print('-');
} }
udp_.printf_P(PSTR(" %s %s - - - "), hostname_.c_str(), uuid::read_flash_string(message.content_->name).c_str()); udp_.printf_P(PSTR(" %s %s - - - "), hostname_.c_str(), (message.content_->name));
char id_c_str[15]; char id_c_str[15];
snprintf_P(id_c_str, sizeof(id_c_str), PSTR(" %lu: "), message.id_); snprintf_P(id_c_str, sizeof(id_c_str), PSTR(" %lu: "), message.id_);

View File

@@ -28,329 +28,322 @@ namespace uuid {
namespace telnet { namespace telnet {
TelnetStream::TelnetStream(WiFiClient &client) TelnetStream::TelnetStream(WiFiClient & client)
: client_(client) { : client_(client) {
output_buffer_.reserve(BUFFER_SIZE); output_buffer_.reserve(BUFFER_SIZE);
} }
void TelnetStream::start() { void TelnetStream::start() {
raw_write({ raw_write({IAC, WILL, OPT_ECHO, IAC, WILL, OPT_BINARY, IAC, WILL, OPT_SGA, IAC, DONT, OPT_ECHO, IAC, DO, OPT_BINARY, IAC, DO, OPT_SGA});
IAC, WILL, OPT_ECHO,
IAC, WILL, OPT_BINARY,
IAC, WILL, OPT_SGA,
IAC, DONT, OPT_ECHO,
IAC, DO, OPT_BINARY,
IAC, DO, OPT_SGA
});
} }
int TelnetStream::available() { int TelnetStream::available() {
if (peek() == -1) { if (peek() == -1) {
return 0; return 0;
} else { } else {
return 1; return 1;
} }
} }
int TelnetStream::read() { int TelnetStream::read() {
if (peek_ != -1) { if (peek_ != -1) {
int data = peek_; int data = peek_;
peek_ = -1; peek_ = -1;
return data; return data;
} }
buffer_flush(); buffer_flush();
restart: restart:
int data = raw_read(); int data = raw_read();
if (data == -1) { if (data == -1) {
return -1; return -1;
} }
unsigned char c = data; unsigned char c = data;
if (sub_negotiation_) { if (sub_negotiation_) {
if (previous_raw_in_ == IAC) { if (previous_raw_in_ == IAC) {
switch (c) { switch (c) {
case SE: case SE:
sub_negotiation_ = false; sub_negotiation_ = false;
previous_raw_in_ = 0; previous_raw_in_ = 0;
goto restart; goto restart;
case IAC: case IAC:
previous_raw_in_ = 0; previous_raw_in_ = 0;
goto restart; goto restart;
} }
} else { } else {
switch (c) { switch (c) {
case IAC: case IAC:
previous_raw_in_ = c; previous_raw_in_ = c;
goto restart; goto restart;
default: default:
previous_raw_in_ = 0; previous_raw_in_ = 0;
goto restart; goto restart;
} }
} }
} else { } else {
if (previous_raw_in_ == IAC) { if (previous_raw_in_ == IAC) {
switch (c) { switch (c) {
case IP: case IP:
// Interrupt (^C) // Interrupt (^C)
previous_raw_in_ = 0; previous_raw_in_ = 0;
c = '\x03'; c = '\x03';
break; break;
case EC: case EC:
// Backspace (^H) // Backspace (^H)
previous_raw_in_ = 0; previous_raw_in_ = 0;
c = '\x08'; c = '\x08';
break; break;
case EL: case EL:
// Delete line (^U) // Delete line (^U)
previous_raw_in_ = 0; previous_raw_in_ = 0;
c = '\x15'; c = '\x15';
break; break;
case IAC: case IAC:
previous_raw_in_ = 0; previous_raw_in_ = 0;
break; break;
case SB: case SB:
case WILL: case WILL:
case WONT: case WONT:
case DO: case DO:
case DONT: case DONT:
previous_raw_in_ = c; previous_raw_in_ = c;
goto restart; goto restart;
case SE: case SE:
case DM: case DM:
case BRK: case BRK:
case AO: case AO:
case AYT: case AYT:
case GA: case GA:
case NOP: case NOP:
default: default:
previous_raw_in_ = 0; previous_raw_in_ = 0;
goto restart; goto restart;
} }
} else if (previous_raw_in_ == SB) { } else if (previous_raw_in_ == SB) {
sub_negotiation_ = true; sub_negotiation_ = true;
previous_raw_in_ = 0; previous_raw_in_ = 0;
goto restart; goto restart;
} else if (previous_raw_in_ == WILL || previous_raw_in_ == WONT) { } else if (previous_raw_in_ == WILL || previous_raw_in_ == WONT) {
switch (c) { switch (c) {
case OPT_ECHO: case OPT_ECHO:
// Don't do these // Don't do these
raw_write({IAC, DONT, c}); raw_write({IAC, DONT, c});
break; break;
case OPT_BINARY: case OPT_BINARY:
case OPT_SGA: case OPT_SGA:
// Do these // Do these
raw_write({IAC, DO, c}); raw_write({IAC, DO, c});
break; break;
default: default:
// Don't do anything else // Don't do anything else
raw_write({IAC, DONT, c}); raw_write({IAC, DONT, c});
break; break;
} }
previous_raw_in_ = 0; previous_raw_in_ = 0;
goto restart; goto restart;
} else if (previous_raw_in_ == DO) { } else if (previous_raw_in_ == DO) {
switch (c) { switch (c) {
case OPT_ECHO: case OPT_ECHO:
case OPT_BINARY: case OPT_BINARY:
case OPT_SGA: case OPT_SGA:
// These are always enabled // These are always enabled
break; break;
default: default:
// Refuse to do anything else // Refuse to do anything else
raw_write({IAC, WONT, c}); raw_write({IAC, WONT, c});
break; break;
} }
previous_raw_in_ = 0; previous_raw_in_ = 0;
goto restart; goto restart;
} else if (previous_raw_in_ == DONT) { } else if (previous_raw_in_ == DONT) {
switch (c) { switch (c) {
case OPT_ECHO: case OPT_ECHO:
case OPT_BINARY: case OPT_BINARY:
case OPT_SGA: case OPT_SGA:
// Insist that we do these // Insist that we do these
raw_write({IAC, WILL, c}); raw_write({IAC, WILL, c});
break; break;
default: default:
// Everything else is always disabled // Everything else is always disabled
break; break;
} }
previous_raw_in_ = 0; previous_raw_in_ = 0;
goto restart; goto restart;
} else { } else {
switch (c) { switch (c) {
case IAC: case IAC:
previous_raw_in_ = c; previous_raw_in_ = c;
goto restart; goto restart;
default: default:
previous_raw_in_ = 0; previous_raw_in_ = 0;
break; break;
} }
} }
} }
if (previous_in_ == CR) { if (previous_in_ == CR) {
if (c == NUL) { if (c == NUL) {
previous_in_ = 0; previous_in_ = 0;
goto restart; goto restart;
} }
} }
previous_in_ = c; previous_in_ = c;
return c; return c;
} }
int TelnetStream::peek() { int TelnetStream::peek() {
buffer_flush(); buffer_flush();
// It's too complicated to implement this by calling peek() // It's too complicated to implement this by calling peek()
// on the original stream, especially if the original stream // on the original stream, especially if the original stream
// doesn't actually support peeking. // doesn't actually support peeking.
if (peek_ == -1) { if (peek_ == -1) {
peek_ = read(); peek_ = read();
} }
return peek_; return peek_;
} }
size_t TelnetStream::write(uint8_t data) { size_t TelnetStream::write(uint8_t data) {
if (previous_out_ == CR && data != LF) { if (previous_out_ == CR && data != LF) {
previous_out_ = data; previous_out_ = data;
if (raw_write({NUL, data}) != 2) { if (raw_write({NUL, data}) != 2) {
return 0; return 0;
} }
} else { } else {
previous_out_ = data; previous_out_ = data;
} }
if (data == IAC) { if (data == IAC) {
if (raw_write({IAC, IAC}) != 2) { if (raw_write({IAC, IAC}) != 2) {
return 0; return 0;
} }
} else { } else {
if (raw_write(data) != 1) { if (raw_write(data) != 1) {
return 0; return 0;
} }
} }
return 1; return 1;
} }
size_t TelnetStream::write(const uint8_t *buffer, size_t size) { size_t TelnetStream::write(const uint8_t * buffer, size_t size) {
std::vector<unsigned char> data; std::vector<unsigned char> data;
data.reserve(size); data.reserve(size);
while (size-- > 0) { while (size-- > 0) {
unsigned char c = *buffer++; unsigned char c = *buffer++;
if (previous_out_ == CR && c != LF) { if (previous_out_ == CR && c != LF) {
data.push_back((unsigned char)NUL); data.push_back((unsigned char)NUL);
} }
if (c == IAC) { if (c == IAC) {
data.push_back((unsigned char)IAC); data.push_back((unsigned char)IAC);
} }
previous_out_ = c; previous_out_ = c;
data.push_back(c); data.push_back(c);
} }
size_t len = raw_write(data); size_t len = raw_write(data);
if (len < size) { if (len < size) {
len = 0; len = 0;
} }
return len; return len;
} }
void TelnetStream::flush() { void TelnetStream::flush() {
// This is a pure virtual function in Arduino's Stream class, which // This is a pure virtual function in Arduino's Stream class, which
// makes no sense because that class is for input and this is an // makes no sense because that class is for input and this is an
// output function. Later versions move it to Print as an empty // output function. Later versions move it to Print as an empty
// virtual function so this is here for backward compatibility. // virtual function so this is here for backward compatibility.
} }
int TelnetStream::raw_available() { int TelnetStream::raw_available() {
return client_.available(); return client_.available();
} }
int TelnetStream::raw_read() { int TelnetStream::raw_read() {
return client_.read(); return client_.read();
} }
void TelnetStream::buffer_flush() { void TelnetStream::buffer_flush() {
if (!output_buffer_.empty()) { if (!output_buffer_.empty()) {
size_t len = client_.write(reinterpret_cast<const unsigned char*>(output_buffer_.data()), output_buffer_.size()); size_t len = client_.write(reinterpret_cast<const unsigned char *>(output_buffer_.data()), output_buffer_.size());
if (len != output_buffer_.size()) { if (len != output_buffer_.size()) {
client_.stop(); client_.stop();
} }
output_buffer_.clear(); output_buffer_.clear();
output_buffer_.shrink_to_fit(); output_buffer_.shrink_to_fit();
} }
} }
size_t TelnetStream::raw_write(unsigned char data) { size_t TelnetStream::raw_write(unsigned char data) {
output_buffer_.push_back(data); output_buffer_.push_back(data);
if (output_buffer_.size() >= BUFFER_SIZE) { if (output_buffer_.size() >= BUFFER_SIZE) {
buffer_flush(); buffer_flush();
} }
return 1; return 1;
} }
size_t TelnetStream::raw_write(const std::vector<unsigned char> &data) { size_t TelnetStream::raw_write(const std::vector<unsigned char> & data) {
return raw_write(reinterpret_cast<const unsigned char*>(data.data()), data.size()); return raw_write(reinterpret_cast<const unsigned char *>(data.data()), data.size());
} }
size_t TelnetStream::raw_write(const uint8_t *buffer, size_t size) { size_t TelnetStream::raw_write(const uint8_t * buffer, size_t size) {
size_t offset = 0; size_t offset = 0;
size_t remaining = size; size_t remaining = size;
if (!output_buffer_.empty()) { if (!output_buffer_.empty()) {
// Fill the rest of the buffer // Fill the rest of the buffer
size_t block = std::min(remaining, BUFFER_SIZE - output_buffer_.size()); size_t block = std::min(remaining, BUFFER_SIZE - output_buffer_.size());
output_buffer_.insert(output_buffer_.end(), buffer, buffer + block); output_buffer_.insert(output_buffer_.end(), buffer, buffer + block);
offset += block; offset += block;
remaining -= block; remaining -= block;
if (output_buffer_.size() >= BUFFER_SIZE) { if (output_buffer_.size() >= BUFFER_SIZE) {
buffer_flush(); buffer_flush();
} }
} }
if (remaining >= BUFFER_SIZE) { if (remaining >= BUFFER_SIZE) {
// Output directly if it won't fit in the buffer // Output directly if it won't fit in the buffer
size_t len = client_.write(buffer + offset, remaining); size_t len = client_.write(buffer + offset, remaining);
if (len != remaining) { if (len != remaining) {
client_.stop(); client_.stop();
return offset + len; return offset + len;
} }
} else if (remaining > 0) { } else if (remaining > 0) {
// Put the rest in the buffer // Put the rest in the buffer
output_buffer_.insert(output_buffer_.end(), buffer + offset, buffer + offset + remaining); output_buffer_.insert(output_buffer_.end(), buffer + offset, buffer + offset + remaining);
} }
return size; return size;
} }
} // namespace telnet } // namespace telnet

View File

@@ -54,22 +54,20 @@
#endif #endif
#endif #endif
static const char __pstr__logger_name[] __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = "telnet";
namespace uuid { namespace uuid {
namespace telnet { namespace telnet {
uuid::log::Logger TelnetService::logger_{FPSTR(__pstr__logger_name), uuid::log::Facility::DAEMON}; uuid::log::Logger TelnetService::logger_{"telnet", uuid::log::Facility::DAEMON};
TelnetService::TelnetService(std::shared_ptr<uuid::console::Commands> commands, unsigned int context, unsigned int flags) TelnetService::TelnetService(std::shared_ptr<uuid::console::Commands> commands, unsigned int context, unsigned int flags)
: TelnetService(DEFAULT_PORT, commands, context, flags) { : TelnetService(DEFAULT_PORT, commands, context, flags) {
} }
TelnetService::TelnetService(uint16_t port, std::shared_ptr<uuid::console::Commands> commands, unsigned int context, unsigned int flags) TelnetService::TelnetService(uint16_t port, std::shared_ptr<uuid::console::Commands> commands, unsigned int context, unsigned int flags)
: TelnetService(port, [commands, context, flags](Stream & stream, const IPAddress & addr __attribute__((unused)), uint16_t port __attribute__((unused))) -> std::shared_ptr<uuid::console::Shell> { : TelnetService(port,
return std::make_shared<uuid::console::StreamConsole>(commands, stream, context, flags); [commands, context, flags](Stream & stream, const IPAddress & addr __attribute__((unused)), uint16_t port __attribute__((unused)))
}) { -> std::shared_ptr<uuid::console::Shell> { return std::make_shared<uuid::console::StreamConsole>(commands, stream, context, flags); }) {
} }
TelnetService::TelnetService(shell_factory_function shell_factory) TelnetService::TelnetService(shell_factory_function shell_factory)
@@ -145,7 +143,9 @@ void TelnetService::loop() {
if (client) { if (client) {
if (connections_.size() >= maximum_connections_) { if (connections_.size() >= maximum_connections_) {
#if UUID_TELNET_HAVE_WIFICLIENT_REMOTE #if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
logger_.info(F("New connection from [%s]:%u rejected (connection limit reached)"), uuid::printable_to_string(client.remoteIP()).c_str(), client.remotePort()); logger_.info(F("New connection from [%s]:%u rejected (connection limit reached)"),
uuid::printable_to_string(client.remoteIP()).c_str(),
client.remotePort());
#else #else
logger_.info(F("New connection rejected (connection limit reached)")); logger_.info(F("New connection rejected (connection limit reached)"));
#endif #endif

View File

@@ -21,9 +21,9 @@
#include <Arduino.h> #include <Arduino.h>
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
# include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#else #else
# include <WiFi.h> #include <WiFi.h>
#endif #endif
#include <WiFiUdp.h> #include <WiFiUdp.h>
@@ -51,47 +51,47 @@ namespace telnet {
* *
* @since 0.1.0 * @since 0.1.0
*/ */
class TelnetStream: public ::Stream { class TelnetStream : public ::Stream {
public: public:
/** /**
* Create a new telnet stream wrapper. * Create a new telnet stream wrapper.
* *
* @param[in] client Client connection. * @param[in] client Client connection.
* @since 0.1.0 * @since 0.1.0
*/ */
explicit TelnetStream(WiFiClient &client); explicit TelnetStream(WiFiClient & client);
virtual ~TelnetStream() = default; virtual ~TelnetStream() = default;
/** /**
* Perform initial negotiation. * Perform initial negotiation.
* *
* @since 0.1.0 * @since 0.1.0
*/ */
void start(); void start();
/** /**
* Check for available input. * Check for available input.
* *
* @return The number of bytes available to read. * @return The number of bytes available to read.
* @since 0.1.0 * @since 0.1.0
*/ */
int available() override; int available() override;
/** /**
* Read one byte from the available input. * Read one byte from the available input.
* *
* @return An unsigned char if input is available, otherwise -1. * @return An unsigned char if input is available, otherwise -1.
* @since 0.1.0 * @since 0.1.0
*/ */
int read() override; int read() override;
/** /**
* Read one byte from the available input without advancing to the * Read one byte from the available input without advancing to the
* next one. * next one.
* *
* @return An unsigned char if input is available, otherwise -1. * @return An unsigned char if input is available, otherwise -1.
* @since 0.1.0 * @since 0.1.0
*/ */
int peek() override; int peek() override;
/** /**
* Write one byte to the output stream. * Write one byte to the output stream.
* *
* Disconnect the client if the socket buffer is full. * Disconnect the client if the socket buffer is full.
@@ -100,8 +100,8 @@ public:
* @return The number of bytes that were output. * @return The number of bytes that were output.
* @since 0.1.0 * @since 0.1.0
*/ */
size_t write(uint8_t data) override; size_t write(uint8_t data) override;
/** /**
* Write an array of bytes to the output stream. * Write an array of bytes to the output stream.
* *
* Disconnect the client if the socket buffer is full. * Disconnect the client if the socket buffer is full.
@@ -111,8 +111,8 @@ public:
* @return The number of bytes that were output. * @return The number of bytes that were output.
* @since 0.1.0 * @since 0.1.0
*/ */
size_t write(const uint8_t *buffer, size_t size) override; size_t write(const uint8_t * buffer, size_t size) override;
/** /**
* Does nothing. * Does nothing.
* *
* This is a pure virtual function in Arduino's Stream class, which * This is a pure virtual function in Arduino's Stream class, which
@@ -122,66 +122,69 @@ public:
* *
* @since 0.1.0 * @since 0.1.0
*/ */
void flush() override; void flush() override;
private: private:
static constexpr const unsigned char NUL = 0; /*!< No operation. @since 0.1.0 */ static constexpr const unsigned char NUL = 0; /*!< No operation. @since 0.1.0 */
static constexpr const unsigned char BEL = 7; /*!< Produces an audible or visible signal. @since 0.1.0 */ static constexpr const unsigned char BEL = 7; /*!< Produces an audible or visible signal. @since 0.1.0 */
static constexpr const unsigned char BS = 8; /*!< Moves the print head one character position towards the left margin. @since 0.1.0 */ static constexpr const unsigned char BS = 8; /*!< Moves the print head one character position towards the left margin. @since 0.1.0 */
static constexpr const unsigned char HT = 9; /*!< Moves the printer to the next horizontal tab stop. @since 0.1.0 */ static constexpr const unsigned char HT = 9; /*!< Moves the printer to the next horizontal tab stop. @since 0.1.0 */
static constexpr const unsigned char LF = 10; /*!< Line Feed. @since 0.1.0 */ static constexpr const unsigned char LF = 10; /*!< Line Feed. @since 0.1.0 */
static constexpr const unsigned char VT = 11; /*!< Moves the printer to the next vertical tab stop. @since 0.1.0 */ static constexpr const unsigned char VT = 11; /*!< Moves the printer to the next vertical tab stop. @since 0.1.0 */
static constexpr const unsigned char FF = 12; /*!< Moves the printer to the top of the next page, keeping the same horizontal position. @since 0.1.0 */ static constexpr const unsigned char FF = 12; /*!< Moves the printer to the top of the next page, keeping the same horizontal position. @since 0.1.0 */
static constexpr const unsigned char CR = 13; /*!< Carriage Return. @since 0.1.0 */ static constexpr const unsigned char CR = 13; /*!< Carriage Return. @since 0.1.0 */
static constexpr const unsigned char SE = 240; /*!< End of sub-negotiation parameters. @since 0.1.0 */ static constexpr const unsigned char SE = 240; /*!< End of sub-negotiation parameters. @since 0.1.0 */
static constexpr const unsigned char NOP = 241; /*!< No operation. @since 0.1.0 */ static constexpr const unsigned char NOP = 241; /*!< No operation. @since 0.1.0 */
static constexpr const unsigned char DM = 242; /*!< The data stream portion of a Synch. @since 0.1.0 */ static constexpr const unsigned char DM = 242; /*!< The data stream portion of a Synch. @since 0.1.0 */
static constexpr const unsigned char BRK = 243; /*!< NVT character BRK. @since 0.1.0 */ static constexpr const unsigned char BRK = 243; /*!< NVT character BRK. @since 0.1.0 */
static constexpr const unsigned char IP = 244; /*!< Interrupt Process function. @since 0.1.0 */ static constexpr const unsigned char IP = 244; /*!< Interrupt Process function. @since 0.1.0 */
static constexpr const unsigned char AO = 245; /*!< Abort Output function. @since 0.1.0 */ static constexpr const unsigned char AO = 245; /*!< Abort Output function. @since 0.1.0 */
static constexpr const unsigned char AYT = 246; /*!< Are You There function. @since 0.1.0 */ static constexpr const unsigned char AYT = 246; /*!< Are You There function. @since 0.1.0 */
static constexpr const unsigned char EC = 247; /*!< Erase Character function. @since 0.1.0 */ static constexpr const unsigned char EC = 247; /*!< Erase Character function. @since 0.1.0 */
static constexpr const unsigned char EL = 248; /*!< Erase Line function. @since 0.1.0 */ static constexpr const unsigned char EL = 248; /*!< Erase Line function. @since 0.1.0 */
static constexpr const unsigned char GA = 249; /*!< Go Ahead signal. @since 0.1.0 */ static constexpr const unsigned char GA = 249; /*!< Go Ahead signal. @since 0.1.0 */
static constexpr const unsigned char SB = 250; /*!< Sub-negotiation of the indicated option. @since 0.1.0 */ static constexpr const unsigned char SB = 250; /*!< Sub-negotiation of the indicated option. @since 0.1.0 */
static constexpr const unsigned char WILL = 251; /*!< Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option. @since 0.1.0 */ static constexpr const unsigned char WILL =
static constexpr const unsigned char WONT = 252; /*!< Indicates the refusal to perform, or continue performing, the indicated option. @since 0.1.0 */ 251; /*!< Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option. @since 0.1.0 */
static constexpr const unsigned char DO = 253; /*!< Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option. @since 0.1.0 */ static constexpr const unsigned char WONT = 252; /*!< Indicates the refusal to perform, or continue performing, the indicated option. @since 0.1.0 */
static constexpr const unsigned char DONT = 254; /*!< Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option. @since 0.1.0 */ static constexpr const unsigned char DO =
static constexpr const unsigned char IAC = 255; /*!< Interpret As Command escape character. @since 0.1.0 */ 253; /*!< Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option. @since 0.1.0 */
static constexpr const unsigned char DONT =
254; /*!< Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option. @since 0.1.0 */
static constexpr const unsigned char IAC = 255; /*!< Interpret As Command escape character. @since 0.1.0 */
static constexpr const unsigned char OPT_BINARY = 0; /*!< Binary (8-bit) transmission mode. (RFC 856). @since 0.1.0 */ static constexpr const unsigned char OPT_BINARY = 0; /*!< Binary (8-bit) transmission mode. (RFC 856). @since 0.1.0 */
static constexpr const unsigned char OPT_ECHO = 1; /*!< Remote Echo (RFC 857). @since 0.1.0 */ static constexpr const unsigned char OPT_ECHO = 1; /*!< Remote Echo (RFC 857). @since 0.1.0 */
static constexpr const unsigned char OPT_SGA = 3; /*!< Suppress Go Ahead (RFC 858). @since 0.1.0 */ static constexpr const unsigned char OPT_SGA = 3; /*!< Suppress Go Ahead (RFC 858). @since 0.1.0 */
static constexpr const size_t BUFFER_SIZE = 536; /*!< Output buffer size. @since 0.1.0 */ static constexpr const size_t BUFFER_SIZE = 536; /*!< Output buffer size. @since 0.1.0 */
TelnetStream(const TelnetStream&) = delete; TelnetStream(const TelnetStream &) = delete;
TelnetStream& operator=(const TelnetStream&) = delete; TelnetStream & operator=(const TelnetStream &) = delete;
/** /**
* Directly check for available input. * Directly check for available input.
* *
* @return The number of bytes available to read. * @return The number of bytes available to read.
* @since 0.1.0 * @since 0.1.0
*/ */
int raw_available(); int raw_available();
/** /**
* Read one byte directly from the available input. * Read one byte directly from the available input.
* *
* @return An unsigned char if input is available, otherwise -1. * @return An unsigned char if input is available, otherwise -1.
* @since 0.1.0 * @since 0.1.0
*/ */
int raw_read(); int raw_read();
/** /**
* Flush output stream buffer. * Flush output stream buffer.
* *
* Disconnect the client if the socket buffer is full. * Disconnect the client if the socket buffer is full.
* *
* @since 0.1.0 * @since 0.1.0
*/ */
void buffer_flush(); void buffer_flush();
/** /**
* Write one byte directly to the output stream. * Write one byte directly to the output stream.
* *
* Disconnect the client if the socket buffer is full. * Disconnect the client if the socket buffer is full.
@@ -190,8 +193,8 @@ private:
* @return The number of bytes that were output. * @return The number of bytes that were output.
* @since 0.1.0 * @since 0.1.0
*/ */
size_t raw_write(unsigned char data); size_t raw_write(unsigned char data);
/** /**
* Write a vector of bytes directly to the output stream. * Write a vector of bytes directly to the output stream.
* *
* Disconnect the client if the socket buffer is full. * Disconnect the client if the socket buffer is full.
@@ -200,8 +203,8 @@ private:
* @return The number of bytes that were output. * @return The number of bytes that were output.
* @since 0.1.0 * @since 0.1.0
*/ */
size_t raw_write(const std::vector<unsigned char> &data); size_t raw_write(const std::vector<unsigned char> & data);
/** /**
* Write an array of bytes directly to the output stream. * Write an array of bytes directly to the output stream.
* *
* Disconnect the client if the socket buffer is full. * Disconnect the client if the socket buffer is full.
@@ -211,15 +214,15 @@ private:
* @return The number of bytes that were output. * @return The number of bytes that were output.
* @since 0.1.0 * @since 0.1.0
*/ */
size_t raw_write(const uint8_t *buffer, size_t size); size_t raw_write(const uint8_t * buffer, size_t size);
WiFiClient &client_; /*!< Client connection. @since 0.1.0 */ WiFiClient & client_; /*!< Client connection. @since 0.1.0 */
unsigned char previous_raw_in_ = 0; /*!< Previous raw character that was received. Used to detect commands. @since 0.1.0 */ unsigned char previous_raw_in_ = 0; /*!< Previous raw character that was received. Used to detect commands. @since 0.1.0 */
bool sub_negotiation_ = false; /*!< Sub-negotiation mode. @since 0.1.0 */ bool sub_negotiation_ = false; /*!< Sub-negotiation mode. @since 0.1.0 */
unsigned char previous_in_ = 0; /*!< Previous character that was received. Used to detect CR NUL. @since 0.1.0 */ unsigned char previous_in_ = 0; /*!< Previous character that was received. Used to detect CR NUL. @since 0.1.0 */
unsigned char previous_out_ = 0; /*!< Previous character that was sent. Used to insert NUL after CR without LF. @since 0.1.0 */ unsigned char previous_out_ = 0; /*!< Previous character that was sent. Used to insert NUL after CR without LF. @since 0.1.0 */
int peek_ = -1; /*!< Previously read data cached by peek(). @since 0.1.0 */ int peek_ = -1; /*!< Previously read data cached by peek(). @since 0.1.0 */
std::vector<char> output_buffer_; /*!< Buffer data to be output until a read function is called. @since 0.1.0 */ std::vector<char> output_buffer_; /*!< Buffer data to be output until a read function is called. @since 0.1.0 */
}; };
/** /**
@@ -228,13 +231,13 @@ private:
* @since 0.1.0 * @since 0.1.0
*/ */
class TelnetService { class TelnetService {
public: public:
static constexpr size_t MAX_CONNECTIONS = 3; /*!< Maximum number of concurrent open connections. @since 0.1.0 */ static constexpr size_t MAX_CONNECTIONS = 3; /*!< Maximum number of concurrent open connections. @since 0.1.0 */
static constexpr uint16_t DEFAULT_PORT = 23; /*!< Default TCP port to listen on. @since 0.1.0 */ static constexpr uint16_t DEFAULT_PORT = 23; /*!< Default TCP port to listen on. @since 0.1.0 */
static constexpr unsigned long DEFAULT_IDLE_TIMEOUT = 600; /*!< Default initial idle timeout (in seconds). @since 0.1.0 */ static constexpr unsigned long DEFAULT_IDLE_TIMEOUT = 600; /*!< Default initial idle timeout (in seconds). @since 0.1.0 */
static constexpr unsigned long DEFAULT_WRITE_TIMEOUT = 0; /*!< Default write timeout (in milliseconds). @ since 0.1.0 */ static constexpr unsigned long DEFAULT_WRITE_TIMEOUT = 0; /*!< Default write timeout (in milliseconds). @ since 0.1.0 */
/** /**
* Function to handle the creation of a shell. * Function to handle the creation of a shell.
* *
* @param[in] stream Stream for the telnet connection. * @param[in] stream Stream for the telnet connection.
@@ -242,9 +245,9 @@ public:
* @param[in] port Remote port. * @param[in] port Remote port.
* @since 0.1.0 * @since 0.1.0
*/ */
using shell_factory_function = std::function<std::shared_ptr<uuid::console::Shell>(Stream &stream, const IPAddress &addr, uint16_t port)>; using shell_factory_function = std::function<std::shared_ptr<uuid::console::Shell>(Stream & stream, const IPAddress & addr, uint16_t port)>;
/** /**
* Create a new telnet service listening on the default port. * Create a new telnet service listening on the default port.
* *
* @param[in] commands Commands available for execution in shells. * @param[in] commands Commands available for execution in shells.
@@ -252,9 +255,9 @@ public:
* @param[in] flags Initial flags for shells. * @param[in] flags Initial flags for shells.
* @since 0.1.0 * @since 0.1.0
*/ */
TelnetService(std::shared_ptr<uuid::console::Commands> commands, unsigned int context = 0, unsigned int flags = 0); TelnetService(std::shared_ptr<uuid::console::Commands> commands, unsigned int context = 0, unsigned int flags = 0);
/** /**
* Create a new telnet service listening on a specific port. * Create a new telnet service listening on a specific port.
* *
* @param[in] port TCP listening port. * @param[in] port TCP listening port.
@@ -263,74 +266,74 @@ public:
* @param[in] flags Initial flags for shells. * @param[in] flags Initial flags for shells.
* @since 0.1.0 * @since 0.1.0
*/ */
TelnetService(uint16_t port, std::shared_ptr<uuid::console::Commands> commands, unsigned int context = 0, unsigned int flags = 0); TelnetService(uint16_t port, std::shared_ptr<uuid::console::Commands> commands, unsigned int context = 0, unsigned int flags = 0);
/** /**
* Create a new telnet service listening on the default port. * Create a new telnet service listening on the default port.
* *
* @param[in] shell_factory Function to create a shell for new connections. * @param[in] shell_factory Function to create a shell for new connections.
* @since 0.1.0 * @since 0.1.0
*/ */
explicit TelnetService(shell_factory_function shell_factory); explicit TelnetService(shell_factory_function shell_factory);
/** /**
* Create a new telnet service listening on a specific port. * Create a new telnet service listening on a specific port.
* *
* @param[in] port TCP listening port. * @param[in] port TCP listening port.
* @param[in] shell_factory Function to create a shell for new connections. * @param[in] shell_factory Function to create a shell for new connections.
* @since 0.1.0 * @since 0.1.0
*/ */
TelnetService(uint16_t port, shell_factory_function shell_factory); TelnetService(uint16_t port, shell_factory_function shell_factory);
~TelnetService() = default; ~TelnetService() = default;
/** /**
* Start listening for connections on the configured port. * Start listening for connections on the configured port.
* *
* @since 0.1.0 * @since 0.1.0
*/ */
void start(); void start();
/** /**
* Close all connections. * Close all connections.
* *
* The listening status is not affected. * The listening status is not affected.
* *
* @since 0.1.0 * @since 0.1.0
*/ */
void close_all(); void close_all();
/** /**
* Stop listening for connections. * Stop listening for connections.
* *
* Existing connections are not affected. * Existing connections are not affected.
* *
* @since 0.1.0 * @since 0.1.0
*/ */
void stop(); void stop();
/** /**
* Get the maximum number of concurrent open connections. * Get the maximum number of concurrent open connections.
* *
* @return The maximum number of concurrent open connections. * @return The maximum number of concurrent open connections.
* @since 0.1.0 * @since 0.1.0
*/ */
size_t maximum_connections() const; size_t maximum_connections() const;
/** /**
* Set the maximum number of concurrent open connections. * Set the maximum number of concurrent open connections.
* *
* Defaults to TelnetService::MAX_CONNECTIONS. * Defaults to TelnetService::MAX_CONNECTIONS.
* *
* @since 0.1.0 * @since 0.1.0
*/ */
void maximum_connections(size_t count); void maximum_connections(size_t count);
/** /**
* Get the initial idle timeout for new connections. * Get the initial idle timeout for new connections.
* *
* @return The initial idle timeout in seconds (or 0 for disabled). * @return The initial idle timeout in seconds (or 0 for disabled).
* @since 0.1.0 * @since 0.1.0
*/ */
unsigned long initial_idle_timeout() const; unsigned long initial_idle_timeout() const;
/** /**
* Set the initial idle timeout for new connections. * Set the initial idle timeout for new connections.
* *
* Defaults to TelnetService::DEFAULT_IDLE_TIMEOUT. * Defaults to TelnetService::DEFAULT_IDLE_TIMEOUT.
@@ -338,17 +341,17 @@ public:
* @param[in] timeout Idle timeout in seconds (or 0 to disable). * @param[in] timeout Idle timeout in seconds (or 0 to disable).
* @since 0.1.0 * @since 0.1.0
*/ */
void initial_idle_timeout(unsigned long timeout); void initial_idle_timeout(unsigned long timeout);
/** /**
* Get the default socket write timeout for new connections. * Get the default socket write timeout for new connections.
* *
* @return The default socket write timeout in seconds (or 0 for * @return The default socket write timeout in seconds (or 0 for
* platform default). * platform default).
* @since 0.1.0 * @since 0.1.0
*/ */
unsigned long default_write_timeout() const; unsigned long default_write_timeout() const;
/** /**
* Set the default socket write timeout for new connections. * Set the default socket write timeout for new connections.
* *
* Defaults to TelnetService::DEFAULT_WRITE_TIMEOUT (platform * Defaults to TelnetService::DEFAULT_WRITE_TIMEOUT (platform
@@ -358,26 +361,26 @@ public:
* platform default). * platform default).
* @since 0.1.0 * @since 0.1.0
*/ */
void default_write_timeout(unsigned long timeout); void default_write_timeout(unsigned long timeout);
/** /**
* Accept new connections. * Accept new connections.
* *
* @since 0.1.0 * @since 0.1.0
*/ */
void loop(); void loop();
private: private:
/** /**
* Telnet connection. * Telnet connection.
* *
* Holds the client and stream instance for the lifetime of the shell. * Holds the client and stream instance for the lifetime of the shell.
* *
* @since 0.1.0 * @since 0.1.0
*/ */
class Connection { class Connection {
public: public:
/** /**
* Create a telnet connection shell. * Create a telnet connection shell.
* *
* @param[in] shell_factory Function to create a shell for new connections. * @param[in] shell_factory Function to create a shell for new connections.
@@ -386,52 +389,52 @@ private:
* @param[in] write_timeout Idle timeout in milliseconds. * @param[in] write_timeout Idle timeout in milliseconds.
* @since 0.1.0 * @since 0.1.0
*/ */
Connection(shell_factory_function &shell_factory, WiFiClient &&client, unsigned long idle_timeout, unsigned long write_timeout); Connection(shell_factory_function & shell_factory, WiFiClient && client, unsigned long idle_timeout, unsigned long write_timeout);
~Connection() = default; ~Connection() = default;
/** /**
* Check if the shell is still active. * Check if the shell is still active.
* *
* @return Active status of the shell. * @return Active status of the shell.
* @since 0.1.0 * @since 0.1.0
*/ */
bool active(); bool active();
/** /**
* Stop the shell if the client is not connected. * Stop the shell if the client is not connected.
* *
* @return Active status of the shell. * @return Active status of the shell.
* @since 0.1.0 * @since 0.1.0
*/ */
bool loop(); bool loop();
/** /**
* Stop the shell. * Stop the shell.
* *
* @since 0.1.0 * @since 0.1.0
*/ */
void stop(); void stop();
private: private:
Connection(const Connection&) = delete; Connection(const Connection &) = delete;
Connection& operator=(const Connection&) = delete; Connection & operator=(const Connection &) = delete;
WiFiClient client_; /*!< Client connection. @since 0.1.0 */ WiFiClient client_; /*!< Client connection. @since 0.1.0 */
TelnetStream stream_; /*!< Telnet stream for the connection. @since 0.1.0 */ TelnetStream stream_; /*!< Telnet stream for the connection. @since 0.1.0 */
std::shared_ptr<uuid::console::Shell> shell_; /*!< Shell for connection. @since 0.1.0 */ std::shared_ptr<uuid::console::Shell> shell_; /*!< Shell for connection. @since 0.1.0 */
IPAddress addr_; /*!< Remote address of connection. @since 0.1.0 */ IPAddress addr_; /*!< Remote address of connection. @since 0.1.0 */
uint16_t port_; /*!< Remote port of connection. @since 0.1.0 */ uint16_t port_; /*!< Remote port of connection. @since 0.1.0 */
}; };
TelnetService(const TelnetService&) = delete; TelnetService(const TelnetService &) = delete;
TelnetService& operator=(const TelnetService&) = delete; TelnetService & operator=(const TelnetService &) = delete;
static uuid::log::Logger logger_; /*!< uuid::log::Logger instance for telnet services. @since 0.1.0 */ static uuid::log::Logger logger_; /*!< uuid::log::Logger instance for telnet services. @since 0.1.0 */
WiFiServer server_; /*!< TCP server. @since 0.1.0 */ WiFiServer server_; /*!< TCP server. @since 0.1.0 */
size_t maximum_connections_ = MAX_CONNECTIONS; /*!< Maximum number of concurrent open connections. @since 0.1.0 */ size_t maximum_connections_ = MAX_CONNECTIONS; /*!< Maximum number of concurrent open connections. @since 0.1.0 */
std::list<Connection> connections_; /*!< Open connections. @since 0.1.0 */ std::list<Connection> connections_; /*!< Open connections. @since 0.1.0 */
shell_factory_function shell_factory_; /*!< Function to create a shell. @since 0.1.0 */ shell_factory_function shell_factory_; /*!< Function to create a shell. @since 0.1.0 */
unsigned long initial_idle_timeout_ = DEFAULT_IDLE_TIMEOUT; /*!< Initial idle timeout (in seconds). @since 0.1.0 */ unsigned long initial_idle_timeout_ = DEFAULT_IDLE_TIMEOUT; /*!< Initial idle timeout (in seconds). @since 0.1.0 */
unsigned long write_timeout_ = DEFAULT_WRITE_TIMEOUT; /*!< Write timeout (in milliseconds). @since 0.1.0 */ unsigned long write_timeout_ = DEFAULT_WRITE_TIMEOUT; /*!< Write timeout (in milliseconds). @since 0.1.0 */
}; };
} // namespace telnet } // namespace telnet

View File

@@ -68,11 +68,10 @@ void ledcWrite(uint8_t chan, uint32_t duty);
#define PROGMEM #define PROGMEM
#define PGM_P const char * #define PGM_P const char *
#define PSTR(s) s
class __FlashStringHelper; class __FlashStringHelper;
#define FPSTR(string_literal) (reinterpret_cast<const __FlashStringHelper *>(string_literal)) #define FPSTR(string_literal) (reinterpret_cast<const __FlashStringHelper *>(string_literal))
#define F(string_literal) (FPSTR(PSTR(string_literal))) #define F(string_literal) (FPSTR((string_literal)))
int snprintf_P(char * str, size_t size, const char * format, ...); int snprintf_P(char * str, size_t size, const char * format, ...);
int vsnprintf_P(char * str, size_t size, const char * format, va_list ap); int vsnprintf_P(char * str, size_t size, const char * format, va_list ap);
@@ -97,7 +96,7 @@ class Print {
virtual size_t write(uint8_t c) = 0; virtual size_t write(uint8_t c) = 0;
virtual size_t write(const uint8_t * buffer, size_t size) = 0; virtual size_t write(const uint8_t * buffer, size_t size) = 0;
size_t print(char c) { size_t print(char c) {
return write((uint8_t)c); return write((uint8_t)c);
} }
size_t print(const char * data) { size_t print(const char * data) {
return write(reinterpret_cast<const uint8_t *>(data), strlen(data)); return write(reinterpret_cast<const uint8_t *>(data), strlen(data));

View File

@@ -26,12 +26,12 @@ struct AsyncMqttClientMessageProperties {
namespace AsyncMqttClientInternals { namespace AsyncMqttClientInternals {
typedef std::function<void(bool sessionPresent)> OnConnectUserCallback; typedef std::function<void(bool sessionPresent)> OnConnectUserCallback;
typedef std::function<void(AsyncMqttClientDisconnectReason reason)> OnDisconnectUserCallback; typedef std::function<void(AsyncMqttClientDisconnectReason reason)> OnDisconnectUserCallback;
typedef std::function<void(uint16_t packetId, uint8_t qos)> OnSubscribeUserCallback; typedef std::function<void(uint16_t packetId, uint8_t qos)> OnSubscribeUserCallback;
typedef std::function<void(uint16_t packetId)> OnUnsubscribeUserCallback; typedef std::function<void(uint16_t packetId)> OnUnsubscribeUserCallback;
typedef std::function<void(char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)> OnMessageUserCallback; typedef std::function<void(char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)> OnMessageUserCallback;
typedef std::function<void(uint16_t packetId)> OnPublishUserCallback; typedef std::function<void(uint16_t packetId)> OnPublishUserCallback;
}; // namespace AsyncMqttClientInternals }; // namespace AsyncMqttClientInternals
class AsyncMqttClient { class AsyncMqttClient {

View File

@@ -13,120 +13,111 @@
template <class T> template <class T>
class HttpGetEndpoint { class HttpGetEndpoint {
public: public:
HttpGetEndpoint(JsonStateReader<T> stateReader, HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService, StatefulService<T> * statefulService,
AsyncWebServer* server, AsyncWebServer * server,
const String& servicePath, const String & servicePath,
SecurityManager* securityManager, SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE)
_stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) { : _stateReader(stateReader)
} , _statefulService(statefulService)
, _bufferSize(bufferSize) {
}
HttpGetEndpoint(JsonStateReader<T> stateReader, HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService, StatefulService<T> * statefulService,
AsyncWebServer* server, AsyncWebServer * server,
const String& servicePath, const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE)
_stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) { : _stateReader(stateReader)
} , _statefulService(statefulService)
, _bufferSize(bufferSize) {
}
protected: protected:
JsonStateReader<T> _stateReader; JsonStateReader<T> _stateReader;
StatefulService<T>* _statefulService; StatefulService<T> * _statefulService;
size_t _bufferSize; size_t _bufferSize;
void fetchSettings(AsyncWebServerRequest* request) { void fetchSettings(AsyncWebServerRequest * request) {
} }
}; };
template <class T> template <class T>
class HttpPostEndpoint { class HttpPostEndpoint {
public: public:
HttpPostEndpoint(JsonStateReader<T> stateReader, HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater, JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T> * statefulService,
AsyncWebServer* server, AsyncWebServer * server,
const String& servicePath, const String & servicePath,
SecurityManager* securityManager, SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE)
_stateReader(stateReader), : _stateReader(stateReader)
_stateUpdater(stateUpdater), , _stateUpdater(stateUpdater)
_statefulService(statefulService), , _statefulService(statefulService)
_bufferSize(bufferSize) { , _bufferSize(bufferSize) {
}
HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_stateReader(stateReader),
_stateUpdater(stateUpdater),
_statefulService(statefulService),
_bufferSize(bufferSize) {
}
protected:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T>* _statefulService;
size_t _bufferSize;
void updateSettings(AsyncWebServerRequest* request, JsonVariant& json) {
if (!json.is<JsonObject>()) {
return;
} }
JsonObject jsonObject = json.as<JsonObject>();
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); HttpPostEndpoint(JsonStateReader<T> stateReader,
if (outcome == StateUpdateResult::ERROR) { JsonStateUpdater<T> stateUpdater,
return; StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader)
, _stateUpdater(stateUpdater)
, _statefulService(statefulService)
, _bufferSize(bufferSize) {
} }
if (outcome == StateUpdateResult::CHANGED) {
protected:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> * _statefulService;
size_t _bufferSize;
void updateSettings(AsyncWebServerRequest * request, JsonVariant & json) {
if (!json.is<JsonObject>()) {
return;
}
JsonObject jsonObject = json.as<JsonObject>();
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
if (outcome == StateUpdateResult::ERROR) {
return;
}
if (outcome == StateUpdateResult::CHANGED) {
}
} }
}
}; };
template <class T> template <class T>
class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> { class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
public: public:
HttpEndpoint(JsonStateReader<T> stateReader, HttpEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater, JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T> * statefulService,
AsyncWebServer* server, AsyncWebServer * server,
const String& servicePath, const String & servicePath,
SecurityManager* securityManager, SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE)
HttpGetEndpoint<T>(stateReader, : HttpGetEndpoint<T>(stateReader, statefulService, server, servicePath, securityManager, authenticationPredicate, bufferSize)
statefulService, , HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, securityManager, authenticationPredicate, bufferSize) {
server, }
servicePath,
securityManager,
authenticationPredicate,
bufferSize),
HttpPostEndpoint<T>(stateReader,
stateUpdater,
statefulService,
server,
servicePath,
securityManager,
authenticationPredicate,
bufferSize) {
}
HttpEndpoint(JsonStateReader<T> stateReader, HttpEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater, JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T> * statefulService,
AsyncWebServer* server, AsyncWebServer * server,
const String& servicePath, const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE)
HttpGetEndpoint<T>(stateReader, statefulService, server, servicePath, bufferSize), : HttpGetEndpoint<T>(stateReader, statefulService, server, servicePath, bufferSize)
HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) { , HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) {
} }
}; };
#endif #endif

View File

@@ -61,51 +61,51 @@ typedef enum {
} wifi_auth_mode_t; } wifi_auth_mode_t;
typedef struct { typedef struct {
uint32_t status; /**< status of scanning APs: 0 — success, 1 - failure */ uint32_t status; /**< status of scanning APs: 0 — success, 1 - failure */
uint8_t number; /**< number of scan results */ uint8_t number; /**< number of scan results */
uint8_t scan_id; /**< scan sequence number, used for block scan */ uint8_t scan_id; /**< scan sequence number, used for block scan */
} wifi_event_sta_scan_done_t; } wifi_event_sta_scan_done_t;
/** Argument structure for WIFI_EVENT_STA_CONNECTED event */ /** Argument structure for WIFI_EVENT_STA_CONNECTED event */
typedef struct { typedef struct {
uint8_t ssid[32]; /**< SSID of connected AP */ uint8_t ssid[32]; /**< SSID of connected AP */
uint8_t ssid_len; /**< SSID length of connected AP */ uint8_t ssid_len; /**< SSID length of connected AP */
uint8_t bssid[6]; /**< BSSID of connected AP*/ uint8_t bssid[6]; /**< BSSID of connected AP*/
uint8_t channel; /**< channel of connected AP*/ uint8_t channel; /**< channel of connected AP*/
wifi_auth_mode_t authmode;/**< authentication mode used by AP*/ wifi_auth_mode_t authmode; /**< authentication mode used by AP*/
} wifi_event_sta_connected_t; } wifi_event_sta_connected_t;
/** Argument structure for WIFI_EVENT_STA_DISCONNECTED event */ /** Argument structure for WIFI_EVENT_STA_DISCONNECTED event */
typedef struct { typedef struct {
uint8_t ssid[32]; /**< SSID of disconnected AP */ uint8_t ssid[32]; /**< SSID of disconnected AP */
uint8_t ssid_len; /**< SSID length of disconnected AP */ uint8_t ssid_len; /**< SSID length of disconnected AP */
uint8_t bssid[6]; /**< BSSID of disconnected AP */ uint8_t bssid[6]; /**< BSSID of disconnected AP */
uint8_t reason; /**< reason of disconnection */ uint8_t reason; /**< reason of disconnection */
} wifi_event_sta_disconnected_t; } wifi_event_sta_disconnected_t;
/** Argument structure for WIFI_EVENT_STA_AUTHMODE_CHANGE event */ /** Argument structure for WIFI_EVENT_STA_AUTHMODE_CHANGE event */
typedef struct { typedef struct {
wifi_auth_mode_t old_mode; /**< the old auth mode of AP */ wifi_auth_mode_t old_mode; /**< the old auth mode of AP */
wifi_auth_mode_t new_mode; /**< the new auth mode of AP */ wifi_auth_mode_t new_mode; /**< the new auth mode of AP */
} wifi_event_sta_authmode_change_t; } wifi_event_sta_authmode_change_t;
/** Argument structure for WIFI_EVENT_STA_WPS_ER_PIN event */ /** Argument structure for WIFI_EVENT_STA_WPS_ER_PIN event */
typedef struct { typedef struct {
uint8_t pin_code[8]; /**< PIN code of station in enrollee mode */ uint8_t pin_code[8]; /**< PIN code of station in enrollee mode */
} wifi_event_sta_wps_er_pin_t; } wifi_event_sta_wps_er_pin_t;
/** Argument structure for WIFI_EVENT_STA_WPS_ER_FAILED event */ /** Argument structure for WIFI_EVENT_STA_WPS_ER_FAILED event */
typedef enum { typedef enum {
WPS_FAIL_REASON_NORMAL = 0, /**< ESP32 WPS normal fail reason */ WPS_FAIL_REASON_NORMAL = 0, /**< ESP32 WPS normal fail reason */
WPS_FAIL_REASON_RECV_M2D, /**< ESP32 WPS receive M2D frame */ WPS_FAIL_REASON_RECV_M2D, /**< ESP32 WPS receive M2D frame */
WPS_FAIL_REASON_MAX WPS_FAIL_REASON_MAX
} wifi_event_sta_wps_fail_reason_t; } wifi_event_sta_wps_fail_reason_t;
typedef union { typedef union {
wifi_event_sta_scan_done_t wifi_scan_done; wifi_event_sta_scan_done_t wifi_scan_done;
wifi_event_sta_authmode_change_t wifi_sta_authmode_change; wifi_event_sta_authmode_change_t wifi_sta_authmode_change;
wifi_event_sta_connected_t wifi_sta_connected; wifi_event_sta_connected_t wifi_sta_connected;
wifi_event_sta_disconnected_t wifi_sta_disconnected; wifi_event_sta_disconnected_t wifi_sta_disconnected;
} arduino_event_info_t; } arduino_event_info_t;
typedef struct { typedef struct {

View File

@@ -4,139 +4,136 @@
#include "../../src/emsesp_stub.h" // proddy added #include "../../src/emsesp_stub.h" // proddy added
SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) : SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs)
_httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this), : _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this)
_fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE), , _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE)
_jwtHandler(FACTORY_JWT_SECRET) { , _jwtHandler(FACTORY_JWT_SECRET) {
addUpdateHandler([&](const String& originId) { configureJWTHandler(); }, false); addUpdateHandler([&](const String & originId) { configureJWTHandler(); }, false);
} }
void SecuritySettingsService::begin() { void SecuritySettingsService::begin() {
_fsPersistence.readFromFS(); _fsPersistence.readFromFS();
configureJWTHandler(); configureJWTHandler();
} }
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest* request) { Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) {
AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER); AsyncWebHeader * authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
if (authorizationHeader) { if (authorizationHeader) {
String value = authorizationHeader->value(); String value = authorizationHeader->value();
if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) { if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN); value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
return authenticateJWT(value); return authenticateJWT(value);
}
} else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) {
AsyncWebParameter * tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER);
String value = tokenParamater->value();
return authenticateJWT(value);
} }
} else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) { return Authentication();
AsyncWebParameter* tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER);
String value = tokenParamater->value();
return authenticateJWT(value);
}
return Authentication();
} }
void SecuritySettingsService::configureJWTHandler() { void SecuritySettingsService::configureJWTHandler() {
_jwtHandler.setSecret(_state.jwtSecret); _jwtHandler.setSecret(_state.jwtSecret);
} }
Authentication SecuritySettingsService::authenticateJWT(String& jwt) { Authentication SecuritySettingsService::authenticateJWT(String & jwt) {
DynamicJsonDocument payloadDocument(MAX_JWT_SIZE); DynamicJsonDocument payloadDocument(MAX_JWT_SIZE);
_jwtHandler.parseJWT(jwt, payloadDocument); _jwtHandler.parseJWT(jwt, payloadDocument);
if (payloadDocument.is<JsonObject>()) { if (payloadDocument.is<JsonObject>()) {
JsonObject parsedPayload = payloadDocument.as<JsonObject>(); JsonObject parsedPayload = payloadDocument.as<JsonObject>();
String username = parsedPayload["username"]; String username = parsedPayload["username"];
for (User _user : _state.users) {
if (_user.username == username && validatePayload(parsedPayload, &_user)) {
return Authentication(_user);
}
}
}
return Authentication();
}
Authentication SecuritySettingsService::authenticate(const String & username, const String & password) {
for (User _user : _state.users) { for (User _user : _state.users) {
if (_user.username == username && validatePayload(parsedPayload, &_user)) { if (_user.username == username && _user.password == password) {
return Authentication(_user); return Authentication(_user);
} }
} }
} return Authentication();
return Authentication();
} }
Authentication SecuritySettingsService::authenticate(const String& username, const String& password) { inline void populateJWTPayload(JsonObject & payload, User * user) {
for (User _user : _state.users) { payload["username"] = user->username;
if (_user.username == username && _user.password == password) { payload["admin"] = user->admin;
return Authentication(_user);
}
}
return Authentication();
} }
inline void populateJWTPayload(JsonObject& payload, User* user) { boolean SecuritySettingsService::validatePayload(JsonObject & parsedPayload, User * user) {
payload["username"] = user->username; DynamicJsonDocument jsonDocument(MAX_JWT_SIZE);
payload["admin"] = user->admin; JsonObject payload = jsonDocument.to<JsonObject>();
populateJWTPayload(payload, user);
return payload == parsedPayload;
} }
boolean SecuritySettingsService::validatePayload(JsonObject& parsedPayload, User* user) { String SecuritySettingsService::generateJWT(User * user) {
DynamicJsonDocument jsonDocument(MAX_JWT_SIZE); DynamicJsonDocument jsonDocument(MAX_JWT_SIZE);
JsonObject payload = jsonDocument.to<JsonObject>(); JsonObject payload = jsonDocument.to<JsonObject>();
populateJWTPayload(payload, user); populateJWTPayload(payload, user);
return payload == parsedPayload; return _jwtHandler.buildJWT(payload);
}
String SecuritySettingsService::generateJWT(User* user) {
DynamicJsonDocument jsonDocument(MAX_JWT_SIZE);
JsonObject payload = jsonDocument.to<JsonObject>();
populateJWTPayload(payload, user);
return _jwtHandler.buildJWT(payload);
} }
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) { ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
return [this, predicate](AsyncWebServerRequest* request) { return [this, predicate](AsyncWebServerRequest * request) {
Authentication authentication = authenticateRequest(request); Authentication authentication = authenticateRequest(request);
return predicate(authentication); return predicate(authentication);
}; };
} }
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
AuthenticationPredicate predicate) { return [this, onRequest, predicate](AsyncWebServerRequest * request) {
return [this, onRequest, predicate](AsyncWebServerRequest* request) { Authentication authentication = authenticateRequest(request);
Authentication authentication = authenticateRequest(request); if (!predicate(authentication)) {
if (!predicate(authentication)) { request->send(401);
request->send(401); return;
return; }
} onRequest(request);
onRequest(request); };
};
} }
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
AuthenticationPredicate predicate) { return [this, onRequest, predicate](AsyncWebServerRequest * request, JsonVariant & json) {
return [this, onRequest, predicate](AsyncWebServerRequest* request, JsonVariant& json) { Authentication authentication = authenticateRequest(request);
Authentication authentication = authenticateRequest(request); if (!predicate(authentication)) {
if (!predicate(authentication)) { request->send(401);
request->send(401); return;
return; }
} onRequest(request, json);
onRequest(request, json); };
};
} }
#else #else
User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true); User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) : SecurityManager() { SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs)
: SecurityManager() {
} }
SecuritySettingsService::~SecuritySettingsService() { SecuritySettingsService::~SecuritySettingsService() {
} }
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) { ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
return [this, predicate](AsyncWebServerRequest* request) { return true; }; return [this, predicate](AsyncWebServerRequest * request) { return true; };
} }
// Return the admin user on all request - disabling security features // Return the admin user on all request - disabling security features
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest* request) { Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) {
return Authentication(ADMIN_USER); return Authentication(ADMIN_USER);
} }
// Return the function unwrapped // Return the function unwrapped
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
AuthenticationPredicate predicate) { return onRequest;
return onRequest;
} }
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
AuthenticationPredicate predicate) { return onRequest;
return onRequest;
} }
#endif #endif

View File

@@ -16,10 +16,10 @@
#endif #endif
enum class StateUpdateResult { enum class StateUpdateResult {
CHANGED = 0, // The update changed the state and propagation should take place if required CHANGED = 0, // The update changed the state and propagation should take place if required
CHANGED_RESTART, // The update changed the state and the service should be restarted CHANGED_RESTART, // The update changed the state and the service should be restarted
UNCHANGED, // The state was unchanged, propagation should not take place UNCHANGED, // The state was unchanged, propagation should not take place
ERROR // There was a problem updating the state, propagation should not take place ERROR // There was a problem updating the state, propagation should not take place
}; };
template <typename T> template <typename T>

View File

@@ -240,6 +240,8 @@ const mqtt_status = {
client_id: 'ems-esp', client_id: 'ems-esp',
disconnect_reason: 0, disconnect_reason: 0,
mqtt_fails: 0, mqtt_fails: 0,
mqtt_queued: 1,
connect_count: 2,
} }
// SYSTEM // SYSTEM
@@ -592,20 +594,15 @@ const emsesp_deviceentities_1 = [
m: 0, m: 0,
w: false, w: false,
}, },
// { {
// v: 'test data', v: 18.2,
// n: 'test', n: 'hc1 selected room temperature',
// id: 'test', id: 'hc1/seltemp',
// m: 0, m: 0,
// w: false, mi: 5,
// }, ma: 52,
// { w: true,
// v: 18.2, },
// n: 'hc1 selected room temperature',
// id: 'hc1/seltemp',
// m: 0,
// w: true,
// },
{ {
v: 22.6, v: 22.6,
n: 'hc1 current room temperature', n: 'hc1 current room temperature',

View File

@@ -92,3 +92,14 @@ board_upload.flash_size = 4MB
board_build.partitions = esp32_partition_4M.csv board_build.partitions = esp32_partition_4M.csv
build_flags = ${common.build_flags} build_flags = ${common.build_flags}
build_unflags = ${common.unbuild_flags} build_unflags = ${common.unbuild_flags}
[env:lolin_s2_mini]
extra_scripts =
pre:scripts/build_interface.py
scripts/rename_fw.py
board = lolin_s2_mini
platform = espressif32
board_upload.flash_size = 4MB
board_build.partitions = esp32_partition_4M.csv
build_flags = ${common.build_flags}
build_unflags = ${common.unbuild_flags}

View File

@@ -18,27 +18,29 @@ def bin_copy(source, target, env):
bag[var] = m.group(1) bag[var] = m.group(1)
app_version = bag.get('app_version') app_version = bag.get('app_version')
platform = "ESP32"
platform = "ESP32"
chip_target = env.get('PIOENV').upper()
# this breaks the CI so removed
# flash_size = env["PIOENV"].split('_')[1] # flash_size = env["PIOENV"].split('_')[1]
# print(env.Dump()) # print(env.Dump())
# my_flags = env.ParseFlags(env['BUILD_FLAGS']) # my_flags = env.ParseFlags(env['BUILD_FLAGS'])
# defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")} # defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")}
# print(my_flags) # print(my_flags)
# print((my_flags.get("CPPDEFINES")) # print(my_flags.get("CPPDEFINES")
# alternatively take platform from the pio target # alternatively take platform from the pio target
# platform = str(target[0]).split(os.path.sep)[2] # platform = str(target[0]).split(os.path.sep)[2]
chip_target = env.get('PIOENV').upper()
print("app version: "+app_version) print("app version: " + app_version)
print("platform: "+platform) print("platform: " + platform)
# print("flash size: "+flash_size) print("chip_target: " + chip_target)
# convert . to _ so Windows doesn't complain # convert . to _ so Windows doesn't complain
variant = "EMS-ESP-" + app_version.replace(".", "_") + "-" + platform variant = "EMS-ESP-" + chip_target + "-" + app_version.replace(".", "_")
# variant = "EMS-ESP-" + app_version.replace(".", "_") + "-" + platform + "_" + flash_size
# check if output directories exist and create if necessary # check if output directories exist and create if necessary
if not os.path.isdir(OUTPUT_DIR): if not os.path.isdir(OUTPUT_DIR):

View File

@@ -31,26 +31,25 @@ void AnalogSensor::start() {
} }
analogSetAttenuation(ADC_2_5db); // for all channels 1.5V analogSetAttenuation(ADC_2_5db); // for all channels 1.5V
LOG_INFO("Starting Analog sensor service");
LOG_INFO(F("Starting Analog sensor service"));
// Add API call for /info // Add API call for /info
Command::add( Command::add(
EMSdevice::DeviceType::ANALOGSENSOR, EMSdevice::DeviceType::ANALOGSENSOR,
F_(info), F_(info),
[&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, id, output); }, [&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, id, output); },
F_(info_cmd)); FL_(info_cmd));
Command::add( Command::add(
EMSdevice::DeviceType::ANALOGSENSOR, EMSdevice::DeviceType::ANALOGSENSOR,
F_(setvalue), F_(setvalue),
[&](const char * value, const int8_t id) { return command_setvalue(value, id); }, [&](const char * value, const int8_t id) { return command_setvalue(value, id); },
F("set io value"), // TODO this needs translating FL_(setiovalue_cmd),
CommandFlag::ADMIN_ONLY); CommandFlag::ADMIN_ONLY);
Command::add( Command::add(
EMSdevice::DeviceType::ANALOGSENSOR, EMSdevice::DeviceType::ANALOGSENSOR,
F_(commands), F_(commands),
[&](const char * value, const int8_t id, JsonObject & output) { return command_commands(value, id, output); }, [&](const char * value, const int8_t id, JsonObject & output) { return command_commands(value, id, output); },
F_(commands_cmd)); FL_(commands_cmd));
Mqtt::subscribe(EMSdevice::DeviceType::ANALOGSENSOR, "analogsensor/#", nullptr); // use empty function callback Mqtt::subscribe(EMSdevice::DeviceType::ANALOGSENSOR, "analogsensor/#", nullptr); // use empty function callback
} }
@@ -123,12 +122,12 @@ void AnalogSensor::reload() {
for (auto & sensor : sensors_) { for (auto & sensor : sensors_) {
sensor.ha_registered = false; // force HA configs to be re-created sensor.ha_registered = false; // force HA configs to be re-created
if (sensor.type() == AnalogType::ADC) { if (sensor.type() == AnalogType::ADC) {
LOG_DEBUG(F("Adding analog ADC sensor on GPIO%d"), sensor.gpio()); LOG_DEBUG("Adding analog ADC sensor on GPIO%d", sensor.gpio());
// analogSetPinAttenuation does not work with analogReadMilliVolts // analogSetPinAttenuation does not work with analogReadMilliVolts
sensor.analog_ = 0; // initialize sensor.analog_ = 0; // initialize
sensor.last_reading_ = 0; sensor.last_reading_ = 0;
} else if (sensor.type() == AnalogType::COUNTER) { } else if (sensor.type() == AnalogType::COUNTER) {
LOG_DEBUG(F("Adding analog I/O Counter sensor on GPIO%d"), sensor.gpio()); LOG_DEBUG("Adding analog I/O Counter sensor on GPIO%d", sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP); pinMode(sensor.gpio(), INPUT_PULLUP);
#ifndef ARDUINO_LOLIN_C3_MINI #ifndef ARDUINO_LOLIN_C3_MINI
if (sensor.gpio() == 25 || sensor.gpio() == 26) { if (sensor.gpio() == 25 || sensor.gpio() == 26) {
@@ -139,7 +138,7 @@ void AnalogSensor::reload() {
sensor.poll_ = digitalRead(sensor.gpio()); sensor.poll_ = digitalRead(sensor.gpio());
publish_sensor(sensor); publish_sensor(sensor);
} else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) { } else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) {
LOG_DEBUG(F("Adding analog Timer/Rate sensor on GPIO%d"), sensor.gpio()); LOG_DEBUG("Adding analog Timer/Rate sensor on GPIO%d", sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP); pinMode(sensor.gpio(), INPUT_PULLUP);
sensor.polltime_ = uuid::get_uptime(); sensor.polltime_ = uuid::get_uptime();
sensor.last_polltime_ = uuid::get_uptime(); sensor.last_polltime_ = uuid::get_uptime();
@@ -148,7 +147,7 @@ void AnalogSensor::reload() {
sensor.set_value(0); sensor.set_value(0);
publish_sensor(sensor); publish_sensor(sensor);
} else if (sensor.type() == AnalogType::DIGITAL_IN) { } else if (sensor.type() == AnalogType::DIGITAL_IN) {
LOG_DEBUG(F("Adding analog Read sensor on GPIO%d"), sensor.gpio()); LOG_DEBUG("Adding analog Read sensor on GPIO%d", sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP); pinMode(sensor.gpio(), INPUT_PULLUP);
sensor.set_value(digitalRead(sensor.gpio())); // initial value sensor.set_value(digitalRead(sensor.gpio())); // initial value
sensor.set_uom(0); // no uom, just for safe measures sensor.set_uom(0); // no uom, just for safe measures
@@ -156,7 +155,7 @@ void AnalogSensor::reload() {
sensor.poll_ = digitalRead(sensor.gpio()); sensor.poll_ = digitalRead(sensor.gpio());
publish_sensor(sensor); publish_sensor(sensor);
} else if (sensor.type() == AnalogType::DIGITAL_OUT) { } else if (sensor.type() == AnalogType::DIGITAL_OUT) {
LOG_DEBUG(F("Adding analog Write sensor on GPIO%d"), sensor.gpio()); LOG_DEBUG("Adding analog Write sensor on GPIO%d", sensor.gpio());
pinMode(sensor.gpio(), OUTPUT); pinMode(sensor.gpio(), OUTPUT);
if (sensor.gpio() == 25 || sensor.gpio() == 26) { if (sensor.gpio() == 25 || sensor.gpio() == 26) {
if (sensor.offset() > 255) { if (sensor.offset() > 255) {
@@ -175,7 +174,7 @@ void AnalogSensor::reload() {
sensor.set_uom(0); // no uom, just for safe measures sensor.set_uom(0); // no uom, just for safe measures
publish_sensor(sensor); publish_sensor(sensor);
} else if (sensor.type() >= AnalogType::PWM_0) { } else if (sensor.type() >= AnalogType::PWM_0) {
LOG_DEBUG(F("Adding PWM output sensor on GPIO%d"), sensor.gpio()); LOG_DEBUG("Adding PWM output sensor on GPIO%d", sensor.gpio());
uint channel = sensor.type() - AnalogType::PWM_0; uint channel = sensor.type() - AnalogType::PWM_0;
ledcSetup(channel, sensor.factor(), 13); ledcSetup(channel, sensor.factor(), 13);
ledcAttachPin(sensor.gpio(), channel); ledcAttachPin(sensor.gpio(), channel);
@@ -277,7 +276,7 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, float offset,
found_sensor = true; // found the record found_sensor = true; // found the record
// see if it's marked for deletion // see if it's marked for deletion
if (type == AnalogType::MARK_DELETED) { if (type == AnalogType::MARK_DELETED) {
LOG_DEBUG(F("Removing analog sensor GPIO %d"), gpio); LOG_DEBUG("Removing analog sensor GPIO %d", gpio);
settings.analogCustomizations.remove(AnalogCustomization); settings.analogCustomizations.remove(AnalogCustomization);
} else { } else {
// update existing record // update existing record
@@ -286,7 +285,7 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, float offset,
AnalogCustomization.factor = factor; AnalogCustomization.factor = factor;
AnalogCustomization.uom = uom; AnalogCustomization.uom = uom;
AnalogCustomization.type = type; AnalogCustomization.type = type;
LOG_DEBUG(F("Customizing existing analog GPIO %d"), gpio); LOG_DEBUG("Customizing existing analog GPIO %d", gpio);
} }
return StateUpdateResult::CHANGED; // persist the change return StateUpdateResult::CHANGED; // persist the change
} }
@@ -312,7 +311,7 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, float offset,
newSensor.uom = uom; newSensor.uom = uom;
newSensor.type = type; newSensor.type = type;
settings.analogCustomizations.push_back(newSensor); settings.analogCustomizations.push_back(newSensor);
LOG_DEBUG(F("Adding new customization for analog sensor GPIO %d"), gpio); LOG_DEBUG("Adding new customization for analog sensor GPIO %d", gpio);
return StateUpdateResult::CHANGED; // persist the change return StateUpdateResult::CHANGED; // persist the change
}, },
"local"); "local");
@@ -338,9 +337,9 @@ void AnalogSensor::publish_sensor(const Sensor & sensor) const {
if (Mqtt::publish_single()) { if (Mqtt::publish_single()) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) { if (Mqtt::publish_single2cmd()) {
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(analogsensor)).c_str(), sensor.name().c_str()); snprintf(topic, sizeof(topic), "%s/%s", (F_(analogsensor)), sensor.name().c_str());
} else { } else {
snprintf(topic, sizeof(topic), "%s%s/%s", read_flash_string(F_(analogsensor)).c_str(), "_data", sensor.name().c_str()); snprintf(topic, sizeof(topic), "%s%s/%s", (F_(analogsensor)), "_data", sensor.name().c_str());
} }
char payload[10]; char payload[10];
Mqtt::publish(topic, Helpers::render_value(payload, sensor.value(), 2)); // always publish as floats Mqtt::publish(topic, Helpers::render_value(payload, sensor.value(), 2)); // always publish as floats
@@ -353,7 +352,7 @@ void AnalogSensor::remove_ha_topic(const uint8_t gpio) const {
return; return;
} }
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
LOG_DEBUG(F("Removing HA config for analog sensor GPIO %d"), gpio); LOG_DEBUG("Removing HA config for analog sensor GPIO %d", gpio);
#endif #endif
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%d/config", Mqtt::basename().c_str(), gpio); snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%d/config", Mqtt::basename().c_str(), gpio);
@@ -390,7 +389,7 @@ void AnalogSensor::publish_values(const bool force) {
case AnalogType::PWM_0: case AnalogType::PWM_0:
case AnalogType::PWM_1: case AnalogType::PWM_1:
case AnalogType::PWM_2: case AnalogType::PWM_2:
dataSensor["value"] = sensor.value(); // float dataSensor["value"] = serialized(Helpers::render_value(s, sensor.value(), 2)); // float
break; break;
default: default:
dataSensor["value"] = (uint8_t)sensor.value(); // convert to char for 1 or 0 dataSensor["value"] = (uint8_t)sensor.value(); // convert to char for 1 or 0
@@ -403,7 +402,7 @@ void AnalogSensor::publish_values(const bool force) {
// create HA config // create HA config
if (Mqtt::ha_enabled() && (!sensor.ha_registered || force)) { if (Mqtt::ha_enabled() && (!sensor.ha_registered || force)) {
LOG_DEBUG(F("Recreating HA config for analog sensor GPIO %d"), sensor.gpio()); LOG_DEBUG("Recreating HA config for analog sensor GPIO %d", sensor.gpio());
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config; StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
@@ -447,7 +446,7 @@ void AnalogSensor::publish_values(const bool force) {
} }
} }
Mqtt::publish(F("analogsensor_data"), doc.as<JsonObject>()); Mqtt::publish("analogsensor_data", doc.as<JsonObject>());
} }
// called from emsesp.cpp, similar to the emsdevice->get_value_info // called from emsesp.cpp, similar to the emsdevice->get_value_info

View File

@@ -25,8 +25,10 @@
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
#include "driver/adc.h" #include "driver/adc.h"
#ifndef ARDUINO_LOLIN_S2_MINI
#include <esp_bt.h> #include <esp_bt.h>
#endif #endif
#endif
#include <uuid/log.h> #include <uuid/log.h>

View File

@@ -82,7 +82,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
// validate the device, make sure it exists // validate the device, make sure it exists
uint8_t device_type = EMSdevice::device_name_2_device_type(device_s); uint8_t device_type = EMSdevice::device_name_2_device_type(device_s);
if (!device_has_commands(device_type)) { if (!device_has_commands(device_type)) {
LOG_DEBUG(F("Command failed: unknown device '%s'"), device_s); LOG_DEBUG("Command failed: unknown device '%s'", device_s);
return message(CommandRet::ERROR, "unknown device", output); return message(CommandRet::ERROR, "unknown device", output);
} }
@@ -170,15 +170,15 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
std::string Command::return_code_string(const uint8_t return_code) { std::string Command::return_code_string(const uint8_t return_code) {
switch (return_code) { switch (return_code) {
case CommandRet::ERROR: case CommandRet::ERROR:
return read_flash_string(F("Error")); return ("Error");
case CommandRet::OK: case CommandRet::OK:
return read_flash_string(F("OK")); return ("OK");
case CommandRet::NOT_FOUND: case CommandRet::NOT_FOUND:
return read_flash_string(F("Not Found")); return ("Not Found");
case CommandRet::NOT_ALLOWED: case CommandRet::NOT_ALLOWED:
return read_flash_string(F("Not Authorized")); return ("Not Authorized");
case CommandRet::FAIL: case CommandRet::FAIL:
return read_flash_string(F("Failed")); return ("Failed");
default: default:
break; break;
} }
@@ -238,7 +238,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * value, const bool is_admin, const int8_t id, JsonObject & output) { uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * value, const bool is_admin, const int8_t id, JsonObject & output) {
uint8_t return_code = CommandRet::OK; uint8_t return_code = CommandRet::OK;
std::string dname = EMSdevice::device_type_2_device_name(device_type); auto dname = EMSdevice::device_type_2_device_name(device_type);
// see if there is a command registered // see if there is a command registered
auto cf = find_command(device_type, cmd); auto cf = find_command(device_type, cmd);
@@ -248,7 +248,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
if ((device_type > EMSdevice::DeviceType::SYSTEM) && (!value || !strlen(value))) { if ((device_type > EMSdevice::DeviceType::SYSTEM) && (!value || !strlen(value))) {
if (!cf || !cf->cmdfunction_json_) { if (!cf || !cf->cmdfunction_json_) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] Calling %s command '%s' to retrieve attributes"), dname.c_str(), cmd); LOG_DEBUG("[DEBUG] Calling %s command '%s' to retrieve attributes", dname, cmd);
#endif #endif
return EMSESP::get_device_value_info(output, cmd, id, device_type) ? CommandRet::OK : CommandRet::ERROR; // entity = cmd return EMSESP::get_device_value_info(output, cmd, id, device_type) ? CommandRet::OK : CommandRet::ERROR; // entity = cmd
} }
@@ -262,17 +262,19 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
return CommandRet::NOT_ALLOWED; // command not allowed return CommandRet::NOT_ALLOWED; // command not allowed
} }
auto description = Helpers::translated_word(cf->description_);
if ((value == nullptr) || (strlen(value) == 0)) { if ((value == nullptr) || (strlen(value) == 0)) {
if (EMSESP::system_.readonly_mode()) { if (EMSESP::system_.readonly_mode()) {
LOG_INFO(F("[readonly] Calling command '%s/%s' (%s)"), dname.c_str(), cmd, read_flash_string(cf->description_).c_str()); LOG_INFO(("[readonly] Calling command '%s/%s' (%s)"), dname, cmd, description);
} else { } else {
LOG_DEBUG(F("Calling command '%s/%s' (%s)"), dname.c_str(), cmd, read_flash_string(cf->description_).c_str()); LOG_DEBUG(("Calling command '%s/%s' (%s)"), dname, cmd, description);
} }
} else { } else {
if (EMSESP::system_.readonly_mode()) { if (EMSESP::system_.readonly_mode()) {
LOG_INFO(F("[readonly] Calling command '%s/%s' (%s) with value %s"), dname.c_str(), cmd, read_flash_string(cf->description_).c_str(), value); LOG_INFO(("[readonly] Calling command '%s/%s' (%s) with value %s"), dname, cmd, description, value);
} else { } else {
LOG_DEBUG(F("Calling command '%s/%s' (%s) with value %s"), dname.c_str(), cmd, read_flash_string(cf->description_).c_str(), value); LOG_DEBUG(("Calling command '%s/%s' (%s) with value %s"), dname, cmd, description, value);
} }
} }
@@ -294,14 +296,14 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
} }
// we didn't find the command and its not an endpoint, report error // we didn't find the command and its not an endpoint, report error
LOG_DEBUG(F("Command failed: invalid command '%s'"), cmd); LOG_DEBUG("Command failed: invalid command '%s'", cmd);
return message(CommandRet::NOT_FOUND, "invalid command", output); return message(CommandRet::NOT_FOUND, "invalid command", output);
} }
// add a command to the list, which does not return json // add a command to the list, which does not return json
void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, const cmd_function_p cb, const __FlashStringHelper * description, uint8_t flags) { void Command::add(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags) {
// if the command already exists for that device type don't add it // if the command already exists for that device type don't add it
if (find_command(device_type, read_flash_string(cmd).c_str()) != nullptr) { if (find_command(device_type, cmd) != nullptr) {
return; return;
} }
@@ -314,9 +316,9 @@ void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, co
} }
// add a command to the list, which does return a json object as output // add a command to the list, which does return a json object as output
void Command::add(const uint8_t device_type, const __FlashStringHelper * cmd, const cmd_json_function_p cb, const __FlashStringHelper * description, uint8_t flags) { void Command::add(const uint8_t device_type, const char * cmd, const cmd_json_function_p cb, const char * const * description, uint8_t flags) {
// if the command already exists for that device type don't add it // if the command already exists for that device type don't add it
if (find_command(device_type, read_flash_string(cmd).c_str()) != nullptr) { if (find_command(device_type, cmd) != nullptr) {
return; return;
} }
@@ -338,7 +340,7 @@ Command::CmdFunction * Command::find_command(const uint8_t device_type, const ch
} }
for (auto & cf : cmdfunctions_) { for (auto & cf : cmdfunctions_) {
if (!strcmp(lowerCmd, Helpers::toLower(read_flash_string(cf.cmd_)).c_str()) && (cf.device_type_ == device_type)) { if (!strcmp(lowerCmd, cf.cmd_) && (cf.device_type_ == device_type)) {
return &cf; return &cf;
} }
} }
@@ -357,15 +359,15 @@ bool Command::list(const uint8_t device_type, JsonObject & output) {
std::list<std::string> sorted_cmds; std::list<std::string> sorted_cmds;
for (const auto & cf : cmdfunctions_) { for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) { if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) {
sorted_cmds.push_back(read_flash_string(cf.cmd_)); sorted_cmds.push_back((cf.cmd_));
} }
} }
sorted_cmds.sort(); sorted_cmds.sort();
for (const auto & cl : sorted_cmds) { for (const auto & cl : sorted_cmds) {
for (const auto & cf : cmdfunctions_) { for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == read_flash_string(cf.cmd_))) { if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == std::string(cf.cmd_))) {
output[cl] = cf.description_; output[cl] = Helpers::translated_word(cf.description_);
} }
} }
} }
@@ -376,7 +378,7 @@ bool Command::list(const uint8_t device_type, JsonObject & output) {
// output list of all commands to console for a specific DeviceType // output list of all commands to console for a specific DeviceType
void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbose) { void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbose) {
if (cmdfunctions_.empty()) { if (cmdfunctions_.empty()) {
shell.println(F("No commands available")); shell.println("No commands available");
return; return;
} }
@@ -384,7 +386,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
std::list<std::string> sorted_cmds; std::list<std::string> sorted_cmds;
for (const auto & cf : cmdfunctions_) { for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) { if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN)) {
sorted_cmds.push_back(read_flash_string(cf.cmd_)); sorted_cmds.push_back((cf.cmd_));
} }
} }
sorted_cmds.sort(); sorted_cmds.sort();
@@ -404,7 +406,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
for (const auto & cl : sorted_cmds) { for (const auto & cl : sorted_cmds) {
// find and print the description // find and print the description
for (const auto & cf : cmdfunctions_) { for (const auto & cf : cmdfunctions_) {
if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == read_flash_string(cf.cmd_))) { if ((cf.device_type_ == device_type) && !cf.has_flags(CommandFlag::HIDDEN) && cf.description_ && (cl == std::string(cf.cmd_))) {
uint8_t i = cl.length(); uint8_t i = cl.length();
shell.print(" "); shell.print(" ");
if (cf.has_flags(MQTT_SUB_FLAG_HC)) { if (cf.has_flags(MQTT_SUB_FLAG_HC)) {
@@ -424,7 +426,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
shell.print(EMSdevice::tag_to_string(DeviceValueTAG::TAG_DEVICE_DATA_WW)); shell.print(EMSdevice::tag_to_string(DeviceValueTAG::TAG_DEVICE_DATA_WW));
shell.print(' '); shell.print(' ');
} }
shell.print(read_flash_string(cf.description_).c_str()); shell.print(Helpers::translated_word(cf.description_));
if (!cf.has_flags(CommandFlag::ADMIN_ONLY)) { if (!cf.has_flags(CommandFlag::ADMIN_ONLY)) {
shell.print(' '); shell.print(' ');
shell.print(COLOR_BRIGHT_RED); shell.print(COLOR_BRIGHT_RED);
@@ -474,19 +476,19 @@ bool Command::device_has_commands(const uint8_t device_type) {
// list sensors and EMS devices // list sensors and EMS devices
void Command::show_devices(uuid::console::Shell & shell) { void Command::show_devices(uuid::console::Shell & shell) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SYSTEM).c_str()); shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SYSTEM));
if (EMSESP::dallassensor_.have_sensors()) { if (EMSESP::dallassensor_.have_sensors()) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::DALLASSENSOR).c_str()); shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::DALLASSENSOR));
} }
if (EMSESP::analogsensor_.have_sensors()) { if (EMSESP::analogsensor_.have_sensors()) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::ANALOGSENSOR).c_str()); shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::ANALOGSENSOR));
} }
for (const auto & device_class : EMSFactory::device_handlers()) { for (const auto & device_class : EMSFactory::device_handlers()) {
for (const auto & emsdevice : EMSESP::emsdevices) { for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_class.first) && (device_has_commands(device_class.first))) { if (emsdevice && (emsdevice->device_type() == device_class.first) && (device_has_commands(device_class.first))) {
shell.printf("%s ", EMSdevice::device_type_2_device_name(device_class.first).c_str()); shell.printf("%s ", EMSdevice::device_type_2_device_name(device_class.first));
break; // we only want to show one (not multiple of the same device types) break; // we only want to show one (not multiple of the same device types)
} }
} }
@@ -497,12 +499,12 @@ void Command::show_devices(uuid::console::Shell & shell) {
// output list of all commands to console // output list of all commands to console
// calls show with verbose mode set // calls show with verbose mode set
void Command::show_all(uuid::console::Shell & shell) { void Command::show_all(uuid::console::Shell & shell) {
shell.println(F("Available commands (*=do not need authorization): ")); shell.println(("Available commands (*=do not need authorization): "));
// show system first // show system first
shell.print(COLOR_BOLD_ON); shell.print(COLOR_BOLD_ON);
shell.print(COLOR_YELLOW); shell.print(COLOR_YELLOW);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SYSTEM).c_str()); shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SYSTEM));
shell.print(COLOR_RESET); shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::SYSTEM, true); show(shell, EMSdevice::DeviceType::SYSTEM, true);
@@ -510,14 +512,14 @@ void Command::show_all(uuid::console::Shell & shell) {
if (EMSESP::dallassensor_.have_sensors()) { if (EMSESP::dallassensor_.have_sensors()) {
shell.print(COLOR_BOLD_ON); shell.print(COLOR_BOLD_ON);
shell.print(COLOR_YELLOW); shell.print(COLOR_YELLOW);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::DALLASSENSOR).c_str()); shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::DALLASSENSOR));
shell.print(COLOR_RESET); shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::DALLASSENSOR, true); show(shell, EMSdevice::DeviceType::DALLASSENSOR, true);
} }
if (EMSESP::analogsensor_.have_sensors()) { if (EMSESP::analogsensor_.have_sensors()) {
shell.print(COLOR_BOLD_ON); shell.print(COLOR_BOLD_ON);
shell.print(COLOR_YELLOW); shell.print(COLOR_YELLOW);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::ANALOGSENSOR).c_str()); shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::ANALOGSENSOR));
shell.print(COLOR_RESET); shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::ANALOGSENSOR, true); show(shell, EMSdevice::DeviceType::ANALOGSENSOR, true);
} }
@@ -527,7 +529,7 @@ void Command::show_all(uuid::console::Shell & shell) {
if (Command::device_has_commands(device_class.first)) { if (Command::device_has_commands(device_class.first)) {
shell.print(COLOR_BOLD_ON); shell.print(COLOR_BOLD_ON);
shell.print(COLOR_YELLOW); shell.print(COLOR_YELLOW);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(device_class.first).c_str()); shell.printf(" %s: ", EMSdevice::device_type_2_device_name(device_class.first));
shell.print(COLOR_RESET); shell.print(COLOR_RESET);
show(shell, device_class.first, true); show(shell, device_class.first, true);
} }

View File

@@ -54,19 +54,19 @@ using cmd_json_function_p = std::function<bool(const char * data, const int8_t i
class Command { class Command {
public: public:
struct CmdFunction { struct CmdFunction {
uint8_t device_type_; // DeviceType:: uint8_t device_type_; // DeviceType::
uint8_t flags_; // mqtt flags for command subscriptions uint8_t flags_; // mqtt flags for command subscriptions
const __FlashStringHelper * cmd_; const char * cmd_;
const cmd_function_p cmdfunction_; const cmd_function_p cmdfunction_;
const cmd_json_function_p cmdfunction_json_; const cmd_json_function_p cmdfunction_json_;
const __FlashStringHelper * description_; const char * const * description_;
CmdFunction(const uint8_t device_type, CmdFunction(const uint8_t device_type,
const uint8_t flags, const uint8_t flags,
const __FlashStringHelper * cmd, const char * cmd,
const cmd_function_p cmdfunction, const cmd_function_p cmdfunction,
const cmd_json_function_p cmdfunction_json, const cmd_json_function_p cmdfunction_json,
const __FlashStringHelper * description) const char * const * description)
: device_type_(device_type) : device_type_(device_type)
, flags_(flags) , flags_(flags)
, cmd_(cmd) , cmd_(cmd)
@@ -93,24 +93,22 @@ class Command {
return cmdfunctions_; return cmdfunctions_;
} }
#define add_
static uint8_t call(const uint8_t device_type, const char * cmd, const char * value, const bool is_admin, const int8_t id, JsonObject & output); static uint8_t call(const uint8_t device_type, const char * cmd, const char * value, const bool is_admin, const int8_t id, JsonObject & output);
static uint8_t call(const uint8_t device_type, const char * cmd, const char * value); static uint8_t call(const uint8_t device_type, const char * cmd, const char * value);
// with normal call back function taking a value and id // with normal call back function taking a value and id
static void add(const uint8_t device_type, static void add(const uint8_t device_type,
const __FlashStringHelper * cmd, const char * cmd,
const cmd_function_p cb, const cmd_function_p cb,
const __FlashStringHelper * description, const char * const * description,
uint8_t flags = CommandFlag::MQTT_SUB_FLAG_DEFAULT); uint8_t flags = CommandFlag::MQTT_SUB_FLAG_DEFAULT);
// callback function taking value, id and a json object for its output // callback function taking value, id and a json object for its output
static void add(const uint8_t device_type, static void add(const uint8_t device_type,
const __FlashStringHelper * cmd, const char * cmd,
const cmd_json_function_p cb, const cmd_json_function_p cb,
const __FlashStringHelper * description, const char * const * description,
uint8_t flags = CommandFlag::MQTT_SUB_FLAG_DEFAULT); uint8_t flags = CommandFlag::MQTT_SUB_FLAG_DEFAULT);
static void show_all(uuid::console::Shell & shell); static void show_all(uuid::console::Shell & shell);
static Command::CmdFunction * find_command(const uint8_t device_type, const char * cmd); static Command::CmdFunction * find_command(const uint8_t device_type, const char * cmd);

View File

@@ -31,29 +31,23 @@ using uuid::log::Level;
#define LOG_ERROR(...) logger_.err(__VA_ARGS__) #define LOG_ERROR(...) logger_.err(__VA_ARGS__)
// flash strings // flash strings
using uuid::flash_string_vector; using uuid::string_vector;
using uuid::read_flash_string; using string_vector = std::vector<const char *>;
#ifdef FPSTR #ifdef FPSTR
#undef FPSTR #undef FPSTR
#endif #endif
#define FJSON(x) x
// #define FJSON(x) F(x)
// clang-format off // clang-format off
#define MAKE_STR(string_name, string_literal) static constexpr const char * __str__##string_name = string_literal; #define FPSTR(pstr_pointer) pstr_pointer
#define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] = string_literal;
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
#define F_(string_name) FPSTR(__pstr__##string_name)
#define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = string_literal;
#define MAKE_PSTR_WORD(string_name) MAKE_PSTR(string_name, #string_name) #define MAKE_PSTR_WORD(string_name) MAKE_PSTR(string_name, #string_name)
#define F_(string_name) (__pstr__##string_name)
#define FL_(list_name) (__pstr__L_##list_name) #define FL_(list_name) (__pstr__L_##list_name)
#define MAKE_PSTR_LIST(list_name, ...) static const __FlashStringHelper * const __pstr__L_##list_name[] PROGMEM = {__VA_ARGS__, nullptr}; #define MAKE_PSTR_LIST(list_name, ...) static const char * const __pstr__L_##list_name[] = {__VA_ARGS__, nullptr};
#define MAKE_PSTR_ENUM(enum_name, ...) static const __FlashStringHelper * const * __pstr__L_##enum_name[] PROGMEM = {__VA_ARGS__, nullptr}; #define MAKE_PSTR_ENUM(enum_name, ...) static const char * const * __pstr__L_##enum_name[] = {__VA_ARGS__, nullptr};
// clang-format on // clang-format on

View File

@@ -45,26 +45,26 @@ EMSESPShell::EMSESPShell()
} }
void EMSESPShell::started() { void EMSESPShell::started() {
logger().log(LogLevel::DEBUG, LogFacility::CONSOLE, F("User session opened on console %s"), console_name().c_str()); logger().log(LogLevel::DEBUG, LogFacility::CONSOLE, ("User session opened on console %s"), console_name().c_str());
} }
void EMSESPShell::stopped() { void EMSESPShell::stopped() {
if (has_flags(CommandFlags::ADMIN)) { if (has_flags(CommandFlags::ADMIN)) {
logger().log(LogLevel::DEBUG, LogFacility::AUTH, F("su session closed on console %s"), console_name().c_str()); logger().log(LogLevel::DEBUG, LogFacility::AUTH, ("su session closed on console %s"), console_name().c_str());
} }
logger().log(LogLevel::DEBUG, LogFacility::CONSOLE, F("User session closed on console %s"), console_name().c_str()); logger().log(LogLevel::DEBUG, LogFacility::CONSOLE, ("User session closed on console %s"), console_name().c_str());
} }
// show welcome banner // show welcome banner
// this is one of the first functions called when the shell is started // this is one of the first functions called when the shell is started
void EMSESPShell::display_banner() { void EMSESPShell::display_banner() {
println(); println();
printfln(F("┌──────────────────────────────────────┐")); printfln("┌──────────────────────────────────────┐");
printfln(F("│ %sEMS-ESP version %-10s%s │"), COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF); printfln("│ %sEMS-ESP version %-10s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF);
printfln(F("│ %s%shttps://github.com/emsesp/EMS-ESP32%s │"), COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET); printfln("│ %s%shttps://github.com/emsesp/EMS-ESP32%s │", COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET);
printfln(F("│ │")); printfln("│ │");
printfln(F("│ type %shelp%s to show available commands │"), COLOR_UNDERLINE, COLOR_RESET); printfln("│ type %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET);
printfln(F("└──────────────────────────────────────┘")); printfln("└──────────────────────────────────────┘");
println(); println();
// set console name // set console name
@@ -100,9 +100,9 @@ void EMSESPShell::add_console_commands() {
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(show)}, string_vector{F_(show)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.printfln(F("%s%sEMS-ESP version %s%s"), COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_RESET); shell.printfln("%s%sEMS-ESP version %s%s", COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_RESET);
shell.println(); shell.println();
EMSESP::show_device_values(shell); EMSESP::show_device_values(shell);
EMSESP::show_sensor_values(shell); EMSESP::show_sensor_values(shell);
@@ -110,36 +110,36 @@ void EMSESPShell::add_console_commands() {
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(show), F_(devices)}, string_vector{F_(show), F_(devices)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_devices(shell); }); [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_devices(shell); });
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(show), F_(ems)}, string_vector{F_(show), F_(ems)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_ems(shell); }); [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_ems(shell); });
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(show), F_(values)}, string_vector{F_(show), F_(values)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_device_values(shell); }); [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_device_values(shell); });
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(show), F_(mqtt)}, string_vector{F_(show), F_(mqtt)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); }); [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); });
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(show), F_(commands)}, string_vector{F_(show), F_(commands)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Command::show_all(shell); }); [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Command::show_all(shell); });
commands->add_command( commands->add_command(
ShellContext::MAIN, ShellContext::MAIN,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(bus_id)}, string_vector{F_(set), F_(bus_id)},
flash_string_vector{F_(deviceid_mandatory)}, string_vector{F_(deviceid_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t device_id = Helpers::hextoint(arguments.front().c_str()); uint8_t device_id = Helpers::hextoint(arguments.front().c_str());
if ((device_id == 0x0B) || (device_id == 0x0D) || (device_id == 0x0A) || (device_id == 0x0F) || (device_id == 0x12)) { if ((device_id == 0x0B) || (device_id == 0x0D) || (device_id == 0x0A) || (device_id == 0x0F) || (device_id == 0x12)) {
@@ -151,24 +151,24 @@ void EMSESPShell::add_console_commands() {
}, },
"local"); "local");
} else { } else {
shell.println(F("Must be 0B, 0D, 0A, 0F or 12")); shell.println("Must be 0B, 0D, 0A, 0F or 12");
} }
}, },
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> { [](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{ return std::vector<std::string>{
read_flash_string(F("0B")), ("0B"),
read_flash_string(F("0D")), ("0D"),
read_flash_string(F("0A")), ("0A"),
read_flash_string(F("0F")), ("0F"),
read_flash_string(F("12")), ("12"),
}; };
}); });
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(tx_mode)}, string_vector{F_(set), F_(tx_mode)},
flash_string_vector{F_(n_mandatory)}, string_vector{F_(n_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t tx_mode = std::strtol(arguments[0].c_str(), nullptr, 10); uint8_t tx_mode = std::strtol(arguments[0].c_str(), nullptr, 10);
// save the tx_mode // save the tx_mode
@@ -183,13 +183,13 @@ void EMSESPShell::add_console_commands() {
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(scan), F_(devices)}, string_vector{F_(scan), F_(devices)},
flash_string_vector{F_(deep_optional)}, string_vector{F_(deep_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) { if (arguments.size() == 0) {
EMSESP::scan_devices(); EMSESP::scan_devices();
} else { } else {
shell.printfln(F("Performing a deep scan...")); shell.printfln("Performing a deep scan...");
EMSESP::clear_all_devices(); EMSESP::clear_all_devices();
std::vector<uint8_t> Device_Ids; std::vector<uint8_t> Device_Ids;
@@ -222,10 +222,10 @@ void EMSESPShell::add_console_commands() {
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(set)}, string_vector{F_(set)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings & settings) {
shell.printfln(F("Language: %s"), settings.locale.c_str()); shell.printfln("Language: %s", settings.locale.c_str());
shell.printfln(F_(tx_mode_fmt), settings.tx_mode); shell.printfln(F_(tx_mode_fmt), settings.tx_mode);
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id); shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
shell.printfln(F_(board_profile_fmt), settings.board_profile.c_str()); shell.printfln(F_(board_profile_fmt), settings.board_profile.c_str());
@@ -234,13 +234,13 @@ void EMSESPShell::add_console_commands() {
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(read)}, string_vector{F_(read)},
flash_string_vector{F_(deviceid_mandatory), F_(typeid_mandatory), F_(offset_optional), F_(length_optional)}, string_vector{F_(deviceid_mandatory), F_(typeid_mandatory), F_(offset_optional), F_(length_optional)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) { [=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint8_t device_id = Helpers::hextoint(arguments.front().c_str()); uint8_t device_id = Helpers::hextoint(arguments.front().c_str());
if (!EMSESP::valid_device(device_id)) { if (!EMSESP::valid_device(device_id)) {
shell.printfln(F("Invalid deviceID")); shell.printfln("Invalid deviceID");
return; return;
} }
@@ -261,31 +261,32 @@ void EMSESPShell::add_console_commands() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(set), F_(timeout)}, string_vector{F_(set), F_(timeout)},
flash_string_vector{F_(n_mandatory)}, string_vector{F_(n_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
uint16_t value = Helpers::atoint(arguments.front().c_str()); uint16_t value = Helpers::atoint(arguments.front().c_str());
telnet_.initial_idle_timeout(value * 60); telnet_.initial_idle_timeout(value * 60);
shell.printfln(F("Telnet timeout set to %d minutes"), value); shell.printfln("Telnet timeout set to %d minutes", value);
}); });
#endif #endif
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(watch)}, string_vector{F_(watch)},
flash_string_vector{F_(watch_format_optional), F_(watchid_optional)}, string_vector{F_(watch_format_optional), F_(watchid_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
uint16_t watch_id = WATCH_ID_NONE; uint16_t watch_id = WATCH_ID_NONE;
// only use english commands, not the translations
if (!arguments.empty()) { if (!arguments.empty()) {
// get raw/pretty // get raw/pretty
if (arguments[0] == read_flash_string(F_(raw))) { if (arguments[0] == (F_(raw))) {
EMSESP::watch(EMSESP::WATCH_RAW); // raw EMSESP::watch(EMSESP::WATCH_RAW); // raw
} else if (arguments[0] == Helpers::translated_word(FL_(on), true) || arguments[0] == read_flash_string(FL_(on)[0])) { } else if (arguments[0] == (FL_(on)[0])) {
EMSESP::watch(EMSESP::WATCH_ON); // on EMSESP::watch(EMSESP::WATCH_ON); // on
} else if (arguments[0] == Helpers::translated_word(FL_(off), true) || arguments[0] == read_flash_string(FL_(off)[0])) { } else if (arguments[0] == (FL_(off)[0])) {
EMSESP::watch(EMSESP::WATCH_OFF); // off EMSESP::watch(EMSESP::WATCH_OFF); // off
} else if (arguments[0] == Helpers::translated_word(FL_(unknown), true) || arguments[0] == read_flash_string(FL_(unknown)[0])) { } else if (arguments[0] == (FL_(unknown)[0])) {
EMSESP::watch(EMSESP::WATCH_UNKNOWN); // unknown EMSESP::watch(EMSESP::WATCH_UNKNOWN); // unknown
watch_id = WATCH_ID_NONE; watch_id = WATCH_ID_NONE;
} else { } else {
@@ -305,43 +306,43 @@ void EMSESPShell::add_console_commands() {
EMSESP::watch_id(watch_id); EMSESP::watch_id(watch_id);
} else { } else {
shell.printfln(F("Invalid: use watch raw|on|off|unknown|id [id]")); shell.printfln("Invalid: use watch raw|on|off|unknown|id [id]");
return; return;
} }
uint8_t watch = EMSESP::watch(); uint8_t watch = EMSESP::watch();
if (watch == EMSESP::WATCH_OFF) { if (watch == EMSESP::WATCH_OFF) {
shell.printfln(F("Watching telegrams is off")); shell.printfln("Watching telegrams is off");
return; return;
} }
// if logging is off, the watch won't show anything, show force it back to NOTICE // if logging is off, the watch won't show anything, show force it back to NOTICE
if (shell.log_level() < Level::NOTICE) { if (shell.log_level() < Level::NOTICE) {
shell.log_level(Level::NOTICE); shell.log_level(Level::NOTICE);
shell.printfln(F("Setting log level to Notice")); shell.printfln("Setting log level to Notice");
} }
if (watch == EMSESP::WATCH_ON) { if (watch == EMSESP::WATCH_ON) {
shell.printfln(F("Watching incoming telegrams, displayed in decoded format")); shell.printfln("Watching incoming telegrams, displayed in decoded format");
} else if (watch == EMSESP::WATCH_RAW) { } else if (watch == EMSESP::WATCH_RAW) {
shell.printfln(F("Watching incoming telegrams, displayed as raw bytes")); // WATCH_RAW shell.printfln("Watching incoming telegrams, displayed as raw bytes"); // WATCH_RAW
} else { } else {
shell.printfln(F("Watching unknown telegrams")); // WATCH_UNKNOWN shell.printfln("Watching unknown telegrams"); // WATCH_UNKNOWN
} }
watch_id = EMSESP::watch_id(); watch_id = EMSESP::watch_id();
if (watch_id > 0x80) { if (watch_id > 0x80) {
shell.printfln(F("Filtering only telegrams that match a telegram type of 0x%02X"), watch_id); shell.printfln("Filtering only telegrams that match a telegram type of 0x%02X", watch_id);
} else if (watch_id != WATCH_ID_NONE) { } else if (watch_id != WATCH_ID_NONE) {
shell.printfln(F("Filtering only telegrams that match a deviceID or telegram type of 0x%02X"), watch_id); shell.printfln("Filtering only telegrams that match a deviceID or telegram type of 0x%02X", watch_id);
} }
}); });
commands->add_command( commands->add_command(
ShellContext::MAIN, ShellContext::MAIN,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(call)}, string_vector{F_(call)},
flash_string_vector{F_(device_type_optional), F_(cmd_optional), F_(data_optional), F_(id_optional)}, string_vector{F_(device_type_optional), F_(cmd_optional), F_(data_optional), F_(id_optional)},
[&](Shell & shell, const std::vector<std::string> & arguments) { [&](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.empty()) { if (arguments.empty()) {
Command::show_all(shell); // list options Command::show_all(shell); // list options
@@ -351,7 +352,7 @@ void EMSESPShell::add_console_commands() {
// validate the device_type // validate the device_type
uint8_t device_type = EMSdevice::device_name_2_device_type(arguments[0].c_str()); uint8_t device_type = EMSdevice::device_name_2_device_type(arguments[0].c_str());
if (!Command::device_has_commands(device_type)) { if (!Command::device_has_commands(device_type)) {
shell.print(F("Invalid device. Available devices are: ")); shell.print("Invalid device. Available devices are: ");
Command::show_devices(shell); Command::show_devices(shell);
return; return;
} }
@@ -359,7 +360,7 @@ void EMSESPShell::add_console_commands() {
// validate that a command is present // validate that a command is present
if (arguments.size() < 2) { if (arguments.size() < 2) {
shell.print(F("Missing command. Available commands are: ")); shell.print("Missing command. Available commands are: ");
Command::show(shell, device_type, false); // non-verbose mode Command::show(shell, device_type, false); // non-verbose mode
return; return;
} }
@@ -399,11 +400,11 @@ void EMSESPShell::add_console_commands() {
} }
if (return_code == CommandRet::NOT_FOUND) { if (return_code == CommandRet::NOT_FOUND) {
shell.println(F("Unknown command")); shell.println("Unknown command");
shell.print(F("Available commands are: ")); shell.print("Available commands are: ");
Command::show(shell, device_type, false); // non-verbose mode Command::show(shell, device_type, false); // non-verbose mode
} else if (return_code != CommandRet::OK) { } else if (return_code != CommandRet::OK) {
shell.printfln(F("Bad syntax (error code %d)"), return_code); shell.printfln(("Bad syntax (error code %d)"), return_code);
} }
}, },
[&](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) -> std::vector<std::string> { [&](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) -> std::vector<std::string> {
@@ -424,7 +425,7 @@ void EMSESPShell::add_console_commands() {
if (Command::device_has_commands(device_type)) { if (Command::device_has_commands(device_type)) {
for (const auto & cf : Command::commands()) { for (const auto & cf : Command::commands()) {
if (cf.device_type_ == device_type) { if (cf.device_type_ == device_type) {
command_list.emplace_back(read_flash_string(cf.cmd_)); command_list.emplace_back((cf.cmd_));
} }
} }
return command_list; return command_list;
@@ -448,8 +449,8 @@ void Console::load_standard_commands(unsigned int context) {
// create commands test and t // create commands test and t
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F("test")}, string_vector{("test")},
flash_string_vector{F_(name_optional), F_(data_optional)}, string_vector{F_(name_optional), F_(data_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.empty()) { if (arguments.empty()) {
Test::run_test(shell, "default"); Test::run_test(shell, "default");
@@ -460,7 +461,7 @@ void Console::load_standard_commands(unsigned int context) {
} }
}); });
EMSESPShell::commands->add_command(context, CommandFlags::USER, flash_string_vector{F("t")}, [](Shell & shell, const std::vector<std::string> & arguments) { EMSESPShell::commands->add_command(context, CommandFlags::USER, string_vector{("t")}, [](Shell & shell, const std::vector<std::string> & arguments) {
Test::run_test(shell, "default"); Test::run_test(shell, "default");
}); });
#endif #endif
@@ -468,8 +469,8 @@ void Console::load_standard_commands(unsigned int context) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(debug)}, string_vector{F_(debug)},
flash_string_vector{F_(name_optional)}, string_vector{F_(name_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.empty()) { if (arguments.empty()) {
Test::debug(shell, "default"); Test::debug(shell, "default");
@@ -482,8 +483,8 @@ void Console::load_standard_commands(unsigned int context) {
EMSESPShell::commands->add_command( EMSESPShell::commands->add_command(
context, context,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(log)}, string_vector{F_(log)},
flash_string_vector{F_(log_level_optional)}, string_vector{F_(log_level_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
if (!arguments.empty()) { if (!arguments.empty()) {
uuid::log::Level level; uuid::log::Level level;
@@ -494,7 +495,7 @@ void Console::load_standard_commands(unsigned int context) {
return; return;
} }
} else { } else {
shell.print(F("levels: ")); shell.print("levels: ");
std::vector<std::string> v = uuid::log::levels_lowercase(); std::vector<std::string> v = uuid::log::levels_lowercase();
size_t i = v.size(); size_t i = v.size();
while (i--) { while (i--) {
@@ -511,22 +512,22 @@ void Console::load_standard_commands(unsigned int context) {
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(help)}, string_vector{F_(help)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.print_all_available_commands(); shell.print_all_available_commands();
}); });
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(exit)}, string_vector{F_(exit)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { shell.stop(); }); [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { shell.stop(); });
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(su)}, string_vector{F_(su)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
auto become_admin = [](Shell & shell) { auto become_admin = [](Shell & shell) {
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("su session opened on console")); shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, ("su session opened on console"));
shell.add_flags(CommandFlags::ADMIN); shell.add_flags(CommandFlags::ADMIN);
}; };
@@ -542,8 +543,8 @@ void Console::load_standard_commands(unsigned int context) {
become_admin(shell); become_admin(shell);
} else { } else {
shell.delay_until(now + INVALID_PASSWORD_DELAY_MS, [](Shell & shell) { shell.delay_until(now + INVALID_PASSWORD_DELAY_MS, [](Shell & shell) {
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("Invalid su password on console")); shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, ("Invalid su password on console"));
shell.println(F("su: incorrect password")); shell.println("su: incorrect password");
}); });
} }
}); });
@@ -557,21 +558,21 @@ void Console::load_standard_commands(unsigned int context) {
void Console::load_system_commands(unsigned int context) { void Console::load_system_commands(unsigned int context) {
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(restart)}, string_vector{F_(restart)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { [](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.system_restart(); EMSESP::system_.system_restart();
}); });
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(wifi), F_(reconnect)}, string_vector{F_(wifi), F_(reconnect)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { [](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.wifi_reconnect(); EMSESP::system_.wifi_reconnect();
}); });
EMSESPShell::commands->add_command(ShellContext::MAIN, EMSESPShell::commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(format)}, string_vector{F_(format)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) { shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) {
if (completed) { if (completed) {
@@ -579,7 +580,7 @@ void Console::load_system_commands(unsigned int context) {
if (securitySettings.jwtSecret.equals(password.c_str())) { if (securitySettings.jwtSecret.equals(password.c_str())) {
EMSESP::system_.format(shell); EMSESP::system_.format(shell);
} else { } else {
shell.println(F("incorrect password")); shell.println("incorrect password");
} }
}); });
} }
@@ -588,7 +589,7 @@ void Console::load_system_commands(unsigned int context) {
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(passwd)}, string_vector{F_(passwd)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) { shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) { if (completed) {
@@ -602,9 +603,9 @@ void Console::load_system_commands(unsigned int context) {
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}, },
"local"); "local");
shell.println(F("su password updated")); shell.println("su password updated");
} else { } else {
shell.println(F("Passwords do not match")); shell.println("Passwords do not match");
} }
} }
}); });
@@ -614,7 +615,7 @@ void Console::load_system_commands(unsigned int context) {
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::USER, CommandFlags::USER,
flash_string_vector{F_(show), F_(system)}, string_vector{F_(show), F_(system)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.show_system(shell); EMSESP::system_.show_system(shell);
shell.println(); shell.println();
@@ -622,8 +623,8 @@ void Console::load_system_commands(unsigned int context) {
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(hostname)}, string_vector{F_(set), F_(hostname)},
flash_string_vector{F_(name_mandatory)}, string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
shell.println("The network connection will be reset..."); shell.println("The network connection will be reset...");
Shell::loop_all(); Shell::loop_all();
@@ -638,8 +639,8 @@ void Console::load_system_commands(unsigned int context) {
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(wifi), F_(ssid)}, string_vector{F_(set), F_(wifi), F_(ssid)},
flash_string_vector{F_(name_mandatory)}, string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
EMSESP::esp8266React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) { EMSESP::esp8266React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) {
networkSettings.ssid = arguments.front().c_str(); networkSettings.ssid = arguments.front().c_str();
@@ -651,8 +652,8 @@ void Console::load_system_commands(unsigned int context) {
// added by mvdp // added by mvdp
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F("mqtt"), F("subscribe")}, string_vector{("mqtt"), ("subscribe")},
flash_string_vector{F("<topic>")}, string_vector{("<topic>")},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
Mqtt::subscribe(arguments.front()); Mqtt::subscribe(arguments.front());
shell.println("subscribing"); shell.println("subscribing");
@@ -660,7 +661,7 @@ void Console::load_system_commands(unsigned int context) {
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(wifi), F_(password)}, string_vector{F_(set), F_(wifi), F_(password)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) { shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) { if (completed) {
@@ -675,7 +676,7 @@ void Console::load_system_commands(unsigned int context) {
}); });
shell.println("Use `wifi reconnect` to save and apply the new settings"); shell.println("Use `wifi reconnect` to save and apply the new settings");
} else { } else {
shell.println(F("Passwords do not match")); shell.println("Passwords do not match");
} }
} }
}); });
@@ -685,13 +686,13 @@ void Console::load_system_commands(unsigned int context) {
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(board_profile)}, string_vector{F_(set), F_(board_profile)},
flash_string_vector{F_(name_mandatory)}, string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> & arguments) {
std::vector<int8_t> data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode std::vector<int8_t> data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode
std::string board_profile = Helpers::toUpper(arguments.front()); std::string board_profile = Helpers::toUpper(arguments.front());
if (!EMSESP::system_.load_board_profile(data, board_profile)) { if (!EMSESP::system_.load_board_profile(data, board_profile)) {
shell.println(F("Invalid board profile (S32, E32, MH-ET, NODEMCU, OLIMEX, OLIMEXPOE, C3MINI, CUSTOM)")); shell.println(("Invalid board profile (S32, E32, MH-ET, NODEMCU, OLIMEX, OLIMEXPOE, C3MINI, CUSTOM)"));
return; return;
} }
EMSESP::webSettingsService.update( EMSESP::webSettingsService.update(
@@ -714,7 +715,7 @@ void Console::load_system_commands(unsigned int context) {
}); });
EMSESPShell::commands->add_command(context, EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(show), F_(users)}, string_vector{F_(show), F_(users)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::system_.show_users(shell); EMSESP::system_.show_users(shell);
}); });
@@ -730,14 +731,14 @@ std::string EMSESPShell::prompt_suffix() {
} }
void EMSESPShell::end_of_transmission() { void EMSESPShell::end_of_transmission() {
invoke_command(read_flash_string(F_(exit))); invoke_command((F_(exit)));
} }
EMSESPStreamConsole::EMSESPStreamConsole(Stream & stream, bool local) EMSESPStreamConsole::EMSESPStreamConsole(Stream & stream, bool local)
: uuid::console::Shell(commands, ShellContext::MAIN, local ? (CommandFlags::USER | CommandFlags::LOCAL) : CommandFlags::USER) : uuid::console::Shell(commands, ShellContext::MAIN, local ? (CommandFlags::USER | CommandFlags::LOCAL) : CommandFlags::USER)
, uuid::console::StreamConsole(stream) , uuid::console::StreamConsole(stream)
, EMSESPShell() , EMSESPShell()
, name_(read_flash_string(F("Serial"))) , name_("Serial")
, pty_(SIZE_MAX) , pty_(SIZE_MAX)
, addr_() , addr_()
, port_(0) { , port_(0) {
@@ -763,14 +764,14 @@ EMSESPStreamConsole::EMSESPStreamConsole(Stream & stream, const IPAddress & addr
snprintf(text.data(), text.size(), "pty%u", (uint16_t)pty_); snprintf(text.data(), text.size(), "pty%u", (uint16_t)pty_);
name_ = text.data(); name_ = text.data();
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
logger().info(F("Allocated console %s for connection from [%s]:%u"), name_.c_str(), uuid::printable_to_string(addr_).c_str(), port_); logger().info("Allocated console %s for connection from [%s]:%u", name_.c_str(), uuid::printable_to_string(addr_).c_str(), port_);
#endif #endif
} }
EMSESPStreamConsole::~EMSESPStreamConsole() { EMSESPStreamConsole::~EMSESPStreamConsole() {
if (pty_ != SIZE_MAX) { if (pty_ != SIZE_MAX) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
logger().info(F("Shutdown console %s for connection from [%s]:%u"), name_.c_str(), uuid::printable_to_string(addr_).c_str(), port_); logger().info("Shutdown console %s for connection from [%s]:%u", name_.c_str(), uuid::printable_to_string(addr_).c_str(), port_);
#endif #endif
ptys_[pty_] = false; ptys_[pty_] = false;
ptys_.shrink_to_fit(); ptys_.shrink_to_fit();

View File

@@ -42,7 +42,7 @@ void DallasSensor::start() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
bus_.begin(dallas_gpio_); bus_.begin(dallas_gpio_);
LOG_INFO(F("Starting Dallas sensor service")); LOG_INFO("Starting Dallas sensor service");
#endif #endif
// Add API calls // Add API calls
@@ -50,12 +50,12 @@ void DallasSensor::start() {
EMSdevice::DeviceType::DALLASSENSOR, EMSdevice::DeviceType::DALLASSENSOR,
F_(info), F_(info),
[&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, id, output); }, [&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, id, output); },
F_(info_cmd)); FL_(info_cmd));
Command::add( Command::add(
EMSdevice::DeviceType::DALLASSENSOR, EMSdevice::DeviceType::DALLASSENSOR,
F_(commands), F_(commands),
[&](const char * value, const int8_t id, JsonObject & output) { return command_commands(value, id, output); }, [&](const char * value, const int8_t id, JsonObject & output) { return command_commands(value, id, output); },
F_(commands_cmd)); FL_(commands_cmd));
Mqtt::subscribe(EMSdevice::DeviceType::DALLASSENSOR, "dallasssensor/#", nullptr); // use empty function callback Mqtt::subscribe(EMSdevice::DeviceType::DALLASSENSOR, "dallasssensor/#", nullptr); // use empty function callback
} }
@@ -84,7 +84,7 @@ void DallasSensor::loop() {
if (state_ == State::IDLE) { if (state_ == State::IDLE) {
if (time_now - last_activity_ >= READ_INTERVAL_MS) { if (time_now - last_activity_ >= READ_INTERVAL_MS) {
#ifdef EMSESP_DEBUG_SENSOR #ifdef EMSESP_DEBUG_SENSOR
LOG_DEBUG(F("[DEBUG] Read sensor temperature")); LOG_DEBUG("[DEBUG] Read sensor temperature");
#endif #endif
if (bus_.reset() || parasite_) { if (bus_.reset() || parasite_) {
YIELD; YIELD;
@@ -99,7 +99,7 @@ void DallasSensor::loop() {
if (++scanretry_ > SCAN_MAX) { // every 30 sec if (++scanretry_ > SCAN_MAX) { // every 30 sec
scanretry_ = 0; scanretry_ = 0;
#ifdef EMSESP_DEBUG_SENSOR #ifdef EMSESP_DEBUG_SENSOR
LOG_ERROR(F("Bus reset failed")); LOG_ERROR("Bus reset failed");
#endif #endif
for (auto & sensor : sensors_) { for (auto & sensor : sensors_) {
sensor.temperature_c = EMS_VALUE_SHORT_NOTSET; sensor.temperature_c = EMS_VALUE_SHORT_NOTSET;
@@ -112,13 +112,13 @@ void DallasSensor::loop() {
} else if (state_ == State::READING) { } else if (state_ == State::READING) {
if (temperature_convert_complete() && (time_now - last_activity_ > CONVERSION_MS)) { if (temperature_convert_complete() && (time_now - last_activity_ > CONVERSION_MS)) {
#ifdef EMSESP_DEBUG_SENSOR #ifdef EMSESP_DEBUG_SENSOR
LOG_DEBUG(F("Scanning for sensors")); LOG_DEBUG("Scanning for sensors");
#endif #endif
bus_.reset_search(); bus_.reset_search();
state_ = State::SCANNING; state_ = State::SCANNING;
} else if (time_now - last_activity_ > READ_TIMEOUT_MS) { } else if (time_now - last_activity_ > READ_TIMEOUT_MS) {
#ifdef EMSESP_DEBUG_SENSOR #ifdef EMSESP_DEBUG_SENSOR
LOG_WARNING(F("Dallas sensor read timeout")); LOG_WARNING("Dallas sensor read timeout");
#endif #endif
state_ = State::IDLE; state_ = State::IDLE;
sensorfails_++; sensorfails_++;
@@ -126,7 +126,7 @@ void DallasSensor::loop() {
} else if (state_ == State::SCANNING) { } else if (state_ == State::SCANNING) {
if (time_now - last_activity_ > SCAN_TIMEOUT_MS) { if (time_now - last_activity_ > SCAN_TIMEOUT_MS) {
#ifdef EMSESP_DEBUG_SENSOR #ifdef EMSESP_DEBUG_SENSOR
LOG_ERROR(F("Dallas sensor scan timeout")); LOG_ERROR("Dallas sensor scan timeout");
#endif #endif
state_ = State::IDLE; state_ = State::IDLE;
sensorfails_++; sensorfails_++;
@@ -181,12 +181,12 @@ void DallasSensor::loop() {
default: default:
sensorfails_++; sensorfails_++;
LOG_ERROR(F("Unknown dallas sensor %s"), Sensor(addr).id().c_str()); LOG_ERROR("Unknown dallas sensor %s", Sensor(addr).id().c_str());
break; break;
} }
} else { } else {
sensorfails_++; sensorfails_++;
LOG_ERROR(F("Invalid dallas sensor %s"), Sensor(addr).id().c_str()); LOG_ERROR("Invalid dallas sensor %s", Sensor(addr).id().c_str());
} }
} else { } else {
if (!parasite_) { if (!parasite_) {
@@ -204,7 +204,7 @@ void DallasSensor::loop() {
scancnt_ = 0; scancnt_ = 0;
} else if (scancnt_ == SCAN_START + 1) { // startup } else if (scancnt_ == SCAN_START + 1) { // startup
firstscan_ = sensors_.size(); firstscan_ = sensors_.size();
// LOG_DEBUG(F("Adding %d dallas sensor(s) from first scan"), firstscan_); // LOG_DEBUG(("Adding %d dallas sensor(s) from first scan"), firstscan_);
} else if ((scancnt_ <= 0) && (firstscan_ != sensors_.size())) { // check 2 times for no change of sensor # } else if ((scancnt_ <= 0) && (firstscan_ != sensors_.size())) { // check 2 times for no change of sensor #
scancnt_ = SCAN_START; scancnt_ = SCAN_START;
sensors_.clear(); // restart scaning and clear to get correct numbering sensors_.clear(); // restart scaning and clear to get correct numbering
@@ -230,7 +230,7 @@ bool DallasSensor::temperature_convert_complete() {
int16_t DallasSensor::get_temperature_c(const uint8_t addr[]) { int16_t DallasSensor::get_temperature_c(const uint8_t addr[]) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (!bus_.reset()) { if (!bus_.reset()) {
LOG_ERROR(F("Bus reset failed before reading scratchpad from %s"), Sensor(addr).id().c_str()); LOG_ERROR("Bus reset failed before reading scratchpad from %s", Sensor(addr).id().c_str());
return EMS_VALUE_SHORT_NOTSET; return EMS_VALUE_SHORT_NOTSET;
} }
YIELD; YIELD;
@@ -242,13 +242,13 @@ int16_t DallasSensor::get_temperature_c(const uint8_t addr[]) {
YIELD; YIELD;
if (!bus_.reset()) { if (!bus_.reset()) {
LOG_ERROR(F("Bus reset failed after reading scratchpad from %s"), Sensor(addr).id().c_str()); LOG_ERROR("Bus reset failed after reading scratchpad from %s", Sensor(addr).id().c_str());
return EMS_VALUE_SHORT_NOTSET; return EMS_VALUE_SHORT_NOTSET;
} }
YIELD; YIELD;
if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) { if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) {
LOG_WARNING(F("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from sensor %s"), LOG_WARNING("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from sensor %s",
scratchpad[0], scratchpad[0],
scratchpad[1], scratchpad[1],
scratchpad[2], scratchpad[2],
@@ -315,7 +315,7 @@ bool DallasSensor::update(const std::string & id, const std::string & name, int1
SensorCustomization.name = name; SensorCustomization.name = name;
SensorCustomization.offset = offset; SensorCustomization.offset = offset;
found = true; found = true;
LOG_DEBUG(F("Customizing existing sensor ID %s"), id.c_str()); LOG_DEBUG("Customizing existing sensor ID %s", id.c_str());
break; break;
} }
} }
@@ -325,7 +325,7 @@ bool DallasSensor::update(const std::string & id, const std::string & name, int1
newSensor.name = name; newSensor.name = name;
newSensor.offset = offset; newSensor.offset = offset;
settings.sensorCustomizations.push_back(newSensor); settings.sensorCustomizations.push_back(newSensor);
LOG_DEBUG(F("Adding new customization for sensor ID %s"), id.c_str()); LOG_DEBUG("Adding new customization for sensor ID %s", id.c_str());
} }
sensor.ha_registered = false; // it's changed so we may need to recreate the HA config sensor.ha_registered = false; // it's changed so we may need to recreate the HA config
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
@@ -360,14 +360,15 @@ bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject
} }
for (const auto & sensor : sensors_) { for (const auto & sensor : sensors_) {
char val[10];
if (id == -1) { // show number and id if (id == -1) { // show number and id
JsonObject dataSensor = output.createNestedObject(sensor.name()); JsonObject dataSensor = output.createNestedObject(sensor.name());
dataSensor["id"] = sensor.id(); dataSensor["id"] = sensor.id();
if (Helpers::hasValue(sensor.temperature_c)) { if (Helpers::hasValue(sensor.temperature_c)) {
dataSensor["temp"] = Helpers::transformNumFloat((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0); dataSensor["temp"] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
} }
} else if (Helpers::hasValue(sensor.temperature_c)) { } else if (Helpers::hasValue(sensor.temperature_c)) {
output[sensor.name()] = Helpers::transformNumFloat((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0); output[sensor.name()] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
} }
} }
@@ -392,14 +393,17 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i
if (strcmp(command_s, sensor.name().c_str()) == 0) { if (strcmp(command_s, sensor.name().c_str()) == 0) {
output["id"] = sensor.id(); output["id"] = sensor.id();
output["name"] = sensor.name(); output["name"] = sensor.name();
char val[10];
if (Helpers::hasValue(sensor.temperature_c)) { if (Helpers::hasValue(sensor.temperature_c)) {
output["value"] = Helpers::transformNumFloat((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0); output["value"] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
} }
output["type"] = F_(number); output["type"] = F_(number);
output["min"] = Helpers::transformNumFloat(-55, 0, EMSESP::system_.fahrenheit() ? 2 : 0); output["min"] = serialized(Helpers::render_value(val, -55, 0, EMSESP::system_.fahrenheit() ? 2 : 0));
output["max"] = Helpers::transformNumFloat(125, 0, EMSESP::system_.fahrenheit() ? 2 : 0); output["max"] = serialized(Helpers::render_value(val, 125, 0, EMSESP::system_.fahrenheit() ? 2 : 0));
output["uom"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES); output["uom"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
output["writeable"] = false; output["writeable"] = false;
// if we're filtering on an attribute, go find it // if we're filtering on an attribute, go find it
if (attribute_s) { if (attribute_s) {
if (output.containsKey(attribute_s)) { if (output.containsKey(attribute_s)) {
@@ -426,9 +430,9 @@ void DallasSensor::publish_sensor(const Sensor & sensor) {
if (Mqtt::publish_single()) { if (Mqtt::publish_single()) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) { if (Mqtt::publish_single2cmd()) {
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(dallassensor)).c_str(), sensor.name().c_str()); snprintf(topic, sizeof(topic), "%s/%s", (F_(dallassensor)), sensor.name().c_str());
} else { } else {
snprintf(topic, sizeof(topic), "%s%s/%s", read_flash_string(F_(dallassensor)).c_str(), "_data", sensor.name().c_str()); snprintf(topic, sizeof(topic), "%s%s/%s", (F_(dallassensor)), "_data", sensor.name().c_str());
} }
char payload[10]; char payload[10];
Mqtt::publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0)); Mqtt::publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
@@ -441,7 +445,7 @@ void DallasSensor::remove_ha_topic(const std::string & id) {
return; return;
} }
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
LOG_DEBUG(F("Removing HA config for temperature sensor ID %s"), id.c_str()); LOG_DEBUG("Removing HA config for temperature sensor ID %s", id.c_str());
#endif #endif
// use '_' as HA doesn't like '-' in the topic name // use '_' as HA doesn't like '-' in the topic name
std::string sensorid = id; std::string sensorid = id;
@@ -469,24 +473,25 @@ void DallasSensor::publish_values(const bool force) {
for (auto & sensor : sensors_) { for (auto & sensor : sensors_) {
bool has_value = Helpers::hasValue(sensor.temperature_c); bool has_value = Helpers::hasValue(sensor.temperature_c);
char val[10];
if (Mqtt::is_nested()) { if (Mqtt::is_nested()) {
JsonObject dataSensor = doc.createNestedObject(sensor.id()); JsonObject dataSensor = doc.createNestedObject(sensor.id());
dataSensor["name"] = sensor.name(); dataSensor["name"] = sensor.name();
if (has_value) { if (has_value) {
dataSensor["temp"] = Helpers::transformNumFloat((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0); dataSensor["temp"] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
} }
} else if (has_value) { } else if (has_value) {
doc[sensor.name()] = Helpers::transformNumFloat((float)(sensor.temperature_c), 10, EMSESP::system_.fahrenheit() ? 2 : 0); doc[sensor.name()] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
} }
// create the HA MQTT config // create the HA MQTT config
// to e.g. homeassistant/sensor/ems-esp/dallassensor_28-233D-9497-0C03/config // to e.g. homeassistant/sensor/ems-esp/dallassensor_28-233D-9497-0C03/config
if (Mqtt::ha_enabled()) { if (Mqtt::ha_enabled()) {
if (!sensor.ha_registered || force) { if (!sensor.ha_registered || force) {
LOG_DEBUG(F("Recreating HA config for sensor ID %s"), sensor.id().c_str()); LOG_DEBUG("Recreating HA config for sensor ID %s", sensor.id().c_str());
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config; StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
config["dev_cla"] = FJSON("temperature"); config["dev_cla"] = "temperature";
char stat_t[50]; char stat_t[50];
snprintf(stat_t, sizeof(stat_t), "%s/dallassensor_data", Mqtt::base().c_str()); snprintf(stat_t, sizeof(stat_t), "%s/dallassensor_data", Mqtt::base().c_str());
@@ -530,7 +535,7 @@ void DallasSensor::publish_values(const bool force) {
} }
} }
Mqtt::publish(F("dallassensor_data"), doc.as<JsonObject>()); Mqtt::publish("dallassensor_data", doc.as<JsonObject>());
} }
@@ -574,7 +579,7 @@ bool DallasSensor::Sensor::apply_customization() {
if (!sensors.empty()) { if (!sensors.empty()) {
for (const auto & sensor : sensors) { for (const auto & sensor : sensors) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
LOG_DEBUG(F("Loading customization for dallas sensor %s"), sensor.id.c_str()); LOG_DEBUG("Loading customization for dallas sensor %s", sensor.id.c_str());
#endif #endif
if (id_ == sensor.id) { if (id_ == sensor.id) {
set_name(sensor.name); set_name(sensor.name);

View File

@@ -24,140 +24,142 @@
*/ */
// Boilers - 0x08 // Boilers - 0x08
{ 64, DeviceType::BOILER, F("BK13/BK15/Smartline/GB1x2"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 64, DeviceType::BOILER, "BK13/BK15/Smartline/GB1x2", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 72, DeviceType::BOILER, F("GB125/MC10"), DeviceFlags::EMS_DEVICE_FLAG_EMS}, { 72, DeviceType::BOILER, "GB125/MC10", DeviceFlags::EMS_DEVICE_FLAG_EMS},
{ 81, DeviceType::BOILER, F("Cascade CM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 81, DeviceType::BOILER, "Cascade CM10", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 84, DeviceType::BOILER, F("Logamax Plus GB022"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 84, DeviceType::BOILER, "Logamax Plus GB022", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 95, DeviceType::BOILER, F("Condens 2500/Logamax/Logomatic/Cerapur Top/Greenstar/Generic HT3"), DeviceFlags::EMS_DEVICE_FLAG_HT3}, { 95, DeviceType::BOILER, "Condens 2500/Logamax/Logomatic/Cerapur Top/Greenstar/Generic HT3", DeviceFlags::EMS_DEVICE_FLAG_HT3},
{115, DeviceType::BOILER, F("Topline/GB162"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {115, DeviceType::BOILER, "Topline/GB162", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{122, DeviceType::BOILER, F("Proline"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {122, DeviceType::BOILER, "Proline", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{123, DeviceType::BOILER, F("GBx72/Trendline/Cerapur/Greenstar Si/27i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {123, DeviceType::BOILER, "GBx72/Trendline/Cerapur/Greenstar Si/27i", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{131, DeviceType::BOILER, F("GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {131, DeviceType::BOILER, "GB212", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{132, DeviceType::BOILER, F("GC7000F"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {132, DeviceType::BOILER, "GC7000F", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{133, DeviceType::BOILER, F("Logano GB125/KB195i/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {133, DeviceType::BOILER, "Logano GB125/KB195i/Logamatic MC110", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{154, DeviceType::BOILER, F("Greenstar 30Ri Compact"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {154, DeviceType::BOILER, "Greenstar 30Ri Compact", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{167, DeviceType::BOILER, F("Cerapur Aero"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {167, DeviceType::BOILER, "Cerapur Aero", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{168, DeviceType::BOILER, F("Hybrid Heatpump"), DeviceFlags::EMS_DEVICE_FLAG_HYBRID}, {168, DeviceType::BOILER, "Hybrid Heatpump", DeviceFlags::EMS_DEVICE_FLAG_HYBRID},
{170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {170, DeviceType::BOILER, "Logano GB212", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{172, DeviceType::BOILER, F("Enviline/Compress 6000AW/Hybrid 7000iAW/SupraEco/Geo 5xx"), DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP}, {172, DeviceType::BOILER, "Enviline/Compress 6000AW/Hybrid 7000iAW/SupraEco/Geo 5xx", DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP},
{173, DeviceType::BOILER, F("Geo 5xx"), DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP}, {173, DeviceType::BOILER, "Geo 5xx", DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP},
{195, DeviceType::BOILER, F("Condens 5000i/Greenstar 8000"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {195, DeviceType::BOILER, "Condens 5000i/Greenstar 8000", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{203, DeviceType::BOILER, F("Logamax U122/Cerapur"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {203, DeviceType::BOILER, "Logamax U122/Cerapur", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{206, DeviceType::BOILER, F("Ecomline Excellent"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {206, DeviceType::BOILER, "Ecomline Excellent", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{208, DeviceType::BOILER, F("Logamax Plus/GB192/Condens GC9000"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {208, DeviceType::BOILER, "Logamax Plus/GB192/Condens GC9000", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{210, DeviceType::BOILER, F("Cascade MC400"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {210, DeviceType::BOILER, "Cascade MC400", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{211, DeviceType::BOILER, F("EasyControl Adapter"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {211, DeviceType::BOILER, "EasyControl Adapter", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{228, DeviceType::BOILER, F("Alternative Heatsource"), DeviceFlags::EMS_DEVICE_FLAG_AM200}, {228, DeviceType::BOILER, "Alternative Heatsource", DeviceFlags::EMS_DEVICE_FLAG_AM200},
{234, DeviceType::BOILER, F("Logamax Plus GB122/Condense 2300"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {234, DeviceType::BOILER, "Logamax Plus GB122/Condense 2300", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Controllers - 0x09 / 0x10 / 0x50 // Controllers - 0x09 / 0x10 / 0x50
{ 68, DeviceType::CONTROLLER, F("BC10/RFM20"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 { 68, DeviceType::CONTROLLER, "BC10/RFM20", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{ 81, DeviceType::CONTROLLER, F("CM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 81, DeviceType::CONTROLLER, "CM10", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 84, DeviceType::CONTROLLER, F("GB022"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 84, DeviceType::CONTROLLER, "GB022", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{ 89, DeviceType::CONTROLLER, F("BC10 GB142"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 { 89, DeviceType::CONTROLLER, "BC10 GB142", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{ 95, DeviceType::CONTROLLER, F("HT3"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 { 95, DeviceType::CONTROLLER, "HT3", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{114, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {114, DeviceType::CONTROLLER, "BC10", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{125, DeviceType::CONTROLLER, F("BC25"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {125, DeviceType::CONTROLLER, "BC25", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{152, DeviceType::CONTROLLER, F("Controller"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {152, DeviceType::CONTROLLER, "Controller", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{168, DeviceType::CONTROLLER, F("Hybrid Heatpump"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {168, DeviceType::CONTROLLER, "Hybrid Heatpump", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{169, DeviceType::CONTROLLER, F("BC40"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {169, DeviceType::CONTROLLER, "BC40", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{190, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {190, DeviceType::CONTROLLER, "BC10", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{194, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {194, DeviceType::CONTROLLER, "BC10", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{206, DeviceType::CONTROLLER, F("Ecomline"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {206, DeviceType::CONTROLLER, "Ecomline", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{207, DeviceType::CONTROLLER, F("Sense II/CS200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x10 {207, DeviceType::CONTROLLER, "Sense II/CS200", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x10
{209, DeviceType::CONTROLLER, F("ErP"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {209, DeviceType::CONTROLLER, "ErP", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{218, DeviceType::CONTROLLER, F("M200/RFM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x50 {218, DeviceType::CONTROLLER, "M200/RFM200", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x50
{224, DeviceType::CONTROLLER, F("9000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {220, DeviceType::CONTROLLER, "BC30", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x16
{229, DeviceType::CONTROLLER, F("8700i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {224, DeviceType::CONTROLLER, "9000i", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{230, DeviceType::CONTROLLER, F("BC Base"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {229, DeviceType::CONTROLLER, "8700i", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{240, DeviceType::CONTROLLER, F("Rego 3000"), DeviceFlags::EMS_DEVICE_FLAG_IVT}, // 0x09 {230, DeviceType::CONTROLLER, "BC Base", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{241, DeviceType::CONTROLLER, F("Condens 5000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {240, DeviceType::CONTROLLER, "Rego 3000", DeviceFlags::EMS_DEVICE_FLAG_IVT}, // 0x09
{241, DeviceType::CONTROLLER, "Condens 5000i", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
// Thermostat - not currently supporting write operations, like the Easy/100 types - 0x18 // Thermostat - not currently supporting write operations, like the Easy/100 types - 0x18
{202, DeviceType::THERMOSTAT, F("Logamatic TC100/Moduline Easy"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write {202, DeviceType::THERMOSTAT, "Logamatic TC100/Moduline Easy", DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
{203, DeviceType::THERMOSTAT, F("EasyControl CT200"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write {203, DeviceType::THERMOSTAT, "EasyControl CT200", DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
// Thermostat - Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18 / 0x19-0x1B for hc2-4 / 0x38 // Thermostat - Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18 / 0x19-0x1B for hc2-4 / 0x38
{ 65, DeviceType::THERMOSTAT, F("RC10"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N},// 0x17 { 65, DeviceType::THERMOSTAT, "RC10", DeviceFlags::EMS_DEVICE_FLAG_RC20_N},// 0x17
{ 67, DeviceType::THERMOSTAT, F("RC30"), DeviceFlags::EMS_DEVICE_FLAG_RC30_N},// 0x10 - based on RC35 { 67, DeviceType::THERMOSTAT, "RC30", DeviceFlags::EMS_DEVICE_FLAG_RC30_N},// 0x10 - based on RC35
{ 77, DeviceType::THERMOSTAT, F("RC20/Moduline 300"), DeviceFlags::EMS_DEVICE_FLAG_RC20},// 0x17 { 77, DeviceType::THERMOSTAT, "RC20/Moduline 300", DeviceFlags::EMS_DEVICE_FLAG_RC20},// 0x17
{ 78, DeviceType::THERMOSTAT, F("Moduline 400"), DeviceFlags::EMS_DEVICE_FLAG_RC30}, // 0x10 { 78, DeviceType::THERMOSTAT, "Moduline 400", DeviceFlags::EMS_DEVICE_FLAG_RC30}, // 0x10
{ 79, DeviceType::THERMOSTAT, F("RC10/Moduline 100"), DeviceFlags::EMS_DEVICE_FLAG_RC10},// 0x17 { 79, DeviceType::THERMOSTAT, "RC10/Moduline 100", DeviceFlags::EMS_DEVICE_FLAG_RC10},// 0x17
{ 80, DeviceType::THERMOSTAT, F("Moduline 200"), DeviceFlags::EMS_DEVICE_FLAG_RC10}, // 0x17 { 80, DeviceType::THERMOSTAT, "Moduline 200", DeviceFlags::EMS_DEVICE_FLAG_RC10}, // 0x17
{ 86, DeviceType::THERMOSTAT, F("RC35"), DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10 { 86, DeviceType::THERMOSTAT, "RC35", DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10
{ 90, DeviceType::THERMOSTAT, F("RC10/Moduline 100"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 { 90, DeviceType::THERMOSTAT, "RC10/Moduline 100", DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17
{ 93, DeviceType::THERMOSTAT, F("RC20RF"), DeviceFlags::EMS_DEVICE_FLAG_RC20}, // 0x19 { 93, DeviceType::THERMOSTAT, "RC20RF", DeviceFlags::EMS_DEVICE_FLAG_RC20}, // 0x19
{ 94, DeviceType::THERMOSTAT, F("RFM20 Remote"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18 { 94, DeviceType::THERMOSTAT, "RFM20 Remote", DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18
{151, DeviceType::THERMOSTAT, F("RC25"), DeviceFlags::EMS_DEVICE_FLAG_RC25}, // 0x17 {151, DeviceType::THERMOSTAT, "RC25", DeviceFlags::EMS_DEVICE_FLAG_RC25}, // 0x17
{157, DeviceType::THERMOSTAT, F("RC200/CW100"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18 {157, DeviceType::THERMOSTAT, "RC200/CW100", DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18
{158, DeviceType::THERMOSTAT, F("RC300/RC310/Moduline 3000/1010H/CW400/Sense II"), DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10 {158, DeviceType::THERMOSTAT, "RC300/RC310/Moduline 3000/1010H/CW400/Sense II", DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
{165, DeviceType::THERMOSTAT, F("RC100/Moduline 1000/1010"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38 {165, DeviceType::THERMOSTAT, "RC100/Moduline 1000/1010", DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38
{172, DeviceType::THERMOSTAT, F("Rego 2000/3000"), DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10 {172, DeviceType::THERMOSTAT, "Rego 2000/3000", DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
{216, DeviceType::THERMOSTAT, F("CRF200S"), DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18 {216, DeviceType::THERMOSTAT, "CRF200S", DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
{246, DeviceType::THERMOSTAT, F("Comfort+2RF"), DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18 {246, DeviceType::THERMOSTAT, "Comfort+2RF", DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
// Thermostat - Sieger - 0x10 / 0x17 // Thermostat - Sieger - 0x10 / 0x17
{ 66, DeviceType::THERMOSTAT, F("ES72/RC20"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 or remote { 66, DeviceType::THERMOSTAT, "ES72/RC20", DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 or remote
{ 76, DeviceType::THERMOSTAT, F("ES73"), DeviceFlags::EMS_DEVICE_FLAG_RC30_N}, // 0x10 { 76, DeviceType::THERMOSTAT, "ES73", DeviceFlags::EMS_DEVICE_FLAG_RC30_N}, // 0x10
{113, DeviceType::THERMOSTAT, F("ES72/RC20"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 {113, DeviceType::THERMOSTAT, "ES72/RC20", DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17
// Thermostat - Junkers - 0x10 // Thermostat - Junkers - 0x10
{105, DeviceType::THERMOSTAT, F("FW100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {105, DeviceType::THERMOSTAT, "FW100", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
{106, DeviceType::THERMOSTAT, F("FW200"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {106, DeviceType::THERMOSTAT, "FW200", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
{107, DeviceType::THERMOSTAT, F("FR100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model {107, DeviceType::THERMOSTAT, "FR100", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model
{108, DeviceType::THERMOSTAT, F("FR110"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model {108, DeviceType::THERMOSTAT, "FR110", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model
{109, DeviceType::THERMOSTAT, F("FB10"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {109, DeviceType::THERMOSTAT, "FB10", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
{110, DeviceType::THERMOSTAT, F("FB100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {110, DeviceType::THERMOSTAT, "FB100", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
{111, DeviceType::THERMOSTAT, F("FR10"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model {111, DeviceType::THERMOSTAT, "FR10", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model
{147, DeviceType::THERMOSTAT, F("FR50"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, {116, DeviceType::THERMOSTAT, "FW500", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
{191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model {147, DeviceType::THERMOSTAT, "FR50", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD},
{192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {191, DeviceType::THERMOSTAT, "FR120", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model
{192, DeviceType::THERMOSTAT, "FW120", DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
// Thermostat remote - 0x38 // Thermostat remote - 0x38
{200, DeviceType::THERMOSTAT, F("RC100H"), DeviceFlags::EMS_DEVICE_FLAG_RC100H}, {200, DeviceType::THERMOSTAT, "RC100H", DeviceFlags::EMS_DEVICE_FLAG_RC100H},
// Solar Modules - 0x30 (for solar), 0x2A, 0x41 (for ww) // Solar Modules - 0x30 (for solar), 0x2A, 0x41 (for ww)
{ 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10}, { 73, DeviceType::SOLAR, "SM10", DeviceFlags::EMS_DEVICE_FLAG_SM10},
{101, DeviceType::SOLAR, F("ISM1"), DeviceFlags::EMS_DEVICE_FLAG_ISM}, {101, DeviceType::SOLAR, "ISM1", DeviceFlags::EMS_DEVICE_FLAG_ISM},
{103, DeviceType::SOLAR, F("ISM2"), DeviceFlags::EMS_DEVICE_FLAG_ISM}, {103, DeviceType::SOLAR, "ISM2", DeviceFlags::EMS_DEVICE_FLAG_ISM},
{162, DeviceType::SOLAR, F("SM50"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, {162, DeviceType::SOLAR, "SM50", DeviceFlags::EMS_DEVICE_FLAG_SM100},
{163, DeviceType::SOLAR, F("SM100/MS100"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, {163, DeviceType::SOLAR, "SM100/MS100", DeviceFlags::EMS_DEVICE_FLAG_SM100},
{164, DeviceType::SOLAR, F("SM200/MS200"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, {164, DeviceType::SOLAR, "SM200/MS200", DeviceFlags::EMS_DEVICE_FLAG_SM100},
// Mixer Modules - 0x20-0x27 for HC, 0x28-0x29 for WWC and 0x11 for the MP100 // Mixer Modules - 0x20-0x27 for HC, 0x28-0x29 for WWC and 0x11 for the MP100
{ 69, DeviceType::MIXER, F("MM10"), DeviceFlags::EMS_DEVICE_FLAG_MM10}, { 69, DeviceType::MIXER, "MM10", DeviceFlags::EMS_DEVICE_FLAG_MM10},
{100, DeviceType::MIXER, F("IPM"), DeviceFlags::EMS_DEVICE_FLAG_IPM}, {100, DeviceType::MIXER, "IPM", DeviceFlags::EMS_DEVICE_FLAG_IPM},
{102, DeviceType::MIXER, F("IPM"), DeviceFlags::EMS_DEVICE_FLAG_IPM}, {102, DeviceType::MIXER, "IPM", DeviceFlags::EMS_DEVICE_FLAG_IPM},
{159, DeviceType::MIXER, F("MM50"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, {159, DeviceType::MIXER, "MM50", DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
{160, DeviceType::MIXER, F("MM100"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, {160, DeviceType::MIXER, "MM100", DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
{161, DeviceType::MIXER, F("MM200"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, {161, DeviceType::MIXER, "MM200", DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
{204, DeviceType::MIXER, F("MP100"), DeviceFlags::EMS_DEVICE_FLAG_MP}, // pool {204, DeviceType::MIXER, "MP100", DeviceFlags::EMS_DEVICE_FLAG_MP}, // pool
// Heat Pumps - 0x38? // Heat Pumps - 0x38?
{252, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {252, DeviceType::HEATPUMP, "HP Module", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Heat Pumps - 0x53 // Heat Pumps - 0x53
{248, DeviceType::HEATPUMP, F("Hybrid Manager HM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {248, DeviceType::HEATPUMP, "Hybrid Manager HM200", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Connect devices - 0x02 // Connect devices - 0x02
{171, DeviceType::CONNECT, F("OpenTherm Converter"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {171, DeviceType::CONNECT, "OpenTherm Converter", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{205, DeviceType::CONNECT, F("Moduline Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {205, DeviceType::CONNECT, "Moduline Easy Connect", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{206, DeviceType::CONNECT, F("Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {206, DeviceType::CONNECT, "Easy Connect", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Wireless sensor base - 0x50 // Wireless sensor base - 0x50
{236, DeviceType::CONNECT, F("Wireless sensor base"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {236, DeviceType::CONNECT, "Wireless sensor base", DeviceFlags::EMS_DEVICE_FLAG_NONE},
{238, DeviceType::CONNECT, F("Wireless sensor base"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {238, DeviceType::CONNECT, "Wireless sensor base", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Switches - 0x11 // Switches - 0x11
{ 71, DeviceType::SWITCH, F("WM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 71, DeviceType::SWITCH, "WM10", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// EM10 error contact and analog flowtemp control- 0x12 // EM10 error contact and analog flowtemp control- 0x12
{ 74, DeviceType::GATEWAY, F("Error Module EM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 74, DeviceType::GATEWAY, "Error Module EM10", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Gateways - 0x48 // Gateways - 0x48
{189, DeviceType::GATEWAY, F("KM200/MB LAN 2"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {189, DeviceType::GATEWAY, "KM200/MB LAN 2", DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Generic - 0x40 or other with no product-id and no version // Generic - 0x40 or other with no product-id and no version
{0, DeviceType::GENERIC, F("unknown"), DeviceFlags::EMS_DEVICE_FLAG_NONE} {0, DeviceType::GENERIC, "unknown", DeviceFlags::EMS_DEVICE_FLAG_NONE}
// clang-format on // clang-format on

View File

@@ -24,16 +24,16 @@ REGISTER_FACTORY(Boiler, EMSdevice::DeviceType::BOILER)
uuid::log::Logger Boiler::logger_{F_(boiler), uuid::log::Facility::CONSOLE}; uuid::log::Logger Boiler::logger_{F_(boiler), uuid::log::Facility::CONSOLE};
Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
// alternative heatsource special messages // alternative heatsource special messages
if (device_id == EMSdevice::EMS_DEVICE_ID_AM200) { if (device_id == EMSdevice::EMS_DEVICE_ID_AM200) {
register_telegram_type(0x54D, F("AmTemperatures"), false, MAKE_PF_CB(process_amTempMessage)); register_telegram_type(0x54D, "AmTemperatures", false, MAKE_PF_CB(process_amTempMessage));
register_telegram_type(0x54E, F("AmStatus"), false, MAKE_PF_CB(process_amStatusMessage)); register_telegram_type(0x54E, "AmStatus", false, MAKE_PF_CB(process_amStatusMessage));
register_telegram_type(0x54F, F("AmCommand"), false, MAKE_PF_CB(process_amCommandMessage)); // not broadcasted, but actually not used register_telegram_type(0x54F, "AmCommand", false, MAKE_PF_CB(process_amCommandMessage)); // not broadcasted, but actually not used
register_telegram_type(0x550, F("AmExtra"), false, MAKE_PF_CB(process_amExtraMessage)); register_telegram_type(0x550, "AmExtra", false, MAKE_PF_CB(process_amExtraMessage));
register_telegram_type(0x54C, F("AmSettings"), true, MAKE_PF_CB(process_amSettingMessage)); // not broadcasted register_telegram_type(0x54C, "AmSettings", true, MAKE_PF_CB(process_amSettingMessage)); // not broadcasted
register_device_value(DeviceValueTAG::TAG_AHS, register_device_value(DeviceValueTAG::TAG_AHS,
&curFlowTemp_, &curFlowTemp_,
@@ -113,12 +113,12 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
if (device_id >= EMSdevice::EMS_DEVICE_ID_BOILER_1) { if (device_id >= EMSdevice::EMS_DEVICE_ID_BOILER_1) {
uint8_t hs = device_id - EMSdevice::EMS_DEVICE_ID_BOILER_1; // heating source id, count from 0 uint8_t hs = device_id - EMSdevice::EMS_DEVICE_ID_BOILER_1; // heating source id, count from 0
// Runtime of each heatingsource in 0x06DC, ff // Runtime of each heatingsource in 0x06DC, ff
register_telegram_type(0x6DC + hs, F("CascadeMessage"), false, MAKE_PF_CB(process_CascadeMessage)); register_telegram_type(0x6DC + hs, "CascadeMessage", false, MAKE_PF_CB(process_CascadeMessage));
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &burnWorkMin_, DeviceValueType::TIME, FL_(burnWorkMin), DeviceValueUOM::MINUTES); register_device_value(DeviceValueTAG::TAG_HS1 + hs, &burnWorkMin_, DeviceValueType::TIME, FL_(burnWorkMin), DeviceValueUOM::MINUTES);
// selBurnpower in D2 and E4 // selBurnpower in D2 and E4
// register_telegram_type(0xD2, F("CascadePowerMessage"), false, MAKE_PF_CB(process_CascadePowerMessage)); // register_telegram_type(0xD2, "CascadePowerMessage", false, MAKE_PF_CB(process_CascadePowerMessage));
// individual Flowtemps and powervalues for each heatingsource in E4 // individual Flowtemps and powervalues for each heatingsource in E4
register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, MAKE_PF_CB(process_UBAMonitorFastPlus)); register_telegram_type(0xE4, "UBAMonitorFastPlus", false, MAKE_PF_CB(process_UBAMonitorFastPlus));
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &selFlowTemp_, DeviceValueType::UINT, FL_(selFlowTemp), DeviceValueUOM::DEGREES); register_device_value(DeviceValueTAG::TAG_HS1 + hs, &selFlowTemp_, DeviceValueType::UINT, FL_(selFlowTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_HS1 + hs, &selBurnPow_, DeviceValueType::UINT, FL_(selBurnPow), DeviceValueUOM::PERCENT); register_device_value(DeviceValueTAG::TAG_HS1 + hs, &selBurnPow_, DeviceValueType::UINT, FL_(selBurnPow), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_HS1 + hs, register_device_value(DeviceValueTAG::TAG_HS1 + hs,
@@ -136,51 +136,51 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
// the telegram handlers... // the telegram handlers...
// common for all boilers // common for all boilers
register_telegram_type(0xBF, F("ErrorMessage"), false, MAKE_PF_CB(process_ErrorMessage)); register_telegram_type(0xBF, "ErrorMessage", false, MAKE_PF_CB(process_ErrorMessage));
register_telegram_type(0x10, F("UBAErrorMessage1"), false, MAKE_PF_CB(process_UBAErrorMessage)); register_telegram_type(0x10, "UBAErrorMessage1", false, MAKE_PF_CB(process_UBAErrorMessage));
register_telegram_type(0x11, F("UBAErrorMessage2"), false, MAKE_PF_CB(process_UBAErrorMessage)); register_telegram_type(0x11, "UBAErrorMessage2", false, MAKE_PF_CB(process_UBAErrorMessage));
register_telegram_type(0xC2, F("UBAErrorMessage3"), false, MAKE_PF_CB(process_UBAErrorMessage2)); register_telegram_type(0xC2, "UBAErrorMessage3", false, MAKE_PF_CB(process_UBAErrorMessage2));
register_telegram_type(0x14, F("UBATotalUptime"), true, MAKE_PF_CB(process_UBATotalUptime)); register_telegram_type(0x14, "UBATotalUptime", true, MAKE_PF_CB(process_UBATotalUptime));
register_telegram_type(0x15, F("UBAMaintenanceData"), false, MAKE_PF_CB(process_UBAMaintenanceData)); register_telegram_type(0x15, "UBAMaintenanceData", false, MAKE_PF_CB(process_UBAMaintenanceData));
register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, MAKE_PF_CB(process_UBAMaintenanceStatus)); register_telegram_type(0x1C, "UBAMaintenanceStatus", false, MAKE_PF_CB(process_UBAMaintenanceStatus));
// EMS1.0 and maybe EMS+? // EMS1.0 and maybe EMS+?
register_telegram_type(0x18, F("UBAMonitorFast"), false, MAKE_PF_CB(process_UBAMonitorFast)); register_telegram_type(0x18, "UBAMonitorFast", false, MAKE_PF_CB(process_UBAMonitorFast));
register_telegram_type(0x19, F("UBAMonitorSlow"), false, MAKE_PF_CB(process_UBAMonitorSlow)); register_telegram_type(0x19, "UBAMonitorSlow", false, MAKE_PF_CB(process_UBAMonitorSlow));
register_telegram_type(0x1A, F("UBASetPoints"), false, MAKE_PF_CB(process_UBASetPoints)); register_telegram_type(0x1A, "UBASetPoints", false, MAKE_PF_CB(process_UBASetPoints));
register_telegram_type(0x35, F("UBAFlags"), false, MAKE_PF_CB(process_UBAFlags)); register_telegram_type(0x35, "UBAFlags", false, MAKE_PF_CB(process_UBAFlags));
// only EMS 1.0 // only EMS 1.0
register_telegram_type(0x16, F("UBAParameters"), true, MAKE_PF_CB(process_UBAParameters)); register_telegram_type(0x16, "UBAParameters", true, MAKE_PF_CB(process_UBAParameters));
register_telegram_type(0x33, F("UBAParameterWW"), true, MAKE_PF_CB(process_UBAParameterWW)); register_telegram_type(0x33, "UBAParameterWW", true, MAKE_PF_CB(process_UBAParameterWW));
register_telegram_type(0x34, F("UBAMonitorWW"), false, MAKE_PF_CB(process_UBAMonitorWW)); register_telegram_type(0x34, "UBAMonitorWW", false, MAKE_PF_CB(process_UBAMonitorWW));
// not ems1.0, but HT3 // not ems1.0, but HT3
if (model() != EMSdevice::EMS_DEVICE_FLAG_EMS) { if (model() != EMSdevice::EMS_DEVICE_FLAG_EMS) {
register_telegram_type(0x26, F("UBASettingsWW"), true, MAKE_PF_CB(process_UBASettingsWW)); register_telegram_type(0x26, "UBASettingsWW", true, MAKE_PF_CB(process_UBASettingsWW));
register_telegram_type(0x2A, F("MC110Status"), false, MAKE_PF_CB(process_MC110Status)); register_telegram_type(0x2A, "MC110Status", false, MAKE_PF_CB(process_MC110Status));
} }
// only EMS+ // only EMS+
if (model() != EMSdevice::EMS_DEVICE_FLAG_EMS && model() != EMSdevice::EMS_DEVICE_FLAG_HT3 && model() != EMSdevice::EMS_DEVICE_FLAG_HYBRID) { if (model() != EMSdevice::EMS_DEVICE_FLAG_EMS && model() != EMSdevice::EMS_DEVICE_FLAG_HT3 && model() != EMSdevice::EMS_DEVICE_FLAG_HYBRID) {
register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, MAKE_PF_CB(process_UBAOutdoorTemp)); register_telegram_type(0xD1, "UBAOutdoorTemp", false, MAKE_PF_CB(process_UBAOutdoorTemp));
register_telegram_type(0xE3, F("UBAMonitorSlowPlus2"), false, MAKE_PF_CB(process_UBAMonitorSlowPlus2)); register_telegram_type(0xE3, "UBAMonitorSlowPlus2", false, MAKE_PF_CB(process_UBAMonitorSlowPlus2));
register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, MAKE_PF_CB(process_UBAMonitorFastPlus)); register_telegram_type(0xE4, "UBAMonitorFastPlus", false, MAKE_PF_CB(process_UBAMonitorFastPlus));
register_telegram_type(0xE5, F("UBAMonitorSlowPlus"), false, MAKE_PF_CB(process_UBAMonitorSlowPlus)); register_telegram_type(0xE5, "UBAMonitorSlowPlus", false, MAKE_PF_CB(process_UBAMonitorSlowPlus));
register_telegram_type(0xE6, F("UBAParametersPlus"), true, MAKE_PF_CB(process_UBAParametersPlus)); register_telegram_type(0xE6, "UBAParametersPlus", true, MAKE_PF_CB(process_UBAParametersPlus));
register_telegram_type(0xE9, F("UBAMonitorWWPlus"), false, MAKE_PF_CB(process_UBAMonitorWWPlus)); register_telegram_type(0xE9, "UBAMonitorWWPlus", false, MAKE_PF_CB(process_UBAMonitorWWPlus));
register_telegram_type(0xEA, F("UBAParameterWWPlus"), true, MAKE_PF_CB(process_UBAParameterWWPlus)); register_telegram_type(0xEA, "UBAParameterWWPlus", true, MAKE_PF_CB(process_UBAParameterWWPlus));
} }
if (model() == EMSdevice::EMS_DEVICE_FLAG_HEATPUMP) { if (model() == EMSdevice::EMS_DEVICE_FLAG_HEATPUMP) {
register_telegram_type(0x494, F("UBAEnergySupplied"), false, MAKE_PF_CB(process_UBAEnergySupplied)); register_telegram_type(0x494, "UBAEnergySupplied", false, MAKE_PF_CB(process_UBAEnergySupplied));
register_telegram_type(0x495, F("UBAInformation"), false, MAKE_PF_CB(process_UBAInformation)); register_telegram_type(0x495, "UBAInformation", false, MAKE_PF_CB(process_UBAInformation));
register_telegram_type(0x48D, F("HpPower"), true, MAKE_PF_CB(process_HpPower)); register_telegram_type(0x48D, "HpPower", true, MAKE_PF_CB(process_HpPower));
register_telegram_type(0x48F, F("HpOutdoor"), false, MAKE_PF_CB(process_HpOutdoor)); register_telegram_type(0x48F, "HpOutdoor", false, MAKE_PF_CB(process_HpOutdoor));
register_telegram_type(0x48A, F("HpPool"), true, MAKE_PF_CB(process_HpPool)); register_telegram_type(0x48A, "HpPool", true, MAKE_PF_CB(process_HpPool));
register_telegram_type(0x4A2, F("HpInput"), false, MAKE_PF_CB(process_HpInput)); register_telegram_type(0x4A2, "HpInput", false, MAKE_PF_CB(process_HpInput));
register_telegram_type(0x486, F("HpInConfig"), false, MAKE_PF_CB(process_HpInConfig)); register_telegram_type(0x486, "HpInConfig", false, MAKE_PF_CB(process_HpInConfig));
register_telegram_type(0x492, F("HpHeaterConfig"), false, MAKE_PF_CB(process_HpHeaterConfig)); register_telegram_type(0x492, "HpHeaterConfig", false, MAKE_PF_CB(process_HpHeaterConfig));
} }
/* /*
@@ -189,7 +189,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
* enable settings here if no thermostat is used in system * enable settings here if no thermostat is used in system
* *
if (model() == EMSdevice::EMS_DEVICE_FLAG_HYBRID) { if (model() == EMSdevice::EMS_DEVICE_FLAG_HYBRID) {
register_telegram_type(0xBB, F("HybridHp"), true, MAKE_PF_CB(process_HybridHp)); register_telegram_type(0xBB, "HybridHp", true, MAKE_PF_CB(process_HybridHp));
} }
*/ */
@@ -2178,11 +2178,11 @@ bool Boiler::set_ww_circulation_mode(const char * value, const int8_t id) {
} }
if (v < 7) { if (v < 7) {
// LOG_INFO(F("Setting dhw circulation mode %dx3min"), v); // LOG_INFO("Setting dhw circulation mode %dx3min", v);
} else if (v == 7) { } else if (v == 7) {
// LOG_INFO(F("Setting dhw circulation mode continuous")); // LOG_INFO("Setting dhw circulation mode continuous");
} else { } else {
// LOG_WARNING(F("Set dhw circulation mode: Invalid value")); // LOG_WARNING("Set dhw circulation mode: Invalid value");
return false; return false;
} }
@@ -2206,12 +2206,12 @@ bool Boiler::set_reset(const char * value, const int8_t id) {
} }
if (num == 1) { if (num == 1) {
// LOG_INFO(F("Reset boiler maintenance message")); // LOG_INFO("Reset boiler maintenance message");
write_command(0x05, 0x08, 0xFF, 0x1C); write_command(0x05, 0x08, 0xFF, 0x1C);
has_update(&reset_); has_update(&reset_);
return true; return true;
} else if (num == 2) { } else if (num == 2) {
// LOG_INFO(F("Reset boiler error message")); // LOG_INFO("Reset boiler error message");
write_command(0x05, 0x00, 0x5A); // error reset write_command(0x05, 0x00, 0x5A); // error reset
has_update(&reset_); has_update(&reset_);
return true; return true;
@@ -2227,8 +2227,8 @@ bool Boiler::set_maintenance(const char * value, const int8_t id) {
std::string s; std::string s;
if (Helpers::value2string(value, s)) { if (Helpers::value2string(value, s)) {
if (s == Helpers::translated_word(FL_(reset))) { if (s == std::string(Helpers::translated_word(FL_(reset)))) {
// LOG_INFO(F("Reset boiler maintenance message")); // LOG_INFO("Reset boiler maintenance message");
write_command(0x05, 0x08, 0xFF, 0x1C); write_command(0x05, 0x08, 0xFF, 0x1C);
return true; return true;
} }
@@ -2239,11 +2239,11 @@ bool Boiler::set_maintenance(const char * value, const int8_t id) {
uint8_t month = (value[3] - '0') * 10 + (value[4] - '0'); uint8_t month = (value[3] - '0') * 10 + (value[4] - '0');
uint8_t year = (uint8_t)(Helpers::atoint(&value[6]) - 2000); uint8_t year = (uint8_t)(Helpers::atoint(&value[6]) - 2000);
if (day > 0 && day < 32 && month > 0 && month < 13) { if (day > 0 && day < 32 && month > 0 && month < 13) {
LOG_INFO(F("Setting maintenance date to %02d.%02d.%04d"), day, month, year + 2000); LOG_INFO("Setting maintenance date to %02d.%02d.%04d", day, month, year + 2000);
uint8_t data[5] = {2, (uint8_t)(maintenanceTime_ / 100), day, month, year}; uint8_t data[5] = {2, (uint8_t)(maintenanceTime_ / 100), day, month, year};
write_command(0x15, 0, data, 5, 0x15); write_command(0x15, 0, data, 5, 0x15);
} else { } else {
LOG_WARNING(F("Setting maintenance: wrong format %d.%d.%d"), day, month, year + 2000); LOG_WARNING("Setting maintenance: wrong format %d.%d.%d", day, month, year + 2000);
return false; return false;
} }
return true; return true;
@@ -2252,7 +2252,7 @@ bool Boiler::set_maintenance(const char * value, const int8_t id) {
int hrs; int hrs;
if (Helpers::value2number(value, hrs)) { if (Helpers::value2number(value, hrs)) {
if (hrs > 99 && hrs < 25600) { if (hrs > 99 && hrs < 25600) {
LOG_INFO(F("Setting maintenance time %d hours"), hrs); LOG_INFO("Setting maintenance time %d hours", hrs);
uint8_t data[2] = {1, (uint8_t)(hrs / 100)}; uint8_t data[2] = {1, (uint8_t)(hrs / 100)};
write_command(0x15, 0, data, 2, 0x15); write_command(0x15, 0, data, 2, 0x15);
return true; return true;
@@ -2261,12 +2261,12 @@ bool Boiler::set_maintenance(const char * value, const int8_t id) {
uint8_t num; uint8_t num;
if (Helpers::value2enum(value, num, FL_(enum_off_time_date_manual))) { if (Helpers::value2enum(value, num, FL_(enum_off_time_date_manual))) {
LOG_INFO(F("Setting maintenance type to %s"), value); LOG_INFO("Setting maintenance type to %s", value);
write_command(0x15, 0, num, 0x15); write_command(0x15, 0, num, 0x15);
return true; return true;
} }
LOG_WARNING(F("Setting maintenance: wrong format")); LOG_WARNING("Setting maintenance: wrong format");
return false; return false;
} }
//maintenance //maintenance
@@ -2274,13 +2274,13 @@ bool Boiler::set_maintenancetime(const char * value, const int8_t id) {
int hrs; int hrs;
if (Helpers::value2number(value, hrs)) { if (Helpers::value2number(value, hrs)) {
if (hrs > 99 && hrs < 25600) { if (hrs > 99 && hrs < 25600) {
LOG_INFO(F("Setting maintenance time %d hours"), hrs); LOG_INFO("Setting maintenance time %d hours", hrs);
uint8_t data[2] = {1, (uint8_t)(hrs / 100)}; uint8_t data[2] = {1, (uint8_t)(hrs / 100)};
write_command(0x15, 0, data, 2, 0x15); write_command(0x15, 0, data, 2, 0x15);
return true; return true;
} }
} }
LOG_WARNING(F("Setting maintenance: wrong format")); LOG_WARNING("Setting maintenance: wrong format");
return false; return false;
} }
@@ -2291,17 +2291,17 @@ bool Boiler::set_maintenancedate(const char * value, const int8_t id) {
uint8_t month = (value[3] - '0') * 10 + (value[4] - '0'); uint8_t month = (value[3] - '0') * 10 + (value[4] - '0');
uint8_t year = (uint8_t)(Helpers::atoint(&value[6]) - 2000); uint8_t year = (uint8_t)(Helpers::atoint(&value[6]) - 2000);
if (day > 0 && day < 32 && month > 0 && month < 13) { if (day > 0 && day < 32 && month > 0 && month < 13) {
LOG_INFO(F("Setting maintenance date to %02d.%02d.%04d"), day, month, year + 2000); LOG_INFO("Setting maintenance date to %02d.%02d.%04d", day, month, year + 2000);
uint8_t data[5] = {2, (uint8_t)(maintenanceTime_ / 100), day, month, year}; uint8_t data[5] = {2, (uint8_t)(maintenanceTime_ / 100), day, month, year};
write_command(0x15, 0, data, 5, 0x15); write_command(0x15, 0, data, 5, 0x15);
} else { } else {
LOG_WARNING(F("Setting maintenance: wrong format %d.%d.%d"), day, month, year + 2000); LOG_WARNING("Setting maintenance: wrong format %d.%d.%d", day, month, year + 2000);
return false; return false;
} }
return true; return true;
} }
LOG_WARNING(F("Setting maintenance: wrong format")); LOG_WARNING("Setting maintenance: wrong format");
return false; return false;
} }
@@ -2312,7 +2312,7 @@ bool Boiler::set_pool_temp(const char * value, const int8_t id) {
return false; return false;
} }
uint8_t v2 = ((v * 2) + 0.5); uint8_t v2 = ((v * 2) + 0.5);
// LOG_INFO(F("Setting pool temperature to %d.%d C"), v2 >> 1, (v2 & 0x01) * 5); // LOG_INFO("Setting pool temperature to %d.%d C", v2 >> 1, (v2 & 0x01) * 5);
write_command(0x48A, 1, v2, 0x48A); write_command(0x48A, 1, v2, 0x48A);
return true; return true;

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Boiler : public EMSdevice { class Boiler : public EMSdevice {
public: public:
Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand); Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -22,7 +22,7 @@ namespace emsesp {
REGISTER_FACTORY(Connect, EMSdevice::DeviceType::CONNECT); REGISTER_FACTORY(Connect, EMSdevice::DeviceType::CONNECT);
Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
} }

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Connect : public EMSdevice { class Connect : public EMSdevice {
public: public:
Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand); Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -22,11 +22,11 @@ namespace emsesp {
REGISTER_FACTORY(Controller, EMSdevice::DeviceType::CONTROLLER); REGISTER_FACTORY(Controller, EMSdevice::DeviceType::CONTROLLER);
Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
// IVT broadcasts Thermostat time from controller (0x09) if display is off. // IVT broadcasts Thermostat time from controller (0x09) if display is off.
if ((flags & 0x0F) == EMS_DEVICE_FLAG_IVT) { if ((flags & 0x0F) == EMS_DEVICE_FLAG_IVT) {
register_telegram_type(0x06, F("RCTime"), false, MAKE_PF_CB(process_dateTime)); register_telegram_type(0x06, "RCTime", false, MAKE_PF_CB(process_dateTime));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dateTime_, DeviceValueType::STRING, FL_(dateTime), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dateTime_, DeviceValueType::STRING, FL_(dateTime), DeviceValueUOM::NONE);
} }
} }

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Controller : public EMSdevice { class Controller : public EMSdevice {
public: public:
Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand); Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
void process_dateTime(std::shared_ptr<const Telegram> telegram); void process_dateTime(std::shared_ptr<const Telegram> telegram);

View File

@@ -22,7 +22,7 @@ namespace emsesp {
REGISTER_FACTORY(Gateway, EMSdevice::DeviceType::GATEWAY); REGISTER_FACTORY(Gateway, EMSdevice::DeviceType::GATEWAY);
Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
} }

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Gateway : public EMSdevice { class Gateway : public EMSdevice {
public: public:
Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand); Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -24,7 +24,7 @@ REGISTER_FACTORY(Generic, EMSdevice::DeviceType::GENERIC);
uuid::log::Logger Generic::logger_{F_(generic), uuid::log::Facility::CONSOLE}; uuid::log::Logger Generic::logger_{F_(generic), uuid::log::Facility::CONSOLE};
Generic::Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) Generic::Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
} }

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Generic : public EMSdevice { class Generic : public EMSdevice {
public: public:
Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand); Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -22,11 +22,11 @@ namespace emsesp {
REGISTER_FACTORY(Heatpump, EMSdevice::DeviceType::HEATPUMP); REGISTER_FACTORY(Heatpump, EMSdevice::DeviceType::HEATPUMP);
Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
// telegram handlers // telegram handlers
register_telegram_type(0x042B, F("HP1"), false, MAKE_PF_CB(process_HPMonitor1)); register_telegram_type(0x042B, "HP1", false, MAKE_PF_CB(process_HPMonitor1));
register_telegram_type(0x047B, F("HP2"), false, MAKE_PF_CB(process_HPMonitor2)); register_telegram_type(0x047B, "HP2", false, MAKE_PF_CB(process_HPMonitor2));
// device values // device values
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &airHumidity_, DeviceValueType::UINT, FL_(airHumidity), DeviceValueUOM::PERCENT); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &airHumidity_, DeviceValueType::UINT, FL_(airHumidity), DeviceValueUOM::PERCENT);

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Heatpump : public EMSdevice { class Heatpump : public EMSdevice {
public: public:
Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand); Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
private: private:
uint8_t airHumidity_; uint8_t airHumidity_;

View File

@@ -24,11 +24,11 @@ REGISTER_FACTORY(Mixer, EMSdevice::DeviceType::MIXER);
uuid::log::Logger Mixer::logger_{F_(mixer), uuid::log::Facility::CONSOLE}; uuid::log::Logger Mixer::logger_{F_(mixer), uuid::log::Facility::CONSOLE};
Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
// Pool module // Pool module
if (flags == EMSdevice::EMS_DEVICE_FLAG_MP) { if (flags == EMSdevice::EMS_DEVICE_FLAG_MP) {
register_telegram_type(0x5BA, F("HpPoolStatus"), true, MAKE_PF_CB(process_HpPoolStatus)); register_telegram_type(0x5BA, "HpPoolStatus", true, MAKE_PF_CB(process_HpPoolStatus));
type_ = Type::MP; type_ = Type::MP;
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&poolTemp_, &poolTemp_,
@@ -43,8 +43,8 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
// EMS+ // EMS+
if (flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) { if (flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
if (device_id >= 0x20 && device_id <= 0x27) { if (device_id >= 0x20 && device_id <= 0x27) {
register_telegram_type(device_id - 0x20 + 0x02D7, F("MMPLUSStatusMessage_HC"), false, MAKE_PF_CB(process_MMPLUSStatusMessage_HC)); register_telegram_type(device_id - 0x20 + 0x02D7, "MMPLUSStatusMessage_HC", false, MAKE_PF_CB(process_MMPLUSStatusMessage_HC));
// register_telegram_type(device_id - 0x20 + 0x02E1, F("MMPLUSStetMessage_HC"), true, MAKE_PF_CB(process_MMPLUSSetMessage_HC)); // register_telegram_type(device_id - 0x20 + 0x02E1, "MMPLUSStetMessage_HC", true, MAKE_PF_CB(process_MMPLUSSetMessage_HC));
type_ = Type::HC; type_ = Type::HC;
hc_ = device_id - 0x20 + 1; hc_ = device_id - 0x20 + 1;
uint8_t tag = DeviceValueTAG::TAG_HC1 + hc_ - 1; uint8_t tag = DeviceValueTAG::TAG_HC1 + hc_ - 1;
@@ -53,9 +53,9 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
register_device_value(tag, &flowSetTemp_, DeviceValueType::UINT, FL_(flowSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flowSetTemp)); register_device_value(tag, &flowSetTemp_, DeviceValueType::UINT, FL_(flowSetTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flowSetTemp));
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, FL_(pumpStatus), DeviceValueUOM::NONE, MAKE_CF_CB(set_pump)); register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, FL_(pumpStatus), DeviceValueUOM::NONE, MAKE_CF_CB(set_pump));
} else if (device_id >= 0x28 && device_id <= 0x29) { } else if (device_id >= 0x28 && device_id <= 0x29) {
register_telegram_type(device_id - 0x28 + 0x0331, F("MMPLUSStatusMessage_WWC"), false, MAKE_PF_CB(process_MMPLUSStatusMessage_WWC)); register_telegram_type(device_id - 0x28 + 0x0331, "MMPLUSStatusMessage_WWC", false, MAKE_PF_CB(process_MMPLUSStatusMessage_WWC));
register_telegram_type(device_id - 0x28 + 0x0313, F("MMPLUSConfigMessage_WWC"), true, MAKE_PF_CB(process_MMPLUSConfigMessage_WWC)); register_telegram_type(device_id - 0x28 + 0x0313, "MMPLUSConfigMessage_WWC", true, MAKE_PF_CB(process_MMPLUSConfigMessage_WWC));
// register_telegram_type(device_id - 0x28 + 0x033B, F("MMPLUSSetMessage_WWC"), true, MAKE_PF_CB(process_MMPLUSSetMessage_WWC)); // register_telegram_type(device_id - 0x28 + 0x033B, "MMPLUSSetMessage_WWC", true, MAKE_PF_CB(process_MMPLUSSetMessage_WWC));
type_ = Type::WWC; type_ = Type::WWC;
hc_ = device_id - 0x28 + 1; hc_ = device_id - 0x28 + 1;
uint8_t tag = DeviceValueTAG::TAG_WWC1 + hc_ - 1; uint8_t tag = DeviceValueTAG::TAG_WWC1 + hc_ - 1;
@@ -85,9 +85,9 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
// EMS 1.0 // EMS 1.0
if (flags == EMSdevice::EMS_DEVICE_FLAG_MM10) { if (flags == EMSdevice::EMS_DEVICE_FLAG_MM10) {
register_telegram_type(0x00AA, F("MMConfigMessage"), true, MAKE_PF_CB(process_MMConfigMessage)); register_telegram_type(0x00AA, "MMConfigMessage", true, MAKE_PF_CB(process_MMConfigMessage));
register_telegram_type(0x00AB, F("MMStatusMessage"), false, MAKE_PF_CB(process_MMStatusMessage)); register_telegram_type(0x00AB, "MMStatusMessage", false, MAKE_PF_CB(process_MMStatusMessage));
register_telegram_type(0x00AC, F("MMSetMessage"), false, MAKE_PF_CB(process_MMSetMessage)); register_telegram_type(0x00AC, "MMSetMessage", false, MAKE_PF_CB(process_MMSetMessage));
type_ = Type::HC; type_ = Type::HC;
hc_ = device_id - 0x20 + 1; hc_ = device_id - 0x20 + 1;
uint8_t tag = DeviceValueTAG::TAG_HC1 + hc_ - 1; uint8_t tag = DeviceValueTAG::TAG_HC1 + hc_ - 1;
@@ -110,10 +110,10 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
// HT3 // HT3
if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) { if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
if (device_id >= 0x40) { // special DHW pos 10 if (device_id >= 0x40) { // special DHW pos 10
register_telegram_type(0x34, F("UBAMonitorWW"), false, MAKE_PF_CB(process_IPMMonitorWW)); register_telegram_type(0x34, "UBAMonitorWW", false, MAKE_PF_CB(process_IPMMonitorWW));
register_telegram_type(0x1E, F("HydrTemp"), false, MAKE_PF_CB(process_IPMHydrTemp)); register_telegram_type(0x1E, "HydrTemp", false, MAKE_PF_CB(process_IPMHydrTemp));
register_telegram_type(0x33, F("UBAParameterWW"), true, MAKE_PF_CB(process_IPMParameterWW)); register_telegram_type(0x33, "UBAParameterWW", true, MAKE_PF_CB(process_IPMParameterWW));
// register_telegram_type(0x10D, F("wwNTCStatus"), false, MAKE_PF_CB(process_wwNTCStatus)); // register_telegram_type(0x10D, "wwNTCStatus", false, MAKE_PF_CB(process_wwNTCStatus));
type_ = Type::WWC; type_ = Type::WWC;
hc_ = device_id - 0x40 + 1; hc_ = device_id - 0x40 + 1;
uint8_t tag = DeviceValueTAG::TAG_WWC9 + hc_ - 1; uint8_t tag = DeviceValueTAG::TAG_WWC9 + hc_ - 1;
@@ -139,9 +139,9 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
MAKE_CF_CB(set_wwCircPump)); MAKE_CF_CB(set_wwCircPump));
register_device_value(tag, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wwCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircMode)); register_device_value(tag, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wwCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircMode));
} else { } else {
register_telegram_type(0x010C, F("IPMStatusMessage"), false, MAKE_PF_CB(process_IPMStatusMessage)); register_telegram_type(0x010C, "IPMStatusMessage", false, MAKE_PF_CB(process_IPMStatusMessage));
register_telegram_type(0x011E, F("IPMTempMessage"), false, MAKE_PF_CB(process_IPMTempMessage)); register_telegram_type(0x011E, "IPMTempMessage", false, MAKE_PF_CB(process_IPMTempMessage));
// register_telegram_type(0x0123, F("IPMSetMessage"), false, MAKE_PF_CB(process_IPMSetMessage)); // register_telegram_type(0x0123, "IPMSetMessage", false, MAKE_PF_CB(process_IPMSetMessage));
type_ = Type::HC; type_ = Type::HC;
hc_ = device_id - 0x20 + 1; hc_ = device_id - 0x20 + 1;
uint8_t tag = DeviceValueTAG::TAG_HC1 + hc_ - 1; uint8_t tag = DeviceValueTAG::TAG_HC1 + hc_ - 1;

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Mixer : public EMSdevice { class Mixer : public EMSdevice {
public: public:
Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand); Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -24,48 +24,48 @@ REGISTER_FACTORY(Solar, EMSdevice::DeviceType::SOLAR);
uuid::log::Logger Solar::logger_{F_(solar), uuid::log::Facility::CONSOLE}; uuid::log::Logger Solar::logger_{F_(solar), uuid::log::Facility::CONSOLE};
Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
// telegram handlers // telegram handlers
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) { if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) {
register_telegram_type(0x97, F("SM10Monitor"), false, MAKE_PF_CB(process_SM10Monitor)); register_telegram_type(0x97, "SM10Monitor", false, MAKE_PF_CB(process_SM10Monitor));
register_telegram_type(0x96, F("SM10Config"), true, MAKE_PF_CB(process_SM10Config)); register_telegram_type(0x96, "SM10Config", true, MAKE_PF_CB(process_SM10Config));
EMSESP::send_read_request(0x97, device_id); EMSESP::send_read_request(0x97, device_id);
} }
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM100) { if (flags == EMSdevice::EMS_DEVICE_FLAG_SM100) {
if (device_id == 0x2A) { // SM100 DHW if (device_id == 0x2A) { // SM100 DHW
register_telegram_type(0x07D6, F("SM100wwTemperature"), false, MAKE_PF_CB(process_SM100wwTemperature)); register_telegram_type(0x07D6, "SM100wwTemperature", false, MAKE_PF_CB(process_SM100wwTemperature));
register_telegram_type(0x07AA, F("SM100wwStatus"), false, MAKE_PF_CB(process_SM100wwStatus)); register_telegram_type(0x07AA, "SM100wwStatus", false, MAKE_PF_CB(process_SM100wwStatus));
register_telegram_type(0x07AB, F("SM100wwCommand"), false, MAKE_PF_CB(process_SM100wwCommand)); register_telegram_type(0x07AB, "SM100wwCommand", false, MAKE_PF_CB(process_SM100wwCommand));
register_telegram_type(0x07A5, F("SM100wwCirc"), true, MAKE_PF_CB(process_SM100wwCirc)); register_telegram_type(0x07A5, "SM100wwCirc", true, MAKE_PF_CB(process_SM100wwCirc));
register_telegram_type(0x07A6, F("SM100wwParam"), true, MAKE_PF_CB(process_SM100wwParam)); register_telegram_type(0x07A6, "SM100wwParam", true, MAKE_PF_CB(process_SM100wwParam));
register_telegram_type(0x07AE, F("SM100wwKeepWarm"), true, MAKE_PF_CB(process_SM100wwKeepWarm)); register_telegram_type(0x07AE, "SM100wwKeepWarm", true, MAKE_PF_CB(process_SM100wwKeepWarm));
register_telegram_type(0x07E0, F("SM100wwStatus2"), true, MAKE_PF_CB(process_SM100wwStatus2)); register_telegram_type(0x07E0, "SM100wwStatus2", true, MAKE_PF_CB(process_SM100wwStatus2));
} else { } else {
// F9 is not a telegram type, it's a flag for configure // F9 is not a telegram type, it's a flag for configure
// register_telegram_type(0xF9, F("ParamCfg"), false, MAKE_PF_CB(process_SM100ParamCfg)); // register_telegram_type(0xF9, "ParamCfg", false, MAKE_PF_CB(process_SM100ParamCfg));
register_telegram_type(0x0358, F("SM100SystemConfig"), true, MAKE_PF_CB(process_SM100SystemConfig)); register_telegram_type(0x0358, "SM100SystemConfig", true, MAKE_PF_CB(process_SM100SystemConfig));
register_telegram_type(0x035A, F("SM100CircuitConfig"), true, MAKE_PF_CB(process_SM100CircuitConfig)); register_telegram_type(0x035A, "SM100CircuitConfig", true, MAKE_PF_CB(process_SM100CircuitConfig));
register_telegram_type(0x035D, F("SM100Circuit2Config"), true, MAKE_PF_CB(process_SM100Circuit2Config)); register_telegram_type(0x035D, "SM100Circuit2Config", true, MAKE_PF_CB(process_SM100Circuit2Config));
register_telegram_type(0x0362, F("SM100Monitor"), false, MAKE_PF_CB(process_SM100Monitor)); register_telegram_type(0x0362, "SM100Monitor", false, MAKE_PF_CB(process_SM100Monitor));
register_telegram_type(0x0363, F("SM100Monitor2"), false, MAKE_PF_CB(process_SM100Monitor2)); register_telegram_type(0x0363, "SM100Monitor2", false, MAKE_PF_CB(process_SM100Monitor2));
register_telegram_type(0x0366, F("SM100Config"), false, MAKE_PF_CB(process_SM100Config)); register_telegram_type(0x0366, "SM100Config", false, MAKE_PF_CB(process_SM100Config));
register_telegram_type(0x0364, F("SM100Status"), false, MAKE_PF_CB(process_SM100Status)); register_telegram_type(0x0364, "SM100Status", false, MAKE_PF_CB(process_SM100Status));
register_telegram_type(0x036A, F("SM100Status2"), false, MAKE_PF_CB(process_SM100Status2)); register_telegram_type(0x036A, "SM100Status2", false, MAKE_PF_CB(process_SM100Status2));
register_telegram_type(0x0380, F("SM100CollectorConfig"), true, MAKE_PF_CB(process_SM100CollectorConfig)); register_telegram_type(0x0380, "SM100CollectorConfig", true, MAKE_PF_CB(process_SM100CollectorConfig));
register_telegram_type(0x038E, F("SM100Energy"), true, MAKE_PF_CB(process_SM100Energy)); register_telegram_type(0x038E, "SM100Energy", true, MAKE_PF_CB(process_SM100Energy));
register_telegram_type(0x0391, F("SM100Time"), true, MAKE_PF_CB(process_SM100Time)); register_telegram_type(0x0391, "SM100Time", true, MAKE_PF_CB(process_SM100Time));
register_telegram_type(0x035F, F("SM100Config1"), true, MAKE_PF_CB(process_SM100Config1)); register_telegram_type(0x035F, "SM100Config1", true, MAKE_PF_CB(process_SM100Config1));
register_telegram_type(0x035C, F("SM100HeatAssist"), true, MAKE_PF_CB(process_SM100HeatAssist)); register_telegram_type(0x035C, "SM100HeatAssist", true, MAKE_PF_CB(process_SM100HeatAssist));
register_telegram_type(0x0361, F("SM100Differential"), true, MAKE_PF_CB(process_SM100Differential)); register_telegram_type(0x0361, "SM100Differential", true, MAKE_PF_CB(process_SM100Differential));
} }
} }
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) { if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, MAKE_PF_CB(process_ISM1StatusMessage)); register_telegram_type(0x0103, "ISM1StatusMessage", true, MAKE_PF_CB(process_ISM1StatusMessage));
register_telegram_type(0x0101, F("ISM1Set"), true, MAKE_PF_CB(process_ISM1Set)); register_telegram_type(0x0101, "ISM1Set", true, MAKE_PF_CB(process_ISM1Set));
register_telegram_type(0x0104, F("ISM2StatusMessage"), false, MAKE_PF_CB(process_ISM2StatusMessage)); register_telegram_type(0x0104, "ISM2StatusMessage", false, MAKE_PF_CB(process_ISM2StatusMessage));
} }
// device values... // device values...
@@ -664,7 +664,7 @@ void Solar::process_SM100ParamCfg(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(max, 13); telegram->read_value(max, 13);
telegram->read_value(cur, 17); telegram->read_value(cur, 17);
// LOG_DEBUG(F("SM100ParamCfg param=0x%04X, offset=%d, min=%d, default=%d, max=%d, current=%d"), t_id, of, min, def, max, cur)); // LOG_DEBUG("SM100ParamCfg param=0x%04X, offset=%d, min=%d, default=%d, max=%d, current=%d", t_id, of, min, def, max, cur));
} }
/* /*

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Solar : public EMSdevice { class Solar : public EMSdevice {
public: public:
Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand); Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -22,12 +22,12 @@ namespace emsesp {
REGISTER_FACTORY(Switch, EMSdevice::DeviceType::SWITCH); REGISTER_FACTORY(Switch, EMSdevice::DeviceType::SWITCH);
Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
// WM10 module, device_id 0x11 // WM10 module, device_id 0x11
register_telegram_type(0x9C, F("WM10MonitorMessage"), false, MAKE_PF_CB(process_WM10MonitorMessage)); register_telegram_type(0x9C, "WM10MonitorMessage", false, MAKE_PF_CB(process_WM10MonitorMessage));
register_telegram_type(0x9D, F("WM10SetMessage"), false, MAKE_PF_CB(process_WM10SetMessage)); register_telegram_type(0x9D, "WM10SetMessage", false, MAKE_PF_CB(process_WM10SetMessage));
register_telegram_type(0x1E, F("WM10TempMessage"), false, MAKE_PF_CB(process_WM10TempMessage)); register_telegram_type(0x1E, "WM10TempMessage", false, MAKE_PF_CB(process_WM10TempMessage));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &activated_, DeviceValueType::BOOL, FL_(activated), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &activated_, DeviceValueType::BOOL, FL_(activated), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Switch : public EMSdevice { class Switch : public EMSdevice {
public: public:
Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand); Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
private: private:
void process_WM10SetMessage(std::shared_ptr<const Telegram> telegram); void process_WM10SetMessage(std::shared_ptr<const Telegram> telegram);

View File

@@ -24,37 +24,37 @@ REGISTER_FACTORY(Thermostat, EMSdevice::DeviceType::THERMOSTAT);
uuid::log::Logger Thermostat::logger_{F_(thermostat), uuid::log::Facility::CONSOLE}; uuid::log::Logger Thermostat::logger_{F_(thermostat), uuid::log::Facility::CONSOLE};
Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
uint8_t model = this->model(); uint8_t model = this->model();
// RF remote sensor seen at 0x40, maybe this is also for different hc with id 0x40 - 0x47? emsesp.cpp maps only 0x40 // RF remote sensor seen at 0x40, maybe this is also for different hc with id 0x40 - 0x47? emsesp.cpp maps only 0x40
if (device_id >= 0x40 && device_id <= 0x47) { if (device_id >= 0x40 && device_id <= 0x47) {
register_telegram_type(0x0435, F("RFTemp"), false, MAKE_PF_CB(process_RemoteTemp)); register_telegram_type(0x0435, "RFTemp", false, MAKE_PF_CB(process_RemoteTemp));
return; return;
} }
// remote thermostats with humidity: RC100H remote // remote thermostats with humidity: RC100H remote
if (device_id >= 0x38 && device_id <= 0x3F) { if (device_id >= 0x38 && device_id <= 0x3F) {
register_telegram_type(0x042B, F("RemoteTemp"), false, MAKE_PF_CB(process_RemoteTemp)); register_telegram_type(0x042B, "RemoteTemp", false, MAKE_PF_CB(process_RemoteTemp));
register_telegram_type(0x047B, F("RemoteHumidity"), false, MAKE_PF_CB(process_RemoteHumidity)); register_telegram_type(0x047B, "RemoteHumidity", false, MAKE_PF_CB(process_RemoteHumidity));
register_telegram_type(0x0273, F("RemoteCorrection"), true, MAKE_PF_CB(process_RemoteCorrection)); register_telegram_type(0x0273, "RemoteCorrection", true, MAKE_PF_CB(process_RemoteCorrection));
register_device_values(); // register device values for common values (not heating circuit) register_device_values(); // register device values for common values (not heating circuit)
return; // no values to add return; // no values to add
} }
// common telegram handlers // common telegram handlers
register_telegram_type(EMS_TYPE_RCOutdoorTemp, F("RCOutdoorTemp"), false, MAKE_PF_CB(process_RCOutdoorTemp)); register_telegram_type(EMS_TYPE_RCOutdoorTemp, "RCOutdoorTemp", false, MAKE_PF_CB(process_RCOutdoorTemp));
register_telegram_type(EMS_TYPE_RCTime, F("RCTime"), false, MAKE_PF_CB(process_RCTime)); register_telegram_type(EMS_TYPE_RCTime, "RCTime", false, MAKE_PF_CB(process_RCTime));
register_telegram_type(0xA2, F("RCError"), false, MAKE_PF_CB(process_RCError)); register_telegram_type(0xA2, "RCError", false, MAKE_PF_CB(process_RCError));
register_telegram_type(0x12, F("RCErrorMessage"), false, MAKE_PF_CB(process_RCErrorMessage)); register_telegram_type(0x12, "RCErrorMessage", false, MAKE_PF_CB(process_RCErrorMessage));
register_telegram_type(0x13, F("RCErrorMessage2"), false, MAKE_PF_CB(process_RCErrorMessage)); register_telegram_type(0x13, "RCErrorMessage2", false, MAKE_PF_CB(process_RCErrorMessage));
// RC10 // RC10
if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) { if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) {
monitor_typeids = {0xB1}; monitor_typeids = {0xB1};
set_typeids = {0xB0}; set_typeids = {0xB0};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC10Monitor"), false, MAKE_PF_CB(process_RC10Monitor)); register_telegram_type(monitor_typeids[i], "RC10Monitor", false, MAKE_PF_CB(process_RC10Monitor));
register_telegram_type(set_typeids[i], F("RC10Set"), false, MAKE_PF_CB(process_RC10Set)); register_telegram_type(set_typeids[i], "RC10Set", false, MAKE_PF_CB(process_RC10Set));
} }
// RC35 // RC35
@@ -64,15 +64,15 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
timer_typeids = {0x3F, 0x49, 0x53, 0x5D}; timer_typeids = {0x3F, 0x49, 0x53, 0x5D};
timer2_typeids = {0x42, 0x4C, 0x56, 0x60}; timer2_typeids = {0x42, 0x4C, 0x56, 0x60};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC35Monitor"), false, MAKE_PF_CB(process_RC35Monitor)); register_telegram_type(monitor_typeids[i], "RC35Monitor", false, MAKE_PF_CB(process_RC35Monitor));
register_telegram_type(set_typeids[i], F("RC35Set"), false, MAKE_PF_CB(process_RC35Set)); register_telegram_type(set_typeids[i], "RC35Set", false, MAKE_PF_CB(process_RC35Set));
register_telegram_type(timer_typeids[i], F("RC35Timer"), false, MAKE_PF_CB(process_RC35Timer)); register_telegram_type(timer_typeids[i], "RC35Timer", false, MAKE_PF_CB(process_RC35Timer));
register_telegram_type(timer2_typeids[i], F("RC35Timer2"), false, MAKE_PF_CB(process_RC35Timer)); register_telegram_type(timer2_typeids[i], "RC35Timer2", false, MAKE_PF_CB(process_RC35Timer));
} }
register_telegram_type(EMS_TYPE_IBASettings, F("IBASettings"), true, MAKE_PF_CB(process_IBASettings)); register_telegram_type(EMS_TYPE_IBASettings, "IBASettings", true, MAKE_PF_CB(process_IBASettings));
register_telegram_type(EMS_TYPE_wwSettings, F("WWSettings"), true, MAKE_PF_CB(process_RC35wwSettings)); register_telegram_type(EMS_TYPE_wwSettings, "WWSettings", true, MAKE_PF_CB(process_RC35wwSettings));
register_telegram_type(0x38, F("WWTimer"), true, MAKE_PF_CB(process_RC35wwTimer)); register_telegram_type(0x38, "WWTimer", true, MAKE_PF_CB(process_RC35wwTimer));
register_telegram_type(0x39, F("WWCircTimer"), true, MAKE_PF_CB(process_RC35wwTimer)); register_telegram_type(0x39, "WWCircTimer", true, MAKE_PF_CB(process_RC35wwTimer));
// RC20 // RC20
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC20) {
@@ -81,22 +81,22 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
curve_typeids = {0x90}; curve_typeids = {0x90};
timer_typeids = {0x8F}; timer_typeids = {0x8F};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, MAKE_PF_CB(process_RC20Monitor)); register_telegram_type(monitor_typeids[i], "RC20Monitor", false, MAKE_PF_CB(process_RC20Monitor));
register_telegram_type(set_typeids[i], F("RC20Set"), false, MAKE_PF_CB(process_RC20Set)); register_telegram_type(set_typeids[i], "RC20Set", false, MAKE_PF_CB(process_RC20Set));
register_telegram_type(curve_typeids[i], F("RC20Temp"), false, MAKE_PF_CB(process_RC20Temp)); register_telegram_type(curve_typeids[i], "RC20Temp", false, MAKE_PF_CB(process_RC20Temp));
register_telegram_type(timer_typeids[i], F("RC20Timer"), false, MAKE_PF_CB(process_RC20Timer)); register_telegram_type(timer_typeids[i], "RC20Timer", false, MAKE_PF_CB(process_RC20Timer));
} }
// remote thermostat uses only 0xAF // remote thermostat uses only 0xAF
register_telegram_type(0xAF, F("RC20Remote"), false, MAKE_PF_CB(process_RC20Remote)); register_telegram_type(0xAF, "RC20Remote", false, MAKE_PF_CB(process_RC20Remote));
// RC20 newer // RC20 newer
} else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC20_N) || (model == EMSdevice::EMS_DEVICE_FLAG_RC25)) { } else if ((model == EMSdevice::EMS_DEVICE_FLAG_RC20_N) || (model == EMSdevice::EMS_DEVICE_FLAG_RC25)) {
monitor_typeids = {0xAE}; monitor_typeids = {0xAE};
set_typeids = {0xAD}; set_typeids = {0xAD};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC20Monitor"), false, MAKE_PF_CB(process_RC20Monitor_2)); register_telegram_type(monitor_typeids[i], "RC20Monitor", false, MAKE_PF_CB(process_RC20Monitor_2));
register_telegram_type(set_typeids[i], F("RC20Set"), false, MAKE_PF_CB(process_RC20Set_2)); register_telegram_type(set_typeids[i], "RC20Set", false, MAKE_PF_CB(process_RC20Set_2));
} }
register_telegram_type(0xAF, F("RC20Remote"), false, MAKE_PF_CB(process_RC20Remote)); register_telegram_type(0xAF, "RC20Remote", false, MAKE_PF_CB(process_RC20Remote));
// RC30 // RC30
} else if (model == EMSdevice::EMS_DEVICE_FLAG_RC30) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_RC30) {
monitor_typeids = {0x41}; monitor_typeids = {0x41};
@@ -104,27 +104,27 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
curve_typeids = {0x40}; curve_typeids = {0x40};
timer_typeids = {0x3F}; timer_typeids = {0x3F};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC30Monitor"), false, MAKE_PF_CB(process_RC30Monitor)); register_telegram_type(monitor_typeids[i], "RC30Monitor", false, MAKE_PF_CB(process_RC30Monitor));
register_telegram_type(set_typeids[i], F("RC30Set"), false, MAKE_PF_CB(process_RC30Set)); register_telegram_type(set_typeids[i], "RC30Set", false, MAKE_PF_CB(process_RC30Set));
register_telegram_type(curve_typeids[i], F("RC30Temp"), false, MAKE_PF_CB(process_RC30Temp)); register_telegram_type(curve_typeids[i], "RC30Temp", false, MAKE_PF_CB(process_RC30Temp));
register_telegram_type(timer_typeids[i], F("RC30Timer"), false, MAKE_PF_CB(process_RC35Timer)); register_telegram_type(timer_typeids[i], "RC30Timer", false, MAKE_PF_CB(process_RC35Timer));
} }
register_telegram_type(EMS_TYPE_RC30wwSettings, F("RC30WWSettings"), true, MAKE_PF_CB(process_RC30wwSettings)); register_telegram_type(EMS_TYPE_RC30wwSettings, "RC30WWSettings", true, MAKE_PF_CB(process_RC30wwSettings));
register_telegram_type(0x38, F("WWTimer"), true, MAKE_PF_CB(process_RC35wwTimer)); register_telegram_type(0x38, "WWTimer", true, MAKE_PF_CB(process_RC35wwTimer));
register_telegram_type(0x39, F("WWCircTimer"), true, MAKE_PF_CB(process_RC35wwTimer)); register_telegram_type(0x39, "WWCircTimer", true, MAKE_PF_CB(process_RC35wwTimer));
// EASY // EASY
} else if (model == EMSdevice::EMS_DEVICE_FLAG_EASY) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_EASY) {
monitor_typeids = {0x0A}; monitor_typeids = {0x0A};
set_typeids = {}; set_typeids = {};
register_telegram_type(monitor_typeids[0], F("EasyMonitor"), true, MAKE_PF_CB(process_EasyMonitor)); register_telegram_type(monitor_typeids[0], "EasyMonitor", true, MAKE_PF_CB(process_EasyMonitor));
// CRF // CRF
} else if (model == EMSdevice::EMS_DEVICE_FLAG_CRF) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_CRF) {
monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8}; monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8};
set_typeids = {}; set_typeids = {};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("CRFMonitor"), false, MAKE_PF_CB(process_CRFMonitor)); register_telegram_type(monitor_typeids[i], "CRFMonitor", false, MAKE_PF_CB(process_CRFMonitor));
} }
// RC300/RC100 // RC300/RC100
@@ -136,46 +136,46 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
curve_typeids = {0x029B, 0x029C, 0x029D, 0x029E, 0x029F, 0x02A0, 0x02A1, 0x02A2}; curve_typeids = {0x029B, 0x029C, 0x029D, 0x029E, 0x029F, 0x02A0, 0x02A1, 0x02A2};
summer2_typeids = {0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0476, 0x0477, 0x0478}; summer2_typeids = {0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0476, 0x0477, 0x0478};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("RC300Monitor"), false, MAKE_PF_CB(process_RC300Monitor)); register_telegram_type(monitor_typeids[i], "RC300Monitor", false, MAKE_PF_CB(process_RC300Monitor));
register_telegram_type(set_typeids[i], F("RC300Set"), false, MAKE_PF_CB(process_RC300Set)); register_telegram_type(set_typeids[i], "RC300Set", false, MAKE_PF_CB(process_RC300Set));
register_telegram_type(summer_typeids[i], F("RC300Summer"), false, MAKE_PF_CB(process_RC300Summer)); register_telegram_type(summer_typeids[i], "RC300Summer", false, MAKE_PF_CB(process_RC300Summer));
register_telegram_type(curve_typeids[i], F("RC300Curves"), false, MAKE_PF_CB(process_RC300Curve)); register_telegram_type(curve_typeids[i], "RC300Curves", false, MAKE_PF_CB(process_RC300Curve));
register_telegram_type(summer2_typeids[i], F("RC300Summer2"), false, MAKE_PF_CB(process_RC300Summer2)); register_telegram_type(summer2_typeids[i], "RC300Summer2", false, MAKE_PF_CB(process_RC300Summer2));
} }
for (uint8_t i = 0; i < set2_typeids.size(); i++) { for (uint8_t i = 0; i < set2_typeids.size(); i++) {
register_telegram_type(set2_typeids[i], F("RC300Set2"), false, MAKE_PF_CB(process_RC300Set2)); register_telegram_type(set2_typeids[i], "RC300Set2", false, MAKE_PF_CB(process_RC300Set2));
} }
register_telegram_type(0x2F5, F("RC300WWmode"), true, MAKE_PF_CB(process_RC300WWmode)); register_telegram_type(0x2F5, "RC300WWmode", true, MAKE_PF_CB(process_RC300WWmode));
register_telegram_type(0x31B, F("RC300WWtemp"), true, MAKE_PF_CB(process_RC300WWtemp)); register_telegram_type(0x31B, "RC300WWtemp", true, MAKE_PF_CB(process_RC300WWtemp));
register_telegram_type(0x31D, F("RC300WWmode2"), false, MAKE_PF_CB(process_RC300WWmode2)); register_telegram_type(0x31D, "RC300WWmode2", false, MAKE_PF_CB(process_RC300WWmode2));
register_telegram_type(0x31E, F("RC300WWmode2"), false, MAKE_PF_CB(process_RC300WWmode2)); register_telegram_type(0x31E, "RC300WWmode2", false, MAKE_PF_CB(process_RC300WWmode2));
register_telegram_type(0x23A, F("RC300OutdoorTemp"), true, MAKE_PF_CB(process_RC300OutdoorTemp)); register_telegram_type(0x23A, "RC300OutdoorTemp", true, MAKE_PF_CB(process_RC300OutdoorTemp));
register_telegram_type(0x267, F("RC300Floordry"), false, MAKE_PF_CB(process_RC300Floordry)); register_telegram_type(0x267, "RC300Floordry", false, MAKE_PF_CB(process_RC300Floordry));
register_telegram_type(0x240, F("RC300Settings"), true, MAKE_PF_CB(process_RC300Settings)); register_telegram_type(0x240, "RC300Settings", true, MAKE_PF_CB(process_RC300Settings));
// JUNKERS/HT3 // JUNKERS/HT3
} else if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172}; monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(monitor_typeids[i], F("JunkersMonitor"), false, MAKE_PF_CB(process_JunkersMonitor)); register_telegram_type(monitor_typeids[i], "JunkersMonitor", false, MAKE_PF_CB(process_JunkersMonitor));
} }
if (has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) { if (has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) {
// FR120, FR100 // FR120, FR100
set_typeids = {0x0179, 0x017A, 0x017B, 0x017C}; set_typeids = {0x0179, 0x017A, 0x017B, 0x017C};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(set_typeids[i], F("JunkersSet"), false, MAKE_PF_CB(process_JunkersSet2)); register_telegram_type(set_typeids[i], "JunkersSet", false, MAKE_PF_CB(process_JunkersSet2));
} }
} else { } else {
set_typeids = {0x0165, 0x0166, 0x0167, 0x0168}; set_typeids = {0x0165, 0x0166, 0x0167, 0x0168};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < monitor_typeids.size(); i++) {
register_telegram_type(set_typeids[i], F("JunkersSet"), false, MAKE_PF_CB(process_JunkersSet)); register_telegram_type(set_typeids[i], "JunkersSet", false, MAKE_PF_CB(process_JunkersSet));
} }
} }
register_telegram_type(0xBB, F("HybridSettings"), true, MAKE_PF_CB(process_JunkersHybridSettings)); register_telegram_type(0xBB, "HybridSettings", true, MAKE_PF_CB(process_JunkersHybridSettings));
register_telegram_type(0x23, F("JunkersSetMixer"), true, MAKE_PF_CB(process_JunkersSetMixer)); register_telegram_type(0x23, "JunkersSetMixer", true, MAKE_PF_CB(process_JunkersSetMixer));
register_telegram_type(0x123, F("JunkersRemote"), false, MAKE_PF_CB(process_JunkersRemoteMonitor)); register_telegram_type(0x123, "JunkersRemote", false, MAKE_PF_CB(process_JunkersRemoteMonitor));
register_telegram_type(0x1D3, F("JunkersDhw"), true, MAKE_PF_CB(process_JunkersWW)); register_telegram_type(0x1D3, "JunkersDhw", true, MAKE_PF_CB(process_JunkersWW));
} }
// register device values for common values (not heating circuit) // register device values for common values (not heating circuit)
@@ -522,7 +522,7 @@ void Thermostat::process_RC10Set(std::shared_ptr<const Telegram> telegram) {
if (hc == nullptr) { if (hc == nullptr) {
return; return;
} }
has_update(telegram, ibaClockOffset_, 0); has_update(telegram, ibaCalIntTemperature_, 0);
has_update(telegram, backlight_, 1); has_update(telegram, backlight_, 1);
has_update(telegram, wwMode_, 2); has_update(telegram, wwMode_, 2);
has_update(telegram, hc->nighttemp, 3); has_update(telegram, hc->nighttemp, 3);
@@ -593,8 +593,7 @@ void Thermostat::process_RC20Timer(std::shared_ptr<const Telegram> telegram) {
uint8_t time = telegram->message_data[1]; uint8_t time = telegram->message_data[1];
// we use EN settings for the day abbreviation // we use EN settings for the day abbreviation
std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day][0]); std::string sday = (FL_(enum_dayOfWeek)[day][0]);
// std::string sday = Helpers::translated_word(FL_(enum_dayOfWeek)[day]);
if (day == 7) { if (day == 7) {
snprintf(data, sizeof(data), "%02d not_set", no); snprintf(data, sizeof(data), "%02d not_set", no);
@@ -680,13 +679,19 @@ void Thermostat::process_JunkersSet(std::shared_ptr<const Telegram> telegram) {
return; return;
} }
has_update(telegram, hc->daytemp, 17); // is * 2 has_update(telegram, hc->daytemp, 17); // is * 2
has_update(telegram, hc->nighttemp, 16); // is * 2 has_update(telegram, hc->nighttemp, 16); // is * 2
has_update(telegram, hc->nofrosttemp, 15); // is * 2 has_update(telegram, hc->nofrosttemp, 15); // is * 2
has_update(telegram, hc->control, 1); // remote: 0-off, 1-FB10, 2-FB100 has_update(telegram, hc->control, 1); // remote: 0-off, 1-FB10, 2-FB100
has_enumupdate(telegram, hc->program, 13, 1); // 1-6: 1 = A, 2 = B,... has_enumupdate(telegram, hc->program, 13, 1); // 1-6: 1 = A, 2 = B,...
has_enumupdate(telegram, hc->mode, 14, 1); // 0 = nofrost, 1 = eco, 2 = heat, 3 = auto has_enumupdate(telegram, hc->mode, 14, 1); // 0 = nofrost, 1 = eco, 2 = heat, 3 = auto
has_update(telegram, hc->roomsensor, 9); // 1-intern, 2-extern, 3-autoselect the lower value has_update(telegram, hc->daytemp, 17); // is * 2
has_update(telegram, hc->nighttemp, 16); // is * 2
has_update(telegram, hc->nofrosttemp, 15); // is * 2
has_update(telegram, hc->control, 1); // remote: 0-off, 1-FB10, 2-FB100
has_enumupdate(telegram, hc->program, 13, 1); // 1-6: 1 = A, 2 = B,...
has_enumupdate(telegram, hc->mode, 14, 1); // 0 = nofrost, 1 = eco, 2 = heat, 3 = auto
has_enumupdate(telegram, hc->roomsensor, 9, 1); // 1-intern, 2-extern, 3-autoselect the lower value
} }
// type 0x0179, ff // type 0x0179, ff
@@ -795,20 +800,11 @@ void Thermostat::process_RC35wwTimer(std::shared_ptr<const Telegram> telegram) {
char data[sizeof(wwSwitchTime_)]; char data[sizeof(wwSwitchTime_)];
// we use EN settings for the day abbreviation // we use EN settings for the day abbreviation
std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day][0]); std::string sday = (FL_(enum_dayOfWeek)[day][0]);
// std::string sday = Helpers::translated_word(FL_(enum_dayOfWeek)[day]);
if (day == 7) { if (day == 7) {
snprintf(data, sizeof(data), "%02d not_set", no); snprintf(data, sizeof(data), "%02d not_set", no);
} else { } else {
snprintf(data, snprintf(data, sizeof(data), "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off");
sizeof(data),
"%02d %s %02d:%02d %s",
no,
sday.c_str(),
time / 6,
10 * (time % 6),
// on ? (Helpers::translated_word(FL_(on))).c_str() : (Helpers::translated_word(FL_(off))).c_str());
on ? "on" : "off");
} }
if (telegram->type_id == 0x38) { if (telegram->type_id == 0x38) {
has_update(wwSwitchTime_, data, sizeof(wwSwitchTime_)); has_update(wwSwitchTime_, data, sizeof(wwSwitchTime_));
@@ -1102,6 +1098,7 @@ void Thermostat::process_RC300OutdoorTemp(std::shared_ptr<const Telegram> telegr
// 0x240 RC300 parameter // 0x240 RC300 parameter
void Thermostat::process_RC300Settings(std::shared_ptr<const Telegram> telegram) { void Thermostat::process_RC300Settings(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, ibaCalIntTemperature_, 7);
has_update(telegram, ibaDamping_, 8); has_update(telegram, ibaDamping_, 8);
has_enumupdate(telegram, ibaBuildingType_, 9, 1); // 1=light, 2=medium, 3=heavy has_enumupdate(telegram, ibaBuildingType_, 9, 1); // 1=light, 2=medium, 3=heavy
has_update(telegram, ibaMinExtTemperature_, 10); has_update(telegram, ibaMinExtTemperature_, 10);
@@ -1273,8 +1270,7 @@ void Thermostat::process_RC35Timer(std::shared_ptr<const Telegram> telegram) {
uint8_t time = telegram->message_data[1]; uint8_t time = telegram->message_data[1];
// we use EN settings for the day abbreviation // we use EN settings for the day abbreviation
std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day][0]); std::string sday = (FL_(enum_dayOfWeek)[day][0]);
// std::string sday = Helpers::translated_word(FL_(enum_dayOfWeek)[day]);
if (day == 7) { if (day == 7) {
snprintf(data, sizeof(data), "%02d not_set", no); snprintf(data, sizeof(data), "%02d not_set", no);
} else if (model() == EMS_DEVICE_FLAG_RC30) { } else if (model() == EMS_DEVICE_FLAG_RC30) {
@@ -1363,14 +1359,15 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
strftime(newdatetime, sizeof(dateTime_), "%d.%m.%G %H:%M", tm_); strftime(newdatetime, sizeof(dateTime_), "%d.%m.%G %H:%M", tm_);
has_update(dateTime_, newdatetime, sizeof(dateTime_)); has_update(dateTime_, newdatetime, sizeof(dateTime_));
bool ivtclock = (telegram->message_data[0] & 0x80) == 0x80; // dont sync ivt-clock, #439 bool ivtclock = (telegram->message_data[0] & 0x80) == 0x80; // dont sync ivt-clock, #439
time_t ttime = mktime(tm_); // thermostat time bool junkersclock = model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS;
time_t ttime = mktime(tm_); // thermostat time
// correct thermostat clock if we have valid ntp time, and could write the command // correct thermostat clock if we have valid ntp time, and could write the command
if (!ivtclock && tset_ && EMSESP::system_.ntp_connected() && !EMSESP::system_.readonly_mode() && has_command(&dateTime_)) { if (!ivtclock && !junkersclock && tset_ && EMSESP::system_.ntp_connected() && !EMSESP::system_.readonly_mode() && has_command(&dateTime_)) {
double difference = difftime(now, ttime); double difference = difftime(now, ttime);
if (difference > 15 || difference < -15) { if (difference > 15 || difference < -15) {
set_datetime("ntp", -1); // set from NTP set_datetime("ntp", -1); // set from NTP
LOG_INFO(F("thermostat time correction from ntp")); LOG_INFO("thermostat time correction from ntp");
} }
} }
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
@@ -1381,7 +1378,7 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
} }
struct timeval newnow = {.tv_sec = ttime}; struct timeval newnow = {.tv_sec = ttime};
settimeofday(&newnow, nullptr); settimeofday(&newnow, nullptr);
LOG_INFO(F("ems-esp time set from thermostat")); LOG_INFO("ems-esp time set from thermostat");
} }
#endif #endif
} }
@@ -1549,7 +1546,7 @@ bool Thermostat::set_calinttemp(const char * value, const int8_t id) {
} }
auto t = (int8_t)(ct * 10); auto t = (int8_t)(ct * 10);
LOG_DEBUG(F("Calibrating internal temperature to %d.%d C"), t / 10, t < 0 ? -t % 10 : t % 10); LOG_DEBUG("Calibrating internal temperature to %d.%d C", t / 10, t < 0 ? -t % 10 : t % 10);
if (model() == EMS_DEVICE_FLAG_RC10) { if (model() == EMS_DEVICE_FLAG_RC10) {
write_command(0xB0, 0, t, 0xB0); write_command(0xB0, 0, t, 0xB0);
@@ -1557,6 +1554,8 @@ bool Thermostat::set_calinttemp(const char * value, const int8_t id) {
write_command(EMS_TYPE_RC30Settings, 1, t, EMS_TYPE_RC30Settings); write_command(EMS_TYPE_RC30Settings, 1, t, EMS_TYPE_RC30Settings);
} else if (model() == EMS_DEVICE_FLAG_RC100H) { } else if (model() == EMS_DEVICE_FLAG_RC100H) {
write_command(0x273, 0, t, 0x273); write_command(0x273, 0, t, 0x273);
} else if (model() == EMS_DEVICE_FLAG_RC100 || model() == EMS_DEVICE_FLAG_RC300) {
write_command(0x240, 7, t, 0x240);
} else { } else {
write_command(EMS_TYPE_IBASettings, 2, t, EMS_TYPE_IBASettings); write_command(EMS_TYPE_IBASettings, 2, t, EMS_TYPE_IBASettings);
} }
@@ -1564,10 +1563,10 @@ bool Thermostat::set_calinttemp(const char * value, const int8_t id) {
return true; return true;
} }
// 0xA5 - Set the display settings // 0xA5 - Set the display settings, RC30_N
bool Thermostat::set_display(const char * value, const int8_t id) { bool Thermostat::set_display(const char * value, const int8_t id) {
int ds = 0; uint8_t ds;
if (!Helpers::value2number(value, ds)) { if (!Helpers::value2enum(value, ds, FL_(enum_ibaMainDisplay))) {
return false; return false;
} }
@@ -1714,7 +1713,7 @@ bool Thermostat::set_roomsensor(const char * value, const int8_t id) {
uint8_t ctrl = 0; uint8_t ctrl = 0;
if (model() == EMS_DEVICE_FLAG_JUNKERS && !has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) { if (model() == EMS_DEVICE_FLAG_JUNKERS && !has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) {
if (Helpers::value2enum(value, ctrl, FL_(enum_roomsensor))) { if (Helpers::value2enum(value, ctrl, FL_(enum_roomsensor))) {
write_command(set_typeids[hc->hc()], 9, ctrl); write_command(set_typeids[hc->hc()], 9, ctrl + 1);
return true; return true;
} }
} }
@@ -2151,7 +2150,7 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
return false; return false;
} }
if (!EMSESP::system_.ntp_connected()) { if (!EMSESP::system_.ntp_connected()) {
LOG_WARNING(F("Set date: no valid NTP data, setting from ESP Clock")); LOG_WARNING("Set date: no valid NTP data, setting from ESP Clock");
} }
data[0] = tm_->tm_year - 100; // Bosch counts from 2000 data[0] = tm_->tm_year - 100; // Bosch counts from 2000
@@ -2162,6 +2161,10 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
data[5] = tm_->tm_sec; data[5] = tm_->tm_sec;
data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su
data[7] = tm_->tm_isdst + 2; // set DST and flag for ext. clock data[7] = tm_->tm_isdst + 2; // set DST and flag for ext. clock
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
data[6]++; // Junkers use 1-7;
data[7] = 0;
}
} else if (dt.length() == 23) { } else if (dt.length() == 23) {
data[0] = (dt[7] - '0') * 100 + (dt[8] - '0') * 10 + (dt[9] - '0'); // year data[0] = (dt[7] - '0') * 100 + (dt[8] - '0') * 10 + (dt[9] - '0'); // year
data[1] = (dt[3] - '0') * 10 + (dt[4] - '0'); // month data[1] = (dt[3] - '0') * 10 + (dt[4] - '0'); // month
@@ -2171,16 +2174,19 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
data[5] = (dt[17] - '0') * 10 + (dt[18] - '0'); // sec data[5] = (dt[17] - '0') * 10 + (dt[18] - '0'); // sec
data[6] = (dt[20] - '0'); // day of week, Mo:0 data[6] = (dt[20] - '0'); // day of week, Mo:0
data[7] = (dt[22] - '0') + 2; // DST and flag data[7] = (dt[22] - '0') + 2; // DST and flag
if (model() == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
data[7] = 0;
}
} else { } else {
LOG_WARNING(F("Set date: invalid data, wrong length")); LOG_WARNING("Set date: invalid data, wrong length");
return false; return false;
} }
if (data[1] == 0 || data[1] > 12 || data[2] > 23 || data[3] == 0 || data[3] > 31 || data[4] > 59 || data[5] > 59 || data[6] > 6 || data[7] > 3) { if (data[1] == 0 || data[1] > 12 || data[2] > 23 || data[3] == 0 || data[3] > 31 || data[4] > 59 || data[5] > 59 || data[6] > 6 || data[7] > 3) {
LOG_WARNING(F("Invalid date/time: %02d.%02d.2%03d-%02d:%02d:%02d-%d-%d"), data[3], data[1], data[0], data[2], data[4], data[5], data[6], data[7]); LOG_WARNING("Invalid date/time: %02d.%02d.2%03d-%02d:%02d:%02d-%d-%d", data[3], data[1], data[0], data[2], data[4], data[5], data[6], data[7]);
return false; return false;
} }
// LOG_INFO(F("Setting date and time: %02d.%02d.2%03d-%02d:%02d:%02d-%d-%d"), data[3], data[1], data[0], data[2], data[4], data[5], data[6], data[7]); // LOG_INFO("Setting date and time: %02d.%02d.2%03d-%02d:%02d:%02d-%d-%d", data[3], data[1], data[0], data[2], data[4], data[5], data[6], data[7]);
write_command(EMS_TYPE_time, 0, data, 8, EMS_TYPE_time); write_command(EMS_TYPE_time, 0, data, 8, EMS_TYPE_time);
return true; return true;
@@ -2203,82 +2209,93 @@ bool Thermostat::set_roominfl_factor(const char * value, const int8_t id) {
return true; return true;
} }
// sets the thermostat working mode, where mode is a string // sets the thermostat working mode
// converts string mode to HeatingCircuit::Mode
bool Thermostat::set_mode(const char * value, const int8_t id) { bool Thermostat::set_mode(const char * value, const int8_t id) {
if ((value == nullptr) || (strlen(value) >= 20)) { if ((value == nullptr) || (strlen(value) >= 20)) {
return false; return false;
} }
std::string mode; // first determine which enum we are using
const char * const ** mode_list = nullptr; // points to a translated list of modes
if (value[0] >= '0' && value[0] <= '9') { switch (model()) {
uint8_t num = value[0] - '0'; case EMSdevice::EMS_DEVICE_FLAG_RC10:
switch (model()) { mode_list = FL_(enum_mode6);
case EMSdevice::EMS_DEVICE_FLAG_RC10: break;
mode = Helpers::translated_word(FL_(enum_mode6)[num], true); case EMSdevice::EMS_DEVICE_FLAG_RC20:
break; case EMSdevice::EMS_DEVICE_FLAG_RC20_N:
case EMSdevice::EMS_DEVICE_FLAG_RC20: mode_list = FL_(enum_mode2);
case EMSdevice::EMS_DEVICE_FLAG_RC20_N: break;
mode = Helpers::translated_word(FL_(enum_mode2)[num], true); case EMSdevice::EMS_DEVICE_FLAG_RC25:
break; case EMSdevice::EMS_DEVICE_FLAG_RC30:
case EMSdevice::EMS_DEVICE_FLAG_RC25: case EMSdevice::EMS_DEVICE_FLAG_RC35:
case EMSdevice::EMS_DEVICE_FLAG_RC30: case EMSdevice::EMS_DEVICE_FLAG_RC30_N:
case EMSdevice::EMS_DEVICE_FLAG_RC35: mode_list = FL_(enum_mode3);
case EMSdevice::EMS_DEVICE_FLAG_RC30_N: break;
mode = Helpers::translated_word(FL_(enum_mode3)[num], true); case EMSdevice::EMS_DEVICE_FLAG_RC300:
break; case EMSdevice::EMS_DEVICE_FLAG_RC100:
case EMSdevice::EMS_DEVICE_FLAG_RC300: mode_list = FL_(enum_mode);
case EMSdevice::EMS_DEVICE_FLAG_RC100: break;
mode = Helpers::translated_word(FL_(enum_mode)[num], true); case EMSdevice::EMS_DEVICE_FLAG_JUNKERS:
break; mode_list = FL_(enum_mode4);
case EMSdevice::EMS_DEVICE_FLAG_JUNKERS: break;
mode = Helpers::translated_word(FL_(enum_mode4)[num], true); case EMSdevice::EMS_DEVICE_FLAG_CRF:
break; mode_list = FL_(enum_mode5);
case EMSdevice::EMS_DEVICE_FLAG_CRF: break;
mode = Helpers::translated_word(FL_(enum_mode5)[num], true); default:
break;
default:
return false;
}
} else if (!Helpers::value2string(value, mode)) {
return false; return false;
} }
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; uint8_t enum_index = 0;
if (Helpers::translated_word(FL_(off), true) == mode) { // check for a mode number as a string with a single digit (0..9)
return set_mode_n(HeatingCircuit::Mode::OFF, hc_num); if (value[0] >= '0' && value[0] <= '9') {
enum_index = value[0] - '0';
if (enum_index >= Helpers::count_items(mode_list)) {
return false; // invalid number, not in enum
}
} else {
// check for the mode being a full string name
if (!Helpers::value2enum(value, enum_index, mode_list)) {
return false; // not found
}
} }
if (Helpers::translated_word(FL_(manual), true) == mode) {
return set_mode_n(HeatingCircuit::Mode::MANUAL, hc_num); uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; // heating circuit
}
if (Helpers::translated_word(FL_(auto), true) == mode) { // compare the english string
auto mode = mode_list[enum_index][0];
if (!strcmp(mode, FL_(auto)[0])) {
return set_mode_n(HeatingCircuit::Mode::AUTO, hc_num); return set_mode_n(HeatingCircuit::Mode::AUTO, hc_num);
} }
if (Helpers::translated_word(FL_(day), true) == mode) { if (!strcmp(mode, FL_(off)[0])) {
return set_mode_n(HeatingCircuit::Mode::OFF, hc_num);
}
if (!strcmp(mode, FL_(manual)[0])) {
return set_mode_n(HeatingCircuit::Mode::MANUAL, hc_num);
}
if (!strcmp(mode, FL_(day)[0])) {
return set_mode_n(HeatingCircuit::Mode::DAY, hc_num); return set_mode_n(HeatingCircuit::Mode::DAY, hc_num);
} }
if (Helpers::translated_word(FL_(night), true) == mode) { if (!strcmp(mode, FL_(night)[0])) {
return set_mode_n(HeatingCircuit::Mode::NIGHT, hc_num); return set_mode_n(HeatingCircuit::Mode::NIGHT, hc_num);
} }
if (Helpers::translated_word(FL_(heat), true) == mode) { if (!strcmp(mode, FL_(heat)[0])) {
return set_mode_n(HeatingCircuit::Mode::HEAT, hc_num); return set_mode_n(HeatingCircuit::Mode::HEAT, hc_num);
} }
if (Helpers::translated_word(FL_(nofrost), true) == mode) { if (!strcmp(mode, FL_(nofrost)[0])) {
return set_mode_n(HeatingCircuit::Mode::NOFROST, hc_num); return set_mode_n(HeatingCircuit::Mode::NOFROST, hc_num);
} }
if (Helpers::translated_word(FL_(eco), true) == mode) { if (!strcmp(mode, FL_(eco)[0])) {
return set_mode_n(HeatingCircuit::Mode::ECO, hc_num); return set_mode_n(HeatingCircuit::Mode::ECO, hc_num);
} }
if (Helpers::translated_word(FL_(holiday), true) == mode) { if (!strcmp(mode, FL_(holiday)[0])) {
return set_mode_n(HeatingCircuit::Mode::HOLIDAY, hc_num); return set_mode_n(HeatingCircuit::Mode::HOLIDAY, hc_num);
} }
if (Helpers::translated_word(FL_(comfort), true) == mode) { if (!strcmp(mode, FL_(comfort)[0])) {
return set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num); return set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num);
} }
return false; return false; // not found
} }
// Set the thermostat working mode // Set the thermostat working mode
@@ -2301,14 +2318,12 @@ bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
case HeatingCircuit::Mode::OFF: case HeatingCircuit::Mode::OFF:
set_mode_value = 0; set_mode_value = 0;
break; break;
case HeatingCircuit::Mode::DAY: case HeatingCircuit::Mode::DAY:
case HeatingCircuit::Mode::HEAT: case HeatingCircuit::Mode::HEAT:
case HeatingCircuit::Mode::MANUAL: case HeatingCircuit::Mode::MANUAL:
case HeatingCircuit::Mode::NOFROST: case HeatingCircuit::Mode::NOFROST:
set_mode_value = 1; set_mode_value = 1;
break; break;
default: // AUTO & ECO default: // AUTO & ECO
set_mode_value = 2; set_mode_value = 2;
break; break;
@@ -2653,7 +2668,7 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char
day = 7; day = 7;
on = 7; on = 7;
time = 0x90; time = 0x90;
// LOG_INFO(F("switchtime %02d cleared"), no); // LOG_INFO("switchtime %02d cleared", no);
} }
} else { } else {
if (strlen(value) > 1) { if (strlen(value) > 1) {
@@ -2670,14 +2685,9 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char
if (strlen(value) > 4) { if (strlen(value) > 4) {
for (uint8_t i = 0; i < 7; i++) { for (uint8_t i = 0; i < 7; i++) {
// we use EN settings for the day abbreviation // we use EN settings for the day abbreviation
if (!strncmp(&value[3], read_flash_string(FL_(enum_dayOfWeek)[i][0]).c_str(), 2)) { if (!strncmp(&value[3], (FL_(enum_dayOfWeek)[i][0]), 2)) {
day = i; day = i;
} }
// auto translated_dow = Helpers::translated_word(FL_(enum_dayOfWeek)[i]);
// if (!strncmp(&value[3], translated_dow.c_str(), translated_dow.length())) {
// day = i;
// }
} }
} }
if (strlen(value) > 10) { if (strlen(value) > 10) {
@@ -2694,7 +2704,7 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char
day = 7; day = 7;
on = 7; on = 7;
time = 0x90; time = 0x90;
// LOG_INFO(F("switchtime %02d cleared"), no); // LOG_INFO("switchtime %02d cleared", no);
} }
} }
uint8_t data[2] = {0xE7, 0x90}; // unset switchtime uint8_t data[2] = {0xE7, 0x90}; // unset switchtime
@@ -2710,21 +2720,19 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char
max_on = 1; max_on = 1;
} }
if (no > 41 || time > 0x90 || ((on < min_on || on > max_on) && on != 7)) { if (no > 41 || time > 0x90 || ((on < min_on || on > max_on) && on != 7)) {
// LOG_WARNING(F("Setting switchtime: Invalid data: %s"), value); // LOG_WARNING("Setting switchtime: Invalid data: %s", value);
// LOG_WARNING(F("Setting switchtime: Invalid data: %02d.%1d.0x%02X.%1d"), no, day, time, on); // LOG_WARNING("Setting switchtime: Invalid data: %02d.%1d.0x%02X.%1d", no, day, time, on);
return false; return false;
} }
if (data[0] != 0xE7) { if (data[0] != 0xE7) {
// we use EN settings for the day abbreviation // we use EN settings for the day abbreviation
std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day][0]); std::string sday = (FL_(enum_dayOfWeek)[day][0]);
// std::string sday = Helpers::translated_word(FL_(enum_dayOfWeek)[day]);
if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) { if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) {
snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off"); snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off");
} else if ((model() == EMS_DEVICE_FLAG_RC20) || (model() == EMS_DEVICE_FLAG_RC30)) { } else if ((model() == EMS_DEVICE_FLAG_RC20) || (model() == EMS_DEVICE_FLAG_RC30)) {
snprintf(out, len, "%02d %s %02d:%02d T%d", no, sday.c_str(), time / 6, 10 * (time % 6), on); snprintf(out, len, "%02d %s %02d:%02d T%d", no, sday.c_str(), time / 6, 10 * (time % 6), on);
} else { } else {
std::string son = read_flash_string(FL_(enum_switchmode)[on][0]); std::string son = (FL_(enum_switchmode)[on][0]);
// std::string son = Helpers::translated_word(FL_(enum_switchmode)[on]);
snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), son.c_str()); snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), son.c_str());
} }
} else { } else {
@@ -3382,6 +3390,13 @@ void Thermostat::register_device_values() {
FL_(dateTime), FL_(dateTime),
DeviceValueUOM::NONE, DeviceValueUOM::NONE,
MAKE_CF_CB(set_datetime)); MAKE_CF_CB(set_datetime));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaCalIntTemperature_,
DeviceValueType::INT,
DeviceValueNumOp::DV_NUMOP_DIV10,
FL_(ibaCalIntTemperature),
DeviceValueUOM::DEGREES_R,
MAKE_CF_CB(set_calinttemp));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&floordrystatus_, &floordrystatus_,
DeviceValueType::ENUM, DeviceValueType::ENUM,
@@ -3506,12 +3521,6 @@ void Thermostat::register_device_values() {
FL_(ibaLanguage), FL_(ibaLanguage),
DeviceValueUOM::NONE, DeviceValueUOM::NONE,
MAKE_CF_CB(set_language)); MAKE_CF_CB(set_language));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaMainDisplay_,
DeviceValueType::ENUM,
FL_(enum_ibaMainDisplay),
FL_(ibaMainDisplay),
DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &backlight_, DeviceValueType::BOOL, FL_(backlight), DeviceValueUOM::NONE, MAKE_CF_CB(set_backlight)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &backlight_, DeviceValueType::BOOL, FL_(backlight), DeviceValueUOM::NONE, MAKE_CF_CB(set_backlight));
register_device_value( register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &brightness_, DeviceValueType::INT, FL_(brightness), DeviceValueUOM::NONE, MAKE_CF_CB(set_brightness), -15, 15); DeviceValueTAG::TAG_DEVICE_DATA, &brightness_, DeviceValueType::INT, FL_(brightness), DeviceValueUOM::NONE, MAKE_CF_CB(set_brightness), -15, 15);
@@ -3599,13 +3608,15 @@ void Thermostat::register_device_values() {
DeviceValueType::ENUM, DeviceValueType::ENUM,
FL_(enum_ibaMainDisplay), FL_(enum_ibaMainDisplay),
FL_(ibaMainDisplay), FL_(ibaMainDisplay),
DeviceValueUOM::NONE); DeviceValueUOM::NONE,
MAKE_CF_CB(set_display));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), FL_(ibaLanguage), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), FL_(ibaLanguage), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaClockOffset_, &ibaClockOffset_,
DeviceValueType::INT, DeviceValueType::INT,
FL_(ibaClockOffset), FL_(ibaClockOffset),
DeviceValueUOM::SECONDS); // offset (in sec) to clock, 0xff=-1s, 0x02=2s DeviceValueUOM::SECONDS,
MAKE_CF_CB(set_clockoffset)); // offset (in sec) to clock, 0xff=-1s, 0x02=2s
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaCalIntTemperature_, &ibaCalIntTemperature_,
DeviceValueType::INT, DeviceValueType::INT,
@@ -3840,8 +3851,9 @@ void Thermostat::register_device_values() {
MAKE_CF_CB(set_wwVacation)); MAKE_CF_CB(set_wwVacation));
break; break;
case EMS_DEVICE_FLAG_JUNKERS: case EMS_DEVICE_FLAG_JUNKERS:
// FR100 is not writable, see. https://github.com/emsesp/EMS-ESP32/issues/536
// FW500 is not writable, see. https://github.com/emsesp/EMS-ESP32/issues/666
if (has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) { if (has_flags(EMS_DEVICE_FLAG_JUNKERS_OLD)) {
// FR100 is not writable, see. https://github.com/emsesp/EMS-ESP32/issues/536
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dateTime_, DeviceValueType::STRING, FL_(tpl_datetime), FL_(dateTime), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dateTime_, DeviceValueType::STRING, FL_(tpl_datetime), FL_(dateTime), DeviceValueUOM::NONE);
} else { } else {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
@@ -3950,7 +3962,7 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->selTemp, DeviceValueType::SHORT, seltemp_divider, FL_(selRoomTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_temp), 0, 30); register_device_value(tag, &hc->selTemp, DeviceValueType::SHORT, seltemp_divider, FL_(selRoomTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_temp), 0, 30);
} }
register_device_value(tag, &hc->roomTemp, DeviceValueType::SHORT, roomtemp_divider, FL_(roomTemp), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->roomTemp, DeviceValueType::SHORT, roomtemp_divider, FL_(roomTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &hc->climate, DeviceValueType::ENUM, FL_(enum_climate), FL_(climate), DeviceValueUOM::NONE); register_device_value(tag, &hc->climate, DeviceValueType::ENUM, FL_(enum_climate), FL_(climate), DeviceValueUOM::NONE, nullptr, 5, 30);
switch (model) { switch (model) {
case EMS_DEVICE_FLAG_RC10: case EMS_DEVICE_FLAG_RC10:
@@ -4135,9 +4147,16 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode3), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode)); register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode3), FL_(mode), DeviceValueUOM::NONE, MAKE_CF_CB(set_mode));
register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype3), FL_(modetype), DeviceValueUOM::NONE); register_device_value(tag, &hc->modetype, DeviceValueType::ENUM, FL_(enum_modetype3), FL_(modetype), DeviceValueUOM::NONE);
register_device_value( register_device_value(
tag, &hc->daytemp, DeviceValueType::UINT, DeviceValueNumOp::DV_NUMOP_DIV2, FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp)); tag, &hc->daytemp, DeviceValueType::UINT, DeviceValueNumOp::DV_NUMOP_DIV2, FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp), 5, 30);
register_device_value( register_device_value(tag,
tag, &hc->nighttemp, DeviceValueType::UINT, DeviceValueNumOp::DV_NUMOP_DIV2, FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp)); &hc->nighttemp,
DeviceValueType::UINT,
DeviceValueNumOp::DV_NUMOP_DIV2,
FL_(nighttemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_nighttemp),
5,
30);
register_device_value(tag, &hc->designtemp, DeviceValueType::UINT, FL_(designtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_designtemp), 30, 90); register_device_value(tag, &hc->designtemp, DeviceValueType::UINT, FL_(designtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_designtemp), 30, 90);
register_device_value(tag, register_device_value(tag,
&hc->offsettemp, &hc->offsettemp,
@@ -4154,7 +4173,9 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
DeviceValueNumOp::DV_NUMOP_DIV2, DeviceValueNumOp::DV_NUMOP_DIV2,
FL_(holidaytemp), FL_(holidaytemp),
DeviceValueUOM::DEGREES, DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_holidaytemp)); MAKE_CF_CB(set_holidaytemp),
5,
30);
register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, FL_(targetflowtemp), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, FL_(targetflowtemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &hc->summertemp, DeviceValueType::UINT, FL_(summertemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_summertemp), 9, 25); register_device_value(tag, &hc->summertemp, DeviceValueType::UINT, FL_(summertemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_summertemp), 9, 25);
register_device_value(tag, &hc->summermode, DeviceValueType::ENUM, FL_(enum_summer), FL_(summermode), DeviceValueUOM::NONE); register_device_value(tag, &hc->summermode, DeviceValueType::ENUM, FL_(enum_summer), FL_(summermode), DeviceValueUOM::NONE);
@@ -4183,7 +4204,9 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
DeviceValueNumOp::DV_NUMOP_DIV2, DeviceValueNumOp::DV_NUMOP_DIV2,
FL_(tempautotemp), FL_(tempautotemp),
DeviceValueUOM::DEGREES, DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_tempautotemp)); MAKE_CF_CB(set_tempautotemp),
0,
30);
register_device_value(tag, &hc->noreducetemp, DeviceValueType::INT, FL_(noreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_noreducetemp), -30, 10); register_device_value(tag, &hc->noreducetemp, DeviceValueType::INT, FL_(noreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_noreducetemp), -30, 10);
register_device_value(tag, &hc->reducetemp, DeviceValueType::INT, FL_(reducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_reducetemp), -20, 10); register_device_value(tag, &hc->reducetemp, DeviceValueType::INT, FL_(reducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_reducetemp), -20, 10);
register_device_value(tag, &hc->vacreducetemp, DeviceValueType::INT, FL_(vacreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_vacreducetemp), -20, 10); register_device_value(tag, &hc->vacreducetemp, DeviceValueType::INT, FL_(vacreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_vacreducetemp), -20, 10);

View File

@@ -25,7 +25,7 @@ namespace emsesp {
class Thermostat : public EMSdevice { class Thermostat : public EMSdevice {
public: public:
Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand); Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand);
class HeatingCircuit { class HeatingCircuit {
public: public:
HeatingCircuit(const uint8_t hc_num, const uint8_t model) HeatingCircuit(const uint8_t hc_num, const uint8_t model)

View File

@@ -44,69 +44,69 @@ bool EMSdevice::has_entities() const {
} }
std::string EMSdevice::tag_to_string(uint8_t tag) { std::string EMSdevice::tag_to_string(uint8_t tag) {
return read_flash_string(DeviceValue::DeviceValueTAG_s[tag]); return (DeviceValue::DeviceValueTAG_s[tag]);
} }
std::string EMSdevice::tag_to_mqtt(uint8_t tag) { std::string EMSdevice::tag_to_mqtt(uint8_t tag) {
return read_flash_string(DeviceValue::DeviceValueTAG_mqtt[tag]); return (DeviceValue::DeviceValueTAG_mqtt[tag]);
} }
// convert UOM to a string - these don't need translating // convert UOM to a string - these don't need translating
std::string EMSdevice::uom_to_string(uint8_t uom) { std::string EMSdevice::uom_to_string(uint8_t uom) {
if (EMSESP::system_.fahrenheit() && (uom == DeviceValueUOM::DEGREES || uom == DeviceValueUOM::DEGREES_R)) { if (EMSESP::system_.fahrenheit() && (uom == DeviceValueUOM::DEGREES || uom == DeviceValueUOM::DEGREES_R)) {
return read_flash_string(DeviceValue::DeviceValueUOM_s[DeviceValueUOM::FAHRENHEIT]); return (DeviceValue::DeviceValueUOM_s[DeviceValueUOM::FAHRENHEIT]);
} }
return read_flash_string(DeviceValue::DeviceValueUOM_s[uom]); return (DeviceValue::DeviceValueUOM_s[uom]);
} }
std::string EMSdevice::brand_to_string() const { std::string EMSdevice::brand_to_string() const {
switch (brand_) { switch (brand_) {
case EMSdevice::Brand::BOSCH: case EMSdevice::Brand::BOSCH:
return read_flash_string(F("Bosch")); return ("Bosch");
case EMSdevice::Brand::JUNKERS: case EMSdevice::Brand::JUNKERS:
return read_flash_string(F("Junkers")); return ("Junkers");
case EMSdevice::Brand::BUDERUS: case EMSdevice::Brand::BUDERUS:
return read_flash_string(F("Buderus")); return ("Buderus");
case EMSdevice::Brand::NEFIT: case EMSdevice::Brand::NEFIT:
return read_flash_string(F("Nefit")); return ("Nefit");
case EMSdevice::Brand::SIEGER: case EMSdevice::Brand::SIEGER:
return read_flash_string(F("Sieger")); return ("Sieger");
case EMSdevice::Brand::WORCESTER: case EMSdevice::Brand::WORCESTER:
return read_flash_string(F("Worcester")); return ("Worcester");
case EMSdevice::Brand::IVT: case EMSdevice::Brand::IVT:
return read_flash_string(F("IVT")); return ("IVT");
default: default:
return read_flash_string(F("")); return ("");
} }
} }
// returns the name of the MQTT topic to use for a specific device, without the base // returns the name of the MQTT topic to use for a specific device, without the base
std::string EMSdevice::device_type_2_device_name(const uint8_t device_type) { const char * EMSdevice::device_type_2_device_name(const uint8_t device_type) {
switch (device_type) { switch (device_type) {
case DeviceType::SYSTEM: case DeviceType::SYSTEM:
return read_flash_string(F_(system)); return (F_(system));
case DeviceType::BOILER: case DeviceType::BOILER:
return read_flash_string(F_(boiler)); return (F_(boiler));
case DeviceType::THERMOSTAT: case DeviceType::THERMOSTAT:
return read_flash_string(F_(thermostat)); return (F_(thermostat));
case DeviceType::HEATPUMP: case DeviceType::HEATPUMP:
return read_flash_string(F_(heatpump)); return (F_(heatpump));
case DeviceType::SOLAR: case DeviceType::SOLAR:
return read_flash_string(F_(solar)); return (F_(solar));
case DeviceType::CONNECT: case DeviceType::CONNECT:
return read_flash_string(F_(connect)); return (F_(connect));
case DeviceType::MIXER: case DeviceType::MIXER:
return read_flash_string(F_(mixer)); return (F_(mixer));
case DeviceType::DALLASSENSOR: case DeviceType::DALLASSENSOR:
return read_flash_string(F_(dallassensor)); return (F_(dallassensor));
case DeviceType::ANALOGSENSOR: case DeviceType::ANALOGSENSOR:
return read_flash_string(F_(analogsensor)); return (F_(analogsensor));
case DeviceType::CONTROLLER: case DeviceType::CONTROLLER:
return read_flash_string(F_(controller)); return (F_(controller));
case DeviceType::SWITCH: case DeviceType::SWITCH:
return read_flash_string(F_(switch)); return (F_(switch));
case DeviceType::GATEWAY: case DeviceType::GATEWAY:
return read_flash_string(F_(gateway)); return (F_(gateway));
default: default:
return Helpers::translated_word(FL_(unknown)); return Helpers::translated_word(FL_(unknown));
} }
@@ -193,11 +193,11 @@ uint8_t EMSdevice::decode_brand(uint8_t value) {
std::string EMSdevice::to_string() const { std::string EMSdevice::to_string() const {
// for devices that haven't been lookup yet, don't show all details // for devices that haven't been lookup yet, don't show all details
if (product_id_ == 0) { if (product_id_ == 0) {
return name_ + " (DeviceID:" + Helpers::hextoa(device_id_) + ")"; return std::string(name_) + " (DeviceID:" + Helpers::hextoa(device_id_) + ")";
} }
if (brand_ == Brand::NO_BRAND) { if (brand_ == Brand::NO_BRAND) {
return name_ + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) + ", Version:" + version_ + ")"; return std::string(name_) + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) + ", Version:" + version_ + ")";
} }
return brand_to_string() + " " + name_ + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) + ", Version:" + version_ return brand_to_string() + " " + name_ + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) + ", Version:" + version_
@@ -215,7 +215,7 @@ std::string EMSdevice::to_string_short() const {
// for each telegram that has the fetch value set (true) do a read request // for each telegram that has the fetch value set (true) do a read request
void EMSdevice::fetch_values() { void EMSdevice::fetch_values() {
EMSESP::logger().debug(F("Fetching values for deviceID 0x%02X"), device_id()); EMSESP::logger().debug("Fetching values for deviceID 0x%02X", device_id());
for (const auto & tf : telegram_functions_) { for (const auto & tf : telegram_functions_) {
if (tf.fetch_) { if (tf.fetch_) {
@@ -226,7 +226,7 @@ void EMSdevice::fetch_values() {
// toggle on/off automatic fetch for a telegramID // toggle on/off automatic fetch for a telegramID
void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) { void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) {
EMSESP::logger().debug(F("Toggling fetch for deviceID 0x%02X, telegramID 0x%02X to %d"), device_id(), telegram_id, toggle); EMSESP::logger().debug("Toggling fetch for deviceID 0x%02X, telegramID 0x%02X to %d", device_id(), telegram_id, toggle);
for (auto & tf : telegram_functions_) { for (auto & tf : telegram_functions_) {
if (tf.telegram_type_id_ == telegram_id) { if (tf.telegram_type_id_ == telegram_id) {
@@ -259,14 +259,14 @@ bool EMSdevice::has_tag(const uint8_t tag) const {
// called from the command 'entities' // called from the command 'entities'
void EMSdevice::list_device_entries(JsonObject & output) const { void EMSdevice::list_device_entries(JsonObject & output) const {
for (const auto & dv : devicevalues_) { for (const auto & dv : devicevalues_) {
std::string fullname = dv.get_fullname(); auto fullname = dv.get_fullname();
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.type != DeviceValueType::CMD && !fullname.empty()) { if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.type != DeviceValueType::CMD && !fullname.empty()) {
// if we have a tag prefix it // if we have a tag prefix it
char key[50]; char key[50];
if (!EMSdevice::tag_to_mqtt(dv.tag).empty()) { if (!EMSdevice::tag_to_mqtt(dv.tag).empty()) {
snprintf(key, sizeof(key), "%s.%s", EMSdevice::tag_to_mqtt(dv.tag).c_str(), read_flash_string(dv.short_name).c_str()); snprintf(key, sizeof(key), "%s.%s", EMSdevice::tag_to_mqtt(dv.tag).c_str(), (dv.short_name));
} else { } else {
snprintf(key, sizeof(key), "%s", read_flash_string(dv.short_name).c_str()); snprintf(key, sizeof(key), "%s", (dv.short_name));
} }
JsonArray details = output.createNestedArray(key); JsonArray details = output.createNestedArray(key);
@@ -289,7 +289,7 @@ void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) const {
} }
/* /*
// colored list of type-ids // colored list of type-ids
shell.printf(F(" This %s will listen to telegram type IDs: "), device_type_name().c_str()); shell.printf(" This %s will listen to telegram type IDs: ", device_type_name().c_str());
for (const auto & tf : telegram_functions_) { for (const auto & tf : telegram_functions_) {
if (tf.received_ && !tf.fetch_) { if (tf.received_ && !tf.fetch_) {
shell.printf(COLOR_BRIGHT_GREEN); shell.printf(COLOR_BRIGHT_GREEN);
@@ -298,34 +298,34 @@ void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) const {
} else { } else {
shell.printf(COLOR_BRIGHT_RED); shell.printf(COLOR_BRIGHT_RED);
} }
shell.printf(F("0x%02X "), tf.telegram_type_id_); shell.printf("0x%02X ", tf.telegram_type_id_);
} }
shell.printf(COLOR_RESET); shell.printf(COLOR_RESET);
*/ */
shell.printf(F(" Received telegram type IDs: ")); shell.printf(" Received telegram type IDs: ");
for (const auto & tf : telegram_functions_) { for (const auto & tf : telegram_functions_) {
if (tf.received_ && !tf.fetch_) { if (tf.received_ && !tf.fetch_) {
shell.printf(F("0x%02X "), tf.telegram_type_id_); shell.printf("0x%02X ", tf.telegram_type_id_);
} }
} }
shell.println(); shell.println();
shell.printf(F(" Fetched telegram type IDs: ")); shell.printf(" Fetched telegram type IDs: ");
for (const auto & tf : telegram_functions_) { for (const auto & tf : telegram_functions_) {
if (tf.fetch_) { if (tf.fetch_) {
shell.printf(F("0x%02X "), tf.telegram_type_id_); shell.printf("0x%02X ", tf.telegram_type_id_);
} }
} }
shell.println(); shell.println();
shell.printf(F(" Pending telegram type IDs: ")); shell.printf(" Pending telegram type IDs: ");
for (const auto & tf : telegram_functions_) { for (const auto & tf : telegram_functions_) {
if (!tf.received_ && !tf.fetch_) { if (!tf.received_ && !tf.fetch_) {
shell.printf(F("0x%02X "), tf.telegram_type_id_); shell.printf("0x%02X ", tf.telegram_type_id_);
} }
} }
shell.println(); shell.println();
shell.printf(F(" Ignored telegram type IDs: ")); shell.printf(" Ignored telegram type IDs: ");
for (auto handlers : handlers_ignored_) { for (auto handlers : handlers_ignored_) {
shell.printf(F("0x%02X "), handlers); shell.printf("0x%02X ", handlers);
} }
shell.println(); shell.println();
} }
@@ -379,7 +379,7 @@ void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) const {
} }
// register a callback function for a specific telegram type // register a callback function for a specific telegram type
void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, const process_function_p f) { void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const char * telegram_type_name, bool fetch, const process_function_p f) {
telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, false, f); telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, false, f);
} }
@@ -390,28 +390,34 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __
// type: one of DeviceValueType // type: one of DeviceValueType
// options: options for enum, which are translated as a list of lists // options: options for enum, which are translated as a list of lists
// options_single: list of names // options_single: list of names
// numeric_operatpr: to divide or multiply, see DeviceValueNumOps:: // numeric_operator: to divide or multiply, see DeviceValueNumOps::
// short_name: used in Mqtt as keys // short_name: used in MQTT as keys
// fullname: used in Web and Console unless empty (nullptr) - can be translated // fullname: used in Web and Console unless empty (nullptr) - can be translated
// uom: unit of measure from DeviceValueUOM // uom: unit of measure from DeviceValueUOM
// has_cmd: true if this is an associated command // has_cmd: true if this is an associated command
// min: min allowed value // min: min allowed value
// max: max allowed value // max: max allowed value
void EMSdevice::add_device_value(uint8_t tag, void EMSdevice::add_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const ** options, const char * const ** options,
const __FlashStringHelper * const * options_single, const char * const * options_single,
int8_t numeric_operator, int8_t numeric_operator,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p f,
int16_t min, int16_t min,
uint16_t max) { uint16_t max) {
bool has_cmd = (f != nullptr); bool has_cmd = (f != nullptr);
auto short_name = name[0]; auto short_name = name[0];
const class __FlashStringHelper * const * fullname = &name[1]; // translations start at index 1
const char * const * fullname;
if (Helpers::count_items(name) == 1) {
fullname = nullptr; // no translations available, use empty to prevent crash
} else {
fullname = &name[1]; // translations start at index 1
}
// initialize the device value depending on it's type // initialize the device value depending on it's type
if (type == DeviceValueType::STRING) { if (type == DeviceValueType::STRING) {
@@ -440,7 +446,7 @@ void EMSdevice::add_device_value(uint8_t tag,
EMSESP::webCustomizationService.read([&](WebCustomization & settings) { EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
for (EntityCustomization entityCustomization : settings.entityCustomizations) { for (EntityCustomization entityCustomization : settings.entityCustomizations) {
if ((entityCustomization.product_id == product_id()) && (entityCustomization.device_id == device_id())) { if ((entityCustomization.product_id == product_id()) && (entityCustomization.device_id == device_id())) {
std::string entity = tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(short_name) : tag_to_string(tag) + "/" + read_flash_string(short_name); std::string entity = tag < DeviceValueTAG::TAG_HC1 ? (short_name) : tag_to_string(tag) + "/" + (short_name);
for (std::string entity_id : entityCustomization.entity_ids) { for (std::string entity_id : entityCustomization.entity_ids) {
// if there is an appended custom name, strip it to get the true entity name // if there is an appended custom name, strip it to get the true entity name
// and extract the new custom name // and extract the new custom name
@@ -472,7 +478,6 @@ void EMSdevice::add_device_value(uint8_t tag,
// add the device entity // add the device entity
devicevalues_.emplace_back( devicevalues_.emplace_back(
device_type_, tag, value_p, type, options, options_single, numeric_operator, short_name, fullname, custom_fullname, uom, has_cmd, min, max, state); device_type_, tag, value_p, type, options, options_single, numeric_operator, short_name, fullname, custom_fullname, uom, has_cmd, min, max, state);
devicevalues_.back().set_custom_minmax();
// add a new command if it has a function attached // add a new command if it has a function attached
if (!has_cmd) { if (!has_cmd) {
@@ -490,108 +495,102 @@ void EMSdevice::add_device_value(uint8_t tag,
} }
// add the command to our library // add the command to our library
// cmd is the short_name and the description is the fullname (not the custom fullname) Command::add(device_type_, short_name, f, fullname, flags);
Command::add(device_type_, short_name, f, Helpers::translated_fword(fullname), flags);
} }
// single list of options // single list of options
void EMSdevice::register_device_value(uint8_t tag, void EMSdevice::register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const * options_single, const char * const * options_single,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f) { const cmd_function_p f) {
// create a multi-list from the options // create a multi-list from the options
add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, 0, 0); add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, 0, 0);
}; };
// single list of options, with no translations, with min and max // single list of options, with no translations, with min and max
void EMSdevice::register_device_value(uint8_t tag, void EMSdevice::register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const * options_single, const char * const * options_single,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p f,
int16_t min, int16_t min,
uint16_t max) { uint16_t max) {
// create a multi-list from the options // create a multi-list from the options
add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, min, max); add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, min, max);
}; };
void EMSdevice::register_device_value(uint8_t tag, void EMSdevice::register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
int8_t numeric_operator, int8_t numeric_operator,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f) { const cmd_function_p f) {
add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, 0, 0); add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, 0, 0);
} }
void EMSdevice::register_device_value(uint8_t tag, void EMSdevice::register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
int8_t numeric_operator, int8_t numeric_operator,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p f,
int16_t min, int16_t min,
uint16_t max) { uint16_t max) {
add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, min, max); add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, min, max);
} }
// no options, no function // no options, no function
void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * name, uint8_t uom, const cmd_function_p f) { void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f) {
add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, 0, 0); add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, 0, 0);
}; };
// no options, with min/max // no options, with min/max
void EMSdevice::register_device_value(uint8_t tag, void EMSdevice::register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p f,
int16_t min, int16_t min,
uint16_t max) { uint16_t max) {
add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, min, max); add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, min, max);
}; };
// function with min and max values // function with min and max values
// adds a new command to the command list // adds a new command to the command list
// in this function we separate out the short and long names and take any translations // in this function we separate out the short and long names and take any translations
void EMSdevice::register_device_value(uint8_t tag, void EMSdevice::register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const ** options, const char * const ** options,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p f,
int16_t min, int16_t min,
uint16_t max) { uint16_t max) {
add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, min, max); add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, min, max);
} }
// function with no min and max values (set to 0) // function with no min and max values (set to 0)
void EMSdevice::register_device_value(uint8_t tag, void EMSdevice::register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const ** options, const char * const ** options,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f) { const cmd_function_p f) {
add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, 0, 0); add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, 0, 0);
} }
// no associated command function, or min/max values // no associated command function, or min/max values
void EMSdevice::register_device_value(uint8_t tag, void EMSdevice::register_device_value(uint8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom) {
void * value_p,
uint8_t type,
const __FlashStringHelper * const ** options,
const __FlashStringHelper * const * name,
uint8_t uom) {
add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, nullptr, 0, 0); add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, nullptr, 0, 0);
} }
@@ -610,7 +609,7 @@ bool EMSdevice::is_readonly(const std::string & cmd, const int8_t id) const {
uint8_t tag = id > 0 ? DeviceValueTAG::TAG_HC1 + id - 1 : DeviceValueTAG::TAG_NONE; uint8_t tag = id > 0 ? DeviceValueTAG::TAG_HC1 + id - 1 : DeviceValueTAG::TAG_NONE;
for (const auto & dv : devicevalues_) { for (const auto & dv : devicevalues_) {
// check command name and tag, id -1 is default hc and only checks name // check command name and tag, id -1 is default hc and only checks name
if (dv.has_cmd && read_flash_string(dv.short_name) == cmd && (dv.tag < DeviceValueTAG::TAG_HC1 || dv.tag == tag || id == -1)) { if (dv.has_cmd && std::string(dv.short_name) == cmd && (dv.tag < DeviceValueTAG::TAG_HC1 || dv.tag == tag || id == -1)) {
return dv.has_state(DeviceValueState::DV_READONLY); return dv.has_state(DeviceValueState::DV_READONLY);
} }
} }
@@ -638,24 +637,14 @@ void EMSdevice::publish_value(void * value_p) const {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) { if (Mqtt::publish_single2cmd()) {
if (dv.tag >= DeviceValueTAG::TAG_HC1) { if (dv.tag >= DeviceValueTAG::TAG_HC1) {
snprintf(topic, snprintf(topic, sizeof(topic), "%s/%s/%s", device_type_2_device_name(device_type_), tag_to_mqtt(dv.tag).c_str(), (dv.short_name));
sizeof(topic),
"%s/%s/%s",
device_type_2_device_name(device_type_).c_str(),
tag_to_mqtt(dv.tag).c_str(),
read_flash_string(dv.short_name).c_str());
} else { } else {
snprintf(topic, sizeof(topic), "%s/%s", device_type_2_device_name(device_type_).c_str(), read_flash_string(dv.short_name).c_str()); snprintf(topic, sizeof(topic), "%s/%s", device_type_2_device_name(device_type_), (dv.short_name));
} }
} else if (Mqtt::is_nested() && dv.tag >= DeviceValueTAG::TAG_HC1) { } else if (Mqtt::is_nested() && dv.tag >= DeviceValueTAG::TAG_HC1) {
snprintf(topic, snprintf(topic, sizeof(topic), "%s/%s/%s", Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), tag_to_mqtt(dv.tag).c_str(), (dv.short_name));
sizeof(topic),
"%s/%s/%s",
Mqtt::tag_to_topic(device_type_, dv.tag).c_str(),
tag_to_mqtt(dv.tag).c_str(),
read_flash_string(dv.short_name).c_str());
} else { } else {
snprintf(topic, sizeof(topic), "%s/%s", Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), read_flash_string(dv.short_name).c_str()); snprintf(topic, sizeof(topic), "%s/%s", Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), (dv.short_name));
} }
int8_t num_op = dv.numeric_operator; int8_t num_op = dv.numeric_operator;
@@ -673,7 +662,7 @@ void EMSdevice::publish_value(void * value_p) const {
Helpers::render_value(payload, *(uint8_t *)(value_p), 0); Helpers::render_value(payload, *(uint8_t *)(value_p), 0);
} else { } else {
auto enum_str = Helpers::translated_word(dv.options[*(uint8_t *)(value_p)]); auto enum_str = Helpers::translated_word(dv.options[*(uint8_t *)(value_p)]);
strlcpy(payload, enum_str.c_str(), sizeof(payload)); strlcpy(payload, enum_str, sizeof(payload));
} }
} }
break; break;
@@ -717,7 +706,6 @@ void EMSdevice::publish_value(void * value_p) const {
// looks up the UOM for a given key from the device value table // looks up the UOM for a given key from the device value table
// key is the fullname // key is the fullname
// TODO really should be using the shortname as the identifier
std::string EMSdevice::get_value_uom(const char * key) const { std::string EMSdevice::get_value_uom(const char * key) const {
// the key may have a TAG string prefixed at the beginning. If so, remove it // the key may have a TAG string prefixed at the beginning. If so, remove it
char new_key[80]; char new_key[80];
@@ -725,11 +713,12 @@ std::string EMSdevice::get_value_uom(const char * key) const {
char * key_p = new_key; char * key_p = new_key;
for (uint8_t i = 0; i < DeviceValue::tag_count; i++) { for (uint8_t i = 0; i < DeviceValue::tag_count; i++) {
auto tag = read_flash_string(DeviceValue::DeviceValueTAG_s[i]); auto tag = (DeviceValue::DeviceValueTAG_s[i]);
if (!tag.empty()) { if (tag) {
std::string key2 = key; // copy char to a std::string std::string key2 = key; // copy string to a std::string so we can use the find function
if ((key2.find(tag) != std::string::npos) && (key[tag.length()] == ' ')) { uint8_t length = strlen(tag);
key_p += tag.length() + 1; // remove the tag if ((key2.find(tag) != std::string::npos) && (key[length] == ' ')) {
key_p += length + 1; // remove the tag
break; break;
} }
} }
@@ -800,9 +789,9 @@ void EMSdevice::generate_values_web(JsonObject & output) {
} else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) { } else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
obj["v"] = Helpers::transformNumFloat(*(uint16_t *)(dv.value_p), dv.numeric_operator, fahrenheit); obj["v"] = Helpers::transformNumFloat(*(uint16_t *)(dv.value_p), dv.numeric_operator, fahrenheit);
} else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) { } else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
obj["v"] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), dv.numeric_operator, fahrenheit); obj["v"] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), dv.numeric_operator);
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) { } else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
obj["v"] = dv.numeric_operator ? (*(uint32_t *)(dv.value_p) / dv.numeric_operator) : *(uint32_t *)(dv.value_p); obj["v"] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), dv.numeric_operator);
} else { } else {
obj["v"] = ""; // must have a value for sorting to work obj["v"] = ""; // must have a value for sorting to work
} }
@@ -824,7 +813,7 @@ void EMSdevice::generate_values_web(JsonObject & output) {
if (dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY)) { if (dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY)) {
// add the name of the Command function // add the name of the Command function
if (dv.tag >= DeviceValueTAG::TAG_HC1) { if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["c"] = tag_to_mqtt(dv.tag) + "/" + read_flash_string(dv.short_name); obj["c"] = tag_to_mqtt(dv.tag) + "/" + (dv.short_name);
} else { } else {
obj["c"] = dv.short_name; obj["c"] = dv.short_name;
} }
@@ -834,7 +823,7 @@ void EMSdevice::generate_values_web(JsonObject & output) {
JsonArray l = obj.createNestedArray("l"); JsonArray l = obj.createNestedArray("l");
for (uint8_t i = 0; i < dv.options_size; i++) { for (uint8_t i = 0; i < dv.options_size; i++) {
auto enum_str = Helpers::translated_word(dv.options[i]); auto enum_str = Helpers::translated_word(dv.options[i]);
if (!enum_str.empty()) { if (enum_str) {
l.add(enum_str); l.add(enum_str);
} }
} }
@@ -860,7 +849,8 @@ void EMSdevice::generate_values_web(JsonObject & output) {
obj["s"] = Helpers::render_value(s, (float)(-1) * dv.numeric_operator, 0); obj["s"] = Helpers::render_value(s, (float)(-1) * dv.numeric_operator, 0);
} }
int16_t dv_set_min, dv_set_max; int16_t dv_set_min;
uint16_t dv_set_max;
if (dv.get_min_max(dv_set_min, dv_set_max)) { if (dv.get_min_max(dv_set_min, dv_set_max)) {
obj["m"] = Helpers::render_value(s, dv_set_min, 0); obj["m"] = Helpers::render_value(s, dv_set_min, 0);
obj["x"] = Helpers::render_value(s, dv_set_max, 0); obj["x"] = Helpers::render_value(s, dv_set_max, 0);
@@ -900,61 +890,39 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
// handle Integers and Floats // handle Integers and Floats
else { else {
int8_t num_op = dv.numeric_operator;
bool make_float;
if (num_op == 0) {
// no changes to number
make_float = false;
num_op = 1; // so it gets *1
} else if (num_op < 0) {
// negative numbers, convert to a positive multiplier
make_float = false;
num_op *= -1;
} else {
// has a divider, make it a float
make_float = true;
}
// always convert temperatures to floats with 1 decimal place
if ((dv.uom == DeviceValueUOM::DEGREES) || (dv.uom == DeviceValueUOM::DEGREES_R)) {
make_float = true;
}
if (dv.type == DeviceValueType::INT) { if (dv.type == DeviceValueType::INT) {
obj["v"] = make_float ? Helpers::transformNumFloat(*(int8_t *)(dv.value_p), num_op, fahrenheit) : *(int8_t *)(dv.value_p) * num_op; obj["v"] = Helpers::transformNumFloat(*(int8_t *)(dv.value_p), dv.numeric_operator, fahrenheit);
} else if (dv.type == DeviceValueType::UINT) { } else if (dv.type == DeviceValueType::UINT) {
obj["v"] = make_float ? Helpers::transformNumFloat(*(uint8_t *)(dv.value_p), num_op, fahrenheit) : *(uint8_t *)(dv.value_p) * num_op; obj["v"] = Helpers::transformNumFloat(*(uint8_t *)(dv.value_p), dv.numeric_operator, fahrenheit);
} else if (dv.type == DeviceValueType::SHORT) { } else if (dv.type == DeviceValueType::SHORT) {
obj["v"] = make_float ? Helpers::transformNumFloat(*(int16_t *)(dv.value_p), num_op, fahrenheit) : *(int16_t *)(dv.value_p) * num_op; obj["v"] = Helpers::transformNumFloat(*(int16_t *)(dv.value_p), dv.numeric_operator, fahrenheit);
} else if (dv.type == DeviceValueType::USHORT) { } else if (dv.type == DeviceValueType::USHORT) {
obj["v"] = make_float ? Helpers::transformNumFloat(*(uint16_t *)(dv.value_p), num_op, fahrenheit) : *(uint16_t *)(dv.value_p) * num_op; obj["v"] = Helpers::transformNumFloat(*(uint16_t *)(dv.value_p), dv.numeric_operator, fahrenheit);
} else if (dv.type == DeviceValueType::ULONG) { } else if (dv.type == DeviceValueType::ULONG) {
obj["v"] = make_float ? Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op) : *(uint32_t *)(dv.value_p) * num_op; obj["v"] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), dv.numeric_operator);
} else if (dv.type == DeviceValueType::TIME) { } else if (dv.type == DeviceValueType::TIME) {
// sometimes we need to divide by 60 obj["v"] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), dv.numeric_operator);
obj["v"] = (num_op == DeviceValueNumOp::DV_NUMOP_DIV60) ? (uint32_t)Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op)
: *(uint32_t *)(dv.value_p);
} }
} }
} }
// id holds the shortname and must always have a value for the WebUI table to work // id holds the shortname and must always have a value for the WebUI table to work
if (dv.tag >= DeviceValueTAG::TAG_HC1) { if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["id"] = tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name); obj["id"] = tag_to_string(dv.tag) + "/" + (dv.short_name);
} else { } else {
obj["id"] = read_flash_string(dv.short_name); obj["id"] = (dv.short_name);
} }
// n is the fullname, and can be optional // n is the fullname, and can be optional
// don't add the fullname if its a command // don't add the fullname if its a command
auto fullname = Helpers::translated_word(dv.fullname); auto fullname = Helpers::translated_word(dv.fullname);
if (dv.type != DeviceValueType::CMD) { if (dv.type != DeviceValueType::CMD) {
if (!fullname.empty()) { if (fullname) {
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) { if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
obj["n"] = fullname; obj["n"] = fullname;
} else { } else {
char name[50]; char name[50];
snprintf(name, sizeof(name), "%s %s", tag_to_string(dv.tag).c_str(), fullname.c_str()); snprintf(name, sizeof(name), "%s %s", tag_to_string(dv.tag).c_str(), fullname);
obj["n"] = name; obj["n"] = name;
} }
} }
@@ -965,8 +933,12 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
obj["cn"] = custom_fullname; obj["cn"] = custom_fullname;
} }
} else { } else {
// it's a command obj["n"] = "!" + std::string(fullname); // prefix commands with a !
obj["n"] = "!" + fullname; // prefix ! to fullname for commands }
// add the custom name, is optional
if (!dv.custom_fullname.empty()) {
obj["cn"] = dv.custom_fullname;
} }
obj["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble obj["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble
@@ -986,12 +958,25 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
} }
} }
void EMSdevice::set_climate_minmax(uint8_t tag, int16_t min, uint16_t max) {
for (auto & dv : devicevalues_) {
if (dv.tag == tag && dv.short_name == FL_(climate[0])) {
if (dv.min != min || dv.max != max) {
dv.min = min;
dv.max = max;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
Mqtt::publish_ha_climate_config(dv.tag, false, true); // delete topic (remove = true)
}
return;
}
}
}
// set mask per device entity based on the id which is prefixed with the 2 char hex mask value // set mask per device entity based on the id which is prefixed with the 2 char hex mask value
// returns true if the entity has a mask set (not 0 the default) // returns true if the entity has a mask set (not 0 the default)
void EMSdevice::setCustomEntity(const std::string & entity_id) { void EMSdevice::setCustomEntity(const std::string & entity_id) {
for (auto & dv : devicevalues_) { for (auto & dv : devicevalues_) {
std::string entity_name = std::string entity_name = dv.tag < DeviceValueTAG::TAG_HC1 ? (dv.short_name) : tag_to_string(dv.tag) + "/" + (dv.short_name);
dv.tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(dv.short_name) : tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name);
// extra shortname // extra shortname
std::string shortname; std::string shortname;
@@ -1009,7 +994,7 @@ void EMSdevice::setCustomEntity(const std::string & entity_id) {
uint8_t new_mask = Helpers::hextoint(entity_id.substr(0, 2).c_str()); // first character contains mask flags uint8_t new_mask = Helpers::hextoint(entity_id.substr(0, 2).c_str()); // first character contains mask flags
// if it's a new mask, reconfigure HA // if it's a new mask, reconfigure HA
if (Mqtt::ha_enabled() && ((current_mask ^ new_mask) & (DeviceValueState::DV_READONLY >> 4))) { if (Mqtt::ha_enabled() && (has_custom_name || ((current_mask ^ new_mask) & (DeviceValueState::DV_READONLY >> 4)))) {
// remove ha config on change of dv_readonly flag // remove ha config on change of dv_readonly flag
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED); dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
Mqtt::publish_ha_sensor_config(dv, "", "", true); // delete topic (remove = true) Mqtt::publish_ha_sensor_config(dv, "", "", true); // delete topic (remove = true)
@@ -1024,7 +1009,17 @@ void EMSdevice::setCustomEntity(const std::string & entity_id) {
} else { } else {
dv.custom_fullname = ""; dv.custom_fullname = "";
} }
auto min = dv.min;
auto max = dv.max;
// set the min / max
dv.set_custom_minmax(); dv.set_custom_minmax();
if (Mqtt::ha_enabled() && dv.short_name == FL_(seltemp)[0] && (min != dv.min || max != dv.max)) {
set_climate_minmax(dv.tag, dv.min, dv.max);
}
return; return;
} }
} }
@@ -1033,9 +1028,8 @@ void EMSdevice::setCustomEntity(const std::string & entity_id) {
// populate a string vector with entities that have masks set or have a custom name // populate a string vector with entities that have masks set or have a custom name
void EMSdevice::getCustomEntities(std::vector<std::string> & entity_ids) { void EMSdevice::getCustomEntities(std::vector<std::string> & entity_ids) {
for (const auto & dv : devicevalues_) { for (const auto & dv : devicevalues_) {
std::string entity_name = std::string entity_name = dv.tag < DeviceValueTAG::TAG_HC1 ? (dv.short_name) : tag_to_string(dv.tag) + "/" + (dv.short_name);
dv.tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(dv.short_name) : tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name); uint8_t mask = dv.state >> 4;
uint8_t mask = dv.state >> 4;
if (mask || !dv.custom_fullname.empty()) { if (mask || !dv.custom_fullname.empty()) {
if (dv.custom_fullname.empty()) { if (dv.custom_fullname.empty()) {
@@ -1073,9 +1067,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
// search device value with this tag // search device value with this tag
for (auto & dv : devicevalues_) { for (auto & dv : devicevalues_) {
if (strcmp(command_s, Helpers::toLower(read_flash_string(dv.short_name)).c_str()) == 0 && (tag <= 0 || tag == dv.tag)) { if (!strcmp(command_s, dv.short_name) && (tag <= 0 || tag == dv.tag)) {
int8_t num_op = dv.numeric_operator;
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0; uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
const char * type = "type"; const char * type = "type";
@@ -1096,6 +1088,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
json["circuit"] = tag_to_mqtt(dv.tag); json["circuit"] = tag_to_mqtt(dv.tag);
} }
char val[10];
switch (dv.type) { switch (dv.type) {
case DeviceValueType::ENUM: { case DeviceValueType::ENUM: {
if (*(uint8_t *)(dv.value_p) < dv.options_size) { if (*(uint8_t *)(dv.value_p) < dv.options_size) {
@@ -1115,35 +1108,35 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
case DeviceValueType::USHORT: case DeviceValueType::USHORT:
if (Helpers::hasValue(*(uint16_t *)(dv.value_p))) { if (Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
json[value] = Helpers::transformNumFloat(*(uint16_t *)(dv.value_p), num_op, fahrenheit); json[value] = serialized(Helpers::render_value(val, *(uint16_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
} }
json[type] = F_(number); json[type] = F_(number);
break; break;
case DeviceValueType::UINT: case DeviceValueType::UINT:
if (Helpers::hasValue(*(uint8_t *)(dv.value_p))) { if (Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
json[value] = Helpers::transformNumFloat(*(uint8_t *)(dv.value_p), num_op, fahrenheit); json[value] = serialized(Helpers::render_value(val, *(uint8_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
} }
json[type] = F_(number); json[type] = F_(number);
break; break;
case DeviceValueType::SHORT: case DeviceValueType::SHORT:
if (Helpers::hasValue(*(int16_t *)(dv.value_p))) { if (Helpers::hasValue(*(int16_t *)(dv.value_p))) {
json[value] = Helpers::transformNumFloat(*(int16_t *)(dv.value_p), num_op, fahrenheit); json[value] = serialized(Helpers::render_value(val, *(int16_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
} }
json[type] = F_(number); json[type] = F_(number);
break; break;
case DeviceValueType::INT: case DeviceValueType::INT:
if (Helpers::hasValue(*(int8_t *)(dv.value_p))) { if (Helpers::hasValue(*(int8_t *)(dv.value_p))) {
json[value] = Helpers::transformNumFloat(*(int8_t *)(dv.value_p), num_op, fahrenheit); json[value] = serialized(Helpers::render_value(val, *(int8_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
} }
json[type] = F_(number); json[type] = F_(number);
break; break;
case DeviceValueType::ULONG: case DeviceValueType::ULONG:
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) { if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
json[value] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op); json[value] = serialized(Helpers::render_value(val, *(uint32_t *)(dv.value_p), dv.numeric_operator));
} }
json[type] = F_(number); json[type] = F_(number);
break; break;
@@ -1160,12 +1153,12 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
json[value] = Helpers::render_boolean(s, value_b); json[value] = Helpers::render_boolean(s, value_b);
} }
} }
json[type] = F("boolean"); json[type] = ("boolean");
break; break;
case DeviceValueType::TIME: case DeviceValueType::TIME:
if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) { if (Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
json[value] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op); json[value] = serialized(Helpers::render_value(val, *(uint32_t *)(dv.value_p), dv.numeric_operator));
} }
json[type] = F_(number); json[type] = F_(number);
break; break;
@@ -1174,7 +1167,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
if (Helpers::hasValue((char *)(dv.value_p))) { if (Helpers::hasValue((char *)(dv.value_p))) {
json[value] = (char *)(dv.value_p); json[value] = (char *)(dv.value_p);
} }
json[type] = F("string"); json[type] = ("string");
break; break;
case DeviceValueType::CMD: case DeviceValueType::CMD:
@@ -1192,11 +1185,14 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
break; break;
} }
// set the min and max // set the min and max only for commands
int16_t dv_set_min, dv_set_max; if (dv.has_cmd) {
if (dv.get_min_max(dv_set_min, dv_set_max)) { int16_t dv_set_min;
json["min"] = dv_set_min; uint16_t dv_set_max;
json["max"] = dv_set_max; if (dv.get_min_max(dv_set_min, dv_set_max)) {
json["min"] = dv_set_min;
json["max"] = dv_set_max;
}
} }
// add uom if it's not a " " (single space) // add uom if it's not a " " (single space)
@@ -1216,7 +1212,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
// if we're filtering on an attribute, go find it // if we're filtering on an attribute, go find it
if (attribute_s) { if (attribute_s) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
EMSESP::logger().debug(F("[DEBUG] Attribute '%s'"), attribute_s); EMSESP::logger().debug("[DEBUG] Attribute '%s'", attribute_s);
#endif #endif
if (json.containsKey(attribute_s)) { if (json.containsKey(attribute_s)) {
JsonVariant data = json[attribute_s]; JsonVariant data = json[attribute_s];
@@ -1291,7 +1287,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
strlcpy(name, fullname.c_str(), sizeof(name)); // use full name strlcpy(name, fullname.c_str(), sizeof(name)); // use full name
} }
} else { } else {
strlcpy(name, read_flash_string(dv.short_name).c_str(), sizeof(name)); // use short name strlcpy(name, (dv.short_name), sizeof(name)); // use short name
// if we have a tag, and its different to the last one create a nested object. only for hc, wwc and hs // if we have a tag, and its different to the last one create a nested object. only for hc, wwc and hs
if (dv.tag != old_tag) { if (dv.tag != old_tag) {
@@ -1316,6 +1312,9 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
json[name] = value_b; json[name] = value_b;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) { } else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
json[name] = value_b ? 1 : 0; json[name] = value_b ? 1 : 0;
} else {
char s[7];
json[name] = Helpers::render_boolean(s, value_b);
} }
} }
@@ -1341,64 +1340,21 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
: (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES) ? 2
: (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1
: 0; : 0;
char val[10];
int8_t num_op = dv.numeric_operator;
bool make_float;
if (num_op == 0) {
// no changes to number
make_float = false;
num_op = 1; // so it gets *1
} else if (num_op < 0) {
// negative numbers, convert to a positive multiplier
make_float = false;
num_op *= -1;
} else {
// has a divider, make it a float
make_float = true;
}
// always convert temperatures to floats with 1 decimal place
if ((dv.uom == DeviceValueUOM::DEGREES) || (dv.uom == DeviceValueUOM::DEGREES_R)) {
make_float = true;
}
if (dv.type == DeviceValueType::INT) { if (dv.type == DeviceValueType::INT) {
if (make_float) { json[name] = serialized(Helpers::render_value(val, *(int8_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
json[name] = Helpers::transformNumFloat(*(int8_t *)(dv.value_p), num_op, fahrenheit);
} else {
json[name] = *(int8_t *)(dv.value_p) * num_op;
}
} else if (dv.type == DeviceValueType::UINT) { } else if (dv.type == DeviceValueType::UINT) {
if (make_float) { json[name] = serialized(Helpers::render_value(val, *(uint8_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
json[name] = Helpers::transformNumFloat(*(uint8_t *)(dv.value_p), num_op, fahrenheit);
} else {
json[name] = *(uint8_t *)(dv.value_p) * num_op;
}
} else if (dv.type == DeviceValueType::SHORT) { } else if (dv.type == DeviceValueType::SHORT) {
if (make_float) { json[name] = serialized(Helpers::render_value(val, *(int16_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
json[name] = Helpers::transformNumFloat(*(int16_t *)(dv.value_p), num_op, fahrenheit);
} else {
json[name] = *(int16_t *)(dv.value_p) * num_op;
}
} else if (dv.type == DeviceValueType::USHORT) { } else if (dv.type == DeviceValueType::USHORT) {
if (make_float) { json[name] = serialized(Helpers::render_value(val, *(uint16_t *)(dv.value_p), dv.numeric_operator, fahrenheit));
json[name] = Helpers::transformNumFloat(*(uint16_t *)(dv.value_p), num_op, fahrenheit);
} else {
json[name] = *(uint16_t *)(dv.value_p) * num_op;
}
} else if (dv.type == DeviceValueType::ULONG) { } else if (dv.type == DeviceValueType::ULONG) {
if (make_float) { json[name] = serialized(Helpers::render_value(val, *(uint32_t *)(dv.value_p), dv.numeric_operator));
json[name] = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op, fahrenheit);
} else {
json[name] = *(uint32_t *)(dv.value_p) * num_op;
}
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) { } else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
uint32_t time_value; uint32_t time_value = *(uint32_t *)(dv.value_p);
if (num_op == DeviceValueNumOp::DV_NUMOP_DIV60) { if (dv.numeric_operator == DeviceValueNumOp::DV_NUMOP_DIV60) {
// sometimes we need to divide by 60 time_value /= 60; // sometimes we need to divide by 60
time_value = Helpers::transformNumFloat(*(uint32_t *)(dv.value_p), num_op);
} else {
time_value = *(uint32_t *)(dv.value_p);
} }
if (output_target == OUTPUT_TARGET::API_VERBOSE || output_target == OUTPUT_TARGET::CONSOLE) { if (output_target == OUTPUT_TARGET::API_VERBOSE || output_target == OUTPUT_TARGET::CONSOLE) {
char time_s[60]; char time_s[60];
@@ -1406,11 +1362,11 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
sizeof(time_s), sizeof(time_s),
"%d %s %d %s %d %s", "%d %s %d %s %d %s",
(time_value / 1440), (time_value / 1440),
Helpers::translated_word(FL_(days)).c_str(), Helpers::translated_word(FL_(days)),
((time_value % 1440) / 60), ((time_value % 1440) / 60),
Helpers::translated_word(FL_(hours)).c_str(), Helpers::translated_word(FL_(hours)),
(time_value % 60), (time_value % 60),
Helpers::translated_word(FL_(minutes)).c_str()); Helpers::translated_word(FL_(minutes)));
json[name] = time_s; json[name] = time_s;
} else { } else {
json[name] = time_value; json[name] = time_value;
@@ -1474,12 +1430,12 @@ void EMSdevice::mqtt_ha_entity_config_create() {
if (*(int8_t *)(dv.value_p) == 1 && (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) || dv.has_state(DeviceValueState::DV_HA_CLIMATE_NO_RT))) { if (*(int8_t *)(dv.value_p) == 1 && (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) || dv.has_state(DeviceValueState::DV_HA_CLIMATE_NO_RT))) {
dv.remove_state(DeviceValueState::DV_HA_CLIMATE_NO_RT); dv.remove_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED); dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
Mqtt::publish_ha_climate_config(dv.tag, true); Mqtt::publish_ha_climate_config(dv.tag, true, false, dv.min, dv.max);
} else if (*(int8_t *)(dv.value_p) == 0 } else if (*(int8_t *)(dv.value_p) == 0
&& (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) || !dv.has_state(DeviceValueState::DV_HA_CLIMATE_NO_RT))) { && (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) || !dv.has_state(DeviceValueState::DV_HA_CLIMATE_NO_RT))) {
dv.add_state(DeviceValueState::DV_HA_CLIMATE_NO_RT); dv.add_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED); dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
Mqtt::publish_ha_climate_config(dv.tag, false); Mqtt::publish_ha_climate_config(dv.tag, false, false, dv.min, dv.max);
} }
} }
if (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) && (dv.type != DeviceValueType::CMD) && dv.has_state(DeviceValueState::DV_ACTIVE) if (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) && (dv.type != DeviceValueType::CMD) && dv.has_state(DeviceValueState::DV_ACTIVE)
@@ -1517,14 +1473,14 @@ bool EMSdevice::has_telegram_id(uint16_t id) const {
std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegram) const { std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegram) const {
// see if it's one of the common ones, like Version // see if it's one of the common ones, like Version
if (telegram->type_id == EMS_TYPE_VERSION) { if (telegram->type_id == EMS_TYPE_VERSION) {
return read_flash_string(F("Version")); return ("Version");
} else if (telegram->type_id == EMS_TYPE_UBADevices) { } else if (telegram->type_id == EMS_TYPE_UBADevices) {
return read_flash_string(F("UBADevices")); return ("UBADevices");
} }
for (const auto & tf : telegram_functions_) { for (const auto & tf : telegram_functions_) {
if ((tf.telegram_type_id_ == telegram->type_id) && (telegram->type_id != 0xFF)) { if ((tf.telegram_type_id_ == telegram->type_id) && (telegram->type_id != 0xFF)) {
return read_flash_string(tf.telegram_type_name_); return (tf.telegram_type_name_);
} }
} }
@@ -1539,7 +1495,7 @@ bool EMSdevice::handle_telegram(std::shared_ptr<const Telegram> telegram) {
// if the data block is empty and we have not received data before, assume that this telegram // if the data block is empty and we have not received data before, assume that this telegram
// is not recognized by the bus master. So remove it from the automatic fetch list // is not recognized by the bus master. So remove it from the automatic fetch list
if (telegram->message_length == 0 && telegram->offset == 0 && !tf.received_) { if (telegram->message_length == 0 && telegram->offset == 0 && !tf.received_) {
EMSESP::logger().debug(F("This telegram (%s) is not recognized by the EMS bus"), read_flash_string(tf.telegram_type_name_).c_str()); EMSESP::logger().debug(("This telegram (%s) is not recognized by the EMS bus"), (tf.telegram_type_name_));
tf.fetch_ = false; tf.fetch_ = false;
return false; return false;
} }

View File

@@ -34,7 +34,7 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20; static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20;
// device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc.. // device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc..
EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand) EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
: device_type_(device_type) : device_type_(device_type)
, device_id_(device_id) , device_id_(device_id)
, product_id_(product_id) , product_id_(product_id)
@@ -46,11 +46,11 @@ class EMSdevice {
std::string device_type_name() const; std::string device_type_name() const;
static std::string device_type_2_device_name(const uint8_t device_type); static const char * device_type_2_device_name(const uint8_t device_type);
static uint8_t device_name_2_device_type(const char * topic); static uint8_t device_name_2_device_type(const char * topic);
static std::string uom_to_string(uint8_t uom); static std::string uom_to_string(uint8_t uom);
static std::string tag_to_string(uint8_t tag); static std::string tag_to_string(uint8_t tag);
static std::string tag_to_mqtt(uint8_t tag); static std::string tag_to_mqtt(uint8_t tag);
bool has_tag(const uint8_t tag) const; bool has_tag(const uint8_t tag) const;
@@ -104,11 +104,11 @@ class EMSdevice {
return brand_; return brand_;
} }
inline void name(const std::string & name) { inline void name(const char * name) {
name_ = name; name_ = name;
} }
inline std::string name() const { inline const char * name() const {
return name_; return name_;
} }
@@ -187,12 +187,13 @@ class EMSdevice {
void list_device_entries(JsonObject & output) const; void list_device_entries(JsonObject & output) const;
void add_handlers_ignored(const uint16_t handler); void add_handlers_ignored(const uint16_t handler);
void set_climate_minmax(uint8_t tag, int16_t min, uint16_t max);
void setCustomEntity(const std::string & entity_id); void setCustomEntity(const std::string & entity_id);
void getCustomEntities(std::vector<std::string> & entity_ids); void getCustomEntities(std::vector<std::string> & entity_ids);
using process_function_p = std::function<void(std::shared_ptr<const Telegram>)>; using process_function_p = std::function<void(std::shared_ptr<const Telegram>)>;
void register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, const process_function_p cb); void register_telegram_type(const uint16_t telegram_type_id, const char * telegram_type_name, bool fetch, const process_function_p cb);
bool handle_telegram(std::shared_ptr<const Telegram> telegram); bool handle_telegram(std::shared_ptr<const Telegram> telegram);
std::string get_value_uom(const char * key) const; std::string get_value_uom(const char * key) const;
@@ -204,93 +205,77 @@ class EMSdevice {
void generate_values_web(JsonObject & output); void generate_values_web(JsonObject & output);
void generate_values_web_customization(JsonArray & output); void generate_values_web_customization(JsonArray & output);
void add_device_value(uint8_t tag, void add_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const ** options, const char * const ** options,
const __FlashStringHelper * const * options_single, const char * const * options_single,
int8_t numeric_operator, int8_t numeric_operator,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p f,
int16_t min, int16_t min,
uint16_t max); uint16_t max);
void register_device_value(uint8_t tag, void register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const ** options, const char * const ** options,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p f,
int16_t min, int16_t min,
uint16_t max); uint16_t max);
void register_device_value(uint8_t tag, void
void * value_p, register_device_value(uint8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom, const cmd_function_p f);
uint8_t type,
const __FlashStringHelper * const ** options,
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f);
void register_device_value(uint8_t tag, void register_device_value(uint8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom);
void * value_p,
uint8_t type,
const __FlashStringHelper * const ** options,
const __FlashStringHelper * const * name,
uint8_t uom);
void register_device_value(uint8_t tag, void register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
int8_t numeric_operator, int8_t numeric_operator,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f = nullptr); const cmd_function_p f = nullptr);
void register_device_value(uint8_t tag, void register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
int8_t numeric_operator, int8_t numeric_operator,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p f,
int16_t min, int16_t min,
uint16_t max); uint16_t max);
// single list of options // single list of options
void register_device_value(uint8_t tag, void register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const * options_single, const char * const * options_single,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f = nullptr); const cmd_function_p f = nullptr);
// single list of options, with no translations, with min and max // single list of options, with no translations, with min and max
void register_device_value(uint8_t tag, void register_device_value(uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const * options_single, const char * const * options_single,
const __FlashStringHelper * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p f,
int16_t min, int16_t min,
uint16_t max); uint16_t max);
// no options, optional function f // no options, optional function f
void register_device_value(uint8_t tag, void * value_p, uint8_t type, const __FlashStringHelper * const * name, uint8_t uom, const cmd_function_p f = nullptr); void register_device_value(uint8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f = nullptr);
// no options, with min/max // no options, with min/max
void register_device_value(uint8_t tag, void
void * value_p, register_device_value(uint8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f, int16_t min, uint16_t max);
uint8_t type,
const __FlashStringHelper * const * name,
uint8_t uom,
const cmd_function_p f,
int16_t min,
uint16_t max);
void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) const; void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) const;
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) const; void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) const;
@@ -424,27 +409,27 @@ class EMSdevice {
bool has_entities() const; bool has_entities() const;
private: private:
uint8_t unique_id_; uint8_t unique_id_;
uint8_t device_type_ = DeviceType::SYSTEM; uint8_t device_type_ = DeviceType::SYSTEM;
uint8_t device_id_ = 0; uint8_t device_id_ = 0;
uint8_t product_id_ = 0; uint8_t product_id_ = 0;
char version_[6]; char version_[6];
std::string name_; // the long name for the EMS model const char * name_; // the long name for the EMS model
uint8_t flags_ = 0; uint8_t flags_ = 0;
uint8_t brand_ = Brand::NO_BRAND; uint8_t brand_ = Brand::NO_BRAND;
bool ha_config_done_ = false; bool ha_config_done_ = false;
bool has_update_ = false; bool has_update_ = false;
bool ha_config_firstrun_ = true; // this means a first setup of HA is needed after a restart bool ha_config_firstrun_ = true; // this means a first setup of HA is needed after a restart
struct TelegramFunction { struct TelegramFunction {
uint16_t telegram_type_id_; // it's type_id uint16_t telegram_type_id_; // it's type_id
const __FlashStringHelper * telegram_type_name_; // e.g. RC20Message const char * telegram_type_name_; // e.g. RC20Message
bool fetch_; // if this type_id be queried automatically bool fetch_; // if this type_id be queried automatically
bool received_; bool received_;
process_function_p process_function_; process_function_p process_function_;
TelegramFunction(uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, bool received, const process_function_p process_function) TelegramFunction(uint16_t telegram_type_id, const char * telegram_type_name, bool fetch, bool received, const process_function_p process_function)
: telegram_type_id_(telegram_type_id) : telegram_type_id_(telegram_type_id)
, telegram_type_name_(telegram_type_name) , telegram_type_name_(telegram_type_name)
, fetch_(fetch) , fetch_(fetch)

View File

@@ -23,21 +23,21 @@
namespace emsesp { namespace emsesp {
// constructor // constructor
DeviceValue::DeviceValue(uint8_t device_type, DeviceValue::DeviceValue(uint8_t device_type,
uint8_t tag, uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const ** options, const char * const ** options,
const __FlashStringHelper * const * options_single, const char * const * options_single,
int8_t numeric_operator, int8_t numeric_operator,
const __FlashStringHelper * const short_name, const char * const short_name,
const __FlashStringHelper * const * fullname, const char * const * fullname,
std::string & custom_fullname, std::string & custom_fullname,
uint8_t uom, uint8_t uom,
bool has_cmd, bool has_cmd,
int16_t min, int16_t min,
uint16_t max, uint16_t max,
uint8_t state) uint8_t state)
: device_type(device_type) : device_type(device_type)
, tag(tag) , tag(tag)
, value_p(value_p) , value_p(value_p)
@@ -60,17 +60,21 @@ DeviceValue::DeviceValue(uint8_t device_type,
options_size = Helpers::count_items(options); options_size = Helpers::count_items(options);
} }
// set the min/max
set_custom_minmax();
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
// only added for debugging // only added for debugging
Serial.print("registering entity: "); Serial.print(COLOR_BRIGHT_RED_BACKGROUND);
Serial.print(read_flash_string(short_name).c_str()); Serial.print(" registering entity: ");
Serial.print((short_name));
Serial.print("/"); Serial.print("/");
if (!custom_fullname.empty()) { if (!custom_fullname.empty()) {
Serial.print(COLOR_BRIGHT_CYAN); Serial.print(COLOR_BRIGHT_CYAN);
Serial.print(custom_fullname.c_str()); Serial.print(custom_fullname.c_str());
Serial.print(COLOR_RESET); Serial.print(COLOR_RESET);
} else { } else {
Serial.print(Helpers::translated_word(fullname).c_str()); Serial.print(Helpers::translated_word(fullname));
} }
Serial.print(" (#options="); Serial.print(" (#options=");
Serial.print(options_size); Serial.print(options_size);
@@ -83,23 +87,23 @@ DeviceValue::DeviceValue(uint8_t device_type,
Serial.print(" option"); Serial.print(" option");
Serial.print(i + 1); Serial.print(i + 1);
Serial.print(":"); Serial.print(":");
auto str = Helpers::translated_fword(options[i]); auto str = Helpers::translated_word(options[i]);
Serial.print(read_flash_string(str).c_str()); Serial.print(str);
i++; i++;
} }
} else if (options_single != nullptr) { } else if (options_single != nullptr) {
Serial.print("option1:!"); Serial.print("option1:!");
Serial.print(read_flash_string(options_single[0]).c_str()); Serial.print((options_single[0]));
Serial.print("!"); Serial.print("!");
} }
Serial.println(""); Serial.println(COLOR_RESET);
#endif #endif
} }
// mapping of UOM, to match order in DeviceValueUOM enum emsdevice.h // mapping of UOM, to match order in DeviceValueUOM enum emsdevice.h
// also maps to DeviceValueUOM in interface/src/project/types.ts for the Web UI // also maps to DeviceValueUOM in interface/src/project/types.ts for the Web UI
// must be an int of 4 bytes, 32bit aligned // must be an int of 4 bytes, 32bit aligned
const __FlashStringHelper * DeviceValue::DeviceValueUOM_s[] __attribute__((__aligned__(sizeof(uint32_t)))) PROGMEM = { const char * DeviceValue::DeviceValueUOM_s[] = {
F_(uom_blank), F_(uom_blank),
F_(uom_degrees), F_(uom_degrees),
@@ -125,7 +129,7 @@ const __FlashStringHelper * DeviceValue::DeviceValueUOM_s[] __attribute__((__ali
// mapping of TAGs, to match order in DeviceValueTAG enum in emsdevice.h // mapping of TAGs, to match order in DeviceValueTAG enum in emsdevice.h
// must be an int of 4 bytes, 32bit aligned // must be an int of 4 bytes, 32bit aligned
const __FlashStringHelper * const DeviceValue::DeviceValueTAG_s[] PROGMEM = { const char * const DeviceValue::DeviceValueTAG_s[] = {
F_(tag_none), // "" F_(tag_none), // ""
F_(tag_heartbeat), // "" F_(tag_heartbeat), // ""
@@ -171,7 +175,7 @@ const __FlashStringHelper * const DeviceValue::DeviceValueTAG_s[] PROGMEM = {
}; };
// MQTT topics derived from tags // MQTT topics derived from tags
const __FlashStringHelper * const DeviceValue::DeviceValueTAG_mqtt[] PROGMEM = { const char * const DeviceValue::DeviceValueTAG_mqtt[] = {
F_(tag_none), // "" F_(tag_none), // ""
F_(heartbeat), // "heartbeat" F_(heartbeat), // "heartbeat"
@@ -217,7 +221,7 @@ const __FlashStringHelper * const DeviceValue::DeviceValueTAG_mqtt[] PROGMEM = {
}; };
// count #tags once at compile time // count #tags once at compile time
size_t DeviceValue::tag_count = sizeof(DeviceValue::DeviceValueTAG_s) / sizeof(__FlashStringHelper *); size_t DeviceValue::tag_count = sizeof(DeviceValue::DeviceValueTAG_s) / sizeof(char *);
// checks whether the device value has an actual value // checks whether the device value has an actual value
// returns true if its valid // returns true if its valid
@@ -266,7 +270,7 @@ bool DeviceValue::hasValue() const {
// converts to signed int, which means rounding to an whole integer // converts to signed int, which means rounding to an whole integer
// returns false if there is no min/max needed // returns false if there is no min/max needed
// Types BOOL, ENUM, STRING and CMD are not used // Types BOOL, ENUM, STRING and CMD are not used
bool DeviceValue::get_min_max(int16_t & dv_set_min, int16_t & dv_set_max) { bool DeviceValue::get_min_max(int16_t & dv_set_min, uint16_t & dv_set_max) {
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0; uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
// if we have individual limits set already, just do the conversion // if we have individual limits set already, just do the conversion

View File

@@ -25,7 +25,6 @@
#include "helpers.h" // for conversions #include "helpers.h" // for conversions
#include "default_settings.h" // for enum types #include "default_settings.h" // for enum types
#include <uuid/common.h> // for read_flash_string
namespace emsesp { namespace emsesp {
@@ -143,41 +142,41 @@ class DeviceValue {
DV_NUMOP_MUL15 = -15 DV_NUMOP_MUL15 = -15
}; };
uint8_t device_type; // EMSdevice::DeviceType uint8_t device_type; // EMSdevice::DeviceType
uint8_t tag; // DeviceValueTAG::* uint8_t tag; // DeviceValueTAG::*
void * value_p; // pointer to variable of any type void * value_p; // pointer to variable of any type
uint8_t type; // DeviceValueType::* uint8_t type; // DeviceValueType::*
const __FlashStringHelper * const ** options; // options as a flash char array const char * const ** options; // options as a flash char array
const __FlashStringHelper * const * options_single; // options are not translated const char * const * options_single; // options are not translated
int8_t numeric_operator; int8_t numeric_operator;
uint8_t options_size; // number of options in the char array, calculated uint8_t options_size; // number of options in the char array, calculated
const __FlashStringHelper * const short_name; // used in MQTT and API const char * const short_name; // used in MQTT and API
const __FlashStringHelper * const * fullname; // used in Web and Console, is translated const char * const * fullname; // used in Web and Console, is translated
std::string custom_fullname; // optional, from customization std::string custom_fullname; // optional, from customization
uint8_t uom; // DeviceValueUOM::* uint8_t uom; // DeviceValueUOM::*
bool has_cmd; // true if there is a Console/MQTT command which matches the short_name bool has_cmd; // true if there is a Console/MQTT command which matches the short_name
int16_t min; // min range int16_t min; // min range
uint16_t max; // max range uint16_t max; // max range
uint8_t state; // DeviceValueState::* uint8_t state; // DeviceValueState::*
DeviceValue(uint8_t device_type, DeviceValue(uint8_t device_type,
uint8_t tag, uint8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const __FlashStringHelper * const ** options, const char * const ** options,
const __FlashStringHelper * const * options_single, const char * const * options_single,
int8_t numeric_operator, int8_t numeric_operator,
const __FlashStringHelper * const short_name, const char * const short_name,
const __FlashStringHelper * const * fullname, const char * const * fullname,
std::string & custom_fullname, std::string & custom_fullname,
uint8_t uom, uint8_t uom,
bool has_cmd, bool has_cmd,
int16_t min, int16_t min,
uint16_t max, uint16_t max,
uint8_t state); uint8_t state);
bool hasValue() const; bool hasValue() const;
bool get_min_max(int16_t & dv_set_min, int16_t & dv_set_max); bool get_min_max(int16_t & dv_set_min, uint16_t & dv_set_max);
void set_custom_minmax(); void set_custom_minmax();
bool get_custom_min(int16_t & val); bool get_custom_min(int16_t & val);
@@ -199,10 +198,10 @@ class DeviceValue {
return state; return state;
} }
static const __FlashStringHelper * DeviceValueUOM_s[]; static const char * DeviceValueUOM_s[];
static const __FlashStringHelper * const DeviceValueTAG_s[]; static const char * const DeviceValueTAG_s[];
static const __FlashStringHelper * const DeviceValueTAG_mqtt[]; static const char * const DeviceValueTAG_mqtt[];
static size_t tag_count; // # tags static size_t tag_count; // # tags
}; };
}; // namespace emsesp }; // namespace emsesp

View File

@@ -190,7 +190,7 @@ void EMSESP::uart_init() {
if (System::is_valid_gpio(rx_gpio) && System::is_valid_gpio(tx_gpio)) { if (System::is_valid_gpio(rx_gpio) && System::is_valid_gpio(tx_gpio)) {
EMSuart::start(tx_mode, rx_gpio, tx_gpio); // start UART EMSuart::start(tx_mode, rx_gpio, tx_gpio); // start UART
} else { } else {
LOG_WARNING(F("Invalid UART Rx/Tx GPIOs. Check config.")); LOG_WARNING("Invalid UART Rx/Tx GPIOs. Check config.");
} }
txservice_.start(); // sends out request to EMS bus for all devices txservice_.start(); // sends out request to EMS bus for all devices
@@ -234,42 +234,42 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
// EMS bus information // EMS bus information
switch (bus_status()) { switch (bus_status()) {
case BUS_STATUS_OFFLINE: case BUS_STATUS_OFFLINE:
shell.printfln(F("EMS Bus is disconnected.")); shell.printfln("EMS Bus is disconnected.");
break; break;
case BUS_STATUS_TX_ERRORS: case BUS_STATUS_TX_ERRORS:
shell.printfln(F("EMS Bus is connected, but Tx is not stable.")); shell.printfln("EMS Bus is connected, but Tx is not stable.");
break; break;
default: default:
shell.printfln(F("EMS Bus is connected.")); shell.printfln("EMS Bus is connected.");
break; break;
} }
shell.println(); shell.println();
if (bus_status() != BUS_STATUS_OFFLINE) { if (bus_status() != BUS_STATUS_OFFLINE) {
shell.printfln(F("EMS Bus info:")); shell.printfln("EMS Bus info:");
EMSESP::webSettingsService.read([&](WebSettings & settings) { shell.printfln(F(" Tx mode: %d"), settings.tx_mode); }); EMSESP::webSettingsService.read([&](WebSettings & settings) { shell.printfln(" Tx mode: %d", settings.tx_mode); });
shell.printfln(F(" Bus protocol: %s"), EMSbus::is_ht3() ? F("HT3") : F("Buderus")); shell.printfln(" Bus protocol: %s", EMSbus::is_ht3() ? ("HT3") : ("Buderus"));
shell.printfln(F(" #recognized EMS devices: %d"), EMSESP::emsdevices.size()); shell.printfln(" #recognized EMS devices: %d", EMSESP::emsdevices.size());
shell.printfln(F(" #telegrams received: %d"), rxservice_.telegram_count()); shell.printfln(" #telegrams received: %d", rxservice_.telegram_count());
shell.printfln(F(" #read requests sent: %d"), txservice_.telegram_read_count()); shell.printfln(" #read requests sent: %d", txservice_.telegram_read_count());
shell.printfln(F(" #write requests sent: %d"), txservice_.telegram_write_count()); shell.printfln(" #write requests sent: %d", txservice_.telegram_write_count());
shell.printfln(F(" #incomplete telegrams: %d"), rxservice_.telegram_error_count()); shell.printfln(" #incomplete telegrams: %d", rxservice_.telegram_error_count());
shell.printfln(F(" #read fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_read_fail_count()); shell.printfln((" #read fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_read_fail_count());
shell.printfln(F(" #write fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_write_fail_count()); shell.printfln((" #write fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_write_fail_count());
shell.printfln(F(" Rx line quality: %d%%"), rxservice_.quality()); shell.printfln(" Rx line quality: %d%%", rxservice_.quality());
shell.printfln(F(" Tx line quality: %d%%"), (txservice_.read_quality() + txservice_.read_quality()) / 2); shell.printfln(" Tx line quality: %d%%", (txservice_.read_quality() + txservice_.read_quality()) / 2);
shell.println(); shell.println();
} }
// Rx queue // Rx queue
auto rx_telegrams = rxservice_.queue(); auto rx_telegrams = rxservice_.queue();
if (rx_telegrams.empty()) { if (rx_telegrams.empty()) {
shell.printfln(F("Rx Queue is empty")); shell.printfln("Rx Queue is empty");
} else { } else {
shell.printfln(F("Rx Queue (%ld telegram%s):"), rx_telegrams.size(), rx_telegrams.size() == 1 ? "" : "s"); shell.printfln(("Rx Queue (%ld telegram%s):"), rx_telegrams.size(), rx_telegrams.size() == 1 ? "" : "s");
for (const auto & it : rx_telegrams) { for (const auto & it : rx_telegrams) {
shell.printfln(F(" [%02d] %s"), it.id_, pretty_telegram(it.telegram_).c_str()); shell.printfln(" [%02d] %s", it.id_, pretty_telegram(it.telegram_).c_str());
} }
} }
@@ -278,20 +278,20 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
// Tx queue // Tx queue
auto tx_telegrams = txservice_.queue(); auto tx_telegrams = txservice_.queue();
if (tx_telegrams.empty()) { if (tx_telegrams.empty()) {
shell.printfln(F("Tx Queue is empty")); shell.printfln("Tx Queue is empty");
} else { } else {
shell.printfln(F("Tx Queue (%ld telegram%s):"), tx_telegrams.size(), tx_telegrams.size() == 1 ? "" : "s"); shell.printfln(("Tx Queue (%ld telegram%s):"), tx_telegrams.size(), tx_telegrams.size() == 1 ? "" : "s");
std::string op; std::string op;
for (const auto & it : tx_telegrams) { for (const auto & it : tx_telegrams) {
if ((it.telegram_->operation) == Telegram::Operation::TX_RAW) { if ((it.telegram_->operation) == Telegram::Operation::TX_RAW) {
op = read_flash_string(F("RAW ")); op = "RAW ";
} else if ((it.telegram_->operation) == Telegram::Operation::TX_READ) { } else if ((it.telegram_->operation) == Telegram::Operation::TX_READ) {
op = read_flash_string(F("READ ")); op = "READ ";
} else if ((it.telegram_->operation) == Telegram::Operation::TX_WRITE) { } else if ((it.telegram_->operation) == Telegram::Operation::TX_WRITE) {
op = read_flash_string(F("WRITE")); op = "WRITE";
} }
shell.printfln(F(" [%02d%c] %s %s"), it.id_, ((it.retry_) ? '*' : ' '), op.c_str(), pretty_telegram(it.telegram_).c_str()); shell.printfln(" [%02d%c] %s %s", it.id_, ((it.retry_) ? '*' : ' '), op.c_str(), pretty_telegram(it.telegram_).c_str());
} }
} }
@@ -301,7 +301,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
// show EMS device values to the shell console // show EMS device values to the shell console
void EMSESP::show_device_values(uuid::console::Shell & shell) { void EMSESP::show_device_values(uuid::console::Shell & shell) {
if (emsdevices.empty()) { if (emsdevices.empty()) {
shell.printfln(F("No EMS devices detected.")); shell.printfln("No EMS devices detected.");
shell.println(); shell.println();
return; return;
} }
@@ -311,7 +311,7 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_class.first)) { if (emsdevice && (emsdevice->device_type() == device_class.first)) {
// print header // print header
shell.printfln(F("%s: %s (%d)"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str(), emsdevice->count_entities()); shell.printfln(("%s: %s (%d)"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str(), emsdevice->count_entities());
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXLARGE_DYN); // use max size DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXLARGE_DYN); // use max size
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
@@ -359,14 +359,14 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
// show Dallas temperature sensors and Analog sensors // show Dallas temperature sensors and Analog sensors
void EMSESP::show_sensor_values(uuid::console::Shell & shell) { void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
if (dallassensor_.have_sensors()) { if (dallassensor_.have_sensors()) {
shell.printfln(F("Temperature sensors:")); shell.printfln("Temperature sensors:");
char s[10]; char s[10];
char s2[10]; char s2[10];
uint8_t fahrenheit = EMSESP::system_.fahrenheit() ? 2 : 0; uint8_t fahrenheit = EMSESP::system_.fahrenheit() ? 2 : 0;
for (const auto & sensor : dallassensor_.sensors()) { for (const auto & sensor : dallassensor_.sensors()) {
if (Helpers::hasValue(sensor.temperature_c)) { if (Helpers::hasValue(sensor.temperature_c)) {
shell.printfln(F(" %s: %s%s °%c%s (offset %s, ID: %s)"), shell.printfln((" %s: %s%s °%c%s (offset %s, ID: %s)"),
sensor.name().c_str(), sensor.name().c_str(),
COLOR_BRIGHT_GREEN, COLOR_BRIGHT_GREEN,
Helpers::render_value(s, sensor.temperature_c, 10, fahrenheit), Helpers::render_value(s, sensor.temperature_c, 10, fahrenheit),
@@ -375,10 +375,7 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
Helpers::render_value(s2, sensor.offset(), 10, fahrenheit), Helpers::render_value(s2, sensor.offset(), 10, fahrenheit),
sensor.id().c_str()); sensor.id().c_str());
} else { } else {
shell.printfln(F(" %s (offset %s, ID: %s)"), shell.printfln((" %s (offset %s, ID: %s)"), sensor.name().c_str(), Helpers::render_value(s, sensor.offset(), 10, fahrenheit), sensor.id().c_str());
sensor.name().c_str(),
Helpers::render_value(s, sensor.offset(), 10, fahrenheit),
sensor.id().c_str());
} }
} }
shell.println(); shell.println();
@@ -387,11 +384,11 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
if (analogsensor_.have_sensors()) { if (analogsensor_.have_sensors()) {
char s[10]; char s[10];
char s2[10]; char s2[10];
shell.printfln(F("Analog sensors:")); shell.printfln("Analog sensors:");
for (const auto & sensor : analogsensor_.sensors()) { for (const auto & sensor : analogsensor_.sensors()) {
switch (sensor.type()) { switch (sensor.type()) {
case AnalogSensor::AnalogType::ADC: case AnalogSensor::AnalogType::ADC:
shell.printfln(F(" %s: %s%s %s%s (Type: ADC, Factor: %s, Offset: %d)"), shell.printfln((" %s: %s%s %s%s (Type: ADC, Factor: %s, Offset: %d)"),
sensor.name().c_str(), sensor.name().c_str(),
COLOR_BRIGHT_GREEN, COLOR_BRIGHT_GREEN,
Helpers::render_value(s, sensor.value(), 2), Helpers::render_value(s, sensor.value(), 2),
@@ -403,7 +400,7 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
default: default:
// case AnalogSensor::AnalogType::DIGITAL_IN: // case AnalogSensor::AnalogType::DIGITAL_IN:
// case AnalogSensor::AnalogType::COUNTER: // case AnalogSensor::AnalogType::COUNTER:
shell.printfln(F(" %s: %s%d%s (Type: %s)"), shell.printfln((" %s: %s%d%s (Type: %s)"),
sensor.name().c_str(), sensor.name().c_str(),
COLOR_BRIGHT_GREEN, COLOR_BRIGHT_GREEN,
(uint16_t)sensor.value(), // as int (uint16_t)sensor.value(), // as int
@@ -540,7 +537,7 @@ void EMSESP::publish_device_values(uint8_t device_type) {
} }
if (need_publish) { if (need_publish) {
if (doc.overflowed()) { if (doc.overflowed()) {
LOG_WARNING(F("MQTT buffer overflow, please use individual topics")); LOG_WARNING("MQTT buffer overflow, please use individual topics");
} }
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json); Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json);
} }
@@ -625,9 +622,9 @@ bool EMSESP::get_device_value_info(JsonObject & root, const char * cmd, const in
// search for recognized device_ids : Me, All, otherwise print hex value // search for recognized device_ids : Me, All, otherwise print hex value
std::string EMSESP::device_tostring(const uint8_t device_id) { std::string EMSESP::device_tostring(const uint8_t device_id) {
if ((device_id & 0x7F) == rxservice_.ems_bus_id()) { if ((device_id & 0x7F) == rxservice_.ems_bus_id()) {
return read_flash_string(F("Me")); return "Me";
} else if (device_id == 0x00) { } else if (device_id == 0x00) {
return read_flash_string(F("All")); return "All";
} else { } else {
char buffer[5]; char buffer[5];
return Helpers::hextoa(buffer, device_id); return Helpers::hextoa(buffer, device_id);
@@ -672,14 +669,14 @@ std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
// check for global/common types like Version & UBADevices // check for global/common types like Version & UBADevices
if (telegram->type_id == EMSdevice::EMS_TYPE_VERSION) { if (telegram->type_id == EMSdevice::EMS_TYPE_VERSION) {
type_name = read_flash_string(F("Version")); type_name = "Version";
} else if (telegram->type_id == EMSdevice::EMS_TYPE_UBADevices) { } else if (telegram->type_id == EMSdevice::EMS_TYPE_UBADevices) {
type_name = read_flash_string(F("UBADevices")); type_name = "UBADevices";
} }
// if we don't know the type show // if we don't know the type show
if (type_name.empty()) { if (type_name.empty()) {
type_name = read_flash_string(F("?")); type_name = "?";
} }
std::string str; std::string str;
@@ -728,7 +725,7 @@ void EMSESP::process_UBADevices(std::shared_ptr<const Telegram> telegram) {
// if we haven't already detected this device, request it's version details, unless its us (EMS-ESP) // if we haven't already detected this device, request it's version details, unless its us (EMS-ESP)
// when the version info is received, it will automagically add the device // when the version info is received, it will automagically add the device
if ((device_id != EMSbus::ems_bus_id()) && !(EMSESP::device_exists(device_id))) { if ((device_id != EMSbus::ems_bus_id()) && !(EMSESP::device_exists(device_id))) {
LOG_DEBUG(F("New EMS device detected with ID 0x%02X. Requesting version information."), device_id); LOG_DEBUG("New EMS device detected with ID 0x%02X. Requesting version information.", device_id);
send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id); send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
} }
} }
@@ -788,7 +785,7 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) { bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
// if watching or reading... // if watching or reading...
if ((telegram->type_id == read_id_) && (telegram->dest == txservice_.ems_bus_id())) { if ((telegram->type_id == read_id_) && (telegram->dest == txservice_.ems_bus_id())) {
LOG_INFO(F("%s"), pretty_telegram(telegram).c_str()); LOG_INFO("%s", pretty_telegram(telegram).c_str());
if (Mqtt::send_response()) { if (Mqtt::send_response()) {
publish_response(telegram); publish_response(telegram);
} }
@@ -800,18 +797,18 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
} else if (watch() == WATCH_ON) { } else if (watch() == WATCH_ON) {
if ((watch_id_ == WATCH_ID_NONE) || (telegram->type_id == watch_id_) if ((watch_id_ == WATCH_ID_NONE) || (telegram->type_id == watch_id_)
|| ((watch_id_ < 0x80) && ((telegram->src == watch_id_) || (telegram->dest == watch_id_)))) { || ((watch_id_ < 0x80) && ((telegram->src == watch_id_) || (telegram->dest == watch_id_)))) {
LOG_NOTICE(F("%s"), pretty_telegram(telegram).c_str()); LOG_NOTICE("%s", pretty_telegram(telegram).c_str());
} else if (!trace_raw_) { } else if (!trace_raw_) {
LOG_TRACE(F("%s"), pretty_telegram(telegram).c_str()); LOG_TRACE("%s", pretty_telegram(telegram).c_str());
} }
} else if (!trace_raw_) { } else if (!trace_raw_) {
LOG_TRACE(F("%s"), pretty_telegram(telegram).c_str()); LOG_TRACE("%s", pretty_telegram(telegram).c_str());
} }
// only process broadcast telegrams or ones sent to us on request // only process broadcast telegrams or ones sent to us on request
// if ((telegram->dest != 0x00) && (telegram->dest != rxservice_.ems_bus_id())) { // if ((telegram->dest != 0x00) && (telegram->dest != rxservice_.ems_bus_id())) {
if (telegram->operation == Telegram::Operation::RX_READ) { if (telegram->operation == Telegram::Operation::RX_READ) {
// LOG_DEBUG(F("read telegram received, not processing")); // LOG_DEBUG("read telegram received, not processing");
return false; return false;
} }
@@ -861,9 +858,9 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
} }
if (!found) { if (!found) {
LOG_DEBUG(F("No telegram type handler found for ID 0x%02X (src 0x%02X)"), telegram->type_id, telegram->src); LOG_DEBUG(("No telegram type handler found for ID 0x%02X (src 0x%02X)"), telegram->type_id, telegram->src);
if (watch() == WATCH_UNKNOWN) { if (watch() == WATCH_UNKNOWN) {
LOG_NOTICE(F("%s"), pretty_telegram(telegram).c_str()); LOG_NOTICE("%s", pretty_telegram(telegram).c_str());
} }
if (!wait_km_ && !knowndevice && (telegram->src != EMSbus::ems_bus_id()) && (telegram->message_length > 0)) { if (!wait_km_ && !knowndevice && (telegram->src != EMSbus::ems_bus_id()) && (telegram->message_length > 0)) {
send_read_request(EMSdevice::EMS_TYPE_VERSION, telegram->src); send_read_request(EMSdevice::EMS_TYPE_VERSION, telegram->src);
@@ -887,12 +884,12 @@ bool EMSESP::device_exists(const uint8_t device_id) {
// for each associated EMS device go and get its system information // for each associated EMS device go and get its system information
void EMSESP::show_devices(uuid::console::Shell & shell) { void EMSESP::show_devices(uuid::console::Shell & shell) {
if (emsdevices.empty()) { if (emsdevices.empty()) {
shell.printfln(F("No EMS devices detected. Try using 'scan devices' from the ems menu.")); shell.printfln("No EMS devices detected. Try using 'scan devices' from the ems menu.");
shell.println(); shell.println();
return; return;
} }
shell.printfln(F("These EMS devices are currently active:")); shell.printfln("These EMS devices are currently active:");
shell.println(); shell.println();
// count the number of thermostats // count the number of thermostats
@@ -908,7 +905,7 @@ void EMSESP::show_devices(uuid::console::Shell & shell) {
for (const auto & device_class : EMSFactory::device_handlers()) { for (const auto & device_class : EMSFactory::device_handlers()) {
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_class.first)) { if (emsdevice && (emsdevice->device_type() == device_class.first)) {
shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str()); shell.printf("%s: %s", emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
shell.println(); shell.println();
emsdevice->show_telegram_handlers(shell); emsdevice->show_telegram_handlers(shell);
@@ -935,7 +932,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
if (product_id == 0) { // update only with valid product_id if (product_id == 0) { // update only with valid product_id
return true; return true;
} }
LOG_DEBUG(F("Updating details for already active deviceID 0x%02X"), device_id); LOG_DEBUG("Updating details for already active deviceID 0x%02X", device_id);
emsdevice->product_id(product_id); emsdevice->product_id(product_id);
emsdevice->version(version); emsdevice->version(version);
// only set brand if it doesn't already exist // only set brand if it doesn't already exist
@@ -945,7 +942,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
// find the name and flags in our database // find the name and flags in our database
for (const auto & device : device_library_) { for (const auto & device : device_library_) {
if (device.product_id == product_id && device.device_type == emsdevice->device_type()) { if (device.product_id == product_id && device.device_type == emsdevice->device_type()) {
emsdevice->name(std::move(read_flash_string(device.name))); emsdevice->name(device.name);
emsdevice->add_flags(device.flags); emsdevice->add_flags(device.flags);
} }
} }
@@ -976,14 +973,13 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
// if we don't recognize the productID report it and add as a generic device // if we don't recognize the productID report it and add as a generic device
if (device_p == nullptr) { if (device_p == nullptr) {
LOG_NOTICE(F("Unrecognized EMS device (deviceID 0x%02X, productID %d). Please report on GitHub."), device_id, product_id); LOG_NOTICE(("Unrecognized EMS device (deviceID 0x%02X, productID %d). Please report on GitHub."), device_id, product_id);
std::string name("unknown");
emsdevices.push_back( emsdevices.push_back(
EMSFactory::add(DeviceType::GENERIC, device_id, product_id, version, name, DeviceFlags::EMS_DEVICE_FLAG_NONE, EMSdevice::Brand::NO_BRAND)); EMSFactory::add(DeviceType::GENERIC, device_id, product_id, version, "unknown", DeviceFlags::EMS_DEVICE_FLAG_NONE, EMSdevice::Brand::NO_BRAND));
return false; // not found return false; // not found
} }
auto name = read_flash_string(device_p->name); auto name = device_p->name;
auto device_type = device_p->device_type; auto device_type = device_p->device_type;
auto flags = device_p->flags; auto flags = device_p->flags;
@@ -1022,14 +1018,14 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
name = "generic boiler"; name = "generic boiler";
device_type = DeviceType::BOILER; device_type = DeviceType::BOILER;
flags = DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP; flags = DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP;
LOG_WARNING(F("Unknown EMS boiler. Using generic profile. Please report on GitHub.")); LOG_WARNING("Unknown EMS boiler. Using generic profile. Please report on GitHub.");
} else { } else {
LOG_WARNING(F("Unrecognized EMS device (device ID 0x%02X, no product ID). Please report on GitHub."), device_id); LOG_WARNING(("Unrecognized EMS device (device ID 0x%02X, no product ID). Please report on GitHub."), device_id);
return false; return false;
} }
} }
LOG_DEBUG(F("Adding new device %s (deviceID 0x%02X, productID %d, version %s)"), name.c_str(), device_id, product_id, version); LOG_DEBUG(("Adding new device %s (deviceID 0x%02X, productID %d, version %s)"), name, device_id, product_id, version);
emsdevices.push_back(EMSFactory::add(device_type, device_id, product_id, version, name, flags, brand)); emsdevices.push_back(EMSFactory::add(device_type, device_id, product_id, version, name, flags, brand));
// assign a unique ID. Note that this is not actual unique after a restart as it's dependent on the order that devices are found // assign a unique ID. Note that this is not actual unique after a restart as it's dependent on the order that devices are found
@@ -1043,7 +1039,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
fetch_device_values(device_id); // go and fetch its data fetch_device_values(device_id); // go and fetch its data
// Print to LOG showing we've added a new device // Print to LOG showing we've added a new device
LOG_INFO(F("Recognized new %s with deviceID 0x%02X"), EMSdevice::device_type_2_device_name(device_type).c_str(), device_id); LOG_INFO("Recognized new %s with deviceID 0x%02X", EMSdevice::device_type_2_device_name(device_type), device_id);
// add command commands for all devices, except for connect, controller and gateway // add command commands for all devices, except for connect, controller and gateway
if ((device_type == DeviceType::CONNECT) || (device_type == DeviceType::CONTROLLER) || (device_type == DeviceType::GATEWAY)) { if ((device_type == DeviceType::CONNECT) || (device_type == DeviceType::CONTROLLER) || (device_type == DeviceType::GATEWAY)) {
@@ -1056,10 +1052,10 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
[device_type](const char * value, const int8_t id, JsonObject & output) { [device_type](const char * value, const int8_t id, JsonObject & output) {
return command_info(device_type, output, id, EMSdevice::OUTPUT_TARGET::API_VERBOSE); return command_info(device_type, output, id, EMSdevice::OUTPUT_TARGET::API_VERBOSE);
}, },
F_(info_cmd)); FL_(info_cmd));
Command::add( Command::add(
device_type, device_type,
F("values"), ("values"),
[device_type](const char * value, const int8_t id, JsonObject & output) { [device_type](const char * value, const int8_t id, JsonObject & output) {
return command_info(device_type, output, id, EMSdevice::OUTPUT_TARGET::API_SHORTNAMES); // HIDDEN command showing short names, used in e.g. /api/boiler return command_info(device_type, output, id, EMSdevice::OUTPUT_TARGET::API_SHORTNAMES); // HIDDEN command showing short names, used in e.g. /api/boiler
}, },
@@ -1069,15 +1065,16 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
device_type, device_type,
F_(commands), F_(commands),
[device_type](const char * value, const int8_t id, JsonObject & output) { return command_commands(device_type, output, id); }, [device_type](const char * value, const int8_t id, JsonObject & output) { return command_commands(device_type, output, id); },
F_(commands_cmd)); FL_(commands_cmd));
Command::add( Command::add(
device_type, device_type,
F_(entities), F_(entities),
[device_type](const char * value, const int8_t id, JsonObject & output) { return command_entities(device_type, output, id); }, [device_type](const char * value, const int8_t id, JsonObject & output) { return command_entities(device_type, output, id); },
F_(entities_cmd)); FL_(entities_cmd));
// MQTT subscribe to the device e.g. "ems-esp/boiler/#" // MQTT subscribe to the device e.g. "ems-esp/boiler/#"
Mqtt::subscribe(device_type, EMSdevice::device_type_2_device_name(device_type) + "/#", nullptr); auto topic = std::string(EMSdevice::device_type_2_device_name(device_type)) + "/#";
Mqtt::subscribe(device_type, topic, nullptr);
return true; return true;
} }
@@ -1178,7 +1175,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data);
#ifdef EMSESP_UART_DEBUG #ifdef EMSESP_UART_DEBUG
// get_uptime is only updated once per loop, does not give the right time // get_uptime is only updated once per loop, does not give the right time
LOG_TRACE(F("[UART_DEBUG] Echo after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str()); LOG_TRACE("[UART_DEBUG] Echo after %d ms: %s", ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
#endif #endif
// add to RxQueue for log/watch // add to RxQueue for log/watch
rxservice_.add(data, length); rxservice_.add(data, length);
@@ -1194,14 +1191,14 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
// if we're waiting on a Write operation, we want a single byte 1 or 4 // if we're waiting on a Write operation, we want a single byte 1 or 4
if ((tx_state == Telegram::Operation::TX_WRITE) && (length == 1)) { if ((tx_state == Telegram::Operation::TX_WRITE) && (length == 1)) {
if (first_value == TxService::TX_WRITE_SUCCESS) { if (first_value == TxService::TX_WRITE_SUCCESS) {
LOG_DEBUG(F("Last Tx write successful")); LOG_DEBUG("Last Tx write successful");
txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok
txservice_.send_poll(); // close the bus txservice_.send_poll(); // close the bus
publish_id_ = txservice_.post_send_query(); // follow up with any post-read if set publish_id_ = txservice_.post_send_query(); // follow up with any post-read if set
txservice_.reset_retry_count(); txservice_.reset_retry_count();
tx_successful = true; tx_successful = true;
} else if (first_value == TxService::TX_WRITE_FAIL) { } else if (first_value == TxService::TX_WRITE_FAIL) {
LOG_ERROR(F("Last Tx write rejected by host")); LOG_ERROR("Last Tx write rejected by host");
txservice_.send_poll(); // close the bus txservice_.send_poll(); // close the bus
txservice_.reset_retry_count(); txservice_.reset_retry_count();
} }
@@ -1210,7 +1207,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
uint8_t src = data[0]; uint8_t src = data[0];
uint8_t dest = data[1]; uint8_t dest = data[1];
if (txservice_.is_last_tx(src, dest)) { if (txservice_.is_last_tx(src, dest)) {
LOG_DEBUG(F("Last Tx read successful")); LOG_DEBUG("Last Tx read successful");
txservice_.increment_telegram_read_count(); txservice_.increment_telegram_read_count();
txservice_.send_poll(); // close the bus txservice_.send_poll(); // close the bus
txservice_.reset_retry_count(); txservice_.reset_retry_count();
@@ -1252,11 +1249,11 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
#ifdef EMSESP_UART_DEBUG #ifdef EMSESP_UART_DEBUG
char s[4]; char s[4];
if (first_value & 0x80) { if (first_value & 0x80) {
LOG_TRACE(F("[UART_DEBUG] next Poll %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_); LOG_TRACE("[UART_DEBUG] next Poll %s after %d ms", Helpers::hextoa(s, first_value), ::millis() - rx_time_);
// time measurement starts here, use millis because get_uptime is only updated once per loop // time measurement starts here, use millis because get_uptime is only updated once per loop
rx_time_ = ::millis(); rx_time_ = ::millis();
} else { } else {
LOG_TRACE(F("[UART_DEBUG] Poll ack %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_); LOG_TRACE("[UART_DEBUG] Poll ack %s after %d ms", Helpers::hextoa(s, first_value), ::millis() - rx_time_);
} }
#endif #endif
// check for poll to us, if so send top message from Tx queue immediately and quit // check for poll to us, if so send top message from Tx queue immediately and quit
@@ -1268,7 +1265,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
return; return;
} else { } else {
#ifdef EMSESP_UART_DEBUG #ifdef EMSESP_UART_DEBUG
LOG_TRACE(F("[UART_DEBUG] Reply after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str()); LOG_TRACE("[UART_DEBUG] Reply after %d ms: %s", ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
#endif #endif
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); // check if there is a message for the roomcontroller Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); // check if there is a message for the roomcontroller
@@ -1293,14 +1290,14 @@ void EMSESP::start() {
webLogService.begin(); // start web log service. now we can start capturing logs to the web log webLogService.begin(); // start web log service. now we can start capturing logs to the web log
#ifdef EMSESP_DEBUG #ifdef EMSESP_DEBUG
LOG_NOTICE(F("System is running in Debug mode")); LOG_NOTICE("System is running in Debug mode");
#endif #endif
LOG_INFO(F("Last system reset reason Core0: %s, Core1: %s"), system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str()); LOG_INFO("Last system reset reason Core0: %s, Core1: %s", system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str());
// do any system upgrades // do any system upgrades
if (system_.check_upgrade()) { if (system_.check_upgrade()) {
LOG_INFO(F("System needs a restart to apply new settings. Please wait.")); LOG_INFO("System needs a restart to apply new settings. Please wait.");
system_.system_restart(); system_.system_restart();
}; };
@@ -1316,7 +1313,7 @@ void EMSESP::start() {
// start all the EMS-ESP services // start all the EMS-ESP services
mqtt_.start(); // mqtt init mqtt_.start(); // mqtt init
system_.start(); // starts commands, led, adc, button, network, syslog & uart system_.start(); // starts commands, led, adc, button, network, syslog & uart
LOG_INFO(F("Starting EMS-ESP version %s (hostname: %s)"), EMSESP_APP_VERSION, system_.hostname().c_str()); // welcome message LOG_INFO(("Starting EMS-ESP version %s (hostname: %s)"), EMSESP_APP_VERSION, system_.hostname().c_str()); // welcome message
shower_.start(); // initialize shower timer and shower alert shower_.start(); // initialize shower timer and shower alert
dallassensor_.start(); // Dallas external sensors dallassensor_.start(); // Dallas external sensors
@@ -1327,7 +1324,7 @@ void EMSESP::start() {
device_library_ = { device_library_ = {
#include "device_library.h" #include "device_library.h"
}; };
LOG_INFO(F("Loaded EMS device library (%d records)"), device_library_.size()); LOG_INFO(("Loaded EMS device library (%d records)"), device_library_.size());
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
Mqtt::on_connect(); // simulate an MQTT connection Mqtt::on_connect(); // simulate an MQTT connection
@@ -1344,6 +1341,7 @@ void EMSESP::scheduled_fetch_values() {
last_fetch_ = uuid::get_uptime(); last_fetch_ = uuid::get_uptime();
no = 1; no = 1;
} }
if (txservice_.tx_queue_empty()) { if (txservice_.tx_queue_empty()) {
uint8_t i = 0; uint8_t i = 0;
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {

View File

@@ -251,10 +251,10 @@ class EMSESP {
static constexpr uint8_t EMS_WAIT_KM_TIMEOUT = 60; // wait one minute static constexpr uint8_t EMS_WAIT_KM_TIMEOUT = 60; // wait one minute
struct Device_record { struct Device_record {
uint8_t product_id; uint8_t product_id;
EMSdevice::DeviceType device_type; EMSdevice::DeviceType device_type;
const __FlashStringHelper * name; const char * name;
uint8_t flags; uint8_t flags;
}; };
static std::vector<Device_record> device_library_; static std::vector<Device_record> device_library_;

View File

@@ -55,12 +55,12 @@ class EMSFactory {
} }
// Construct derived class returning an unique ptr // Construct derived class returning an unique ptr
static auto add(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, std::string & name, uint8_t flags, uint8_t brand) static auto add(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
-> std::unique_ptr<EMSdevice> { -> std::unique_ptr<EMSdevice> {
return std::unique_ptr<EMSdevice>(EMSFactory::makeRaw(device_type, device_id, product_id, version, name, flags, brand)); return std::unique_ptr<EMSdevice>(EMSFactory::makeRaw(device_type, device_id, product_id, version, name, flags, brand));
} }
virtual auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, std::string & name, uint8_t flags, uint8_t brand) const virtual auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand) const
-> EMSdevice * = 0; -> EMSdevice * = 0;
private: private:
@@ -72,7 +72,7 @@ class EMSFactory {
// Construct derived class returning a raw pointer // Construct derived class returning a raw pointer
// find which EMS device it is and use that class // find which EMS device it is and use that class
static auto makeRaw(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, std::string & name, uint8_t flags, uint8_t brand) static auto makeRaw(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
-> EMSdevice * { -> EMSdevice * {
auto it = EMSFactory::getRegister().find(device_type); auto it = EMSFactory::getRegister().find(device_type);
if (it != EMSFactory::getRegister().end()) { if (it != EMSFactory::getRegister().end()) {
@@ -90,7 +90,7 @@ class ConcreteEMSFactory : EMSFactory {
EMSFactory::registerFactory(device_type, this); EMSFactory::registerFactory(device_type, this);
} }
auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, std::string & name, uint8_t flags, uint8_t brand) const auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand) const
-> EMSdevice * { -> EMSdevice * {
return new DerivedClass(device_type, device_id, product_id, version, name, flags, brand); return new DerivedClass(device_type, device_id, product_id, version, name, flags, brand);
} }

View File

@@ -179,9 +179,9 @@ char * Helpers::render_boolean(char * result, const bool value, const bool dashb
uint8_t bool_format_ = dashboard ? EMSESP::system_.bool_dashboard() : EMSESP::system_.bool_format(); uint8_t bool_format_ = dashboard ? EMSESP::system_.bool_dashboard() : EMSESP::system_.bool_format();
if (bool_format_ == BOOL_FORMAT_ONOFF_STR) { if (bool_format_ == BOOL_FORMAT_ONOFF_STR) {
strlcpy(result, value ? translated_word(FL_(on)).c_str() : translated_word(FL_(off)).c_str(), 5); strlcpy(result, value ? translated_word(FL_(on)) : translated_word(FL_(off)), 5);
} else if (bool_format_ == BOOL_FORMAT_ONOFF_STR_CAP) { } else if (bool_format_ == BOOL_FORMAT_ONOFF_STR_CAP) {
strlcpy(result, value ? translated_word(FL_(ON)).c_str() : translated_word(FL_(OFF)).c_str(), 5); strlcpy(result, value ? translated_word(FL_(ON)) : translated_word(FL_(OFF)), 5);
} else if ((bool_format_ == BOOL_FORMAT_10) || (bool_format_ == BOOL_FORMAT_10_STR)) { } else if ((bool_format_ == BOOL_FORMAT_10) || (bool_format_ == BOOL_FORMAT_10_STR)) {
strlcpy(result, value ? "1" : "0", 2); strlcpy(result, value ? "1" : "0", 2);
} else { } else {
@@ -593,12 +593,12 @@ bool Helpers::value2bool(const char * value, bool & value_b) {
std::string bool_str = toLower(value); // convert to lower case std::string bool_str = toLower(value); // convert to lower case
if ((bool_str == Helpers::translated_word(FL_(on))) || (bool_str == "on") || (bool_str == "1") || (bool_str == "true")) { if ((bool_str == std::string(Helpers::translated_word(FL_(on)))) || (bool_str == "on") || (bool_str == "1") || (bool_str == "true")) {
value_b = true; value_b = true;
return true; // is a bool return true; // is a bool
} }
if ((bool_str == Helpers::translated_word(FL_(off))) || (bool_str == "off") || (bool_str == "0") || (bool_str == "false")) { if ((bool_str == std::string(Helpers::translated_word(FL_(off)))) || (bool_str == "off") || (bool_str == "0") || (bool_str == "false")) {
value_b = false; value_b = false;
return true; // is a bool return true; // is a bool
} }
@@ -608,15 +608,15 @@ bool Helpers::value2bool(const char * value, bool & value_b) {
// checks to see if a string is member of a vector and return the index, also allow true/false for on/off // checks to see if a string is member of a vector and return the index, also allow true/false for on/off
// this for a list of lists, when using translated strings // this for a list of lists, when using translated strings
bool Helpers::value2enum(const char * value, uint8_t & value_ui, const __FlashStringHelper * const ** strs) { bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * const ** strs) {
if ((value == nullptr) || (strlen(value) == 0)) { if ((value == nullptr) || (strlen(value) == 0)) {
return false; return false;
} }
std::string str = toLower(value); std::string str = toLower(value);
for (value_ui = 0; strs[value_ui]; value_ui++) { for (value_ui = 0; strs[value_ui]; value_ui++) {
std::string str1 = toLower(Helpers::translated_word(strs[value_ui])); std::string str1 = toLower(std::string(Helpers::translated_word(strs[value_ui])));
std::string str2 = toLower(read_flash_string(strs[value_ui][0])); // also check for default language std::string str2 = toLower((strs[value_ui][0])); // also check for default language
if ((str1 != "") if ((str1 != "")
&& ((str2 == "off" && str == "false") || (str2 == "on" && str == "true") || (str == str1) || (str == str2) && ((str2 == "off" && str == "false") || (str2 == "on" && str == "true") || (str == str1) || (str == str2)
|| (value[0] == ('0' + value_ui) && value[1] == '\0'))) { || (value[0] == ('0' + value_ui) && value[1] == '\0'))) {
@@ -627,19 +627,25 @@ bool Helpers::value2enum(const char * value, uint8_t & value_ui, const __FlashSt
return false; return false;
} }
// checks to see if a string is member of a vector and return the index, also allow true/false for on/off // finds the string (value) of a list vector (strs)
bool Helpers::value2enum(const char * value, uint8_t & value_ui, const __FlashStringHelper * const * strs) { // returns true if found, and sets the value_ui to the index, else false
// also allow true/false for on/off
bool Helpers::value2enum(const char * value, uint8_t & value_ui, const char * const * strs) {
if ((value == nullptr) || (strlen(value) == 0)) { if ((value == nullptr) || (strlen(value) == 0)) {
return false; return false;
} }
std::string str = toLower(value); std::string str = toLower(value);
std::string s_on = Helpers::translated_word(FL_(on));
std::string s_off = Helpers::translated_word(FL_(off));
// stops when a nullptr is found, which is the end delimeter of a MAKE_PSTR_LIST()
// could use count_items() to avoid buffer over-run but this works
for (value_ui = 0; strs[value_ui]; value_ui++) { for (value_ui = 0; strs[value_ui]; value_ui++) {
std::string enum_str = toLower(read_flash_string(strs[value_ui])); std::string enum_str = toLower((strs[value_ui]));
if ((enum_str != "") if ((enum_str != "")
&& ((enum_str == "off" && (str == Helpers::translated_word(FL_(off)) || str == "false")) && ((enum_str == "off" && (str == s_off || str == "false")) || (enum_str == "on" && (str == s_on || str == "true")) || (str == enum_str)
|| (enum_str == "on" && (str == Helpers::translated_word(FL_(on)) || str == "true")) || (str == enum_str)
|| (value[0] == ('0' + value_ui) && value[1] == '\0'))) { || (value[0] == ('0' + value_ui) && value[1] == '\0'))) {
return true; return true;
} }
@@ -655,6 +661,10 @@ std::string Helpers::toLower(std::string const & s) {
return lc; return lc;
} }
std::string Helpers::toLower(const char * s) {
return toLower(std::string(s));
}
std::string Helpers::toUpper(std::string const & s) { std::string Helpers::toUpper(std::string const & s) {
std::string lc = s; std::string lc = s;
std::transform(lc.begin(), lc.end(), lc.begin(), [](unsigned char c) { return std::toupper(c); }); std::transform(lc.begin(), lc.end(), lc.begin(), [](unsigned char c) { return std::toupper(c); });
@@ -676,7 +686,7 @@ void Helpers::replace_char(char * str, char find, char replace) {
// count number of items in a list // count number of items in a list
// the end of a list has a nullptr // the end of a list has a nullptr
uint8_t Helpers::count_items(const __FlashStringHelper * const * list) { uint8_t Helpers::count_items(const char * const * list) {
uint8_t list_size = 0; uint8_t list_size = 0;
if (list != nullptr) { if (list != nullptr) {
while (list[list_size]) { while (list[list_size]) {
@@ -688,7 +698,7 @@ uint8_t Helpers::count_items(const __FlashStringHelper * const * list) {
// count number of items in a list of lists // count number of items in a list of lists
// the end of a list has a nullptr // the end of a list has a nullptr
uint8_t Helpers::count_items(const __FlashStringHelper * const ** list) { uint8_t Helpers::count_items(const char * const ** list) {
uint8_t list_size = 0; uint8_t list_size = 0;
if (list != nullptr) { if (list != nullptr) {
while (list[list_size]) { while (list[list_size]) {
@@ -698,27 +708,17 @@ uint8_t Helpers::count_items(const __FlashStringHelper * const ** list) {
return list_size; return list_size;
} }
// return translated string as a std::string, optionally converting to lowercase (for console commands) // returns char pointer to translated description or fullname
// takes a FL(...) const char * Helpers::translated_word(const char * const * strings) {
std::string Helpers::translated_word(const __FlashStringHelper * const * strings, bool to_lower) {
uint8_t language_index = EMSESP::system_.language_index(); uint8_t language_index = EMSESP::system_.language_index();
uint8_t index = 0; uint8_t index = 0;
// see how many translations we have for this entity. if there is no translation for this, revert to EN if (!strings) {
if (Helpers::count_items(strings) >= language_index + 1 && !read_flash_string(strings[language_index]).empty()) { return ""; // no translations
index = language_index;
} }
return to_lower ? toLower(read_flash_string(strings[index])) : read_flash_string(strings[index]);
}
// return translated string
// takes a F(...)
const __FlashStringHelper * Helpers::translated_fword(const __FlashStringHelper * const * strings) {
uint8_t language_index = EMSESP::system_.language_index();
uint8_t index = 0;
// see how many translations we have for this entity. if there is no translation for this, revert to EN // see how many translations we have for this entity. if there is no translation for this, revert to EN
if (Helpers::count_items(strings) >= language_index + 1 && !read_flash_string(strings[language_index]).empty()) { if (Helpers::count_items(strings) >= language_index + 1 && strlen(strings[language_index])) {
index = language_index; index = language_index;
} }
return strings[index]; return strings[index];

View File

@@ -54,7 +54,9 @@ class Helpers {
static std::string toLower(std::string const & s); static std::string toLower(std::string const & s);
static std::string toUpper(std::string const & s); static std::string toUpper(std::string const & s);
static void replace_char(char * str, char find, char replace); static std::string toLower(const char * s);
static void replace_char(char * str, char find, char replace);
static bool hasValue(const uint8_t & value, const uint8_t isBool = 0); static bool hasValue(const uint8_t & value, const uint8_t isBool = 0);
static bool hasValue(const int8_t & value); static bool hasValue(const int8_t & value);
@@ -67,16 +69,15 @@ class Helpers {
static bool value2float(const char * value, float & value_f); static bool value2float(const char * value, float & value_f);
static bool value2bool(const char * value, bool & value_b); static bool value2bool(const char * value, bool & value_b);
static bool value2string(const char * value, std::string & value_s); static bool value2string(const char * value, std::string & value_s);
static bool value2enum(const char * value, uint8_t & value_ui, const __FlashStringHelper * const ** strs); static bool value2enum(const char * value, uint8_t & value_ui, const char * const ** strs);
static bool value2enum(const char * value, uint8_t & value_ui, const __FlashStringHelper * const * strs); static bool value2enum(const char * value, uint8_t & value_ui, const char * const * strs);
static bool value2temperature(const char * value, float & value_f, bool relative = false); static bool value2temperature(const char * value, float & value_f, bool relative = false);
static bool value2temperature(const char * value, int & value_i, const bool relative = false, const int min = -2147483648, const int max = 2147483647); static bool value2temperature(const char * value, int & value_i, const bool relative = false, const int min = -2147483648, const int max = 2147483647);
static uint8_t count_items(const __FlashStringHelper * const ** list); static uint8_t count_items(const char * const ** list);
static uint8_t count_items(const __FlashStringHelper * const * list); static uint8_t count_items(const char * const * list);
static std::string translated_word(const __FlashStringHelper * const * strings, bool to_lower = false); static const char * translated_word(const char * const * strings);
static const __FlashStringHelper * translated_fword(const __FlashStringHelper * const * strings);
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
static char * ultostr(char * ptr, uint32_t value, const uint8_t base); static char * ultostr(char * ptr, uint32_t value, const uint8_t base);

View File

@@ -20,6 +20,8 @@
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable" #pragma GCC diagnostic ignored "-Wunused-variable"
// clang-format off
/* /*
* THIS FILE CONTAINS STANDARD STRING LITERALS THAT DON'T NEED LANGUAGE TRANSLATIONS * THIS FILE CONTAINS STANDARD STRING LITERALS THAT DON'T NEED LANGUAGE TRANSLATIONS
*/ */
@@ -144,55 +146,55 @@ MAKE_PSTR(password_prompt, "Password: ")
MAKE_PSTR(unset, "<unset>") MAKE_PSTR(unset, "<unset>")
// more common names that don't need translations // more common names that don't need translations
MAKE_PSTR_LIST(1x3min, F("1x3min")) MAKE_PSTR_LIST(1x3min, "1x3min")
MAKE_PSTR_LIST(2x3min, F("2x3min")) MAKE_PSTR_LIST(2x3min, "2x3min")
MAKE_PSTR_LIST(3x3min, F("3x3min")) MAKE_PSTR_LIST(3x3min, "3x3min")
MAKE_PSTR_LIST(4x3min, F("4x3min")) MAKE_PSTR_LIST(4x3min, "4x3min")
MAKE_PSTR_LIST(5x3min, F("5x3min")) MAKE_PSTR_LIST(5x3min, "5x3min")
MAKE_PSTR_LIST(6x3min, F("6x3min")) MAKE_PSTR_LIST(6x3min, "6x3min")
MAKE_PSTR_LIST(auto, F("auto")) MAKE_PSTR_LIST(auto, "auto")
MAKE_PSTR_LIST(na, F("n/a")) MAKE_PSTR_LIST(na, "n/a")
MAKE_PSTR_LIST(rc3x, F("rc3x")) MAKE_PSTR_LIST(rc3x, "rc3x")
MAKE_PSTR_LIST(rc20, F("rc20")) MAKE_PSTR_LIST(rc20, "rc20")
MAKE_PSTR_LIST(fb10, F("fb10")) MAKE_PSTR_LIST(fb10, "fb10")
MAKE_PSTR_LIST(fb100, F("fb100")) MAKE_PSTR_LIST(fb100, "fb100")
MAKE_PSTR_LIST(dash, F("-")) MAKE_PSTR_LIST(dash, "-")
MAKE_PSTR_LIST(error, F("error")) MAKE_PSTR_LIST(error, "error")
MAKE_PSTR_LIST(BLANK, F("")) MAKE_PSTR_LIST(BLANK, "")
MAKE_PSTR_LIST(pwm, F("pwm")) MAKE_PSTR_LIST(pwm, "pwm")
MAKE_PSTR_LIST(pwm_invers, F("pwm inverse")) MAKE_PSTR_LIST(pwm_invers, "pwm inverse")
MAKE_PSTR_LIST(mpc, F("mpc")) MAKE_PSTR_LIST(mpc, "mpc")
MAKE_PSTR_LIST(tempauto, F("temp auto")) MAKE_PSTR_LIST(tempauto, "temp auto")
MAKE_PSTR_LIST(bypass, F("bypass")) MAKE_PSTR_LIST(bypass, "bypass")
MAKE_PSTR_LIST(mixer, F("mixer")) MAKE_PSTR_LIST(mixer, "mixer")
MAKE_PSTR_LIST(monovalent, F("monovalent")) MAKE_PSTR_LIST(monovalent, "monovalent")
MAKE_PSTR_LIST(bivalent, F("bivalent")) MAKE_PSTR_LIST(bivalent, "bivalent")
MAKE_PSTR_LIST(n_o, F("n_o")) MAKE_PSTR_LIST(n_o, "n_o")
MAKE_PSTR_LIST(n_c, F("n_c")) MAKE_PSTR_LIST(n_c, "n_c")
MAKE_PSTR_LIST(prog1, F("prog 1")) MAKE_PSTR_LIST(prog1, "prog 1")
MAKE_PSTR_LIST(prog2, F("prog 2")) MAKE_PSTR_LIST(prog2, "prog 2")
MAKE_PSTR_LIST(proga, F("prog a")) MAKE_PSTR_LIST(proga, "prog a")
MAKE_PSTR_LIST(progb, F("prog b")) MAKE_PSTR_LIST(progb, "prog b")
MAKE_PSTR_LIST(progc, F("prog c")) MAKE_PSTR_LIST(progc, "prog c")
MAKE_PSTR_LIST(progd, F("prog d")) MAKE_PSTR_LIST(progd, "prog d")
MAKE_PSTR_LIST(proge, F("prog e")) MAKE_PSTR_LIST(proge, "prog e")
MAKE_PSTR_LIST(progf, F("prog f")) MAKE_PSTR_LIST(progf, "prog f")
MAKE_PSTR_LIST(rc35, F("RC35")) MAKE_PSTR_LIST(rc35, "RC35")
MAKE_PSTR_LIST(0kW, F("0 kW")) MAKE_PSTR_LIST(0kW, "0 kW")
MAKE_PSTR_LIST(2kW, F("2 kW")) MAKE_PSTR_LIST(2kW, "2 kW")
MAKE_PSTR_LIST(3kW, F("3 kW")) MAKE_PSTR_LIST(3kW, "3 kW")
MAKE_PSTR_LIST(4kW, F("4 kW")) MAKE_PSTR_LIST(4kW, "4 kW")
MAKE_PSTR_LIST(6kW, F("6 kW")) MAKE_PSTR_LIST(6kW, "6 kW")
MAKE_PSTR_LIST(9kW, F("9 kW")) MAKE_PSTR_LIST(9kW, "9 kW")
// templates - this are not translated and will be saved under optons_single // templates - this are not translated and will be saved under options_single
MAKE_PSTR_LIST(tpl_datetime, F("Format: < NTP | dd.mm.yyyy-hh:mm:ss-day(0-6)-dst(0/1) >")) MAKE_PSTR_LIST(tpl_datetime, "Format: < NTP | dd.mm.yyyy-hh:mm:ss-day(0-6)-dst(0/1) >")
MAKE_PSTR_LIST(tpl_switchtime, F("Format: <nn> [ not_set | day hh:mm on|off ]")) MAKE_PSTR_LIST(tpl_switchtime, "Format: <nn> [ not_set | day hh:mm on|off ]")
MAKE_PSTR_LIST(tpl_switchtime1, F("Format: <nn> [ not_set | day hh:mm Tn ]")) MAKE_PSTR_LIST(tpl_switchtime1, "Format: <nn> [ not_set | day hh:mm Tn ]")
MAKE_PSTR_LIST(tpl_holidays, F("Format: < dd.mm.yyyy-dd.mm.yyyy >")) MAKE_PSTR_LIST(tpl_holidays, "Format: < dd.mm.yyyy-dd.mm.yyyy >")
MAKE_PSTR_LIST(tpl_date, F("Format: < dd.mm.yyyy >")) MAKE_PSTR_LIST(tpl_date, "Format: < dd.mm.yyyy >")
MAKE_PSTR_LIST(tpl_input, F("Format: <inv>[<evu1><evu2><evu3><comp><aux><cool><heat><dhw><pv>]")) MAKE_PSTR_LIST(tpl_input, "Format: <inv>[<evu1><evu2><evu3><comp><aux><cool><heat><dhw><pv>]")
MAKE_PSTR_LIST(tpl_input4, F("Format: <inv>[<comp><aux><cool><heat><dhw><pv>]")) MAKE_PSTR_LIST(tpl_input4, "Format: <inv>[<comp><aux><cool><heat><dhw><pv>]")
// Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp // Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp
// These don't need translating, it will mess up HA and the API // These don't need translating, it will mess up HA and the API
@@ -217,11 +219,6 @@ MAKE_PSTR(uom_sqm, "sqm")
MAKE_PSTR(uom_m3, "m3") MAKE_PSTR(uom_m3, "m3")
MAKE_PSTR(uom_l, "l") MAKE_PSTR(uom_l, "l")
// commands
MAKE_PSTR(info_cmd, "lists all values")
MAKE_PSTR(commands_cmd, "lists all commands")
MAKE_PSTR(entities_cmd, "lists all entities")
// TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp // TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp
// use empty string if want to suppress showing tags // use empty string if want to suppress showing tags
// mqtt tags must not have spaces // mqtt tags must not have spaces
@@ -273,16 +270,17 @@ MAKE_PSTR(response, "response")
MAKE_PSTR(tag_boiler_data_ww_mqtt, "ww") MAKE_PSTR(tag_boiler_data_ww_mqtt, "ww")
MAKE_PSTR(tag_device_data_ww_mqtt, "") MAKE_PSTR(tag_device_data_ww_mqtt, "")
MAKE_PSTR_LIST(climate, F("HA climate config creation")) // Home Assistant - this is special and has no translations
MAKE_PSTR_LIST(climate, "HA climate config creation")
// syslog // syslog
MAKE_PSTR_LIST(list_syslog_level, F("off"), F("emerg"), F("alert"), F("crit"), F("error"), F("warn"), F("notice"), F("info"), F("debug"), F("trace"), F("all")) MAKE_PSTR_LIST(list_syslog_level, "off", "emerg", "alert", "crit", "error", "warn", "notice", "info", "debug", "trace", "all")
// sensors // sensors
MAKE_PSTR_LIST(list_sensortype, F("none"), F("digital in"), F("counter"), F("adc"), F("timer"), F("rate"), F("digital out"), F("pwm 0"), F("pwm 1"), F("pwm 2")) MAKE_PSTR_LIST(list_sensortype, "none", "digital in", "counter", "adc", "timer", "rate", "digital out", "pwm 0", "pwm 1", "pwm 2")
// watch // watch
MAKE_PSTR_LIST(list_watch, F("off"), F("on"), F("raw"), F("unknown")) MAKE_PSTR_LIST(list_watch, "off", "on", "raw", "unknown")
/* /*
* The rest below are Enums and generated from translations lists * The rest below are Enums and generated from translations lists
@@ -302,16 +300,7 @@ MAKE_PSTR_ENUM(enum_reset, FL_(dash), FL_(maintenance), FL_(error))
MAKE_PSTR_ENUM(enum_maxHeat, FL_(0kW), FL_(2kW), FL_(3kW), FL_(4kW), FL_(6kW), FL_(9kW)) MAKE_PSTR_ENUM(enum_maxHeat, FL_(0kW), FL_(2kW), FL_(3kW), FL_(4kW), FL_(6kW), FL_(9kW))
// thermostat lists // thermostat lists
MAKE_PSTR_ENUM(enum_ibaMainDisplay, MAKE_PSTR_ENUM(enum_ibaMainDisplay, FL_(internal_temperature), FL_(internal_setpoint), FL_(external_temperature), FL_(burner_temperature), FL_(ww_temperature), FL_(functioning_mode), FL_(time), FL_(date), FL_(smoke_temperature))
FL_(internal_temperature),
FL_(internal_setpoint),
FL_(external_temperature),
FL_(burner_temperature),
FL_(ww_temperature),
FL_(functioning_mode),
FL_(time),
FL_(date),
FL_(smoke_temperature))
MAKE_PSTR_ENUM(enum_ibaLanguage, FL_(german), FL_(dutch), FL_(french), FL_(italian)) MAKE_PSTR_ENUM(enum_ibaLanguage, FL_(german), FL_(dutch), FL_(french), FL_(italian))
MAKE_PSTR_ENUM(enum_ibaLanguage_RC30, FL_(german), FL_(dutch)) MAKE_PSTR_ENUM(enum_ibaLanguage_RC30, FL_(german), FL_(dutch))
MAKE_PSTR_ENUM(enum_floordrystatus, FL_(off), FL_(start), FL_(heat), FL_(hold), FL_(cool), FL_(end)) MAKE_PSTR_ENUM(enum_floordrystatus, FL_(off), FL_(start), FL_(heat), FL_(hold), FL_(cool), FL_(end))
@@ -379,3 +368,5 @@ MAKE_PSTR_ENUM(enum_bufConfig, FL_(monovalent), FL_(bivalent))
MAKE_PSTR_ENUM(enum_blockTerm, FL_(n_o), FL_(n_c)) MAKE_PSTR_ENUM(enum_blockTerm, FL_(n_o), FL_(n_c))
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
// clang-format on

File diff suppressed because it is too large Load Diff

View File

@@ -186,26 +186,26 @@ void Mqtt::loop() {
// print MQTT log and other stuff to console // print MQTT log and other stuff to console
void Mqtt::show_mqtt(uuid::console::Shell & shell) { void Mqtt::show_mqtt(uuid::console::Shell & shell) {
shell.printfln(F("MQTT is %s"), connected() ? read_flash_string(F_(connected)).c_str() : read_flash_string(F_(disconnected)).c_str()); shell.printfln("MQTT is %s", connected() ? F_(connected) : F_(disconnected));
shell.printfln(F("MQTT publish errors: %lu"), mqtt_publish_fails_); shell.printfln("MQTT publish errors: %lu", mqtt_publish_fails_);
shell.println(); shell.println();
// show subscriptions // show subscriptions
shell.printfln(F("MQTT topic subscriptions:")); shell.printfln("MQTT topic subscriptions:");
for (const auto & mqtt_subfunction : mqtt_subfunctions_) { for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
shell.printfln(F(" %s/%s"), mqtt_base_.c_str(), mqtt_subfunction.topic_.c_str()); shell.printfln(" %s/%s", mqtt_base_.c_str(), mqtt_subfunction.topic_.c_str());
} }
shell.println(); shell.println();
// show queues // show queues
if (mqtt_messages_.empty()) { if (mqtt_messages_.empty()) {
shell.printfln(F("MQTT queue is empty")); shell.printfln("MQTT queue is empty");
shell.println(); shell.println();
return; return;
} }
shell.printfln(F("MQTT queue (%d/%d messages):"), mqtt_messages_.size(), MAX_MQTT_MESSAGES); shell.printfln(("MQTT queue (%d/%d messages):"), mqtt_messages_.size(), MAX_MQTT_MESSAGES);
for (const auto & message : mqtt_messages_) { for (const auto & message : mqtt_messages_) {
auto content = message.content_; auto content = message.content_;
@@ -222,12 +222,12 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
// Publish messages // Publish messages
if (message.retry_count_ == 0) { if (message.retry_count_ == 0) {
if (message.packet_id_ == 0) { if (message.packet_id_ == 0) {
shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s"), message.id_, topic, content->payload.c_str()); shell.printfln((" [%02d] (Pub) topic=%s payload=%s"), message.id_, topic, content->payload.c_str());
} else { } else {
shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s (pid %d)"), message.id_, topic, content->payload.c_str(), message.packet_id_); shell.printfln((" [%02d] (Pub) topic=%s payload=%s (pid %d)"), message.id_, topic, content->payload.c_str(), message.packet_id_);
} }
} else { } else {
shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)"), shell.printfln((" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)"),
message.id_, message.id_,
topic, topic,
content->payload.c_str(), content->payload.c_str(),
@@ -236,7 +236,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
} }
} else { } else {
// Subscribe messages // Subscribe messages
shell.printfln(F(" [%02d] (Sub) topic=%s"), message.id_, topic); shell.printfln((" [%02d] (Sub) topic=%s"), message.id_, topic);
} }
} }
shell.println(); shell.println();
@@ -264,16 +264,16 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) cons
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
if (len) { if (len) {
LOG_DEBUG(F("Received topic `%s` => payload `%s` (length %d)"), topic, message, len); LOG_DEBUG(("Received topic `%s` => payload `%s` (length %d)"), topic, message, len);
} else { } else {
LOG_DEBUG(F("Received topic `%s`"), topic); LOG_DEBUG("Received topic `%s`", topic);
} }
#endif #endif
// remove HA topics if we don't use discovery // remove HA topics if we don't use discovery
if (strncmp(topic, discovery_prefix().c_str(), discovery_prefix().size()) == 0) { if (strncmp(topic, discovery_prefix().c_str(), discovery_prefix().size()) == 0) {
if (!ha_enabled_ && len) { // don't ping pong the empty message if (!ha_enabled_ && len) { // don't ping pong the empty message
queue_publish_message(topic, "", true); queue_publish_message(topic, "", true);
LOG_DEBUG(F("Remove topic %s"), topic); LOG_DEBUG("Remove topic %s", topic);
} }
return; return;
} }
@@ -285,7 +285,7 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) cons
snprintf(full_topic, sizeof(full_topic), "%s/%s", mqtt_base_.c_str(), mf.topic_.c_str()); snprintf(full_topic, sizeof(full_topic), "%s/%s", mqtt_base_.c_str(), mf.topic_.c_str());
if ((!strcmp(topic, full_topic)) && (mf.mqtt_subfunction_)) { if ((!strcmp(topic, full_topic)) && (mf.mqtt_subfunction_)) {
if (!(mf.mqtt_subfunction_)(message)) { if (!(mf.mqtt_subfunction_)(message)) {
LOG_ERROR(F("error: invalid payload %s for this topic %s"), message, topic); LOG_ERROR("error: invalid payload %s for this topic %s", message, topic);
if (send_response_) { if (send_response_) {
Mqtt::publish(F_(response), "error: invalid data"); Mqtt::publish(F_(response), "error: invalid data");
} }
@@ -341,10 +341,10 @@ void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t devic
return; return;
} }
// shell.print(F(" Subscribed MQTT topics: ")); // shell.print(" Subscribed MQTT topics: ");
// for (const auto & mqtt_subfunction : mqtt_subfunctions_) { // for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
// if (mqtt_subfunction.device_type_ == device_type) { // if (mqtt_subfunction.device_type_ == device_type) {
// shell.printf(F("%s "), mqtt_subfunction.topic_.c_str()); // shell.printf("%s ", mqtt_subfunction.topic_.c_str());
// } // }
// } // }
shell.println(); shell.println();
@@ -358,7 +358,7 @@ void Mqtt::on_publish(uint16_t packetId) const {
// find the MQTT message in the queue and remove it // find the MQTT message in the queue and remove it
if (mqtt_messages_.empty()) { if (mqtt_messages_.empty()) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] No message stored for ACK pid %d"), packetId); LOG_DEBUG("[DEBUG] No message stored for ACK pid %d", packetId);
#endif #endif
return; return;
} }
@@ -368,18 +368,18 @@ void Mqtt::on_publish(uint16_t packetId) const {
// if the last published failed, don't bother checking it. wait for the next retry // if the last published failed, don't bother checking it. wait for the next retry
if (mqtt_message.packet_id_ == 0) { if (mqtt_message.packet_id_ == 0) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] ACK for failed message pid 0")); LOG_DEBUG("[DEBUG] ACK for failed message pid 0");
#endif #endif
return; return;
} }
if (mqtt_message.packet_id_ != packetId) { if (mqtt_message.packet_id_ != packetId) {
LOG_ERROR(F("Mismatch, expecting PID %d, got %d"), mqtt_message.packet_id_, packetId); LOG_ERROR("Mismatch, expecting PID %d, got %d", mqtt_message.packet_id_, packetId);
mqtt_publish_fails_++; // increment error count mqtt_publish_fails_++; // increment error count
} }
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] ACK pid %d"), packetId); LOG_DEBUG("[DEBUG] ACK pid %d", packetId);
#endif #endif
mqtt_messages_.pop_front(); // always remove from queue, regardless if there was a successful ACK mqtt_messages_.pop_front(); // always remove from queue, regardless if there was a successful ACK
@@ -450,17 +450,17 @@ void Mqtt::start() {
} }
connecting_ = false; connecting_ = false;
if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) { if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {
LOG_WARNING(F("MQTT disconnected: TCP")); LOG_WARNING("MQTT disconnected: TCP");
} else if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) { } else if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) {
LOG_WARNING(F("MQTT disconnected: Identifier Rejected")); LOG_WARNING("MQTT disconnected: Identifier Rejected");
} else if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) { } else if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) {
LOG_WARNING(F("MQTT disconnected: Server unavailable")); LOG_WARNING("MQTT disconnected: Server unavailable");
} else if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) { } else if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) {
LOG_WARNING(F("MQTT disconnected: Malformed credentials")); LOG_WARNING("MQTT disconnected: Malformed credentials");
} else if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) { } else if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
LOG_WARNING(F("MQTT disconnected: Not authorized")); LOG_WARNING("MQTT disconnected: Not authorized");
} else { } else {
LOG_WARNING(F("MQTT disconnected: code %d"), reason); LOG_WARNING("MQTT disconnected: code %d", reason);
} }
}); });
@@ -535,7 +535,7 @@ void Mqtt::on_connect() {
return; return;
} }
LOG_INFO(F("MQTT connected")); LOG_INFO("MQTT connected");
connecting_ = true; connecting_ = true;
connectcount_++; connectcount_++;
@@ -546,15 +546,15 @@ void Mqtt::on_connect() {
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> doc;
// first time to connect // first time to connect
if (connectcount_ == 1) { if (connectcount_ == 1) {
doc["event"] = FJSON("start"); doc["event"] = "start";
} else { } else {
doc["event"] = FJSON("reconnect"); doc["event"] = "reconnect";
} }
doc["version"] = EMSESP_APP_VERSION; doc["version"] = EMSESP_APP_VERSION;
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (WiFi.status() == WL_CONNECTED) { if (WiFi.status() == WL_CONNECTED) {
doc["connection"] = F("WiFi"); doc["connection"] = ("WiFi");
doc["hostname"] = WiFi.getHostname(); doc["hostname"] = WiFi.getHostname();
doc["SSID"] = WiFi.SSID(); doc["SSID"] = WiFi.SSID();
doc["BSSID"] = WiFi.BSSIDstr(); doc["BSSID"] = WiFi.BSSIDstr();
@@ -567,7 +567,7 @@ void Mqtt::on_connect() {
doc["IPv6 address"] = uuid::printable_to_string(WiFi.localIPv6()); doc["IPv6 address"] = uuid::printable_to_string(WiFi.localIPv6());
} }
} else if (EMSESP::system_.ethernet_connected()) { } else if (EMSESP::system_.ethernet_connected()) {
doc["connection"] = F("Ethernet"); doc["connection"] = ("Ethernet");
doc["hostname"] = ETH.getHostname(); doc["hostname"] = ETH.getHostname();
doc["MAC"] = ETH.macAddress(); doc["MAC"] = ETH.macAddress();
doc["IPv4 address"] = uuid::printable_to_string(ETH.localIP()) + "/" + uuid::printable_to_string(ETH.subnetMask()); doc["IPv4 address"] = uuid::printable_to_string(ETH.localIP()) + "/" + uuid::printable_to_string(ETH.subnetMask());
@@ -600,7 +600,7 @@ void Mqtt::on_connect() {
// re-subscribe to all custom registered MQTT topics // re-subscribe to all custom registered MQTT topics
resubscribe(); resubscribe();
publish_retain(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on publish_retain("status", "online", true); // say we're alive to the Last Will topic, with retain on
mqtt_publish_fails_ = 0; // reset fail count to 0 mqtt_publish_fails_ = 0; // reset fail count to 0
@@ -609,7 +609,7 @@ void Mqtt::on_connect() {
LOG_INFO("Queue size: %d", mqtt_messages_.size()); LOG_INFO("Queue size: %d", mqtt_messages_.size());
for (const auto & message : mqtt_messages_) { for (const auto & message : mqtt_messages_) {
auto content = message.content_; auto content = message.content_;
LOG_INFO(F(" [%02d] (%d) topic=%s payload=%s"), message.id_, content->operation, content->topic.c_str(), content->payload.c_str()); LOG_INFO((" [%02d] (%d) topic=%s payload=%s"), message.id_, content->operation, content->topic.c_str(), content->payload.c_str());
} }
*/ */
} }
@@ -620,21 +620,21 @@ void Mqtt::on_connect() {
void Mqtt::ha_status() { void Mqtt::ha_status() {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc; StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = FJSON("ems-esp-system"); doc["uniq_id"] = "ems-esp-system";
doc["~"] = mqtt_base_; // default ems-esp doc["~"] = mqtt_base_; // default ems-esp
// doc["avty_t"] = FJSON("~/status"); // commented out, as it causes errors in HA sometimes // doc["avty_t"] = "~/status"; // commented out, as it causes errors in HA sometimes
// doc["json_attr_t"] = FJSON("~/heartbeat"); // store also as HA attributes // doc["json_attr_t"] = "~/heartbeat"; // store also as HA attributes
doc["stat_t"] = FJSON("~/heartbeat"); doc["stat_t"] = "~/heartbeat";
doc["object_id"] = FJSON("ems_esp_status"); doc["object_id"] = "ems_esp_status";
doc["name"] = FJSON("EMS-ESP status"); doc["name"] = "EMS-ESP status";
doc["ic"] = F_(icondevice); doc["ic"] = F_(icondevice);
doc["val_tpl"] = FJSON("{{value_json['bus_status']}}"); doc["val_tpl"] = "{{value_json['bus_status']}}";
JsonObject dev = doc.createNestedObject("dev"); JsonObject dev = doc.createNestedObject("dev");
dev["name"] = F_(EMSESP); // "EMS-ESP" dev["name"] = "EMS-ESP";
dev["sw"] = "v" + std::string(EMSESP_APP_VERSION); dev["sw"] = "v" + std::string(EMSESP_APP_VERSION);
dev["mf"] = FJSON("proddy"); dev["mf"] = "proddy";
dev["mdl"] = F_(EMSESP); // "EMS-ESP" dev["mdl"] = "EMS-ESP";
JsonArray ids = dev.createNestedArray("ids"); JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp"); ids.add("ems-esp");
@@ -644,19 +644,20 @@ void Mqtt::ha_status() {
// create the sensors - must match the MQTT payload keys // create the sensors - must match the MQTT payload keys
if (!EMSESP::system_.ethernet_connected()) { if (!EMSESP::system_.ethernet_connected()) {
publish_system_ha_sensor_config(DeviceValueType::INT, F("WiFi RSSI"), F("rssi"), DeviceValueUOM::DBM); publish_system_ha_sensor_config(DeviceValueType::INT, ("WiFi RSSI"), ("rssi"), DeviceValueUOM::DBM);
publish_system_ha_sensor_config(DeviceValueType::INT, F("WiFi strength"), F("wifistrength"), DeviceValueUOM::PERCENT); publish_system_ha_sensor_config(DeviceValueType::INT, ("WiFi strength"), ("wifistrength"), DeviceValueUOM::PERCENT);
} }
publish_system_ha_sensor_config(DeviceValueType::INT, F("Uptime"), F("uptime"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Uptime (sec)"), F("uptime_sec"), DeviceValueUOM::SECONDS); publish_system_ha_sensor_config(DeviceValueType::INT, ("Uptime"), ("uptime"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::BOOL, F("NTP status"), F("ntp_status"), DeviceValueUOM::NONE); publish_system_ha_sensor_config(DeviceValueType::INT, ("Uptime (sec)"), ("uptime_sec"), DeviceValueUOM::SECONDS);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Free memory"), F("freemem"), DeviceValueUOM::KB); publish_system_ha_sensor_config(DeviceValueType::BOOL, ("NTP status"), ("ntp_status"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("MQTT fails"), F("mqttfails"), DeviceValueUOM::NONE); publish_system_ha_sensor_config(DeviceValueType::INT, ("Free memory"), ("freemem"), DeviceValueUOM::KB);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Rx received"), F("rxreceived"), DeviceValueUOM::NONE); publish_system_ha_sensor_config(DeviceValueType::INT, ("MQTT fails"), ("mqttfails"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Rx fails"), F("rxfails"), DeviceValueUOM::NONE); publish_system_ha_sensor_config(DeviceValueType::INT, ("Rx received"), ("rxreceived"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Tx reads"), F("txreads"), DeviceValueUOM::NONE); publish_system_ha_sensor_config(DeviceValueType::INT, ("Rx fails"), ("rxfails"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Tx writes"), F("txwrites"), DeviceValueUOM::NONE); publish_system_ha_sensor_config(DeviceValueType::INT, ("Tx reads"), ("txreads"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Tx fails"), F("txfails"), DeviceValueUOM::NONE); publish_system_ha_sensor_config(DeviceValueType::INT, ("Tx writes"), ("txwrites"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, ("Tx fails"), ("txfails"), DeviceValueUOM::NONE);
} }
// add sub or pub task to the queue. // add sub or pub task to the queue.
@@ -686,7 +687,7 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
// if the queue is full, make room but removing the last one // if the queue is full, make room but removing the last one
if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) { if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) {
mqtt_messages_.pop_front(); mqtt_messages_.pop_front();
LOG_WARNING(F("Queue overflow, removing one message")); LOG_WARNING("Queue overflow, removing one message");
mqtt_publish_fails_++; mqtt_publish_fails_++;
} }
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message)); mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
@@ -718,17 +719,17 @@ void Mqtt::publish(const std::string & topic, const std::string & payload) {
} }
// MQTT Publish, using a user's retain flag - except for char * strings // MQTT Publish, using a user's retain flag - except for char * strings
void Mqtt::publish(const __FlashStringHelper * topic, const char * payload) { void Mqtt::publish(const char * topic, const char * payload) {
queue_publish_message(read_flash_string(topic), payload, mqtt_retain_); queue_publish_message((topic), payload, mqtt_retain_);
} }
// MQTT Publish, using a specific retain flag, topic is a flash string // MQTT Publish, using a specific retain flag, topic is a flash string
void Mqtt::publish(const __FlashStringHelper * topic, const std::string & payload) { void Mqtt::publish(const char * topic, const std::string & payload) {
queue_publish_message(read_flash_string(topic), payload, mqtt_retain_); queue_publish_message((topic), payload, mqtt_retain_);
} }
void Mqtt::publish(const __FlashStringHelper * topic, const JsonObject & payload) { void Mqtt::publish(const char * topic, const JsonObject & payload) {
publish(read_flash_string(topic), payload); publish_retain(topic, payload, mqtt_retain_);
} }
// publish json doc, only if its not empty // publish json doc, only if its not empty
@@ -737,12 +738,16 @@ void Mqtt::publish(const std::string & topic, const JsonObject & payload) {
} }
// MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag // MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag
void Mqtt::publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain) { void Mqtt::publish_retain(const char * topic, const std::string & payload, bool retain) {
queue_publish_message(read_flash_string(topic), payload, retain); queue_publish_message((topic), payload, retain);
} }
// publish json doc, only if its not empty, using the retain flag // publish json doc, only if its not empty, using the retain flag
void Mqtt::publish_retain(const std::string & topic, const JsonObject & payload, bool retain) { void Mqtt::publish_retain(const std::string & topic, const JsonObject & payload, bool retain) {
publish_retain(topic.c_str(), payload, retain);
}
void Mqtt::publish_retain(const char * topic, const JsonObject & payload, bool retain) {
if (enabled() && payload.size()) { if (enabled() && payload.size()) {
std::string payload_text; std::string payload_text;
serializeJson(payload, payload_text); // convert json to string serializeJson(payload, payload_text); // convert json to string
@@ -750,30 +755,22 @@ void Mqtt::publish_retain(const std::string & topic, const JsonObject & payload,
} }
} }
void Mqtt::publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain) {
publish_retain(read_flash_string(topic), payload, retain);
}
void Mqtt::publish_ha(const __FlashStringHelper * topic, const JsonObject & payload) {
publish_ha(read_flash_string(topic), payload);
}
// publish empty payload to remove the topic // publish empty payload to remove the topic
void Mqtt::publish_ha(const std::string & topic) { void Mqtt::publish_ha(const char * topic) {
if (!enabled()) { if (!enabled()) {
return; return;
} }
std::string fulltopic = Mqtt::discovery_prefix() + topic; std::string fulltopic = Mqtt::discovery_prefix() + topic;
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] Publishing empty HA topic=%s"), fulltopic.c_str()); LOG_DEBUG("[DEBUG] Publishing empty HA topic=%s", fulltopic.c_str());
#endif #endif
queue_publish_message(fulltopic, "", true); // publish with retain to remove from broker queue_publish_message(fulltopic, "", true); // publish with retain to remove from broker
} }
// publish a Home Assistant config topic and payload, with retain flag off. // publish a Home Assistant config topic and payload, with retain flag off.
void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) { void Mqtt::publish_ha(const char * topic, const JsonObject & payload) {
if (!enabled()) { if (!enabled()) {
return; return;
} }
@@ -784,9 +781,9 @@ void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) {
std::string fulltopic = Mqtt::discovery_prefix() + topic; std::string fulltopic = Mqtt::discovery_prefix() + topic;
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
LOG_DEBUG(F("Publishing HA topic=%s, payload=%s"), fulltopic.c_str(), payload_text.c_str()); LOG_DEBUG("Publishing HA topic=%s, payload=%s", fulltopic.c_str(), payload_text.c_str());
#elif defined(EMSESP_DEBUG) #elif defined(EMSESP_DEBUG)
LOG_DEBUG(F("[debug] Publishing HA topic=%s, payload=%s"), fulltopic.c_str(), payload_text.c_str()); LOG_DEBUG("[debug] Publishing HA topic=%s, payload=%s", fulltopic.c_str(), payload_text.c_str());
#endif #endif
// queue messages if the MQTT connection is not yet established. to ensure we don't miss messages // queue messages if the MQTT connection is not yet established. to ensure we don't miss messages
@@ -815,7 +812,7 @@ void Mqtt::process_queue() {
// it will have a real packet ID // it will have a real packet ID
if (mqtt_message.packet_id_ > 0) { if (mqtt_message.packet_id_ > 0) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] Waiting for QOS-ACK")); LOG_DEBUG("[DEBUG] Waiting for QOS-ACK");
#endif #endif
// if we don't get the ack within 10 minutes, republish with new packet_id // if we don't get the ack within 10 minutes, republish with new packet_id
if (uuid::get_uptime_sec() - last_publish_queue_ < 600) { if (uuid::get_uptime_sec() - last_publish_queue_ < 600) {
@@ -826,13 +823,13 @@ void Mqtt::process_queue() {
// if we're subscribing... // if we're subscribing...
if (message->operation == Operation::SUBSCRIBE) { if (message->operation == Operation::SUBSCRIBE) {
LOG_DEBUG(F("Subscribing to topic '%s'"), topic); LOG_DEBUG("Subscribing to topic '%s'", topic);
uint16_t packet_id = mqttClient_->subscribe(topic, mqtt_qos_); uint16_t packet_id = mqttClient_->subscribe(topic, mqtt_qos_);
if (!packet_id) { if (!packet_id) {
if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) { if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) {
return; return;
} }
LOG_ERROR(F("Error subscribing to topic '%s'"), topic); LOG_ERROR("Error subscribing to topic '%s'", topic);
mqtt_publish_fails_++; // increment failure counter mqtt_publish_fails_++; // increment failure counter
} }
@@ -843,13 +840,13 @@ void Mqtt::process_queue() {
// if we're unsubscribing... // if we're unsubscribing...
if (message->operation == Operation::UNSUBSCRIBE) { if (message->operation == Operation::UNSUBSCRIBE) {
LOG_DEBUG(F("Subscribing to topic '%s'"), topic); LOG_DEBUG("Subscribing to topic '%s'", topic);
uint16_t packet_id = mqttClient_->unsubscribe(topic); uint16_t packet_id = mqttClient_->unsubscribe(topic);
if (!packet_id) { if (!packet_id) {
if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) { if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) {
return; return;
} }
LOG_ERROR(F("Error unsubscribing to topic '%s'"), topic); LOG_ERROR("Error unsubscribing to topic '%s'", topic);
mqtt_publish_fails_++; // increment failure counter mqtt_publish_fails_++; // increment failure counter
} }
@@ -860,7 +857,7 @@ void Mqtt::process_queue() {
// else try and publish it // else try and publish it
uint16_t packet_id = mqttClient_->publish(topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_); uint16_t packet_id = mqttClient_->publish(topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_);
LOG_DEBUG(F("Publishing topic %s (#%02d, retain=%d, retry=%d, size=%d, pid=%d)"), LOG_DEBUG(("Publishing topic %s (#%02d, retain=%d, retry=%d, size=%d, pid=%d)"),
topic, topic,
mqtt_message.id_, mqtt_message.id_,
message->retain, message->retain,
@@ -871,14 +868,14 @@ void Mqtt::process_queue() {
if (packet_id == 0) { if (packet_id == 0) {
// it failed. if we retried n times, give up. remove from queue // it failed. if we retried n times, give up. remove from queue
if (mqtt_message.retry_count_ == (MQTT_PUBLISH_MAX_RETRY - 1)) { if (mqtt_message.retry_count_ == (MQTT_PUBLISH_MAX_RETRY - 1)) {
LOG_ERROR(F("Failed to publish to %s after %d attempts"), topic, mqtt_message.retry_count_ + 1); LOG_ERROR("Failed to publish to %s after %d attempts", topic, mqtt_message.retry_count_ + 1);
mqtt_publish_fails_++; // increment failure counter mqtt_publish_fails_++; // increment failure counter
mqtt_messages_.pop_front(); // delete mqtt_messages_.pop_front(); // delete
return; return;
} else { } else {
// update the record // update the record
mqtt_messages_.front().retry_count_++; mqtt_messages_.front().retry_count_++;
LOG_DEBUG(F("Failed to publish to %s. Trying again, #%d"), topic, mqtt_message.retry_count_ + 1); LOG_DEBUG("Failed to publish to %s. Trying again, #%d", topic, mqtt_message.retry_count_ + 1);
return; // leave on queue for next time so it gets republished return; // leave on queue for next time so it gets republished
} }
} }
@@ -888,7 +885,7 @@ void Mqtt::process_queue() {
if (mqtt_qos_ != 0) { if (mqtt_qos_ != 0) {
mqtt_messages_.front().packet_id_ = packet_id; mqtt_messages_.front().packet_id_ = packet_id;
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
LOG_DEBUG(F("[DEBUG] Setting packetID for ACK to %d"), packet_id); LOG_DEBUG("[DEBUG] Setting packetID for ACK to %d", packet_id);
#endif #endif
return; return;
} }
@@ -902,23 +899,24 @@ void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model,
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> dev_json; StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> dev_json;
// always create the ids // always create the ids
JsonArray ids = dev_json.createNestedArray("ids"); JsonArray ids = dev_json.createNestedArray("ids");
char ha_device[40]; char ha_device[40];
std::string device_type_name = EMSdevice::device_type_2_device_name(dv.device_type); auto device_type_name = EMSdevice::device_type_2_device_name(dv.device_type);
snprintf(ha_device, sizeof(ha_device), "ems-esp-%s", device_type_name.c_str()); snprintf(ha_device, sizeof(ha_device), "ems-esp-%s", device_type_name);
ids.add(ha_device); ids.add(ha_device);
if (create_device_config) { if (create_device_config) {
device_type_name[0] = toupper(device_type_name[0]); // capitalize auto cap_name = strdup(device_type_name);
dev_json["name"] = "EMS-ESP " + device_type_name; cap_name[0] = toupper(cap_name[0]); // capitalize
dev_json["name"] = std::string("EMS-ESP ") + cap_name;
dev_json["mf"] = brand; dev_json["mf"] = brand;
dev_json["mdl"] = model; dev_json["mdl"] = model;
dev_json["via_device"] = "ems-esp"; dev_json["via_device"] = "ems-esp";
} }
// calculate the min and max // calculate the min and max
int16_t dv_set_min; int16_t dv_set_min;
int16_t dv_set_max; uint16_t dv_set_max;
(void)dv.get_min_max(dv_set_min, dv_set_max); (void)dv.get_min_max(dv_set_min, dv_set_max);
// determine if we're creating the command topics which we use special HA configs // determine if we're creating the command topics which we use special HA configs
@@ -927,8 +925,8 @@ void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model,
publish_ha_sensor_config(dv.type, publish_ha_sensor_config(dv.type,
dv.tag, dv.tag,
dv.get_fullname(), dv.get_fullname().c_str(),
dv.fullname[0], (dv.fullname ? dv.fullname[0] : nullptr), // EN name
dv.device_type, dv.device_type,
dv.short_name, dv.short_name,
dv.uom, dv.uom,
@@ -942,50 +940,47 @@ void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model,
} }
// publish HA sensor for System using the heartbeat tag // publish HA sensor for System using the heartbeat tag
void Mqtt::publish_system_ha_sensor_config(uint8_t type, const __FlashStringHelper * name, const __FlashStringHelper * entity, const uint8_t uom) { void Mqtt::publish_system_ha_sensor_config(uint8_t type, const char * name, const char * entity, const uint8_t uom) {
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc; StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
JsonObject dev_json = doc.createNestedObject("dev"); JsonObject dev_json = doc.createNestedObject("dev");
JsonArray ids = dev_json.createNestedArray("ids"); JsonArray ids = dev_json.createNestedArray("ids");
ids.add("ems-esp"); ids.add("ems-esp");
auto fullname = read_flash_string(name); publish_ha_sensor_config(type, DeviceValueTAG::TAG_HEARTBEAT, name, name, EMSdevice::DeviceType::SYSTEM, entity, uom, false, false, nullptr, 0, 0, 0, dev_json);
publish_ha_sensor_config(type, DeviceValueTAG::TAG_HEARTBEAT, fullname, name, EMSdevice::DeviceType::SYSTEM, entity, uom, false, false, nullptr, 0, 0, 0, dev_json);
} }
// MQTT discovery configs // MQTT discovery configs
// entity must match the key/value pair in the *_data topic // entity must match the key/value pair in the *_data topic
// note: some extra string copying done here, it looks messy but does help with heap fragmentation issues // note: some extra string copying done here, it looks messy but does help with heap fragmentation issues
void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevice::DeviceValueType void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevice::DeviceValueType
uint8_t tag, // EMSdevice::DeviceValueTAG uint8_t tag, // EMSdevice::DeviceValueTAG
const std::string & fullname, // fullname, already translated const char * const fullname, // fullname, already translated
const __FlashStringHelper * const en_name, const char * const en_name,
const uint8_t device_type, // EMSdevice::DeviceType const uint8_t device_type, // EMSdevice::DeviceType
const __FlashStringHelper * const entity, // same as shortname const char * const entity, // same as shortname
const uint8_t uom, // EMSdevice::DeviceValueUOM (0=NONE) const uint8_t uom, // EMSdevice::DeviceValueUOM (0=NONE)
const bool remove, // true if we want to remove this topic const bool remove, // true if we want to remove this topic
const bool has_cmd, const bool has_cmd,
const __FlashStringHelper * const ** options, const char * const ** options,
uint8_t options_size, uint8_t options_size,
const int16_t dv_set_min, const int16_t dv_set_min,
const int16_t dv_set_max, const int16_t dv_set_max,
const JsonObject & dev_json) { const JsonObject & dev_json) {
// ignore if name (fullname) is empty // ignore if name (fullname) is empty
if (fullname.empty() || en_name == nullptr) { if (fullname == nullptr || en_name == nullptr) {
return; return;
} }
// create the device name // create the device name
char device_name[50]; auto device_name = EMSdevice::device_type_2_device_name(device_type);
strlcpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name));
// create entity by add the hc/wwc tag if present, separating with a . // create entity by add the hc/wwc tag if present, separating with a .
char new_entity[50]; char new_entity[50];
if (tag >= DeviceValueTAG::TAG_HC1) { if (tag >= DeviceValueTAG::TAG_HC1) {
snprintf(new_entity, sizeof(new_entity), "%s.%s", EMSdevice::tag_to_string(tag).c_str(), read_flash_string(entity).c_str()); snprintf(new_entity, sizeof(new_entity), "%s.%s", EMSdevice::tag_to_string(tag).c_str(), (entity));
} else { } else {
snprintf(new_entity, sizeof(new_entity), "%s", read_flash_string(entity).c_str()); snprintf(new_entity, sizeof(new_entity), "%s", (entity));
} }
// build unique identifier which will be used in the topic, replacing all . with _ as not to break HA // build unique identifier which will be used in the topic, replacing all . with _ as not to break HA
@@ -1037,7 +1032,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
// if we're asking to remove this topic, send an empty payload and exit // if we're asking to remove this topic, send an empty payload and exit
// https://github.com/emsesp/EMS-ESP32/issues/196 // https://github.com/emsesp/EMS-ESP32/issues/196
if (remove) { if (remove) {
LOG_DEBUG(F("Removing HA config for %s"), uniq); LOG_DEBUG("Removing HA config for %s", uniq);
publish_ha(topic); publish_ha(topic);
return; return;
} }
@@ -1107,7 +1102,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
// friendly name = <tag> <name> // friendly name = <tag> <name>
char ha_name[70]; char ha_name[70];
char * F_name = strdup(fullname.c_str()); char * F_name = strdup(fullname);
F_name[0] = toupper(F_name[0]); // capitalize first letter F_name[0] = toupper(F_name[0]); // capitalize first letter
if (have_tag) { if (have_tag) {
snprintf(ha_name, sizeof(ha_name), "%s %s", EMSdevice::tag_to_string(tag).c_str(), F_name); snprintf(ha_name, sizeof(ha_name), "%s %s", EMSdevice::tag_to_string(tag).c_str(), F_name);
@@ -1120,14 +1115,12 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
// entity id is generated from the name, see https://www.home-assistant.io/docs/mqtt/discovery/#use-object_id-to-influence-the-entity-id // entity id is generated from the name, see https://www.home-assistant.io/docs/mqtt/discovery/#use-object_id-to-influence-the-entity-id
// so we override it to make it unique using entity_id // so we override it to make it unique using entity_id
// See https://github.com/emsesp/EMS-ESP32/issues/596 // See https://github.com/emsesp/EMS-ESP32/issues/596
// keep it compatible to v3.4, use english fullname, no prefix (basename prefix commmented out) // keep it compatible to v3.4, use english fullname, no prefix (basename prefix commented out)
char object_id[130]; char object_id[130];
if (have_tag) { if (have_tag) {
// snprintf(object_id, sizeof(object_id), "%s_%s_%s_%s", mqtt_basename_, device_name, EMSdevice::tag_to_string(tag).c_str(), read_flash_string(en_name).c_str()); snprintf(object_id, sizeof(object_id), "%s_%s_%s", device_name, EMSdevice::tag_to_string(tag).c_str(), en_name);
snprintf(object_id, sizeof(object_id), "%s_%s_%s", device_name, EMSdevice::tag_to_string(tag).c_str(), read_flash_string(en_name).c_str());
} else { } else {
// snprintf(object_id, sizeof(object_id), "%s_%s_%s", mqtt_basename_, device_name, read_flash_string(en_name).c_str()); snprintf(object_id, sizeof(object_id), "%s_%s", device_name, en_name);
snprintf(object_id, sizeof(object_id), "%s_%s", device_name, read_flash_string(en_name).c_str());
} }
doc["object_id"] = object_id; doc["object_id"] = object_id;
@@ -1137,7 +1130,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
if (is_nested()) { if (is_nested()) {
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", new_entity); snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", new_entity);
} else { } else {
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", read_flash_string(entity).c_str()); snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", entity);
} }
doc["val_tpl"] = val_tpl; doc["val_tpl"] = val_tpl;
@@ -1147,9 +1140,9 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
// and has no unit of measure or icon // and has no unit of measure or icon
if (type == DeviceValueType::BOOL) { if (type == DeviceValueType::BOOL) {
char result[10]; char result[10];
doc[F("payload_on")] = Helpers::render_boolean(result, true); doc[("payload_on")] = Helpers::render_boolean(result, true);
doc[F("payload_off")] = Helpers::render_boolean(result, false); doc[("payload_off")] = Helpers::render_boolean(result, false);
doc[sc_ha] = F_(measurement); doc[sc_ha] = F_(measurement);
} else { } else {
// always set the uom // always set the uom
if (uom != DeviceValueUOM::NONE) { if (uom != DeviceValueUOM::NONE) {
@@ -1169,11 +1162,11 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
case DeviceValueUOM::DEGREES: case DeviceValueUOM::DEGREES:
case DeviceValueUOM::DEGREES_R: case DeviceValueUOM::DEGREES_R:
doc[sc_ha] = F_(measurement); doc[sc_ha] = F_(measurement);
doc[dc_ha] = F("temperature"); // no icon needed doc[dc_ha] = "temperature"; // no icon needed
break; break;
case DeviceValueUOM::PERCENT: case DeviceValueUOM::PERCENT:
doc[sc_ha] = F_(measurement); doc[sc_ha] = F_(measurement);
doc[dc_ha] = F("power_factor"); // no icon needed doc[dc_ha] = "power_factor"; // no icon needed
break; break;
case DeviceValueUOM::SECONDS: case DeviceValueUOM::SECONDS:
case DeviceValueUOM::MINUTES: case DeviceValueUOM::MINUTES:
@@ -1198,11 +1191,11 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
} else { } else {
doc[sc_ha] = F_(measurement); doc[sc_ha] = F_(measurement);
} }
doc[dc_ha] = F("energy"); // no icon needed doc[dc_ha] = "energy"; // no icon needed
break; break;
case DeviceValueUOM::KWH: case DeviceValueUOM::KWH:
doc[sc_ha] = F_(total_increasing); doc[sc_ha] = F_(total_increasing);
doc[dc_ha] = F("energy"); // no icon needed doc[dc_ha] = "energy"; // no icon needed
break; break;
case DeviceValueUOM::UA: case DeviceValueUOM::UA:
doc[ic_ha] = F_(iconua); doc[ic_ha] = F_(iconua);
@@ -1210,16 +1203,16 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
break; break;
case DeviceValueUOM::BAR: case DeviceValueUOM::BAR:
doc[sc_ha] = F_(measurement); doc[sc_ha] = F_(measurement);
doc[dc_ha] = F("pressure"); doc[dc_ha] = "pressure";
break; break;
case DeviceValueUOM::W: case DeviceValueUOM::W:
case DeviceValueUOM::KW: case DeviceValueUOM::KW:
doc[sc_ha] = F_(measurement); doc[sc_ha] = F_(measurement);
doc[dc_ha] = F("power"); doc[dc_ha] = "power";
break; break;
case DeviceValueUOM::DBM: case DeviceValueUOM::DBM:
doc[sc_ha] = F_(measurement); doc[sc_ha] = F_(measurement);
doc[dc_ha] = F("signal_strength"); doc[dc_ha] = "signal_strength";
break; break;
case DeviceValueUOM::NONE: case DeviceValueUOM::NONE:
// for device entities which have numerical values, with no UOM // for device entities which have numerical values, with no UOM
@@ -1248,7 +1241,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
publish_ha(topic, doc.as<JsonObject>()); publish_ha(topic, doc.as<JsonObject>());
} }
void Mqtt::publish_ha_climate_config(uint8_t tag, bool has_roomtemp, bool remove) { void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, const bool remove, const int16_t min, const uint16_t max) {
uint8_t hc_num = tag - DeviceValueTAG::TAG_HC1 + 1; uint8_t hc_num = tag - DeviceValueTAG::TAG_HC1 + 1;
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
@@ -1321,8 +1314,8 @@ void Mqtt::publish_ha_climate_config(uint8_t tag, bool has_roomtemp, bool remove
doc["curr_temp_tpl"] = currtemp_s; doc["curr_temp_tpl"] = currtemp_s;
} }
doc["min_temp"] = Helpers::render_value(min_s, (uint32_t)5, 0, EMSESP::system_.fahrenheit() ? 2 : 0); doc["min_temp"] = Helpers::render_value(min_s, min, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
doc["max_temp"] = Helpers::render_value(max_s, (uint32_t)30, 0, EMSESP::system_.fahrenheit() ? 2 : 0); doc["max_temp"] = Helpers::render_value(max_s, max, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
doc["temp_step"] = "0.5"; doc["temp_step"] = "0.5";
// the HA climate component only responds to auto, heat and off // the HA climate component only responds to auto, heat and off
@@ -1348,11 +1341,13 @@ std::string Mqtt::tag_to_topic(uint8_t device_type, uint8_t tag) {
return EMSdevice::tag_to_mqtt(tag); return EMSdevice::tag_to_mqtt(tag);
} }
std::string topic = EMSdevice::device_type_2_device_name(device_type);
// if there is a tag add it // if there is a tag add it
if (!EMSdevice::tag_to_mqtt(tag).empty() && ((tag == DeviceValueTAG::TAG_BOILER_DATA_WW) || (!is_nested() && tag >= DeviceValueTAG::TAG_HC1))) { if (!EMSdevice::tag_to_mqtt(tag).empty() && ((tag == DeviceValueTAG::TAG_BOILER_DATA_WW) || (!is_nested() && tag >= DeviceValueTAG::TAG_HC1))) {
return EMSdevice::device_type_2_device_name(device_type) + "_data_" + EMSdevice::tag_to_mqtt(tag); return topic + "_data_" + EMSdevice::tag_to_mqtt(tag);
} else { } else {
return EMSdevice::device_type_2_device_name(device_type) + "_data"; return topic + "_data";
} }
} }

View File

@@ -79,36 +79,35 @@ class Mqtt {
static void resubscribe(); static void resubscribe();
static void publish(const std::string & topic, const std::string & payload); static void publish(const std::string & topic, const std::string & payload);
static void publish(const __FlashStringHelper * topic, const char * payload); static void publish(const char * topic, const char * payload);
static void publish(const std::string & topic, const JsonObject & payload); static void publish(const std::string & topic, const JsonObject & payload);
static void publish(const __FlashStringHelper * topic, const JsonObject & payload); static void publish(const char * topic, const JsonObject & payload);
static void publish(const __FlashStringHelper * topic, const std::string & payload); static void publish(const char * topic, const std::string & payload);
static void publish_retain(const std::string & topic, const JsonObject & payload, bool retain); static void publish_retain(const std::string & topic, const JsonObject & payload, bool retain);
static void publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain); static void publish_retain(const char * topic, const std::string & payload, bool retain);
static void publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain); static void publish_retain(const char * topic, const JsonObject & payload, bool retain);
static void publish_ha(const std::string & topic, const JsonObject & payload); static void publish_ha(const char * topic, const JsonObject & payload);
static void publish_ha(const __FlashStringHelper * topic, const JsonObject & payload); static void publish_ha(const char * topic);
static void publish_ha(const std::string & topic);
static void static void
publish_ha_sensor_config(DeviceValue & dv, const std::string & model, const std::string & brand, const bool remove, const bool create_device_config = false); publish_ha_sensor_config(DeviceValue & dv, const std::string & model, const std::string & brand, const bool remove, const bool create_device_config = false);
static void publish_ha_sensor_config(uint8_t type, static void publish_ha_sensor_config(uint8_t type,
uint8_t tag, uint8_t tag,
const std::string & fullname, const char * const fullname,
const __FlashStringHelper * const en_name, const char * const en_name,
const uint8_t device_type, const uint8_t device_type,
const __FlashStringHelper * const entity, const char * const entity,
const uint8_t uom, const uint8_t uom,
const bool remove, const bool remove,
const bool has_cmd, const bool has_cmd,
const __FlashStringHelper * const ** options, const char * const ** options,
uint8_t options_size, uint8_t options_size,
const int16_t dv_set_min, const int16_t dv_set_min,
const int16_t dv_set_max, const int16_t dv_set_max,
const JsonObject & dev_json); const JsonObject & dev_json);
static void publish_system_ha_sensor_config(uint8_t type, const __FlashStringHelper * name, const __FlashStringHelper * entity, const uint8_t uom); static void publish_system_ha_sensor_config(uint8_t type, const char * name, const char * entity, const uint8_t uom);
static void publish_ha_climate_config(uint8_t tag, bool has_roomtemp, bool remove = false); static void publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, const bool remove = false, const int16_t min = 5, const uint16_t max = 30);
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type); static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
static void show_mqtt(uuid::console::Shell & shell); static void show_mqtt(uuid::console::Shell & shell);
@@ -167,6 +166,14 @@ class Mqtt {
return mqtt_publish_fails_; return mqtt_publish_fails_;
} }
static uint32_t publish_queued() {
return mqtt_messages_.size();
}
static uint8_t connect_count() {
return connectcount_;
}
static void reset_mqtt(); static void reset_mqtt();
static bool is_nested() { static bool is_nested() {
@@ -178,7 +185,7 @@ class Mqtt {
} }
static bool publish_single() { static bool publish_single() {
return publish_single_; return mqtt_enabled_ && publish_single_;
} }
static bool publish_single2cmd() { static bool publish_single2cmd() {
@@ -190,7 +197,7 @@ class Mqtt {
} }
static bool ha_enabled() { static bool ha_enabled() {
return ha_enabled_; return mqtt_enabled_ && ha_enabled_;
} }
static void ha_enabled(bool ha_enabled) { static void ha_enabled(bool ha_enabled) {

View File

@@ -57,7 +57,7 @@ void Shower::loop() {
// first check to see if hot water has been on long enough to be recognized as a Shower/Bath // first check to see if hot water has been on long enough to be recognized as a Shower/Bath
if (!shower_state_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) { if (!shower_state_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) {
set_shower_state(true); set_shower_state(true);
LOG_DEBUG(F("[Shower] hot water still running, starting shower timer")); LOG_DEBUG("[Shower] hot water still running, starting shower timer");
} }
// check if the shower has been on too long // check if the shower has been on too long
else if ((time_now - timer_start_) > shower_alert_trigger_) { else if ((time_now - timer_start_) > shower_alert_trigger_) {
@@ -82,8 +82,8 @@ void Shower::loop() {
char s[50]; char s[50];
snprintf(s, 50, "%d minutes and %d seconds", (uint8_t)(duration_ / 60000), (uint8_t)((duration_ / 1000) % 60)); snprintf(s, 50, "%d minutes and %d seconds", (uint8_t)(duration_ / 60000), (uint8_t)((duration_ / 1000) % 60));
doc["duration"] = s; doc["duration"] = s;
Mqtt::publish(F("shower_data"), doc.as<JsonObject>()); Mqtt::publish("shower_data", doc.as<JsonObject>());
LOG_DEBUG(F("[Shower] finished with duration %d"), duration_); LOG_DEBUG("[Shower] finished with duration %d", duration_);
} }
} }
@@ -109,7 +109,7 @@ void Shower::loop() {
// turn back on the hot water for the shower // turn back on the hot water for the shower
void Shower::shower_alert_stop() { void Shower::shower_alert_stop() {
if (doing_cold_shot_) { if (doing_cold_shot_) {
LOG_DEBUG(F("Shower Alert stopped")); LOG_DEBUG("Shower Alert stopped");
(void)Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "true"); (void)Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "true");
doing_cold_shot_ = false; doing_cold_shot_ = false;
} }
@@ -117,7 +117,7 @@ void Shower::shower_alert_stop() {
// turn off hot water to send a shot of cold // turn off hot water to send a shot of cold
void Shower::shower_alert_start() { void Shower::shower_alert_start() {
if (shower_alert_) { if (shower_alert_) {
LOG_DEBUG(F("Shower Alert started")); LOG_DEBUG("Shower Alert started");
(void)Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false"); (void)Command::call(EMSdevice::DeviceType::BOILER, "wwtapactivated", "false");
doing_cold_shot_ = true; doing_cold_shot_ = true;
alert_timer_start_ = uuid::get_uptime(); // timer starts now alert_timer_start_ = uuid::get_uptime(); // timer starts now
@@ -144,22 +144,22 @@ void Shower::set_shower_state(bool state, bool force) {
// always publish as a string // always publish as a string
char s[7]; char s[7];
Mqtt::publish(F("shower_active"), Helpers::render_boolean(s, shower_state_)); // https://github.com/emsesp/EMS-ESP/issues/369 Mqtt::publish("shower_active", Helpers::render_boolean(s, shower_state_)); // https://github.com/emsesp/EMS-ESP/issues/369
// send out HA MQTT Discovery config topic // send out HA MQTT Discovery config topic
if ((Mqtt::ha_enabled()) && (!ha_configdone_ || force)) { if ((Mqtt::ha_enabled()) && (!ha_configdone_ || force)) {
ha_configdone_ = true; ha_configdone_ = true;
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc; StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["name"] = FJSON("Shower Active"); doc["name"] = "Shower Active";
doc["uniq_id"] = FJSON("shower_active"); doc["uniq_id"] = "shower_active";
doc["~"] = Mqtt::base(); doc["~"] = Mqtt::base();
doc["stat_t"] = FJSON("~/shower_active"); doc["stat_t"] = "~/shower_active";
// always render boolean as strings for HA // always render boolean as strings for HA
char result[10]; char result[10];
doc[F("payload_on")] = Helpers::render_boolean(result, true); doc[("payload_on")] = Helpers::render_boolean(result, true);
doc[F("payload_off")] = Helpers::render_boolean(result, false); doc[("payload_off")] = Helpers::render_boolean(result, false);
JsonObject dev = doc.createNestedObject("dev"); JsonObject dev = doc.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids"); JsonArray ids = dev.createNestedArray("ids");

Some files were not shown because too many files have changed in this diff Show More