From a2e41d6d1e2dbe4e416b2465457aa7bc98a17b51 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 28 Sep 2024 21:01:37 +0200 Subject: [PATCH] URL updates --- interface/package.json | 8 +- interface/src/api/app.ts | 9 +- interface/src/app/main/Help.tsx | 122 ++++++++++-------- interface/src/app/main/types.ts | 6 + interface/src/app/settings/DownloadUpload.tsx | 43 +++--- interface/src/utils/file.ts | 11 ++ interface/yarn.lock | 40 +++--- mock-api/rest_server.ts | 113 ++++++++++------ platformio.ini | 2 +- src/locale_translations.h | 2 +- src/system.cpp | 16 ++- src/system.h | 4 +- src/test/test.cpp | 3 - src/web/WebCustomEntityService.cpp | 1 - src/web/WebStatusService.cpp | 60 ++++++--- src/web/WebStatusService.h | 14 +- 16 files changed, 277 insertions(+), 177 deletions(-) create mode 100644 interface/src/utils/file.ts diff --git a/interface/package.json b/interface/package.json index 508e1ac10..b331a4168 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,7 +57,7 @@ "formidable": "^3.5.1", "prettier": "^3.3.3", "rollup-plugin-visualizer": "^5.12.0", - "terser": "^5.34.0", + "terser": "^5.34.1", "typescript-eslint": "8.7.0", "vite": "^5.4.8", "vite-plugin-imagemin": "^0.6.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/app/main/Help.tsx b/interface/src/app/main/Help.tsx index 32b410f04..e8bd18d22 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,33 +35,79 @@ 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: { data: { img_url: string; html: string[] } }) => { + if (event.data) { + setCustomSupportIMG(event.data.img_url); + setCustomSupportHTML(event.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 && ( @@ -100,31 +145,6 @@ const Help = () => { - ) : ( - } - sx={{ - borderRadius: 3, - border: '2px solid grey', - justifyContent: 'space-around', - alignItems: 'center' - }} - > - - {LL.HELP_INFORMATION_5()} - - - )} @@ -135,7 +155,7 @@ const Help = () => { startIcon={} variant="outlined" color="primary" - onClick={() => callAPI('system', 'info')} + onClick={() => sendAPI({ device: 'system', cmd: 'info', id: 0 })} > {LL.DOWNLOAD(1)} {LL.SUPPORT_INFORMATION(0)} @@ -146,7 +166,7 @@ const Help = () => { startIcon={} variant="outlined" color="primary" - onClick={() => callAPI('system', 'allvalues')} + onClick={() => sendExportAllValues()} > {LL.DOWNLOAD(1)} {LL.ALLVALUES()} 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..dffb7f81b 100644 --- a/interface/src/app/settings/DownloadUpload.tsx +++ b/interface/src/app/settings/DownloadUpload.tsx @@ -18,7 +18,7 @@ import { import Grid from '@mui/material/Grid2'; import * as SystemApi from 'api/system'; -import { API, exportData } from 'api/app'; +import { API, callAction } from 'api/app'; import { checkUpgrade, getDevVersion, @@ -37,6 +37,7 @@ import { useLayoutTitle } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; +import { saveFile } from 'utils/file'; const DownloadUpload = () => { const { LL } = useI18nContext(); @@ -46,11 +47,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) => { @@ -83,12 +96,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 +109,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/'; @@ -142,18 +149,6 @@ const DownloadUpload = () => { 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/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..398f61769 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 @@ -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,7 +1698,7 @@ __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" @@ -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 diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index 5b3b2476d..c2e449bd4 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -117,6 +117,55 @@ 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 { + 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' + }; +} + +// 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 +451,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 +464,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 +4130,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 +4445,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,32 +4581,32 @@ 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); - } - }) - // upload URL .post('/rest/uploadURL', () => { console.log('upload File from URL'); return status(200); }) + // 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); + } + } + return status(404); // cmd not found + }) + // API which are usually POST for security .post(EMSESP_SYSTEM_INFO_ENDPOINT, () => emsesp_info) .get(EMSESP_SYSTEM_INFO_ENDPOINT, () => emsesp_info) @@ -4594,9 +4626,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..604d2e2e5 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 = diff --git a/src/locale_translations.h b/src/locale_translations.h index 35a88d45c..b48f469a2 100644 --- a/src/locale_translations.h +++ b/src/locale_translations.h @@ -75,7 +75,7 @@ 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(allvalues_cmd, "output all values in system", "Alle Werte im System ausgeben", "", "", "wyświetl wszystkie wartości", "", "", "", "", "vypísať všetky hodnoty") // TODO remove 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/system.cpp b/src/system.cpp index f796514d8..d5478b512 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -113,6 +113,7 @@ bool System::command_response(const char * value, const int8_t id, JsonObject ou // output all the devices and the values // not system info +// TODO remove? bool System::command_allvalues(const char * value, const int8_t id, JsonObject output) { JsonDocument doc; JsonObject device_output; @@ -874,7 +875,7 @@ 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)); + Command::add(EMSdevice::DeviceType::SYSTEM, F("allvalues"), System::command_allvalues, FL_(allvalues_cmd)); // TODO remove // MQTT subscribe "ems-esp/system/#" Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback @@ -1139,7 +1140,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 +1168,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 == "custom_support") { + // it's a custom support file - save it to /config + new_file.close(); + if (LittleFS.rename(TEMP_FILENAME_PATH, EMSESP_CUSTOMSUPPORT_FILE)) { + LOG_INFO("Custom support information loaded"); + 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 diff --git a/src/system.h b/src/system.h index 2e980a22b..22b89be5a 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 }; @@ -63,7 +65,7 @@ class System { 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 command_allvalues(const char * value, const int8_t id, JsonObject output); // TODO fix 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); diff --git a/src/test/test.cpp b/src/test/test.cpp index c81d98b96..60f7b9ebb 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"); diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index 0a0a56061..a26285dc8 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -463,7 +463,6 @@ uint8_t WebCustomEntityService::count_entities() { uint8_t count = 0; for (const CustomEntityItem & entity : *customEntityItems_) { render_value(output, entity); - // TODO check JsonVariant if (output[entity.name].is() || entity.writeable) { count++; } diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index 8ea907161..417a59e02 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -27,10 +27,9 @@ namespace emsesp { WebStatusService::WebStatusService(AsyncWebServer * server, 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); }); + // generic action - POST + server->on(EMSESP_ACTION_SERVICE_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { action(request, json); }); } // /rest/systemStatus @@ -146,13 +145,42 @@ 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 optional param + std::string action = json["action"]; + std::string param = json["param"]; // is optional + + // TODO remove + Serial.printf("Action: %s\n", action.c_str()); + Serial.printf("Param: %s\n", param.c_str()); + + 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); + } + + // send response + if (!ok) { + request->send(400); + return; + } + + response->setLength(); + request->send(response); +} + + +// 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 +189,11 @@ 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(); - - String type = request->getParam("type")->value(); +bool WebStatusService::exportData(JsonObject root, std::string & type) { root["type"] = type; if (type == "settings") { @@ -189,12 +212,15 @@ void WebStatusService::exportData(AsyncWebServerRequest * request) { } else if (type == "entities") { System::extractSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", root); } else { - request->send(400); - return; + return false; } + return true; +} - response->setLength(); - request->send(response); +// custom support +bool WebStatusService::customSupport(JsonObject root) { + root["custom_support"] = true; + return true; } } // namespace emsesp diff --git a/src/web/WebStatusService.h b/src/web/WebStatusService.h index d4c4806b7..3e04b2ce0 100644 --- a/src/web/WebStatusService.h +++ b/src/web/WebStatusService.h @@ -2,8 +2,8 @@ #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 +13,14 @@ class WebStatusService { public: WebStatusService(AsyncWebServer * server, SecurityManager * securityManager); - private: + protected: void systemStatus(AsyncWebServerRequest * request); - void checkUpgrade(AsyncWebServerRequest * request, JsonVariant json); - void exportData(AsyncWebServerRequest * request); + void action(AsyncWebServerRequest * request, JsonVariant json); + + private: + bool checkUpgrade(JsonObject root, std::string & latest_version); + bool exportData(JsonObject root, std::string & type); + bool customSupport(JsonObject root); }; } // namespace emsesp