diff --git a/.gitignore b/.gitignore index 387e569b9..c6cb358e8 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,6 @@ venv/ # cspell words-found-verbose.txt + +# sonarlint +compile_commands.json diff --git a/.sonarlint/connectedMode.json b/.sonarlint/connectedMode.json new file mode 100644 index 000000000..a3f4f6d2f --- /dev/null +++ b/.sonarlint/connectedMode.json @@ -0,0 +1,4 @@ +{ + "sonarCloudOrganization": "emsesp", + "projectKey": "emsesp_EMS-ESP32" +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 57c1e2f0c..f8137d519 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -96,5 +96,6 @@ "sonarlint.connectedMode.project": { "connectionId": "emsesp", "projectKey": "emsesp_EMS-ESP32" - } + }, + "sonarlint.pathToCompileCommands": "${workspaceFolder}/compile_commands.json" } \ No newline at end of file diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index e2184d1e8..02dc06d9b 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -40,8 +40,9 @@ For more details go to [www.emsesp.org](https://www.emsesp.org/). - autodetect and download firmware upgrades via the WebUI - command 'show log' that lists out the current weblog buffer, showing last messages. - default web log buffer to 25 lines for ESP32s with no PSRAM -- Try and determine correct board profile if none is set +- try and determine correct board profile if none is set during boot - auto Scroll in WebLog UI - reduced delay so incoming logs are faster +- uploading custom support info for Guest users [#2054] ## Fixed @@ -72,6 +73,7 @@ For more details go to [www.emsesp.org](https://www.emsesp.org/). - WebLog UI matches color schema of the terminal console correctly - Updated Web libraries, ArduinoJson - Help page doesn't show detailed tech info if the user is not 'admin' role [#2054](https://github.com/emsesp/EMS-ESP32/issues/2054) +- removed system command `allvalues` and moved to an action called `export` - Show ems-esp internal devices in device list of system/info - Scheduler and mqtt run async on systems with psram - Show IPv6 address type (local/global/ula) in log diff --git a/interface/package.json b/interface/package.json index 508e1ac10..f10624f8a 100644 --- a/interface/package.json +++ b/interface/package.json @@ -27,7 +27,7 @@ "@mui/icons-material": "^6.1.1", "@mui/material": "^6.1.1", "@table-library/react-table-library": "4.1.7", - "alova": "3.0.16", + "alova": "3.0.17", "async-validator": "^4.2.5", "jwt-decode": "^4.0.0", "mime-types": "^2.1.35", @@ -47,8 +47,8 @@ "@preact/preset-vite": "^2.9.1", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/formidable": "^3", - "@types/node": "^22.7.3", - "@types/react": "^18.3.9", + "@types/node": "^22.7.4", + "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", "@types/react-router-dom": "^5.3.3", "concurrently": "^9.0.1", @@ -57,8 +57,8 @@ "formidable": "^3.5.1", "prettier": "^3.3.3", "rollup-plugin-visualizer": "^5.12.0", - "terser": "^5.34.0", - "typescript-eslint": "8.7.0", + "terser": "^5.34.1", + "typescript-eslint": "8.8.0", "vite": "^5.4.8", "vite-plugin-imagemin": "^0.6.1", "vite-tsconfig-paths": "^5.0.1" diff --git a/interface/src/api/app.ts b/interface/src/api/app.ts index 1863a6893..a856d2096 100644 --- a/interface/src/api/app.ts +++ b/interface/src/api/app.ts @@ -2,6 +2,7 @@ import { alovaInstance } from 'api/endpoints'; import type { APIcall, + Action, Activity, CoreData, DeviceData, @@ -52,9 +53,9 @@ export const readActivity = () => alovaInstance.Get('/rest/activity'); // API export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall); -// DownloadUpload -export const exportData = (type: string) => - alovaInstance.Get('/rest/exportData', { params: { type } }); +// Generic action +export const callAction = (action: Action) => + alovaInstance.Post('/rest/action', action); // SettingsCustomization export const readDeviceEntities = (id: number) => @@ -118,7 +119,7 @@ export const writeModules = (data: { license: string; }) => alovaInstance.Post('/rest/modules', data); -// SettingsEntities +// CustomEntities export const readCustomEntities = () => alovaInstance.Get('/rest/customEntities', { transform(data) { diff --git a/interface/src/api/system.ts b/interface/src/api/system.ts index b1d4298e0..02b8b0dff 100644 --- a/interface/src/api/system.ts +++ b/interface/src/api/system.ts @@ -13,7 +13,7 @@ export const updateLogSettings = (data: LogSettings) => alovaInstance.Post('/rest/logSettings', data); export const fetchLogES = () => alovaInstance.Get('/es/log'); -// Get versions from github +// Get versions from GitHub export const getStableVersion = () => alovaInstanceGH.Get('latest', { transform(response: { data: { name: string } }) { @@ -34,9 +34,3 @@ export const uploadFile = (file: File) => { timeout: 60000 // override timeout for uploading firmware - 1 minute }); }; - -export const uploadURL = (data: { url: string }) => - alovaInstance.Post('/rest/uploadURL', data); - -export const checkUpgrade = (data: { version: string }) => - alovaInstance.Post('/rest/checkUpgrade', data); diff --git a/interface/src/app/main/Help.tsx b/interface/src/app/main/Help.tsx index 32b410f04..da45a0f1b 100644 --- a/interface/src/app/main/Help.tsx +++ b/interface/src/app/main/Help.tsx @@ -1,4 +1,4 @@ -import { useContext } from 'react'; +import { useContext, useState } from 'react'; import { toast } from 'react-toastify'; import CommentIcon from '@mui/icons-material/CommentTwoTone'; @@ -16,18 +16,17 @@ import { ListItemAvatar, ListItemButton, ListItemText, - Paper, Stack, - Typography, - styled + Typography } from '@mui/material'; import { useRequest } from 'alova/client'; import { SectionContent, useLayoutTitle } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; +import { saveFile } from 'utils/file'; -import { API } from '../../api/app'; +import { API, callAction } from '../../api/app'; import type { APIcall } from './types'; const Help = () => { @@ -36,131 +35,150 @@ const Help = () => { const { me } = useContext(AuthenticatedContext); - const { send: sendAPI } = useRequest((data: APIcall) => API(data), { - immediate: false - }).onSuccess((event) => { - const anchor = document.createElement('a'); - anchor.href = URL.createObjectURL( - new Blob([JSON.stringify(event.data, null, 2)], { - type: 'text/plain' - }) - ); + const [customSupportIMG, setCustomSupportIMG] = useState(null); + const [customSupportHTML, setCustomSupportHTML] = useState(null); - anchor.download = - 'emsesp_' + event.args[0].device + '_' + event.args[0].entity + '.txt'; - anchor.click(); - URL.revokeObjectURL(anchor.href); - toast.info(LL.DOWNLOAD_SUCCESSFUL()); + useRequest(() => callAction({ action: 'customSupport' })).onSuccess((event) => { + if (event && event.data && Object.keys(event.data).length !== 0) { + const data = event.data.Support; + if (data.img_url) { + setCustomSupportIMG(data.img_url); + } + if (data.html) { + setCustomSupportHTML(data.html.join('
')); + } + } }); - const callAPI = async (device: string, cmd: string) => { - await sendAPI({ device, cmd, id: 0 }).catch((error: Error) => { + const { send: sendExportAllValues } = useRequest( + () => callAction({ action: 'export', param: 'allvalues' }), + { + immediate: false + } + ) + .onSuccess((event) => { + saveFile(event.data, 'allvalues', '.txt'); + toast.info(LL.DOWNLOAD_SUCCESSFUL()); + }) + .onError((error) => { + toast.error(error.message); + }); + + const { send: sendAPI } = useRequest((data: APIcall) => API(data), { + immediate: false + }) + .onSuccess((event) => { + saveFile(event.data, 'system_info', '.json'); + toast.info(LL.DOWNLOAD_SUCCESSFUL()); + }) + .onError((error) => { toast.error(error.message); }); - }; return ( - <> - - {me.admin ? ( - - - - - - - - - - - + + } + sx={{ + borderRadius: 3, + border: '2px solid grey', + justifyContent: 'space-evenly', + alignItems: 'center' + }} + > + + {customSupportHTML ? ( +
+ ) : ( + LL.HELP_INFORMATION_5() + )} + + + - - - - - - - - - - + {me.admin && ( + + + + + + + + + + + - - - - - - - - - - - - ) : ( - } - sx={{ - borderRadius: 3, - border: '2px solid grey', - justifyContent: 'space-around', - alignItems: 'center' - }} - > - - {LL.HELP_INFORMATION_5()} - - - - )} + + + + + + + + + + - - - {LL.HELP_INFORMATION_4()} - - - + + + + + + + + + + + + )} + + + {LL.HELP_INFORMATION_4()} + + - + - - ©  - - {'emsesp.org'} - - - - + + + + ©  + + {'emsesp.org'} + + + ); }; diff --git a/interface/src/app/main/types.ts b/interface/src/app/main/types.ts index 0a615e058..1efa9bfc8 100644 --- a/interface/src/app/main/types.ts +++ b/interface/src/app/main/types.ts @@ -265,6 +265,12 @@ export interface APIcall { cmd: string; id: number; } + +export interface Action { + action: string; + param?: string; // optional +} + export interface WriteAnalogSensor { id: number; gpio: number; diff --git a/interface/src/app/settings/DownloadUpload.tsx b/interface/src/app/settings/DownloadUpload.tsx index c222c4901..c3cc12e1a 100644 --- a/interface/src/app/settings/DownloadUpload.tsx +++ b/interface/src/app/settings/DownloadUpload.tsx @@ -18,13 +18,8 @@ import { import Grid from '@mui/material/Grid2'; import * as SystemApi from 'api/system'; -import { API, exportData } from 'api/app'; -import { - checkUpgrade, - getDevVersion, - getStableVersion, - uploadURL -} from 'api/system'; +import { API, callAction } from 'api/app'; +import { getDevVersion, getStableVersion } from 'api/system'; import { dialogStyle } from 'CustomTheme'; import { useRequest } from 'alova/client'; @@ -37,6 +32,7 @@ import { useLayoutTitle } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; +import { saveFile } from 'utils/file'; const DownloadUpload = () => { const { LL } = useI18nContext(); @@ -46,11 +42,23 @@ const DownloadUpload = () => { const [useDev, setUseDev] = useState(false); const [upgradeAvailable, setUpgradeAvailable] = useState(false); - const { send: sendExportData } = useRequest((type: string) => exportData(type), { - immediate: false - }) + const { send: sendCheckUpgrade } = useRequest( + (version: string) => callAction({ action: 'checkUpgrade', param: version }), + { + immediate: false + } + ).onSuccess((event) => { + setUpgradeAvailable((event.data as { upgradeable: boolean }).upgradeable); + }); + + const { send: sendExportData } = useRequest( + (type: string) => callAction({ action: 'export', param: type }), + { + immediate: false + } + ) .onSuccess((event) => { - saveFile(event.data, event.args[0]); + saveFile(event.data, event.args[0], '.json'); toast.info(LL.DOWNLOAD_SUCCESSFUL()); }) .onError((error) => { @@ -61,14 +69,10 @@ const DownloadUpload = () => { immediate: false }); - const { - data: data, - send: loadData, - error - } = useRequest(SystemApi.readSystemStatus); + const { data, send: loadData, error } = useRequest(SystemApi.readSystemStatus); const { send: sendUploadURL } = useRequest( - (data: { url: string }) => uploadURL(data), + (url: string) => callAction({ action: 'uploadURL', param: url }), { immediate: false } @@ -83,12 +87,6 @@ const DownloadUpload = () => { ); }; - const { send: sendCheckUpgrade } = useRequest(checkUpgrade, { - immediate: false - }).onSuccess((event) => { - setUpgradeAvailable(event.data.upgradeable); - }); - // called immediately to get the latest version, on page load const { data: latestVersion } = useRequest(getStableVersion, { // uncomment next 2 lines for testing, uses https://github.com/emsesp/EMS-ESP32/releases/download/v3.6.5/EMS-ESP-3_6_5-ESP32-16MB+.bin @@ -102,7 +100,7 @@ const DownloadUpload = () => { // immediate: false, // initialData: '3.7.0-dev.32' }).onSuccess((event) => { - void sendCheckUpgrade({ version: event.data }); + void sendCheckUpgrade(event.data); }); const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/'; @@ -136,24 +134,12 @@ const DownloadUpload = () => { }; const installFirmwareURL = async (url: string) => { - await sendUploadURL({ url: url }).catch((error: Error) => { + await sendUploadURL(url).catch((error: Error) => { toast.error(error.message); }); setRestarting(true); }; - const saveFile = (json: unknown, filename: string) => { - const anchor = document.createElement('a'); - anchor.href = URL.createObjectURL( - new Blob([JSON.stringify(json, null, 2)], { - type: 'text/plain' - }) - ); - anchor.download = 'emsesp_' + filename + '.json'; - anchor.click(); - URL.revokeObjectURL(anchor.href); - }; - useLayoutTitle(LL.DOWNLOAD_UPLOAD()); const internet_live = diff --git a/interface/src/app/status/RestartMonitor.tsx b/interface/src/app/status/RestartMonitor.tsx index 855d597c9..d95487d59 100644 --- a/interface/src/app/status/RestartMonitor.tsx +++ b/interface/src/app/status/RestartMonitor.tsx @@ -45,7 +45,7 @@ const RestartMonitor = () => { return ( - + { ) : ( - + )} diff --git a/interface/src/components/layout/LayoutAppBar.tsx b/interface/src/components/layout/LayoutAppBar.tsx index c8f31ee0c..f254ccc77 100644 --- a/interface/src/components/layout/LayoutAppBar.tsx +++ b/interface/src/components/layout/LayoutAppBar.tsx @@ -41,6 +41,7 @@ const LayoutAppBar = ({ title, onToggleDrawer }: LayoutAppBarProps) => { {show_back && ( navigate(pathnames[0])} diff --git a/interface/src/components/upload/DragNdrop.tsx b/interface/src/components/upload/DragNdrop.tsx index d9d06c585..44902417d 100644 --- a/interface/src/components/upload/DragNdrop.tsx +++ b/interface/src/components/upload/DragNdrop.tsx @@ -12,6 +12,7 @@ import './dragNdrop.css'; const DragNdrop = ({ onFileSelected }) => { const [file, setFile] = useState(); + const [dragged, setDragged] = useState(false); const inputRef = useRef(null); const { LL } = useI18nContext(); @@ -45,6 +46,7 @@ const DragNdrop = ({ onFileSelected }) => { const handleRemoveFile = (event) => { event.stopPropagation(); setFile(undefined); + setDragged(false); }; const handleUploadClick = (event) => { @@ -56,11 +58,17 @@ const DragNdrop = ({ onFileSelected }) => { inputRef.current?.click(); }; + const handleDragOver = (event) => { + event.preventDefault(); // prevent file from being opened + setDragged(true); + }; + return (
event.preventDefault()} + onDragOver={handleDragOver} + onDragLeave={() => setDragged(false)} onClick={handleBrowseClick} >
diff --git a/interface/src/utils/file.ts b/interface/src/utils/file.ts new file mode 100644 index 000000000..d6d5df4d5 --- /dev/null +++ b/interface/src/utils/file.ts @@ -0,0 +1,11 @@ +export const saveFile = (json: unknown, filename: string, extension: string) => { + const anchor = document.createElement('a'); + anchor.href = URL.createObjectURL( + new Blob([JSON.stringify(json, null, 2)], { + type: 'text/plain' + }) + ); + anchor.download = 'emsesp_' + filename + extension; + anchor.click(); + URL.revokeObjectURL(anchor.href); +}; diff --git a/interface/yarn.lock b/interface/yarn.lock index 5c976fafc..34380ea50 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1446,12 +1446,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^22.7.3": - version: 22.7.3 - resolution: "@types/node@npm:22.7.3" +"@types/node@npm:^22.7.4": + version: 22.7.4 + resolution: "@types/node@npm:22.7.4" dependencies: undici-types: "npm:~6.19.2" - checksum: 10c0/0e579813528b0370454337a952f43b792cd12731e10fdca0fdb627158e980c1219bba99e9048c134b6a19325d817016059afe016ccd372326c838a1b85a51574 + checksum: 10c0/c22bf54515c78ff3170142c1e718b90e2a0003419dc2d55f79c9c9362edd590a6ab1450deb09ff6e1b32d1b4698da407930b16285e8be3a009ea6cd2695cac01 languageName: node linkType: hard @@ -1518,13 +1518,13 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.3.9": - version: 18.3.9 - resolution: "@types/react@npm:18.3.9" +"@types/react@npm:^18.3.10": + version: 18.3.10 + resolution: "@types/react@npm:18.3.10" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10c0/a92b8e061d0c833e096254782c56a802316593f4a907fb834b557cabe848a0829b9eb6056404ea239eb4d5ec5ac7b7724309761516c0a7a277916fa04dd4f805 + checksum: 10c0/f5be1de1b0331c1fdb33d577f4cf7f1b949d4bded5347b2351a537f03c51dade5be115e21b161dcf1b37061954d320f6a0bdf8d7b70e24eda51071fdd614383d languageName: node linkType: hard @@ -1546,15 +1546,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.7.0": - version: 8.7.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.7.0" +"@typescript-eslint/eslint-plugin@npm:8.8.0": + version: 8.8.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.8.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.7.0" - "@typescript-eslint/type-utils": "npm:8.7.0" - "@typescript-eslint/utils": "npm:8.7.0" - "@typescript-eslint/visitor-keys": "npm:8.7.0" + "@typescript-eslint/scope-manager": "npm:8.8.0" + "@typescript-eslint/type-utils": "npm:8.8.0" + "@typescript-eslint/utils": "npm:8.8.0" + "@typescript-eslint/visitor-keys": "npm:8.8.0" graphemer: "npm:^1.4.0" ignore: "npm:^5.3.1" natural-compare: "npm:^1.4.0" @@ -1565,66 +1565,66 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/f04d6fa6a30e32d51feba0f08789f75ca77b6b67cfe494bdbd9aafa241871edc918fa8b344dc9d13dd59ae055d42c3920f0e542534f929afbfdca653dae598fa + checksum: 10c0/98ac37587eda02a713710f0a62ca979833482024968f1d1735881718abe102a6b49707db4f1dac0d7c731d1cbf8111d829c5125348d4829ab6fad7a7b3b344e4 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.7.0": - version: 8.7.0 - resolution: "@typescript-eslint/parser@npm:8.7.0" +"@typescript-eslint/parser@npm:8.8.0": + version: 8.8.0 + resolution: "@typescript-eslint/parser@npm:8.8.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.7.0" - "@typescript-eslint/types": "npm:8.7.0" - "@typescript-eslint/typescript-estree": "npm:8.7.0" - "@typescript-eslint/visitor-keys": "npm:8.7.0" + "@typescript-eslint/scope-manager": "npm:8.8.0" + "@typescript-eslint/types": "npm:8.8.0" + "@typescript-eslint/typescript-estree": "npm:8.8.0" + "@typescript-eslint/visitor-keys": "npm:8.8.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/1d5020ff1f5d3eb726bc6034d23f0a71e8fe7a713756479a0a0b639215326f71c0b44e2c25cc290b4e7c144bd3c958f1405199711c41601f0ea9174068714a64 + checksum: 10c0/cf72a644b89c62cd55b09fa1d22b51a2c726714aac344a797f0c2ad80bfbabcb7567000fadd4ea8188aa1d923675bebdca06acc1d28ac1b8360bf28a36b46f3a languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.7.0": - version: 8.7.0 - resolution: "@typescript-eslint/scope-manager@npm:8.7.0" +"@typescript-eslint/scope-manager@npm:8.8.0": + version: 8.8.0 + resolution: "@typescript-eslint/scope-manager@npm:8.8.0" dependencies: - "@typescript-eslint/types": "npm:8.7.0" - "@typescript-eslint/visitor-keys": "npm:8.7.0" - checksum: 10c0/8b731a0d0bd3e8f6a322b3b25006f56879b5d2aad86625070fa438b803cf938cb8d5c597758bfa0d65d6e142b204dc6f363fa239bc44280a74e25aa427408eda + "@typescript-eslint/types": "npm:8.8.0" + "@typescript-eslint/visitor-keys": "npm:8.8.0" + checksum: 10c0/29ddf589ff0e465dbbf3eb87b79a29face4ec5a6cb617bbaafbac6ae8340d376b5b405bca762ee1c7a40cbdf7912a32734f9119f6864df048c7a0b2de21bdd3d languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.7.0": - version: 8.7.0 - resolution: "@typescript-eslint/type-utils@npm:8.7.0" +"@typescript-eslint/type-utils@npm:8.8.0": + version: 8.8.0 + resolution: "@typescript-eslint/type-utils@npm:8.8.0" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.7.0" - "@typescript-eslint/utils": "npm:8.7.0" + "@typescript-eslint/typescript-estree": "npm:8.8.0" + "@typescript-eslint/utils": "npm:8.8.0" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.3.0" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/2bd9fb93a50ff1c060af41528e39c775ae93b09dd71450defdb42a13c68990dd388460ae4e81fb2f4a49c38dc12152c515d43e845eca6198c44b14aab66733bc + checksum: 10c0/d6ee11f4686fb54daea1f436f73b96eb31a95f6e535abc0534abf5794e7597669a92d12300969c8afee0fc1912dbc1591664f7e37f0da5935016cc981b2921a8 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.7.0": - version: 8.7.0 - resolution: "@typescript-eslint/types@npm:8.7.0" - checksum: 10c0/f7529eaea4ecc0f5e2d94ea656db8f930f6d1c1e65a3ffcb2f6bec87361173de2ea981405c2c483a35a927b3bdafb606319a1d0395a6feb1284448c8ba74c31e +"@typescript-eslint/types@npm:8.8.0": + version: 8.8.0 + resolution: "@typescript-eslint/types@npm:8.8.0" + checksum: 10c0/cd168fafcaf77641b023c4405ea3a8c30fbad1737abb5aec9fce67fe2ae20224b624b5a2e3e84900ba81dc7dd33343add3653763703a225326cc81356b182d09 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.7.0": - version: 8.7.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.7.0" +"@typescript-eslint/typescript-estree@npm:8.8.0": + version: 8.8.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.8.0" dependencies: - "@typescript-eslint/types": "npm:8.7.0" - "@typescript-eslint/visitor-keys": "npm:8.7.0" + "@typescript-eslint/types": "npm:8.8.0" + "@typescript-eslint/visitor-keys": "npm:8.8.0" debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" is-glob: "npm:^4.0.3" @@ -1634,31 +1634,31 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/d714605b6920a9631ab1511b569c1c158b1681c09005ab240125c442a63e906048064151a61ce5eb5f8fe75cea861ce5ae1d87be9d7296b012e4ab6d88755e8b + checksum: 10c0/9b9e849f6b2d4e250840ef8e05f55a97d6598adaf48c1e6df83084b94c30feca6a3e7916ee1c235178188d0db6364a877cbf8fe218c36d5f8d5acb50767f3273 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.7.0": - version: 8.7.0 - resolution: "@typescript-eslint/utils@npm:8.7.0" +"@typescript-eslint/utils@npm:8.8.0": + version: 8.8.0 + resolution: "@typescript-eslint/utils@npm:8.8.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.7.0" - "@typescript-eslint/types": "npm:8.7.0" - "@typescript-eslint/typescript-estree": "npm:8.7.0" + "@typescript-eslint/scope-manager": "npm:8.8.0" + "@typescript-eslint/types": "npm:8.8.0" + "@typescript-eslint/typescript-estree": "npm:8.8.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - checksum: 10c0/7355b754ce2fc118773ed27a3e02b7dfae270eec73c2d896738835ecf842e8309544dfd22c5105aba6cae2787bfdd84129bbc42f4b514f57909dc7f6890b8eba + checksum: 10c0/fcf2dfd4a2d9491aa096a29c2c1fdd891ca3c13933d20cfea44e51b3d10a397e7ed9a9cd71ac9a29e8c4706264ae00c25a29394e2a6bda3291be298062901f2c languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.7.0": - version: 8.7.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.7.0" +"@typescript-eslint/visitor-keys@npm:8.8.0": + version: 8.8.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.8.0" dependencies: - "@typescript-eslint/types": "npm:8.7.0" + "@typescript-eslint/types": "npm:8.8.0" eslint-visitor-keys: "npm:^3.4.3" - checksum: 10c0/1240da13c15f9f875644b933b0ad73713ef12f1db5715236824c1ec359e6ef082ce52dd9b2186d40e28be6a816a208c226e6e9af96e5baeb24b4399fe786ae7c + checksum: 10c0/580ce74c9b09b9e6a6f3f0ac2d2f0c6a6b983a78ce3b2544822ee08107c57142858d674897f61ff32a9a5e8fca00c916545c159bb75d134f4380884642542d38 languageName: node linkType: hard @@ -1678,11 +1678,11 @@ __metadata: "@table-library/react-table-library": "npm:4.1.7" "@trivago/prettier-plugin-sort-imports": "npm:^4.3.0" "@types/formidable": "npm:^3" - "@types/node": "npm:^22.7.3" - "@types/react": "npm:^18.3.9" + "@types/node": "npm:^22.7.4" + "@types/react": "npm:^18.3.10" "@types/react-dom": "npm:^18.3.0" "@types/react-router-dom": "npm:^5.3.3" - alova: "npm:3.0.16" + alova: "npm:3.0.17" async-validator: "npm:^4.2.5" concurrently: "npm:^9.0.1" eslint: "npm:^9.11.1" @@ -1698,10 +1698,10 @@ __metadata: react-router-dom: "npm:^6.26.2" react-toastify: "npm:^10.0.5" rollup-plugin-visualizer: "npm:^5.12.0" - terser: "npm:^5.34.0" + terser: "npm:^5.34.1" typesafe-i18n: "npm:^5.26.2" typescript: "npm:^5.6.2" - typescript-eslint: "npm:8.7.0" + typescript-eslint: "npm:8.8.0" vite: "npm:^5.4.8" vite-plugin-imagemin: "npm:^0.6.1" vite-tsconfig-paths: "npm:^5.0.1" @@ -1764,13 +1764,13 @@ __metadata: languageName: node linkType: hard -"alova@npm:3.0.16": - version: 3.0.16 - resolution: "alova@npm:3.0.16" +"alova@npm:3.0.17": + version: 3.0.17 + resolution: "alova@npm:3.0.17" dependencies: "@alova/shared": "npm:^1.0.5" rate-limiter-flexible: "npm:^5.0.3" - checksum: 10c0/66cb597f4f00feda04b7619dd852fde92bc920cc97b018be70791240c8e8c64677a998a02a684f3aace5997322236a677264f25afe6bcaf4ec856ae42be859a8 + checksum: 10c0/e8a2ae885a3ff44dafec230d9388dc22b6445bb0cf8511fc9855b5a98ad9961941b0d33a7da874df23db4af0dba75872a470e3edebbdcc5ead8aecbc7fcc3d6b languageName: node linkType: hard @@ -6654,9 +6654,9 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.34.0": - version: 5.34.0 - resolution: "terser@npm:5.34.0" +"terser@npm:^5.34.1": + version: 5.34.1 + resolution: "terser@npm:5.34.1" dependencies: "@jridgewell/source-map": "npm:^0.3.3" acorn: "npm:^8.8.2" @@ -6664,7 +6664,7 @@ __metadata: source-map-support: "npm:~0.5.20" bin: terser: bin/terser - checksum: 10c0/74e8ef4e565e5600415cd9377a90eed419b8076465d453c0c76aef4053c45371512d2de76c34d01e004cdd49ea5a749d77eeb343f7e665b2d172158ca08ba23e + checksum: 10c0/51c7d704c5c4ae88bf937124112c9972aed4e1fd29d805cc2d86e0f54cd631ecd4e69db5bb3c1e3b450c741c86e2313328bea0fde925329e8a31a07a7941723c languageName: node linkType: hard @@ -6803,17 +6803,17 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:8.7.0": - version: 8.7.0 - resolution: "typescript-eslint@npm:8.7.0" +"typescript-eslint@npm:8.8.0": + version: 8.8.0 + resolution: "typescript-eslint@npm:8.8.0" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.7.0" - "@typescript-eslint/parser": "npm:8.7.0" - "@typescript-eslint/utils": "npm:8.7.0" + "@typescript-eslint/eslint-plugin": "npm:8.8.0" + "@typescript-eslint/parser": "npm:8.8.0" + "@typescript-eslint/utils": "npm:8.8.0" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/c0c3f909227c664f193d11a912851d6144a7cfcc0ac5e57f695c3e50679ef02bb491cc330ad9787e00170ce3be3a3b8c80bb81d5e20a40c1b3ee713ec3b0955a + checksum: 10c0/545f0ce051282921aff56288baf288cffe6f7bafee5149f1b87af2c67f81f8c2088924a2e0fc0f0dcd12692b6a97eca10149a619c8c85d4aaef2fe763938da8d languageName: node linkType: hard diff --git a/lib/framework/UploadFileService.cpp b/lib/framework/UploadFileService.cpp index a1ce63bb7..7e61b5a75 100644 --- a/lib/framework/UploadFileService.cpp +++ b/lib/framework/UploadFileService.cpp @@ -24,11 +24,6 @@ UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager * [this](AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) { handleUpload(request, filename, index, data, len, final); }); - - // upload from a URL - server->on(UPLOAD_URL_PATH, - securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { uploadURL(request, json); }, - AuthenticationPredicates::IS_AUTHENTICATED)); } void UploadFileService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) { @@ -91,7 +86,6 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri Update.setMD5(_md5.data()); _md5.front() = '\0'; } - // emsesp::EMSESP::system_.upload_status(true); // force just in case, this is stop UART, MQTT and other services request->onDisconnect([this] { handleEarlyDisconnect(); }); // success, let's make sure we end the update if the client hangs up } else { handleError(request, 507); // failed to begin, send an error response Insufficient Storage @@ -171,15 +165,3 @@ void UploadFileService::handleEarlyDisconnect() { _is_firmware = false; Update.abort(); } - -// upload firmware from a URL, like GitHub Release assets, Cloudflare R2 or Amazon S3 -void UploadFileService::uploadURL(AsyncWebServerRequest * request, JsonVariant json) { - if (json.is()) { - // this will keep a copy of the URL, but won't initiate the download yet - emsesp::EMSESP::system_.uploadFirmwareURL(json["url"].as()); - - // end the connection - AsyncWebServerResponse * response = request->beginResponse(200); - request->send(response); - } -} diff --git a/lib/framework/UploadFileService.h b/lib/framework/UploadFileService.h index 04a19a2c9..5afcb3028 100644 --- a/lib/framework/UploadFileService.h +++ b/lib/framework/UploadFileService.h @@ -12,7 +12,6 @@ #include #define UPLOAD_FILE_PATH "/rest/uploadFile" -#define UPLOAD_URL_PATH "/rest/uploadURL" #define TEMP_FILENAME_PATH "/tmp_upload" // for uploaded json files @@ -28,7 +27,6 @@ class UploadFileService { void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final); void uploadComplete(AsyncWebServerRequest * request); void handleError(AsyncWebServerRequest * request, int code); - void uploadURL(AsyncWebServerRequest * request, JsonVariant json); void handleEarlyDisconnect(); }; diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index 5b3b2476d..ee91e53f8 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -117,6 +117,61 @@ function updateMask(entity: any, de: any, dd: any) { } } +// called by Action endpoint +function export_data(type: string) { + console.log('exporting ' + type + '...'); + switch (type) { + case 'settings': + return emsesp_info; + case 'customizations': + return emsesp_deviceentities_2; // fixed for one device + case 'entities': + return emsesp_customentities; + case 'schedule': + return emsesp_schedule; + case 'modules': + return emsesp_modules; + case 'allvalues': + return emsesp_allvalues; + default: + return status(404); + } +} + +// called by Action endpoint +function custom_support() { + // return {}; + return { + type: 'custom_support', + Support: { + html: [ + 'This product is installed and managed by:', + '', + 'Bosch Installer Example', + '', + 'Nefit Road 12', + '1234 AB Amsterdam', + 'Phone: +31 123 456 789', + 'email: support@boschinstaller.nl', + '', + "For help and questions please contact your installer." + ], + img_url: 'https://emsesp.org/_media/images/designer.png' + // img_url: 'https://picsum.photos/200/300' + } + }; +} + +// called by Action endpoint +function check_upgrade(version: string) { + console.log('check upgrade from version', version); + const data = { + upgradeable: true + // upgradeable: false + }; + return data; +} + // START DATA // LOG @@ -402,7 +457,6 @@ const EMSESP_DEVICEDATA_ENDPOINT2 = REST_ENDPOINT_ROOT + 'deviceData/:id?'; const EMSESP_DEVICEENTITIES_ENDPOINT1 = REST_ENDPOINT_ROOT + 'deviceEntities'; const EMSESP_DEVICEENTITIES_ENDPOINT2 = REST_ENDPOINT_ROOT + 'deviceEntities/:id?'; -const EMSESP_CHECK_UPGRADE_ENDPOINT = REST_ENDPOINT_ROOT + 'checkUpgrade'; const EMSESP_BOARDPROFILE_ENDPOINT = REST_ENDPOINT_ROOT + 'boardProfile'; const EMSESP_WRITE_DEVICEVALUE_ENDPOINT = REST_ENDPOINT_ROOT + 'writeDeviceValue'; const EMSESP_WRITE_DEVICENAME_ENDPOINT = REST_ENDPOINT_ROOT + 'writeDeviceName'; @@ -416,7 +470,8 @@ const EMSESP_RESET_CUSTOMIZATIONS_ENDPOINT = const EMSESP_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'schedule'; const EMSESP_CUSTOMENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'customEntities'; const EMSESP_MODULES_ENDPOINT = REST_ENDPOINT_ROOT + 'modules'; -const EMSESP_EXPORT_DATA_ENDPOINT = REST_ENDPOINT_ROOT + 'exportData'; + +const EMSESP_ACTION_ENDPOINT = REST_ENDPOINT_ROOT + 'action'; // these are used in the API calls only const EMSESP_SYSTEM_INFO_ENDPOINT = API_ENDPOINT_ROOT + 'system/info'; @@ -4081,16 +4136,10 @@ router router .get(ACTIVITY_ENDPOINT, () => activity) .get(SYSTEM_STATUS_ENDPOINT, () => { - if (countHardwarePoll === 0) { - console.log('Resetting hardware count...'); - } - if (countHardwarePoll >= 2) { countHardwarePoll = 0; system_status.status = 'ready'; } - - console.log('Hardware count ' + countHardwarePoll + ' of 2'); countHardwarePoll++; return system_status; @@ -4402,17 +4451,6 @@ router return status(200); }) - // check upgrade - .post(EMSESP_CHECK_UPGRADE_ENDPOINT, async (request: any) => { - const content = await request.json(); - console.log('check upgrade from ', content.version); - const data = { - upgradeable: true - // upgradeable: false - }; - return data; - }) - // Settings - board profile .get(EMSESP_BOARDPROFILE_ENDPOINT, (request) => { const board_profile = request.query.boardProfile; @@ -4549,30 +4587,28 @@ router return data; }) - // Download Settings - .get(EMSESP_EXPORT_DATA_ENDPOINT, (request) => { - const type = request.query.type; - console.log('exporting ' + type + ' data'); - switch (type) { - case 'settings': - return emsesp_info; - case 'customizations': - return emsesp_deviceentities_2; // fixed for one device - case 'entities': - return emsesp_customentities; - case 'schedule': - return emsesp_schedule; - case 'modules': - return emsesp_modules; - default: - return status(404); + // generic action for all /rest/... endpoints + // takes an action and param in JSON + .post(EMSESP_ACTION_ENDPOINT, async (request: any) => { + const content = await request.json(); + if (content.hasOwnProperty('action')) { + const action = content.action; + if (action === 'export') { + // export data + return export_data(content.param); + } else if (action === 'customSupport') { + // send custom support + return custom_support(); + } else if (action === 'checkUpgrade') { + // check upgrade + return check_upgrade(content.param); + } else if (action === 'uploadURL') { + // upload URL + console.log('upload File from URL', content.param); + return status(200); + } } - }) - - // upload URL - .post('/rest/uploadURL', () => { - console.log('upload File from URL'); - return status(200); + return status(404); // cmd not found }) // API which are usually POST for security @@ -4594,9 +4630,6 @@ router if (cmd === 'info') { return emsesp_info; } - if (cmd === 'allvalues') { - return emsesp_allvalues; - } if (cmd === 'format') { console.log('formatting...'); return status(200); diff --git a/platformio.ini b/platformio.ini index 0b8ae8d43..45f4bc6ab 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,7 +39,7 @@ unbuild_flags = ${common.core_unbuild_flags} [espressif32_base] -platform = espressif32@6.8.1 +platform = espressif32@6.9.0 framework = arduino board_build.filesystem = littlefs build_flags = @@ -78,7 +78,7 @@ check_flags = clangtidy: --checks=-*,clang-analyzer-*,performance-* lib_ldf_mode = chain+ lib_deps = - https://github.com/emsesp/EMS-ESP-Modules.git@1.0.1 + https://github.com/emsesp/EMS-ESP-Modules.git@1.0.2 ; ; builds for GitHub Actions CI diff --git a/project-words.txt b/project-words.txt index d1ea72cb8..834361ef3 100644 --- a/project-words.txt +++ b/project-words.txt @@ -1345,4 +1345,7 @@ zulufttemp zyxwvutsrqponmlkjihgfedcba Omea Bolv -hardwarestatus \ No newline at end of file +hardwarestatus +hpcurrpower +hppowerlimit +CUSTOMSUPPORT \ No newline at end of file diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index e839fec13..7b1703f54 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -102,9 +102,6 @@ void AnalogSensor::reload(bool get_nvs) { } } if (!found) { - // if (!System::is_valid_gpio(sensor.gpio)) { - // continue; - // } sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type); sensors_.back().ha_registered = false; // this will trigger recreate of the HA config if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) { @@ -610,7 +607,7 @@ void AnalogSensor::publish_values(const bool force) { // see if we need to create the [devs] discovery section, as this needs only to be done once for all sensors bool is_ha_device_created = false; - for (auto & sensor : sensors_) { + for (auto const & sensor : sensors_) { if (sensor.ha_registered) { is_ha_device_created = true; break; diff --git a/src/command.cpp b/src/command.cpp index cad49e506..2befa9172 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -420,7 +420,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha } else { if (single_command) { // log as DEBUG (TRACE) regardless if compiled with EMSESP_DEBUG - logger_.debug(("%sCalled command %s"), ro.c_str(), info_s); + logger_.debug("%sCalled command %s", ro.c_str(), info_s); } else { if (id > 0) { LOG_INFO(("%sCalled command %s with value %s and id %d on device 0x%02X"), ro.c_str(), info_s, value, id, device_id); @@ -499,7 +499,7 @@ void Command::erase_command(const uint8_t device_type, const char * cmd, uint8_t return; } auto it = cmdfunctions_.begin(); - for (auto & cf : cmdfunctions_) { + for (auto const & cf : cmdfunctions_) { if (Helpers::toLower(cmd) == Helpers::toLower(cf.cmd_) && (cf.device_type_ == device_type) && ((flag & 0x3F) == (cf.flags_ & 0x3F))) { cmdfunctions_.erase(it); return; @@ -561,16 +561,13 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo } } - if (!verbose) { - sorted_cmds.push_back(F_(info)); - sorted_cmds.push_back(F_(commands)); - sorted_cmds.push_back(F_(values)); - } - sorted_cmds.sort(); // sort them // if not in verbose mode, just print them on a single line and exit if (!verbose) { + sorted_cmds.emplace_front(F_(info)); + sorted_cmds.emplace_front(F_(commands)); + sorted_cmds.emplace_front(F_(values)); for (const auto & cl : sorted_cmds) { shell.print(cl); shell.print(" "); @@ -582,14 +579,6 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo // verbose mode shell.printfln("\n%s%s %s:%s", COLOR_BOLD_ON, COLOR_YELLOW, EMSdevice::device_type_2_device_name(device_type), COLOR_RESET); - // we hard code 'info' and 'commands' commands so print them first - shell.printf(" info \t\t\t\t%slist all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); - shell.println(COLOR_RESET); - shell.printf(" commands \t\t\t%slist all commands %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); - shell.println(COLOR_RESET); - shell.printf(" values \t\t\t%slist all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); - shell.println(COLOR_RESET); - for (const auto & cl : sorted_cmds) { // find and print the description for (const auto & cf : cmdfunctions_) { @@ -682,6 +671,13 @@ void Command::show_devices(uuid::console::Shell & shell) { // calls show with verbose mode set void Command::show_all(uuid::console::Shell & shell) { shell.printfln("Showing all available commands (%s*%s=authentication not required):", COLOR_BRIGHT_GREEN, COLOR_RESET); + shell.println("Each device has these additional default commands:"); + shell.printf(" info \t\t\t\t%slist all values with description%s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); + shell.println(COLOR_RESET); + shell.printf(" commands \t\t\t%slist all commands %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); + shell.println(COLOR_RESET); + shell.printf(" values \t\t\t%slist all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN); + shell.println(COLOR_RESET); // show system ones first show(shell, EMSdevice::DeviceType::SYSTEM, true); diff --git a/src/console.cpp b/src/console.cpp index ca700c819..bd7aff5e0 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -76,54 +76,47 @@ static void setup_commands(std::shared_ptr & commands) { // // Show commands // - commands->add_command(ShellContext::MAIN, CommandFlags::USER, {F_(show)}, [=](Shell & shell, const std::vector & arguments) { - to_app(shell).system_.show_system(shell); - }); - commands->add_command(ShellContext::MAIN, - CommandFlags::USER, - string_vector{F_(show), F_(system)}, - [=](Shell & shell, const std::vector & arguments) { to_app(shell).system_.show_system(shell); }); + commands->add_command( + ShellContext::MAIN, + CommandFlags::USER, + {F_(show)}, + {F_(show_commands)}, + [=](Shell & shell, const std::vector & arguments) { + if (arguments.empty()) { + to_app(shell).system_.show_system(shell); + return; + } - commands->add_command(ShellContext::MAIN, - CommandFlags::ADMIN, - string_vector{F_(show), F_(users)}, - [](Shell & shell, const std::vector & arguments) { to_app(shell).system_.show_users(shell); }); - - commands->add_command(ShellContext::MAIN, - CommandFlags::USER, - string_vector{F_(show), F_(devices)}, - [](Shell & shell, const std::vector & arguments) { to_app(shell).show_devices(shell); }); - - commands->add_command(ShellContext::MAIN, CommandFlags::USER, string_vector{F_(show), F_(log)}, [](Shell & shell, const std::vector & arguments) { - to_app(shell).webLogService.show(shell); - }); - - - commands->add_command(ShellContext::MAIN, CommandFlags::USER, string_vector{F_(show), F_(ems)}, [](Shell & shell, const std::vector & arguments) { - to_app(shell).show_ems(shell); - }); - - commands->add_command(ShellContext::MAIN, CommandFlags::USER, string_vector{F_(show), F_(values)}, [](Shell & shell, const std::vector & arguments) { - to_app(shell).show_device_values(shell); - to_app(shell).show_sensor_values(shell); - }); - - commands->add_command(ShellContext::MAIN, - CommandFlags::USER, - string_vector{F_(show), F_(mqtt)}, - [](Shell & shell, const std::vector & arguments) { Mqtt::show_mqtt(shell); }); - - - commands->add_command(ShellContext::MAIN, - CommandFlags::USER, - string_vector{F_(show), F_(commands)}, - [](Shell & shell, const std::vector & arguments) { Command::show_all(shell); }); + auto command = arguments.front(); + if (command == F_(commands)) { + Command::show_all(shell); + } else if (command == F_(system)) { + to_app(shell).system_.show_system(shell); + } else if (command == F_(users) && (shell.has_flags(CommandFlags::ADMIN))) { + to_app(shell).system_.show_users(shell); // admin only + } else if (command == F_(devices)) { + to_app(shell).show_devices(shell); + } else if (command == F_(log)) { + to_app(shell).webLogService.show(shell); + } else if (command == F_(ems)) { + to_app(shell).show_ems(shell); + } else if (command == F_(values)) { + to_app(shell).show_device_values(shell); + to_app(shell).show_sensor_values(shell); + } else if (command == F_(mqtt)) { + Mqtt::show_mqtt(shell); + } else { + shell.printfln("Unknown show command"); + } + }, + [](Shell & shell, const std::vector & current_arguments, const std::string & next_argument) -> std::vector { + return std::vector{"system", "users", "devices", "log", "ems", "values", "mqtt", "commands"}; + }); // // System commands // - #if defined(EMSESP_TEST) // create commands test commands->add_command(ShellContext::MAIN, diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index c4909c314..9fda392c5 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -297,7 +297,7 @@ uint8_t EMSdevice::decode_brand(uint8_t value) { } // returns string of a human friendly description of the EMS device -const std::string EMSdevice::to_string() { +std::string EMSdevice::to_string() { // for devices that haven't been lookup yet, don't show all details if (product_id_ == 0) { return std::string(name()) + " (DeviceID:" + Helpers::hextoa(device_id_) + ")"; @@ -313,7 +313,7 @@ const std::string EMSdevice::to_string() { // returns out brand + device name // translated -const std::string EMSdevice::to_string_short() { +std::string EMSdevice::to_string_short() { if (brand_ == Brand::NO_BRAND) { return std::string(device_type_2_device_name_translated()) + ": " + name(); } @@ -553,11 +553,11 @@ void EMSdevice::add_device_value(int8_t tag, // to b } } - uint8_t state = DeviceValueState::DV_DEFAULT; // determine state - std::string custom_fullname = std::string(""); // custom fullname - auto short_name = name[0]; // entity name - bool has_cmd = (f != nullptr); // is it a command? - bool ignore = false; // ignore this entity? + uint8_t state = DeviceValueState::DV_DEFAULT; // determine state + auto custom_fullname = std::string(""); // custom fullname + auto short_name = name[0]; // entity name + bool has_cmd = (f != nullptr); // is it a command? + bool ignore = false; // ignore this entity? // get fullname, getting translation if it exists const char * const * fullname; @@ -1025,7 +1025,7 @@ void EMSdevice::generate_values_web(JsonObject output) { // this is used only for WebCustomizationService::device_entities() void EMSdevice::generate_values_web_customization(JsonArray output) { for (auto & dv : devicevalues_) { - // also show commands and entities that have an empty full name + // also show commands and entities that have an empty fullname JsonObject obj = output.add(); uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0; diff --git a/src/emsdevice.h b/src/emsdevice.h index d9e299830..cb7716ed9 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -42,7 +42,6 @@ class EMSdevice { , flags_(flags) , brand_(brand) { strlcpy(version_, version, sizeof(version_)); - custom_name_ = ""; // init custom name to blank } // static functions, used outside the class like in console.cpp, command.cpp, emsesp.cpp, mqtt.cpp @@ -113,7 +112,7 @@ class EMSdevice { } // set custom device name - inline void custom_name(std::string & custom_name) { + inline void custom_name(std::string const & custom_name) { custom_name_ = custom_name; } std::string name(); // returns either default or custom name if defined @@ -205,17 +204,16 @@ class EMSdevice { int get_modbus_value(uint8_t tag, const std::string & shortname, std::vector & result); int modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector & modbus_data, JsonObject jsonValue); - const char * brand_to_char(); - const std::string to_string(); - const std::string to_string_short(); + const char * brand_to_char(); + std::string to_string(); + std::string to_string_short(); enum Handlers : uint8_t { ALL, RECEIVED, FETCHED, PENDING, IGNORED }; void show_telegram_handlers(uuid::console::Shell & shell) const; char * show_telegram_handlers(char * result, const size_t len, const uint8_t handlers); void show_mqtt_handlers(uuid::console::Shell & shell) 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(int8_t tag, int16_t min, uint32_t max); void setCustomizationEntity(const std::string & entity_id); @@ -456,15 +454,13 @@ class EMSdevice { uint8_t count_entities(); bool has_entities() const; - /* - void reserve_device_values(uint8_t elements) { - devicevalues_.reserve(elements); - } + // void reserve_device_values(uint8_t elements) { + // devicevalues_.reserve(elements); + // } - void reserve_telegram_functions(uint8_t elements) { - telegram_functions_.reserve(elements); - } - */ + // void reserve_telegram_functions(uint8_t elements) { + // telegram_functions_.reserve(elements); + // } #if defined(EMSESP_STANDALONE) struct TelegramFunctionDump { @@ -487,10 +483,10 @@ class EMSdevice { uint8_t device_id_ = 0; uint8_t product_id_ = 0; char version_[6]; - const char * default_name_; // the fixed name the EMS model taken from the device library - std::string custom_name_; // custom name - uint8_t flags_ = 0; - uint8_t brand_ = Brand::NO_BRAND; + const char * default_name_; // the fixed name the EMS model taken from the device library + std::string custom_name_ = ""; // custom name + uint8_t flags_ = 0; + uint8_t brand_ = Brand::NO_BRAND; bool ha_config_done_ = false; bool has_update_ = false; diff --git a/src/emsesp.cpp b/src/emsesp.cpp index bf81c7f4e..12a515590 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -208,7 +208,7 @@ void EMSESP::uart_init() { uint8_t tx_mode = 0; uint8_t rx_gpio = 0; uint8_t tx_gpio = 0; - EMSESP::webSettingsService.read([&](WebSettings & settings) { + EMSESP::webSettingsService.read([&](WebSettings const & settings) { tx_mode = settings.tx_mode; rx_gpio = settings.rx_gpio; tx_gpio = settings.tx_gpio; @@ -279,7 +279,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) { if (bus_status() != BUS_STATUS_OFFLINE) { shell.printfln("EMS Bus info:"); - EMSESP::webSettingsService.read([&](WebSettings & settings) { shell.printfln(" Tx mode: %d", settings.tx_mode); }); + EMSESP::webSettingsService.read([&](WebSettings const & settings) { shell.printfln(" Tx mode: %d", settings.tx_mode); }); shell.printfln(" Bus protocol: %s", EMSbus::is_ht3() ? "HT3" : "Buderus"); shell.printfln(" #recognized EMS devices: %d", EMSESP::emsdevices.size()); shell.printfln(" #telegrams received: %d", rxservice_.telegram_count()); @@ -1317,7 +1317,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const emsdevices.push_back(EMSFactory::add(device_type, device_id, product_id, version, default_name, flags, brand)); // see if we have a custom device name in our Customizations list, and if so set it - webCustomizationService.read([&](WebCustomization & settings) { + webCustomizationService.read([&](WebCustomization const & settings) { for (EntityCustomization e : settings.entityCustomizations) { if ((e.device_id == device_id) && (e.product_id == product_id)) { LOG_DEBUG("Have customizations for %s with deviceID 0x%02X productID %d", e.custom_name.c_str(), device_id, product_id); diff --git a/src/helpers.cpp b/src/helpers.cpp index 508f1819c..2d8f9a76c 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -798,10 +798,6 @@ uint16_t Helpers::string2minutes(const std::string & str) { if (tmp > 60) { return 0; } - // Serial.print("*"); - // Serial.print(tmp); - // Serial.println("*"); - res += tmp; } // Or we got an extra colon diff --git a/src/locale_common.h b/src/locale_common.h index 97fb49ff0..08a65d68c 100644 --- a/src/locale_common.h +++ b/src/locale_common.h @@ -154,6 +154,7 @@ MAKE_WORD_CUSTOM(deviceid_mandatory, "") MAKE_WORD_CUSTOM(device_type_optional, "[device]") MAKE_WORD_CUSTOM(invalid_log_level, "Invalid log level") MAKE_WORD_CUSTOM(log_level_optional, "[level]") +MAKE_WORD_CUSTOM(show_commands, "[system | users | devices | log | ems | values | mqtt | commands") MAKE_WORD_CUSTOM(name_mandatory, "") MAKE_WORD_CUSTOM(name_optional, "[name]") MAKE_WORD_CUSTOM(new_password_prompt1, "Enter new password: ") diff --git a/src/locale_translations.h b/src/locale_translations.h index 35a88d45c..55a49b86c 100644 --- a/src/locale_translations.h +++ b/src/locale_translations.h @@ -63,7 +63,7 @@ MAKE_WORD_TRANSLATION(info_cmd, "list all values (verbose)", "Liste aller Werte" MAKE_WORD_TRANSLATION(commands_cmd, "list all commands", "Liste aller Kommandos", "lijst van alle commando's", "", "wyświetl wszystkie komendy", "Viser alle kommandoer", "", "Tüm komutları listele", "elencaa tutti i comandi", "zobraziť všetky príkazy") // TODO translate MAKE_WORD_TRANSLATION(entities_cmd, "list all entities", "Liste aller Entitäten", "lijst van alle entiteiten", "", "wyświetl wszsytkie encje", "Viser alle enheter", "", "Tüm varlıkları listele", "elenca tutte le entità", "zobraziť všetky entity") // TODO translate MAKE_WORD_TRANSLATION(send_cmd, "send a telegram", "Sende EMS-Telegramm", "stuur een telegram", "", "wyślij telegram", "send et telegram", "", "Bir telegram gönder", "invia un telegramma", "poslať telegram") // TODO translate -MAKE_WORD_TRANSLATION(setiovalue_cmd, "set io value", "Setze Werte E/A", "instellen standaardwaarde", "", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io", "nastaviť hodnotu io") // TODO translate +MAKE_WORD_TRANSLATION(setiovalue_cmd, "set I/O value", "Setze Werte E/A", "instellen standaardwaarde", "", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io", "nastaviť hodnotu io") // TODO translate MAKE_WORD_TRANSLATION(changeloglevel_cmd, "change log level", "Ändere Protokollebene", "aanpassen log niveau", "", "zmień poziom log-u", "endre loggnivå", "", "Kayıt seviyesini değiştir", "cambia livello registrazione", "zmeniť úroveň protokolu") // TODO translate MAKE_WORD_TRANSLATION(fetch_cmd, "refresh all EMS values", "Aktualisiere alle EMS-Werte", "Verversen alle EMS waardes", "", "odśwież wszystkie wartości EMS", "oppfrisk alle EMS verdier", "", "Bütün EMS değerlerini yenile", "aggiornare tutti i valori EMS", "obnoviť všetky hodnoty EMS") // TODO translate MAKE_WORD_TRANSLATION(restart_cmd, "restart EMS-ESP", "Neustart", "opnieuw opstarten", "", "uruchom ponownie EMS-ESP", "restart EMS-ESP", "redémarrer EMS-ESP", "EMS-ESPyi yeniden başlat", "riavvia EMS-ESP", "reštart EMS-ESP") // TODO translate @@ -75,7 +75,6 @@ MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplane MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "verstuur custom waarde naar EMS", "", "wyślij własną wartość na EMS", "", "", "emp üzerinde özel değer ayarla", "imposta valori personalizzati su EMS", "nastaviť vlastnú hodnotu na ems") // TODO translate MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "", "uzyskaj odpowiedź", "", "", "gelen cevap", "", "získať odpoveď") // TODO translate MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "Zugabe einer Menge kalten Wassers", "", "", "uruchom tryśnięcie zimnej wody", "", "", "soğuk su gönder", "", "pošlite studenú dávku vody") // TODO translate -MAKE_WORD_TRANSLATION(allvalues_cmd, "output all values in system", "Alle Werte im System ausgeben", "", "", "wyświetl wszystkie wartości", "", "", "", "", "vypísať všetky hodnoty") // TODO translate MAKE_WORD_TRANSLATION(message_cmd, "send a message", "Eine Nachricht senden", "", "", "", "", "", "", "", "poslať správu") // TODO translate MAKE_WORD_TRANSLATION(values_cmd, "list all values", "Liste alle Werte auf", "", "", "", "", "", "", "", "vypísať všetky hodnoty") // TODO translate diff --git a/src/modbus.h b/src/modbus.h index 5e11f4061..5e16344e4 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -14,9 +14,7 @@ #include #if defined(EMSESP_STANDALONE) - -#include - +#include <../test/test_modbus/modbus_test.h> #endif #ifndef EMSESP_STANDALONE diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 0fe1e6319..d0b42dcc9 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -1115,11 +1115,12 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev // special case to handle booleans // applies to both Binary Sensor (read only) and a Switch (for a command) - // has no unit of measure or icon + // has no unit of measure or icon, and must be true/false (not on/off or 1/0) if (type == DeviceValueType::BOOL) { add_ha_bool(doc); - Helpers::render_boolean(sample_val, false); + strlcpy(sample_val, "false", sizeof(sample_val)); // default is "false" } + doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else " + sample_val + "}}"; // add the dev json object to the end, not for commands diff --git a/src/roomcontrol.cpp b/src/roomcontrol.cpp index 12eed60f6..890ee7ee3 100644 --- a/src/roomcontrol.cpp +++ b/src/roomcontrol.cpp @@ -124,10 +124,7 @@ void Roomctrl::send(uint8_t addr) { } temperature(addr, 0x10, hc); // send to master-thermostat } - } else if (type_[hc] == RC200 || type_[hc] == RC100) { - send_time_[hc] = uuid::get_uptime(); - temperature(addr, 0x10, hc); - } else if (type_[hc] == FB10) { + } else if (type_[hc] == RC200 || type_[hc] == RC100 || type_[hc] == FB10) { send_time_[hc] = uuid::get_uptime(); temperature(addr, 0x10, hc); // send to master-thermostat (https://github.com/emsesp/EMS-ESP32/issues/336) } else { // type==RC20 or SENSOR diff --git a/src/shower.cpp b/src/shower.cpp index cc8210740..1460c93f6 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -25,7 +25,7 @@ uuid::log::Logger Shower::logger_{F_(shower), uuid::log::Facility::CONSOLE}; static bool force_coldshot = false; void Shower::start() { - EMSESP::webSettingsService.read([&](WebSettings & settings) { + EMSESP::webSettingsService.read([&](WebSettings const & settings) { shower_timer_ = settings.shower_timer; shower_alert_ = settings.shower_alert; shower_alert_trigger_ = settings.shower_alert_trigger * 60; // convert from minutes to seconds @@ -61,7 +61,6 @@ void Shower::loop() { return; } - // uint32_t time_now = uuid::get_uptime(); // in ms auto time_now = uuid::get_uptime_sec(); // in sec // if already in cold mode, ignore all this logic until we're out of the cold blast diff --git a/src/system.cpp b/src/system.cpp index f796514d8..531c7ff22 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -111,44 +111,6 @@ bool System::command_response(const char * value, const int8_t id, JsonObject ou return true; } -// output all the devices and the values -// not system info -bool System::command_allvalues(const char * value, const int8_t id, JsonObject output) { - JsonDocument doc; - JsonObject device_output; - // default to values - if (value == nullptr || strlen(value) == 0) { - value = F_(values); - } - - // System Entities - // device_output = output["System"].to(); - // get_value_info(device_output, value); - - // EMS-Device Entities - for (const auto & emsdevice : EMSESP::emsdevices) { - std::string title = emsdevice->device_type_2_device_name_translated() + std::string(" ") + emsdevice->to_string(); - device_output = output[title].to(); - emsdevice->get_value_info(device_output, value, DeviceValueTAG::TAG_NONE); - } - - // Custom Entities - device_output = output["Custom Entities"].to(); - EMSESP::webCustomEntityService.get_value_info(device_output, value); - - // Scheduler - device_output = output["Scheduler"].to(); - EMSESP::webSchedulerService.get_value_info(device_output, value); - - // Sensors - device_output = output["Analog Sensors"].to(); - EMSESP::analogsensor_.get_value_info(device_output, value); - device_output = output["Temperature Sensors"].to(); - EMSESP::temperaturesensor_.get_value_info(device_output, value); - - return true; -} - // fetch device values bool System::command_fetch(const char * value, const int8_t id) { std::string value_s; @@ -156,13 +118,13 @@ bool System::command_fetch(const char * value, const int8_t id) { if (value_s == "all") { LOG_INFO("Requesting data from EMS devices"); EMSESP::fetch_device_values(); - } else if (value_s == (F_(boiler))) { + } else if (value_s == F_(boiler)) { EMSESP::fetch_device_values_type(EMSdevice::DeviceType::BOILER); - } else if (value_s == (F_(thermostat))) { + } else if (value_s == F_(thermostat)) { EMSESP::fetch_device_values_type(EMSdevice::DeviceType::THERMOSTAT); - } else if (value_s == (F_(solar))) { + } else if (value_s == F_(solar)) { EMSESP::fetch_device_values_type(EMSdevice::DeviceType::SOLAR); - } else if (value_s == (F_(mixer))) { + } else if (value_s == F_(mixer)) { EMSESP::fetch_device_values_type(EMSdevice::DeviceType::MIXER); } } else { @@ -338,6 +300,8 @@ void System::system_restart(const char * partitionname) { delay(1000); // wait 1 second ESP.restart(); #else + restart_requested(false); + restart_pending(false); if (partitionname != nullptr) { LOG_INFO("Restarting EMS-ESP from %s partition", partitionname); } else { @@ -375,9 +339,6 @@ void System::syslog_init() { syslog_.destination(syslog_host_.c_str(), syslog_port_); syslog_.hostname(hostname().c_str()); - // removed in 3.6.0 - // Command::add(EMSdevice::DeviceType::SYSTEM, F_(syslog), System::command_syslog_level, FL_(changeloglevel_cmd), CommandFlag::ADMIN_ONLY); - } else if (syslog_.started()) { // in case service is still running, this flushes the queue // https://github.com/emsesp/EMS-ESP/issues/496 @@ -874,7 +835,6 @@ void System::commands_init() { // these commands will return data in JSON format Command::add(EMSdevice::DeviceType::SYSTEM, F("response"), System::command_response, FL_(commands_response)); - Command::add(EMSdevice::DeviceType::SYSTEM, F("allvalues"), System::command_allvalues, FL_(allvalues_cmd)); // MQTT subscribe "ems-esp/system/#" Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback @@ -1031,8 +991,11 @@ void System::show_system(uuid::console::Shell & shell) { shell.printfln(" App used/free: %lu KB / %lu KB", appUsed(), appFree()); uint32_t FSused = LittleFS.usedBytes() / 1024; shell.printfln(" FS used/free: %lu KB / %lu KB", FSused, FStotal() - FSused); + shell.printfln(" Flash size: %lu KB", ESP.getFlashChipSize() / 1024); if (PSram()) { shell.printfln(" PSRAM size/free: %lu KB / %lu KB", PSram(), ESP.getFreePsram() / 1024); + } else { + shell.printfln(" PSRAM: not available"); } shell.println(); @@ -1087,7 +1050,7 @@ void System::show_system(uuid::console::Shell & shell) { shell.printfln(" WiFi Network: Disconnected"); break; - case WL_NO_SHIELD: + // case WL_NO_SHIELD: default: shell.printfln(" WiFi MAC address: %s", WiFi.macAddress().c_str()); shell.printfln(" WiFi Network: not connected"); @@ -1121,7 +1084,7 @@ void System::show_system(uuid::console::Shell & shell) { } else { shell.printfln(" Syslog: %s", syslog_.started() ? "started" : "stopped"); shell.print(" "); - shell.printfln(F_(host_fmt), !syslog_host_.isEmpty() ? syslog_host_.c_str() : (F_(unset))); + shell.printfln(F_(host_fmt), !syslog_host_.isEmpty() ? syslog_host_.c_str() : F_(unset)); shell.printfln(" IP: %s", uuid::printable_to_string(syslog_.ip()).c_str()); shell.print(" "); shell.printfln(F_(port_fmt), syslog_port_); @@ -1139,7 +1102,7 @@ void System::show_system(uuid::console::Shell & shell) { // see if there is a restore of an older settings file that needs to be applied bool System::check_restore() { - bool reboot_required = false; + bool reboot_required = false; // true if we need to reboot #ifndef EMSESP_STANDALONE File new_file = LittleFS.open(TEMP_FILENAME_PATH); @@ -1167,11 +1130,20 @@ bool System::check_restore() { } else if (settings_type == "entities") { // it's a entity file, just replace it and there's no need to reboot saveSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", input); + } else if (settings_type == "customSupport") { + // it's a custom support file - save it to /config + new_file.close(); + if (LittleFS.rename(TEMP_FILENAME_PATH, EMSESP_CUSTOMSUPPORT_FILE)) { + LOG_DEBUG("Custom support information found"); + return false; // no need to reboot + } else { + LOG_ERROR("Failed to save custom support file"); + } } else { LOG_ERROR("Unrecognized file uploaded"); } } else { - LOG_ERROR("Unrecognized file uploaded, not json. Will be removed."); + LOG_ERROR("Unrecognized file uploaded, not json."); } // close (just in case) and remove the temp file @@ -1192,7 +1164,7 @@ bool System::check_upgrade(bool factory_settings) { if (!factory_settings) { // fetch current version from settings file - EMSESP::webSettingsService.read([&](WebSettings & settings) { settingsVersion = settings.version.c_str(); }); + EMSESP::webSettingsService.read([&](WebSettings const & settings) { settingsVersion = settings.version.c_str(); }); // see if we're missing a version, will be < 3.5.0b13 from Dec 23 2022 missing_version = (settingsVersion.empty() || (settingsVersion.length() < 5)); @@ -1363,14 +1335,11 @@ bool System::get_value_info(JsonObject output, const char * cmd) { if (!strcmp(cmd, F_(entities))) { for (JsonPair p : root) { if (p.value().is()) { - // String prefix = p.key().c_str(); for (JsonPair p1 : p.value().as()) { JsonObject entity = output[std::string(p.key().c_str()) + "." + p1.key().c_str()].to(); get_value_json(entity, p.key().c_str(), p1.key().c_str(), p1.value()); } - } // else { // we don't have pairs in json root object - // get_value_json(entity, "", p.key().c_str(), p.value()); - // } + } } return true; } @@ -1523,7 +1492,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output } }); #ifndef EMSESP_STANDALONE - EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) { + EMSESP::esp8266React.getAPSettingsService()->read([&](const APSettings & settings) { const char * pM[] = {"always", "disconnected", "never"}; node["APProvisionMode"] = pM[settings.provisionMode]; node["APSecurity"] = settings.password.length() ? "wpa2" : "open"; @@ -1535,11 +1504,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output node = output["ntp"].to(); #ifndef EMSESP_STANDALONE node["NTPStatus"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected"; - EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) { + EMSESP::esp8266React.getNTPSettingsService()->read([&](const NTPSettings & settings) { node["enabled"] = settings.enabled; node["server"] = settings.server; node["tzLabel"] = settings.tzLabel; - // node["tz format"] = settings.tzFormat; }); #endif @@ -1552,7 +1520,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output node["MQTTPublishFails"] = Mqtt::publish_fails(); node["MQTTConnects"] = Mqtt::connect_count(); } - EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { + EMSESP::esp8266React.getMqttSettingsService()->read([&](const MqttSettings & settings) { node["enabled"] = settings.enabled; node["clientID"] = settings.clientId; node["keepAlive"] = settings.keepAlive; @@ -1631,7 +1599,6 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output node["busStatus"] = "unknown"; break; } - // if (EMSESP::bus_status() != EMSESP::BUS_STATUS_OFFLINE) { node["busProtocol"] = EMSbus::is_ht3() ? "HT3" : "Buderus"; node["busTelegramsReceived"] = EMSESP::rxservice_.telegram_count(); node["busReads"] = EMSESP::txservice_.telegram_read_count(); @@ -1641,11 +1608,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output node["busWritesFailed"] = EMSESP::txservice_.telegram_write_fail_count(); node["busRxLineQuality"] = EMSESP::rxservice_.quality(); node["busTxLineQuality"] = (EMSESP::txservice_.read_quality() + EMSESP::txservice_.read_quality()) / 2; - // } // Settings node = output["settings"].to(); - EMSESP::webSettingsService.read([&](WebSettings & settings) { + EMSESP::webSettingsService.read([&](const WebSettings & settings) { node["boardProfile"] = settings.board_profile; node["locale"] = settings.locale; node["txMode"] = settings.tx_mode; @@ -1732,7 +1698,6 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output obj["name"] = F_(temperaturesensor); obj["entities"] = EMSESP::temperaturesensor_.count_entities(); } - // if (EMSESP::analog_enabled()) { if (EMSESP::analogsensor_.count_entities()) { JsonObject obj = devices.add(); obj["type"] = F_(analogsensor); @@ -1895,7 +1860,7 @@ std::string System::reset_reason(uint8_t cpu) const { break; } #endif - return ("Unknown"); + return "Unknown"; } #pragma GCC diagnostic pop diff --git a/src/system.h b/src/system.h index 2e980a22b..60b619195 100644 --- a/src/system.h +++ b/src/system.h @@ -43,6 +43,8 @@ using uuid::console::Shell; #define EMSESP_FS_CONFIG_DIRECTORY "/config" +#define EMSESP_CUSTOMSUPPORT_FILE "/config/customSupport.json" + namespace emsesp { enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110 }; @@ -58,12 +60,10 @@ class System { static bool command_fetch(const char * value, const int8_t id); static bool command_restart(const char * value, const int8_t id); static bool command_format(const char * value, const int8_t id); - // static bool command_syslog_level(const char * value, const int8_t id); static bool command_watch(const char * value, const int8_t id); static bool command_message(const char * value, const int8_t id); static bool command_info(const char * value, const int8_t id, JsonObject output); static bool command_response(const char * value, const int8_t id, JsonObject output); - static bool command_allvalues(const char * value, const int8_t id, JsonObject output); static bool get_value_info(JsonObject root, const char * cmd); static void get_value_json(JsonObject output, const std::string & circuit, const std::string & name, JsonVariant val); @@ -162,10 +162,12 @@ class System { readonly_mode_ = readonly_mode; } + // Boolean Format API/MQTT uint8_t bool_format() { return bool_format_; } + // Boolean Format Web uint8_t bool_dashboard() { return bool_dashboard_; } diff --git a/src/temperaturesensor.cpp b/src/temperaturesensor.cpp index c647b82a4..5d1a65ee8 100644 --- a/src/temperaturesensor.cpp +++ b/src/temperaturesensor.cpp @@ -53,7 +53,7 @@ void TemperatureSensor::start() { // load settings void TemperatureSensor::reload() { // load the service settings - EMSESP::webSettingsService.read([&](WebSettings & settings) { + EMSESP::webSettingsService.read([&](WebSettings const & settings) { dallas_gpio_ = settings.dallas_gpio; parasite_ = settings.dallas_parasite; }); @@ -274,7 +274,7 @@ int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) { case 11: raw_value &= ~0x1; break; - case 12: + default: // 12 break; } } @@ -302,7 +302,7 @@ bool TemperatureSensor::update(const std::string & id, const std::string & name, sensor.set_offset(offset); // store the new name and offset in our configuration - EMSESP::webCustomizationService.update([&](WebCustomization & settings) { + EMSESP::webCustomizationService.update([&id, &name, &offset, &sensor](WebCustomization & settings) { // look it up to see if it exists bool found = false; for (auto & SensorCustomization : settings.sensorCustomizations) { @@ -315,10 +315,10 @@ bool TemperatureSensor::update(const std::string & id, const std::string & name, } } if (!found) { - SensorCustomization newSensor = SensorCustomization(); - newSensor.id = id; - newSensor.name = name; - newSensor.offset = offset; + auto newSensor = SensorCustomization(); + newSensor.id = id; + newSensor.name = name; + newSensor.offset = offset; settings.sensorCustomizations.push_back(newSensor); LOG_DEBUG("Adding new customization for sensor ID %s", id.c_str()); } @@ -401,9 +401,9 @@ void TemperatureSensor::publish_sensor(const Sensor & sensor) { if (Mqtt::enabled() && Mqtt::publish_single()) { char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; if (Mqtt::publish_single2cmd()) { - snprintf(topic, sizeof(topic), "%s/%s", (F_(temperaturesensor)), sensor.name().c_str()); + snprintf(topic, sizeof(topic), "%s/%s", F_(temperaturesensor), sensor.name().c_str()); } else { - snprintf(topic, sizeof(topic), "%s%s/%s", (F_(temperaturesensor)), "_data", sensor.name().c_str()); + snprintf(topic, sizeof(topic), "%s%s/%s", F_(temperaturesensor), "_data", sensor.name().c_str()); } char payload[10]; Mqtt::queue_publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0)); @@ -513,7 +513,7 @@ void TemperatureSensor::publish_values(const bool force) { // see if we need to create the [devs] discovery section, as this needs only to be done once for all sensors bool is_ha_device_created = false; - for (auto & sensor : sensors_) { + for (const auto & sensor : sensors_) { if (sensor.ha_registered) { is_ha_device_created = true; break; @@ -570,8 +570,8 @@ std::string TemperatureSensor::Sensor::name() const { // look up in customization service for a specific sensor // and set the name and offset from that entry if it exists bool TemperatureSensor::Sensor::apply_customization() { - EMSESP::webCustomizationService.read([&](WebCustomization & settings) { - auto sensors = settings.sensorCustomizations; + EMSESP::webCustomizationService.read([&](const WebCustomization & settings) { + auto const & sensors = settings.sensorCustomizations; if (!sensors.empty()) { for (const auto & sensor : sensors) { if (id_ == sensor.id) { diff --git a/src/temperaturesensor.h b/src/temperaturesensor.h index 1d9e4d364..d63017884 100644 --- a/src/temperaturesensor.h +++ b/src/temperaturesensor.h @@ -90,23 +90,23 @@ class TemperatureSensor { return sensors_; } - uint32_t reads() { + uint32_t reads() const { return sensorreads_; } - uint32_t fails() { + uint32_t fails() const { return sensorfails_; } - bool sensor_enabled() { + bool sensor_enabled() const { return (dallas_gpio_ != 0); } - bool have_sensors() { + bool have_sensors() const { return (!sensors_.empty()); } - size_t count_entities() { + size_t count_entities() const { return sensors_.size(); } diff --git a/src/test/test.cpp b/src/test/test.cpp index c81d98b96..63f9241bf 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -327,7 +327,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const // shell.invoke_command("show devices"); // shell.invoke_command("show values"); - // shell.invoke_command("call system allvalues"); // shell.invoke_command("call system publish"); // shell.invoke_command("show mqtt"); ok = true; @@ -792,7 +791,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const shell.invoke_command("call temperaturesensor"); shell.invoke_command("show values"); - shell.invoke_command("call system allvalues"); shell.invoke_command("call temperaturesensor info"); shell.invoke_command("call temperaturesensor values"); @@ -842,7 +840,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const shell.invoke_command("call analogsensor"); shell.invoke_command("show values"); - shell.invoke_command("call system allvalues"); shell.invoke_command("call analogsensor info"); shell.invoke_command("call analogsensor values"); @@ -992,11 +989,26 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const // EMSESP::webAPIService.webAPIService(&request); request.method(HTTP_POST); - char data_api[] = "{\"device\":\"system\", \"cmd\":\"restart\",\"id\":-1}"; - deserializeJson(doc, data_api); - json = doc.as(); + + char data1[] = "{\"device\":\"system\", \"cmd\":\"restart\",\"id\":-1}"; + deserializeJson(doc, data1); request.url("/api"); - EMSESP::webAPIService.webAPIService(&request, json); + EMSESP::webAPIService.webAPIService(&request, doc.as()); + + char data2[] = "{\"action\":\"customSupport\", \"param\":\"hello\"}"; + deserializeJson(doc, data2); + request.url("/rest/action"); + EMSESP::webStatusService.action(&request, doc.as()); + + char data3[] = "{\"action\":\"export\", \"param\":\"schedule\"}"; + deserializeJson(doc, data3); + request.url("/rest/action"); + EMSESP::webStatusService.action(&request, doc.as()); + + char data4[] = "{\"action\":\"export\", \"param\":\"allvalues\"}"; + deserializeJson(doc, data4); + request.url("/rest/action"); + EMSESP::webStatusService.action(&request, doc.as()); // request.url("/api/thermostat"); // EMSESP::webAPIService.webAPIService(&request); diff --git a/src/version.h b/src/version.h index b78c093ae..e0e7948ea 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.7.0-dev.41" +#define EMSESP_APP_VERSION "3.7.0-dev.42" diff --git a/src/web/WebAPIService.cpp b/src/web/WebAPIService.cpp index 4175d3b75..063ca114f 100644 --- a/src/web/WebAPIService.cpp +++ b/src/web/WebAPIService.cpp @@ -58,7 +58,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) { void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) { // check if the user has admin privileges (token is included and authorized) bool is_admin = false; - EMSESP::webSettingsService.read([&](WebSettings & settings) { + EMSESP::webSettingsService.read([&](WebSettings const & settings) { Authentication authentication = _securityManager->authenticateRequest(request); is_admin = settings.notoken_api || AuthenticationPredicates::IS_ADMIN(authentication); }); @@ -94,7 +94,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) { emsesp::EMSESP::system_.refreshHeapMem(); // output json buffer - AsyncJsonResponse * response = new AsyncJsonResponse(false); + auto response = new AsyncJsonResponse(false); // add more mem if needed - won't be needed in ArduinoJson 7 // while (!response->getSize()) { diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index 0a0a56061..bb71fc6cf 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -47,7 +47,7 @@ void WebCustomEntityService::begin() { void WebCustomEntity::read(WebCustomEntity & webEntity, JsonObject root) { JsonArray entity = root["entities"].to(); uint8_t counter = 0; - for (const CustomEntityItem & entityItem : webEntity.customEntityItems) { + for (CustomEntityItem & entityItem : webEntity.customEntityItems) { JsonObject ei = entity.add(); ei["id"] = counter++; // id is only used to render the table and must be unique ei["ram"] = entityItem.ram; @@ -213,7 +213,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i // output of a single value // if add_uom is true it will add the UOM string to the value -void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem entity, const bool useVal, const bool web, const bool add_uom) { +void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem & entity, const bool useVal, const bool web, const bool add_uom) { char payload[12]; std::string name = useVal ? "value" : entity.name; switch (entity.value_type) { @@ -262,7 +262,7 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem en output[name] = add_uom ? serialized(v + ' ' + EMSdevice::uom_to_string(entity.uom)) : serialized(v); } break; - case DeviceValueType::STRING: + // case DeviceValueType::STRING: default: // if no type treat it as a string if (entity.data.length() > 0) { @@ -275,7 +275,7 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem en // display all custom entities // adding each one, with UOM to a json object string void WebCustomEntityService::show_values(JsonObject output) { - for (const CustomEntityItem & entity : *customEntityItems_) { + for (CustomEntityItem & entity : *customEntityItems_) { render_value(output, entity, false, false, true); // with add_uom } } @@ -285,14 +285,14 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) // if no custom entries, return empty json // even if we're looking for a specific entity // https://github.com/emsesp/EMS-ESP32/issues/1297 - if (customEntityItems_->size() == 0) { + if (customEntityItems_->empty()) { return true; } // if it's info or values... if (!strlen(cmd) || !strcmp(cmd, F_(values)) || !strcmp(cmd, F_(info))) { // list all names - for (const CustomEntityItem & entity : *customEntityItems_) { + for (CustomEntityItem & entity : *customEntityItems_) { render_value(output, entity); } return true; @@ -300,7 +300,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) // list all entities if (!strcmp(cmd, F_(entities))) { - for (const auto & entity : *customEntityItems_) { + for (auto & entity : *customEntityItems_) { auto nest = output[entity.name].to(); get_value_json(nest, entity); } @@ -309,7 +309,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) // specific value info const char * attribute_s = Command::get_attribute(cmd); - for (const auto & entity : *customEntityItems_) { + for (auto & entity : *customEntityItems_) { if (Helpers::toLower(entity.name) == cmd) { get_value_json(output, entity); return Command::set_attribute(output, cmd, attribute_s); @@ -319,7 +319,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) } // build the json for specific entity -void WebCustomEntityService::get_value_json(JsonObject output, const CustomEntityItem & entity) { +void WebCustomEntityService::get_value_json(JsonObject output, CustomEntityItem & entity) { output["name"] = entity.name; output["fullname"] = entity.name; output["storage"] = entity.ram ? "ram" : "ems"; @@ -344,7 +344,7 @@ void WebCustomEntityService::get_value_json(JsonObject output, const CustomEntit } // publish single value -void WebCustomEntityService::publish_single(const CustomEntityItem & entity) { +void WebCustomEntityService::publish_single(CustomEntityItem & entity) { if (!Mqtt::enabled() || !Mqtt::publish_single()) { return; } @@ -372,11 +372,11 @@ void WebCustomEntityService::publish(const bool force) { return; } - if (customEntityItems_->size() == 0) { + if (customEntityItems_->empty()) { return; } if (Mqtt::publish_single() && force) { - for (const CustomEntityItem & entityItem : *customEntityItems_) { + for (CustomEntityItem & entityItem : *customEntityItems_) { publish_single(entityItem); } } @@ -385,7 +385,7 @@ void WebCustomEntityService::publish(const bool force) { JsonObject output = doc.to(); bool ha_created = ha_registered_; - for (const CustomEntityItem & entityItem : *customEntityItems_) { + for (CustomEntityItem & entityItem : *customEntityItems_) { render_value(output, entityItem); // create HA config if (Mqtt::ha_enabled() && !ha_registered_) { @@ -454,16 +454,16 @@ void WebCustomEntityService::publish(const bool force) { // count only entities with valid value or command to show in dashboard uint8_t WebCustomEntityService::count_entities() { - if (customEntityItems_->size() == 0) { + if (customEntityItems_->empty()) { return 0; } JsonDocument doc; JsonObject output = doc.to(); uint8_t count = 0; - for (const CustomEntityItem & entity : *customEntityItems_) { + + for (CustomEntityItem & entity : *customEntityItems_) { render_value(output, entity); - // TODO check JsonVariant if (output[entity.name].is() || entity.writeable) { count++; } @@ -558,7 +558,7 @@ void WebCustomEntityService::generate_value_web(JsonObject output) { void WebCustomEntityService::fetch() { const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3, 4}; - for (auto & entity : *customEntityItems_) { + for (auto const & entity : *customEntityItems_) { if (entity.device_id > 0 && entity.type_id > 0) { // this excludes also RAM type bool needFetch = true; uint8_t fetchblock = entity.type_id > 0x0FF ? 25 : 27; @@ -580,7 +580,6 @@ void WebCustomEntityService::fetch() { } } } - // EMSESP::logger().debug("fetch custom entities"); } // called on process telegram, read from telegram diff --git a/src/web/WebCustomEntityService.h b/src/web/WebCustomEntityService.h index 15db79c7e..cacefd3b8 100644 --- a/src/web/WebCustomEntityService.h +++ b/src/web/WebCustomEntityService.h @@ -55,14 +55,14 @@ class WebCustomEntityService : public StatefulService { WebCustomEntityService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); void begin(); - void publish_single(const CustomEntityItem & entity); + void publish_single(CustomEntityItem & entity); void publish(const bool force = false); bool command_setvalue(const char * value, const int8_t id, const char * name); bool get_value_info(JsonObject output, const char * cmd); - void get_value_json(JsonObject output, const CustomEntityItem & entity); + void get_value_json(JsonObject output, CustomEntityItem & entity); bool get_value(std::shared_ptr telegram); void fetch(); - void render_value(JsonObject output, CustomEntityItem entity, const bool useVal = false, const bool web = false, const bool add_uom = false); + void render_value(JsonObject output, CustomEntityItem & entity, const bool useVal = false, const bool web = false, const bool add_uom = false); void show_values(JsonObject output); void generate_value_web(JsonObject output); diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index 167a0f654..b1b0229c4 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -134,7 +134,7 @@ bool WebSchedulerService::command_setvalue(const char * value, const int8_t id, // process json output for info/commands and value_info bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) { - if (scheduleItems_->size() == 0) { + if (scheduleItems_->empty()) { return true; } @@ -233,7 +233,7 @@ void WebSchedulerService::publish(const bool force) { return; } - if (scheduleItems_->size() == 0) { + if (scheduleItems_->empty()) { return; } @@ -449,7 +449,7 @@ void WebSchedulerService::loop() { static uint32_t last_uptime_sec = 0; // get list of scheduler events and exit if it's empty - if (scheduleItems_->size() == 0) { + if (scheduleItems_->empty()) { return; } @@ -584,7 +584,7 @@ void WebSchedulerService::test() { test_value = "(custom/seltemp)"; command("test5", test_cmd.c_str(), compute(test_value).c_str()); - // this will fail unless test("boiler") is loaded + // note: this will fail unless test("boiler") is loaded before hand test_value = "(boiler/outdoortemp)"; command("test6", test_cmd.c_str(), compute(test_value).c_str()); diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index 8ea907161..1cf9f7cdb 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -24,13 +24,13 @@ namespace emsesp { -WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager) { +WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager) + : _securityManager(securityManager) { // GET server->on(EMSESP_SYSTEM_STATUS_SERVICE_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { systemStatus(request); }); - server->on(EMSESP_EXPORT_DATA_SERVICE_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { exportData(request); }); - // POST - server->on(EMSESP_CHECK_UPGRADE_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { checkUpgrade(request, json); }); + // POST - generic action handler + server->on(EMSESP_ACTION_SERVICE_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { action(request, json); }); } // /rest/systemStatus @@ -146,13 +146,55 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) { request->send(response); } -// returns trues if there is an upgrade available -void WebStatusService::checkUpgrade(AsyncWebServerRequest * request, JsonVariant json) { +// generic action handler - as a POST +void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json) { auto * response = new AsyncJsonResponse(); JsonObject root = response->getRoot(); + // get action and any optional param + std::string action = json["action"]; + std::string param = json["param"]; // is optional + + // check if we're authenticated for admin tasks, some actions are only for admins + Authentication authentication = _securityManager->authenticateRequest(request); + bool is_admin = AuthenticationPredicates::IS_ADMIN(authentication); + + bool ok = true; + if (action == "checkUpgrade") { + ok = checkUpgrade(root, param); + } else if (action == "export") { + ok = exportData(root, param); + } else if (action == "customSupport") { + ok = customSupport(root); + } else if (action == "uploadURL" && is_admin) { + ok = uploadURL(param.c_str()); + } + +#if defined(EMSESP_UNITY) + // store the result so we can test with Unity later + storeResponse(output); +#endif +#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY) + Serial.printf("%sweb output: %s[%s]", COLOR_WHITE, COLOR_BRIGHT_CYAN, request->url().c_str()); + Serial.printf(" %s(%d)%s ", ok ? COLOR_BRIGHT_GREEN : COLOR_BRIGHT_RED, ok ? 200 : 400, COLOR_YELLOW); + serializeJson(root, Serial); + Serial.println(COLOR_RESET); +#endif + + // send response + if (!ok) { + request->send(400); + return; + } + + response->setLength(); + request->send(response); +} + +// action = checkUpgrade +// returns true if there is an upgrade available +bool WebStatusService::checkUpgrade(JsonObject root, std::string & latest_version) { version::Semver200_version settings_version(EMSESP_APP_VERSION); - const std::string latest_version = json["version"] | EMSESP_APP_VERSION; version::Semver200_version this_version(latest_version); #if defined(EMSESP_DEBUG) @@ -161,16 +203,40 @@ void WebStatusService::checkUpgrade(AsyncWebServerRequest * request, JsonVariant root["upgradeable"] = (this_version > settings_version); - response->setLength(); - request->send(response); + return true; } -// returns data for a specific feature/settings as a json object -void WebStatusService::exportData(AsyncWebServerRequest * request) { - auto * response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); +// action = allvalues +// output all the devices and the values +void WebStatusService::allvalues(JsonObject output) { + JsonObject device_output; + auto value = F_(values); - String type = request->getParam("type")->value(); + // EMS-Device Entities + for (const auto & emsdevice : EMSESP::emsdevices) { + std::string title = emsdevice->device_type_2_device_name_translated() + std::string(" ") + emsdevice->to_string(); + device_output = output[title].to(); + emsdevice->get_value_info(device_output, value, DeviceValueTAG::TAG_NONE); + } + + // Custom Entities + device_output = output["Custom Entities"].to(); + EMSESP::webCustomEntityService.get_value_info(device_output, value); + + // Scheduler + device_output = output["Scheduler"].to(); + EMSESP::webSchedulerService.get_value_info(device_output, value); + + // Sensors + device_output = output["Analog Sensors"].to(); + EMSESP::analogsensor_.get_value_info(device_output, value); + device_output = output["Temperature Sensors"].to(); + EMSESP::temperaturesensor_.get_value_info(device_output, value); +} + +// action = export +// returns data for a specific feature/settings as a json object +bool WebStatusService::exportData(JsonObject root, std::string & type) { root["type"] = type; if (type == "settings") { @@ -188,13 +254,44 @@ void WebStatusService::exportData(AsyncWebServerRequest * request) { System::extractSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", root); } else if (type == "entities") { System::extractSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", root); + } else if (type == "allvalues") { + root.clear(); // don't need the "type" key + allvalues(root); } else { - request->send(400); - return; + return false; + } + return true; +} + +// action = customSupport +// reads any upload customSupport.json file and sends to to Help page to be shown as Guest +bool WebStatusService::customSupport(JsonObject root) { +#ifndef EMSESP_STANDALONE + // check if we have custom support file uploaded + File file = LittleFS.open(EMSESP_CUSTOMSUPPORT_FILE, "r"); + if (!file) { + // there is no custom file, return empty object + return true; } - response->setLength(); - request->send(response); + // read the contents of the file into the root output json object + DeserializationError error = deserializeJson(root, file); + if (error) { + emsesp::EMSESP::logger().err("Failed to read custom support file"); + return false; + } + + file.close(); +#endif + return true; +} + +// action = uploadURL +// uploads a firmware file from a URL +bool WebStatusService::uploadURL(const char * url) { + // this will keep a copy of the URL, but won't initiate the download yet + emsesp::EMSESP::system_.uploadFirmwareURL(url); + return true; } } // namespace emsesp diff --git a/src/web/WebStatusService.h b/src/web/WebStatusService.h index d4c4806b7..703464589 100644 --- a/src/web/WebStatusService.h +++ b/src/web/WebStatusService.h @@ -2,8 +2,7 @@ #define WebStatusService_h #define EMSESP_SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus" -#define EMSESP_CHECK_UPGRADE_PATH "/rest/checkUpgrade" -#define EMSESP_EXPORT_DATA_SERVICE_PATH "/rest/exportData" +#define EMSESP_ACTION_SERVICE_PATH "/rest/action" #include // for version checking @@ -13,10 +12,23 @@ class WebStatusService { public: WebStatusService(AsyncWebServer * server, SecurityManager * securityManager); - private: +// make action function public so we can test in the debug and standalone mode +#ifndef EMSESP_STANDALONE + protected: +#endif void systemStatus(AsyncWebServerRequest * request); - void checkUpgrade(AsyncWebServerRequest * request, JsonVariant json); - void exportData(AsyncWebServerRequest * request); + void action(AsyncWebServerRequest * request, JsonVariant json); + + private: + SecurityManager * _securityManager; + + // actions + bool checkUpgrade(JsonObject root, std::string & latest_version); + bool exportData(JsonObject root, std::string & type); + bool customSupport(JsonObject root); + bool uploadURL(const char * url); + + void allvalues(JsonObject output); }; } // namespace emsesp diff --git a/src/modbus_test.h b/test/test_modbus/modbus_test.h similarity index 100% rename from src/modbus_test.h rename to test/test_modbus/modbus_test.h