mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
refactored restart and format services to be non-blocking
This commit is contained in:
@@ -10,12 +10,6 @@ export const readHardwareStatus = () =>
|
||||
export const readSystemStatus = () =>
|
||||
alovaInstance.Get<SystemStatus>('/rest/systemStatus');
|
||||
|
||||
// commands
|
||||
export const restart = () => alovaInstance.Post('/rest/restart');
|
||||
export const partition = () => alovaInstance.Post('/rest/partition');
|
||||
export const factoryPartition = () => alovaInstance.Post('/rest/factoryPartition');
|
||||
export const factoryReset = () => alovaInstance.Post('/rest/factoryReset');
|
||||
|
||||
// SystemLog
|
||||
export const readLogSettings = () =>
|
||||
alovaInstance.Get<LogSettings>(`/rest/logSettings`);
|
||||
|
||||
@@ -26,8 +26,6 @@ import {
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { restart } from 'api/system';
|
||||
|
||||
import {
|
||||
Body,
|
||||
Cell,
|
||||
@@ -51,6 +49,7 @@ import {
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import {
|
||||
API,
|
||||
readDeviceEntities,
|
||||
readDevices,
|
||||
resetCustomizations,
|
||||
@@ -61,7 +60,7 @@ import SettingsCustomizationsDialog from './CustomizationsDialog';
|
||||
import EntityMaskToggle from './EntityMaskToggle';
|
||||
import OptionIcon from './OptionIcon';
|
||||
import { DeviceEntityMask } from './types';
|
||||
import type { DeviceEntity, DeviceShort } from './types';
|
||||
import type { APIcall, DeviceEntity, DeviceShort } from './types';
|
||||
|
||||
export const APIURL = window.location.origin + '/api/';
|
||||
|
||||
@@ -85,6 +84,10 @@ const Customizations = () => {
|
||||
// fetch devices first
|
||||
const { data: devices, send: fetchDevices } = useRequest(readDevices);
|
||||
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const [selectedDevice, setSelectedDevice] = useState<number>(
|
||||
Number(useLocation().state) || -1
|
||||
);
|
||||
@@ -132,9 +135,14 @@ const Customizations = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const { send: sendRestart } = useRequest(restart(), {
|
||||
immediate: false
|
||||
});
|
||||
const doRestart = async () => {
|
||||
setRestarting(true);
|
||||
await sendAPI({ device: 'system', cmd: 'restart', id: -1 }).catch(
|
||||
(error: Error) => {
|
||||
toast.error(error.message);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const entities_theme = useTheme({
|
||||
Table: `
|
||||
@@ -247,13 +255,6 @@ const Customizations = () => {
|
||||
}
|
||||
}, [devices, selectedDevice]);
|
||||
|
||||
const doRestart = async () => {
|
||||
await sendRestart().catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
setRestarting(true);
|
||||
};
|
||||
|
||||
function formatValue(value: unknown) {
|
||||
if (typeof value === 'number') {
|
||||
return new Intl.NumberFormat().format(value);
|
||||
@@ -509,7 +510,7 @@ const Customizations = () => {
|
||||
container
|
||||
mb={1}
|
||||
mt={0}
|
||||
spacing={1}
|
||||
spacing={2}
|
||||
direction="row"
|
||||
justifyContent="flex-start"
|
||||
alignItems="center"
|
||||
|
||||
@@ -28,7 +28,7 @@ const Help = () => {
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.HELP());
|
||||
|
||||
const { send: getAPI } = useRequest((data: APIcall) => API(data), {
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
}).onSuccess((event) => {
|
||||
const anchor = document.createElement('a');
|
||||
@@ -45,8 +45,8 @@ const Help = () => {
|
||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||
});
|
||||
|
||||
const callAPI = async (device: string, entity: string) => {
|
||||
await getAPI({ device, entity, id: 0 }).catch((error: Error) => {
|
||||
const callAPI = async (device: string, cmd: string) => {
|
||||
await sendAPI({ device, cmd, id: 0 }).catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
};
|
||||
@@ -113,7 +113,7 @@ const Help = () => {
|
||||
color="primary"
|
||||
onClick={() => callAPI('system', 'allvalues')}
|
||||
>
|
||||
{LL.ALLVALUES(0)}
|
||||
{LL.ALLVALUES()}
|
||||
</Button>
|
||||
|
||||
<Box border={1} p={1} mt={4}>
|
||||
|
||||
@@ -272,8 +272,8 @@ export interface BoardProfile {
|
||||
|
||||
export interface APIcall {
|
||||
device: string;
|
||||
entity: string;
|
||||
id: unknown;
|
||||
cmd: string;
|
||||
id: number;
|
||||
}
|
||||
export interface WriteAnalogSensor {
|
||||
id: number;
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { readHardwareStatus, restart } from 'api/system';
|
||||
import { readHardwareStatus } from 'api/system';
|
||||
|
||||
import { useRequest } from 'alova/client';
|
||||
import RestartMonitor from 'app/status/RestartMonitor';
|
||||
@@ -35,9 +35,9 @@ import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
|
||||
import { getBoardProfile, readSettings, writeSettings } from '../../api/app';
|
||||
import { API, getBoardProfile, readSettings, writeSettings } from '../../api/app';
|
||||
import { BOARD_PROFILES } from '../main/types';
|
||||
import type { Settings } from '../main/types';
|
||||
import type { APIcall, Settings } from '../main/types';
|
||||
import { createSettingsValidator } from '../main/validators';
|
||||
|
||||
export function boardProfileSelectItems() {
|
||||
@@ -80,6 +80,10 @@ const ApplicationSettings = () => {
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { loading: processingBoard, send: readBoardProfile } = useRequest(
|
||||
(boardProfile: string) => getBoardProfile(boardProfile),
|
||||
{
|
||||
@@ -102,9 +106,14 @@ const ApplicationSettings = () => {
|
||||
});
|
||||
});
|
||||
|
||||
const { send: restartCommand } = useRequest(restart(), {
|
||||
immediate: false
|
||||
});
|
||||
const doRestart = async () => {
|
||||
setRestarting(true);
|
||||
await sendAPI({ device: 'system', cmd: 'restart', id: -1 }).catch(
|
||||
(error: Error) => {
|
||||
toast.error(error.message);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const updateBoardProfile = async (board_profile: string) => {
|
||||
await readBoardProfile(board_profile).catch((error: Error) => {
|
||||
@@ -158,10 +167,7 @@ const ApplicationSettings = () => {
|
||||
|
||||
const restart = async () => {
|
||||
await validateAndSubmit();
|
||||
await restartCommand().catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
setRestarting(true);
|
||||
await doRestart();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -204,7 +210,7 @@ const ApplicationSettings = () => {
|
||||
label={LL.ENABLE_MODBUS()}
|
||||
/>
|
||||
{data.modbus_enabled && (
|
||||
<Grid container spacing={1} rowSpacing={0}>
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
@@ -258,7 +264,7 @@ const ApplicationSettings = () => {
|
||||
label={LL.ENABLE_SYSLOG()}
|
||||
/>
|
||||
{data.syslog_enabled && (
|
||||
<Grid container spacing={1} rowSpacing={0}>
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
@@ -351,7 +357,7 @@ const ApplicationSettings = () => {
|
||||
<Typography sx={{ pb: 1, pt: 2 }} variant="h6" color="primary">
|
||||
{LL.FORMATTING_OPTIONS()}
|
||||
</Typography>
|
||||
<Grid container spacing={1}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={3}>
|
||||
<TextField
|
||||
name="locale"
|
||||
@@ -469,7 +475,7 @@ const ApplicationSettings = () => {
|
||||
</TextField>
|
||||
{data.board_profile === 'CUSTOM' && (
|
||||
<>
|
||||
<Grid container spacing={1} rowSpacing={0}>
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
@@ -555,7 +561,7 @@ const ApplicationSettings = () => {
|
||||
</Grid>
|
||||
</Grid>
|
||||
{data.phy_type !== 0 && (
|
||||
<Grid container spacing={1} rowSpacing={0}>
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="eth_power"
|
||||
@@ -601,7 +607,7 @@ const ApplicationSettings = () => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Grid container spacing={1} rowSpacing={0}>
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="tx_mode"
|
||||
@@ -717,7 +723,7 @@ const ApplicationSettings = () => {
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Grid container spacing={1} rowSpacing={0}>
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
@@ -740,7 +746,7 @@ const ApplicationSettings = () => {
|
||||
disabled={!data.shower_timer}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid container spacing={1} rowSpacing={2} sx={{ pt: 2 }}>
|
||||
<Grid container spacing={2} sx={{ pt: 2 }}>
|
||||
{data.shower_timer && (
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
checkUpgrade,
|
||||
getDevVersion,
|
||||
getStableVersion,
|
||||
restart,
|
||||
uploadURL
|
||||
} from 'api/system';
|
||||
|
||||
@@ -76,7 +75,11 @@ const DownloadUpload = () => {
|
||||
saveFile(event.data, 'schedule.json');
|
||||
});
|
||||
|
||||
const { send: getAPI } = useRequest((data: APIcall) => API(data), {
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { send: sendAPIandSave } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
}).onSuccess((event) => {
|
||||
saveFile(
|
||||
@@ -98,15 +101,13 @@ const DownloadUpload = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const { send: restartCommand } = useRequest(restart(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const callRestart = async () => {
|
||||
const doRestart = async () => {
|
||||
setRestarting(true);
|
||||
await restartCommand().catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
await sendAPI({ device: 'system', cmd: 'restart', id: -1 }).catch(
|
||||
(error: Error) => {
|
||||
toast.error(error.message);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const { send: sendCheckUpgrade } = useRequest(checkUpgrade, {
|
||||
@@ -200,8 +201,8 @@ const DownloadUpload = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const callAPI = async (device: string, entity: string) => {
|
||||
await getAPI({ device, entity, id: 0 }).catch((error: Error) => {
|
||||
const callAPIandSave = async (device: string, cmd: string) => {
|
||||
await sendAPIandSave({ device, cmd, id: 0 }).catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
};
|
||||
@@ -291,7 +292,7 @@ const DownloadUpload = () => {
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => callAPI('system', 'info')}
|
||||
onClick={() => callAPIandSave('system', 'info')}
|
||||
>
|
||||
{LL.SUPPORT_INFORMATION(0)}
|
||||
</Button>
|
||||
@@ -300,7 +301,7 @@ const DownloadUpload = () => {
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => callAPI('system', 'allvalues')}
|
||||
onClick={() => callAPIandSave('system', 'allvalues')}
|
||||
>
|
||||
{LL.ALLVALUES()}
|
||||
</Button>
|
||||
@@ -420,15 +421,13 @@ const DownloadUpload = () => {
|
||||
<Typography variant="body2">{LL.UPLOAD_TEXT()}</Typography>
|
||||
</Box>
|
||||
|
||||
<SingleUpload callRestart={callRestart} />
|
||||
<SingleUpload doRestart={doRestart} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent>
|
||||
{restarting ? <RestartMonitor message={LL.WAIT_FIRMWARE()} /> : content()}
|
||||
</SectionContent>
|
||||
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -21,9 +21,11 @@ import {
|
||||
} from '@mui/material';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
import { API } from 'api/app';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import type { APIcall } from 'app/main/types';
|
||||
import { SectionContent, useLayoutTitle } from 'components';
|
||||
import ListMenuItem from 'components/layout/ListMenuItem';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
@@ -34,13 +36,14 @@ const Settings = () => {
|
||||
|
||||
const [confirmFactoryReset, setConfirmFactoryReset] = useState<boolean>(false);
|
||||
|
||||
const { send: factoryResetCommand } = useRequest(SystemApi.factoryReset(), {
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const factoryReset = async () => {
|
||||
await factoryResetCommand();
|
||||
setConfirmFactoryReset(false);
|
||||
const doFormat = async () => {
|
||||
await sendAPI({ device: 'system', cmd: 'format', id: 0 }).then(() => {
|
||||
setConfirmFactoryReset(false);
|
||||
});
|
||||
};
|
||||
|
||||
const renderFactoryResetDialog = () => (
|
||||
@@ -63,7 +66,7 @@ const Settings = () => {
|
||||
<Button
|
||||
startIcon={<SettingsBackupRestoreIcon />}
|
||||
variant="outlined"
|
||||
onClick={factoryReset}
|
||||
onClick={doFormat}
|
||||
color="error"
|
||||
>
|
||||
{LL.FACTORY_RESET()}
|
||||
|
||||
@@ -22,9 +22,10 @@ import {
|
||||
} from '@mui/material';
|
||||
|
||||
import * as NetworkApi from 'api/network';
|
||||
import * as SystemApi from 'api/system';
|
||||
import { API } from 'api/app';
|
||||
|
||||
import { updateState, useRequest } from 'alova/client';
|
||||
import type { APIcall } from 'app/main/types';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import {
|
||||
BlockFormControlLabel,
|
||||
@@ -71,7 +72,7 @@ const NetworkSettings = () => {
|
||||
update: NetworkApi.updateNetworkSettings
|
||||
});
|
||||
|
||||
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
@@ -131,11 +132,13 @@ const NetworkSettings = () => {
|
||||
await loadData();
|
||||
};
|
||||
|
||||
const restart = async () => {
|
||||
await restartCommand().catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
const doRestart = async () => {
|
||||
setRestarting(true);
|
||||
await sendAPI({ device: 'system', cmd: 'restart', id: -1 }).catch(
|
||||
(error: Error) => {
|
||||
toast.error(error.message);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -358,7 +361,7 @@ const NetworkSettings = () => {
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={restart}
|
||||
onClick={doRestart}
|
||||
>
|
||||
{LL.RESTART()}
|
||||
</Button>
|
||||
|
||||
@@ -1,52 +1,80 @@
|
||||
import { type FC, useEffect, useRef, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
import { readHardwareStatus } from 'api/system';
|
||||
|
||||
import { useRequest } from 'alova/client';
|
||||
import { FormLoader } from 'components';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import MessageBox from 'components/MessageBox';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const RESTART_TIMEOUT = 2 * 60 * 1000; // 2 minutes
|
||||
const POLL_INTERVAL = 2000; // every 2 seconds
|
||||
|
||||
export interface RestartMonitorProps {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
const RestartMonitor: FC<RestartMonitorProps> = ({ message }) => {
|
||||
const [failed, setFailed] = useState<boolean>(false);
|
||||
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
|
||||
const RestartMonitor = () => {
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
|
||||
let count = 0;
|
||||
|
||||
const { send } = useRequest(readHardwareStatus, { immediate: false });
|
||||
|
||||
const poll = useRef(async () => {
|
||||
try {
|
||||
await send();
|
||||
document.location.href = '/';
|
||||
} catch {
|
||||
if (new Date().getTime() < timeoutAt.current) {
|
||||
setTimeoutId(setTimeout(poll.current, POLL_INTERVAL));
|
||||
} else {
|
||||
setFailed(true);
|
||||
const { data } = useAutoRequest(readHardwareStatus, {
|
||||
pollingTime: 1000,
|
||||
force: true,
|
||||
initialData: { status: 'Getting ready...' },
|
||||
async middleware(_, next) {
|
||||
if (count++ >= 1) {
|
||||
// skip first request (1 seconds) to allow AsyncWS to send its response
|
||||
await next();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setTimeoutId(setTimeout(poll.current, POLL_INTERVAL));
|
||||
}, []);
|
||||
|
||||
useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]);
|
||||
})
|
||||
.onSuccess((event) => {
|
||||
console.log(event.data.status); // TODO remove
|
||||
if (event.data.status === 'ready' || event.data.status === undefined) {
|
||||
document.location.href = '/';
|
||||
}
|
||||
})
|
||||
.onError((error, _method) => {
|
||||
setErrorMessage(error.message);
|
||||
});
|
||||
|
||||
return (
|
||||
<FormLoader
|
||||
message={message ? message : LL.APPLICATION_RESTARTING() + '...'}
|
||||
errorMessage={failed ? 'Timed out' : undefined}
|
||||
/>
|
||||
<Dialog fullWidth={true} sx={dialogStyle} open={true}>
|
||||
<DialogContent dividers>
|
||||
<Box m={2} py={2} display="flex" alignItems="center" flexDirection="column">
|
||||
<Typography
|
||||
color="secondary"
|
||||
variant="h6"
|
||||
fontWeight={400}
|
||||
textAlign="center"
|
||||
>
|
||||
{data?.status === 'uploading'
|
||||
? LL.WAIT_FIRMWARE()
|
||||
: data?.status === 'restarting'
|
||||
? LL.APPLICATION_RESTARTING()
|
||||
: data?.status === 'ready'
|
||||
? 'Reloading'
|
||||
: 'Preparing'}
|
||||
</Typography>
|
||||
<Typography mt={2} variant="h6" fontWeight={400} textAlign="center">
|
||||
{LL.PLEASE_WAIT()}…
|
||||
</Typography>
|
||||
|
||||
{errorMessage ? (
|
||||
<MessageBox my={2} level="error" message={errorMessage} />
|
||||
) : (
|
||||
<Box py={2}>
|
||||
<CircularProgress size={48} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -30,10 +30,11 @@ import {
|
||||
} from '@mui/material';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
import { API } from 'api/app';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useAutoRequest, useRequest } from 'alova/client';
|
||||
import { busConnectionStatus } from 'app/main/types';
|
||||
import { type APIcall, busConnectionStatus } from 'app/main/types';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import ListMenuItem from 'components/layout/ListMenuItem';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
@@ -54,7 +55,7 @@ const SystemStatus = () => {
|
||||
const [confirmRestart, setConfirmRestart] = useState<boolean>(false);
|
||||
const [restarting, setRestarting] = useState<boolean>();
|
||||
|
||||
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
@@ -64,7 +65,12 @@ const SystemStatus = () => {
|
||||
error
|
||||
} = useAutoRequest(SystemApi.readSystemStatus, {
|
||||
initialData: [],
|
||||
pollingTime: 5000
|
||||
pollingTime: 5000,
|
||||
async middleware(_, next) {
|
||||
if (!restarting) {
|
||||
await next();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const theme = useTheme();
|
||||
@@ -195,17 +201,14 @@ const SystemStatus = () => {
|
||||
const activeHighlight = (value: boolean) =>
|
||||
value ? theme.palette.success.main : theme.palette.info.main;
|
||||
|
||||
const restart = async () => {
|
||||
await restartCommand()
|
||||
.then(() => {
|
||||
setRestarting(true);
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
const doRestart = async () => {
|
||||
setConfirmRestart(false);
|
||||
setRestarting(true);
|
||||
await sendAPI({ device: 'system', cmd: 'restart', id: -1 }).catch(
|
||||
(error: Error) => {
|
||||
toast.error(error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setConfirmRestart(false);
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const renderRestartDialog = () => (
|
||||
@@ -228,7 +231,7 @@ const SystemStatus = () => {
|
||||
<Button
|
||||
startIcon={<PowerSettingsNewIcon />}
|
||||
variant="outlined"
|
||||
onClick={restart}
|
||||
onClick={doRestart}
|
||||
color="error"
|
||||
>
|
||||
{LL.RESTART()}
|
||||
|
||||
@@ -16,7 +16,7 @@ UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager *
|
||||
: _securityManager(securityManager)
|
||||
, _is_firmware(false)
|
||||
, _md5() {
|
||||
// end-points
|
||||
// upload a file via a form
|
||||
server->on(
|
||||
UPLOAD_FILE_PATH,
|
||||
HTTP_POST,
|
||||
@@ -25,6 +25,7 @@ UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager *
|
||||
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));
|
||||
@@ -123,7 +124,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
|
||||
request->_tempFile.close(); // close the file handle as the upload is now done
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
request->onDisconnect(RestartService::restartNow);
|
||||
emsesp::EMSESP::system_.restart_pending(true); // will be handled by the main loop
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -132,7 +133,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
|
||||
if (_is_firmware && !request->_tempObject) {
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
request->onDisconnect(RestartService::restartNow);
|
||||
emsesp::EMSESP::system_.restart_pending(true); // will be handled by the main loop
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -215,20 +215,6 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
|
||||
string_vector{F_(wifi), F_(reconnect)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) { to_app(shell).system_.wifi_reconnect(); });
|
||||
|
||||
commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, string_vector{F_(format)}, [](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) {
|
||||
if (completed) {
|
||||
to_app(shell).esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
|
||||
if (securitySettings.jwtSecret.equals(password.c_str())) {
|
||||
to_app(shell).system_.format(shell);
|
||||
} else {
|
||||
shell.println("incorrect password");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// SET commands
|
||||
//
|
||||
@@ -651,11 +637,7 @@ void EMSESPShell::stopped() {
|
||||
void EMSESPShell::display_banner() {
|
||||
println();
|
||||
printfln("┌───────────────────────────────────────┐");
|
||||
#ifndef EMSESP_DEBUG
|
||||
printfln("│ %sEMS-ESP version %-20s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF);
|
||||
#else
|
||||
printfln("│ %sEMS-ESP version %s%-8s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, " (D)", COLOR_BOLD_OFF);
|
||||
#endif
|
||||
printfln("│ │");
|
||||
printfln("│ %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET);
|
||||
printfln("│ %ssu%s to access admin commands │", COLOR_UNDERLINE, COLOR_RESET);
|
||||
|
||||
@@ -1569,7 +1569,7 @@ void EMSESP::start() {
|
||||
// do a quick scan of the filesystem to see if we have a /config folder
|
||||
// so we know if this is a new install or not
|
||||
#ifndef EMSESP_STANDALONE
|
||||
File root = LittleFS.open("/config");
|
||||
File root = LittleFS.open(EMSESP_FS_CONFIG_DIRECTORY);
|
||||
bool factory_settings = !root;
|
||||
if (!root) {
|
||||
LOG_INFO("No config found, assuming factory settings");
|
||||
@@ -1589,9 +1589,9 @@ void EMSESP::start() {
|
||||
LOG_DEBUG("NVS device information: %s", system_.getBBQKeesGatewayDetails().c_str());
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
LOG_INFO("Starting EMS-ESP version %s from %s partition", EMSESP_APP_VERSION, esp_ota_get_running_partition()->label); // welcome message
|
||||
LOG_INFO("Booting EMS-ESP version %s from %s partition", EMSESP_APP_VERSION, esp_ota_get_running_partition()->label); // welcome message
|
||||
#else
|
||||
LOG_INFO("Starting EMS-ESP version %s", EMSESP_APP_VERSION); // welcome message
|
||||
LOG_INFO("Booting EMS-ESP version %s", EMSESP_APP_VERSION); // welcome message
|
||||
#endif
|
||||
LOG_DEBUG("System is running in Debug mode");
|
||||
LOG_INFO("Last system reset reason Core0: %s, Core1: %s", system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str());
|
||||
@@ -1610,6 +1610,12 @@ void EMSESP::start() {
|
||||
system_.system_restart();
|
||||
};
|
||||
|
||||
// Load our library of known devices into stack mem. Names are stored in Flash memory
|
||||
device_library_ = {
|
||||
#include "device_library.h"
|
||||
};
|
||||
LOG_INFO("Loaded EMS device library (%d)", device_library_.size());
|
||||
|
||||
system_.reload_settings(); // ... and store some of the settings locally
|
||||
|
||||
webCustomizationService.begin(); // load the customizations
|
||||
@@ -1627,31 +1633,22 @@ void EMSESP::start() {
|
||||
#endif
|
||||
}
|
||||
|
||||
// start services
|
||||
if (system_.modbus_enabled()) {
|
||||
modbus_ = new Modbus;
|
||||
modbus_->start(1, system_.modbus_port(), system_.modbus_max_clients(), system_.modbus_timeout());
|
||||
}
|
||||
|
||||
mqtt_.start(); // mqtt init
|
||||
system_.start(); // starts commands, led, adc, button, network (sets hostname), syslog & uart
|
||||
shower_.start(); // initialize shower timer and shower alert
|
||||
temperaturesensor_.start(); // Temperature external sensors
|
||||
analogsensor_.start(); // Analog external sensors
|
||||
webLogService.start(); // apply settings to weblog service
|
||||
|
||||
// start web services
|
||||
webLogService.start(); // apply settings to weblog service
|
||||
webModulesService.begin(); // setup the external library modules
|
||||
|
||||
// Load our library of known devices into stack mem. Names are stored in Flash memory
|
||||
device_library_ = {
|
||||
#include "device_library.h"
|
||||
};
|
||||
LOG_INFO("Loaded EMS device library (%d records)", device_library_.size());
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
Mqtt::on_connect(); // simulate an MQTT connection
|
||||
#endif
|
||||
|
||||
webServer.begin(); // start the web server
|
||||
webServer.begin(); // start the web server
|
||||
LOG_INFO("Starting Web Server");
|
||||
}
|
||||
|
||||
// main loop calling all services
|
||||
|
||||
@@ -67,6 +67,7 @@ MAKE_WORD_TRANSLATION(setiovalue_cmd, "set io value", "Setze Wertevorgabe", "ins
|
||||
MAKE_WORD_TRANSLATION(changeloglevel_cmd, "change log level", "Ändere Sysloglevel", "aanpassen log niveau", "", "zmień poziom log-u", "endre loggnivå", "", "Kayıt seviyesini değiştir", "cambia livello registrazione", "") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(fetch_cmd, "refresh all EMS values", "Lese alle EMS-Werte neu", "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(format_cmd, "factory reset EMS-ESP", "", "", "", "", "", "", "", "", "") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Watch auf eingehende Telegramme", "inkomende telegrammen bekijken", "", "obserwuj przyczodzące telegramy", "se innkommende telegrammer", "", "Gelen telegramları", "guardare i telegrammi in arrivo", "sledovať prichádzajúce telegramy") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "publiceer alles naar MQTT", "", "opublikuj wszystko na MQTT", "Publiser alt til MQTT", "", "Hepsini MQTTye gönder", "pubblica tutto su MQTT", "zverejniť všetko na MQTT") // TODO translate
|
||||
MAKE_WORD_TRANSLATION(system_info_cmd, "show system info", "Zeige System-Status", "toon systeemstatus", "", "pokaż status systemu", "vis system status", "", "Sistem Durumunu Göster", "visualizza stati di sistema", "zobraziť stav systému") // TODO translate
|
||||
|
||||
@@ -78,6 +78,7 @@ uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN};
|
||||
// init statics
|
||||
PButton System::myPButton_;
|
||||
bool System::restart_requested_ = false;
|
||||
bool System::restart_pending_ = false;
|
||||
bool System::test_set_all_active_ = false;
|
||||
uint32_t System::max_alloc_mem_;
|
||||
uint32_t System::heap_mem_;
|
||||
@@ -328,10 +329,13 @@ void System::system_restart(const char * partitionname) {
|
||||
LOG_INFO("Restarting EMS-ESP...");
|
||||
}
|
||||
|
||||
restart_requested(false); // make sure it's not repeated
|
||||
store_nvs_values(); // save any NVS values
|
||||
Shell::loop_all(); // flush log to output
|
||||
delay(1000); // wait 1 second
|
||||
// make sure it's only executed once
|
||||
restart_requested(false);
|
||||
restart_pending(false);
|
||||
|
||||
store_nvs_values(); // save any NVS values
|
||||
Shell::loop_all(); // flush log to output
|
||||
delay(1000); // wait 1 second
|
||||
ESP.restart();
|
||||
#endif
|
||||
}
|
||||
@@ -346,19 +350,6 @@ void System::wifi_reconnect() {
|
||||
EMSESP::esp8266React.getNetworkSettingsService()->callUpdateHandlers(); // in case we've changed ssid or password
|
||||
}
|
||||
|
||||
// format the FS. Wipes everything.
|
||||
void System::format(uuid::console::Shell & shell) {
|
||||
auto msg = ("Formatting file system. This will reset all settings to their defaults");
|
||||
shell.logger().warning(msg);
|
||||
EMSuart::stop();
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
LittleFS.format();
|
||||
#endif
|
||||
|
||||
System::system_restart();
|
||||
}
|
||||
|
||||
void System::syslog_init() {
|
||||
EMSESP::webSettingsService.read([&](WebSettings & settings) {
|
||||
syslog_enabled_ = settings.syslog_enabled;
|
||||
@@ -538,12 +529,9 @@ void System::button_OnLongPress(PButton & b) {
|
||||
EMSESP::system_.system_restart("boot");
|
||||
}
|
||||
|
||||
// button indefinite press
|
||||
// button indefinite press - do nothing for now
|
||||
void System::button_OnVLongPress(PButton & b) {
|
||||
LOG_NOTICE("Button pressed - very long press - factory reset");
|
||||
#ifndef EMSESP_STANDALONE
|
||||
EMSESP::esp8266React.factoryReset();
|
||||
#endif
|
||||
LOG_NOTICE("Button pressed - very long press");
|
||||
}
|
||||
|
||||
// push button
|
||||
@@ -860,9 +848,8 @@ void System::system_check() {
|
||||
void System::commands_init() {
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, FL_(send_cmd), CommandFlag::ADMIN_ONLY);
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, FL_(fetch_cmd), CommandFlag::ADMIN_ONLY);
|
||||
|
||||
// restart, watch, message (and test) are also exposed as Console commands
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_cmd), CommandFlag::ADMIN_ONLY);
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(format), System::command_format, FL_(format_cmd), CommandFlag::ADMIN_ONLY);
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), System::command_watch, FL_(watch_cmd));
|
||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(message), System::command_message, FL_(message_cmd));
|
||||
#if defined(EMSESP_TEST)
|
||||
@@ -1748,13 +1735,35 @@ bool System::load_board_profile(std::vector<int8_t> & data, const std::string &
|
||||
return true;
|
||||
}
|
||||
|
||||
// format command - factory reset, removing all config files
|
||||
bool System::command_format(const char * value, const int8_t id) {
|
||||
LOG_INFO("Removing all config files");
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// TODO To replaced with fs.rmdir(FS_CONFIG_DIRECTORY) now we're using IDF 4.2+
|
||||
File root = LittleFS.open(EMSESP_FS_CONFIG_DIRECTORY);
|
||||
File file;
|
||||
while ((file = root.openNextFile())) {
|
||||
String path = file.path();
|
||||
file.close();
|
||||
LittleFS.remove(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
EMSESP::system_.restart_requested(true); // will be handled by the main loop
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// restart command - perform a hard reset
|
||||
bool System::command_restart(const char * value, const int8_t id) {
|
||||
if (value != nullptr && value[0] != '\0') {
|
||||
EMSESP::system_.system_restart(value);
|
||||
} else {
|
||||
EMSESP::system_.system_restart();
|
||||
if (id != 0) {
|
||||
// if it has an id then it's a web call and we need to queue the restart
|
||||
LOG_INFO("Preparing to restart system");
|
||||
EMSESP::system_.restart_pending(true);
|
||||
return true;
|
||||
}
|
||||
LOG_INFO("Restarting system");
|
||||
EMSESP::system_.restart_requested(true); // will be handled by the main loop
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1908,7 +1917,7 @@ bool System::uploadFirmwareURL(const char * url) {
|
||||
|
||||
LOG_INFO("Firmware uploaded successfully. Restarting...");
|
||||
|
||||
restart_requested(true);
|
||||
restart_pending(true);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
16
src/system.h
16
src/system.h
@@ -41,6 +41,8 @@
|
||||
|
||||
using uuid::console::Shell;
|
||||
|
||||
#define EMSESP_FS_CONFIG_DIRECTORY "/config"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110 };
|
||||
@@ -55,12 +57,14 @@ class System {
|
||||
static bool command_publish(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_syslog_level(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);
|
||||
|
||||
@@ -72,7 +76,6 @@ class System {
|
||||
|
||||
void store_nvs_values();
|
||||
void system_restart(const char * partition = nullptr);
|
||||
void format(uuid::console::Shell & shell);
|
||||
void upload_status(bool in_progress);
|
||||
bool upload_status();
|
||||
void show_mem(const char * note);
|
||||
@@ -116,11 +119,17 @@ class System {
|
||||
static void restart_requested(bool restart_requested) {
|
||||
restart_requested_ = restart_requested;
|
||||
}
|
||||
|
||||
static bool restart_requested() {
|
||||
return restart_requested_;
|
||||
}
|
||||
|
||||
static void restart_pending(bool restart_pending) {
|
||||
restart_pending_ = restart_pending;
|
||||
}
|
||||
static bool restart_pending() {
|
||||
return restart_pending_;
|
||||
}
|
||||
|
||||
bool telnet_enabled() {
|
||||
return telnet_enabled_;
|
||||
}
|
||||
@@ -291,6 +300,7 @@ class System {
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
static bool restart_requested_;
|
||||
static bool restart_pending_; // used in 2-stage process to call restart from Web API
|
||||
static bool test_set_all_active_; // force all entities in a device to have a value
|
||||
static uint32_t max_alloc_mem_;
|
||||
static uint32_t heap_mem_;
|
||||
|
||||
@@ -156,7 +156,7 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques
|
||||
if (LittleFS.remove(EMSESP_CUSTOMIZATION_FILE)) {
|
||||
AsyncWebServerResponse * response = request->beginResponse(205); // restart needed
|
||||
request->send(response);
|
||||
EMSESP::system_.restart_requested(true);
|
||||
EMSESP::system_.restart_pending(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,8 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
|
||||
} else {
|
||||
EMSESP::nvs_.putString("boot", "S32");
|
||||
}
|
||||
ESP.restart();
|
||||
// ESP.restart();
|
||||
EMSESP::system_.restart_requested(true);
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
settings.board_profile = "C3MINI";
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
|
||||
@@ -34,12 +34,6 @@ WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * se
|
||||
|
||||
// /rest/systemStatus
|
||||
void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
|
||||
// This is a litle trick for the OTA upload. We don't want the React RestartService to think we're finished
|
||||
// with the upload so we fake it and pretent the /rest/systemStatus is not available. That way the spinner keeps spinning.
|
||||
if (EMSESP::system_.upload_status()) {
|
||||
return; // ignore endpoint
|
||||
}
|
||||
|
||||
EMSESP::system_.refreshHeapMem(); // refresh free heap and max alloc heap
|
||||
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
@@ -85,6 +79,7 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
|
||||
}
|
||||
|
||||
// /rest/hardwareStatus
|
||||
// This is also used for polling
|
||||
void WebStatusService::hardwareStatus(AsyncWebServerRequest * request) {
|
||||
EMSESP::system_.refreshHeapMem(); // refresh free heap and max alloc heap
|
||||
|
||||
@@ -144,6 +139,14 @@ void WebStatusService::hardwareStatus(AsyncWebServerRequest * request) {
|
||||
root["has_partition"] = false;
|
||||
}
|
||||
|
||||
// Matches RestartMonitor.tsx
|
||||
if (EMSESP::system_.restart_pending()) {
|
||||
root["status"] = "restarting";
|
||||
EMSESP::system_.restart_requested(true); // tell emsesp loop to start restart
|
||||
} else {
|
||||
root["status"] = EMSESP::system_.upload_status() ? "uploading" : "ready";
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
response->setLength();
|
||||
|
||||
Reference in New Issue
Block a user