URL updates

This commit is contained in:
proddy
2024-09-28 21:01:37 +02:00
parent f096c1b632
commit a2e41d6d1e
16 changed files with 277 additions and 177 deletions

View File

@@ -27,7 +27,7 @@
"@mui/icons-material": "^6.1.1", "@mui/icons-material": "^6.1.1",
"@mui/material": "^6.1.1", "@mui/material": "^6.1.1",
"@table-library/react-table-library": "4.1.7", "@table-library/react-table-library": "4.1.7",
"alova": "3.0.16", "alova": "3.0.17",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
@@ -47,8 +47,8 @@
"@preact/preset-vite": "^2.9.1", "@preact/preset-vite": "^2.9.1",
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/formidable": "^3", "@types/formidable": "^3",
"@types/node": "^22.7.3", "@types/node": "^22.7.4",
"@types/react": "^18.3.9", "@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
@@ -57,7 +57,7 @@
"formidable": "^3.5.1", "formidable": "^3.5.1",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.34.0", "terser": "^5.34.1",
"typescript-eslint": "8.7.0", "typescript-eslint": "8.7.0",
"vite": "^5.4.8", "vite": "^5.4.8",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",

View File

@@ -2,6 +2,7 @@ import { alovaInstance } from 'api/endpoints';
import type { import type {
APIcall, APIcall,
Action,
Activity, Activity,
CoreData, CoreData,
DeviceData, DeviceData,
@@ -52,9 +53,9 @@ export const readActivity = () => alovaInstance.Get<Activity>('/rest/activity');
// API // API
export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall); export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall);
// DownloadUpload // Generic action
export const exportData = (type: string) => export const callAction = (action: Action) =>
alovaInstance.Get('/rest/exportData', { params: { type } }); alovaInstance.Post('/rest/action', action);
// SettingsCustomization // SettingsCustomization
export const readDeviceEntities = (id: number) => export const readDeviceEntities = (id: number) =>
@@ -118,7 +119,7 @@ export const writeModules = (data: {
license: string; license: string;
}) => alovaInstance.Post('/rest/modules', data); }) => alovaInstance.Post('/rest/modules', data);
// SettingsEntities // CustomEntities
export const readCustomEntities = () => export const readCustomEntities = () =>
alovaInstance.Get<EntityItem[]>('/rest/customEntities', { alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
transform(data) { transform(data) {

View File

@@ -1,4 +1,4 @@
import { useContext } from 'react'; import { useContext, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import CommentIcon from '@mui/icons-material/CommentTwoTone'; import CommentIcon from '@mui/icons-material/CommentTwoTone';
@@ -16,18 +16,17 @@ import {
ListItemAvatar, ListItemAvatar,
ListItemButton, ListItemButton,
ListItemText, ListItemText,
Paper,
Stack, Stack,
Typography, Typography
styled
} from '@mui/material'; } from '@mui/material';
import { useRequest } from 'alova/client'; import { useRequest } from 'alova/client';
import { SectionContent, useLayoutTitle } from 'components'; import { SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication'; import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react'; 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'; import type { APIcall } from './types';
const Help = () => { const Help = () => {
@@ -36,33 +35,79 @@ const Help = () => {
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
const { send: sendAPI } = useRequest((data: APIcall) => API(data), { const [customSupportIMG, setCustomSupportIMG] = useState<string | null>(null);
immediate: false const [customSupportHTML, setCustomSupportHTML] = useState<string | null>(null);
}).onSuccess((event) => {
const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(
new Blob([JSON.stringify(event.data, null, 2)], {
type: 'text/plain'
})
);
anchor.download = useRequest(() => callAction({ action: 'customSupport' })).onSuccess(
'emsesp_' + event.args[0].device + '_' + event.args[0].entity + '.txt'; (event: { data: { img_url: string; html: string[] } }) => {
anchor.click(); if (event.data) {
URL.revokeObjectURL(anchor.href); setCustomSupportIMG(event.data.img_url);
toast.info(LL.DOWNLOAD_SUCCESSFUL()); setCustomSupportHTML(event.data.html.join('<br/>'));
}); }
}
);
const callAPI = async (device: string, cmd: string) => { const { send: sendExportAllValues } = useRequest(
await sendAPI({ device, cmd, id: 0 }).catch((error: Error) => { () => 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); toast.error(error.message);
}); });
};
return ( return (
<> <>
<SectionContent> <SectionContent>
{me.admin ? ( <Stack
padding={1}
mb={2}
direction="row"
divider={<Divider orientation="vertical" flexItem />}
sx={{
borderRadius: 3,
border: '2px solid grey',
justifyContent: 'space-evenly',
alignItems: 'center'
}}
>
<Typography variant="subtitle1">
{customSupportHTML ? (
<div dangerouslySetInnerHTML={{ __html: customSupportHTML }} />
) : (
LL.HELP_INFORMATION_5()
)}
</Typography>
<Box
component="img"
sx={{
maxHeight: 250
}}
src={
customSupportIMG
? customSupportIMG
: 'https://emsesp.org/_media/images/installer.jpeg'
}
/>
</Stack>
{me.admin && (
<List sx={{ borderRadius: 3, border: '2px solid grey' }}> <List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListItem> <ListItem>
<ListItemButton component="a" href="https://emsesp.org"> <ListItemButton component="a" href="https://emsesp.org">
@@ -100,31 +145,6 @@ const Help = () => {
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
</List> </List>
) : (
<Stack
spacing={1}
padding={1}
direction="row"
divider={<Divider orientation="vertical" flexItem />}
sx={{
borderRadius: 3,
border: '2px solid grey',
justifyContent: 'space-around',
alignItems: 'center'
}}
>
<Typography border="red" variant="subtitle1">
{LL.HELP_INFORMATION_5()}
</Typography>
<Box
padding={1}
component="img"
sx={{
maxHeight: { xs: 100, md: 250 }
}}
src="https://emsesp.org/_media/images/installer.jpeg"
/>
</Stack>
)} )}
<Box p={2} color="warning.main"> <Box p={2} color="warning.main">
@@ -135,7 +155,7 @@ const Help = () => {
startIcon={<DownloadIcon />} startIcon={<DownloadIcon />}
variant="outlined" variant="outlined"
color="primary" color="primary"
onClick={() => callAPI('system', 'info')} onClick={() => sendAPI({ device: 'system', cmd: 'info', id: 0 })}
> >
{LL.DOWNLOAD(1)}&nbsp;{LL.SUPPORT_INFORMATION(0)} {LL.DOWNLOAD(1)}&nbsp;{LL.SUPPORT_INFORMATION(0)}
</Button> </Button>
@@ -146,7 +166,7 @@ const Help = () => {
startIcon={<DownloadIcon />} startIcon={<DownloadIcon />}
variant="outlined" variant="outlined"
color="primary" color="primary"
onClick={() => callAPI('system', 'allvalues')} onClick={() => sendExportAllValues()}
> >
{LL.DOWNLOAD(1)}&nbsp;{LL.ALLVALUES()} {LL.DOWNLOAD(1)}&nbsp;{LL.ALLVALUES()}
</Button> </Button>

View File

@@ -265,6 +265,12 @@ export interface APIcall {
cmd: string; cmd: string;
id: number; id: number;
} }
export interface Action {
action: string;
param?: string; // optional
}
export interface WriteAnalogSensor { export interface WriteAnalogSensor {
id: number; id: number;
gpio: number; gpio: number;

View File

@@ -18,7 +18,7 @@ import {
import Grid from '@mui/material/Grid2'; import Grid from '@mui/material/Grid2';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { API, exportData } from 'api/app'; import { API, callAction } from 'api/app';
import { import {
checkUpgrade, checkUpgrade,
getDevVersion, getDevVersion,
@@ -37,6 +37,7 @@ import {
useLayoutTitle useLayoutTitle
} from 'components'; } from 'components';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import { saveFile } from 'utils/file';
const DownloadUpload = () => { const DownloadUpload = () => {
const { LL } = useI18nContext(); const { LL } = useI18nContext();
@@ -46,11 +47,23 @@ const DownloadUpload = () => {
const [useDev, setUseDev] = useState<boolean>(false); const [useDev, setUseDev] = useState<boolean>(false);
const [upgradeAvailable, setUpgradeAvailable] = useState<boolean>(false); const [upgradeAvailable, setUpgradeAvailable] = useState<boolean>(false);
const { send: sendExportData } = useRequest((type: string) => exportData(type), { const { send: sendCheckUpgrade } = useRequest(
immediate: false (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) => { .onSuccess((event) => {
saveFile(event.data, event.args[0]); saveFile(event.data, event.args[0], '.json');
toast.info(LL.DOWNLOAD_SUCCESSFUL()); toast.info(LL.DOWNLOAD_SUCCESSFUL());
}) })
.onError((error) => { .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 // called immediately to get the latest version, on page load
const { data: latestVersion } = useRequest(getStableVersion, { 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 // 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, // immediate: false,
// initialData: '3.7.0-dev.32' // initialData: '3.7.0-dev.32'
}).onSuccess((event) => { }).onSuccess((event) => {
void sendCheckUpgrade({ version: event.data }); void sendCheckUpgrade(event.data);
}); });
const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/'; const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/';
@@ -142,18 +149,6 @@ const DownloadUpload = () => {
setRestarting(true); 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()); useLayoutTitle(LL.DOWNLOAD_UPLOAD());
const internet_live = const internet_live =

View File

@@ -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);
};

View File

@@ -1446,12 +1446,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/node@npm:^22.7.3": "@types/node@npm:^22.7.4":
version: 22.7.3 version: 22.7.4
resolution: "@types/node@npm:22.7.3" resolution: "@types/node@npm:22.7.4"
dependencies: dependencies:
undici-types: "npm:~6.19.2" undici-types: "npm:~6.19.2"
checksum: 10c0/0e579813528b0370454337a952f43b792cd12731e10fdca0fdb627158e980c1219bba99e9048c134b6a19325d817016059afe016ccd372326c838a1b85a51574 checksum: 10c0/c22bf54515c78ff3170142c1e718b90e2a0003419dc2d55f79c9c9362edd590a6ab1450deb09ff6e1b32d1b4698da407930b16285e8be3a009ea6cd2695cac01
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1518,13 +1518,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react@npm:^18.3.9": "@types/react@npm:^18.3.10":
version: 18.3.9 version: 18.3.10
resolution: "@types/react@npm:18.3.9" resolution: "@types/react@npm:18.3.10"
dependencies: dependencies:
"@types/prop-types": "npm:*" "@types/prop-types": "npm:*"
csstype: "npm:^3.0.2" csstype: "npm:^3.0.2"
checksum: 10c0/a92b8e061d0c833e096254782c56a802316593f4a907fb834b557cabe848a0829b9eb6056404ea239eb4d5ec5ac7b7724309761516c0a7a277916fa04dd4f805 checksum: 10c0/f5be1de1b0331c1fdb33d577f4cf7f1b949d4bded5347b2351a537f03c51dade5be115e21b161dcf1b37061954d320f6a0bdf8d7b70e24eda51071fdd614383d
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1678,11 +1678,11 @@ __metadata:
"@table-library/react-table-library": "npm:4.1.7" "@table-library/react-table-library": "npm:4.1.7"
"@trivago/prettier-plugin-sort-imports": "npm:^4.3.0" "@trivago/prettier-plugin-sort-imports": "npm:^4.3.0"
"@types/formidable": "npm:^3" "@types/formidable": "npm:^3"
"@types/node": "npm:^22.7.3" "@types/node": "npm:^22.7.4"
"@types/react": "npm:^18.3.9" "@types/react": "npm:^18.3.10"
"@types/react-dom": "npm:^18.3.0" "@types/react-dom": "npm:^18.3.0"
"@types/react-router-dom": "npm:^5.3.3" "@types/react-router-dom": "npm:^5.3.3"
alova: "npm:3.0.16" alova: "npm:3.0.17"
async-validator: "npm:^4.2.5" async-validator: "npm:^4.2.5"
concurrently: "npm:^9.0.1" concurrently: "npm:^9.0.1"
eslint: "npm:^9.11.1" eslint: "npm:^9.11.1"
@@ -1698,7 +1698,7 @@ __metadata:
react-router-dom: "npm:^6.26.2" react-router-dom: "npm:^6.26.2"
react-toastify: "npm:^10.0.5" react-toastify: "npm:^10.0.5"
rollup-plugin-visualizer: "npm:^5.12.0" rollup-plugin-visualizer: "npm:^5.12.0"
terser: "npm:^5.34.0" terser: "npm:^5.34.1"
typesafe-i18n: "npm:^5.26.2" typesafe-i18n: "npm:^5.26.2"
typescript: "npm:^5.6.2" typescript: "npm:^5.6.2"
typescript-eslint: "npm:8.7.0" typescript-eslint: "npm:8.7.0"
@@ -1764,13 +1764,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"alova@npm:3.0.16": "alova@npm:3.0.17":
version: 3.0.16 version: 3.0.17
resolution: "alova@npm:3.0.16" resolution: "alova@npm:3.0.17"
dependencies: dependencies:
"@alova/shared": "npm:^1.0.5" "@alova/shared": "npm:^1.0.5"
rate-limiter-flexible: "npm:^5.0.3" rate-limiter-flexible: "npm:^5.0.3"
checksum: 10c0/66cb597f4f00feda04b7619dd852fde92bc920cc97b018be70791240c8e8c64677a998a02a684f3aace5997322236a677264f25afe6bcaf4ec856ae42be859a8 checksum: 10c0/e8a2ae885a3ff44dafec230d9388dc22b6445bb0cf8511fc9855b5a98ad9961941b0d33a7da874df23db4af0dba75872a470e3edebbdcc5ead8aecbc7fcc3d6b
languageName: node languageName: node
linkType: hard linkType: hard
@@ -6654,9 +6654,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"terser@npm:^5.34.0": "terser@npm:^5.34.1":
version: 5.34.0 version: 5.34.1
resolution: "terser@npm:5.34.0" resolution: "terser@npm:5.34.1"
dependencies: dependencies:
"@jridgewell/source-map": "npm:^0.3.3" "@jridgewell/source-map": "npm:^0.3.3"
acorn: "npm:^8.8.2" acorn: "npm:^8.8.2"
@@ -6664,7 +6664,7 @@ __metadata:
source-map-support: "npm:~0.5.20" source-map-support: "npm:~0.5.20"
bin: bin:
terser: bin/terser terser: bin/terser
checksum: 10c0/74e8ef4e565e5600415cd9377a90eed419b8076465d453c0c76aef4053c45371512d2de76c34d01e004cdd49ea5a749d77eeb343f7e665b2d172158ca08ba23e checksum: 10c0/51c7d704c5c4ae88bf937124112c9972aed4e1fd29d805cc2d86e0f54cd631ecd4e69db5bb3c1e3b450c741c86e2313328bea0fde925329e8a31a07a7941723c
languageName: node languageName: node
linkType: hard linkType: hard

View File

@@ -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:',
'',
'<b>Bosch Installer Example</b>',
'Nefit Road 12',
'1234 AB Amsterdam',
'Phone: +31 123 456 789',
'email: support@boschinstaller.nl',
'',
"For help and questions please <a target='_blank' href='https://emsesp.org'>contact</a> 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 // START DATA
// LOG // 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_ENDPOINT1 = REST_ENDPOINT_ROOT + 'deviceEntities';
const EMSESP_DEVICEENTITIES_ENDPOINT2 = REST_ENDPOINT_ROOT + 'deviceEntities/:id?'; 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_BOARDPROFILE_ENDPOINT = REST_ENDPOINT_ROOT + 'boardProfile';
const EMSESP_WRITE_DEVICEVALUE_ENDPOINT = REST_ENDPOINT_ROOT + 'writeDeviceValue'; const EMSESP_WRITE_DEVICEVALUE_ENDPOINT = REST_ENDPOINT_ROOT + 'writeDeviceValue';
const EMSESP_WRITE_DEVICENAME_ENDPOINT = REST_ENDPOINT_ROOT + 'writeDeviceName'; 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_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'schedule';
const EMSESP_CUSTOMENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'customEntities'; const EMSESP_CUSTOMENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'customEntities';
const EMSESP_MODULES_ENDPOINT = REST_ENDPOINT_ROOT + 'modules'; 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 // these are used in the API calls only
const EMSESP_SYSTEM_INFO_ENDPOINT = API_ENDPOINT_ROOT + 'system/info'; const EMSESP_SYSTEM_INFO_ENDPOINT = API_ENDPOINT_ROOT + 'system/info';
@@ -4081,16 +4130,10 @@ router
router router
.get(ACTIVITY_ENDPOINT, () => activity) .get(ACTIVITY_ENDPOINT, () => activity)
.get(SYSTEM_STATUS_ENDPOINT, () => { .get(SYSTEM_STATUS_ENDPOINT, () => {
if (countHardwarePoll === 0) {
console.log('Resetting hardware count...');
}
if (countHardwarePoll >= 2) { if (countHardwarePoll >= 2) {
countHardwarePoll = 0; countHardwarePoll = 0;
system_status.status = 'ready'; system_status.status = 'ready';
} }
console.log('Hardware count ' + countHardwarePoll + ' of 2');
countHardwarePoll++; countHardwarePoll++;
return system_status; return system_status;
@@ -4402,17 +4445,6 @@ router
return status(200); 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 // Settings - board profile
.get(EMSESP_BOARDPROFILE_ENDPOINT, (request) => { .get(EMSESP_BOARDPROFILE_ENDPOINT, (request) => {
const board_profile = request.query.boardProfile; const board_profile = request.query.boardProfile;
@@ -4549,32 +4581,32 @@ router
return data; 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 // upload URL
.post('/rest/uploadURL', () => { .post('/rest/uploadURL', () => {
console.log('upload File from URL'); console.log('upload File from URL');
return status(200); 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 // API which are usually POST for security
.post(EMSESP_SYSTEM_INFO_ENDPOINT, () => emsesp_info) .post(EMSESP_SYSTEM_INFO_ENDPOINT, () => emsesp_info)
.get(EMSESP_SYSTEM_INFO_ENDPOINT, () => emsesp_info) .get(EMSESP_SYSTEM_INFO_ENDPOINT, () => emsesp_info)
@@ -4594,9 +4626,6 @@ router
if (cmd === 'info') { if (cmd === 'info') {
return emsesp_info; return emsesp_info;
} }
if (cmd === 'allvalues') {
return emsesp_allvalues;
}
if (cmd === 'format') { if (cmd === 'format') {
console.log('formatting...'); console.log('formatting...');
return status(200); return status(200);

View File

@@ -39,7 +39,7 @@ unbuild_flags =
${common.core_unbuild_flags} ${common.core_unbuild_flags}
[espressif32_base] [espressif32_base]
platform = espressif32@6.8.1 platform = espressif32@6.9.0
framework = arduino framework = arduino
board_build.filesystem = littlefs board_build.filesystem = littlefs
build_flags = build_flags =

View File

@@ -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(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(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(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(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 MAKE_WORD_TRANSLATION(values_cmd, "list all values", "Liste alle Werte auf", "", "", "", "", "", "", "", "vypísať všetky hodnoty") // TODO translate

View File

@@ -113,6 +113,7 @@ bool System::command_response(const char * value, const int8_t id, JsonObject ou
// output all the devices and the values // output all the devices and the values
// not system info // not system info
// TODO remove?
bool System::command_allvalues(const char * value, const int8_t id, JsonObject output) { bool System::command_allvalues(const char * value, const int8_t id, JsonObject output) {
JsonDocument doc; JsonDocument doc;
JsonObject device_output; JsonObject device_output;
@@ -874,7 +875,7 @@ void System::commands_init() {
// these commands will return data in JSON format // 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("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 "ems-esp/system/#"
Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback 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 // see if there is a restore of an older settings file that needs to be applied
bool System::check_restore() { bool System::check_restore() {
bool reboot_required = false; bool reboot_required = false; // true if we need to reboot
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
File new_file = LittleFS.open(TEMP_FILENAME_PATH); File new_file = LittleFS.open(TEMP_FILENAME_PATH);
@@ -1167,11 +1168,20 @@ bool System::check_restore() {
} else if (settings_type == "entities") { } else if (settings_type == "entities") {
// it's a entity file, just replace it and there's no need to reboot // it's a entity file, just replace it and there's no need to reboot
saveSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", input); 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 { } else {
LOG_ERROR("Unrecognized file uploaded"); LOG_ERROR("Unrecognized file uploaded");
} }
} else { } 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 // close (just in case) and remove the temp file

View File

@@ -43,6 +43,8 @@ using uuid::console::Shell;
#define EMSESP_FS_CONFIG_DIRECTORY "/config" #define EMSESP_FS_CONFIG_DIRECTORY "/config"
#define EMSESP_CUSTOMSUPPORT_FILE "/config/customSupport.json"
namespace emsesp { namespace emsesp {
enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110 }; 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_message(const char * value, const int8_t id);
static bool command_info(const char * value, const int8_t id, JsonObject output); 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_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 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); static void get_value_json(JsonObject output, const std::string & circuit, const std::string & name, JsonVariant val);

View File

@@ -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 devices");
// shell.invoke_command("show values"); // shell.invoke_command("show values");
// shell.invoke_command("call system allvalues");
// shell.invoke_command("call system publish"); // shell.invoke_command("call system publish");
// shell.invoke_command("show mqtt"); // shell.invoke_command("show mqtt");
ok = true; 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("call temperaturesensor");
shell.invoke_command("show values"); shell.invoke_command("show values");
shell.invoke_command("call system allvalues");
shell.invoke_command("call temperaturesensor info"); shell.invoke_command("call temperaturesensor info");
shell.invoke_command("call temperaturesensor values"); 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("call analogsensor");
shell.invoke_command("show values"); shell.invoke_command("show values");
shell.invoke_command("call system allvalues");
shell.invoke_command("call analogsensor info"); shell.invoke_command("call analogsensor info");
shell.invoke_command("call analogsensor values"); shell.invoke_command("call analogsensor values");

View File

@@ -463,7 +463,6 @@ uint8_t WebCustomEntityService::count_entities() {
uint8_t count = 0; uint8_t count = 0;
for (const CustomEntityItem & entity : *customEntityItems_) { for (const CustomEntityItem & entity : *customEntityItems_) {
render_value(output, entity); render_value(output, entity);
// TODO check JsonVariant
if (output[entity.name].is<JsonVariantConst>() || entity.writeable) { if (output[entity.name].is<JsonVariantConst>() || entity.writeable) {
count++; count++;
} }

View File

@@ -27,10 +27,9 @@ namespace emsesp {
WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager) { WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager) {
// GET // GET
server->on(EMSESP_SYSTEM_STATUS_SERVICE_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { systemStatus(request); }); 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 // generic action - POST
server->on(EMSESP_CHECK_UPGRADE_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { checkUpgrade(request, json); }); server->on(EMSESP_ACTION_SERVICE_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { action(request, json); });
} }
// /rest/systemStatus // /rest/systemStatus
@@ -146,13 +145,42 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
request->send(response); request->send(response);
} }
// returns trues if there is an upgrade available // generic action handler - as a POST
void WebStatusService::checkUpgrade(AsyncWebServerRequest * request, JsonVariant json) { void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json) {
auto * response = new AsyncJsonResponse(); auto * response = new AsyncJsonResponse();
JsonObject root = response->getRoot(); 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); 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); version::Semver200_version this_version(latest_version);
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
@@ -161,16 +189,11 @@ void WebStatusService::checkUpgrade(AsyncWebServerRequest * request, JsonVariant
root["upgradeable"] = (this_version > settings_version); root["upgradeable"] = (this_version > settings_version);
response->setLength(); return true;
request->send(response);
} }
// returns data for a specific feature/settings as a json object // returns data for a specific feature/settings as a json object
void WebStatusService::exportData(AsyncWebServerRequest * request) { bool WebStatusService::exportData(JsonObject root, std::string & type) {
auto * response = new AsyncJsonResponse();
JsonObject root = response->getRoot();
String type = request->getParam("type")->value();
root["type"] = type; root["type"] = type;
if (type == "settings") { if (type == "settings") {
@@ -189,12 +212,15 @@ void WebStatusService::exportData(AsyncWebServerRequest * request) {
} else if (type == "entities") { } else if (type == "entities") {
System::extractSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", root); System::extractSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", root);
} else { } else {
request->send(400); return false;
return;
} }
return true;
}
response->setLength(); // custom support
request->send(response); bool WebStatusService::customSupport(JsonObject root) {
root["custom_support"] = true;
return true;
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -2,8 +2,8 @@
#define WebStatusService_h #define WebStatusService_h
#define EMSESP_SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus" #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 <semver200.h> // for version checking #include <semver200.h> // for version checking
@@ -13,10 +13,14 @@ class WebStatusService {
public: public:
WebStatusService(AsyncWebServer * server, SecurityManager * securityManager); WebStatusService(AsyncWebServer * server, SecurityManager * securityManager);
private: protected:
void systemStatus(AsyncWebServerRequest * request); void systemStatus(AsyncWebServerRequest * request);
void checkUpgrade(AsyncWebServerRequest * request, JsonVariant json); void action(AsyncWebServerRequest * request, JsonVariant json);
void exportData(AsyncWebServerRequest * request);
private:
bool checkUpgrade(JsonObject root, std::string & latest_version);
bool exportData(JsonObject root, std::string & type);
bool customSupport(JsonObject root);
}; };
} // namespace emsesp } // namespace emsesp