mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 08:19:52 +03:00
initial commit
This commit is contained in:
30
interface/src/system/OTASettingsController.tsx
Normal file
30
interface/src/system/OTASettingsController.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components';
|
||||
import { OTA_SETTINGS_ENDPOINT } from '../api';
|
||||
|
||||
import OTASettingsForm from './OTASettingsForm';
|
||||
import { OTASettings } from './types';
|
||||
|
||||
type OTASettingsControllerProps = RestControllerProps<OTASettings>;
|
||||
|
||||
class OTASettingsController extends Component<OTASettingsControllerProps> {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadData();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SectionContent title="OTA Settings" titleGutter>
|
||||
<RestFormLoader
|
||||
{...this.props}
|
||||
render={formProps => <OTASettingsForm {...formProps} />}
|
||||
/>
|
||||
</SectionContent>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default restController(OTA_SETTINGS_ENDPOINT, OTASettingsController);
|
||||
69
interface/src/system/OTASettingsForm.tsx
Normal file
69
interface/src/system/OTASettingsForm.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
|
||||
|
||||
import { Checkbox } from '@material-ui/core';
|
||||
import SaveIcon from '@material-ui/icons/Save';
|
||||
|
||||
import { RestFormProps, BlockFormControlLabel, PasswordValidator, FormButton, FormActions } from '../components';
|
||||
import {isIP,isHostname,or} from '../validators';
|
||||
|
||||
import { OTASettings } from './types';
|
||||
|
||||
type OTASettingsFormProps = RestFormProps<OTASettings>;
|
||||
|
||||
class OTASettingsForm extends React.Component<OTASettingsFormProps> {
|
||||
|
||||
componentDidMount() {
|
||||
ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, handleValueChange, saveData, loadData } = this.props;
|
||||
return (
|
||||
<ValidatorForm onSubmit={saveData}>
|
||||
<BlockFormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.enabled}
|
||||
onChange={handleValueChange("enabled")}
|
||||
/>
|
||||
}
|
||||
label="Enable OTA Updates?"
|
||||
/>
|
||||
<TextValidator
|
||||
validators={['required', 'isNumber', 'minNumber:1025', 'maxNumber:65535']}
|
||||
errorMessages={['Port is required', "Must be a number", "Must be greater than 1024 ", "Max value is 65535"]}
|
||||
name="port"
|
||||
label="Port"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.port}
|
||||
type="number"
|
||||
onChange={handleValueChange('port')}
|
||||
margin="normal"
|
||||
/>
|
||||
<PasswordValidator
|
||||
validators={['required', 'matchRegexp:^.{1,64}$']}
|
||||
errorMessages={['OTA Password is required', 'OTA Point Password must be 64 characters or less']}
|
||||
name="password"
|
||||
label="Password"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.password}
|
||||
onChange={handleValueChange('password')}
|
||||
margin="normal"
|
||||
/>
|
||||
<FormActions>
|
||||
<FormButton startIcon={<SaveIcon />} variant="contained" color="primary" type="submit">
|
||||
Save
|
||||
</FormButton>
|
||||
<FormButton variant="contained" color="secondary" onClick={loadData}>
|
||||
Reset
|
||||
</FormButton>
|
||||
</FormActions>
|
||||
</ValidatorForm>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default OTASettingsForm;
|
||||
51
interface/src/system/System.tsx
Normal file
51
interface/src/system/System.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'
|
||||
|
||||
import { Tabs, Tab } from '@material-ui/core';
|
||||
|
||||
import { WithFeaturesProps, withFeatures } from '../features/FeaturesContext';
|
||||
|
||||
import { withAuthenticatedContext, AuthenticatedContextProps, AuthenticatedRoute } from '../authentication';
|
||||
import { MenuAppBar } from '../components';
|
||||
|
||||
import SystemStatusController from './SystemStatusController';
|
||||
import OTASettingsController from './OTASettingsController';
|
||||
import UploadFirmwareController from './UploadFirmwareController';
|
||||
|
||||
type SystemProps = AuthenticatedContextProps & RouteComponentProps & WithFeaturesProps;
|
||||
|
||||
class System extends Component<SystemProps> {
|
||||
|
||||
handleTabChange = (event: React.ChangeEvent<{}>, path: string) => {
|
||||
this.props.history.push(path);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { authenticatedContext, features } = this.props;
|
||||
return (
|
||||
<MenuAppBar sectionTitle="System">
|
||||
<Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
|
||||
<Tab value="/system/status" label="System Status" />
|
||||
{features.ota && (
|
||||
<Tab value="/system/ota" label="OTA Settings" disabled={!authenticatedContext.me.admin} />
|
||||
)}
|
||||
{features.upload_firmware && (
|
||||
<Tab value="/system/upload" label="Upload Firmware" disabled={!authenticatedContext.me.admin} />
|
||||
)}
|
||||
</Tabs>
|
||||
<Switch>
|
||||
<AuthenticatedRoute exact path="/system/status" component={SystemStatusController} />
|
||||
{features.ota && (
|
||||
<AuthenticatedRoute exact path="/system/ota" component={OTASettingsController} />
|
||||
)}
|
||||
{features.upload_firmware && (
|
||||
<AuthenticatedRoute exact path="/system/upload" component={UploadFirmwareController} />
|
||||
)}
|
||||
<Redirect to="/system/status" />
|
||||
</Switch>
|
||||
</MenuAppBar>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withFeatures(withAuthenticatedContext(System));
|
||||
30
interface/src/system/SystemStatusController.tsx
Normal file
30
interface/src/system/SystemStatusController.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components';
|
||||
import { SYSTEM_STATUS_ENDPOINT } from '../api';
|
||||
|
||||
import SystemStatusForm from './SystemStatusForm';
|
||||
import { SystemStatus } from './types';
|
||||
|
||||
type SystemStatusControllerProps = RestControllerProps<SystemStatus>;
|
||||
|
||||
class SystemStatusController extends Component<SystemStatusControllerProps> {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadData();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SectionContent title="System Status">
|
||||
<RestFormLoader
|
||||
{...this.props}
|
||||
render={formProps => <SystemStatusForm {...formProps} />}
|
||||
/>
|
||||
</SectionContent>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default restController(SYSTEM_STATUS_ENDPOINT, SystemStatusController);
|
||||
245
interface/src/system/SystemStatusForm.tsx
Normal file
245
interface/src/system/SystemStatusForm.tsx
Normal file
@@ -0,0 +1,245 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
|
||||
import { Avatar, Button, Divider, Dialog, DialogTitle, DialogContent, DialogActions, Box } from '@material-ui/core';
|
||||
import { List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core';
|
||||
|
||||
import DevicesIcon from '@material-ui/icons/Devices';
|
||||
import MemoryIcon from '@material-ui/icons/Memory';
|
||||
import ShowChartIcon from '@material-ui/icons/ShowChart';
|
||||
import SdStorageIcon from '@material-ui/icons/SdStorage';
|
||||
import FolderIcon from '@material-ui/icons/Folder';
|
||||
import DataUsageIcon from '@material-ui/icons/DataUsage';
|
||||
import AppsIcon from '@material-ui/icons/Apps';
|
||||
import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
import SettingsBackupRestoreIcon from '@material-ui/icons/SettingsBackupRestore';
|
||||
|
||||
import { redirectingAuthorizedFetch, AuthenticatedContextProps, withAuthenticatedContext } from '../authentication';
|
||||
import { RestFormProps, FormButton, ErrorButton } from '../components';
|
||||
import { FACTORY_RESET_ENDPOINT, RESTART_ENDPOINT } from '../api';
|
||||
|
||||
import { SystemStatus, EspPlatform } from './types';
|
||||
|
||||
interface SystemStatusFormState {
|
||||
confirmRestart: boolean;
|
||||
confirmFactoryReset: boolean;
|
||||
processing: boolean;
|
||||
}
|
||||
|
||||
type SystemStatusFormProps = AuthenticatedContextProps & RestFormProps<SystemStatus>;
|
||||
|
||||
function formatNumber(num: number) {
|
||||
return new Intl.NumberFormat().format(num);
|
||||
}
|
||||
|
||||
class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusFormState> {
|
||||
|
||||
state: SystemStatusFormState = {
|
||||
confirmRestart: false,
|
||||
confirmFactoryReset: false,
|
||||
processing: false
|
||||
}
|
||||
|
||||
createListItems() {
|
||||
const { data } = this.props
|
||||
return (
|
||||
<Fragment>
|
||||
<ListItem >
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DevicesIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Device (Platform / SDK)" secondary={data.esp_platform + ' / ' + data.sdk_version} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem >
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<ShowChartIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="CPU Frequency" secondary={data.cpu_freq_mhz + ' MHz'} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem >
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<MemoryIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Heap (Free / Max Alloc)" secondary={formatNumber(data.free_heap) + ' / ' + formatNumber(data.max_alloc_heap) + ' bytes ' + (data.esp_platform === EspPlatform.ESP8266 ? '(' + data.heap_fragmentation + '% fragmentation)' : '')} />
|
||||
</ListItem>
|
||||
{
|
||||
(data.esp_platform === EspPlatform.ESP32 && data.psram_size > 0) && (
|
||||
<Fragment>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem >
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<AppsIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="PSRAM (Size / Free)" secondary={formatNumber(data.psram_size) + ' / ' + formatNumber(data.free_psram) + ' bytes'} />
|
||||
</ListItem>
|
||||
</Fragment>)
|
||||
}
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem >
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DataUsageIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Sketch (Size / Free)" secondary={formatNumber(data.sketch_size) + ' / ' + formatNumber(data.free_sketch_space) + ' bytes'} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem >
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<SdStorageIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Flash Chip (Size / Speed)" secondary={formatNumber(data.flash_chip_size) + ' bytes / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz'} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem >
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<FolderIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="File System (Used / Total)" secondary={formatNumber(data.fs_used) + ' / ' + formatNumber(data.fs_total) + ' bytes (' + formatNumber(data.fs_total - data.fs_used) + '\xa0bytes free)'} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderRestartDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.confirmRestart}
|
||||
onClose={this.onRestartRejected}
|
||||
>
|
||||
<DialogTitle>Confirm Restart</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
Are you sure you want to restart the device?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="contained" onClick={this.onRestartRejected} color="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" onClick={this.onRestartConfirmed} disabled={this.state.processing} color="primary" autoFocus>
|
||||
Restart
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
onRestart = () => {
|
||||
this.setState({ confirmRestart: true });
|
||||
}
|
||||
|
||||
onRestartRejected = () => {
|
||||
this.setState({ confirmRestart: false });
|
||||
}
|
||||
|
||||
onRestartConfirmed = () => {
|
||||
this.setState({ processing: true });
|
||||
redirectingAuthorizedFetch(RESTART_ENDPOINT, { method: 'POST' })
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
this.props.enqueueSnackbar("Device is restarting", { variant: 'info' });
|
||||
this.setState({ processing: false, confirmRestart: false });
|
||||
} else {
|
||||
throw Error("Invalid status code: " + response.status);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.props.enqueueSnackbar(error.message || "Problem restarting device", { variant: 'error' });
|
||||
this.setState({ processing: false, confirmRestart: false });
|
||||
});
|
||||
}
|
||||
|
||||
renderFactoryResetDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
open={this.state.confirmFactoryReset}
|
||||
onClose={this.onFactoryResetRejected}
|
||||
>
|
||||
<DialogTitle>Confirm Factory Reset</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
Are you sure you want to reset the device to its factory defaults?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="contained" onClick={this.onFactoryResetRejected} color="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
<ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryResetConfirmed} disabled={this.state.processing} autoFocus>
|
||||
Factory Reset
|
||||
</ErrorButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
onFactoryReset = () => {
|
||||
this.setState({ confirmFactoryReset: true });
|
||||
}
|
||||
|
||||
onFactoryResetRejected = () => {
|
||||
this.setState({ confirmFactoryReset: false });
|
||||
}
|
||||
|
||||
onFactoryResetConfirmed = () => {
|
||||
this.setState({ processing: true });
|
||||
redirectingAuthorizedFetch(FACTORY_RESET_ENDPOINT, { method: 'POST' })
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
this.props.enqueueSnackbar("Factory reset in progress.", { variant: 'error' });
|
||||
this.setState({ processing: false, confirmFactoryReset: false });
|
||||
} else {
|
||||
throw Error("Invalid status code: " + response.status);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.props.enqueueSnackbar(error.message || "Problem factory resetting device", { variant: 'error' });
|
||||
this.setState({ processing: false, confirmRestart: false });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const me = this.props.authenticatedContext.me;
|
||||
return (
|
||||
<Fragment>
|
||||
<List>
|
||||
{this.createListItems()}
|
||||
</List>
|
||||
<Box display="flex" flexWrap="wrap">
|
||||
<Box flexGrow={1} padding={1}>
|
||||
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
|
||||
Refresh
|
||||
</FormButton>
|
||||
</Box>
|
||||
{me.admin &&
|
||||
<Box flexWrap="none" padding={1} whiteSpace="nowrap">
|
||||
<FormButton startIcon={<PowerSettingsNewIcon />} variant="contained" color="primary" onClick={this.onRestart}>
|
||||
Restart
|
||||
</FormButton>
|
||||
<ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryReset}>
|
||||
Factory reset
|
||||
</ErrorButton>
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
{this.renderRestartDialog()}
|
||||
{this.renderFactoryResetDialog()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withAuthenticatedContext(SystemStatusForm);
|
||||
71
interface/src/system/UploadFirmwareController.tsx
Normal file
71
interface/src/system/UploadFirmwareController.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { SectionContent } from '../components';
|
||||
import { UPLOAD_FIRMWARE_ENDPOINT } from '../api';
|
||||
|
||||
import UploadFirmwareForm from './UploadFirmwareForm';
|
||||
import { redirectingAuthorizedUpload } from '../authentication';
|
||||
import { withSnackbar, WithSnackbarProps } from 'notistack';
|
||||
|
||||
interface UploadFirmwareControllerState {
|
||||
xhr?: XMLHttpRequest;
|
||||
progress?: ProgressEvent;
|
||||
}
|
||||
|
||||
class UploadFirmwareController extends Component<WithSnackbarProps, UploadFirmwareControllerState> {
|
||||
|
||||
state: UploadFirmwareControllerState = {
|
||||
xhr: undefined,
|
||||
progress: undefined
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.state.xhr?.abort();
|
||||
}
|
||||
|
||||
updateProgress = (progress: ProgressEvent) => {
|
||||
this.setState({ progress });
|
||||
}
|
||||
|
||||
uploadFile = (file: File) => {
|
||||
if (this.state.xhr) {
|
||||
return;
|
||||
}
|
||||
var xhr = new XMLHttpRequest();
|
||||
this.setState({ xhr });
|
||||
redirectingAuthorizedUpload(xhr, UPLOAD_FIRMWARE_ENDPOINT, file, this.updateProgress).then(() => {
|
||||
if (xhr.status !== 200) {
|
||||
throw Error("Invalid status code: " + xhr.status);
|
||||
}
|
||||
this.props.enqueueSnackbar("Activating new firmware", { variant: 'success' });
|
||||
this.setState({ xhr: undefined, progress: undefined });
|
||||
}).catch((error: Error) => {
|
||||
if (error.name === 'AbortError') {
|
||||
this.props.enqueueSnackbar("Upload cancelled by user", { variant: 'warning' });
|
||||
} else {
|
||||
const errorMessage = error.name === 'UploadError' ? "Error during upload" : (error.message || "Unknown error");
|
||||
this.props.enqueueSnackbar("Problem uploading: " + errorMessage, { variant: 'error' });
|
||||
this.setState({ xhr: undefined, progress: undefined });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancelUpload = () => {
|
||||
if (this.state.xhr) {
|
||||
this.state.xhr.abort();
|
||||
this.setState({ xhr: undefined, progress: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { xhr, progress } = this.state;
|
||||
return (
|
||||
<SectionContent title="Upload Firmware">
|
||||
<UploadFirmwareForm onFileSelected={this.uploadFile} onCancel={this.cancelUpload} uploading={!!xhr} progress={progress} />
|
||||
</SectionContent>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withSnackbar(UploadFirmwareController);
|
||||
35
interface/src/system/UploadFirmwareForm.tsx
Normal file
35
interface/src/system/UploadFirmwareForm.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { SingleUpload } from '../components';
|
||||
import { Box } from '@material-ui/core';
|
||||
|
||||
interface UploadFirmwareFormProps {
|
||||
uploading: boolean;
|
||||
progress?: ProgressEvent;
|
||||
onFileSelected: (file: File) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
class UploadFirmwareForm extends React.Component<UploadFirmwareFormProps> {
|
||||
|
||||
handleDrop = (files: File[]) => {
|
||||
const file = files[0];
|
||||
if (file) {
|
||||
this.props.onFileSelected(files[0]);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { uploading, progress, onCancel } = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<Box py={2}>
|
||||
Upload a new firmware (.bin) file below to replace the existing firmware.
|
||||
</Box>
|
||||
<SingleUpload accept="application/octet-stream" onDrop={this.handleDrop} uploading={uploading} progress={progress} onCancel={onCancel} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default UploadFirmwareForm;
|
||||
37
interface/src/system/types.ts
Normal file
37
interface/src/system/types.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export enum EspPlatform {
|
||||
ESP8266 = "esp8266",
|
||||
ESP32 = "esp32"
|
||||
}
|
||||
|
||||
interface ESPSystemStatus {
|
||||
esp_platform: EspPlatform;
|
||||
max_alloc_heap: number;
|
||||
cpu_freq_mhz: number;
|
||||
free_heap: number;
|
||||
sketch_size: number;
|
||||
free_sketch_space: number;
|
||||
sdk_version: string;
|
||||
flash_chip_size: number;
|
||||
flash_chip_speed: number;
|
||||
fs_used: number;
|
||||
fs_total: number;
|
||||
}
|
||||
|
||||
export interface ESP32SystemStatus extends ESPSystemStatus {
|
||||
esp_platform: EspPlatform.ESP32;
|
||||
psram_size: number;
|
||||
free_psram: number;
|
||||
}
|
||||
|
||||
export interface ESP8266SystemStatus extends ESPSystemStatus {
|
||||
esp_platform: EspPlatform.ESP8266;
|
||||
heap_fragmentation: number;
|
||||
}
|
||||
|
||||
export type SystemStatus = ESP8266SystemStatus | ESP32SystemStatus;
|
||||
|
||||
export interface OTASettings {
|
||||
enabled: boolean;
|
||||
port: number;
|
||||
password: string;
|
||||
}
|
||||
Reference in New Issue
Block a user