Merge pull request #2066 from proddy/dev

custom support page, action endpoint
This commit is contained in:
Proddy
2024-10-02 13:31:22 +02:00
committed by GitHub
46 changed files with 677 additions and 566 deletions

3
.gitignore vendored
View File

@@ -68,3 +68,6 @@ venv/
# cspell # cspell
words-found-verbose.txt words-found-verbose.txt
# sonarlint
compile_commands.json

View File

@@ -0,0 +1,4 @@
{
"sonarCloudOrganization": "emsesp",
"projectKey": "emsesp_EMS-ESP32"
}

View File

@@ -96,5 +96,6 @@
"sonarlint.connectedMode.project": { "sonarlint.connectedMode.project": {
"connectionId": "emsesp", "connectionId": "emsesp",
"projectKey": "emsesp_EMS-ESP32" "projectKey": "emsesp_EMS-ESP32"
} },
"sonarlint.pathToCompileCommands": "${workspaceFolder}/compile_commands.json"
} }

View File

@@ -40,8 +40,9 @@ For more details go to [www.emsesp.org](https://www.emsesp.org/).
- autodetect and download firmware upgrades via the WebUI - autodetect and download firmware upgrades via the WebUI
- command 'show log' that lists out the current weblog buffer, showing last messages. - 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 - 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 - auto Scroll in WebLog UI - reduced delay so incoming logs are faster
- uploading custom support info for Guest users [#2054]<https://github.com/emsesp/EMS-ESP32/issues/2054>
## Fixed ## 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 - WebLog UI matches color schema of the terminal console correctly
- Updated Web libraries, ArduinoJson - 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) - 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 - Show ems-esp internal devices in device list of system/info
- Scheduler and mqtt run async on systems with psram - Scheduler and mqtt run async on systems with psram
- Show IPv6 address type (local/global/ula) in log - Show IPv6 address type (local/global/ula) in log

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,8 +57,8 @@
"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.8.0",
"vite": "^5.4.8", "vite": "^5.4.8",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^5.0.1" "vite-tsconfig-paths": "^5.0.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

@@ -13,7 +13,7 @@ export const updateLogSettings = (data: LogSettings) =>
alovaInstance.Post('/rest/logSettings', data); alovaInstance.Post('/rest/logSettings', data);
export const fetchLogES = () => alovaInstance.Get('/es/log'); export const fetchLogES = () => alovaInstance.Get('/es/log');
// Get versions from github // Get versions from GitHub
export const getStableVersion = () => export const getStableVersion = () =>
alovaInstanceGH.Get('latest', { alovaInstanceGH.Get('latest', {
transform(response: { data: { name: string } }) { transform(response: { data: { name: string } }) {
@@ -34,9 +34,3 @@ export const uploadFile = (file: File) => {
timeout: 60000 // override timeout for uploading firmware - 1 minute 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);

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,131 +35,150 @@ 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((event) => {
'emsesp_' + event.args[0].device + '_' + event.args[0].entity + '.txt'; if (event && event.data && Object.keys(event.data).length !== 0) {
anchor.click(); const data = event.data.Support;
URL.revokeObjectURL(anchor.href); if (data.img_url) {
toast.info(LL.DOWNLOAD_SUCCESSFUL()); setCustomSupportIMG(data.img_url);
}
if (data.html) {
setCustomSupportHTML(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> <Stack
{me.admin ? ( padding={1}
<List sx={{ borderRadius: 3, border: '2px solid grey' }}> mb={2}
<ListItem> direction="row"
<ListItemButton component="a" href="https://emsesp.org"> divider={<Divider orientation="vertical" flexItem />}
<ListItemAvatar> sx={{
<Avatar sx={{ bgcolor: '#72caf9' }}> borderRadius: 3,
<MenuBookIcon /> border: '2px solid grey',
</Avatar> justifyContent: 'space-evenly',
</ListItemAvatar> alignItems: 'center'
<ListItemText primary={LL.HELP_INFORMATION_1()} /> }}
</ListItemButton> >
</ListItem> <Typography variant="subtitle1">
{customSupportHTML ? (
<div dangerouslySetInnerHTML={{ __html: customSupportHTML }} />
) : (
LL.HELP_INFORMATION_5()
)}
</Typography>
<Box
component="img"
referrerPolicy="no-referrer"
sx={{
maxHeight: { xs: 100, md: 250 }
}}
src={customSupportIMG || 'https://emsesp.org/_media/images/installer.jpeg'}
/>
</Stack>
<ListItem> {me.admin && (
<ListItemButton component="a" href="https://discord.gg/3J3GgnzpyT"> <List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListItemAvatar> <ListItem>
<Avatar sx={{ bgcolor: '#72caf9' }}> <ListItemButton component="a" href="https://emsesp.org">
<CommentIcon /> <ListItemAvatar>
</Avatar> <Avatar sx={{ bgcolor: '#72caf9' }}>
</ListItemAvatar> <MenuBookIcon />
<ListItemText primary={LL.HELP_INFORMATION_2()} /> </Avatar>
</ListItemButton> </ListItemAvatar>
</ListItem> <ListItemText primary={LL.HELP_INFORMATION_1()} />
</ListItemButton>
</ListItem>
<ListItem> <ListItem>
<ListItemButton <ListItemButton component="a" href="https://discord.gg/3J3GgnzpyT">
component="a" <ListItemAvatar>
href="https://github.com/emsesp/EMS-ESP32/issues/new/choose" <Avatar sx={{ bgcolor: '#72caf9' }}>
> <CommentIcon />
<ListItemAvatar> </Avatar>
<Avatar sx={{ bgcolor: '#72caf9' }}> </ListItemAvatar>
<GitHubIcon /> <ListItemText primary={LL.HELP_INFORMATION_2()} />
</Avatar> </ListItemButton>
</ListItemAvatar> </ListItem>
<ListItemText primary={LL.HELP_INFORMATION_3()} />
</ListItemButton>
</ListItem>
</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"> <ListItem>
<Typography mb={1} variant="body2"> <ListItemButton
{LL.HELP_INFORMATION_4()} component="a"
</Typography> href="https://github.com/emsesp/EMS-ESP32/issues/new/choose"
<Button >
startIcon={<DownloadIcon />} <ListItemAvatar>
variant="outlined" <Avatar sx={{ bgcolor: '#72caf9' }}>
color="primary" <GitHubIcon />
onClick={() => callAPI('system', 'info')} </Avatar>
> </ListItemAvatar>
{LL.DOWNLOAD(1)}&nbsp;{LL.SUPPORT_INFORMATION(0)} <ListItemText primary={LL.HELP_INFORMATION_3()} />
</Button> </ListItemButton>
</Box> </ListItem>
</List>
)}
<Box p={2} color="warning.main">
<Typography mb={1} variant="body2">
{LL.HELP_INFORMATION_4()}
</Typography>
<Button <Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />} startIcon={<DownloadIcon />}
variant="outlined" variant="outlined"
color="primary" color="primary"
onClick={() => callAPI('system', 'allvalues')} onClick={() => sendAPI({ device: 'system', cmd: 'info', id: 0 })}
> >
{LL.DOWNLOAD(1)}&nbsp;{LL.ALLVALUES()} {LL.DOWNLOAD(1)}&nbsp;{LL.SUPPORT_INFORMATION(0)}
</Button> </Button>
</Box>
<Divider sx={{ mt: 4 }} /> <Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => sendExportAllValues()}
>
{LL.DOWNLOAD(1)}&nbsp;{LL.ALLVALUES()}
</Button>
<Typography color="white" variant="subtitle1" align="center" mt={1}> <Divider sx={{ mt: 4 }} />
&copy;&nbsp;
<Link target="_blank" href="https://emsesp.org" color="primary"> <Typography color="white" variant="subtitle1" align="center" mt={1}>
{'emsesp.org'} &copy;&nbsp;
</Link> <Link target="_blank" href="https://emsesp.org" color="primary">
</Typography> {'emsesp.org'}
</SectionContent> </Link>
</> </Typography>
</SectionContent>
); );
}; };

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,13 +18,8 @@ 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 { getDevVersion, getStableVersion } from 'api/system';
checkUpgrade,
getDevVersion,
getStableVersion,
uploadURL
} from 'api/system';
import { dialogStyle } from 'CustomTheme'; import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova/client'; import { useRequest } from 'alova/client';
@@ -37,6 +32,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 +42,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) => {
@@ -61,14 +69,10 @@ const DownloadUpload = () => {
immediate: false immediate: false
}); });
const { const { data, send: loadData, error } = useRequest(SystemApi.readSystemStatus);
data: data,
send: loadData,
error
} = useRequest(SystemApi.readSystemStatus);
const { send: sendUploadURL } = useRequest( const { send: sendUploadURL } = useRequest(
(data: { url: string }) => uploadURL(data), (url: string) => callAction({ action: 'uploadURL', param: url }),
{ {
immediate: false 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 // 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 +100,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/';
@@ -136,24 +134,12 @@ const DownloadUpload = () => {
}; };
const installFirmwareURL = async (url: string) => { const installFirmwareURL = async (url: string) => {
await sendUploadURL({ url: url }).catch((error: Error) => { await sendUploadURL(url).catch((error: Error) => {
toast.error(error.message); toast.error(error.message);
}); });
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

@@ -45,7 +45,7 @@ const RestartMonitor = () => {
return ( return (
<Dialog fullWidth={true} sx={dialogStyle} open={true}> <Dialog fullWidth={true} sx={dialogStyle} open={true}>
<DialogContent dividers> <DialogContent dividers>
<Box m={2} py={2} display="flex" alignItems="center" flexDirection="column"> <Box m={0} py={0} display="flex" alignItems="center" flexDirection="column">
<Typography <Typography
color="secondary" color="secondary"
variant="h6" variant="h6"
@@ -69,7 +69,7 @@ const RestartMonitor = () => {
<MessageBox my={2} level="error" message={errorMessage} /> <MessageBox my={2} level="error" message={errorMessage} />
) : ( ) : (
<Box py={2}> <Box py={2}>
<CircularProgress size={48} /> <CircularProgress size={32} />
</Box> </Box>
)} )}
</Box> </Box>

View File

@@ -41,6 +41,7 @@ const LayoutAppBar = ({ title, onToggleDrawer }: LayoutAppBarProps) => {
{show_back && ( {show_back && (
<IconButton <IconButton
sx={{ mr: 1 }}
color="inherit" color="inherit"
edge="start" edge="start"
onClick={() => navigate(pathnames[0])} onClick={() => navigate(pathnames[0])}

View File

@@ -12,6 +12,7 @@ import './dragNdrop.css';
const DragNdrop = ({ onFileSelected }) => { const DragNdrop = ({ onFileSelected }) => {
const [file, setFile] = useState<File>(); const [file, setFile] = useState<File>();
const [dragged, setDragged] = useState(false);
const inputRef = useRef<HTMLInputElement | null>(null); const inputRef = useRef<HTMLInputElement | null>(null);
const { LL } = useI18nContext(); const { LL } = useI18nContext();
@@ -45,6 +46,7 @@ const DragNdrop = ({ onFileSelected }) => {
const handleRemoveFile = (event) => { const handleRemoveFile = (event) => {
event.stopPropagation(); event.stopPropagation();
setFile(undefined); setFile(undefined);
setDragged(false);
}; };
const handleUploadClick = (event) => { const handleUploadClick = (event) => {
@@ -56,11 +58,17 @@ const DragNdrop = ({ onFileSelected }) => {
inputRef.current?.click(); inputRef.current?.click();
}; };
const handleDragOver = (event) => {
event.preventDefault(); // prevent file from being opened
setDragged(true);
};
return ( return (
<div <div
className={`document-uploader ${file ? 'upload-box active' : 'upload-box'}`} className={`document-uploader ${file || dragged ? 'active' : ''}`}
onDrop={handleDrop} onDrop={handleDrop}
onDragOver={(event) => event.preventDefault()} onDragOver={handleDragOver}
onDragLeave={() => setDragged(false)}
onClick={handleBrowseClick} onClick={handleBrowseClick}
> >
<div className="upload-info"> <div className="upload-info">

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
@@ -1546,15 +1546,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.7.0": "@typescript-eslint/eslint-plugin@npm:8.8.0":
version: 8.7.0 version: 8.8.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.7.0" resolution: "@typescript-eslint/eslint-plugin@npm:8.8.0"
dependencies: dependencies:
"@eslint-community/regexpp": "npm:^4.10.0" "@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.7.0" "@typescript-eslint/scope-manager": "npm:8.8.0"
"@typescript-eslint/type-utils": "npm:8.7.0" "@typescript-eslint/type-utils": "npm:8.8.0"
"@typescript-eslint/utils": "npm:8.7.0" "@typescript-eslint/utils": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.7.0" "@typescript-eslint/visitor-keys": "npm:8.8.0"
graphemer: "npm:^1.4.0" graphemer: "npm:^1.4.0"
ignore: "npm:^5.3.1" ignore: "npm:^5.3.1"
natural-compare: "npm:^1.4.0" natural-compare: "npm:^1.4.0"
@@ -1565,66 +1565,66 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10c0/f04d6fa6a30e32d51feba0f08789f75ca77b6b67cfe494bdbd9aafa241871edc918fa8b344dc9d13dd59ae055d42c3920f0e542534f929afbfdca653dae598fa checksum: 10c0/98ac37587eda02a713710f0a62ca979833482024968f1d1735881718abe102a6b49707db4f1dac0d7c731d1cbf8111d829c5125348d4829ab6fad7a7b3b344e4
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/parser@npm:8.7.0": "@typescript-eslint/parser@npm:8.8.0":
version: 8.7.0 version: 8.8.0
resolution: "@typescript-eslint/parser@npm:8.7.0" resolution: "@typescript-eslint/parser@npm:8.8.0"
dependencies: dependencies:
"@typescript-eslint/scope-manager": "npm:8.7.0" "@typescript-eslint/scope-manager": "npm:8.8.0"
"@typescript-eslint/types": "npm:8.7.0" "@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/typescript-estree": "npm:8.7.0" "@typescript-eslint/typescript-estree": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.7.0" "@typescript-eslint/visitor-keys": "npm:8.8.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10c0/1d5020ff1f5d3eb726bc6034d23f0a71e8fe7a713756479a0a0b639215326f71c0b44e2c25cc290b4e7c144bd3c958f1405199711c41601f0ea9174068714a64 checksum: 10c0/cf72a644b89c62cd55b09fa1d22b51a2c726714aac344a797f0c2ad80bfbabcb7567000fadd4ea8188aa1d923675bebdca06acc1d28ac1b8360bf28a36b46f3a
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/scope-manager@npm:8.7.0": "@typescript-eslint/scope-manager@npm:8.8.0":
version: 8.7.0 version: 8.8.0
resolution: "@typescript-eslint/scope-manager@npm:8.7.0" resolution: "@typescript-eslint/scope-manager@npm:8.8.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:8.7.0" "@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.7.0" "@typescript-eslint/visitor-keys": "npm:8.8.0"
checksum: 10c0/8b731a0d0bd3e8f6a322b3b25006f56879b5d2aad86625070fa438b803cf938cb8d5c597758bfa0d65d6e142b204dc6f363fa239bc44280a74e25aa427408eda checksum: 10c0/29ddf589ff0e465dbbf3eb87b79a29face4ec5a6cb617bbaafbac6ae8340d376b5b405bca762ee1c7a40cbdf7912a32734f9119f6864df048c7a0b2de21bdd3d
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/type-utils@npm:8.7.0": "@typescript-eslint/type-utils@npm:8.8.0":
version: 8.7.0 version: 8.8.0
resolution: "@typescript-eslint/type-utils@npm:8.7.0" resolution: "@typescript-eslint/type-utils@npm:8.8.0"
dependencies: dependencies:
"@typescript-eslint/typescript-estree": "npm:8.7.0" "@typescript-eslint/typescript-estree": "npm:8.8.0"
"@typescript-eslint/utils": "npm:8.7.0" "@typescript-eslint/utils": "npm:8.8.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.3.0" ts-api-utils: "npm:^1.3.0"
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10c0/2bd9fb93a50ff1c060af41528e39c775ae93b09dd71450defdb42a13c68990dd388460ae4e81fb2f4a49c38dc12152c515d43e845eca6198c44b14aab66733bc checksum: 10c0/d6ee11f4686fb54daea1f436f73b96eb31a95f6e535abc0534abf5794e7597669a92d12300969c8afee0fc1912dbc1591664f7e37f0da5935016cc981b2921a8
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/types@npm:8.7.0": "@typescript-eslint/types@npm:8.8.0":
version: 8.7.0 version: 8.8.0
resolution: "@typescript-eslint/types@npm:8.7.0" resolution: "@typescript-eslint/types@npm:8.8.0"
checksum: 10c0/f7529eaea4ecc0f5e2d94ea656db8f930f6d1c1e65a3ffcb2f6bec87361173de2ea981405c2c483a35a927b3bdafb606319a1d0395a6feb1284448c8ba74c31e checksum: 10c0/cd168fafcaf77641b023c4405ea3a8c30fbad1737abb5aec9fce67fe2ae20224b624b5a2e3e84900ba81dc7dd33343add3653763703a225326cc81356b182d09
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/typescript-estree@npm:8.7.0": "@typescript-eslint/typescript-estree@npm:8.8.0":
version: 8.7.0 version: 8.8.0
resolution: "@typescript-eslint/typescript-estree@npm:8.7.0" resolution: "@typescript-eslint/typescript-estree@npm:8.8.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:8.7.0" "@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.7.0" "@typescript-eslint/visitor-keys": "npm:8.8.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2" fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3" is-glob: "npm:^4.0.3"
@@ -1634,31 +1634,31 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10c0/d714605b6920a9631ab1511b569c1c158b1681c09005ab240125c442a63e906048064151a61ce5eb5f8fe75cea861ce5ae1d87be9d7296b012e4ab6d88755e8b checksum: 10c0/9b9e849f6b2d4e250840ef8e05f55a97d6598adaf48c1e6df83084b94c30feca6a3e7916ee1c235178188d0db6364a877cbf8fe218c36d5f8d5acb50767f3273
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/utils@npm:8.7.0": "@typescript-eslint/utils@npm:8.8.0":
version: 8.7.0 version: 8.8.0
resolution: "@typescript-eslint/utils@npm:8.7.0" resolution: "@typescript-eslint/utils@npm:8.8.0"
dependencies: dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0" "@eslint-community/eslint-utils": "npm:^4.4.0"
"@typescript-eslint/scope-manager": "npm:8.7.0" "@typescript-eslint/scope-manager": "npm:8.8.0"
"@typescript-eslint/types": "npm:8.7.0" "@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/typescript-estree": "npm:8.7.0" "@typescript-eslint/typescript-estree": "npm:8.8.0"
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
checksum: 10c0/7355b754ce2fc118773ed27a3e02b7dfae270eec73c2d896738835ecf842e8309544dfd22c5105aba6cae2787bfdd84129bbc42f4b514f57909dc7f6890b8eba checksum: 10c0/fcf2dfd4a2d9491aa096a29c2c1fdd891ca3c13933d20cfea44e51b3d10a397e7ed9a9cd71ac9a29e8c4706264ae00c25a29394e2a6bda3291be298062901f2c
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/visitor-keys@npm:8.7.0": "@typescript-eslint/visitor-keys@npm:8.8.0":
version: 8.7.0 version: 8.8.0
resolution: "@typescript-eslint/visitor-keys@npm:8.7.0" resolution: "@typescript-eslint/visitor-keys@npm:8.8.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:8.7.0" "@typescript-eslint/types": "npm:8.8.0"
eslint-visitor-keys: "npm:^3.4.3" eslint-visitor-keys: "npm:^3.4.3"
checksum: 10c0/1240da13c15f9f875644b933b0ad73713ef12f1db5715236824c1ec359e6ef082ce52dd9b2186d40e28be6a816a208c226e6e9af96e5baeb24b4399fe786ae7c checksum: 10c0/580ce74c9b09b9e6a6f3f0ac2d2f0c6a6b983a78ce3b2544822ee08107c57142858d674897f61ff32a9a5e8fca00c916545c159bb75d134f4380884642542d38
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,10 +1698,10 @@ __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.8.0"
vite: "npm:^5.4.8" vite: "npm:^5.4.8"
vite-plugin-imagemin: "npm:^0.6.1" vite-plugin-imagemin: "npm:^0.6.1"
vite-tsconfig-paths: "npm:^5.0.1" vite-tsconfig-paths: "npm:^5.0.1"
@@ -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
@@ -6803,17 +6803,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"typescript-eslint@npm:8.7.0": "typescript-eslint@npm:8.8.0":
version: 8.7.0 version: 8.8.0
resolution: "typescript-eslint@npm:8.7.0" resolution: "typescript-eslint@npm:8.8.0"
dependencies: dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.7.0" "@typescript-eslint/eslint-plugin": "npm:8.8.0"
"@typescript-eslint/parser": "npm:8.7.0" "@typescript-eslint/parser": "npm:8.8.0"
"@typescript-eslint/utils": "npm:8.7.0" "@typescript-eslint/utils": "npm:8.8.0"
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10c0/c0c3f909227c664f193d11a912851d6144a7cfcc0ac5e57f695c3e50679ef02bb491cc330ad9787e00170ce3be3a3b8c80bb81d5e20a40c1b3ee713ec3b0955a checksum: 10c0/545f0ce051282921aff56288baf288cffe6f7bafee5149f1b87af2c67f81f8c2088924a2e0fc0f0dcd12692b6a97eca10149a619c8c85d4aaef2fe763938da8d
languageName: node languageName: node
linkType: hard linkType: hard

View File

@@ -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) { [this](AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
handleUpload(request, filename, index, data, len, 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) { 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()); Update.setMD5(_md5.data());
_md5.front() = '\0'; _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 request->onDisconnect([this] { handleEarlyDisconnect(); }); // success, let's make sure we end the update if the client hangs up
} else { } else {
handleError(request, 507); // failed to begin, send an error response Insufficient Storage handleError(request, 507); // failed to begin, send an error response Insufficient Storage
@@ -171,15 +165,3 @@ void UploadFileService::handleEarlyDisconnect() {
_is_firmware = false; _is_firmware = false;
Update.abort(); 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<JsonObject>()) {
// this will keep a copy of the URL, but won't initiate the download yet
emsesp::EMSESP::system_.uploadFirmwareURL(json["url"].as<const char *>());
// end the connection
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
}
}

View File

@@ -12,7 +12,6 @@
#include <array> #include <array>
#define UPLOAD_FILE_PATH "/rest/uploadFile" #define UPLOAD_FILE_PATH "/rest/uploadFile"
#define UPLOAD_URL_PATH "/rest/uploadURL"
#define TEMP_FILENAME_PATH "/tmp_upload" // for uploaded json files #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 handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final);
void uploadComplete(AsyncWebServerRequest * request); void uploadComplete(AsyncWebServerRequest * request);
void handleError(AsyncWebServerRequest * request, int code); void handleError(AsyncWebServerRequest * request, int code);
void uploadURL(AsyncWebServerRequest * request, JsonVariant json);
void handleEarlyDisconnect(); void handleEarlyDisconnect();
}; };

View File

@@ -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:',
'',
'<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'
// 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 // START DATA
// LOG // 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_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 +470,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 +4136,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 +4451,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,30 +4587,28 @@ router
return data; return data;
}) })
// Download Settings // generic action for all /rest/... endpoints
.get(EMSESP_EXPORT_DATA_ENDPOINT, (request) => { // takes an action and param in JSON
const type = request.query.type; .post(EMSESP_ACTION_ENDPOINT, async (request: any) => {
console.log('exporting ' + type + ' data'); const content = await request.json();
switch (type) { if (content.hasOwnProperty('action')) {
case 'settings': const action = content.action;
return emsesp_info; if (action === 'export') {
case 'customizations': // export data
return emsesp_deviceentities_2; // fixed for one device return export_data(content.param);
case 'entities': } else if (action === 'customSupport') {
return emsesp_customentities; // send custom support
case 'schedule': return custom_support();
return emsesp_schedule; } else if (action === 'checkUpgrade') {
case 'modules': // check upgrade
return emsesp_modules; return check_upgrade(content.param);
default: } else if (action === 'uploadURL') {
return status(404); // upload URL
console.log('upload File from URL', content.param);
return status(200);
}
} }
}) return status(404); // cmd not found
// upload URL
.post('/rest/uploadURL', () => {
console.log('upload File from URL');
return status(200);
}) })
// API which are usually POST for security // API which are usually POST for security
@@ -4594,9 +4630,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 =
@@ -78,7 +78,7 @@ check_flags =
clangtidy: --checks=-*,clang-analyzer-*,performance-* clangtidy: --checks=-*,clang-analyzer-*,performance-*
lib_ldf_mode = chain+ lib_ldf_mode = chain+
lib_deps = 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 ; builds for GitHub Actions CI

View File

@@ -1346,3 +1346,6 @@ zyxwvutsrqponmlkjihgfedcba
Omea Omea
Bolv Bolv
hardwarestatus hardwarestatus
hpcurrpower
hppowerlimit
CUSTOMSUPPORT

View File

@@ -102,9 +102,6 @@ void AnalogSensor::reload(bool get_nvs) {
} }
} }
if (!found) { 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_.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 sensors_.back().ha_registered = false; // this will trigger recreate of the HA config
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) { 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 // 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; bool is_ha_device_created = false;
for (auto & sensor : sensors_) { for (auto const & sensor : sensors_) {
if (sensor.ha_registered) { if (sensor.ha_registered) {
is_ha_device_created = true; is_ha_device_created = true;
break; break;

View File

@@ -420,7 +420,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
} else { } else {
if (single_command) { if (single_command) {
// log as DEBUG (TRACE) regardless if compiled with EMSESP_DEBUG // 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 { } else {
if (id > 0) { 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); 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; return;
} }
auto it = cmdfunctions_.begin(); 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))) { if (Helpers::toLower(cmd) == Helpers::toLower(cf.cmd_) && (cf.device_type_ == device_type) && ((flag & 0x3F) == (cf.flags_ & 0x3F))) {
cmdfunctions_.erase(it); cmdfunctions_.erase(it);
return; 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 sorted_cmds.sort(); // sort them
// if not in verbose mode, just print them on a single line and exit // if not in verbose mode, just print them on a single line and exit
if (!verbose) { 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) { for (const auto & cl : sorted_cmds) {
shell.print(cl); shell.print(cl);
shell.print(" "); shell.print(" ");
@@ -582,14 +579,6 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
// verbose mode // verbose mode
shell.printfln("\n%s%s %s:%s", COLOR_BOLD_ON, COLOR_YELLOW, EMSdevice::device_type_2_device_name(device_type), COLOR_RESET); 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) { for (const auto & cl : sorted_cmds) {
// find and print the description // find and print the description
for (const auto & cf : cmdfunctions_) { for (const auto & cf : cmdfunctions_) {
@@ -682,6 +671,13 @@ void Command::show_devices(uuid::console::Shell & shell) {
// calls show with verbose mode set // calls show with verbose mode set
void Command::show_all(uuid::console::Shell & shell) { void Command::show_all(uuid::console::Shell & shell) {
shell.printfln("Showing all available commands (%s*%s=authentication not required):", COLOR_BRIGHT_GREEN, COLOR_RESET); 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 system ones first
show(shell, EMSdevice::DeviceType::SYSTEM, true); show(shell, EMSdevice::DeviceType::SYSTEM, true);

View File

@@ -76,54 +76,47 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
// //
// Show commands // Show commands
// //
commands->add_command(ShellContext::MAIN, CommandFlags::USER, {F_(show)}, [=](Shell & shell, const std::vector<std::string> & arguments) { commands->add_command(
to_app(shell).system_.show_system(shell); ShellContext::MAIN,
}); CommandFlags::USER,
commands->add_command(ShellContext::MAIN, {F_(show)},
CommandFlags::USER, {F_(show_commands)},
string_vector{F_(show), F_(system)}, [=](Shell & shell, const std::vector<std::string> & arguments) {
[=](Shell & shell, const std::vector<std::string> & arguments) { to_app(shell).system_.show_system(shell); }); if (arguments.empty()) {
to_app(shell).system_.show_system(shell);
return;
}
commands->add_command(ShellContext::MAIN, auto command = arguments.front();
CommandFlags::ADMIN, if (command == F_(commands)) {
string_vector{F_(show), F_(users)}, Command::show_all(shell);
[](Shell & shell, const std::vector<std::string> & arguments) { to_app(shell).system_.show_users(shell); }); } else if (command == F_(system)) {
to_app(shell).system_.show_system(shell);
commands->add_command(ShellContext::MAIN, } else if (command == F_(users) && (shell.has_flags(CommandFlags::ADMIN))) {
CommandFlags::USER, to_app(shell).system_.show_users(shell); // admin only
string_vector{F_(show), F_(devices)}, } else if (command == F_(devices)) {
[](Shell & shell, const std::vector<std::string> & arguments) { to_app(shell).show_devices(shell); }); to_app(shell).show_devices(shell);
} else if (command == F_(log)) {
commands->add_command(ShellContext::MAIN, CommandFlags::USER, string_vector{F_(show), F_(log)}, [](Shell & shell, const std::vector<std::string> & arguments) { to_app(shell).webLogService.show(shell);
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);
commands->add_command(ShellContext::MAIN, CommandFlags::USER, string_vector{F_(show), F_(ems)}, [](Shell & shell, const std::vector<std::string> & arguments) { to_app(shell).show_sensor_values(shell);
to_app(shell).show_ems(shell); } else if (command == F_(mqtt)) {
}); Mqtt::show_mqtt(shell);
} else {
commands->add_command(ShellContext::MAIN, CommandFlags::USER, string_vector{F_(show), F_(values)}, [](Shell & shell, const std::vector<std::string> & arguments) { shell.printfln("Unknown show command");
to_app(shell).show_device_values(shell); }
to_app(shell).show_sensor_values(shell); },
}); [](Shell & shell, const std::vector<std::string> & current_arguments, const std::string & next_argument) -> std::vector<std::string> {
return std::vector<std::string>{"system", "users", "devices", "log", "ems", "values", "mqtt", "commands"};
commands->add_command(ShellContext::MAIN, });
CommandFlags::USER,
string_vector{F_(show), F_(mqtt)},
[](Shell & shell, const std::vector<std::string> & arguments) { Mqtt::show_mqtt(shell); });
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
string_vector{F_(show), F_(commands)},
[](Shell & shell, const std::vector<std::string> & arguments) { Command::show_all(shell); });
// //
// System commands // System commands
// //
#if defined(EMSESP_TEST) #if defined(EMSESP_TEST)
// create commands test // create commands test
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,

View File

@@ -297,7 +297,7 @@ uint8_t EMSdevice::decode_brand(uint8_t value) {
} }
// returns string of a human friendly description of the EMS device // 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 // for devices that haven't been lookup yet, don't show all details
if (product_id_ == 0) { if (product_id_ == 0) {
return std::string(name()) + " (DeviceID:" + Helpers::hextoa(device_id_) + ")"; return std::string(name()) + " (DeviceID:" + Helpers::hextoa(device_id_) + ")";
@@ -313,7 +313,7 @@ const std::string EMSdevice::to_string() {
// returns out brand + device name // returns out brand + device name
// translated // translated
const std::string EMSdevice::to_string_short() { std::string EMSdevice::to_string_short() {
if (brand_ == Brand::NO_BRAND) { if (brand_ == Brand::NO_BRAND) {
return std::string(device_type_2_device_name_translated()) + ": " + name(); 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 uint8_t state = DeviceValueState::DV_DEFAULT; // determine state
std::string custom_fullname = std::string(""); // custom fullname auto custom_fullname = std::string(""); // custom fullname
auto short_name = name[0]; // entity name auto short_name = name[0]; // entity name
bool has_cmd = (f != nullptr); // is it a command? bool has_cmd = (f != nullptr); // is it a command?
bool ignore = false; // ignore this entity? bool ignore = false; // ignore this entity?
// get fullname, getting translation if it exists // get fullname, getting translation if it exists
const char * const * fullname; const char * const * fullname;
@@ -1025,7 +1025,7 @@ void EMSdevice::generate_values_web(JsonObject output) {
// this is used only for WebCustomizationService::device_entities() // this is used only for WebCustomizationService::device_entities()
void EMSdevice::generate_values_web_customization(JsonArray output) { void EMSdevice::generate_values_web_customization(JsonArray output) {
for (auto & dv : devicevalues_) { 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<JsonObject>(); JsonObject obj = output.add<JsonObject>();
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0; uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;

View File

@@ -42,7 +42,6 @@ class EMSdevice {
, flags_(flags) , flags_(flags)
, brand_(brand) { , brand_(brand) {
strlcpy(version_, version, sizeof(version_)); 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 // 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 // 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; custom_name_ = custom_name;
} }
std::string name(); // returns either default or custom name if defined 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<uint16_t> & result); int get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result);
int modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector<uint8_t> & modbus_data, JsonObject jsonValue); int modbus_value_to_json(uint8_t tag, const std::string & shortname, const std::vector<uint8_t> & modbus_data, JsonObject jsonValue);
const char * brand_to_char(); const char * brand_to_char();
const std::string to_string(); std::string to_string();
const std::string to_string_short(); std::string to_string_short();
enum Handlers : uint8_t { ALL, RECEIVED, FETCHED, PENDING, IGNORED }; enum Handlers : uint8_t { ALL, RECEIVED, FETCHED, PENDING, IGNORED };
void show_telegram_handlers(uuid::console::Shell & shell) const; void show_telegram_handlers(uuid::console::Shell & shell) const;
char * show_telegram_handlers(char * result, const size_t len, const uint8_t handlers); char * show_telegram_handlers(char * result, const size_t len, const uint8_t handlers);
void show_mqtt_handlers(uuid::console::Shell & shell) const; 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 set_climate_minmax(int8_t tag, int16_t min, uint32_t max);
void setCustomizationEntity(const std::string & entity_id); void setCustomizationEntity(const std::string & entity_id);
@@ -456,15 +454,13 @@ class EMSdevice {
uint8_t count_entities(); uint8_t count_entities();
bool has_entities() const; bool has_entities() const;
/* // void reserve_device_values(uint8_t elements) {
void reserve_device_values(uint8_t elements) { // devicevalues_.reserve(elements);
devicevalues_.reserve(elements); // }
}
void reserve_telegram_functions(uint8_t elements) { // void reserve_telegram_functions(uint8_t elements) {
telegram_functions_.reserve(elements); // telegram_functions_.reserve(elements);
} // }
*/
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
struct TelegramFunctionDump { struct TelegramFunctionDump {
@@ -487,10 +483,10 @@ class EMSdevice {
uint8_t device_id_ = 0; uint8_t device_id_ = 0;
uint8_t product_id_ = 0; uint8_t product_id_ = 0;
char version_[6]; char version_[6];
const char * default_name_; // the fixed name the EMS model taken from the device library const char * default_name_; // the fixed name the EMS model taken from the device library
std::string custom_name_; // custom name std::string custom_name_ = ""; // custom name
uint8_t flags_ = 0; uint8_t flags_ = 0;
uint8_t brand_ = Brand::NO_BRAND; uint8_t brand_ = Brand::NO_BRAND;
bool ha_config_done_ = false; bool ha_config_done_ = false;
bool has_update_ = false; bool has_update_ = false;

View File

@@ -208,7 +208,7 @@ void EMSESP::uart_init() {
uint8_t tx_mode = 0; uint8_t tx_mode = 0;
uint8_t rx_gpio = 0; uint8_t rx_gpio = 0;
uint8_t tx_gpio = 0; uint8_t tx_gpio = 0;
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings const & settings) {
tx_mode = settings.tx_mode; tx_mode = settings.tx_mode;
rx_gpio = settings.rx_gpio; rx_gpio = settings.rx_gpio;
tx_gpio = settings.tx_gpio; tx_gpio = settings.tx_gpio;
@@ -279,7 +279,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
if (bus_status() != BUS_STATUS_OFFLINE) { if (bus_status() != BUS_STATUS_OFFLINE) {
shell.printfln("EMS Bus info:"); 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(" Bus protocol: %s", EMSbus::is_ht3() ? "HT3" : "Buderus");
shell.printfln(" #recognized EMS devices: %d", EMSESP::emsdevices.size()); shell.printfln(" #recognized EMS devices: %d", EMSESP::emsdevices.size());
shell.printfln(" #telegrams received: %d", rxservice_.telegram_count()); 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)); 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 // 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) { for (EntityCustomization e : settings.entityCustomizations) {
if ((e.device_id == device_id) && (e.product_id == product_id)) { 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); LOG_DEBUG("Have customizations for %s with deviceID 0x%02X productID %d", e.custom_name.c_str(), device_id, product_id);

View File

@@ -798,10 +798,6 @@ uint16_t Helpers::string2minutes(const std::string & str) {
if (tmp > 60) { if (tmp > 60) {
return 0; return 0;
} }
// Serial.print("*");
// Serial.print(tmp);
// Serial.println("*");
res += tmp; res += tmp;
} }
// Or we got an extra colon // Or we got an extra colon

View File

@@ -154,6 +154,7 @@ MAKE_WORD_CUSTOM(deviceid_mandatory, "<deviceID>")
MAKE_WORD_CUSTOM(device_type_optional, "[device]") MAKE_WORD_CUSTOM(device_type_optional, "[device]")
MAKE_WORD_CUSTOM(invalid_log_level, "Invalid log level") MAKE_WORD_CUSTOM(invalid_log_level, "Invalid log level")
MAKE_WORD_CUSTOM(log_level_optional, "[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, "<name>") MAKE_WORD_CUSTOM(name_mandatory, "<name>")
MAKE_WORD_CUSTOM(name_optional, "[name]") MAKE_WORD_CUSTOM(name_optional, "[name]")
MAKE_WORD_CUSTOM(new_password_prompt1, "Enter new password: ") MAKE_WORD_CUSTOM(new_password_prompt1, "Enter new password: ")

View File

@@ -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(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(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(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(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(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 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(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(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

@@ -14,9 +14,7 @@
#include <utility> #include <utility>
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
#include <../test/test_modbus/modbus_test.h>
#include <modbus_test.h>
#endif #endif
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE

View File

@@ -1115,11 +1115,12 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// special case to handle booleans // special case to handle booleans
// applies to both Binary Sensor (read only) and a Switch (for a command) // 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) { if (type == DeviceValueType::BOOL) {
add_ha_bool(doc); 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 + "}}"; doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else " + sample_val + "}}";
// add the dev json object to the end, not for commands // add the dev json object to the end, not for commands

View File

@@ -124,10 +124,7 @@ void Roomctrl::send(uint8_t addr) {
} }
temperature(addr, 0x10, hc); // send to master-thermostat temperature(addr, 0x10, hc); // send to master-thermostat
} }
} else if (type_[hc] == RC200 || type_[hc] == RC100) { } else if (type_[hc] == RC200 || type_[hc] == RC100 || type_[hc] == FB10) {
send_time_[hc] = uuid::get_uptime();
temperature(addr, 0x10, hc);
} else if (type_[hc] == FB10) {
send_time_[hc] = uuid::get_uptime(); send_time_[hc] = uuid::get_uptime();
temperature(addr, 0x10, hc); // send to master-thermostat (https://github.com/emsesp/EMS-ESP32/issues/336) temperature(addr, 0x10, hc); // send to master-thermostat (https://github.com/emsesp/EMS-ESP32/issues/336)
} else { // type==RC20 or SENSOR } else { // type==RC20 or SENSOR

View File

@@ -25,7 +25,7 @@ uuid::log::Logger Shower::logger_{F_(shower), uuid::log::Facility::CONSOLE};
static bool force_coldshot = false; static bool force_coldshot = false;
void Shower::start() { void Shower::start() {
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings const & settings) {
shower_timer_ = settings.shower_timer; shower_timer_ = settings.shower_timer;
shower_alert_ = settings.shower_alert; shower_alert_ = settings.shower_alert;
shower_alert_trigger_ = settings.shower_alert_trigger * 60; // convert from minutes to seconds shower_alert_trigger_ = settings.shower_alert_trigger * 60; // convert from minutes to seconds
@@ -61,7 +61,6 @@ void Shower::loop() {
return; return;
} }
// uint32_t time_now = uuid::get_uptime(); // in ms
auto time_now = uuid::get_uptime_sec(); // in sec 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 // if already in cold mode, ignore all this logic until we're out of the cold blast

View File

@@ -111,44 +111,6 @@ bool System::command_response(const char * value, const int8_t id, JsonObject ou
return true; 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<JsonObject>();
// 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<JsonObject>();
emsdevice->get_value_info(device_output, value, DeviceValueTAG::TAG_NONE);
}
// Custom Entities
device_output = output["Custom Entities"].to<JsonObject>();
EMSESP::webCustomEntityService.get_value_info(device_output, value);
// Scheduler
device_output = output["Scheduler"].to<JsonObject>();
EMSESP::webSchedulerService.get_value_info(device_output, value);
// Sensors
device_output = output["Analog Sensors"].to<JsonObject>();
EMSESP::analogsensor_.get_value_info(device_output, value);
device_output = output["Temperature Sensors"].to<JsonObject>();
EMSESP::temperaturesensor_.get_value_info(device_output, value);
return true;
}
// fetch device values // fetch device values
bool System::command_fetch(const char * value, const int8_t id) { bool System::command_fetch(const char * value, const int8_t id) {
std::string value_s; std::string value_s;
@@ -156,13 +118,13 @@ bool System::command_fetch(const char * value, const int8_t id) {
if (value_s == "all") { if (value_s == "all") {
LOG_INFO("Requesting data from EMS devices"); LOG_INFO("Requesting data from EMS devices");
EMSESP::fetch_device_values(); EMSESP::fetch_device_values();
} else if (value_s == (F_(boiler))) { } else if (value_s == F_(boiler)) {
EMSESP::fetch_device_values_type(EMSdevice::DeviceType::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); 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); 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); EMSESP::fetch_device_values_type(EMSdevice::DeviceType::MIXER);
} }
} else { } else {
@@ -338,6 +300,8 @@ void System::system_restart(const char * partitionname) {
delay(1000); // wait 1 second delay(1000); // wait 1 second
ESP.restart(); ESP.restart();
#else #else
restart_requested(false);
restart_pending(false);
if (partitionname != nullptr) { if (partitionname != nullptr) {
LOG_INFO("Restarting EMS-ESP from %s partition", partitionname); LOG_INFO("Restarting EMS-ESP from %s partition", partitionname);
} else { } else {
@@ -375,9 +339,6 @@ void System::syslog_init() {
syslog_.destination(syslog_host_.c_str(), syslog_port_); syslog_.destination(syslog_host_.c_str(), syslog_port_);
syslog_.hostname(hostname().c_str()); 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()) { } else if (syslog_.started()) {
// in case service is still running, this flushes the queue // in case service is still running, this flushes the queue
// https://github.com/emsesp/EMS-ESP/issues/496 // https://github.com/emsesp/EMS-ESP/issues/496
@@ -874,7 +835,6 @@ 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));
// 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
@@ -1031,8 +991,11 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" App used/free: %lu KB / %lu KB", appUsed(), appFree()); shell.printfln(" App used/free: %lu KB / %lu KB", appUsed(), appFree());
uint32_t FSused = LittleFS.usedBytes() / 1024; uint32_t FSused = LittleFS.usedBytes() / 1024;
shell.printfln(" FS used/free: %lu KB / %lu KB", FSused, FStotal() - FSused); shell.printfln(" FS used/free: %lu KB / %lu KB", FSused, FStotal() - FSused);
shell.printfln(" Flash size: %lu KB", ESP.getFlashChipSize() / 1024);
if (PSram()) { if (PSram()) {
shell.printfln(" PSRAM size/free: %lu KB / %lu KB", PSram(), ESP.getFreePsram() / 1024); shell.printfln(" PSRAM size/free: %lu KB / %lu KB", PSram(), ESP.getFreePsram() / 1024);
} else {
shell.printfln(" PSRAM: not available");
} }
shell.println(); shell.println();
@@ -1087,7 +1050,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" WiFi Network: Disconnected"); shell.printfln(" WiFi Network: Disconnected");
break; break;
case WL_NO_SHIELD: // case WL_NO_SHIELD:
default: default:
shell.printfln(" WiFi MAC address: %s", WiFi.macAddress().c_str()); shell.printfln(" WiFi MAC address: %s", WiFi.macAddress().c_str());
shell.printfln(" WiFi Network: not connected"); shell.printfln(" WiFi Network: not connected");
@@ -1121,7 +1084,7 @@ void System::show_system(uuid::console::Shell & shell) {
} else { } else {
shell.printfln(" Syslog: %s", syslog_.started() ? "started" : "stopped"); shell.printfln(" Syslog: %s", syslog_.started() ? "started" : "stopped");
shell.print(" "); 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.printfln(" IP: %s", uuid::printable_to_string(syslog_.ip()).c_str());
shell.print(" "); shell.print(" ");
shell.printfln(F_(port_fmt), syslog_port_); 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 // 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 +1130,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 == "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 { } 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
@@ -1192,7 +1164,7 @@ bool System::check_upgrade(bool factory_settings) {
if (!factory_settings) { if (!factory_settings) {
// fetch current version from settings file // 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 // see if we're missing a version, will be < 3.5.0b13 from Dec 23 2022
missing_version = (settingsVersion.empty() || (settingsVersion.length() < 5)); 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))) { if (!strcmp(cmd, F_(entities))) {
for (JsonPair p : root) { for (JsonPair p : root) {
if (p.value().is<JsonObject>()) { if (p.value().is<JsonObject>()) {
// String prefix = p.key().c_str();
for (JsonPair p1 : p.value().as<JsonObject>()) { for (JsonPair p1 : p.value().as<JsonObject>()) {
JsonObject entity = output[std::string(p.key().c_str()) + "." + p1.key().c_str()].to<JsonObject>(); JsonObject entity = output[std::string(p.key().c_str()) + "." + p1.key().c_str()].to<JsonObject>();
get_value_json(entity, p.key().c_str(), p1.key().c_str(), p1.value()); 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; return true;
} }
@@ -1523,7 +1492,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
} }
}); });
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) { EMSESP::esp8266React.getAPSettingsService()->read([&](const APSettings & settings) {
const char * pM[] = {"always", "disconnected", "never"}; const char * pM[] = {"always", "disconnected", "never"};
node["APProvisionMode"] = pM[settings.provisionMode]; node["APProvisionMode"] = pM[settings.provisionMode];
node["APSecurity"] = settings.password.length() ? "wpa2" : "open"; 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<JsonObject>(); node = output["ntp"].to<JsonObject>();
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
node["NTPStatus"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected"; 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["enabled"] = settings.enabled;
node["server"] = settings.server; node["server"] = settings.server;
node["tzLabel"] = settings.tzLabel; node["tzLabel"] = settings.tzLabel;
// node["tz format"] = settings.tzFormat;
}); });
#endif #endif
@@ -1552,7 +1520,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["MQTTPublishFails"] = Mqtt::publish_fails(); node["MQTTPublishFails"] = Mqtt::publish_fails();
node["MQTTConnects"] = Mqtt::connect_count(); node["MQTTConnects"] = Mqtt::connect_count();
} }
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { EMSESP::esp8266React.getMqttSettingsService()->read([&](const MqttSettings & settings) {
node["enabled"] = settings.enabled; node["enabled"] = settings.enabled;
node["clientID"] = settings.clientId; node["clientID"] = settings.clientId;
node["keepAlive"] = settings.keepAlive; node["keepAlive"] = settings.keepAlive;
@@ -1631,7 +1599,6 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["busStatus"] = "unknown"; node["busStatus"] = "unknown";
break; break;
} }
// if (EMSESP::bus_status() != EMSESP::BUS_STATUS_OFFLINE) {
node["busProtocol"] = EMSbus::is_ht3() ? "HT3" : "Buderus"; node["busProtocol"] = EMSbus::is_ht3() ? "HT3" : "Buderus";
node["busTelegramsReceived"] = EMSESP::rxservice_.telegram_count(); node["busTelegramsReceived"] = EMSESP::rxservice_.telegram_count();
node["busReads"] = EMSESP::txservice_.telegram_read_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["busWritesFailed"] = EMSESP::txservice_.telegram_write_fail_count();
node["busRxLineQuality"] = EMSESP::rxservice_.quality(); node["busRxLineQuality"] = EMSESP::rxservice_.quality();
node["busTxLineQuality"] = (EMSESP::txservice_.read_quality() + EMSESP::txservice_.read_quality()) / 2; node["busTxLineQuality"] = (EMSESP::txservice_.read_quality() + EMSESP::txservice_.read_quality()) / 2;
// }
// Settings // Settings
node = output["settings"].to<JsonObject>(); node = output["settings"].to<JsonObject>();
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](const WebSettings & settings) {
node["boardProfile"] = settings.board_profile; node["boardProfile"] = settings.board_profile;
node["locale"] = settings.locale; node["locale"] = settings.locale;
node["txMode"] = settings.tx_mode; 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["name"] = F_(temperaturesensor);
obj["entities"] = EMSESP::temperaturesensor_.count_entities(); obj["entities"] = EMSESP::temperaturesensor_.count_entities();
} }
// if (EMSESP::analog_enabled()) {
if (EMSESP::analogsensor_.count_entities()) { if (EMSESP::analogsensor_.count_entities()) {
JsonObject obj = devices.add<JsonObject>(); JsonObject obj = devices.add<JsonObject>();
obj["type"] = F_(analogsensor); obj["type"] = F_(analogsensor);
@@ -1895,7 +1860,7 @@ std::string System::reset_reason(uint8_t cpu) const {
break; break;
} }
#endif #endif
return ("Unknown"); return "Unknown";
} }
#pragma GCC diagnostic pop #pragma GCC diagnostic pop

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 };
@@ -58,12 +60,10 @@ class System {
static bool command_fetch(const char * value, const int8_t id); 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_restart(const char * value, const int8_t id);
static bool command_format(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_watch(const char * value, const int8_t id);
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 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);
@@ -162,10 +162,12 @@ class System {
readonly_mode_ = readonly_mode; readonly_mode_ = readonly_mode;
} }
// Boolean Format API/MQTT
uint8_t bool_format() { uint8_t bool_format() {
return bool_format_; return bool_format_;
} }
// Boolean Format Web
uint8_t bool_dashboard() { uint8_t bool_dashboard() {
return bool_dashboard_; return bool_dashboard_;
} }

View File

@@ -53,7 +53,7 @@ void TemperatureSensor::start() {
// load settings // load settings
void TemperatureSensor::reload() { void TemperatureSensor::reload() {
// load the service settings // load the service settings
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings const & settings) {
dallas_gpio_ = settings.dallas_gpio; dallas_gpio_ = settings.dallas_gpio;
parasite_ = settings.dallas_parasite; parasite_ = settings.dallas_parasite;
}); });
@@ -274,7 +274,7 @@ int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) {
case 11: case 11:
raw_value &= ~0x1; raw_value &= ~0x1;
break; break;
case 12: default: // 12
break; break;
} }
} }
@@ -302,7 +302,7 @@ bool TemperatureSensor::update(const std::string & id, const std::string & name,
sensor.set_offset(offset); sensor.set_offset(offset);
// store the new name and offset in our configuration // 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 // look it up to see if it exists
bool found = false; bool found = false;
for (auto & SensorCustomization : settings.sensorCustomizations) { for (auto & SensorCustomization : settings.sensorCustomizations) {
@@ -315,10 +315,10 @@ bool TemperatureSensor::update(const std::string & id, const std::string & name,
} }
} }
if (!found) { if (!found) {
SensorCustomization newSensor = SensorCustomization(); auto newSensor = SensorCustomization();
newSensor.id = id; newSensor.id = id;
newSensor.name = name; newSensor.name = name;
newSensor.offset = offset; newSensor.offset = offset;
settings.sensorCustomizations.push_back(newSensor); settings.sensorCustomizations.push_back(newSensor);
LOG_DEBUG("Adding new customization for sensor ID %s", id.c_str()); 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()) { if (Mqtt::enabled() && Mqtt::publish_single()) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) { if (Mqtt::publish_single2cmd()) {
snprintf(topic, sizeof(topic), "%s/%s", (F_(temperaturesensor)), sensor.name().c_str()); snprintf(topic, sizeof(topic), "%s/%s", F_(temperaturesensor), sensor.name().c_str());
} else { } 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]; char payload[10];
Mqtt::queue_publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0)); 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 // 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; bool is_ha_device_created = false;
for (auto & sensor : sensors_) { for (const auto & sensor : sensors_) {
if (sensor.ha_registered) { if (sensor.ha_registered) {
is_ha_device_created = true; is_ha_device_created = true;
break; break;
@@ -570,8 +570,8 @@ std::string TemperatureSensor::Sensor::name() const {
// look up in customization service for a specific sensor // look up in customization service for a specific sensor
// and set the name and offset from that entry if it exists // and set the name and offset from that entry if it exists
bool TemperatureSensor::Sensor::apply_customization() { bool TemperatureSensor::Sensor::apply_customization() {
EMSESP::webCustomizationService.read([&](WebCustomization & settings) { EMSESP::webCustomizationService.read([&](const WebCustomization & settings) {
auto sensors = settings.sensorCustomizations; auto const & sensors = settings.sensorCustomizations;
if (!sensors.empty()) { if (!sensors.empty()) {
for (const auto & sensor : sensors) { for (const auto & sensor : sensors) {
if (id_ == sensor.id) { if (id_ == sensor.id) {

View File

@@ -90,23 +90,23 @@ class TemperatureSensor {
return sensors_; return sensors_;
} }
uint32_t reads() { uint32_t reads() const {
return sensorreads_; return sensorreads_;
} }
uint32_t fails() { uint32_t fails() const {
return sensorfails_; return sensorfails_;
} }
bool sensor_enabled() { bool sensor_enabled() const {
return (dallas_gpio_ != 0); return (dallas_gpio_ != 0);
} }
bool have_sensors() { bool have_sensors() const {
return (!sensors_.empty()); return (!sensors_.empty());
} }
size_t count_entities() { size_t count_entities() const {
return sensors_.size(); return sensors_.size();
} }

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");
@@ -992,11 +989,26 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// EMSESP::webAPIService.webAPIService(&request); // EMSESP::webAPIService.webAPIService(&request);
request.method(HTTP_POST); request.method(HTTP_POST);
char data_api[] = "{\"device\":\"system\", \"cmd\":\"restart\",\"id\":-1}";
deserializeJson(doc, data_api); char data1[] = "{\"device\":\"system\", \"cmd\":\"restart\",\"id\":-1}";
json = doc.as<JsonVariant>(); deserializeJson(doc, data1);
request.url("/api"); request.url("/api");
EMSESP::webAPIService.webAPIService(&request, json); EMSESP::webAPIService.webAPIService(&request, doc.as<JsonVariant>());
char data2[] = "{\"action\":\"customSupport\", \"param\":\"hello\"}";
deserializeJson(doc, data2);
request.url("/rest/action");
EMSESP::webStatusService.action(&request, doc.as<JsonVariant>());
char data3[] = "{\"action\":\"export\", \"param\":\"schedule\"}";
deserializeJson(doc, data3);
request.url("/rest/action");
EMSESP::webStatusService.action(&request, doc.as<JsonVariant>());
char data4[] = "{\"action\":\"export\", \"param\":\"allvalues\"}";
deserializeJson(doc, data4);
request.url("/rest/action");
EMSESP::webStatusService.action(&request, doc.as<JsonVariant>());
// request.url("/api/thermostat"); // request.url("/api/thermostat");
// EMSESP::webAPIService.webAPIService(&request); // EMSESP::webAPIService.webAPIService(&request);

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.7.0-dev.41" #define EMSESP_APP_VERSION "3.7.0-dev.42"

View File

@@ -58,7 +58,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) { void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
// check if the user has admin privileges (token is included and authorized) // check if the user has admin privileges (token is included and authorized)
bool is_admin = false; bool is_admin = false;
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings const & settings) {
Authentication authentication = _securityManager->authenticateRequest(request); Authentication authentication = _securityManager->authenticateRequest(request);
is_admin = settings.notoken_api || AuthenticationPredicates::IS_ADMIN(authentication); is_admin = settings.notoken_api || AuthenticationPredicates::IS_ADMIN(authentication);
}); });
@@ -94,7 +94,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
emsesp::EMSESP::system_.refreshHeapMem(); emsesp::EMSESP::system_.refreshHeapMem();
// output json buffer // 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 // add more mem if needed - won't be needed in ArduinoJson 7
// while (!response->getSize()) { // while (!response->getSize()) {

View File

@@ -47,7 +47,7 @@ void WebCustomEntityService::begin() {
void WebCustomEntity::read(WebCustomEntity & webEntity, JsonObject root) { void WebCustomEntity::read(WebCustomEntity & webEntity, JsonObject root) {
JsonArray entity = root["entities"].to<JsonArray>(); JsonArray entity = root["entities"].to<JsonArray>();
uint8_t counter = 0; uint8_t counter = 0;
for (const CustomEntityItem & entityItem : webEntity.customEntityItems) { for (CustomEntityItem & entityItem : webEntity.customEntityItems) {
JsonObject ei = entity.add<JsonObject>(); JsonObject ei = entity.add<JsonObject>();
ei["id"] = counter++; // id is only used to render the table and must be unique ei["id"] = counter++; // id is only used to render the table and must be unique
ei["ram"] = entityItem.ram; ei["ram"] = entityItem.ram;
@@ -213,7 +213,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
// output of a single value // output of a single value
// if add_uom is true it will add the UOM string to the 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]; char payload[12];
std::string name = useVal ? "value" : entity.name; std::string name = useVal ? "value" : entity.name;
switch (entity.value_type) { 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); output[name] = add_uom ? serialized(v + ' ' + EMSdevice::uom_to_string(entity.uom)) : serialized(v);
} }
break; break;
case DeviceValueType::STRING: // case DeviceValueType::STRING:
default: default:
// if no type treat it as a string // if no type treat it as a string
if (entity.data.length() > 0) { if (entity.data.length() > 0) {
@@ -275,7 +275,7 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem en
// display all custom entities // display all custom entities
// adding each one, with UOM to a json object string // adding each one, with UOM to a json object string
void WebCustomEntityService::show_values(JsonObject output) { 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 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 // if no custom entries, return empty json
// even if we're looking for a specific entity // even if we're looking for a specific entity
// https://github.com/emsesp/EMS-ESP32/issues/1297 // https://github.com/emsesp/EMS-ESP32/issues/1297
if (customEntityItems_->size() == 0) { if (customEntityItems_->empty()) {
return true; return true;
} }
// if it's info or values... // if it's info or values...
if (!strlen(cmd) || !strcmp(cmd, F_(values)) || !strcmp(cmd, F_(info))) { if (!strlen(cmd) || !strcmp(cmd, F_(values)) || !strcmp(cmd, F_(info))) {
// list all names // list all names
for (const CustomEntityItem & entity : *customEntityItems_) { for (CustomEntityItem & entity : *customEntityItems_) {
render_value(output, entity); render_value(output, entity);
} }
return true; return true;
@@ -300,7 +300,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
// list all entities // list all entities
if (!strcmp(cmd, F_(entities))) { if (!strcmp(cmd, F_(entities))) {
for (const auto & entity : *customEntityItems_) { for (auto & entity : *customEntityItems_) {
auto nest = output[entity.name].to<JsonObject>(); auto nest = output[entity.name].to<JsonObject>();
get_value_json(nest, entity); get_value_json(nest, entity);
} }
@@ -309,7 +309,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
// specific value info // specific value info
const char * attribute_s = Command::get_attribute(cmd); const char * attribute_s = Command::get_attribute(cmd);
for (const auto & entity : *customEntityItems_) { for (auto & entity : *customEntityItems_) {
if (Helpers::toLower(entity.name) == cmd) { if (Helpers::toLower(entity.name) == cmd) {
get_value_json(output, entity); get_value_json(output, entity);
return Command::set_attribute(output, cmd, attribute_s); 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 // 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["name"] = entity.name;
output["fullname"] = entity.name; output["fullname"] = entity.name;
output["storage"] = entity.ram ? "ram" : "ems"; output["storage"] = entity.ram ? "ram" : "ems";
@@ -344,7 +344,7 @@ void WebCustomEntityService::get_value_json(JsonObject output, const CustomEntit
} }
// publish single value // publish single value
void WebCustomEntityService::publish_single(const CustomEntityItem & entity) { void WebCustomEntityService::publish_single(CustomEntityItem & entity) {
if (!Mqtt::enabled() || !Mqtt::publish_single()) { if (!Mqtt::enabled() || !Mqtt::publish_single()) {
return; return;
} }
@@ -372,11 +372,11 @@ void WebCustomEntityService::publish(const bool force) {
return; return;
} }
if (customEntityItems_->size() == 0) { if (customEntityItems_->empty()) {
return; return;
} }
if (Mqtt::publish_single() && force) { if (Mqtt::publish_single() && force) {
for (const CustomEntityItem & entityItem : *customEntityItems_) { for (CustomEntityItem & entityItem : *customEntityItems_) {
publish_single(entityItem); publish_single(entityItem);
} }
} }
@@ -385,7 +385,7 @@ void WebCustomEntityService::publish(const bool force) {
JsonObject output = doc.to<JsonObject>(); JsonObject output = doc.to<JsonObject>();
bool ha_created = ha_registered_; bool ha_created = ha_registered_;
for (const CustomEntityItem & entityItem : *customEntityItems_) { for (CustomEntityItem & entityItem : *customEntityItems_) {
render_value(output, entityItem); render_value(output, entityItem);
// create HA config // create HA config
if (Mqtt::ha_enabled() && !ha_registered_) { 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 // count only entities with valid value or command to show in dashboard
uint8_t WebCustomEntityService::count_entities() { uint8_t WebCustomEntityService::count_entities() {
if (customEntityItems_->size() == 0) { if (customEntityItems_->empty()) {
return 0; return 0;
} }
JsonDocument doc; JsonDocument doc;
JsonObject output = doc.to<JsonObject>(); JsonObject output = doc.to<JsonObject>();
uint8_t count = 0; uint8_t count = 0;
for (const CustomEntityItem & entity : *customEntityItems_) {
for (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++;
} }
@@ -558,7 +558,7 @@ void WebCustomEntityService::generate_value_web(JsonObject output) {
void WebCustomEntityService::fetch() { void WebCustomEntityService::fetch() {
const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3, 4}; 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 if (entity.device_id > 0 && entity.type_id > 0) { // this excludes also RAM type
bool needFetch = true; bool needFetch = true;
uint8_t fetchblock = entity.type_id > 0x0FF ? 25 : 27; 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 // called on process telegram, read from telegram

View File

@@ -55,14 +55,14 @@ class WebCustomEntityService : public StatefulService<WebCustomEntity> {
WebCustomEntityService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); WebCustomEntityService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin(); void begin();
void publish_single(const CustomEntityItem & entity); void publish_single(CustomEntityItem & entity);
void publish(const bool force = false); void publish(const bool force = false);
bool command_setvalue(const char * value, const int8_t id, const char * name); bool command_setvalue(const char * value, const int8_t id, const char * name);
bool get_value_info(JsonObject output, const char * cmd); 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<const Telegram> telegram); bool get_value(std::shared_ptr<const Telegram> telegram);
void fetch(); 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 show_values(JsonObject output);
void generate_value_web(JsonObject output); void generate_value_web(JsonObject output);

View File

@@ -134,7 +134,7 @@ bool WebSchedulerService::command_setvalue(const char * value, const int8_t id,
// process json output for info/commands and value_info // process json output for info/commands and value_info
bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) { bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
if (scheduleItems_->size() == 0) { if (scheduleItems_->empty()) {
return true; return true;
} }
@@ -233,7 +233,7 @@ void WebSchedulerService::publish(const bool force) {
return; return;
} }
if (scheduleItems_->size() == 0) { if (scheduleItems_->empty()) {
return; return;
} }
@@ -449,7 +449,7 @@ void WebSchedulerService::loop() {
static uint32_t last_uptime_sec = 0; static uint32_t last_uptime_sec = 0;
// get list of scheduler events and exit if it's empty // get list of scheduler events and exit if it's empty
if (scheduleItems_->size() == 0) { if (scheduleItems_->empty()) {
return; return;
} }
@@ -584,7 +584,7 @@ void WebSchedulerService::test() {
test_value = "(custom/seltemp)"; test_value = "(custom/seltemp)";
command("test5", test_cmd.c_str(), compute(test_value).c_str()); 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)"; test_value = "(boiler/outdoortemp)";
command("test6", test_cmd.c_str(), compute(test_value).c_str()); command("test6", test_cmd.c_str(), compute(test_value).c_str());

View File

@@ -24,13 +24,13 @@
namespace emsesp { namespace emsesp {
WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager) { WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager)
: _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 // POST - generic action handler
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 +146,55 @@ 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 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); 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 +203,40 @@ 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 // action = allvalues
void WebStatusService::exportData(AsyncWebServerRequest * request) { // output all the devices and the values
auto * response = new AsyncJsonResponse(); void WebStatusService::allvalues(JsonObject output) {
JsonObject root = response->getRoot(); 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<JsonObject>();
emsdevice->get_value_info(device_output, value, DeviceValueTAG::TAG_NONE);
}
// Custom Entities
device_output = output["Custom Entities"].to<JsonObject>();
EMSESP::webCustomEntityService.get_value_info(device_output, value);
// Scheduler
device_output = output["Scheduler"].to<JsonObject>();
EMSESP::webSchedulerService.get_value_info(device_output, value);
// Sensors
device_output = output["Analog Sensors"].to<JsonObject>();
EMSESP::analogsensor_.get_value_info(device_output, value);
device_output = output["Temperature Sensors"].to<JsonObject>();
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; root["type"] = type;
if (type == "settings") { if (type == "settings") {
@@ -188,13 +254,44 @@ void WebStatusService::exportData(AsyncWebServerRequest * request) {
System::extractSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", root); System::extractSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", root);
} else if (type == "entities") { } else if (type == "entities") {
System::extractSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", root); System::extractSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", root);
} else if (type == "allvalues") {
root.clear(); // don't need the "type" key
allvalues(root);
} else { } else {
request->send(400); return false;
return; }
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(); // read the contents of the file into the root output json object
request->send(response); 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 } // namespace emsesp

View File

@@ -2,8 +2,7 @@
#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_ACTION_SERVICE_PATH "/rest/action"
#define EMSESP_EXPORT_DATA_SERVICE_PATH "/rest/exportData"
#include <semver200.h> // for version checking #include <semver200.h> // for version checking
@@ -13,10 +12,23 @@ class WebStatusService {
public: public:
WebStatusService(AsyncWebServer * server, SecurityManager * securityManager); 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 systemStatus(AsyncWebServerRequest * request);
void checkUpgrade(AsyncWebServerRequest * request, JsonVariant json); void action(AsyncWebServerRequest * request, JsonVariant json);
void exportData(AsyncWebServerRequest * request);
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 } // namespace emsesp