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
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": {
"connectionId": "emsesp",
"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
- command 'show log' that lists out the current weblog buffer, showing last messages.
- default web log buffer to 25 lines for ESP32s with no PSRAM
- Try and determine correct board profile if none is set
- try and determine correct board profile if none is set during boot
- auto Scroll in WebLog UI - reduced delay so incoming logs are faster
- uploading custom support info for Guest users [#2054]<https://github.com/emsesp/EMS-ESP32/issues/2054>
## Fixed
@@ -72,6 +73,7 @@ For more details go to [www.emsesp.org](https://www.emsesp.org/).
- WebLog UI matches color schema of the terminal console correctly
- Updated Web libraries, ArduinoJson
- Help page doesn't show detailed tech info if the user is not 'admin' role [#2054](https://github.com/emsesp/EMS-ESP32/issues/2054)
- removed system command `allvalues` and moved to an action called `export`
- Show ems-esp internal devices in device list of system/info
- Scheduler and mqtt run async on systems with psram
- Show IPv6 address type (local/global/ula) in log

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { useContext } from 'react';
import { useContext, useState } from 'react';
import { toast } from 'react-toastify';
import CommentIcon from '@mui/icons-material/CommentTwoTone';
@@ -16,18 +16,17 @@ import {
ListItemAvatar,
ListItemButton,
ListItemText,
Paper,
Stack,
Typography,
styled
Typography
} from '@mui/material';
import { useRequest } from 'alova/client';
import { SectionContent, useLayoutTitle } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import { saveFile } from 'utils/file';
import { API } from '../../api/app';
import { API, callAction } from '../../api/app';
import type { APIcall } from './types';
const Help = () => {
@@ -36,131 +35,150 @@ const Help = () => {
const { me } = useContext(AuthenticatedContext);
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
immediate: false
}).onSuccess((event) => {
const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(
new Blob([JSON.stringify(event.data, null, 2)], {
type: 'text/plain'
})
);
const [customSupportIMG, setCustomSupportIMG] = useState<string | null>(null);
const [customSupportHTML, setCustomSupportHTML] = useState<string | null>(null);
anchor.download =
'emsesp_' + event.args[0].device + '_' + event.args[0].entity + '.txt';
anchor.click();
URL.revokeObjectURL(anchor.href);
toast.info(LL.DOWNLOAD_SUCCESSFUL());
useRequest(() => callAction({ action: 'customSupport' })).onSuccess((event) => {
if (event && event.data && Object.keys(event.data).length !== 0) {
const data = event.data.Support;
if (data.img_url) {
setCustomSupportIMG(data.img_url);
}
if (data.html) {
setCustomSupportHTML(data.html.join('<br/>'));
}
}
});
const callAPI = async (device: string, cmd: string) => {
await sendAPI({ device, cmd, id: 0 }).catch((error: Error) => {
const { send: sendExportAllValues } = useRequest(
() => callAction({ action: 'export', param: 'allvalues' }),
{
immediate: false
}
)
.onSuccess((event) => {
saveFile(event.data, 'allvalues', '.txt');
toast.info(LL.DOWNLOAD_SUCCESSFUL());
})
.onError((error) => {
toast.error(error.message);
});
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
immediate: false
})
.onSuccess((event) => {
saveFile(event.data, 'system_info', '.json');
toast.info(LL.DOWNLOAD_SUCCESSFUL());
})
.onError((error) => {
toast.error(error.message);
});
};
return (
<>
<SectionContent>
{me.admin ? (
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListItem>
<ListItemButton component="a" href="https://emsesp.org">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#72caf9' }}>
<MenuBookIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.HELP_INFORMATION_1()} />
</ListItemButton>
</ListItem>
<SectionContent>
<Stack
padding={1}
mb={2}
direction="row"
divider={<Divider orientation="vertical" flexItem />}
sx={{
borderRadius: 3,
border: '2px solid grey',
justifyContent: 'space-evenly',
alignItems: 'center'
}}
>
<Typography variant="subtitle1">
{customSupportHTML ? (
<div dangerouslySetInnerHTML={{ __html: customSupportHTML }} />
) : (
LL.HELP_INFORMATION_5()
)}
</Typography>
<Box
component="img"
referrerPolicy="no-referrer"
sx={{
maxHeight: { xs: 100, md: 250 }
}}
src={customSupportIMG || 'https://emsesp.org/_media/images/installer.jpeg'}
/>
</Stack>
<ListItem>
<ListItemButton component="a" href="https://discord.gg/3J3GgnzpyT">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#72caf9' }}>
<CommentIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.HELP_INFORMATION_2()} />
</ListItemButton>
</ListItem>
{me.admin && (
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
<ListItem>
<ListItemButton component="a" href="https://emsesp.org">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#72caf9' }}>
<MenuBookIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.HELP_INFORMATION_1()} />
</ListItemButton>
</ListItem>
<ListItem>
<ListItemButton
component="a"
href="https://github.com/emsesp/EMS-ESP32/issues/new/choose"
>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#72caf9' }}>
<GitHubIcon />
</Avatar>
</ListItemAvatar>
<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>
)}
<ListItem>
<ListItemButton component="a" href="https://discord.gg/3J3GgnzpyT">
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#72caf9' }}>
<CommentIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.HELP_INFORMATION_2()} />
</ListItemButton>
</ListItem>
<Box p={2} color="warning.main">
<Typography mb={1} variant="body2">
{LL.HELP_INFORMATION_4()}
</Typography>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => callAPI('system', 'info')}
>
{LL.DOWNLOAD(1)}&nbsp;{LL.SUPPORT_INFORMATION(0)}
</Button>
</Box>
<ListItem>
<ListItemButton
component="a"
href="https://github.com/emsesp/EMS-ESP32/issues/new/choose"
>
<ListItemAvatar>
<Avatar sx={{ bgcolor: '#72caf9' }}>
<GitHubIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={LL.HELP_INFORMATION_3()} />
</ListItemButton>
</ListItem>
</List>
)}
<Box p={2} color="warning.main">
<Typography mb={1} variant="body2">
{LL.HELP_INFORMATION_4()}
</Typography>
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
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>
</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}>
&copy;&nbsp;
<Link target="_blank" href="https://emsesp.org" color="primary">
{'emsesp.org'}
</Link>
</Typography>
</SectionContent>
</>
<Divider sx={{ mt: 4 }} />
<Typography color="white" variant="subtitle1" align="center" mt={1}>
&copy;&nbsp;
<Link target="_blank" href="https://emsesp.org" color="primary">
{'emsesp.org'}
</Link>
</Typography>
</SectionContent>
);
};

View File

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

View File

