diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index a5a2f8731..f7f9f531d 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -22,3 +22,5 @@ - Syslog BOM only for utf-8 messages [#91](https://github.com/emsesp/EMS-ESP32/issues/91) - Check for KM200 by device-id 0x48, remove tx-delay[#90](https://github.com/emsesp/EMS-ESP32/issues/90) - rename `fastheatupfactor` to `fastheatup` and add percent [#122] +- "unit" renamed to "uom" in API call to recall a Device Value [**BREAKING CHANGE**] +- initial backend React changes to replace the class componentns (HOCs) with React Hooks diff --git a/Makefile b/Makefile index 4b611b114..114d15fa0 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ CXX_STANDARD := -std=c++11 #---------------------------------------------------------------------- # Defined Symbols #---------------------------------------------------------------------- -DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_DEFAULT_BOARD_PROFILE=\"LOLIN\" +DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_DEFAULT_BOARD_PROFILE=\"LOLIN\" #---------------------------------------------------------------------- # Sources & Files diff --git a/interface/package-lock.json b/interface/package-lock.json index a9ca3a7a6..62b4fd527 100644 --- a/interface/package-lock.json +++ b/interface/package-lock.json @@ -8,13 +8,13 @@ "name": "emsesp-react", "version": "0.1.0", "dependencies": { - "@material-ui/core": "^4.11.4", + "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.11.2", "@msgpack/msgpack": "^2.7.0", - "@types/lodash": "^4.14.168", - "@types/node": "^15.0.1", - "@types/react": "^17.0.4", - "@types/react-dom": "^17.0.3", + "@types/lodash": "^4.14.172", + "@types/node": "^12.20.20", + "@types/react": "^17.0.19", + "@types/react-dom": "^17.0.9", "@types/react-material-ui-form-validator": "^2.1.0", "@types/react-router": "^5.1.13", "@types/react-router-dom": "^5.1.7", @@ -35,7 +35,7 @@ "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", "sockette": "^2.0.6", - "typescript": "4.2.4", + "typescript": "4.3.5", "zlib": "^1.0.5" }, "devDependencies": { @@ -2050,13 +2050,13 @@ } }, "node_modules/@material-ui/core": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.4.tgz", - "integrity": "sha512-oqb+lJ2Dl9HXI9orc6/aN8ZIAMkeThufA5iZELf2LQeBn2NtjVilF5D2w7e9RpntAzDb4jK5DsVhkfOvFY/8fg==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.3.tgz", + "integrity": "sha512-sdpgI/PL56QVsEJldwEe4FFaFTLUqN+rd7sSZiRCdx2E/C7z5yK0y/khAWVBH24tXwto7I1hCzNWfJGZIYJKnw==", "dependencies": { "@babel/runtime": "^7.4.4", "@material-ui/styles": "^4.11.4", - "@material-ui/system": "^4.11.3", + "@material-ui/system": "^4.12.1", "@material-ui/types": "5.1.0", "@material-ui/utils": "^4.11.2", "@types/react-transition-group": "^4.2.0", @@ -2137,9 +2137,9 @@ } }, "node_modules/@material-ui/system": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.11.3.tgz", - "integrity": "sha512-SY7otguNGol41Mu2Sg6KbBP1ZRFIbFLHGK81y4KYbsV2yIcaEPOmsCK6zwWlp+2yTV3J/VwT6oSBARtGIVdXPw==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.1.tgz", + "integrity": "sha512-lUdzs4q9kEXZGhbN7BptyiS1rLNHe6kG9o8Y307HCvF4sQxbCgpL2qi+gUk+yI8a2DNk48gISEQxoxpgph0xIw==", "dependencies": { "@babel/runtime": "^7.4.4", "@material-ui/utils": "^4.11.2", @@ -2148,6 +2148,20 @@ }, "engines": { "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@material-ui/types": { @@ -2639,9 +2653,9 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, "node_modules/@types/lodash": { - "version": "4.14.168", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz", - "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==" + "version": "4.14.173", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.173.tgz", + "integrity": "sha512-vv0CAYoaEjCw/mLy96GBTnRoZrSxkGE0BKzKimdR8P3OzrNYNvBgtW7p055A+E8C31vXNUhWKoFCbhq7gbyhFg==" }, "node_modules/@types/material-ui": { "version": "0.21.8", @@ -2658,9 +2672,9 @@ "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==" }, "node_modules/@types/node": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.1.tgz", - "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==" + "version": "12.20.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.25.tgz", + "integrity": "sha512-hcTWqk7DR/HrN9Xe7AlJwuCaL13Vcd9/g/T54YrJz4Q3ESM5mr33YCzW2bOfzSIc3aZMeGBvbLGvgN6mIJ0I5Q==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.0", @@ -2688,9 +2702,9 @@ "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" }, "node_modules/@types/react": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.4.tgz", - "integrity": "sha512-onz2BqScSFMoTRdJUZUDD/7xrusM8hBA2Fktk2qgaTYPCgPvWnDEgkrOs8hhPUf2jfcIXkJ5yK6VfYormJS3Jw==", + "version": "17.0.22", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.22.tgz", + "integrity": "sha512-kq/BMeaAVLJM6Pynh8C2rnr/drCK+/5ksH0ch9asz+8FW3DscYCIEFtCeYTFeIx/ubvOsMXmRfy7qEJ76gM96A==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2706,9 +2720,9 @@ } }, "node_modules/@types/react-dom": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.3.tgz", - "integrity": "sha512-4NnJbCeWE+8YBzupn/YrJxZ8VnjcJq5iR1laqQ1vkpQgBiA7bwk0Rp24fxsdNinzJY2U+HHS4dJJDPdoMjdJ7w==", + "version": "17.0.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz", + "integrity": "sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==", "dependencies": { "@types/react": "*" } @@ -19249,9 +19263,9 @@ } }, "node_modules/typescript": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", - "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -23150,13 +23164,13 @@ } }, "@material-ui/core": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.4.tgz", - "integrity": "sha512-oqb+lJ2Dl9HXI9orc6/aN8ZIAMkeThufA5iZELf2LQeBn2NtjVilF5D2w7e9RpntAzDb4jK5DsVhkfOvFY/8fg==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.3.tgz", + "integrity": "sha512-sdpgI/PL56QVsEJldwEe4FFaFTLUqN+rd7sSZiRCdx2E/C7z5yK0y/khAWVBH24tXwto7I1hCzNWfJGZIYJKnw==", "requires": { "@babel/runtime": "^7.4.4", "@material-ui/styles": "^4.11.4", - "@material-ui/system": "^4.11.3", + "@material-ui/system": "^4.12.1", "@material-ui/types": "5.1.0", "@material-ui/utils": "^4.11.2", "@types/react-transition-group": "^4.2.0", @@ -23200,9 +23214,9 @@ } }, "@material-ui/system": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.11.3.tgz", - "integrity": "sha512-SY7otguNGol41Mu2Sg6KbBP1ZRFIbFLHGK81y4KYbsV2yIcaEPOmsCK6zwWlp+2yTV3J/VwT6oSBARtGIVdXPw==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.1.tgz", + "integrity": "sha512-lUdzs4q9kEXZGhbN7BptyiS1rLNHe6kG9o8Y307HCvF4sQxbCgpL2qi+gUk+yI8a2DNk48gISEQxoxpgph0xIw==", "requires": { "@babel/runtime": "^7.4.4", "@material-ui/utils": "^4.11.2", @@ -23611,9 +23625,9 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, "@types/lodash": { - "version": "4.14.168", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz", - "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==" + "version": "4.14.173", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.173.tgz", + "integrity": "sha512-vv0CAYoaEjCw/mLy96GBTnRoZrSxkGE0BKzKimdR8P3OzrNYNvBgtW7p055A+E8C31vXNUhWKoFCbhq7gbyhFg==" }, "@types/material-ui": { "version": "0.21.8", @@ -23630,9 +23644,9 @@ "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==" }, "@types/node": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.1.tgz", - "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==" + "version": "12.20.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.25.tgz", + "integrity": "sha512-hcTWqk7DR/HrN9Xe7AlJwuCaL13Vcd9/g/T54YrJz4Q3ESM5mr33YCzW2bOfzSIc3aZMeGBvbLGvgN6mIJ0I5Q==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -23660,9 +23674,9 @@ "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" }, "@types/react": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.4.tgz", - "integrity": "sha512-onz2BqScSFMoTRdJUZUDD/7xrusM8hBA2Fktk2qgaTYPCgPvWnDEgkrOs8hhPUf2jfcIXkJ5yK6VfYormJS3Jw==", + "version": "17.0.22", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.22.tgz", + "integrity": "sha512-kq/BMeaAVLJM6Pynh8C2rnr/drCK+/5ksH0ch9asz+8FW3DscYCIEFtCeYTFeIx/ubvOsMXmRfy7qEJ76gM96A==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -23685,9 +23699,9 @@ } }, "@types/react-dom": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.3.tgz", - "integrity": "sha512-4NnJbCeWE+8YBzupn/YrJxZ8VnjcJq5iR1laqQ1vkpQgBiA7bwk0Rp24fxsdNinzJY2U+HHS4dJJDPdoMjdJ7w==", + "version": "17.0.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz", + "integrity": "sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==", "requires": { "@types/react": "*" } @@ -37376,9 +37390,9 @@ } }, "typescript": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", - "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==" + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==" }, "unbox-primitive": { "version": "1.0.1", diff --git a/interface/package.json b/interface/package.json index fd3d99c94..abd7f4a96 100644 --- a/interface/package.json +++ b/interface/package.json @@ -3,13 +3,13 @@ "version": "0.1.0", "private": true, "dependencies": { - "@material-ui/core": "^4.11.4", + "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.11.2", "@msgpack/msgpack": "^2.7.0", - "@types/lodash": "^4.14.168", - "@types/node": "^15.0.1", - "@types/react": "^17.0.4", - "@types/react-dom": "^17.0.3", + "@types/lodash": "^4.14.172", + "@types/node": "^12.20.20", + "@types/react": "^17.0.19", + "@types/react-dom": "^17.0.9", "@types/react-material-ui-form-validator": "^2.1.0", "@types/react-router": "^5.1.13", "@types/react-router-dom": "^5.1.7", @@ -30,7 +30,7 @@ "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", "sockette": "^2.0.6", - "typescript": "4.2.4", + "typescript": "4.3.5", "zlib": "^1.0.5" }, "scripts": { diff --git a/interface/src/authentication/UnauthenticatedRoute.tsx b/interface/src/authentication/UnauthenticatedRoute.tsx index ca6512b7d..9396c44b3 100644 --- a/interface/src/authentication/UnauthenticatedRoute.tsx +++ b/interface/src/authentication/UnauthenticatedRoute.tsx @@ -22,25 +22,13 @@ interface UnauthenticatedRouteProps | React.ComponentType; } -type RenderComponent = (props: RouteComponentProps) => React.ReactNode; - class UnauthenticatedRoute extends Route { public render() { - const { - authenticationContext, - component: Component, - features, - ...rest - } = this.props; - const renderComponent: RenderComponent = (props) => { - if (authenticationContext.me) { - return ; - } - if (Component) { - return ; - } - }; - return ; + const { authenticationContext, features, ...rest } = this.props; + if (authenticationContext.me) { + return ; + } + return ; } } diff --git a/interface/src/components/FormLoader.tsx b/interface/src/components/FormLoader.tsx new file mode 100644 index 000000000..3efeeb663 --- /dev/null +++ b/interface/src/components/FormLoader.tsx @@ -0,0 +1,56 @@ +import { FC } from 'react'; + +import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; +import { Button, LinearProgress, Typography } from '@material-ui/core'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + loadingSettings: { + margin: theme.spacing(0.5) + }, + loadingSettingsDetails: { + margin: theme.spacing(4), + textAlign: 'center' + }, + button: { + marginRight: theme.spacing(2), + marginTop: theme.spacing(2) + } + }) +); + +interface FormLoaderProps { + errorMessage?: string; + loadData: () => void; +} + +const FormLoader: FC = ({ errorMessage, loadData }) => { + const classes = useStyles(); + if (errorMessage) { + return ( +
+ + {errorMessage} + + +
+ ); + } + return ( +
+ + + Loading… + +
+ ); +}; + +export default FormLoader; diff --git a/interface/src/components/index.ts b/interface/src/components/index.ts index 47348a94d..e98047039 100644 --- a/interface/src/components/index.ts +++ b/interface/src/components/index.ts @@ -5,6 +5,7 @@ export { default as HighlightAvatar } from './HighlightAvatar'; export { default as MenuAppBar } from './MenuAppBar'; export { default as PasswordValidator } from './PasswordValidator'; export { default as RestFormLoader } from './RestFormLoader'; +export { default as FormLoader } from './FormLoader'; export { default as SectionContent } from './SectionContent'; export { default as WebSocketFormLoader } from './WebSocketFormLoader'; export { default as ErrorButton } from './ErrorButton'; diff --git a/interface/src/features/FeaturesWrapper.tsx b/interface/src/features/FeaturesWrapper.tsx index 4742a4bcc..e5c762f23 100644 --- a/interface/src/features/FeaturesWrapper.tsx +++ b/interface/src/features/FeaturesWrapper.tsx @@ -1,58 +1,31 @@ -import { Component } from 'react'; +import { FC } from 'react'; -import { Features } from './types'; -import { FeaturesContext } from './FeaturesContext'; import FullScreenLoading from '../components/FullScreenLoading'; import ApplicationError from '../components/ApplicationError'; import { FEATURES_ENDPOINT } from '../api'; +import { useRest } from '../hooks'; -interface FeaturesWrapperState { - features?: Features; - error?: string; -} +import { Features } from './types'; +import { FeaturesContext } from './FeaturesContext'; -class FeaturesWrapper extends Component<{}, FeaturesWrapperState> { - state: FeaturesWrapperState = {}; +const FeaturesWrapper: FC = ({ children }) => { + const { data: features, errorMessage: error } = useRest({ + endpoint: FEATURES_ENDPOINT + }); - componentDidMount() { - this.fetchFeaturesDetails(); + if (features) { + return ( + + {children} + + ); } - fetchFeaturesDetails = () => { - fetch(FEATURES_ENDPOINT) - .then((response) => { - if (response.status === 200) { - return response.json(); - } else { - throw Error('Unexpected status code: ' + response.status); - } - }) - .then((features) => { - this.setState({ features }); - }) - .catch((error) => { - this.setState({ error: error.message }); - }); - }; - - render() { - const { features, error } = this.state; - if (features) { - return ( - - {this.props.children} - - ); - } - if (error) { - return ; - } - return ; + if (error) { + return ; } -} + + return ; +}; export default FeaturesWrapper; diff --git a/interface/src/hooks/index.ts b/interface/src/hooks/index.ts new file mode 100644 index 000000000..94897b22a --- /dev/null +++ b/interface/src/hooks/index.ts @@ -0,0 +1,2 @@ +export { default as useRest } from './useRest'; +export { default as useAuthorizedRest } from './useAuthorizedRest'; diff --git a/interface/src/hooks/useAuthorizedRest.ts b/interface/src/hooks/useAuthorizedRest.ts new file mode 100644 index 000000000..78fa7ce56 --- /dev/null +++ b/interface/src/hooks/useAuthorizedRest.ts @@ -0,0 +1,12 @@ +import { redirectingAuthorizedFetch } from '../authentication'; +import useRest, { RestRequestOptions } from './useRest'; + +const useAuthorizedRest = ({ + endpoint +}: Omit) => + useRest({ + endpoint, + fetchFunction: redirectingAuthorizedFetch + }); + +export default useAuthorizedRest; diff --git a/interface/src/hooks/useRest.ts b/interface/src/hooks/useRest.ts new file mode 100644 index 000000000..0d6b30743 --- /dev/null +++ b/interface/src/hooks/useRest.ts @@ -0,0 +1,79 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useSnackbar } from 'notistack'; + +export interface RestRequestOptions { + endpoint: string; + fetchFunction?: typeof fetch; +} + +const useRest = ({ + endpoint, + fetchFunction = fetch +}: RestRequestOptions) => { + const { enqueueSnackbar } = useSnackbar(); + + const [saving, setSaving] = useState(false); + const [data, setData] = useState(); + const [errorMessage, setErrorMessage] = useState(); + + const handleError = useCallback( + (error: any) => { + const errorMessage = error.message || 'Unknown error'; + enqueueSnackbar('Problem fetching: ' + errorMessage, { + variant: 'error' + }); + setErrorMessage(errorMessage); + }, + [enqueueSnackbar] + ); + + const loadData = useCallback(async () => { + setData(undefined); + setErrorMessage(undefined); + try { + const response = await fetchFunction(endpoint); + if (response.status !== 200) { + throw Error('Invalid status code: ' + response.status); + } + setData(await response.json()); + } catch (error) { + handleError(error); + } + }, [handleError, fetchFunction, endpoint]); + + const save = useCallback( + async (data: D) => { + setSaving(true); + setErrorMessage(undefined); + try { + const response = await fetchFunction(endpoint, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json' + } + }); + if (response.status !== 200) { + throw Error('Invalid status code: ' + response.status); + } + enqueueSnackbar('Update successful.', { variant: 'success' }); + setData(await response.json()); + } catch (error) { + handleError(error); + } finally { + setSaving(false); + } + }, + [enqueueSnackbar, handleError, fetchFunction, endpoint] + ); + + const saveData = () => data && save(data); + + useEffect(() => { + loadData(); + }, [loadData]); + + return { loadData, saveData, saving, setData, data, errorMessage } as const; +}; + +export default useRest; diff --git a/interface/src/utils/binding.ts b/interface/src/utils/binding.ts new file mode 100644 index 000000000..dac942044 --- /dev/null +++ b/interface/src/utils/binding.ts @@ -0,0 +1,33 @@ +type UpdateEntity = (state: (prevState: Readonly) => S) => void; + +export const extractEventValue = ( + event: React.ChangeEvent +) => { + switch (event.target.type) { + case 'number': + return event.target.valueAsNumber; + case 'checkbox': + return event.target.checked; + default: + return event.target.value; + } +}; + +export const updateValue = (updateEntity: UpdateEntity) => ( + event: React.ChangeEvent +) => { + updateEntity((prevState) => ({ + ...prevState, + [event.target.name]: extractEventValue(event) + })); +}; + +export const updateBooleanValue = (updateEntity: UpdateEntity) => ( + name: string, + value?: boolean +) => { + updateEntity((prevState) => ({ + ...prevState, + [name]: value + })); +}; diff --git a/interface/src/utils/index.ts b/interface/src/utils/index.ts new file mode 100644 index 000000000..1ad877297 --- /dev/null +++ b/interface/src/utils/index.ts @@ -0,0 +1 @@ +export * from './binding'; diff --git a/lib/uuid-common/src/read_flash_string.cpp b/lib/uuid-common/src/read_flash_string.cpp index a5d6b78fe..94fccb6b1 100644 --- a/lib/uuid-common/src/read_flash_string.cpp +++ b/lib/uuid-common/src/read_flash_string.cpp @@ -25,6 +25,10 @@ namespace uuid { std::string read_flash_string(const __FlashStringHelper * flash_str) { + if (flash_str == nullptr) { + return std::string(""); // prevent crash + } + std::string str(::strlen_P(reinterpret_cast(flash_str)), '\0'); ::strncpy_P(&str[0], reinterpret_cast(flash_str), str.capacity() + 1); diff --git a/lib_standalone/ESP8266React.h b/lib_standalone/ESP8266React.h index 908b776b6..fcea74c55 100644 --- a/lib_standalone/ESP8266React.h +++ b/lib_standalone/ESP8266React.h @@ -35,7 +35,7 @@ class DummySettings { uint8_t mqtt_qos = 0; bool mqtt_retain = false; bool enabled = true; - uint8_t nested_format = 1; + uint8_t nested_format = 1; // 1=nested 2=single uint8_t ha_climate_format = 1; bool ha_enabled = true; String base = "ems-esp"; diff --git a/src/console.cpp b/src/console.cpp index 21b5aa161..2825a74b5 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -404,8 +404,6 @@ void EMSESPShell::add_console_commands() { cmd_return = Command::call(device_type, cmd, arguments[2].c_str(), true, atoi(arguments[3].c_str()), json); } - shell.printfln("size=%d measure=%d overflowed=%d", doc.size(), measureJson(doc), doc.overflowed()); // TODO remove debug - if (cmd_return == CommandRet::OK && json.size()) { serializeJsonPretty(doc, shell); shell.println(); diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 97c0f34f1..5aa803a49 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -142,10 +142,10 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_device_value(TAG_BOILER_DATA, &burnWorkMin_, DeviceValueType::TIME, nullptr, FL_(burnWorkMin), DeviceValueUOM::MINUTES); register_device_value(TAG_BOILER_DATA, &heatWorkMin_, DeviceValueType::TIME, nullptr, FL_(heatWorkMin), DeviceValueUOM::MINUTES); register_device_value(TAG_BOILER_DATA, &UBAuptime_, DeviceValueType::TIME, nullptr, FL_(UBAuptime), DeviceValueUOM::MINUTES); - register_device_value(TAG_BOILER_DATA, &lastCode_, DeviceValueType::TEXT, nullptr, FL_(lastCode), DeviceValueUOM::NONE); - register_device_value(TAG_BOILER_DATA, &serviceCode_, DeviceValueType::TEXT, nullptr, FL_(serviceCode), DeviceValueUOM::NONE); - register_device_value(TAG_BOILER_DATA, &serviceCodeNumber_, DeviceValueType::USHORT, nullptr, FL_(serviceCodeNumber), DeviceValueUOM::NONE); - register_device_value(TAG_BOILER_DATA, &maintenanceMessage_, DeviceValueType::TEXT, nullptr, FL_(maintenanceMessage), DeviceValueUOM::NONE); + register_device_value(TAG_BOILER_DATA, &lastCode_, DeviceValueType::STRING, nullptr, FL_(lastCode), DeviceValueUOM::NONE); + register_device_value(TAG_BOILER_DATA, &serviceCode_, DeviceValueType::STRING, nullptr, FL_(serviceCode), DeviceValueUOM::NONE); + register_device_value(TAG_BOILER_DATA, &serviceCodeNumber_, DeviceValueType::USHORT, nullptr, FL_(serviceCodeNumber), DeviceValueUOM::NUM); + register_device_value(TAG_BOILER_DATA, &maintenanceMessage_, DeviceValueType::STRING, nullptr, FL_(maintenanceMessage), DeviceValueUOM::NONE); register_device_value(TAG_BOILER_DATA, &maintenanceType_, DeviceValueType::ENUM, @@ -156,7 +156,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_device_value( TAG_BOILER_DATA, &maintenanceTime_, DeviceValueType::USHORT, nullptr, FL_(maintenanceTime), DeviceValueUOM::HOURS, MAKE_CF_CB(set_maintenancetime)); register_device_value( - TAG_BOILER_DATA, &maintenanceDate_, DeviceValueType::TEXT, nullptr, FL_(maintenanceDate), DeviceValueUOM::NONE, MAKE_CF_CB(set_maintenancedate)); + TAG_BOILER_DATA, &maintenanceDate_, DeviceValueType::STRING, nullptr, FL_(maintenanceDate), DeviceValueUOM::NONE, MAKE_CF_CB(set_maintenancedate)); // heatpump info if (model() == EMS_DEVICE_FLAG_HEATPUMP) { register_device_value(TAG_BOILER_DATA, &upTimeControl_, DeviceValueType::TIME, FL_(div60), FL_(upTimeControl), DeviceValueUOM::MINUTES); @@ -230,7 +230,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_device_value( TAG_BOILER_DATA_WW, &wwComfort_, DeviceValueType::ENUM, FL_(enum_comfort), FL_(wwComfort), DeviceValueUOM::LIST, MAKE_CF_CB(set_warmwater_mode)); register_device_value( - TAG_BOILER_DATA_WW, &wwFlowTempOffset_, DeviceValueType::UINT, nullptr, FL_(wwFlowTempOffset), DeviceValueUOM::NONE, MAKE_CF_CB(set_wWFlowTempOffset)); + TAG_BOILER_DATA_WW, &wwFlowTempOffset_, DeviceValueType::UINT, nullptr, FL_(wwFlowTempOffset), DeviceValueUOM::NUM, MAKE_CF_CB(set_wWFlowTempOffset)); register_device_value( TAG_BOILER_DATA_WW, &wwMaxPower_, DeviceValueType::UINT, nullptr, FL_(wwMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_warmwater_maxpower)); register_device_value( @@ -261,7 +261,8 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_device_value( TAG_BOILER_DATA_WW, &wwActivated_, DeviceValueType::BOOL, nullptr, FL_(wwActivated), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_warmwater_activated)); register_device_value(TAG_BOILER_DATA_WW, &wwOneTime_, DeviceValueType::BOOL, nullptr, FL_(wwOneTime), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_warmwater_onetime)); - register_device_value(TAG_BOILER_DATA_WW, &wwDisinfect_, DeviceValueType::BOOL, nullptr, FL_(wwDisinfect), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_warmwater_disinfect)); + register_device_value( + TAG_BOILER_DATA_WW, &wwDisinfect_, DeviceValueType::BOOL, nullptr, FL_(wwDisinfect), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_warmwater_disinfect)); register_device_value(TAG_BOILER_DATA_WW, &wwCharging_, DeviceValueType::BOOL, nullptr, FL_(wwCharging), DeviceValueUOM::BOOLEAN); register_device_value(TAG_BOILER_DATA_WW, &wwRecharging_, DeviceValueType::BOOL, nullptr, FL_(wwRecharging), DeviceValueUOM::BOOLEAN); register_device_value(TAG_BOILER_DATA_WW, &wwTempOK_, DeviceValueType::BOOL, nullptr, FL_(wwTempOK), DeviceValueUOM::BOOLEAN); diff --git a/src/devices/mixer.cpp b/src/devices/mixer.cpp index f009ab606..0f6a34ad7 100644 --- a/src/devices/mixer.cpp +++ b/src/devices/mixer.cpp @@ -60,7 +60,7 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s register_device_value(tag, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE); register_device_value(tag, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(wwTemp), DeviceValueUOM::DEGREES); register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(wwPumpStatus), DeviceValueUOM::BOOLEAN); - register_device_value(tag, &status_, DeviceValueType::INT, nullptr, FL_(wwTempStatus), DeviceValueUOM::NONE); + register_device_value(tag, &status_, DeviceValueType::INT, nullptr, FL_(wwTempStatus), DeviceValueUOM::NUM); } } diff --git a/src/devices/solar.cpp b/src/devices/solar.cpp index b235d723e..235594a5d 100644 --- a/src/devices/solar.cpp +++ b/src/devices/solar.cpp @@ -64,7 +64,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s // special case for a device_id with 0x2A where it's not actual a solar module if (device_id == 0x2A) { - register_device_value(TAG_NONE, &type_, DeviceValueType::TEXT, nullptr, FL_(type), DeviceValueUOM::NONE); + register_device_value(TAG_NONE, &type_, DeviceValueType::STRING, nullptr, FL_(type), DeviceValueUOM::NONE); strlcpy(type_, "warm water circuit", sizeof(type_)); register_device_value(TAG_NONE, &wwTemp_1_, DeviceValueType::UINT, nullptr, FL_(wwTemp1), DeviceValueUOM::DEGREES); register_device_value(TAG_NONE, &wwTemp_3_, DeviceValueType::UINT, nullptr, FL_(wwTemp3), DeviceValueUOM::DEGREES); @@ -169,13 +169,13 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s MAKE_CF_CB(set_doubleMatchFlow)); // double Match Flow, 00=off // telegram 0x380 - register_device_value(TAG_NONE, &climateZone_, DeviceValueType::UINT, nullptr, FL_(climateZone), DeviceValueUOM::NONE, MAKE_CF_CB(set_climateZone)); // climate zone identifier + register_device_value(TAG_NONE, &climateZone_, DeviceValueType::UINT, nullptr, FL_(climateZone), DeviceValueUOM::NUM, MAKE_CF_CB(set_climateZone)); // climate zone identifier register_device_value(TAG_NONE, &collector1Area_, DeviceValueType::USHORT, FL_(div10), FL_(collector1Area), - DeviceValueUOM::NONE, + DeviceValueUOM::NUM, MAKE_CF_CB(set_collector1Area)); // Area of collector field 1 register_device_value(TAG_NONE, &collector1Type_, diff --git a/src/devices/switch.cpp b/src/devices/switch.cpp index 06b91a945..4a00c4233 100644 --- a/src/devices/switch.cpp +++ b/src/devices/switch.cpp @@ -37,7 +37,7 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const register_device_value(TAG_NONE, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE); register_device_value(TAG_NONE, &activated_, DeviceValueType::BOOL, nullptr, FL_(activated), DeviceValueUOM::BOOLEAN); register_device_value(TAG_NONE, &flowTempHc_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempHc), DeviceValueUOM::DEGREES); - register_device_value(TAG_NONE, &status_, DeviceValueType::INT, nullptr, FL_(status), DeviceValueUOM::NONE); + register_device_value(TAG_NONE, &status_, DeviceValueType::INT, nullptr, FL_(status), DeviceValueUOM::NUM); id_ = product_id; } diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index aa408023c..b12635c86 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -2389,13 +2389,13 @@ bool Thermostat::set_roominfluence(const char * value, const int8_t id) { void Thermostat::register_device_values() { // Common for all thermostats register_device_value(TAG_THERMOSTAT_DATA, &id_, DeviceValueType::UINT, nullptr, FL_(ID), DeviceValueUOM::NONE); - register_device_value(TAG_THERMOSTAT_DATA, &errorCode_, DeviceValueType::TEXT, nullptr, FL_(errorCode), DeviceValueUOM::NONE); - register_device_value(TAG_THERMOSTAT_DATA, &lastCode_, DeviceValueType::TEXT, nullptr, FL_(lastCode), DeviceValueUOM::NONE); + register_device_value(TAG_THERMOSTAT_DATA, &errorCode_, DeviceValueType::STRING, nullptr, FL_(errorCode), DeviceValueUOM::NONE); + register_device_value(TAG_THERMOSTAT_DATA, &lastCode_, DeviceValueType::STRING, nullptr, FL_(lastCode), DeviceValueUOM::NONE); switch (this->model()) { case EMS_DEVICE_FLAG_RC100: case EMS_DEVICE_FLAG_RC300: - register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); + register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); register_device_value(TAG_THERMOSTAT_DATA, &floordrystatus_, DeviceValueType::ENUM, FL_(enum_floordrystatus), FL_(floordrystatus), DeviceValueUOM::LIST); register_device_value(TAG_THERMOSTAT_DATA, &dampedoutdoortemp2_, DeviceValueType::SHORT, FL_(div10), FL_(dampedoutdoortemp), DeviceValueUOM::DEGREES); register_device_value(TAG_THERMOSTAT_DATA, &floordrytemp_, DeviceValueType::UINT, nullptr, FL_(floordrytemp), DeviceValueUOM::DEGREES); @@ -2432,10 +2432,10 @@ void Thermostat::register_device_values() { break; case EMS_DEVICE_FLAG_RC20_N: case EMS_DEVICE_FLAG_RC20: - register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime + register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime break; case EMS_DEVICE_FLAG_RC30_N: - register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime + register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime register_device_value(TAG_THERMOSTAT_DATA, &ibaMainDisplay_, DeviceValueType::ENUM, FL_(enum_ibaMainDisplay), FL_(ibaMainDisplay), DeviceValueUOM::LIST); register_device_value(TAG_THERMOSTAT_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), FL_(ibaLanguage), DeviceValueUOM::LIST); register_device_value(TAG_THERMOSTAT_DATA, @@ -2443,7 +2443,7 @@ void Thermostat::register_device_values() { DeviceValueType::UINT, nullptr, FL_(ibaClockOffset), - DeviceValueUOM::NONE); // offset (in sec) to clock, 0xff=-1s, 0x02=2s + DeviceValueUOM::NUM); // offset (in sec) to clock, 0xff=-1s, 0x02=2s register_device_value(TAG_THERMOSTAT_DATA, &ibaCalIntTemperature_, DeviceValueType::INT, @@ -2471,7 +2471,7 @@ void Thermostat::register_device_values() { TAG_THERMOSTAT_DATA, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), FL_(wwCircMode), DeviceValueUOM::LIST, MAKE_CF_CB(set_wwcircmode)); break; case EMS_DEVICE_FLAG_RC35: - register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); + register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); register_device_value(TAG_THERMOSTAT_DATA, &ibaCalIntTemperature_, DeviceValueType::INT, @@ -2518,7 +2518,7 @@ void Thermostat::register_device_values() { DeviceValueType::UINT, nullptr, FL_(wwDisinfectHour), - DeviceValueUOM::NONE, + DeviceValueUOM::NUM, MAKE_CF_CB(set_wwDisinfectHour), 0, 23); @@ -2527,14 +2527,14 @@ void Thermostat::register_device_values() { TAG_THERMOSTAT_DATA, &wwOneTimeKey_, DeviceValueType::BOOL, nullptr, FL_(wwOneTimeKey), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_wwOneTimeKey)); break; case EMS_DEVICE_FLAG_JUNKERS: - register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); + register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE, MAKE_CF_CB(set_datetime)); break; case EMS_DEVICE_FLAG_EASY: // Easy TC100 have no date/time, see issue #100, not sure about CT200, so leave it. - register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime + register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime break; default: - register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::TEXT, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime + register_device_value(TAG_THERMOSTAT_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime break; } } @@ -2572,6 +2572,8 @@ void Thermostat::register_device_values_hc(std::shared_ptrsetpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, FL_(temp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_temp), 5, 29); } @@ -2613,7 +2615,7 @@ void Thermostat::register_device_values_hc(std::shared_ptroffsettemp, DeviceValueType::INT, nullptr, FL_(offsettemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_offsettemp)); register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp)); register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp)); - register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::NONE, MAKE_CF_CB(set_roominfluence)); + register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::NUM, MAKE_CF_CB(set_roominfluence)); register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp)); register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::LIST); @@ -2622,10 +2624,9 @@ void Thermostat::register_device_values_hc(std::shared_ptrsummermode, DeviceValueType::BOOL, nullptr, FL_(summermode), DeviceValueUOM::BOOLEAN); register_device_value( tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode), FL_(controlmode), DeviceValueUOM::LIST, MAKE_CF_CB(set_controlmode)); - register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); + register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NUM, MAKE_CF_CB(set_program)); register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp)); - register_device_value( - tag, &hc->fastHeatup, DeviceValueType::UINT, nullptr, FL_(fastheatup), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_fastheatup)); + register_device_value(tag, &hc->fastHeatup, DeviceValueType::UINT, nullptr, FL_(fastheatup), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_fastheatup)); break; case EMS_DEVICE_FLAG_CRF: register_device_value(tag, &hc->mode, DeviceValueType::ENUM, FL_(enum_mode5), FL_(mode), DeviceValueUOM::LIST); @@ -2640,7 +2641,7 @@ void Thermostat::register_device_values_hc(std::shared_ptrmodetype, DeviceValueType::ENUM, FL_(enum_modetype2), FL_(modetype), DeviceValueUOM::LIST); register_device_value(tag, &hc->daytemp, DeviceValueType::UINT, FL_(div2), FL_(daytemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_daytemp)); register_device_value(tag, &hc->nighttemp, DeviceValueType::UINT, FL_(div2), FL_(nighttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nighttemp)); - register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); + register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NUM, MAKE_CF_CB(set_program)); // RC25 additions, guess, not validated by users, see:https://github.com/emsesp/EMS-ESP32/issues/106 register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp)); register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp)); @@ -2664,7 +2665,7 @@ void Thermostat::register_device_values_hc(std::shared_ptrsummermode, DeviceValueType::BOOL, nullptr, FL_(summermode), DeviceValueUOM::BOOLEAN); register_device_value(tag, &hc->holidaymode, DeviceValueType::BOOL, nullptr, FL_(holidaymode), DeviceValueUOM::BOOLEAN, MAKE_CF_CB(set_holiday)); register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp)); - register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::NONE, MAKE_CF_CB(set_roominfluence)); + register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::NUM, MAKE_CF_CB(set_roominfluence)); register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp)); register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp)); register_device_value(tag, &hc->flowtempoffset, DeviceValueType::UINT, nullptr, FL_(flowtempoffset), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flowtempoffset)); @@ -2674,7 +2675,7 @@ void Thermostat::register_device_values_hc(std::shared_ptrcontrolmode, DeviceValueType::ENUM, FL_(enum_controlmode2), FL_(controlmode), DeviceValueUOM::LIST, MAKE_CF_CB(set_controlmode)); register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control), FL_(control), DeviceValueUOM::LIST, MAKE_CF_CB(set_control)); - register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); + register_device_value(tag, &hc->program, DeviceValueType::UINT, nullptr, FL_(program), DeviceValueUOM::NUM, MAKE_CF_CB(set_program)); register_device_value(tag, &hc->pause, DeviceValueType::UINT, nullptr, FL_(pause), DeviceValueUOM::HOURS, MAKE_CF_CB(set_pause)); register_device_value(tag, &hc->party, DeviceValueType::UINT, nullptr, FL_(party), DeviceValueUOM::HOURS, MAKE_CF_CB(set_party)); register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp)); diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 44f79d914..56ae8923e 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -370,7 +370,7 @@ bool EMSdevice::is_fetch(uint16_t telegram_id) { // list of registered device entries, adding the HA entity if it exists void EMSdevice::list_device_entries(JsonObject & json) { for (const auto & dv : devicevalues_) { - if (dv.full_name && dv.type != DeviceValueType::CMD) { + if (dv_is_visible(dv) && dv.type != DeviceValueType::CMD) { // if we have a tag prefix it char key[50]; if (!EMSdevice::tag_to_string(dv.tag).empty()) { @@ -484,7 +484,7 @@ void EMSdevice::register_device_value(uint8_t tag, int32_t min, uint32_t max) { // init the value depending on it's type - if (type == DeviceValueType::TEXT) { + if (type == DeviceValueType::STRING) { *(char *)(value_p) = {'\0'}; } else if (type == DeviceValueType::INT) { *(int8_t *)(value_p) = EMS_VALUE_INT_NOTSET; @@ -574,7 +574,7 @@ const std::string EMSdevice::get_value_uom(const char * key) { // find the key (p) in the name for (const auto & dv : devicevalues_) { - if (dv.full_name != nullptr) { + if (dv_is_visible(dv)) { if (uuid::read_flash_string(dv.full_name) == p) { // ignore TIME since "minutes" is already added to the string value if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) { @@ -596,7 +596,7 @@ void EMSdevice::generate_values_json_web(JsonObject & json) { for (const auto & dv : devicevalues_) { // ignore if full_name empty and also commands - if ((dv.full_name != nullptr) && (dv.type != DeviceValueType::CMD)) { + if (dv_is_visible(dv) && dv.type != DeviceValueType::CMD) { JsonObject obj; // create the object, if needed // handle Booleans (true, false) @@ -606,7 +606,7 @@ void EMSdevice::generate_values_json_web(JsonObject & json) { } // handle TEXT strings - else if ((dv.type == DeviceValueType::TEXT) && (Helpers::hasValue((char *)(dv.value_p)))) { + else if ((dv.type == DeviceValueType::STRING) && (Helpers::hasValue((char *)(dv.value_p)))) { obj = data.createNestedObject(); obj["v"] = (char *)(dv.value_p); } @@ -633,7 +633,7 @@ void EMSdevice::generate_values_json_web(JsonObject & json) { uint8_t divider = 0; uint8_t factor = 1; if (dv.options_size == 1) { - const char * s = uuid::read_flash_string(dv.options[0]).c_str(); + const char * s = uuid::read_flash_string(dv.options[0]).c_str(); if (s[0] == '*') { factor = Helpers::atoint(&s[1]); } else { @@ -719,11 +719,13 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t // search device value with this tag for (auto & dv : devicevalues_) { - if (strcmp(cmd, Helpers::toLower(uuid::read_flash_string(dv.short_name)).c_str()) == 0 && (tag <= 0 || tag == dv.tag)) { + // ignore any device values which have a uom of NONE as we use this to hide them + if ((dv.uom != DeviceValueUOM::NONE) + && (strcmp(cmd, Helpers::toLower(uuid::read_flash_string(dv.short_name)).c_str()) == 0 && (tag <= 0 || tag == dv.tag))) { uint8_t divider = 0; uint8_t factor = 1; if (dv.options_size == 1) { - const char * s = uuid::read_flash_string(dv.options[0]).c_str(); + const char * s = uuid::read_flash_string(dv.options[0]).c_str(); if (s[0] == '*') { factor = Helpers::atoint(&s[1]); } else { @@ -737,7 +739,7 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t json["name"] = dv.short_name; // prefix tag if it's included - if (dv.full_name != nullptr) { + if (dv_is_visible(dv)) { if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) { json["fullname"] = dv.full_name; } else { @@ -849,7 +851,7 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t json[max] = divider ? EMS_VALUE_ULONG_NOTSET / divider : EMS_VALUE_ULONG_NOTSET; break; - case DeviceValueType::TEXT: + case DeviceValueType::STRING: if (Helpers::hasValue((char *)(dv.value_p))) { json[value] = (char *)(dv.value_p); } @@ -896,24 +898,29 @@ bool EMSdevice::get_value_info(JsonObject & root, const char * cmd, const int8_t // For each value in the device create the json object pair and add it to given json // return false if empty -// this is used to create both the MQTT payloads and Console messages (console = true) -bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter, const bool nested, const bool console) { +// this is used to create both the MQTT payloads, Console messages and Web API calls +bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter, const bool nested, const uint8_t output_target) { bool has_values = false; // to see if we've added a value. it's faster than doing a json.size() at the end uint8_t old_tag = 255; // NAN JsonObject json = root; for (auto & dv : devicevalues_) { + // conditions + bool condition; + condition = (tag_filter == DeviceValueTAG::TAG_NONE) || (tag_filter == dv.tag); // tag must be either empty or match a tag passed to this function + + if (output_target != OUTPUT_TARGET::MQTT) { + condition &= dv_is_visible(dv); // value must be visible if outputting to API (web or console). This is for ID, hamode, hatemp etc + } + bool has_value = false; - // only show if tag is either empty (TAG_NONE) or matches a value - // and don't show if full_name is empty unless we're outputing for mqtt payloads - // for nested we use all values, dont show command only (have_cmd and no fullname) - if (((nested) || tag_filter == DeviceValueTAG::TAG_NONE || (tag_filter == dv.tag)) && (dv.full_name != nullptr || !console) - && !(dv.full_name == nullptr && dv.has_cmd)) { + + if (condition) { // we have a tag if it matches the filter given, and that the tag name is not empty/"" bool have_tag = ((dv.tag != tag_filter) && !tag_to_string(dv.tag).empty()); char name[80]; - if (console) { + if (output_target == OUTPUT_TARGET::API_VERBOSE) { // prefix the tag in brackets, unless it's Boiler because we're naughty and use tag for the MQTT topic if (have_tag) { snprintf(name, 80, "%s %s", tag_to_string(dv.tag).c_str(), uuid::read_flash_string(dv.full_name).c_str()); @@ -935,9 +942,8 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter // handle Booleans (true, false) if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) { // see how to render the value depending on the setting - // when in console mode we always use on and off uint8_t bool_format = EMSESP::bool_format(); - if ((bool_format == BOOL_FORMAT_ONOFF) || console) { + if (bool_format == BOOL_FORMAT_ONOFF) { json[name] = *(uint8_t *)(dv.value_p) ? F_(on) : F_(off); } else if (bool_format == BOOL_FORMAT_ONOFF_CAP) { json[name] = *(uint8_t *)(dv.value_p) ? F_(ON) : F_(OFF); @@ -950,7 +956,7 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter } // handle TEXT strings - else if ((dv.type == DeviceValueType::TEXT) && (Helpers::hasValue((char *)(dv.value_p)))) { + else if ((dv.type == DeviceValueType::STRING) && (Helpers::hasValue((char *)(dv.value_p)))) { json[name] = (char *)(dv.value_p); has_value = true; } @@ -976,7 +982,7 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter uint8_t divider = 0; uint8_t factor = 1; if (dv.options_size == 1) { - const char * s = uuid::read_flash_string(dv.options[0]).c_str(); + const char * s = uuid::read_flash_string(dv.options[0]).c_str(); if (s[0] == '*') { factor = Helpers::atoint(&s[1]); } else { @@ -1023,7 +1029,7 @@ bool EMSdevice::generate_values_json(JsonObject & root, const uint8_t tag_filter } else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) { uint32_t time_value = *(uint32_t *)(dv.value_p); time_value = (divider) ? time_value / divider : time_value * factor; // sometimes we need to divide by 60 - if (console) { + if (output_target == EMSdevice::OUTPUT_TARGET::API_VERBOSE) { char time_s[40]; snprintf(time_s, sizeof(time_s), "%d days %d hours %d minutes", (time_value / 1440), ((time_value % 1440) / 60), (time_value % 60)); json[name] = time_s; diff --git a/src/emsdevice.h b/src/emsdevice.h index 4bcfe914b..2be6b7d18 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -39,7 +39,7 @@ enum DeviceValueType : uint8_t { ULONG, TIME, // same as ULONG (32 bits) ENUM, - TEXT, + STRING, CMD // special for commands only }; @@ -148,10 +148,6 @@ class EMSdevice { , brand_(brand) { } - inline uint8_t device_id() const { - return device_id_; - } - const std::string device_type_name() const; static const std::string device_type_2_device_name(const uint8_t device_type); static uint8_t device_name_2_device_type(const char * topic); @@ -160,6 +156,10 @@ class EMSdevice { static const std::string tag_to_string(uint8_t tag); static const std::string tag_to_mqtt(uint8_t tag); + inline uint8_t device_id() const { + return device_id_; + } + inline uint8_t product_id() const { return product_id_; } @@ -186,9 +186,8 @@ class EMSdevice { return flags_; } - // see enum DeviceType below inline uint8_t device_type() const { - return device_type_; + return device_type_; // see enum DeviceType below } inline void version(std::string & version) { @@ -249,8 +248,11 @@ class EMSdevice { const std::string get_value_uom(const char * key); bool get_value_info(JsonObject & root, const char * cmd, const int8_t id); - bool generate_values_json(JsonObject & json, const uint8_t tag_filter, const bool nested, const bool console = false); - void generate_values_json_web(JsonObject & json); + + enum OUTPUT_TARGET : uint8_t { API_VERBOSE, API, MQTT }; + bool generate_values_json(JsonObject & json, const uint8_t tag_filter, const bool nested, const uint8_t output_target); + + void generate_values_json_web(JsonObject & json); void register_device_value(uint8_t tag, void * value_p, @@ -417,6 +419,8 @@ class EMSdevice { } }; + + // DeviceValue holds all the attributes for a device value (also a device parameter) struct DeviceValue { uint8_t device_type; // EMSdevice::DeviceType uint8_t tag; // DeviceValueTAG::* @@ -470,6 +474,10 @@ class EMSdevice { void init_devicevalues(uint8_t size) { devicevalues_.reserve(size); } + + inline bool dv_is_visible(DeviceValue dv) { + return (dv.full_name); + } }; } // namespace emsesp diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 5f3171ba1..759c3cdfd 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -299,7 +299,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) { } // show EMS device values to the shell console -// generate_values_json is called in verbose mode (set to true) +// generate_values_json is called in verbose mode void EMSESP::show_device_values(uuid::console::Shell & shell) { if (emsdevices.empty()) { shell.printfln(F("No EMS devices detected. Try using 'scan devices' from the ems menu.")); @@ -316,7 +316,7 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) { DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); // use max size JsonObject json = doc.to(); - emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true, true); // console mode and nested + emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::API_VERBOSE); // verbose mode and nested // print line uint8_t id = 0; @@ -451,7 +451,7 @@ void EMSESP::publish_device_values(uint8_t device_type) { JsonObject json = doc.to(); bool need_publish = false; - uint8_t nested = Mqtt::nested_format(); + bool nested = (Mqtt::nested_format() == 1); // 1 is nested, 2 is single // group by device type for (const auto & emsdevice : emsdevices) { @@ -461,13 +461,13 @@ void EMSESP::publish_device_values(uint8_t device_type) { emsdevice->publish_mqtt_ha_sensor(); // create the configs for each value as a sensor } - // if its a boiler, generate json for each group and publish it directly + // if its a boiler, generate json for each group and publish it directly. not nested if (device_type == DeviceType::BOILER) { - if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA, false)) { + if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA, false, EMSdevice::OUTPUT_TARGET::MQTT)) { Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA), json); } doc.clear(); - if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA_WW, false)) { + if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA_WW, false, EMSdevice::OUTPUT_TARGET::MQTT)) { Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA_WW), json); } need_publish = false; @@ -478,15 +478,15 @@ void EMSESP::publish_device_values(uint8_t device_type) { // only publish the single master thermostat if (emsdevice->device_id() == EMSESP::actual_master_thermostat()) { if (nested) { - need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true); // nested + need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested } else { - if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_THERMOSTAT_DATA, false)) { // not nested + if (emsdevice->generate_values_json(json, DeviceValueTAG::TAG_THERMOSTAT_DATA, false, EMSdevice::OUTPUT_TARGET::MQTT)) { // not nested Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json); } doc.clear(); for (uint8_t hc_tag = TAG_HC1; hc_tag <= DeviceValueTAG::TAG_HC4; hc_tag++) { - if (emsdevice->generate_values_json(json, hc_tag, false)) { // not nested + if (emsdevice->generate_values_json(json, hc_tag, false, EMSdevice::OUTPUT_TARGET::MQTT)) { // not nested Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json); } doc.clear(); @@ -499,10 +499,10 @@ void EMSESP::publish_device_values(uint8_t device_type) { // Mixer else if (device_type == DeviceType::MIXER) { if (nested) { - need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true); // nested + need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested } else { for (uint8_t hc_tag = TAG_HC1; hc_tag <= DeviceValueTAG::TAG_WWC4; hc_tag++) { - if (emsdevice->generate_values_json(json, hc_tag, false)) { // not nested + if (emsdevice->generate_values_json(json, hc_tag, false, EMSdevice::OUTPUT_TARGET::MQTT)) { // not nested Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json); } doc.clear(); @@ -512,7 +512,7 @@ void EMSESP::publish_device_values(uint8_t device_type) { } else { // for all other devices add the values to the json - need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true); // nested + need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested } } } @@ -992,12 +992,14 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std:: Command::add_json( device_type, F_(info), - [device_type](const char * value, const int8_t id, JsonObject & json) { return command_info(device_type, json, id, true); }, + [device_type](const char * value, const int8_t id, JsonObject & json) { + return command_info(device_type, json, id, EMSdevice::OUTPUT_TARGET::API_VERBOSE); + }, F_(info_cmd)); Command::add_json( device_type, F("info_short"), - [device_type](const char * value, const int8_t id, JsonObject & json) { return command_info(device_type, json, id, false); }, + [device_type](const char * value, const int8_t id, JsonObject & json) { return command_info(device_type, json, id, EMSdevice::OUTPUT_TARGET::API); }, nullptr, CommandFlag::HIDDEN); // this command is hidden Command::add_json( @@ -1036,7 +1038,7 @@ bool EMSESP::command_commands(uint8_t device_type, JsonObject & json, const int8 // export all values to info command // value is ignored here // info command always shows in verbose mode, so full names are displayed -bool EMSESP::command_info(uint8_t device_type, JsonObject & json, const int8_t id, bool verbose) { +bool EMSESP::command_info(uint8_t device_type, JsonObject & json, const int8_t id, const uint8_t output_target) { bool has_value = false; uint8_t tag; if (id >= 1 && id <= 4) { @@ -1049,10 +1051,13 @@ bool EMSESP::command_info(uint8_t device_type, JsonObject & json, const int8_t i return false; } + // if id=-1 it means we have no endpoint so default to API + uint8_t target = (id == -1) ? EMSdevice::OUTPUT_TARGET::API_VERBOSE : EMSdevice::OUTPUT_TARGET::API; + for (const auto & emsdevice : emsdevices) { if (emsdevice && (emsdevice->device_type() == device_type) && ((device_type != DeviceType::THERMOSTAT) || (emsdevice->device_id() == EMSESP::actual_master_thermostat()))) { - has_value |= emsdevice->generate_values_json(json, tag, (id < 1), verbose && (id == -1)); // nested for id -1,0 & console for id -1 + has_value |= emsdevice->generate_values_json(json, tag, (id < 1), target); // nested for id -1 and 0 } } diff --git a/src/emsesp.h b/src/emsesp.h index 37e1b6150..b64a6eb01 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -249,7 +249,7 @@ class EMSESP { static void process_version(std::shared_ptr telegram); static void publish_response(std::shared_ptr telegram); static void publish_all_loop(); - static bool command_info(uint8_t device_type, JsonObject & json, const int8_t id, bool verbose = true); + static bool command_info(uint8_t device_type, JsonObject & json, const int8_t id, const uint8_t output_target); static bool command_commands(uint8_t device_type, JsonObject & json, const int8_t id); static bool command_entities(uint8_t device_type, JsonObject & json, const int8_t id); diff --git a/src/locale_EN.h b/src/locale_EN.h index 23e365e99..cf9088f69 100644 --- a/src/locale_EN.h +++ b/src/locale_EN.h @@ -301,9 +301,9 @@ MAKE_PSTR_WORD(design) MAKE_PSTR_WORD(tempauto) MAKE_PSTR_WORD(minflow) MAKE_PSTR_WORD(maxflow) - MAKE_PSTR_WORD(rc3x) MAKE_PSTR_WORD(rc20) + MAKE_PSTR(internal_temperature, "internal temperature") MAKE_PSTR(internal_setpoint, "internal setpoint") MAKE_PSTR(external_temperature, "external temperature") @@ -365,12 +365,11 @@ MAKE_PSTR_LIST(enum_collectortype, F("flat"), F("vacuum")) // MQTT topics and full text for values and commands MAKE_PSTR(homeassistant, "homeassistant/") -// id used to store the device ID, goes into MQTT payload -// empty full name to prevent being shown in web or console +// id used to store the device ID. empty full name so only gets displayed in the MQTT payload MAKE_PSTR_LIST(ID, F_(id)) // Boiler -// extra commands, no output +// extra commands, with no json output MAKE_PSTR_LIST(wwtapactivated, F("wwtapactivated"), F("turn on/off DHW by going into maintenance mode")) MAKE_PSTR_LIST(reset, F("reset"), F("reset messages")) @@ -523,14 +522,13 @@ MAKE_PSTR_LIST(wwMaxTemp, F("wwmaxtemp"), F("maximum temperature")) MAKE_PSTR_LIST(wwOneTimeKey, F("wwonetimekey"), F("one time key function")) // thermostat -// extra commands -MAKE_PSTR_LIST(switchtime, F("switchtime"), F("single program switchtime")) -// extra commands, with no long name so they don't show up in WebUI +// commands, with no long name so they only appear in the MQTT payloads MAKE_PSTR_LIST(temp, F("temp")) MAKE_PSTR_LIST(hatemp, F("hatemp")) MAKE_PSTR_LIST(hamode, F("hamode")) // mqtt values / commands +MAKE_PSTR_LIST(switchtime, F("switchtime"), F("single program switchtime")) MAKE_PSTR_LIST(dateTime, F("datetime"), F("date/time")) MAKE_PSTR_LIST(errorCode, F("errorcode"), F("error code")) MAKE_PSTR_LIST(ibaMainDisplay, F("display"), F("display")) diff --git a/src/mqtt.h b/src/mqtt.h index dc9bfc246..b964be7c8 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -174,11 +174,11 @@ class Mqtt { } static uint8_t nested_format() { - return nested_format_; + return nested_format_; // nested_format is 1 if nested, otherwise 2 for single topics } static void nested_format(uint8_t nested_format) { - nested_format_ = nested_format; + nested_format_ = nested_format; // nested_format is 1 if nested, otherwise 2 for single topics } static bool ha_enabled() { diff --git a/src/system.cpp b/src/system.cpp index a220827b7..fed00e290 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -981,7 +981,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json obj["type"] = emsdevice->device_type_name(); obj["name"] = emsdevice->to_string(); char result[200]; - obj["handlers"] = emsdevice->show_telegram_handlers(result); + (void)emsdevice->show_telegram_handlers(result); + if (result[0] != '\0') { + obj["handlers"] = result; // don't show hanlders if there aren't any + } } } } diff --git a/src/test/test.cpp b/src/test/test.cpp index 66b367ce5..876efec0e 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -482,8 +482,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { // first with nested Mqtt::nested_format(1); shell.invoke_command("call system publish"); + shell.invoke_command("show mqtt"); - // then without nested + // then without nested - single mode Mqtt::nested_format(2); shell.invoke_command("call system publish"); shell.invoke_command("show mqtt"); @@ -829,6 +830,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { if (command == "mqtt") { shell.printfln(F("Testing MQTT...")); + Mqtt::ha_enabled(false); + Mqtt::enabled(true); + // add a boiler add_device(0x08, 123); // Nefit Trendline @@ -841,6 +845,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { uart_telegram("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03"); // without CRC uart_telegram_withCRC("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 13"); // with CRC + shell.invoke_command("call system publish"); + shell.invoke_command("show mqtt"); shell.loop_all(); char boiler_topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; @@ -853,11 +859,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { strcpy(thermostat_topic, "ems-esp/thermostat"); strcpy(system_topic, "ems-esp/system"); - EMSESP::mqtt_.incoming(boiler_topic, ""); // test if ignore empty payloads // invalid format + EMSESP::mqtt_.incoming(boiler_topic, ""); // test if ignore empty payloads - EMSESP::mqtt_.incoming(boiler_topic, "12345"); // invalid format - EMSESP::mqtt_.incoming("bad_topic", "12345"); // no matching topic - EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"garbage\",\"data\":22.52}"); // should report error + EMSESP::mqtt_.incoming(boiler_topic, "12345"); // error: invalid format + EMSESP::mqtt_.incoming("bad_topic", "12345"); // error: no matching topic + EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"garbage\",\"data\":22.52}"); // error: should report error EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"comfort\",\"data\":\"eco\"}"); EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"wwactivated\",\"data\":\"1\"}"); // with quotes @@ -948,12 +954,23 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { if (command == "api") { #if defined(EMSESP_STANDALONE) shell.printfln(F("Testing RESTful API...")); - Mqtt::ha_enabled(false); + Mqtt::ha_enabled(true); Mqtt::enabled(false); run_test("general"); AsyncWebServerRequest request; // GET + request.url("/api/thermostat"); + EMSESP::webAPIService.webAPIService_get(&request); + request.url("/api/thermostat/info"); + EMSESP::webAPIService.webAPIService_get(&request); + + // these next 2 should fail + request.url("/api/boiler/id"); + EMSESP::webAPIService.webAPIService_get(&request); + request.url("/api/thermostat/hamode"); + EMSESP::webAPIService.webAPIService_get(&request); + request.method(HTTP_GET); request.url("/api/thermostat/seltemp"); EMSESP::webAPIService.webAPIService_get(&request); @@ -994,7 +1011,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { EMSESP::webAPIService.webAPIService_post(&request, json); // 3 - char data3[] = "{\"device\":\"thermostat\", \"name\":\"temp\",\"value\":11}"; + char data3[] = "{\"device\":\"thermostat\", \"name\":\"temp\",\"value\":13}"; deserializeJson(doc, data3); json = doc.as(); request.url("/api"); diff --git a/src/test/test.h b/src/test/test.h index 5e4e29ffc..120e3060f 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -33,8 +33,8 @@ namespace emsesp { // #define EMSESP_DEBUG_DEFAULT "general" // #define EMSESP_DEBUG_DEFAULT "boiler" // #define EMSESP_DEBUG_DEFAULT "mqtt2" -// #define EMSESP_DEBUG_DEFAULT "mqtt_nested" -#define EMSESP_DEBUG_DEFAULT "ha" +#define EMSESP_DEBUG_DEFAULT "mqtt_nested" +// #define EMSESP_DEBUG_DEFAULT "ha" // #define EMSESP_DEBUG_DEFAULT "board_profile" // #define EMSESP_DEBUG_DEFAULT "shower_alert" // #define EMSESP_DEBUG_DEFAULT "310" diff --git a/src/version.h b/src/version.h index ccad8f1db..9ad2c5671 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.2.2b6" +#define EMSESP_APP_VERSION "v3.2.2b7"