mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 08:19:52 +03:00
introduce system status in WebUI for easier monitoring of tasks
This commit is contained in:
@@ -58,7 +58,7 @@ const AuthenticatedRouting = () => {
|
||||
<Route path="/settings/ntp" element={<NTPSettings />} />
|
||||
<Route path="/settings/ap" element={<APSettings />} />
|
||||
<Route path="/settings/modules" element={<Modules />} />
|
||||
<Route path="/settings/upload" element={<DownloadUpload />} />
|
||||
<Route path="/settings/downloadUpload" element={<DownloadUpload />} />
|
||||
|
||||
<Route path="/settings/network/*" element={<Network />} />
|
||||
<Route path="/settings/security/*" element={<Security />} />
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import RestartMonitor from 'app/status/RestartMonitor';
|
||||
import SystemMonitor from 'app/status/SystemMonitor';
|
||||
import {
|
||||
BlockNavigation,
|
||||
ButtonRow,
|
||||
@@ -737,7 +737,7 @@ const Customizations = () => {
|
||||
return (
|
||||
<SectionContent>
|
||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||
{restarting ? <RestartMonitor /> : renderContent()}
|
||||
{restarting ? <SystemMonitor /> : renderContent()}
|
||||
{selectedDeviceEntity && (
|
||||
<SettingsCustomizationsDialog
|
||||
open={dialogOpen}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { readSystemStatus } from 'api/system';
|
||||
|
||||
import { useRequest } from 'alova/client';
|
||||
import RestartMonitor from 'app/status/RestartMonitor';
|
||||
import SystemMonitor from 'app/status/SystemMonitor';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import {
|
||||
BlockFormControlLabel,
|
||||
@@ -859,7 +859,7 @@ const ApplicationSettings = () => {
|
||||
return (
|
||||
<SectionContent>
|
||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||
{restarting ? <RestartMonitor /> : content()}
|
||||
{restarting ? <SystemMonitor /> : content()}
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import { API, callAction } from 'api/app';
|
||||
|
||||
import { useRequest } from 'alova/client';
|
||||
import type { APIcall } from 'app/main/types';
|
||||
import RestartMonitor from 'app/status/RestartMonitor';
|
||||
import SystemMonitor from 'app/status/SystemMonitor';
|
||||
import {
|
||||
FormLoader,
|
||||
SectionContent,
|
||||
@@ -123,7 +123,7 @@ const DownloadUpload = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
|
||||
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ const Settings = () => {
|
||||
bgcolor="#5d89f7"
|
||||
label={LL.DOWNLOAD_UPLOAD()}
|
||||
text={LL.DOWNLOAD_UPLOAD_1()}
|
||||
to="upload"
|
||||
to="downloadUpload"
|
||||
/>
|
||||
</List>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { getDevVersion, getStableVersion } from 'api/system';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import RestartMonitor from 'app/status/RestartMonitor';
|
||||
import SystemMonitor from 'app/status/SystemMonitor';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
@@ -224,7 +224,7 @@ const Version = () => {
|
||||
<>
|
||||
<Box p={2} border="1px solid grey" borderRadius={2}>
|
||||
<Typography mb={2} variant="h6" color="primary">
|
||||
Firmware Version
|
||||
Firmware {LL.VERSION()}
|
||||
</Typography>
|
||||
|
||||
<Grid
|
||||
@@ -360,7 +360,7 @@ const Version = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
|
||||
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ import { updateValueDirty, useRest } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
import { createNetworkSettingsValidator } from 'validators/network';
|
||||
|
||||
import RestartMonitor from '../../status/RestartMonitor';
|
||||
import SystemMonitor from '../../status/SystemMonitor';
|
||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
|
||||
|
||||
@@ -400,7 +400,7 @@ const NetworkSettings = () => {
|
||||
return (
|
||||
<SectionContent>
|
||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||
{restarting ? <RestartMonitor /> : content()}
|
||||
{restarting ? <SystemMonitor /> : content()}
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
import { readSystemStatus } from 'api/system';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import MessageBox from 'components/MessageBox';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
const RestartMonitor = () => {
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
let count = 0;
|
||||
|
||||
const { data, send } = useRequest(readSystemStatus, {
|
||||
force: true,
|
||||
initialData: { status: 'Getting ready...' },
|
||||
async middleware(_, next) {
|
||||
if (count++ >= 1) {
|
||||
// skip first request (1 second) to allow AsyncWS to send its response
|
||||
await next();
|
||||
}
|
||||
}
|
||||
})
|
||||
.onSuccess((event) => {
|
||||
if (event.data.status === 'ready' || event.data.status === undefined) {
|
||||
document.location.href = '/';
|
||||
}
|
||||
})
|
||||
.onError((error) => {
|
||||
setErrorMessage(error.message);
|
||||
});
|
||||
|
||||
useInterval(() => {
|
||||
void send();
|
||||
}, 1000); // check every second
|
||||
|
||||
return (
|
||||
<Dialog fullWidth={true} sx={dialogStyle} open={true}>
|
||||
<DialogContent dividers>
|
||||
<Box m={0} py={0} 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'
|
||||
? LL.RESTARTING_PRE()
|
||||
: LL.RESTARTING_POST()}
|
||||
…
|
||||
</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={32} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default RestartMonitor;
|
||||
@@ -40,7 +40,7 @@ import { NTPSyncStatus, NetworkConnectionStatus } from 'types';
|
||||
import { useInterval } from 'utils';
|
||||
import { formatDateTime } from 'utils/time';
|
||||
|
||||
import RestartMonitor from './RestartMonitor';
|
||||
import SystemMonitor from './SystemMonitor';
|
||||
|
||||
const SystemStatus = () => {
|
||||
const { LL } = useI18nContext();
|
||||
@@ -349,7 +349,7 @@ const SystemStatus = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
|
||||
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
122
interface/src/app/status/SystemMonitor.tsx
Normal file
122
interface/src/app/status/SystemMonitor.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
import { callAction } from 'api/app';
|
||||
import { readSystemStatus } from 'api/system';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import MessageBox from 'components/MessageBox';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { SystemStatusCodes } from 'types';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
const SystemMonitor = () => {
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
let count = 0;
|
||||
|
||||
const { send: setSystemStatus } = useRequest(
|
||||
(status: string) => callAction({ action: 'systemStatus', param: status }),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
const { data, send } = useRequest(readSystemStatus, {
|
||||
force: true,
|
||||
async middleware(_, next) {
|
||||
if (count++ >= 1) {
|
||||
// skip first request (1 second) to allow AsyncWS to send its response
|
||||
await next();
|
||||
}
|
||||
}
|
||||
})
|
||||
.onSuccess((event) => {
|
||||
if (
|
||||
event.data.status === SystemStatusCodes.SYSTEM_STATUS_NORMAL ||
|
||||
event.data.status === undefined
|
||||
) {
|
||||
document.location.href = '/';
|
||||
} else if (
|
||||
event.data.status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD
|
||||
) {
|
||||
setErrorMessage('Please check system logs for possible causes');
|
||||
}
|
||||
})
|
||||
.onError((error) => {
|
||||
setErrorMessage(error.message);
|
||||
});
|
||||
|
||||
useInterval(() => {
|
||||
void send();
|
||||
}, 1000); // check every second
|
||||
|
||||
const onCancel = () => {
|
||||
setErrorMessage(undefined);
|
||||
setSystemStatus(SystemStatusCodes.SYSTEM_STATUS_NORMAL as unknown as string);
|
||||
document.location.href = '/';
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog fullWidth={true} sx={dialogStyle} open={true}>
|
||||
<DialogContent dividers>
|
||||
<Box m={0} py={0} display="flex" alignItems="center" flexDirection="column">
|
||||
<Typography
|
||||
color="secondary"
|
||||
variant="h6"
|
||||
fontWeight={400}
|
||||
textAlign="center"
|
||||
>
|
||||
{data?.status === SystemStatusCodes.SYSTEM_STATUS_UPLOADING
|
||||
? LL.WAIT_FIRMWARE()
|
||||
: data?.status === SystemStatusCodes.SYSTEM_STATUS_RESTARTING
|
||||
? LL.APPLICATION_RESTARTING()
|
||||
: data?.status === SystemStatusCodes.SYSTEM_STATUS_NORMAL
|
||||
? LL.RESTARTING_PRE()
|
||||
: data?.status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD
|
||||
? 'Upload Failed'
|
||||
: LL.RESTARTING_POST()}
|
||||
</Typography>
|
||||
|
||||
{errorMessage ? (
|
||||
<MessageBox my={2} level="error" message={errorMessage}>
|
||||
<Button
|
||||
size="small"
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<CancelIcon />}
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{LL.RESET(0)}
|
||||
</Button>
|
||||
</MessageBox>
|
||||
) : (
|
||||
<>
|
||||
<Typography mt={2} variant="h6" fontWeight={400} textAlign="center">
|
||||
{LL.PLEASE_WAIT()}…
|
||||
</Typography>
|
||||
<Box py={2}>
|
||||
<CircularProgress size={32} />
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemMonitor;
|
||||
@@ -2,6 +2,17 @@ import type { busConnectionStatus } from 'app/main/types';
|
||||
|
||||
import type { NetworkConnectionStatus } from './network';
|
||||
|
||||
export enum SystemStatusCodes {
|
||||
SYSTEM_STATUS_NORMAL = 0,
|
||||
SYSTEM_STATUS_PENDING_UPLOAD = 1,
|
||||
SYSTEM_STATUS_UPLOADING = 2,
|
||||
SYSTEM_STATUS_ERROR_UPLOAD = 3,
|
||||
SYSTEM_STATUS_RESTARTING = 4,
|
||||
SYSTEM_STATUS_ERROR = 5,
|
||||
SYSTEM_STATUS_PENDING_RESTART = 6,
|
||||
SYSTEM_STATUS_RESTART_REQUESTED = 7
|
||||
}
|
||||
|
||||
export interface SystemStatus {
|
||||
emsesp_version: string;
|
||||
bus_status: busConnectionStatus;
|
||||
@@ -41,7 +52,7 @@ export interface SystemStatus {
|
||||
model: string;
|
||||
has_loader: boolean;
|
||||
has_partition: boolean;
|
||||
status: string;
|
||||
status: number; // SystemStatusCodes which matches SYSTEM_STATUS in System.h
|
||||
temperature?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ let settings = {
|
||||
let system_status = {
|
||||
emsesp_version: 'XX.XX.XX', // defined later
|
||||
bus_status: 0,
|
||||
// status: 2,
|
||||
uptime: 77186,
|
||||
bus_uptime: 77121,
|
||||
num_devices: 2,
|
||||
@@ -108,7 +107,8 @@ let system_status = {
|
||||
has_loader: true,
|
||||
model: '',
|
||||
// model: 'BBQKees Electronics EMS Gateway E32 V2 (E32 V2.0 P3/2024011)',
|
||||
status: 'downloading'
|
||||
// status: 0,
|
||||
status: 3
|
||||
};
|
||||
|
||||
let VERSION_IS_UPGRADEABLE: boolean;
|
||||
|
||||
@@ -118,7 +118,8 @@ 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);
|
||||
emsesp::EMSESP::system_.restart_pending(true); // will be handled by the main loop. We use pending for the Web's RestartMonitor
|
||||
emsesp::EMSESP::system_.systemStatus(
|
||||
emsesp::SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART); // will be handled by the main loop. We use pending for the Web's SystemMonitor
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -127,7 +128,8 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
|
||||
if (_is_firmware && !request->_tempObject) {
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
emsesp::EMSESP::system_.restart_pending(true); // will be handled by the main loop. We use pending for the Web's RestartMonitor
|
||||
emsesp::EMSESP::system_.systemStatus(
|
||||
emsesp::SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART); // will be handled by the main loop. We use pending for the Web's SystemMonitor
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1734,29 +1734,34 @@ void EMSESP::loop() {
|
||||
esp32React.loop(); // web services
|
||||
system_.loop(); // does LED and checks system health, and syslog service
|
||||
|
||||
// if we're doing an OTA upload, skip everything except from console refresh
|
||||
static bool upload_status = true; // ready for any OTA uploads
|
||||
if (!system_.upload_isrunning()) {
|
||||
// run the loop, unless we're in the middle of an OTA upload
|
||||
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_NORMAL) {
|
||||
// service loops
|
||||
webLogService.loop(); // log in Web UI
|
||||
rxservice_.loop(); // process any incoming Rx telegrams
|
||||
shower_.loop(); // check for shower on/off
|
||||
temperaturesensor_.loop(); // read sensor temperatures
|
||||
analogsensor_.loop(); // read analog sensor values
|
||||
publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue
|
||||
publish_all_loop(); // with HA messages in parts to avoid flooding the MQTT queue
|
||||
mqtt_.loop(); // sends out anything in the MQTT queue
|
||||
webModulesService.loop(); // loop through the external library modules
|
||||
if (system_.PSram() == 0) { // run non-async if there is no PSRAM available
|
||||
webSchedulerService.loop();
|
||||
}
|
||||
|
||||
scheduled_fetch_values(); // force a query on the EMS devices to fetch latest data at a set interval (1 min)
|
||||
}
|
||||
|
||||
} else if (upload_status) {
|
||||
// start an upload from a URL, if it exists. This is blocking.
|
||||
if (!system_.uploadFirmwareURL()) {
|
||||
upload_status = false; // abort all other attempts, until reset (after a restart normally)
|
||||
system_.upload_isrunning(false);
|
||||
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_PENDING_UPLOAD) {
|
||||
// start an upload from a URL, assuming if the URL exists from a previous pass.
|
||||
// Note this function is synchronous and blocking.
|
||||
if (system_.uploadFirmwareURL()) {
|
||||
// firmware has been uploaded, set status to uploading
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING);
|
||||
} else {
|
||||
// if it fails to pass, reset
|
||||
Shell::loop_all(); // flush log buffers so latest error message are shown in console
|
||||
system_.uploadFirmwareURL("reset");
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,8 +83,6 @@ 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_;
|
||||
@@ -298,8 +296,7 @@ void System::system_restart(const char * partitionname) {
|
||||
}
|
||||
|
||||
// make sure it's only executed once
|
||||
restart_requested(false);
|
||||
restart_pending(false);
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL);
|
||||
|
||||
store_nvs_values(); // save any NVS values
|
||||
Shell::loop_all(); // flush log to output
|
||||
@@ -565,27 +562,10 @@ void System::led_init(bool refresh) {
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if OTA is uploading
|
||||
bool System::upload_isrunning() {
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
return false;
|
||||
#else
|
||||
return upload_isrunning_ || Update.isRunning();
|
||||
#endif
|
||||
}
|
||||
|
||||
void System::upload_isrunning(bool in_progress) {
|
||||
// if we've just started an upload
|
||||
if (!upload_isrunning_ && in_progress) {
|
||||
EMSuart::stop();
|
||||
}
|
||||
upload_isrunning_ = in_progress;
|
||||
}
|
||||
|
||||
// checks system health and handles LED flashing wizardry
|
||||
void System::loop() {
|
||||
// check if we're supposed to do a reset/restart
|
||||
if (restart_requested()) {
|
||||
if (systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED) {
|
||||
system_restart();
|
||||
}
|
||||
|
||||
@@ -706,7 +686,7 @@ void System::heartbeat_json(JsonObject output) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
output["freemem"] = getHeapMem();
|
||||
output["max_alloc"] = getMaxAllocMem();
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
|
||||
output["temperature"] = temperature_;
|
||||
#endif
|
||||
#endif
|
||||
@@ -1901,7 +1881,7 @@ bool System::load_board_profile(std::vector<int8_t> & data, const std::string &
|
||||
return true;
|
||||
}
|
||||
|
||||
// format command - factory reset, removing all config files
|
||||
// format command - factory reset, removing all config fi`les
|
||||
bool System::command_format(const char * value, const int8_t id) {
|
||||
LOG_INFO("Removing all config files");
|
||||
#ifndef EMSESP_STANDALONE
|
||||
@@ -1915,8 +1895,8 @@ bool System::command_format(const char * value, const int8_t id) {
|
||||
}
|
||||
#endif
|
||||
|
||||
EMSESP::system_.restart_requested(true); // will be handled by the main loop
|
||||
|
||||
// restart will be handled by the main loop
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1926,11 +1906,13 @@ bool System::command_restart(const char * value, const int8_t id) {
|
||||
// if it has an id then it's a web call and we need to queue the restart
|
||||
// default id is -1 when calling /api/system/restart directly for example
|
||||
LOG_INFO("Preparing to restart system");
|
||||
EMSESP::system_.restart_pending(true);
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_INFO("Restarting system immediately");
|
||||
EMSESP::system_.restart_requested(true); // will be handled by the main loop
|
||||
// restart will be handled by the main loop
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2022,25 +2004,38 @@ String System::getBBQKeesGatewayDetails() {
|
||||
|
||||
// Stream from an URL and send straight to OTA uploader service.
|
||||
//
|
||||
// This function needs to be called twice, once with a url to persist it, and second with no arguments to start the upload
|
||||
// This function needs to be called twice, 1st pass once with a url to persist it, 2nd pass with no arguments to start the upload
|
||||
// This is to avoid timeouts in callback functions, like calling from a web hook.
|
||||
bool System::uploadFirmwareURL(const char * url) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
|
||||
static String saved_url;
|
||||
|
||||
// if the URL is not empty, store the URL for the 2nd pass
|
||||
// if the URL is not empty, store the URL for the 2nd pass and exit
|
||||
if (url && strlen(url) > 0) {
|
||||
saved_url = url;
|
||||
EMSESP::system_.upload_isrunning(true); // tell EMS-ESP we're ready to start the uploading process
|
||||
// if the passed URL is "reset" abort the current upload. This is called when an error happens during OTA
|
||||
if (strncmp(url, "reset", 5) == 0) {
|
||||
LOG_DEBUG("Firmware upload - resetting");
|
||||
saved_url.clear();
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL);
|
||||
return true;
|
||||
}
|
||||
|
||||
// make sure we have a valid URL
|
||||
// given a URL to download from, save it
|
||||
saved_url = url;
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_PENDING_UPLOAD); // we're ready to start the upload
|
||||
return true;
|
||||
}
|
||||
|
||||
// assumed we have a valid URL from the 1st pass
|
||||
if (saved_url.isEmpty()) {
|
||||
LOG_ERROR("Firmware upload failed - invalid URL");
|
||||
return false; // error
|
||||
}
|
||||
|
||||
LOG_INFO("Firmware downloading from %s", saved_url.c_str());
|
||||
Shell::loop_all(); // flush log buffers so latest messages are shown in console
|
||||
|
||||
// Configure temporary client
|
||||
HTTPClient http;
|
||||
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); // important for GitHub 302's
|
||||
@@ -2051,42 +2046,46 @@ bool System::uploadFirmwareURL(const char * url) {
|
||||
// start a connection, returns -1 if fails
|
||||
int httpCode = http.GET();
|
||||
if (httpCode != HTTP_CODE_OK) {
|
||||
LOG_ERROR("Firmware upload failed. URL %s, HTTP code %d", saved_url.c_str(), httpCode);
|
||||
LOG_ERROR("Firmware upload failed - HTTP code %d", httpCode);
|
||||
http.end();
|
||||
return false; // error
|
||||
}
|
||||
|
||||
// check we have enough space for the upload in the ota partition
|
||||
int firmware_size = http.getSize();
|
||||
LOG_INFO("Firmware uploading (file: %s, size: %d bytes). Please wait...", saved_url.c_str(), firmware_size);
|
||||
LOG_INFO("Firmware uploading (size: %d bytes). Please wait...", firmware_size);
|
||||
if (!Update.begin(firmware_size)) {
|
||||
LOG_ERROR("Firmware upload failed - no space");
|
||||
http.end();
|
||||
return false; // error
|
||||
}
|
||||
|
||||
// flush log buffers so latest messages are shown
|
||||
Shell::loop_all();
|
||||
|
||||
Shell::loop_all(); // flush log buffers so latest messages are shown in console
|
||||
|
||||
// TODO do we need to stop the UART with EMSuart::stop() ?
|
||||
|
||||
// get tcp stream and send it to Updater
|
||||
WiFiClient * stream = http.getStreamPtr();
|
||||
if (Update.writeStream(*stream) != firmware_size) {
|
||||
LOG_ERROR("Firmware upload failed - size differences");
|
||||
http.end();
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD);
|
||||
return false; // error
|
||||
}
|
||||
|
||||
if (!Update.end(true)) {
|
||||
LOG_ERROR("Firmware upload failed - general error");
|
||||
http.end();
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_ERROR_UPLOAD);
|
||||
return false; // error
|
||||
}
|
||||
|
||||
// finished with upload
|
||||
http.end();
|
||||
|
||||
EMSESP::system_.upload_isrunning(false);
|
||||
saved_url.clear(); // prevent from downloading again
|
||||
|
||||
LOG_INFO("Firmware uploaded successfully. Restarting...");
|
||||
|
||||
restart_pending(true);
|
||||
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
|
||||
#endif
|
||||
|
||||
return true; // OK
|
||||
@@ -2148,4 +2147,14 @@ bool System::command_read(const char * value, const int8_t id) {
|
||||
return readCommand(value);
|
||||
}
|
||||
|
||||
// set the system status code - SYSTEM_STATUS in system.h
|
||||
void System::systemStatus(uint8_t status_code) {
|
||||
systemStatus_ = status_code;
|
||||
LOG_DEBUG("Setting System status code %d", status_code);
|
||||
}
|
||||
|
||||
uint8_t System::systemStatus() {
|
||||
return systemStatus_;
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -59,6 +59,17 @@ namespace emsesp {
|
||||
|
||||
enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110 };
|
||||
|
||||
enum SYSTEM_STATUS : uint8_t {
|
||||
SYSTEM_STATUS_NORMAL = 0,
|
||||
SYSTEM_STATUS_PENDING_UPLOAD = 1,
|
||||
SYSTEM_STATUS_UPLOADING = 2,
|
||||
SYSTEM_STATUS_ERROR_UPLOAD = 3,
|
||||
SYSTEM_STATUS_RESTARTING = 4,
|
||||
SYSTEM_STATUS_ERROR = 5,
|
||||
SYSTEM_STATUS_PENDING_RESTART = 6,
|
||||
SYSTEM_STATUS_RESTART_REQUESTED = 7
|
||||
};
|
||||
|
||||
class System {
|
||||
public:
|
||||
void start();
|
||||
@@ -88,8 +99,7 @@ class System {
|
||||
|
||||
void store_nvs_values();
|
||||
void system_restart(const char * partition = nullptr);
|
||||
void upload_isrunning(bool in_progress);
|
||||
bool upload_isrunning();
|
||||
|
||||
void show_mem(const char * note);
|
||||
void reload_settings();
|
||||
void syslog_init();
|
||||
@@ -122,6 +132,9 @@ class System {
|
||||
void button_init(bool refresh);
|
||||
void commands_init();
|
||||
|
||||
void systemStatus(uint8_t status_code);
|
||||
uint8_t systemStatus();
|
||||
|
||||
static void extractSettings(const char * filename, const char * section, JsonObject output);
|
||||
static bool saveSettings(const char * filename, const char * section, JsonObject input);
|
||||
|
||||
@@ -130,20 +143,6 @@ class System {
|
||||
|
||||
static bool readCommand(const char * data);
|
||||
|
||||
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_;
|
||||
}
|
||||
@@ -341,12 +340,13 @@ 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_;
|
||||
|
||||
uint8_t systemStatus_; // uses SYSTEM_STATUS enum
|
||||
|
||||
// button
|
||||
static PButton myPButton_; // PButton instance
|
||||
static void button_OnClick(PButton & b);
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "3.7.2-dev.11"
|
||||
#define EMSESP_APP_VERSION "3.7.2-dev.12"
|
||||
|
||||
@@ -160,7 +160,8 @@ 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_pending(true);
|
||||
emsesp::EMSESP::system_.systemStatus(
|
||||
emsesp::SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART); // will be handled by the main loop. We use pending for the Web's SystemMonitor
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -523,7 +523,7 @@ void WebSchedulerService::loop() {
|
||||
void WebSchedulerService::scheduler_task(void * pvParameters) {
|
||||
while (1) {
|
||||
delay(10); // no need to hurry
|
||||
if (!EMSESP::system_.upload_isrunning()) {
|
||||
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_NORMAL) {
|
||||
EMSESP::webSchedulerService.loop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * se
|
||||
|
||||
// /rest/systemStatus
|
||||
// This contains both system & hardware Status to avoid having multiple costly endpoints
|
||||
// This is also used for polling during the RestartMonitor to see if EMS-ESP is alive
|
||||
// This is also used for polling during the SystemMonitor to see if EMS-ESP is alive
|
||||
void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
|
||||
EMSESP::system_.refreshHeapMem(); // refresh free heap and max alloc heap
|
||||
|
||||
@@ -146,12 +146,11 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) {
|
||||
root["has_partition"] = false;
|
||||
}
|
||||
|
||||
// Matches status codes in 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_isrunning() ? "uploading" : "ready";
|
||||
// Also used in SystemMonitor.tsx
|
||||
root["status"] = EMSESP::system_.systemStatus(); // send the status. See System.h for status codes
|
||||
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART) {
|
||||
// we're ready to do the actual restart ASAP
|
||||
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -194,6 +193,8 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
|
||||
ok = getCustomSupport(root);
|
||||
} else if (action == "uploadURL" && is_admin) {
|
||||
ok = uploadURL(param.c_str());
|
||||
} else if (action == "systemStatus" && is_admin) {
|
||||
ok = setSystemStatus(param.c_str());
|
||||
}
|
||||
|
||||
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
|
||||
@@ -359,4 +360,11 @@ bool WebStatusService::uploadURL(const char * url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// action = systemStatus
|
||||
// sets the system status
|
||||
bool WebStatusService::setSystemStatus(const char * status) {
|
||||
emsesp::EMSESP::system_.systemStatus(Helpers::atoint(status));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -27,7 +27,7 @@ class WebStatusService {
|
||||
bool exportData(JsonObject root, std::string & type);
|
||||
bool getCustomSupport(JsonObject root);
|
||||
bool uploadURL(const char * url);
|
||||
|
||||
bool setSystemStatus(const char * status);
|
||||
void allvalues(JsonObject output);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user