@@ -18,13 +18,8 @@ import {
import Grid from '@mui/material/Grid2';
import * as SystemApi from 'api/system';
import { API, exportData } from 'api/app';
import {
checkUpgrade,
getDevVersion,
getStableVersion,
uploadURL
} from 'api/system';
import { API, callAction } from 'api/app';
import { getDevVersion, getStableVersion } from 'api/system';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova/client';
@@ -37,6 +32,7 @@ import {
useLayoutTitle
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { saveFile } from 'utils/file';
const DownloadUpload = () => {
const { LL } = useI18nContext();
@@ -46,11 +42,23 @@ const DownloadUpload = () => {
const [useDev, setUseDev] = useState<boolean>(false);
const [upgradeAvailable, setUpgradeAvailable] = useState<boolean>(false);
const { send: sendExportData } = useRequest((type: string) => exportData(type), {
immediate: false
})
const { send: sendCheckUpgrade } = useRequest(
(version: string) => callAction({ action: 'checkUpgrade', param: version }),
{
immediate: false
}
).onSuccess((event) => {
setUpgradeAvailable((event.data as { upgradeable: boolean }).upgradeable);
});
const { send: sendExportData } = useRequest(
(type: string) => callAction({ action: 'export', param: type }),
{
immediate: false
}
)
.onSuccess((event) => {
saveFile(event.data, event.args[0]);
saveFile(event.data, event.args[0], '.json');
toast.info(LL.DOWNLOAD_SUCCESSFUL());
})
.onError((error) => {
@@ -61,14 +69,10 @@ const DownloadUpload = () => {
immediate: false
});
const {
data: data,
send: loadData,
error
} = useRequest(SystemApi.readSystemStatus);
const { data, send: loadData, error } = useRequest(SystemApi.readSystemStatus);
const { send: sendUploadURL } = useRequest(
(data: { url: string }) => uploadURL(data),
(url: string) => callAction({ action: 'uploadURL', param: url }),
{
immediate: false
}
@@ -83,12 +87,6 @@ const DownloadUpload = () => {
);
};
const { send: sendCheckUpgrade } = useRequest(checkUpgrade, {
immediate: false
}).onSuccess((event) => {
setUpgradeAvailable(event.data.upgradeable);
});
// called immediately to get the latest version, on page load
const { data: latestVersion } = useRequest(getStableVersion, {
// uncomment next 2 lines for testing, uses https://github.com/emsesp/EMS-ESP32/releases/download/v3.6.5/EMS-ESP-3_6_5-ESP32-16MB+.bin
@@ -102,7 +100,7 @@ const DownloadUpload = () => {
// immediate: false,
// initialData: '3.7.0-dev.32'
}).onSuccess((event) => {
void sendCheckUpgrade({ version: event.data });
void sendCheckUpgrade(event.data);
});
const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/';
@@ -136,24 +134,12 @@ const DownloadUpload = () => {
};
const installFirmwareURL = async (url: string) => {
await sendUploadURL({ url: url }).catch((error: Error) => {
await sendUploadURL(url).catch((error: Error) => {
toast.error(error.message);
});
setRestarting(true);
};
const saveFile = (json: unknown, filename: string) => {
const anchor = document.createElement('a');
anchor.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain'
})
);
anchor.download = 'emsesp_' + filename + '.json';
anchor.click();
URL.revokeObjectURL(anchor.href);
};
useLayoutTitle(LL.DOWNLOAD_UPLOAD());
const internet_live =

View File

@@ -45,7 +45,7 @@ const RestartMonitor = () => {
return (
<Dialog fullWidth={true} sx={dialogStyle} open={true}>
<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
color="secondary"
variant="h6"
@@ -69,7 +69,7 @@ const RestartMonitor = () => {
<MessageBox my={2} level="error" message={errorMessage} />
) : (
<Box py={2}>
<CircularProgress size={48} />
<CircularProgress size={32} />
</Box>
)}
</Box>

View File

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

View File

@@ -12,6 +12,7 @@ import './dragNdrop.css';
const DragNdrop = ({ onFileSelected }) => {
const [file, setFile] = useState<File>();
const [dragged, setDragged] = useState(false);
const inputRef = useRef<HTMLInputElement | null>(null);
const { LL } = useI18nContext();
@@ -45,6 +46,7 @@ const DragNdrop = ({ onFileSelected }) => {
const handleRemoveFile = (event) => {
event.stopPropagation();
setFile(undefined);
setDragged(false);
};
const handleUploadClick = (event) => {
@@ -56,11 +58,17 @@ const DragNdrop = ({ onFileSelected }) => {
inputRef.current?.click();
};
const handleDragOver = (event) => {
event.preventDefault(); // prevent file from being opened
setDragged(true);
};
return (
<div
className={`document-uploader ${file ? 'upload-box active' : 'upload-box'}`}
className={`document-uploader ${file || dragged ? 'active' : ''}`}
onDrop={handleDrop}
onDragOver={(event) => event.preventDefault()}
onDragOver={handleDragOver}
onDragLeave={() => setDragged(false)}
onClick={handleBrowseClick}
>
<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
linkType: hard
"@types/node@npm:^22.7.3":
version: 22.7.3
resolution: "@types/node@npm:22.7.3"
"@types/node@npm:^22.7.4":
version: 22.7.4
resolution: "@types/node@npm:22.7.4"
dependencies:
undici-types: "npm:~6.19.2"
checksum: 10c0/0e579813528b0370454337a952f43b792cd12731e10fdca0fdb627158e980c1219bba99e9048c134b6a19325d817016059afe016ccd372326c838a1b85a51574
checksum: 10c0/c22bf54515c78ff3170142c1e718b90e2a0003419dc2d55f79c9c9362edd590a6ab1450deb09ff6e1b32d1b4698da407930b16285e8be3a009ea6cd2695cac01
languageName: node
linkType: hard
@@ -1518,13 +1518,13 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:^18.3.9":
version: 18.3.9
resolution: "@types/react@npm:18.3.9"
"@types/react@npm:^18.3.10":
version: 18.3.10
resolution: "@types/react@npm:18.3.10"
dependencies:
"@types/prop-types": "npm:*"
csstype: "npm:^3.0.2"
checksum: 10c0/a92b8e061d0c833e096254782c56a802316593f4a907fb834b557cabe848a0829b9eb6056404ea239eb4d5ec5ac7b7724309761516c0a7a277916fa04dd4f805
checksum: 10c0/f5be1de1b0331c1fdb33d577f4cf7f1b949d4bded5347b2351a537f03c51dade5be115e21b161dcf1b37061954d320f6a0bdf8d7b70e24eda51071fdd614383d
languageName: node
linkType: hard
@@ -1546,15 +1546,15 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.7.0":
version: 8.7.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.7.0"
"@typescript-eslint/eslint-plugin@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.8.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.7.0"
"@typescript-eslint/type-utils": "npm:8.7.0"
"@typescript-eslint/utils": "npm:8.7.0"
"@typescript-eslint/visitor-keys": "npm:8.7.0"
"@typescript-eslint/scope-manager": "npm:8.8.0"
"@typescript-eslint/type-utils": "npm:8.8.0"
"@typescript-eslint/utils": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.8.0"
graphemer: "npm:^1.4.0"
ignore: "npm:^5.3.1"
natural-compare: "npm:^1.4.0"
@@ -1565,66 +1565,66 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/f04d6fa6a30e32d51feba0f08789f75ca77b6b67cfe494bdbd9aafa241871edc918fa8b344dc9d13dd59ae055d42c3920f0e542534f929afbfdca653dae598fa
checksum: 10c0/98ac37587eda02a713710f0a62ca979833482024968f1d1735881718abe102a6b49707db4f1dac0d7c731d1cbf8111d829c5125348d4829ab6fad7a7b3b344e4
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.7.0":
version: 8.7.0
resolution: "@typescript-eslint/parser@npm:8.7.0"
"@typescript-eslint/parser@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/parser@npm:8.8.0"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.7.0"
"@typescript-eslint/types": "npm:8.7.0"
"@typescript-eslint/typescript-estree": "npm:8.7.0"
"@typescript-eslint/visitor-keys": "npm:8.7.0"
"@typescript-eslint/scope-manager": "npm:8.8.0"
"@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/typescript-estree": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.8.0"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/1d5020ff1f5d3eb726bc6034d23f0a71e8fe7a713756479a0a0b639215326f71c0b44e2c25cc290b4e7c144bd3c958f1405199711c41601f0ea9174068714a64
checksum: 10c0/cf72a644b89c62cd55b09fa1d22b51a2c726714aac344a797f0c2ad80bfbabcb7567000fadd4ea8188aa1d923675bebdca06acc1d28ac1b8360bf28a36b46f3a
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.7.0":
version: 8.7.0
resolution: "@typescript-eslint/scope-manager@npm:8.7.0"
"@typescript-eslint/scope-manager@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/scope-manager@npm:8.8.0"
dependencies:
"@typescript-eslint/types": "npm:8.7.0"
"@typescript-eslint/visitor-keys": "npm:8.7.0"
checksum: 10c0/8b731a0d0bd3e8f6a322b3b25006f56879b5d2aad86625070fa438b803cf938cb8d5c597758bfa0d65d6e142b204dc6f363fa239bc44280a74e25aa427408eda
"@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.8.0"
checksum: 10c0/29ddf589ff0e465dbbf3eb87b79a29face4ec5a6cb617bbaafbac6ae8340d376b5b405bca762ee1c7a40cbdf7912a32734f9119f6864df048c7a0b2de21bdd3d
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.7.0":
version: 8.7.0
resolution: "@typescript-eslint/type-utils@npm:8.7.0"
"@typescript-eslint/type-utils@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/type-utils@npm:8.8.0"
dependencies:
"@typescript-eslint/typescript-estree": "npm:8.7.0"
"@typescript-eslint/utils": "npm:8.7.0"
"@typescript-eslint/typescript-estree": "npm:8.8.0"
"@typescript-eslint/utils": "npm:8.8.0"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.3.0"
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/2bd9fb93a50ff1c060af41528e39c775ae93b09dd71450defdb42a13c68990dd388460ae4e81fb2f4a49c38dc12152c515d43e845eca6198c44b14aab66733bc
checksum: 10c0/d6ee11f4686fb54daea1f436f73b96eb31a95f6e535abc0534abf5794e7597669a92d12300969c8afee0fc1912dbc1591664f7e37f0da5935016cc981b2921a8
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.7.0":
version: 8.7.0
resolution: "@typescript-eslint/types@npm:8.7.0"
checksum: 10c0/f7529eaea4ecc0f5e2d94ea656db8f930f6d1c1e65a3ffcb2f6bec87361173de2ea981405c2c483a35a927b3bdafb606319a1d0395a6feb1284448c8ba74c31e
"@typescript-eslint/types@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/types@npm:8.8.0"
checksum: 10c0/cd168fafcaf77641b023c4405ea3a8c30fbad1737abb5aec9fce67fe2ae20224b624b5a2e3e84900ba81dc7dd33343add3653763703a225326cc81356b182d09
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.7.0":
version: 8.7.0
resolution: "@typescript-eslint/typescript-estree@npm:8.7.0"
"@typescript-eslint/typescript-estree@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/typescript-estree@npm:8.8.0"
dependencies:
"@typescript-eslint/types": "npm:8.7.0"
"@typescript-eslint/visitor-keys": "npm:8.7.0"
"@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/visitor-keys": "npm:8.8.0"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
@@ -1634,31 +1634,31 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/d714605b6920a9631ab1511b569c1c158b1681c09005ab240125c442a63e906048064151a61ce5eb5f8fe75cea861ce5ae1d87be9d7296b012e4ab6d88755e8b
checksum: 10c0/9b9e849f6b2d4e250840ef8e05f55a97d6598adaf48c1e6df83084b94c30feca6a3e7916ee1c235178188d0db6364a877cbf8fe218c36d5f8d5acb50767f3273
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.7.0":
version: 8.7.0
resolution: "@typescript-eslint/utils@npm:8.7.0"
"@typescript-eslint/utils@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/utils@npm:8.8.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0"
"@typescript-eslint/scope-manager": "npm:8.7.0"
"@typescript-eslint/types": "npm:8.7.0"
"@typescript-eslint/typescript-estree": "npm:8.7.0"
"@typescript-eslint/scope-manager": "npm:8.8.0"
"@typescript-eslint/types": "npm:8.8.0"
"@typescript-eslint/typescript-estree": "npm:8.8.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
checksum: 10c0/7355b754ce2fc118773ed27a3e02b7dfae270eec73c2d896738835ecf842e8309544dfd22c5105aba6cae2787bfdd84129bbc42f4b514f57909dc7f6890b8eba
checksum: 10c0/fcf2dfd4a2d9491aa096a29c2c1fdd891ca3c13933d20cfea44e51b3d10a397e7ed9a9cd71ac9a29e8c4706264ae00c25a29394e2a6bda3291be298062901f2c
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.7.0":
version: 8.7.0
resolution: "@typescript-eslint/visitor-keys@npm:8.7.0"
"@typescript-eslint/visitor-keys@npm:8.8.0":
version: 8.8.0
resolution: "@typescript-eslint/visitor-keys@npm:8.8.0"
dependencies:
"@typescript-eslint/types": "npm:8.7.0"
"@typescript-eslint/types": "npm:8.8.0"
eslint-visitor-keys: "npm:^3.4.3"
checksum: 10c0/1240da13c15f9f875644b933b0ad73713ef12f1db5715236824c1ec359e6ef082ce52dd9b2186d40e28be6a816a208c226e6e9af96e5baeb24b4399fe786ae7c
checksum: 10c0/580ce74c9b09b9e6a6f3f0ac2d2f0c6a6b983a78ce3b2544822ee08107c57142858d674897f61ff32a9a5e8fca00c916545c159bb75d134f4380884642542d38
languageName: node
linkType: hard
@@ -1678,11 +1678,11 @@ __metadata:
"@table-library/react-table-library": "npm:4.1.7"
"@trivago/prettier-plugin-sort-imports": "npm:^4.3.0"
"@types/formidable": "npm:^3"
"@types/node": "npm:^22.7.3"
"@types/react": "npm:^18.3.9"
"@types/node": "npm:^22.7.4"
"@types/react": "npm:^18.3.10"
"@types/react-dom": "npm:^18.3.0"
"@types/react-router-dom": "npm:^5.3.3"
alova: "npm:3.0.16"
alova: "npm:3.0.17"
async-validator: "npm:^4.2.5"
concurrently: "npm:^9.0.1"
eslint: "npm:^9.11.1"
@@ -1698,10 +1698,10 @@ __metadata:
react-router-dom: "npm:^6.26.2"
react-toastify: "npm:^10.0.5"
rollup-plugin-visualizer: "npm:^5.12.0"
terser: "npm:^5.34.0"
terser: "npm:^5.34.1"
typesafe-i18n: "npm:^5.26.2"
typescript: "npm:^5.6.2"
typescript-eslint: "npm:8.7.0"
typescript-eslint: "npm:8.8.0"
vite: "npm:^5.4.8"
vite-plugin-imagemin: "npm:^0.6.1"
vite-tsconfig-paths: "npm:^5.0.1"
@@ -1764,13 +1764,13 @@ __metadata:
languageName: node
linkType: hard
"alova@npm:3.0.16":
version: 3.0.16
resolution: "alova@npm:3.0.16"
"alova@npm:3.0.17":
version: 3.0.17
resolution: "alova@npm:3.0.17"
dependencies:
"@alova/shared": "npm:^1.0.5"
rate-limiter-flexible: "npm:^5.0.3"
checksum: 10c0/66cb597f4f00feda04b7619dd852fde92bc920cc97b018be70791240c8e8c64677a998a02a684f3aace5997322236a677264f25afe6bcaf4ec856ae42be859a8
checksum: 10c0/e8a2ae885a3ff44dafec230d9388dc22b6445bb0cf8511fc9855b5a98ad9961941b0d33a7da874df23db4af0dba75872a470e3edebbdcc5ead8aecbc7fcc3d6b
languageName: node
linkType: hard
@@ -6654,9 +6654,9 @@ __metadata:
languageName: node
linkType: hard
"terser@npm:^5.34.0":
version: 5.34.0
resolution: "terser@npm:5.34.0"
"terser@npm:^5.34.1":
version: 5.34.1
resolution: "terser@npm:5.34.1"
dependencies:
"@jridgewell/source-map": "npm:^0.3.3"
acorn: "npm:^8.8.2"
@@ -6664,7 +6664,7 @@ __metadata:
source-map-support: "npm:~0.5.20"
bin:
terser: bin/terser
checksum: 10c0/74e8ef4e565e5600415cd9377a90eed419b8076465d453c0c76aef4053c45371512d2de76c34d01e004cdd49ea5a749d77eeb343f7e665b2d172158ca08ba23e
checksum: 10c0/51c7d704c5c4ae88bf937124112c9972aed4e1fd29d805cc2d86e0f54cd631ecd4e69db5bb3c1e3b450c741c86e2313328bea0fde925329e8a31a07a7941723c
languageName: node
linkType: hard
@@ -6803,17 +6803,17 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:8.7.0":
version: 8.7.0
resolution: "typescript-eslint@npm:8.7.0"
"typescript-eslint@npm:8.8.0":
version: 8.8.0
resolution: "typescript-eslint@npm:8.8.0"
dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.7.0"
"@typescript-eslint/parser": "npm:8.7.0"
"@typescript-eslint/utils": "npm:8.7.0"
"@typescript-eslint/eslint-plugin": "npm:8.8.0"
"@typescript-eslint/parser": "npm:8.8.0"
"@typescript-eslint/utils": "npm:8.8.0"
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/c0c3f909227c664f193d11a912851d6144a7cfcc0ac5e57f695c3e50679ef02bb491cc330ad9787e00170ce3be3a3b8c80bb81d5e20a40c1b3ee713ec3b0955a
checksum: 10c0/545f0ce051282921aff56288baf288cffe6f7bafee5149f1b87af2c67f81f8c2088924a2e0fc0f0dcd12692b6a97eca10149a619c8c85d4aaef2fe763938da8d
languageName: node
linkType: hard

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) {
handleUpload(request, filename, index, data, len, final);
});
// upload from a URL
server->on(UPLOAD_URL_PATH,
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { uploadURL(request, json); },
AuthenticationPredicates::IS_AUTHENTICATED));
}
void UploadFileService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
@@ -91,7 +86,6 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
Update.setMD5(_md5.data());
_md5.front() = '\0';
}
// emsesp::EMSESP::system_.upload_status(true); // force just in case, this is stop UART, MQTT and other services
request->onDisconnect([this] { handleEarlyDisconnect(); }); // success, let's make sure we end the update if the client hangs up
} else {
handleError(request, 507); // failed to begin, send an error response Insufficient Storage
@@ -171,15 +165,3 @@ void UploadFileService::handleEarlyDisconnect() {
_is_firmware = false;
Update.abort();
}
// upload firmware from a URL, like GitHub Release assets, Cloudflare R2 or Amazon S3
void UploadFileService::uploadURL(AsyncWebServerRequest * request, JsonVariant json) {
if (json.is<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>
#define UPLOAD_FILE_PATH "/rest/uploadFile"
#define UPLOAD_URL_PATH "/rest/uploadURL"
#define TEMP_FILENAME_PATH "/tmp_upload" // for uploaded json files
@@ -28,7 +27,6 @@ class UploadFileService {
void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final);
void uploadComplete(AsyncWebServerRequest * request);
void handleError(AsyncWebServerRequest * request, int code);
void uploadURL(AsyncWebServerRequest * request, JsonVariant json);
void handleEarlyDisconnect();
};

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
// LOG
@@ -402,7 +457,6 @@ const EMSESP_DEVICEDATA_ENDPOINT2 = REST_ENDPOINT_ROOT + 'deviceData/:id?';
const EMSESP_DEVICEENTITIES_ENDPOINT1 = REST_ENDPOINT_ROOT + 'deviceEntities';
const EMSESP_DEVICEENTITIES_ENDPOINT2 = REST_ENDPOINT_ROOT + 'deviceEntities/:id?';
const EMSESP_CHECK_UPGRADE_ENDPOINT = REST_ENDPOINT_ROOT + 'checkUpgrade';
const EMSESP_BOARDPROFILE_ENDPOINT = REST_ENDPOINT_ROOT + 'boardProfile';
const EMSESP_WRITE_DEVICEVALUE_ENDPOINT = REST_ENDPOINT_ROOT + 'writeDeviceValue';
const EMSESP_WRITE_DEVICENAME_ENDPOINT = REST_ENDPOINT_ROOT + 'writeDeviceName';
@@ -416,7 +470,8 @@ const EMSESP_RESET_CUSTOMIZATIONS_ENDPOINT =
const EMSESP_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'schedule';
const EMSESP_CUSTOMENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'customEntities';
const EMSESP_MODULES_ENDPOINT = REST_ENDPOINT_ROOT + 'modules';
const EMSESP_EXPORT_DATA_ENDPOINT = REST_ENDPOINT_ROOT + 'exportData';
const EMSESP_ACTION_ENDPOINT = REST_ENDPOINT_ROOT + 'action';
// these are used in the API calls only
const EMSESP_SYSTEM_INFO_ENDPOINT = API_ENDPOINT_ROOT + 'system/info';
@@ -4081,16 +4136,10 @@ router
router
.get(ACTIVITY_ENDPOINT, () => activity)
.get(SYSTEM_STATUS_ENDPOINT, () => {
if (countHardwarePoll === 0) {
console.log('Resetting hardware count...');
}
if (countHardwarePoll >= 2) {
countHardwarePoll = 0;
system_status.status = 'ready';
}
console.log('Hardware count ' + countHardwarePoll + ' of 2');
countHardwarePoll++;
return system_status;
@@ -4402,17 +4451,6 @@ router
return status(200);
})
// check upgrade
.post(EMSESP_CHECK_UPGRADE_ENDPOINT, async (request: any) => {
const content = await request.json();
console.log('check upgrade from ', content.version);
const data = {
upgradeable: true
// upgradeable: false
};
return data;
})
// Settings - board profile
.get(EMSESP_BOARDPROFILE_ENDPOINT, (request) => {
const board_profile = request.query.boardProfile;
@@ -4549,30 +4587,28 @@ router
return data;
})
// Download Settings
.get(EMSESP_EXPORT_DATA_ENDPOINT, (request) => {
const type = request.query.type;
console.log('exporting ' + type + ' data');
switch (type) {
case 'settings':
return emsesp_info;
case 'customizations':
return emsesp_deviceentities_2; // fixed for one device
case 'entities':
return emsesp_customentities;
case 'schedule':
return emsesp_schedule;
case 'modules':
return emsesp_modules;
default:
return status(404);
// generic action for all /rest/... endpoints
// takes an action and param in JSON
.post(EMSESP_ACTION_ENDPOINT, async (request: any) => {
const content = await request.json();
if (content.hasOwnProperty('action')) {
const action = content.action;
if (action === 'export') {
// export data
return export_data(content.param);
} else if (action === 'customSupport') {
// send custom support
return custom_support();
} else if (action === 'checkUpgrade') {
// check upgrade
return check_upgrade(content.param);
} else if (action === 'uploadURL') {
// upload URL
console.log('upload File from URL', content.param);
return status(200);
}
}
})
// upload URL
.post('/rest/uploadURL', () => {
console.log('upload File from URL');
return status(200);
return status(404); // cmd not found
})
// API which are usually POST for security
@@ -4594,9 +4630,6 @@ router
if (cmd === 'info') {
return emsesp_info;
}
if (cmd === 'allvalues') {
return emsesp_allvalues;
}
if (cmd === 'format') {
console.log('formatting...');
return status(200);

View File

@@ -39,7 +39,7 @@ unbuild_flags =
${common.core_unbuild_flags}
[espressif32_base]
platform = espressif32@6.8.1
platform = espressif32@6.9.0
framework = arduino
board_build.filesystem = littlefs
build_flags =
@@ -78,7 +78,7 @@ check_flags =
clangtidy: --checks=-*,clang-analyzer-*,performance-*
lib_ldf_mode = chain+
lib_deps =
https://github.com/emsesp/EMS-ESP-Modules.git@1.0.1
https://github.com/emsesp/EMS-ESP-Modules.git@1.0.2
;
; builds for GitHub Actions CI

View File

@@ -1345,4 +1345,7 @@ zulufttemp
zyxwvutsrqponmlkjihgfedcba
Omea
Bolv
hardwarestatus
hardwarestatus
hpcurrpower
hppowerlimit
CUSTOMSUPPORT

View File

@@ -102,9 +102,6 @@ void AnalogSensor::reload(bool get_nvs) {
}
}
if (!found) {
// if (!System::is_valid_gpio(sensor.gpio)) {
// continue;
// }
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type);
sensors_.back().ha_registered = false; // this will trigger recreate of the HA config
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) {
@@ -610,7 +607,7 @@ void AnalogSensor::publish_values(const bool force) {
// see if we need to create the [devs] discovery section, as this needs only to be done once for all sensors
bool is_ha_device_created = false;
for (auto & sensor : sensors_) {
for (auto const & sensor : sensors_) {
if (sensor.ha_registered) {
is_ha_device_created = true;
break;

View File

@@ -420,7 +420,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
} else {
if (single_command) {
// log as DEBUG (TRACE) regardless if compiled with EMSESP_DEBUG
logger_.debug(("%sCalled command %s"), ro.c_str(), info_s);
logger_.debug("%sCalled command %s", ro.c_str(), info_s);
} else {
if (id > 0) {
LOG_INFO(("%sCalled command %s with value %s and id %d on device 0x%02X"), ro.c_str(), info_s, value, id, device_id);
@@ -499,7 +499,7 @@ void Command::erase_command(const uint8_t device_type, const char * cmd, uint8_t
return;
}
auto it = cmdfunctions_.begin();
for (auto & cf : cmdfunctions_) {
for (auto const & cf : cmdfunctions_) {
if (Helpers::toLower(cmd) == Helpers::toLower(cf.cmd_) && (cf.device_type_ == device_type) && ((flag & 0x3F) == (cf.flags_ & 0x3F))) {
cmdfunctions_.erase(it);
return;
@@ -561,16 +561,13 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
}
}
if (!verbose) {
sorted_cmds.push_back(F_(info));
sorted_cmds.push_back(F_(commands));
sorted_cmds.push_back(F_(values));
}
sorted_cmds.sort(); // sort them
// if not in verbose mode, just print them on a single line and exit
if (!verbose) {
sorted_cmds.emplace_front(F_(info));
sorted_cmds.emplace_front(F_(commands));
sorted_cmds.emplace_front(F_(values));
for (const auto & cl : sorted_cmds) {
shell.print(cl);
shell.print(" ");
@@ -582,14 +579,6 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
// verbose mode
shell.printfln("\n%s%s %s:%s", COLOR_BOLD_ON, COLOR_YELLOW, EMSdevice::device_type_2_device_name(device_type), COLOR_RESET);
// we hard code 'info' and 'commands' commands so print them first
shell.printf(" info \t\t\t\t%slist all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
shell.println(COLOR_RESET);
shell.printf(" commands \t\t\t%slist all commands %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
shell.println(COLOR_RESET);
shell.printf(" values \t\t\t%slist all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
shell.println(COLOR_RESET);
for (const auto & cl : sorted_cmds) {
// find and print the description
for (const auto & cf : cmdfunctions_) {
@@ -682,6 +671,13 @@ void Command::show_devices(uuid::console::Shell & shell) {
// calls show with verbose mode set
void Command::show_all(uuid::console::Shell & shell) {
shell.printfln("Showing all available commands (%s*%s=authentication not required):", COLOR_BRIGHT_GREEN, COLOR_RESET);
shell.println("Each device has these additional default commands:");
shell.printf(" info \t\t\t\t%slist all values with description%s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
shell.println(COLOR_RESET);
shell.printf(" commands \t\t\t%slist all commands %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
shell.println(COLOR_RESET);
shell.printf(" values \t\t\t%slist all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
shell.println(COLOR_RESET);
// show system ones first
show(shell, EMSdevice::DeviceType::SYSTEM, true);

View File

@@ -76,54 +76,47 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
//
// Show commands
//
commands->add_command(ShellContext::MAIN, CommandFlags::USER, {F_(show)}, [=](Shell & shell, const std::vector<std::string> & arguments) {
to_app(shell).system_.show_system(shell);
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
string_vector{F_(show), F_(system)},
[=](Shell & shell, const std::vector<std::string> & arguments) { to_app(shell).system_.show_system(shell); });
commands->add_command(
ShellContext::MAIN,
CommandFlags::USER,
{F_(show)},
{F_(show_commands)},
[=](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.empty()) {
to_app(shell).system_.show_system(shell);
return;
}
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
string_vector{F_(show), F_(users)},
[](Shell & shell, const std::vector<std::string> & arguments) { to_app(shell).system_.show_users(shell); });
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
string_vector{F_(show), F_(devices)},
[](Shell & shell, const std::vector<std::string> & arguments) { to_app(shell).show_devices(shell); });
commands->add_command(ShellContext::MAIN, CommandFlags::USER, string_vector{F_(show), F_(log)}, [](Shell & shell, const std::vector<std::string> & arguments) {
to_app(shell).webLogService.show(shell);
});
commands->add_command(ShellContext::MAIN, CommandFlags::USER, string_vector{F_(show), F_(ems)}, [](Shell & shell, const std::vector<std::string> & arguments) {
to_app(shell).show_ems(shell);
});
commands->add_command(ShellContext::MAIN, CommandFlags::USER, string_vector{F_(show), F_(values)}, [](Shell & shell, const std::vector<std::string> & arguments) {
to_app(shell).show_device_values(shell);
to_app(shell).show_sensor_values(shell);
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
string_vector{F_(show), F_(mqtt)},
[](Shell & shell, const std::vector<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); });
auto command = arguments.front();
if (command == F_(commands)) {
Command::show_all(shell);
} else if (command == F_(system)) {
to_app(shell).system_.show_system(shell);
} else if (command == F_(users) && (shell.has_flags(CommandFlags::ADMIN))) {
to_app(shell).system_.show_users(shell); // admin only
} else if (command == F_(devices)) {
to_app(shell).show_devices(shell);
} else if (command == F_(log)) {
to_app(shell).webLogService.show(shell);
} else if (command == F_(ems)) {
to_app(shell).show_ems(shell);
} else if (command == F_(values)) {
to_app(shell).show_device_values(shell);
to_app(shell).show_sensor_values(shell);
} else if (command == F_(mqtt)) {
Mqtt::show_mqtt(shell);
} else {
shell.printfln("Unknown show command");
}
},
[](Shell & shell, const std::vector<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"};
});
//
// System commands
//
#if defined(EMSESP_TEST)
// create commands test
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
const std::string EMSdevice::to_string() {
std::string EMSdevice::to_string() {
// for devices that haven't been lookup yet, don't show all details
if (product_id_ == 0) {
return std::string(name()) + " (DeviceID:" + Helpers::hextoa(device_id_) + ")";
@@ -313,7 +313,7 @@ const std::string EMSdevice::to_string() {
// returns out brand + device name
// translated
const std::string EMSdevice::to_string_short() {
std::string EMSdevice::to_string_short() {
if (brand_ == Brand::NO_BRAND) {
return std::string(device_type_2_device_name_translated()) + ": " + name();
}
@@ -553,11 +553,11 @@ void EMSdevice::add_device_value(int8_t tag, // to b
}
}
uint8_t state = DeviceValueState::DV_DEFAULT; // determine state
std::string custom_fullname = std::string(""); // custom fullname
auto short_name = name[0]; // entity name
bool has_cmd = (f != nullptr); // is it a command?
bool ignore = false; // ignore this entity?
uint8_t state = DeviceValueState::DV_DEFAULT; // determine state
auto custom_fullname = std::string(""); // custom fullname
auto short_name = name[0]; // entity name
bool has_cmd = (f != nullptr); // is it a command?
bool ignore = false; // ignore this entity?
// get fullname, getting translation if it exists
const char * const * fullname;
@@ -1025,7 +1025,7 @@ void EMSdevice::generate_values_web(JsonObject output) {
// this is used only for WebCustomizationService::device_entities()
void EMSdevice::generate_values_web_customization(JsonArray output) {
for (auto & dv : devicevalues_) {
// also show commands and entities that have an empty full name
// also show commands and entities that have an empty fullname
JsonObject obj = output.add<JsonObject>();
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)
, brand_(brand) {
strlcpy(version_, version, sizeof(version_));
custom_name_ = ""; // init custom name to blank
}
// static functions, used outside the class like in console.cpp, command.cpp, emsesp.cpp, mqtt.cpp
@@ -113,7 +112,7 @@ class EMSdevice {
}
// set custom device name
inline void custom_name(std::string & custom_name) {
inline void custom_name(std::string const & custom_name) {
custom_name_ = custom_name;
}
std::string name(); // returns either default or custom name if defined
@@ -205,17 +204,16 @@ class EMSdevice {
int get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result);
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 std::string to_string();
const std::string to_string_short();
const char * brand_to_char();
std::string to_string();
std::string to_string_short();
enum Handlers : uint8_t { ALL, RECEIVED, FETCHED, PENDING, IGNORED };
void show_telegram_handlers(uuid::console::Shell & shell) const;
char * show_telegram_handlers(char * result, const size_t len, const uint8_t handlers);
void show_mqtt_handlers(uuid::console::Shell & shell) const;
// void list_device_entries(JsonObject output) const;
void add_handlers_ignored(const uint16_t handler);
void add_handlers_ignored(const uint16_t handler);
void set_climate_minmax(int8_t tag, int16_t min, uint32_t max);
void setCustomizationEntity(const std::string & entity_id);
@@ -456,15 +454,13 @@ class EMSdevice {
uint8_t count_entities();
bool has_entities() const;
/*
void reserve_device_values(uint8_t elements) {
devicevalues_.reserve(elements);
}
// void reserve_device_values(uint8_t elements) {
// devicevalues_.reserve(elements);
// }
void reserve_telegram_functions(uint8_t elements) {
telegram_functions_.reserve(elements);
}
*/
// void reserve_telegram_functions(uint8_t elements) {
// telegram_functions_.reserve(elements);
// }
#if defined(EMSESP_STANDALONE)
struct TelegramFunctionDump {
@@ -487,10 +483,10 @@ class EMSdevice {
uint8_t device_id_ = 0;
uint8_t product_id_ = 0;
char version_[6];
const char * default_name_; // the fixed name the EMS model taken from the device library
std::string custom_name_; // custom name
uint8_t flags_ = 0;
uint8_t brand_ = Brand::NO_BRAND;
const char * default_name_; // the fixed name the EMS model taken from the device library
std::string custom_name_ = ""; // custom name
uint8_t flags_ = 0;
uint8_t brand_ = Brand::NO_BRAND;
bool ha_config_done_ = false;
bool has_update_ = false;

View File

@@ -208,7 +208,7 @@ void EMSESP::uart_init() {
uint8_t tx_mode = 0;
uint8_t rx_gpio = 0;
uint8_t tx_gpio = 0;
EMSESP::webSettingsService.read([&](WebSettings & settings) {
EMSESP::webSettingsService.read([&](WebSettings const & settings) {
tx_mode = settings.tx_mode;
rx_gpio = settings.rx_gpio;
tx_gpio = settings.tx_gpio;
@@ -279,7 +279,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
if (bus_status() != BUS_STATUS_OFFLINE) {
shell.printfln("EMS Bus info:");
EMSESP::webSettingsService.read([&](WebSettings & settings) { shell.printfln(" Tx mode: %d", settings.tx_mode); });
EMSESP::webSettingsService.read([&](WebSettings const & settings) { shell.printfln(" Tx mode: %d", settings.tx_mode); });
shell.printfln(" Bus protocol: %s", EMSbus::is_ht3() ? "HT3" : "Buderus");
shell.printfln(" #recognized EMS devices: %d", EMSESP::emsdevices.size());
shell.printfln(" #telegrams received: %d", rxservice_.telegram_count());
@@ -1317,7 +1317,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
emsdevices.push_back(EMSFactory::add(device_type, device_id, product_id, version, default_name, flags, brand));
// see if we have a custom device name in our Customizations list, and if so set it
webCustomizationService.read([&](WebCustomization & settings) {
webCustomizationService.read([&](WebCustomization const & settings) {
for (EntityCustomization e : settings.entityCustomizations) {
if ((e.device_id == device_id) && (e.product_id == product_id)) {
LOG_DEBUG("Have customizations for %s with deviceID 0x%02X productID %d", e.custom_name.c_str(), device_id, product_id);

View File

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

View File

@@ -154,6 +154,7 @@ MAKE_WORD_CUSTOM(deviceid_mandatory, "<deviceID>")
MAKE_WORD_CUSTOM(device_type_optional, "[device]")
MAKE_WORD_CUSTOM(invalid_log_level, "Invalid log level")
MAKE_WORD_CUSTOM(log_level_optional, "[level]")
MAKE_WORD_CUSTOM(show_commands, "[system | users | devices | log | ems | values | mqtt | commands")
MAKE_WORD_CUSTOM(name_mandatory, "<name>")
MAKE_WORD_CUSTOM(name_optional, "[name]")
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(entities_cmd, "list all entities", "Liste aller Entitäten", "lijst van alle entiteiten", "", "wyświetl wszsytkie encje", "Viser alle enheter", "", "Tüm varlıkları listele", "elenca tutte le entità", "zobraziť všetky entity") // TODO translate
MAKE_WORD_TRANSLATION(send_cmd, "send a telegram", "Sende EMS-Telegramm", "stuur een telegram", "", "wyślij telegram", "send et telegram", "", "Bir telegram gönder", "invia un telegramma", "poslať telegram") // TODO translate
MAKE_WORD_TRANSLATION(setiovalue_cmd, "set io value", "Setze Werte E/A", "instellen standaardwaarde", "", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io", "nastaviť hodnotu io") // TODO translate
MAKE_WORD_TRANSLATION(setiovalue_cmd, "set I/O value", "Setze Werte E/A", "instellen standaardwaarde", "", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io", "nastaviť hodnotu io") // TODO translate
MAKE_WORD_TRANSLATION(changeloglevel_cmd, "change log level", "Ändere Protokollebene", "aanpassen log niveau", "", "zmień poziom log-u", "endre loggnivå", "", "Kayıt seviyesini değiştir", "cambia livello registrazione", "zmeniť úroveň protokolu") // TODO translate
MAKE_WORD_TRANSLATION(fetch_cmd, "refresh all EMS values", "Aktualisiere alle EMS-Werte", "Verversen alle EMS waardes", "", "odśwież wszystkie wartości EMS", "oppfrisk alle EMS verdier", "", "Bütün EMS değerlerini yenile", "aggiornare tutti i valori EMS", "obnoviť všetky hodnoty EMS") // TODO translate
MAKE_WORD_TRANSLATION(restart_cmd, "restart EMS-ESP", "Neustart", "opnieuw opstarten", "", "uruchom ponownie EMS-ESP", "restart EMS-ESP", "redémarrer EMS-ESP", "EMS-ESPyi yeniden başlat", "riavvia EMS-ESP", "reštart EMS-ESP") // TODO translate
@@ -75,7 +75,6 @@ MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplane
MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "verstuur custom waarde naar EMS", "", "wyślij własną wartość na EMS", "", "", "emp üzerinde özel değer ayarla", "imposta valori personalizzati su EMS", "nastaviť vlastnú hodnotu na ems") // TODO translate
MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "", "uzyskaj odpowiedź", "", "", "gelen cevap", "", "získať odpoveď") // TODO translate
MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "Zugabe einer Menge kalten Wassers", "", "", "uruchom tryśnięcie zimnej wody", "", "", "soğuk su gönder", "", "pošlite studenú dávku vody") // TODO translate
MAKE_WORD_TRANSLATION(allvalues_cmd, "output all values in system", "Alle Werte im System ausgeben", "", "", "wyświetl wszystkie wartości", "", "", "", "", "vypísať všetky hodnoty") // TODO translate
MAKE_WORD_TRANSLATION(message_cmd, "send a message", "Eine Nachricht senden", "", "", "", "", "", "", "", "poslať správu") // TODO translate
MAKE_WORD_TRANSLATION(values_cmd, "list all values", "Liste alle Werte auf", "", "", "", "", "", "", "", "vypísať všetky hodnoty") // TODO translate

View File

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

View File

@@ -1115,11 +1115,12 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// special case to handle booleans
// applies to both Binary Sensor (read only) and a Switch (for a command)
// has no unit of measure or icon
// has no unit of measure or icon, and must be true/false (not on/off or 1/0)
if (type == DeviceValueType::BOOL) {
add_ha_bool(doc);
Helpers::render_boolean(sample_val, false);
strlcpy(sample_val, "false", sizeof(sample_val)); // default is "false"
}
doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else " + sample_val + "}}";
// add the dev json object to the end, not for commands

View File

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

View File

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

View File

@@ -111,44 +111,6 @@ bool System::command_response(const char * value, const int8_t id, JsonObject ou
return true;
}
// output all the devices and the values
// not system info
bool System::command_allvalues(const char * value, const int8_t id, JsonObject output) {
JsonDocument doc;
JsonObject device_output;
// default to values
if (value == nullptr || strlen(value) == 0) {
value = F_(values);
}
// System Entities
// device_output = output["System"].to<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
bool System::command_fetch(const char * value, const int8_t id) {
std::string value_s;
@@ -156,13 +118,13 @@ bool System::command_fetch(const char * value, const int8_t id) {
if (value_s == "all") {
LOG_INFO("Requesting data from EMS devices");
EMSESP::fetch_device_values();
} else if (value_s == (F_(boiler))) {
} else if (value_s == F_(boiler)) {
EMSESP::fetch_device_values_type(EMSdevice::DeviceType::BOILER);
} else if (value_s == (F_(thermostat))) {
} else if (value_s == F_(thermostat)) {
EMSESP::fetch_device_values_type(EMSdevice::DeviceType::THERMOSTAT);
} else if (value_s == (F_(solar))) {
} else if (value_s == F_(solar)) {
EMSESP::fetch_device_values_type(EMSdevice::DeviceType::SOLAR);
} else if (value_s == (F_(mixer))) {
} else if (value_s == F_(mixer)) {
EMSESP::fetch_device_values_type(EMSdevice::DeviceType::MIXER);
}
} else {
@@ -338,6 +300,8 @@ void System::system_restart(const char * partitionname) {
delay(1000); // wait 1 second
ESP.restart();
#else
restart_requested(false);
restart_pending(false);
if (partitionname != nullptr) {
LOG_INFO("Restarting EMS-ESP from %s partition", partitionname);
} else {
@@ -375,9 +339,6 @@ void System::syslog_init() {
syslog_.destination(syslog_host_.c_str(), syslog_port_);
syslog_.hostname(hostname().c_str());
// removed in 3.6.0
// Command::add(EMSdevice::DeviceType::SYSTEM, F_(syslog), System::command_syslog_level, FL_(changeloglevel_cmd), CommandFlag::ADMIN_ONLY);
} else if (syslog_.started()) {
// in case service is still running, this flushes the queue
// https://github.com/emsesp/EMS-ESP/issues/496
@@ -874,7 +835,6 @@ void System::commands_init() {
// these commands will return data in JSON format
Command::add(EMSdevice::DeviceType::SYSTEM, F("response"), System::command_response, FL_(commands_response));
Command::add(EMSdevice::DeviceType::SYSTEM, F("allvalues"), System::command_allvalues, FL_(allvalues_cmd));
// MQTT subscribe "ems-esp/system/#"
Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback
@@ -1031,8 +991,11 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" App used/free: %lu KB / %lu KB", appUsed(), appFree());
uint32_t FSused = LittleFS.usedBytes() / 1024;
shell.printfln(" FS used/free: %lu KB / %lu KB", FSused, FStotal() - FSused);
shell.printfln(" Flash size: %lu KB", ESP.getFlashChipSize() / 1024);
if (PSram()) {
shell.printfln(" PSRAM size/free: %lu KB / %lu KB", PSram(), ESP.getFreePsram() / 1024);
} else {
shell.printfln(" PSRAM: not available");
}
shell.println();
@@ -1087,7 +1050,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" WiFi Network: Disconnected");
break;
case WL_NO_SHIELD:
// case WL_NO_SHIELD:
default:
shell.printfln(" WiFi MAC address: %s", WiFi.macAddress().c_str());
shell.printfln(" WiFi Network: not connected");
@@ -1121,7 +1084,7 @@ void System::show_system(uuid::console::Shell & shell) {
} else {
shell.printfln(" Syslog: %s", syslog_.started() ? "started" : "stopped");
shell.print(" ");
shell.printfln(F_(host_fmt), !syslog_host_.isEmpty() ? syslog_host_.c_str() : (F_(unset)));
shell.printfln(F_(host_fmt), !syslog_host_.isEmpty() ? syslog_host_.c_str() : F_(unset));
shell.printfln(" IP: %s", uuid::printable_to_string(syslog_.ip()).c_str());
shell.print(" ");
shell.printfln(F_(port_fmt), syslog_port_);
@@ -1139,7 +1102,7 @@ void System::show_system(uuid::console::Shell & shell) {
// see if there is a restore of an older settings file that needs to be applied
bool System::check_restore() {
bool reboot_required = false;
bool reboot_required = false; // true if we need to reboot
#ifndef EMSESP_STANDALONE
File new_file = LittleFS.open(TEMP_FILENAME_PATH);
@@ -1167,11 +1130,20 @@ bool System::check_restore() {
} else if (settings_type == "entities") {
// it's a entity file, just replace it and there's no need to reboot
saveSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", input);
} else if (settings_type == "customSupport") {
// it's a custom support file - save it to /config
new_file.close();
if (LittleFS.rename(TEMP_FILENAME_PATH, EMSESP_CUSTOMSUPPORT_FILE)) {
LOG_DEBUG("Custom support information found");
return false; // no need to reboot
} else {
LOG_ERROR("Failed to save custom support file");
}
} else {
LOG_ERROR("Unrecognized file uploaded");
}
} else {
LOG_ERROR("Unrecognized file uploaded, not json. Will be removed.");
LOG_ERROR("Unrecognized file uploaded, not json.");
}
// close (just in case) and remove the temp file
@@ -1192,7 +1164,7 @@ bool System::check_upgrade(bool factory_settings) {
if (!factory_settings) {
// fetch current version from settings file
EMSESP::webSettingsService.read([&](WebSettings & settings) { settingsVersion = settings.version.c_str(); });
EMSESP::webSettingsService.read([&](WebSettings const & settings) { settingsVersion = settings.version.c_str(); });
// see if we're missing a version, will be < 3.5.0b13 from Dec 23 2022
missing_version = (settingsVersion.empty() || (settingsVersion.length() < 5));
@@ -1363,14 +1335,11 @@ bool System::get_value_info(JsonObject output, const char * cmd) {
if (!strcmp(cmd, F_(entities))) {
for (JsonPair p : root) {
if (p.value().is<JsonObject>()) {
// String prefix = p.key().c_str();
for (JsonPair p1 : p.value().as<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());
}
} // else { // we don't have pairs in json root object
// get_value_json(entity, "", p.key().c_str(), p.value());
// }
}
}
return true;
}
@@ -1523,7 +1492,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
}
});
#ifndef EMSESP_STANDALONE
EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) {
EMSESP::esp8266React.getAPSettingsService()->read([&](const APSettings & settings) {
const char * pM[] = {"always", "disconnected", "never"};
node["APProvisionMode"] = pM[settings.provisionMode];
node["APSecurity"] = settings.password.length() ? "wpa2" : "open";
@@ -1535,11 +1504,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node = output["ntp"].to<JsonObject>();
#ifndef EMSESP_STANDALONE
node["NTPStatus"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected";
EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
EMSESP::esp8266React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
node["enabled"] = settings.enabled;
node["server"] = settings.server;
node["tzLabel"] = settings.tzLabel;
// node["tz format"] = settings.tzFormat;
});
#endif
@@ -1552,7 +1520,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["MQTTPublishFails"] = Mqtt::publish_fails();
node["MQTTConnects"] = Mqtt::connect_count();
}
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
EMSESP::esp8266React.getMqttSettingsService()->read([&](const MqttSettings & settings) {
node["enabled"] = settings.enabled;
node["clientID"] = settings.clientId;
node["keepAlive"] = settings.keepAlive;
@@ -1631,7 +1599,6 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["busStatus"] = "unknown";
break;
}
// if (EMSESP::bus_status() != EMSESP::BUS_STATUS_OFFLINE) {
node["busProtocol"] = EMSbus::is_ht3() ? "HT3" : "Buderus";
node["busTelegramsReceived"] = EMSESP::rxservice_.telegram_count();
node["busReads"] = EMSESP::txservice_.telegram_read_count();
@@ -1641,11 +1608,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["busWritesFailed"] = EMSESP::txservice_.telegram_write_fail_count();
node["busRxLineQuality"] = EMSESP::rxservice_.quality();
node["busTxLineQuality"] = (EMSESP::txservice_.read_quality() + EMSESP::txservice_.read_quality()) / 2;
// }
// Settings
node = output["settings"].to<JsonObject>();
EMSESP::webSettingsService.read([&](WebSettings & settings) {
EMSESP::webSettingsService.read([&](const WebSettings & settings) {
node["boardProfile"] = settings.board_profile;
node["locale"] = settings.locale;
node["txMode"] = settings.tx_mode;
@@ -1732,7 +1698,6 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
obj["name"] = F_(temperaturesensor);
obj["entities"] = EMSESP::temperaturesensor_.count_entities();
}
// if (EMSESP::analog_enabled()) {
if (EMSESP::analogsensor_.count_entities()) {
JsonObject obj = devices.add<JsonObject>();
obj["type"] = F_(analogsensor);
@@ -1895,7 +1860,7 @@ std::string System::reset_reason(uint8_t cpu) const {
break;
}
#endif
return ("Unknown");
return "Unknown";
}
#pragma GCC diagnostic pop

View File

@@ -43,6 +43,8 @@ using uuid::console::Shell;
#define EMSESP_FS_CONFIG_DIRECTORY "/config"
#define EMSESP_CUSTOMSUPPORT_FILE "/config/customSupport.json"
namespace emsesp {
enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110 };
@@ -58,12 +60,10 @@ class System {
static bool command_fetch(const char * value, const int8_t id);
static bool command_restart(const char * value, const int8_t id);
static bool command_format(const char * value, const int8_t id);
// static bool command_syslog_level(const char * value, const int8_t id);
static bool command_watch(const char * value, const int8_t id);
static bool command_message(const char * value, const int8_t id);
static bool command_info(const char * value, const int8_t id, JsonObject output);
static bool command_response(const char * value, const int8_t id, JsonObject output);
static bool command_allvalues(const char * value, const int8_t id, JsonObject output);
static bool get_value_info(JsonObject root, const char * cmd);
static void get_value_json(JsonObject output, const std::string & circuit, const std::string & name, JsonVariant val);
@@ -162,10 +162,12 @@ class System {
readonly_mode_ = readonly_mode;
}
// Boolean Format API/MQTT
uint8_t bool_format() {
return bool_format_;
}
// Boolean Format Web
uint8_t bool_dashboard() {
return bool_dashboard_;
}

View File

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

View File

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

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 values");
// shell.invoke_command("call system allvalues");
// shell.invoke_command("call system publish");
// shell.invoke_command("show mqtt");
ok = true;
@@ -792,7 +791,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call temperaturesensor");
shell.invoke_command("show values");
shell.invoke_command("call system allvalues");
shell.invoke_command("call temperaturesensor info");
shell.invoke_command("call temperaturesensor values");
@@ -842,7 +840,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call analogsensor");
shell.invoke_command("show values");
shell.invoke_command("call system allvalues");
shell.invoke_command("call analogsensor info");
shell.invoke_command("call analogsensor values");
@@ -992,11 +989,26 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// EMSESP::webAPIService.webAPIService(&request);
request.method(HTTP_POST);
char data_api[] = "{\"device\":\"system\", \"cmd\":\"restart\",\"id\":-1}";
deserializeJson(doc, data_api);
json = doc.as<JsonVariant>();
char data1[] = "{\"device\":\"system\", \"cmd\":\"restart\",\"id\":-1}";
deserializeJson(doc, data1);
request.url("/api");
EMSESP::webAPIService.webAPIService(&request, json);
EMSESP::webAPIService.webAPIService(&request, doc.as<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");
// 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) {
// check if the user has admin privileges (token is included and authorized)
bool is_admin = false;
EMSESP::webSettingsService.read([&](WebSettings & settings) {
EMSESP::webSettingsService.read([&](WebSettings const & settings) {
Authentication authentication = _securityManager->authenticateRequest(request);
is_admin = settings.notoken_api || AuthenticationPredicates::IS_ADMIN(authentication);
});
@@ -94,7 +94,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
emsesp::EMSESP::system_.refreshHeapMem();
// output json buffer
AsyncJsonResponse * response = new AsyncJsonResponse(false);
auto response = new AsyncJsonResponse(false);
// add more mem if needed - won't be needed in ArduinoJson 7
// while (!response->getSize()) {

View File

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

View File

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

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
bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
if (scheduleItems_->size() == 0) {
if (scheduleItems_->empty()) {
return true;
}
@@ -233,7 +233,7 @@ void WebSchedulerService::publish(const bool force) {
return;
}
if (scheduleItems_->size() == 0) {
if (scheduleItems_->empty()) {
return;
}
@@ -449,7 +449,7 @@ void WebSchedulerService::loop() {
static uint32_t last_uptime_sec = 0;
// get list of scheduler events and exit if it's empty
if (scheduleItems_->size() == 0) {
if (scheduleItems_->empty()) {
return;
}
@@ -584,7 +584,7 @@ void WebSchedulerService::test() {
test_value = "(custom/seltemp)";
command("test5", test_cmd.c_str(), compute(test_value).c_str());
// this will fail unless test("boiler") is loaded
// note: this will fail unless test("boiler") is loaded before hand
test_value = "(boiler/outdoortemp)";
command("test6", test_cmd.c_str(), compute(test_value).c_str());

View File

@@ -24,13 +24,13 @@
namespace emsesp {
WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager) {
WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) {
// GET
server->on(EMSESP_SYSTEM_STATUS_SERVICE_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { systemStatus(request); });
server->on(EMSESP_EXPORT_DATA_SERVICE_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { exportData(request); });
// POST
server->on(EMSESP_CHECK_UPGRADE_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { checkUpgrade(request, json); });
// POST - generic action handler
server->on(EMSESP_ACTION_SERVICE_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { action(request, json); });
}
// /rest/systemStatus
@@ -146,13 +146,55 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
request->send(response);
}
// returns trues if there is an upgrade available
void WebStatusService::checkUpgrade(AsyncWebServerRequest * request, JsonVariant json) {
// generic action handler - as a POST
void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json) {
auto * response = new AsyncJsonResponse();
JsonObject root = response->getRoot();
// get action and any optional param
std::string action = json["action"];
std::string param = json["param"]; // is optional
// check if we're authenticated for admin tasks, some actions are only for admins
Authentication authentication = _securityManager->authenticateRequest(request);
bool is_admin = AuthenticationPredicates::IS_ADMIN(authentication);
bool ok = true;
if (action == "checkUpgrade") {
ok = checkUpgrade(root, param);
} else if (action == "export") {
ok = exportData(root, param);
} else if (action == "customSupport") {
ok = customSupport(root);
} else if (action == "uploadURL" && is_admin) {
ok = uploadURL(param.c_str());
}
#if defined(EMSESP_UNITY)
// store the result so we can test with Unity later
storeResponse(output);
#endif
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
Serial.printf("%sweb output: %s[%s]", COLOR_WHITE, COLOR_BRIGHT_CYAN, request->url().c_str());
Serial.printf(" %s(%d)%s ", ok ? COLOR_BRIGHT_GREEN : COLOR_BRIGHT_RED, ok ? 200 : 400, COLOR_YELLOW);
serializeJson(root, Serial);
Serial.println(COLOR_RESET);
#endif
// send response
if (!ok) {
request->send(400);
return;
}
response->setLength();
request->send(response);
}
// action = checkUpgrade
// returns true if there is an upgrade available
bool WebStatusService::checkUpgrade(JsonObject root, std::string & latest_version) {
version::Semver200_version settings_version(EMSESP_APP_VERSION);
const std::string latest_version = json["version"] | EMSESP_APP_VERSION;
version::Semver200_version this_version(latest_version);
#if defined(EMSESP_DEBUG)
@@ -161,16 +203,40 @@ void WebStatusService::checkUpgrade(AsyncWebServerRequest * request, JsonVariant
root["upgradeable"] = (this_version > settings_version);
response->setLength();
request->send(response);
return true;
}
// returns data for a specific feature/settings as a json object
void WebStatusService::exportData(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse();
JsonObject root = response->getRoot();
// action = allvalues
// output all the devices and the values
void WebStatusService::allvalues(JsonObject output) {
JsonObject device_output;
auto value = F_(values);
String type = request->getParam("type")->value();
// EMS-Device Entities
for (const auto & emsdevice : EMSESP::emsdevices) {
std::string title = emsdevice->device_type_2_device_name_translated() + std::string(" ") + emsdevice->to_string();
device_output = output[title].to<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;
if (type == "settings") {
@@ -188,13 +254,44 @@ void WebStatusService::exportData(AsyncWebServerRequest * request) {
System::extractSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", root);
} else if (type == "entities") {
System::extractSettings(EMSESP_CUSTOMENTITY_FILE, "Entities", root);
} else if (type == "allvalues") {
root.clear(); // don't need the "type" key
allvalues(root);
} else {
request->send(400);
return;
return false;
}
return true;
}
// action = customSupport
// reads any upload customSupport.json file and sends to to Help page to be shown as Guest
bool WebStatusService::customSupport(JsonObject root) {
#ifndef EMSESP_STANDALONE
// check if we have custom support file uploaded
File file = LittleFS.open(EMSESP_CUSTOMSUPPORT_FILE, "r");
if (!file) {
// there is no custom file, return empty object
return true;
}
response->setLength();
request->send(response);
// read the contents of the file into the root output json object
DeserializationError error = deserializeJson(root, file);
if (error) {
emsesp::EMSESP::logger().err("Failed to read custom support file");
return false;
}
file.close();
#endif
return true;
}
// action = uploadURL
// uploads a firmware file from a URL
bool WebStatusService::uploadURL(const char * url) {
// this will keep a copy of the URL, but won't initiate the download yet
emsesp::EMSESP::system_.uploadFirmwareURL(url);
return true;
}
} // namespace emsesp

View File

@@ -2,8 +2,7 @@
#define WebStatusService_h
#define EMSESP_SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus"
#define EMSESP_CHECK_UPGRADE_PATH "/rest/checkUpgrade"
#define EMSESP_EXPORT_DATA_SERVICE_PATH "/rest/exportData"
#define EMSESP_ACTION_SERVICE_PATH "/rest/action"
#include <semver200.h> // for version checking
@@ -13,10 +12,23 @@ class WebStatusService {
public:
WebStatusService(AsyncWebServer * server, SecurityManager * securityManager);
private:
// make action function public so we can test in the debug and standalone mode
#ifndef EMSESP_STANDALONE
protected:
#endif
void systemStatus(AsyncWebServerRequest * request);
void checkUpgrade(AsyncWebServerRequest * request, JsonVariant json);
void exportData(AsyncWebServerRequest * request);
void action(AsyncWebServerRequest * request, JsonVariant json);
private:
SecurityManager * _securityManager;
// actions
bool checkUpgrade(JsonObject root, std::string & latest_version);
bool exportData(JsonObject root, std::string & type);
bool customSupport(JsonObject root);
bool uploadURL(const char * url);
void allvalues(JsonObject output);
};
} // namespace emsesp