refactored restart and format services to be non-blocking

This commit is contained in:
proddy
2024-08-31 16:12:30 +02:00
parent 382c46622d
commit 931827c526
19 changed files with 243 additions and 202 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()}&hellip;
</Typography>
{errorMessage ? (
<MessageBox my={2} level="error" message={errorMessage} />
) : (
<Box py={2}>
<CircularProgress size={48} />
</Box>
)}
</Box>
</DialogContent>
</Dialog>
);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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_;

View File

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

View File

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

View File

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