This commit is contained in:
proddy
2023-06-13 23:36:50 +02:00
parent 8e081ce04f
commit c44903e1b0
31 changed files with 1132 additions and 1106 deletions

View File

@@ -23,14 +23,14 @@
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.11.16",
"@mui/material": "^5.13.4",
"@mui/material": "^5.13.5",
"@table-library/react-table-library": "4.1.4",
"@types/lodash-es": "^4.17.7",
"@types/node": "^20.3.0",
"@types/react": "^18.2.11",
"@types/react-dom": "^18.2.4",
"@types/node": "^20.3.1",
"@types/react": "^18.2.12",
"@types/react-dom": "^18.2.5",
"@types/react-router-dom": "^5.3.3",
"alova": "^2.6.0",
"alova": "^2.6.1",
"async-validator": "^4.2.5",
"axios": "^1.4.0",
"history": "^5.3.0",
@@ -47,8 +47,8 @@
"typescript": "^5.1.3"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.59.9",
"@typescript-eslint/parser": "^5.59.9",
"@typescript-eslint/eslint-plugin": "^5.59.11",
"@typescript-eslint/parser": "^5.59.11",
"@vitejs/plugin-react-swc": "^3.3.2",
"eslint": "^8.42.0",
"eslint-config-airbnb": "^19.0.4",
@@ -65,7 +65,7 @@
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.9.2",
"terser": "^5.17.7",
"terser": "^5.18.0",
"vite": "^4.3.9",
"vite-plugin-svgr": "^3.2.0",
"vite-tsconfig-paths": "^4.2.0"

View File

@@ -5,6 +5,7 @@ import type { APSettings, APStatus } from 'types';
export const readAPStatus = () => alovaInstance.Get<APStatus>('/apStatus');
// TODO change AXIOS to Alova
export function readAPSettings(): AxiosPromise<APSettings> {
return AXIOS.get('/apSettings');
}

View File

@@ -32,10 +32,10 @@ export const alovaInstance = createAlova({
responded: {
onSuccess: async (response) => {
if (response.status == 202) {
if (response.status == 205) {
throw new Error('Reboot required');
} else if (response.status === 400) {
throw new Error('Invalid command');
throw new Error('Request Failed');
} else if (response.status >= 400) {
throw new Error(response.statusText);
}
@@ -114,8 +114,7 @@ export const AXIOS_BIN = axios.create({
transformResponse: [(data) => unpack(data)]
});
// TODO replace with alova
// TODO see https://alova.js.org/next-step/download-upload-progress
// TODO replace upload with alova, see https://alova.js.org/next-step/download-upload-progress
export interface FileUploadConfig {
cancelToken?: CancelToken;
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;

View File

@@ -28,7 +28,6 @@ export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
};
const APStatusForm: FC = () => {
// TODO missing update!
const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus());
const { LL } = useI18nContext();

View File

@@ -26,7 +26,6 @@ export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: T
export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatus, theme: Theme) => {
if (mqtt_fails === 0) return theme.palette.success.main;
if (mqtt_fails < 10) return theme.palette.warning.main;
return theme.palette.error.main;
@@ -39,8 +38,7 @@ export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatus, theme: Theme) =>
};
const MqttStatusForm: FC = () => {
// TODO missing update!
// TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus());
const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus });
const { LL } = useI18nContext();

View File

@@ -59,8 +59,7 @@ const IPs = (status: NetworkStatus) => {
};
const NetworkStatusForm: FC = () => {
// TODO missing update!
// TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus());
const { loadData, data, errorMessage } = useRest<NetworkStatus>({ read: NetworkApi.readNetworkStatus });
const { LL } = useI18nContext();

View File

@@ -52,9 +52,9 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => {
};
const NTPStatusForm: FC = () => {
// TODO missing update!
// TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus());
const { loadData, data, errorMessage } = useRest<NTPStatus>({ read: NTPApi.readNTPStatus });
const [localTime, setLocalTime] = useState<string>('');
const [settingTime, setSettingTime] = useState<boolean>(false);
const [processing, setProcessing] = useState<boolean>(false);

View File

@@ -49,8 +49,7 @@ const levelLabel = (level: LogLevel) => {
const SystemLog: FC = () => {
const { LL } = useI18nContext();
// TODO missing update!
// TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus());
const { loadData, data, setData, origData, dirtyFlags, blocker, setDirtyFlags, setOrigData } = useRest<LogSettings>({
read: SystemApi.readLogSettings
});

View File

@@ -53,8 +53,7 @@ const SystemStatusForm: FC = () => {
const { LL } = useI18nContext();
const [restarting, setRestarting] = useState<boolean>();
// TODO missing update!
// TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus());
const { loadData, data, errorMessage } = useRest<SystemStatus>({ read: SystemApi.readSystemStatus });
const { me } = useContext(AuthenticatedContext);

View File

@@ -69,7 +69,7 @@ const DashboardDevices: FC = () => {
immediate: true
});
const { data: deviceData, send: readDeviceData } = useRequest((data) => EMSESP.readDeviceData(data), {
const { data: deviceData, send: readDeviceData } = useRequest((id) => EMSESP.readDeviceData(id), {
initialData: {
data: []
},
@@ -444,7 +444,7 @@ const DashboardDevices: FC = () => {
};
const renderDeviceData = () => {
if (!selectedDevice || deviceData.data === undefined) {
if (!selectedDevice) {
return;
}

View File

@@ -7,7 +7,8 @@ import { Button, Typography, Box } from '@mui/material';
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
import { useState, useContext, useCallback, useEffect } from 'react';
import { useRequest } from 'alova';
import { useState, useContext, useEffect } from 'react';
import { toast } from 'react-toastify';
@@ -17,24 +18,40 @@ import * as EMSESP from './api';
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames } from './types';
import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators';
import type { SensorData, TemperatureSensor, AnalogSensor } from './types';
import type { TemperatureSensor, AnalogSensor } from './types';
import type { FC } from 'react';
import { ButtonRow, SectionContent } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import { extractErrorMessage } from 'utils';
const DashboardSensors: FC = () => {
const { LL } = useI18nContext();
const { me } = useContext(AuthenticatedContext);
const [sensorData, setSensorData] = useState<SensorData>({ ts: [], as: [], analog_enabled: false });
const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState<TemperatureSensor>();
const [selectedAnalogSensor, setSelectedAnalogSensor] = useState<AnalogSensor>();
const [temperatureDialogOpen, setTemperatureDialogOpen] = useState<boolean>(false);
const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false);
const [creating, setCreating] = useState<boolean>(false);
const { data: sensorData, send: fetchSensorData } = useRequest(() => EMSESP.readSensorData(), {
initialData: {
ts: [],
as: [],
analog_enabled: false
},
force: true,
immediate: true
});
const { send: writeTemperatureSensor } = useRequest((data) => EMSESP.writeTemperatureSensor(data), {
immediate: false
});
const { send: writeAnalogSensor } = useRequest((data) => EMSESP.writeAnalogSensor(data), {
immediate: false
});
const isAdmin = me.admin;
const common_theme = useTheme({
@@ -101,20 +118,6 @@ const DashboardSensors: FC = () => {
}
]);
const fetchSensorData = useCallback(async () => {
if (!analogDialogOpen && !temperatureDialogOpen) {
try {
setSensorData((await EMSESP.readSensorData()).data);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
}
}, [LL, analogDialogOpen, temperatureDialogOpen]);
useEffect(() => {
void fetchSensorData();
}, [fetchSensorData]);
const getSortIcon = (state: any, sortKey: any) => {
if (state.sortKey === sortKey && state.reverse) {
return <KeyboardArrowDownOutlinedIcon />;
@@ -229,27 +232,18 @@ const DashboardSensors: FC = () => {
};
const onTemperatureDialogSave = async (ts: TemperatureSensor) => {
try {
const response = await EMSESP.writeTemperatureSensor({
id: ts.id,
name: ts.n,
offset: ts.o
});
if (response.status === 204) {
toast.error(LL.UPDATE_OF(LL.SENSOR(2)) + ' ' + LL.FAILED(1));
} else if (response.status === 403) {
// TODO fix
toast.error(LL.HTTP_ERROR('poep'));
} else {
await writeTemperatureSensor({ id: ts.id, name: ts.n, offset: ts.o })
.then(() => {
toast.success(LL.UPDATED_OF(LL.SENSOR(1)));
}
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
} finally {
})
.catch(() => {
toast.error(LL.UPDATE_OF(LL.SENSOR(2)) + ' ' + LL.FAILED(1));
})
.finally(async () => {
setTemperatureDialogOpen(false);
setSelectedTemperatureSensor(undefined);
await fetchSensorData();
}
});
};
const updateAnalogSensor = (as: AnalogSensor) => {
@@ -281,8 +275,7 @@ const DashboardSensors: FC = () => {
};
const onAnalogDialogSave = async (as: AnalogSensor) => {
try {
const response = await EMSESP.writeAnalogSensor({
await writeAnalogSensor({
id: as.id,
gpio: as.g,
name: as.n,
@@ -291,23 +284,18 @@ const DashboardSensors: FC = () => {
uom: as.u,
type: as.t,
deleted: as.d
});
if (response.status === 204) {
toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR(5)) + ' ' + LL.FAILED(1));
} else if (response.status === 403) {
// TODO fix
toast.error(LL.HTTP_ERROR('poep'));
} else {
})
.then(() => {
toast.success(LL.UPDATED_OF(LL.ANALOG_SENSOR(2)));
}
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
} finally {
})
.catch(() => {
toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR(5)) + ' ' + LL.FAILED(1));
})
.finally(async () => {
setAnalogDialogOpen(false);
setSelectedAnalogSensor(undefined);
await fetchSensorData();
}
});
};
const RenderTemperatureSensors = () => (
@@ -433,7 +421,7 @@ const DashboardSensors: FC = () => {
{sensorData?.analog_enabled === true && (
<>
<Typography sx={{ pt: 4, pb: 1 }} variant="h6" color="secondary">
{LL.ANALOG_SENSORS(0)}
{LL.ANALOG_SENSORS()}
</Typography>
<RenderAnalogSensors />
{selectedAnalogSensor && (

View File

@@ -19,6 +19,7 @@ import {
} from '@mui/material';
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
import { useRequest } from 'alova';
import { useContext, useEffect, useState } from 'react';
import { toast } from 'react-toastify';
@@ -32,7 +33,6 @@ import type { FC } from 'react';
import { ButtonRow, FormLoader, SectionContent } from 'components';
import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import { extractErrorMessage, useRest } from 'utils';
export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE;
@@ -64,9 +64,7 @@ const showQuality = (stat: Stat) => {
};
const DashboardStatus: FC = () => {
// TODO missing update!
const { loadData, data, errorMessage } = useRest<Status>({ read: EMSESP.readStatus });
const { data: data, send: loadData, error } = useRequest(EMSESP.readStatus, { force: true });
const { LL } = useI18nContext();
@@ -75,6 +73,10 @@ const DashboardStatus: FC = () => {
const { me } = useContext(AuthenticatedContext);
const { send: scanDevices } = useRequest(EMSESP.scanDevices, {
immediate: false
});
const stats_theme = tableTheme({
Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px;
@@ -160,14 +162,23 @@ const DashboardStatus: FC = () => {
};
const scan = async () => {
try {
await EMSESP.scanDevices();
await scanDevices()
.then(() => {
toast.info(LL.SCANNING() + '...');
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
} finally {
})
.catch((err) => {
toast.error(err.message);
});
setConfirmScan(false);
}
// try {
// await EMSESP.scanDevices();
// toast.info(LL.SCANNING() + '...');
// } catch (error) {
// toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
// } finally {
// setConfirmScan(false);
// }
};
const renderScanDialog = () => (
@@ -187,7 +198,7 @@ const DashboardStatus: FC = () => {
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (

View File

@@ -2,6 +2,7 @@ import CancelIcon from '@mui/icons-material/Cancel';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import WarningIcon from '@mui/icons-material/Warning';
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment, TextField } from '@mui/material';
import { useRequest } from 'alova';
import { useState } from 'react';
import { toast } from 'react-toastify';
@@ -23,7 +24,7 @@ import {
import RestartMonitor from 'framework/system/RestartMonitor';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, extractErrorMessage, updateValueDirty, useRest2 } from 'utils';
import { numberValue, updateValueDirty, useRest2 } from 'utils';
import { validate } from 'validators';
export function boardProfileSelectItems() {
@@ -39,7 +40,7 @@ const SettingsApplication: FC = () => {
loadData,
saveData,
saving,
setData,
updateDataValue,
data,
origData,
dirtyFlags,
@@ -51,45 +52,48 @@ const SettingsApplication: FC = () => {
read: EMSESP.readSettings,
update: EMSESP.writeSettings
});
const [restarting, setRestarting] = useState<boolean>();
const { LL } = useI18nContext();
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const [processingBoard, setProcessingBoard] = useState<boolean>(false);
// TODO remove - just for testing loaddata
// useEffect(() => {
// void loadData();
// }, []);
// TODO replace with Alova!
const updateBoardProfile = async (boardProfile: string) => {
setProcessingBoard(true);
try {
const response = await EMSESP.getBoardProfile({ board_profile: boardProfile });
if (data) {
setData({
...data,
board_profile: boardProfile,
led_gpio: response.data.led_gpio,
dallas_gpio: response.data.dallas_gpio,
rx_gpio: response.data.rx_gpio,
tx_gpio: response.data.tx_gpio,
pbutton_gpio: response.data.pbutton_gpio,
phy_type: response.data.phy_type,
eth_power: response.data.eth_power,
eth_phy_addr: response.data.eth_phy_addr,
eth_clock_mode: response.data.eth_clock_mode
const {
loading: processingBoard,
send: readBoardProfile,
onSuccess: onSuccessBoardProfile
} = useRequest((boardProfile) => EMSESP.getBoardProfile(boardProfile), {
immediate: false
});
const { send: restartCommand } = useRequest(EMSESP.restart(), {
immediate: false
});
onSuccessBoardProfile((event) => {
const response = event.data as unknown as Settings;
updateDataValue({
...data,
board_profile: response.board_profile,
led_gpio: response.led_gpio,
dallas_gpio: response.dallas_gpio,
rx_gpio: response.rx_gpio,
tx_gpio: response.tx_gpio,
pbutton_gpio: response.pbutton_gpio,
phy_type: response.phy_type,
eth_power: response.eth_power,
eth_phy_addr: response.eth_phy_addr,
eth_clock_mode: response.eth_clock_mode
});
});
const updateBoardProfile = async (board_profile: string) => {
await readBoardProfile(board_profile).catch((error) => {
toast.error(error.message);
});
}
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
} finally {
setProcessingBoard(false);
}
};
const content = () => {
@@ -101,9 +105,10 @@ const SettingsApplication: FC = () => {
try {
setFieldErrors(undefined);
await validate(createSettingsValidator(data), data);
await saveData();
} catch (errors: any) {
setFieldErrors(errors);
} finally {
await saveData();
}
};
@@ -111,7 +116,7 @@ const SettingsApplication: FC = () => {
const boardProfile = event.target.value;
updateFormValue(event);
if (boardProfile === 'CUSTOM') {
setData({
updateDataValue({
...data,
board_profile: boardProfile
});
@@ -122,12 +127,10 @@ const SettingsApplication: FC = () => {
const restart = async () => {
await validateAndSubmit();
try {
await EMSESP.restart();
await restartCommand().catch((error) => {
toast.error(error.message);
});
setRestarting(true);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
}
};
return (

View File

@@ -21,6 +21,7 @@ import {
} from '@mui/material';
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
import { useRequest } from 'alova';
import { useState, useEffect, useCallback } from 'react';
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
import { toast } from 'react-toastify';
@@ -32,13 +33,12 @@ import SettingsCustomizationDialog from './SettingsCustomizationDialog';
import * as EMSESP from './api';
import { DeviceEntityMask } from './types';
import type { DeviceShort, Devices, DeviceEntity } from './types';
import type { DeviceShort, DeviceEntity } from './types';
import type { FC } from 'react';
import { ButtonRow, FormLoader, SectionContent, MessageBox, BlockNavigation } from 'components';
import { ButtonRow, SectionContent, MessageBox, BlockNavigation } from 'components';
import RestartMonitor from 'framework/system/RestartMonitor';
import { useI18nContext } from 'i18n/i18n-react';
import { extractErrorMessage } from 'utils';
export const APIURL = window.location.origin + '/api/';
@@ -46,11 +46,10 @@ const SettingsCustomization: FC = () => {
const { LL } = useI18nContext();
const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0);
const [restarting, setRestarting] = useState<boolean>(false);
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>();
const [devices, setDevices] = useState<Devices>();
const [errorMessage, setErrorMessage] = useState<string>();
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>([]);
const [selectedDevice, setSelectedDevice] = useState<number>(-1);
const [confirmReset, setConfirmReset] = useState<boolean>(false);
const [selectedFilters, setSelectedFilters] = useState<number>(0);
@@ -58,6 +57,36 @@ const SettingsCustomization: FC = () => {
const [selectedDeviceEntity, setSelectedDeviceEntity] = useState<DeviceEntity>();
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), {
immediate: false
});
const { data: devices } = useRequest(EMSESP.readDevices());
const { send: writeCustomEntities } = useRequest((data) => EMSESP.writeCustomEntities(data), { immediate: false });
const {
send: readDeviceEntities,
update: updateDeviceEntities,
onSuccess: onSuccess
} = useRequest((data) => EMSESP.readDeviceEntities(data), {
initialData: [],
immediate: false,
force: true
});
const setOriginalSettings = (data: DeviceEntity[]) => {
setDeviceEntities(data.map((de) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })));
};
onSuccess((event) => {
setOriginalSettings(event.data);
});
const { send: restartCommand } = useRequest(EMSESP.restart(), {
immediate: false
});
const entities_theme = useTheme({
Table: `
--data-table-library_grid-template-columns: 150px repeat(1, minmax(80px, 1fr)) 45px minmax(45px, auto) minmax(120px, auto);
@@ -131,7 +160,7 @@ const SettingsCustomization: FC = () => {
}
useEffect(() => {
if (deviceEntities) {
if (deviceEntities.length) {
setNumChanges(
deviceEntities
.filter((de) => hasEntityChanged(de))
@@ -148,29 +177,11 @@ const SettingsCustomization: FC = () => {
}
}, [deviceEntities]);
const fetchDevices = useCallback(async () => {
try {
setDevices((await EMSESP.readDevices()).data);
} catch (error) {
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
}, [LL]);
useEffect(() => {
void fetchDevices();
}, [fetchDevices]);
const setOriginalSettings = (data: DeviceEntity[]) => {
setDeviceEntities(data.map((de) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })));
};
const fetchDeviceEntities = async (unique_id: number) => {
try {
const new_deviceEntities = (await EMSESP.readDeviceEntities({ id: unique_id })).data;
setOriginalSettings(new_deviceEntities);
} catch (error) {
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
}
const restart = async () => {
await restartCommand().catch((error) => {
toast.error(error.message);
});
setRestarting(true);
};
function formatValue(value: any) {
@@ -225,7 +236,7 @@ const SettingsCustomization: FC = () => {
};
const maskDisabled = (set: boolean) => {
setDeviceEntities(
updateDeviceEntities(
deviceEntities?.map(function (de) {
if ((de.m & selectedFilters || !selectedFilters) && de.id.toLowerCase().includes(search.toLowerCase())) {
return {
@@ -246,31 +257,22 @@ const SettingsCustomization: FC = () => {
const selected_device = parseInt(event.target.value, 10);
setSelectedDevice(selected_device);
setNumChanges(0);
void fetchDeviceEntities(devices?.devices[selected_device].i);
void readDeviceEntities(devices?.devices[selected_device].i);
setRestartNeeded(false);
}
};
const resetCustomization = async () => {
try {
await EMSESP.resetCustomizations();
await resetCustomizations();
toast.info(LL.CUSTOMIZATIONS_RESTART());
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
toast.error(error.message);
} finally {
setConfirmReset(false);
}
};
const restart = async () => {
try {
await EMSESP.restart();
setRestarting(true);
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
}
};
const onDialogClose = () => {
setDialogOpen(false);
};
@@ -300,7 +302,7 @@ const SettingsCustomization: FC = () => {
const saveCustomization = async () => {
if (devices && deviceEntities && selectedDevice !== -1) {
const masked_entities = deviceEntities
.filter((de) => hasEntityChanged(de))
.filter((de: DeviceEntity) => hasEntityChanged(de))
.map(
(new_de) =>
new_de.m.toString(16).padStart(2, '0') +
@@ -318,31 +320,21 @@ const SettingsCustomization: FC = () => {
return;
}
try {
const response = await EMSESP.writeCustomEntities({
id: devices?.devices[selectedDevice].i,
entity_ids: masked_entities
});
if (response.status === 200) {
toast.success(LL.CUSTOMIZATIONS_SAVED());
} else if (response.status === 201) {
await writeCustomEntities({ id: devices?.devices[selectedDevice].i, entity_ids: masked_entities }).catch(
(error) => {
if (error.message === 'Reboot required') {
setRestartNeeded(true);
} else {
toast.error(LL.PROBLEM_UPDATING());
toast.error(error.message);
}
} catch (error) {
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
}
);
// does this work or use onSuccess hook?
setOriginalSettings(deviceEntities);
}
};
const renderDeviceList = () => {
if (!devices) {
return <FormLoader errorMessage={errorMessage} />;
}
return (
const renderDeviceList = () => (
<>
<Box mb={2} color="warning.main">
<Typography variant="body2">{LL.CUSTOMIZATIONS_HELP_1()}.</Typography>
@@ -376,14 +368,9 @@ const SettingsCustomization: FC = () => {
</TextField>
</>
);
};
const renderDeviceData = () => {
if (!deviceEntities) {
return;
}
if (devices?.devices.length === 0 || deviceEntities[0].id === '') {
if (deviceEntities.length === 0) {
return;
}
@@ -521,7 +508,7 @@ const SettingsCustomization: FC = () => {
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.DEVICE_ENTITIES()}
</Typography>
{renderDeviceList()}
{devices && renderDeviceList()}
{renderDeviceData()}
{restartNeeded && (
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
@@ -539,7 +526,7 @@ const SettingsCustomization: FC = () => {
startIcon={<CancelIcon />}
variant="outlined"
color="secondary"
onClick={() => devices && fetchDeviceEntities(devices.devices[selectedDevice].i)}
onClick={() => devices && readDeviceEntities(devices.devices[selectedDevice].i)}
>
{LL.CANCEL()}
</Button>

View File

@@ -1,86 +1,63 @@
import type {
BoardProfile,
BoardProfileName,
APIcall,
Settings,
Status,
CoreData,
Devices,
DeviceData,
DeviceEntity,
UniqueID,
CustomEntities,
WriteTemperatureSensor,
WriteAnalogSensor,
SensorData,
Schedule,
Entities
Entities,
DeviceData
} from './types';
import type { AxiosPromise } from 'axios';
import { AXIOS, AXIOS_API, AXIOS_BIN, alovaInstance } from 'api/endpoints';
import { AXIOS, AXIOS_API, alovaInstance } from 'api/endpoints';
// DashboardDevices
export const readCoreData = () => alovaInstance.Get<CoreData>(`/coreData`);
export const readDeviceData = (id: number) =>
alovaInstance.Get<DeviceData>('/deviceData', {
params: { id },
responseType: 'arraybuffer' // uses msgpack
});
export const writeDeviceValue = (data: any) => alovaInstance.Post('/writeDeviceValue', data);
// SettingsApplication
export const readSettings = () => alovaInstance.Get<Settings>('/settings');
export const writeSettings = (data: any) => alovaInstance.Post('/settings', data);
export const getBoardProfile = (boardProfile: string) =>
alovaInstance.Get('/boardProfile', {
params: { boardProfile }
});
export const restart = () => alovaInstance.Post('/restart');
//
// TODO change below to use alova
//
// SettingsCustomization
export const readDeviceEntities = (id: number) =>
alovaInstance.Get<DeviceEntity[]>('/deviceEntities', {
params: { id },
responseType: 'arraybuffer',
transformData(rawData: any) {
return rawData.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma }));
}
});
export const readDevices = () => alovaInstance.Get<Devices>('/devices');
export const resetCustomizations = () => alovaInstance.Post('/resetCustomizations');
export const writeCustomEntities = (data: any) => alovaInstance.Post('/customEntities', data);
export function restart(): AxiosPromise<void> {
return AXIOS.post('/restart');
}
// DashboardSensors
export const readSensorData = () => alovaInstance.Get<SensorData>('/sensorData');
export const writeTemperatureSensor = (ts: WriteTemperatureSensor) => alovaInstance.Post('/writeTemperatureSensor', ts);
export const writeAnalogSensor = (as: WriteAnalogSensor) => alovaInstance.Post('/writeAnalogSensor', as);
// TODO change to GET
export function getBoardProfile(boardProfile: BoardProfileName): AxiosPromise<BoardProfile> {
return AXIOS.post('/boardProfile', boardProfile);
}
// TODO change to GET
export function readDeviceEntities(unique_id: UniqueID): AxiosPromise<DeviceEntity[]> {
return AXIOS_BIN.post('/deviceEntities', unique_id);
}
// TODO think about naming, get... and not get etc...
export function readStatus(): AxiosPromise<Status> {
return AXIOS.get('/status');
}
// DashboardStatus
export const readStatus = () => alovaInstance.Get<Status>('/status');
export const scanDevices = () => alovaInstance.Post('/scanDevices');
export function readDevices(): AxiosPromise<Devices> {
return AXIOS.get('/devices');
}
export function scanDevices(): AxiosPromise<void> {
return AXIOS.post('/scanDevices'); // call command
}
export function readSensorData(): AxiosPromise<SensorData> {
return AXIOS.get('/sensorData');
}
export function writeCustomEntities(customEntities: CustomEntities): AxiosPromise<void> {
return AXIOS.post('/customEntities', customEntities);
}
export function writeTemperatureSensor(ts: WriteTemperatureSensor): AxiosPromise<void> {
return AXIOS.post('/writeTemperatureSensor', ts);
}
export function writeAnalogSensor(as: WriteAnalogSensor): AxiosPromise<void> {
return AXIOS.post('/writeAnalogSensor', as);
}
export function resetCustomizations(): AxiosPromise<void> {
return AXIOS.post('/resetCustomizations'); // command
}
// ALOVA goes here....
export function API(apiCall: APIcall): AxiosPromise<void> {
return AXIOS_API.post('/', apiCall); // command

View File

@@ -131,8 +131,6 @@ export interface DeviceValue {
m?: number; // min, optional
x?: number; // max, optional
}
// TODO can be refacvtored to DeviceValue[]?
export interface DeviceData {
data: DeviceValue[];
}
@@ -152,16 +150,6 @@ export interface DeviceEntity {
o_ma?: number; // original max value
}
export interface CustomEntities {
id: number;
entity_ids: string[];
}
// TODO can be removed?
export interface UniqueID {
id: number;
}
export enum DeviceValueUOM {
NONE = 0,
DEGREES,
@@ -258,10 +246,6 @@ export const BOARD_PROFILES: BoardProfiles = {
S3MINI: 'Liligo S3'
};
export interface BoardProfileName {
board_profile: string;
}
export interface BoardProfile {
board_profile: string;
led_gpio: number;

View File

@@ -23,14 +23,13 @@ export const updateValue =
};
export const updateValueDirty =
<S>(origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: any) =>
// <S>(origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: UpdateEntity<S>) =>
(origData: any, dirtyFlags: any, setDirtyFlags: any, updateDataValue: any) =>
(event: React.ChangeEvent<HTMLInputElement>) => {
const updated_value = extractEventValue(event);
const name = event.target.name;
// TODO not sure how this is even working!!
updateEntity((prevState) => ({
updateDataValue((prevState) => ({
...prevState,
[name]: updated_value
}));
@@ -38,9 +37,9 @@ export const updateValueDirty =
const arr: string[] = dirtyFlags;
// TODO remove comments
// console.log('updating ' + name + ' to ' + updated_value); // TODO remove
// console.log('dirtyFlags:', dirtyFlags); // TODO remove
// console.log('binding.ts origData:', origData); // TODO remove
// console.log('updating ' + name + ' to ' + updated_value);
// console.log('dirtyFlags:', dirtyFlags);
// console.log('binding.ts origData:', origData);
if (origData[name] !== updated_value) {
if (!arr.includes(name)) {

View File

@@ -1,4 +1,4 @@
// TODO can be removed!
// TODO extractErrorMessage function can be removed!
export const extractErrorMessage = (error: any, defaultMessage: string) => {
if (error.request) {
return defaultMessage + ' (' + error.request.status + ': ' + error.request.statusText + ')';

View File

@@ -4,6 +4,6 @@ export * from './route';
export * from './submit';
export * from './time';
export * from './useRest';
// TODO remove
// TODO remove useRest2
export * from './useRest2';
export * from './props';

View File

@@ -52,8 +52,8 @@ export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
const response = await update(toSave);
setOrigData(response.data);
setData(response.data);
if (response.status === 202) {
setRestartNeeded(true);
if (response.status === 205) {
setRestartNeeded(true); // reboot required
} else {
toast.success(LL.UPDATED_OF(LL.SETTINGS()));
}

View File

@@ -7,14 +7,15 @@ import type { AlovaXHRRequestConfig, AlovaXHRResponse, AlovaXHRResponseHeaders }
import { useI18nContext } from 'i18n/i18n-react';
export interface RestRequestOptions<D> {
export interface RestRequestOptions2<D> {
read: () => Method<any, any, any, any, any, AlovaXHRResponse<any>, AlovaXHRResponseHeaders>;
update: (
value: D
) => Method<any, unknown, unknown, unknown, AlovaXHRRequestConfig, AlovaXHRResponse<any>, AlovaXHRResponseHeaders>;
}
export const useRest2 = <D>({ read, update }: RestRequestOptions<D>) => {
// TODO rename back to useRest
export const useRest2 = <D>({ read, update }: RestRequestOptions2<D>) => {
const { LL } = useI18nContext();
const [errorMessage, setErrorMessage] = useState<string>();
@@ -32,12 +33,8 @@ export const useRest2 = <D>({ read, update }: RestRequestOptions<D>) => {
onSuccess: onWriteSuccess
} = useRequest((newData: D) => update(newData), { immediate: false });
const setData = (new_data: D) => {
console.log('SET DATA'); // TODO remove console
console.log('new_data:', new_data); // TODO remove console
updateData({
data: new_data
});
const updateDataValue = (new_data: D) => {
updateData({ data: new_data });
};
onWriteSuccess(() => {
@@ -46,7 +43,7 @@ export const useRest2 = <D>({ read, update }: RestRequestOptions<D>) => {
});
onReadComplete((event) => {
setOrigData(event.data); // make a copy
setOrigData(event.data);
});
const loadData = async () => {
@@ -62,7 +59,6 @@ export const useRest2 = <D>({ read, update }: RestRequestOptions<D>) => {
if (!data) {
return;
}
console.log('SAVE DATA'); // TODO remove console
setRestartNeeded(false);
setErrorMessage(undefined);
await writeData(data).catch((error) => {
@@ -79,7 +75,7 @@ export const useRest2 = <D>({ read, update }: RestRequestOptions<D>) => {
loadData,
saveData,
saving,
setData,
updateDataValue,
data,
origData,
dirtyFlags,

View File

@@ -767,14 +767,14 @@ __metadata:
languageName: node
linkType: hard
"@mui/material@npm:^5.13.4":
version: 5.13.4
resolution: "@mui/material@npm:5.13.4"
"@mui/material@npm:^5.13.5":
version: 5.13.5
resolution: "@mui/material@npm:5.13.5"
dependencies:
"@babel/runtime": ^7.21.0
"@mui/base": 5.0.0-beta.4
"@mui/core-downloads-tracker": ^5.13.4
"@mui/system": ^5.13.2
"@mui/system": ^5.13.5
"@mui/types": ^7.2.4
"@mui/utils": ^5.13.1
"@types/react-transition-group": ^4.4.6
@@ -796,7 +796,7 @@ __metadata:
optional: true
"@types/react":
optional: true
checksum: 1f0b26c74e06fb6849af7398b8bc1d211d0af146822c998a9c55c31552ffcf426834eac56dc909c15bae8a3b957df91f32d9c1b6cb1300e063d184bfb530512f
checksum: 325a99809efa041aa615b144bfde634ad7def22102b6267769e140aee0cdeaa3f611b79d4e3dc85a832b1f120da19b3e57933fb17487c7d5f67d7c2bbe7f3254
languageName: node
linkType: hard
@@ -838,9 +838,9 @@ __metadata:
languageName: node
linkType: hard
"@mui/system@npm:^5.13.2":
version: 5.13.2
resolution: "@mui/system@npm:5.13.2"
"@mui/system@npm:^5.13.5":
version: 5.13.5
resolution: "@mui/system@npm:5.13.5"
dependencies:
"@babel/runtime": ^7.21.0
"@mui/private-theming": ^5.13.1
@@ -862,7 +862,7 @@ __metadata:
optional: true
"@types/react":
optional: true
checksum: 34ebb580e5dd83123cc397c3fd54c3430f66ab715eb1538cf2510821d88249814294f79ea046081b61249643383fd9c23552d9791322855fa2099bf8f1c4e51b
checksum: 0bef4c575d9c54e7d93ad14009aecf4a0f18440398a14a6523f3fcea665913ceb344dc496d02b56a3ef53e4dac828f8e7ca5a55fb60448a76363622159d18379
languageName: node
linkType: hard
@@ -1288,10 +1288,10 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:^20.3.0":
version: 20.3.0
resolution: "@types/node@npm:20.3.0"
checksum: f717d92c29c4877db394b604771b3734216f013312f93252f72c2018aabe8083be905fbcf0644c859938c8183b6e0245faaeaab94c9e78268b87a449bc6ef4aa
"@types/node@npm:^20.3.1":
version: 20.3.1
resolution: "@types/node@npm:20.3.1"
checksum: 7e8a6f5d6fc1ad3778f038f5f8df570741459984280fd2e9539af32620d93438c955fd1b90d00f9cc438cd132ec04d7669ada9e32502336e78713a3ad9b51d10
languageName: node
linkType: hard
@@ -1309,12 +1309,12 @@ __metadata:
languageName: node
linkType: hard
"@types/react-dom@npm:^18.2.4":
version: 18.2.4
resolution: "@types/react-dom@npm:18.2.4"
"@types/react-dom@npm:^18.2.5":
version: 18.2.5
resolution: "@types/react-dom@npm:18.2.5"
dependencies:
"@types/react": "*"
checksum: dfeaabb4268d39bdd5addc6c0b7099d5c57a364e70f1087b7c3ee189374312dc65201abfd3d87fee0de11d27c225678ce39c22d14b3035cde5792678704c27b5
checksum: 7f438f695c91735ff16e6465573a4378fabad6709d99dd08bee4932967ca7e4ccc26dc248f4b88569529885bbca9b1aca0075bee7b833bfa932a558c49d2a066
languageName: node
linkType: hard
@@ -1368,14 +1368,14 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:^18.2.11":
version: 18.2.11
resolution: "@types/react@npm:18.2.11"
"@types/react@npm:^18.2.12":
version: 18.2.12
resolution: "@types/react@npm:18.2.12"
dependencies:
"@types/prop-types": "*"
"@types/scheduler": "*"
csstype: ^3.0.2
checksum: 9360d7be13195050eb16598796056123ee9d30470e7073a914300fed9282585d0dd0638bb5ff65843e308c3ac213d25b3388e8186f3134490c758f18f11f3bd8
checksum: dbaefc7732f77cd0c2bdf6e704ab4ee0f611540777c0b9506d972f165144b6fd6883a29dad61f6965c974c040264852aa8def1e3866b5775b3965a4e67bf92f9
languageName: node
linkType: hard
@@ -1393,14 +1393,14 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^5.59.9":
version: 5.59.9
resolution: "@typescript-eslint/eslint-plugin@npm:5.59.9"
"@typescript-eslint/eslint-plugin@npm:^5.59.11":
version: 5.59.11
resolution: "@typescript-eslint/eslint-plugin@npm:5.59.11"
dependencies:
"@eslint-community/regexpp": ^4.4.0
"@typescript-eslint/scope-manager": 5.59.9
"@typescript-eslint/type-utils": 5.59.9
"@typescript-eslint/utils": 5.59.9
"@typescript-eslint/scope-manager": 5.59.11
"@typescript-eslint/type-utils": 5.59.11
"@typescript-eslint/utils": 5.59.11
debug: ^4.3.4
grapheme-splitter: ^1.0.4
ignore: ^5.2.0
@@ -1413,43 +1413,43 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 4bb9981bcc009c044ffd6b64288309480df2b6c9cdf6b345987e4b565d0973d1d98b7209f6b46b92880735d788f564e17553641087aa59f67990c84526622a27
checksum: 77f29b8bde5d11a5f222b21e5a3546e86f79a07efd8352b3ebcde67fd2c568ba7c6946fa992dbb9d7d80b6f2455017266fdede1ed134bd181b03f98d81fe71c3
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^5.59.9":
version: 5.59.9
resolution: "@typescript-eslint/parser@npm:5.59.9"
"@typescript-eslint/parser@npm:^5.59.11":
version: 5.59.11
resolution: "@typescript-eslint/parser@npm:5.59.11"
dependencies:
"@typescript-eslint/scope-manager": 5.59.9
"@typescript-eslint/types": 5.59.9
"@typescript-eslint/typescript-estree": 5.59.9
"@typescript-eslint/scope-manager": 5.59.11
"@typescript-eslint/types": 5.59.11
"@typescript-eslint/typescript-estree": 5.59.11
debug: ^4.3.4
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
typescript:
optional: true
checksum: af0e041e8a541734ff237ec0eac47e355c2f78dd2b0db4eb4ab0c10ba1b6d5d70f84ddc16f856bc72c4cacd53ef04b5f4948baffb5c8cb2d9a0ffd83a8fbc547
checksum: 3deea3a9b694ea97e315881ba37d0a90d7f37f0acbb5990270f44c79db9fc3d5675df856f8d1fb7d92c8d38dc63226a42402d57633513981ee526c06e6e8f3c3
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:5.59.9":
version: 5.59.9
resolution: "@typescript-eslint/scope-manager@npm:5.59.9"
"@typescript-eslint/scope-manager@npm:5.59.11":
version: 5.59.11
resolution: "@typescript-eslint/scope-manager@npm:5.59.11"
dependencies:
"@typescript-eslint/types": 5.59.9
"@typescript-eslint/visitor-keys": 5.59.9
checksum: 41622fd270e5b8574347ed5dd020bbb9752d85e6f40df180e944c1110d9bd2227a949067feb23dd4117dd2be0623c05a47bc363abe605c96deb295753f6dd080
"@typescript-eslint/types": 5.59.11
"@typescript-eslint/visitor-keys": 5.59.11
checksum: e0173e9beb44408ba3e9a1eb173dd83f5c0281af09f936d03f635b7fac41abe1f829a88d0cba134f250dfa4ce527d8cfa32c96e7ada5de876a7aa8bcc933db3b
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:5.59.9":
version: 5.59.9
resolution: "@typescript-eslint/type-utils@npm:5.59.9"
"@typescript-eslint/type-utils@npm:5.59.11":
version: 5.59.11
resolution: "@typescript-eslint/type-utils@npm:5.59.11"
dependencies:
"@typescript-eslint/typescript-estree": 5.59.9
"@typescript-eslint/utils": 5.59.9
"@typescript-eslint/typescript-estree": 5.59.11
"@typescript-eslint/utils": 5.59.11
debug: ^4.3.4
tsutils: ^3.21.0
peerDependencies:
@@ -1457,23 +1457,23 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: c3a9773d2b81350923025933623e1572538f79bf119b40bed17389eda11632f6d364a49b385aa6d915d85f7c3d45376085cc55263d865dbc2b753598bba6473b
checksum: 9675cf17970bbf01814d8c8a94aa076fb7c5f5ab8c405800f972a68a72748db07070a526aa2c94d30b2e5ba43bdbef3929a588182bffc3387288b24223574f52
languageName: node
linkType: hard
"@typescript-eslint/types@npm:5.59.9":
version: 5.59.9
resolution: "@typescript-eslint/types@npm:5.59.9"
checksum: 951046891bcc9fa27d72a5489b496291e44cedcff204d3ce6c10c8916fc5e255332738efd4d7555200a55b49ff4ba1204e186960d216d51fea89fe92a982180e
"@typescript-eslint/types@npm:5.59.11":
version: 5.59.11
resolution: "@typescript-eslint/types@npm:5.59.11"
checksum: 8d007c6c66323582d526429d059c477dcb522b904938a6aaf29804027e6f427533fabe9eb3c987491df74d4288dab238c8c948886f03244a1d908f2985631368
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:5.59.9":
version: 5.59.9
resolution: "@typescript-eslint/typescript-estree@npm:5.59.9"
"@typescript-eslint/typescript-estree@npm:5.59.11":
version: 5.59.11
resolution: "@typescript-eslint/typescript-estree@npm:5.59.11"
dependencies:
"@typescript-eslint/types": 5.59.9
"@typescript-eslint/visitor-keys": 5.59.9
"@typescript-eslint/types": 5.59.11
"@typescript-eslint/visitor-keys": 5.59.11
debug: ^4.3.4
globby: ^11.1.0
is-glob: ^4.0.3
@@ -1482,35 +1482,35 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 2f3d8df6d454fbc52d305abfe8447bff8e8d63294ce47e4679c920f647643f5d15a1f693caf74f4fabece12d5ba27ebdb156d507b16fbd2751fc01ba6c4df3c8
checksum: 4306317ad65668a05d9ec7b280eab94dd7e0d15394db633864bad5d9633fbb9dc516e6cd9f8a0fc2f7a4fc0d53658e5dac61cbee3e070b0b7ac99bdbb0bb45ef
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:5.59.9":
version: 5.59.9
resolution: "@typescript-eslint/utils@npm:5.59.9"
"@typescript-eslint/utils@npm:5.59.11":
version: 5.59.11
resolution: "@typescript-eslint/utils@npm:5.59.11"
dependencies:
"@eslint-community/eslint-utils": ^4.2.0
"@types/json-schema": ^7.0.9
"@types/semver": ^7.3.12
"@typescript-eslint/scope-manager": 5.59.9
"@typescript-eslint/types": 5.59.9
"@typescript-eslint/typescript-estree": 5.59.9
"@typescript-eslint/scope-manager": 5.59.11
"@typescript-eslint/types": 5.59.11
"@typescript-eslint/typescript-estree": 5.59.11
eslint-scope: ^5.1.1
semver: ^7.3.7
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
checksum: b8a04a83c121faa3e36abb2b6113f2e0ec5cf86884d0cb8619bfc50f7442341ee17e4495d69f8abeb6edad9e0347de8382ea1708a5fd6da1e4c80b7b8215c6ab
checksum: c1927950d97afcf3cfc4c3901bc1932caa32bfc533f950f7dab420c478097d3d8daf030c27489e0d49ecdc1f87c52c782833cc505b245ab2a3094c707b0d776e
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:5.59.9":
version: 5.59.9
resolution: "@typescript-eslint/visitor-keys@npm:5.59.9"
"@typescript-eslint/visitor-keys@npm:5.59.11":
version: 5.59.11
resolution: "@typescript-eslint/visitor-keys@npm:5.59.11"
dependencies:
"@typescript-eslint/types": 5.59.9
"@typescript-eslint/types": 5.59.11
eslint-visitor-keys: ^3.3.0
checksum: 882fd03830cbe0eca8f9a547aecc6519ddbec10e55f5f3de66e605a3f3d42a6237abd3c09b34d9cc3343c8e11386e999876aec384efe523e1478cb22752d326d
checksum: 1644c5bcb87e26717bc587f1ed1d87c96e89dae7d5bdafc1eed35a7c49e3157f8c37936b3897571892f412b7dd8439ba9cf8903128bb847831696f6517bb2d7a
languageName: node
linkType: hard
@@ -1533,17 +1533,17 @@ __metadata:
"@emotion/react": ^11.11.1
"@emotion/styled": ^11.11.0
"@mui/icons-material": ^5.11.16
"@mui/material": ^5.13.4
"@mui/material": ^5.13.5
"@table-library/react-table-library": 4.1.4
"@types/lodash-es": ^4.17.7
"@types/node": ^20.3.0
"@types/react": ^18.2.11
"@types/react-dom": ^18.2.4
"@types/node": ^20.3.1
"@types/react": ^18.2.12
"@types/react-dom": ^18.2.5
"@types/react-router-dom": ^5.3.3
"@typescript-eslint/eslint-plugin": ^5.59.9
"@typescript-eslint/parser": ^5.59.9
"@typescript-eslint/eslint-plugin": ^5.59.11
"@typescript-eslint/parser": ^5.59.11
"@vitejs/plugin-react-swc": ^3.3.2
alova: ^2.6.0
alova: ^2.6.1
async-validator: ^4.2.5
axios: ^1.4.0
eslint: ^8.42.0
@@ -1571,7 +1571,7 @@ __metadata:
react-toastify: ^9.1.3
rollup-plugin-visualizer: ^5.9.2
sockette: ^2.0.6
terser: ^5.17.7
terser: ^5.18.0
typesafe-i18n: ^5.24.3
typescript: ^5.1.3
vite: ^4.3.9
@@ -1647,10 +1647,10 @@ __metadata:
languageName: node
linkType: hard
"alova@npm:^2.6.0":
version: 2.6.0
resolution: "alova@npm:2.6.0"
checksum: a99dd001f094cccbc6166c5cc56ed8d417434f9edf05aa5176992a3a3735600a3b626b41b50dd867b8a86b3edf44cfbd576568349af937626a7023f9b839226b
"alova@npm:^2.6.1":
version: 2.6.1
resolution: "alova@npm:2.6.1"
checksum: 55504d1cfab8efff3679d5734e7f78891b3b9c581c6669e6a6df6cc854d05c5d275f6645e1347c633f4418a41d418105857c7d96e0f69dc24abeccc06f0a8c18
languageName: node
linkType: hard
@@ -5536,9 +5536,9 @@ __metadata:
languageName: node
linkType: hard
"terser@npm:^5.17.7":
version: 5.17.7
resolution: "terser@npm:5.17.7"
"terser@npm:^5.18.0":
version: 5.18.0
resolution: "terser@npm:5.18.0"
dependencies:
"@jridgewell/source-map": ^0.3.3
acorn: ^8.8.2
@@ -5546,7 +5546,7 @@ __metadata:
source-map-support: ~0.5.20
bin:
terser: bin/terser
checksum: 864154a1750daf516012e5add4f0749bfc71e8f4f918973ec3d504db6a148be976adf46ae490e795173eeff59ec579d7d464bb6354c1bb71f8e14ff398409aed
checksum: 37f562843537c57e119b2ed6d96c2113344d9182a83613abd8e933534b89a3c622ee7ee47d4023249c1d34a2dd1b41a0e56fd6d2e2251f48b79fb7671f269b01
languageName: node
linkType: hard

View File

@@ -23,8 +23,7 @@
#include "cbuf.h"
// Since ESP8266 does not link memchr by default, here's its implementation.
void* memchr(void* ptr, int ch, size_t count)
{
void * memchr(void * ptr, int ch, size_t count) {
unsigned char * p = static_cast<unsigned char *>(ptr);
while (count--)
if (*p++ == static_cast<unsigned char>(ch))
@@ -38,48 +37,90 @@ void* memchr(void* ptr, int ch, size_t count)
* */
const char * AsyncWebServerResponse::_responseCodeToString(int code) {
switch (code) {
case 100: return ("Continue");
case 101: return ("Switching Protocols");
case 200: return ("OK");
case 201: return ("Created");
case 202: return ("Accepted");
case 203: return ("Non-Authoritative Information");
case 204: return ("No Content");
case 205: return ("Reset Content");
case 206: return ("Partial Content");
case 300: return ("Multiple Choices");
case 301: return ("Moved Permanently");
case 302: return ("Found");
case 303: return ("See Other");
case 304: return ("Not Modified");
case 305: return ("Use Proxy");
case 307: return ("Temporary Redirect");
case 400: return ("Bad Request");
case 401: return ("Unauthorized");
case 402: return ("Payment Required");
case 403: return ("Forbidden");
case 404: return ("Not Found");
case 405: return ("Method Not Allowed");
case 406: return ("Not Acceptable");
case 407: return ("Proxy Authentication Required");
case 408: return ("Request Time-out");
case 409: return ("Conflict");
case 410: return ("Gone");
case 411: return ("Length Required");
case 412: return ("Precondition Failed");
case 413: return ("Request Entity Too Large");
case 414: return ("Request-URI Too Large");
case 415: return ("Unsupported Media Type");
case 416: return ("Requested range not satisfiable");
case 417: return ("Expectation Failed");
case 500: return ("Internal Server Error");
case 501: return ("Not Implemented");
case 502: return ("Bad Gateway");
case 503: return ("Service Unavailable");
case 504: return ("Gateway Time-out");
case 505: return ("HTTP Version not supported");
case 507: return ("Insufficient Storage");
default: return ("");
case 100:
return ("Continue");
case 101:
return ("Switching Protocols");
case 200:
return ("OK");
case 201:
return ("Created");
case 202:
return ("Accepted"); // proddy: used in wifi
case 203:
return ("Non-Authoritative Information");
case 204:
return ("No Content");
case 205:
return ("Reset Content"); // proddy: reboot required
case 206:
return ("Partial Content");
case 300:
return ("Multiple Choices");
case 301:
return ("Moved Permanently");
case 302:
return ("Found");
case 303:
return ("See Other");
case 304:
return ("Not Modified");
case 305:
return ("Use Proxy");
case 307:
return ("Temporary Redirect");
case 400:
return ("Bad Request");
case 401:
return ("Unauthorized");
case 402:
return ("Payment Required");
case 403:
return ("Forbidden");
case 404:
return ("Not Found");
case 405:
return ("Method Not Allowed");
case 406:
return ("Not Acceptable");
case 407:
return ("Proxy Authentication Required");
case 408:
return ("Request Time-out");
case 409:
return ("Conflict");
case 410:
return ("Gone");
case 411:
return ("Length Required");
case 412:
return ("Precondition Failed");
case 413:
return ("Request Entity Too Large");
case 414:
return ("Request-URI Too Large");
case 415:
return ("Unsupported Media Type");
case 416:
return ("Requested range not satisfiable");
case 417:
return ("Expectation Failed");
case 500:
return ("Internal Server Error");
case 501:
return ("Not Implemented");
case 502:
return ("Bad Gateway");
case 503:
return ("Service Unavailable");
case 504:
return ("Gateway Time-out");
case 505:
return ("HTTP Version not supported");
case 507:
return ("Insufficient Storage");
default:
return ("");
}
}
@@ -98,8 +139,7 @@ AsyncWebServerResponse::AsyncWebServerResponse()
, _sentLength(0)
, _ackedLength(0)
, _writtenLength(0)
, _state(RESPONSE_SETUP)
{
, _state(RESPONSE_SETUP) {
for (auto header : DefaultHeaders::Instance()) {
_headers.add(new AsyncWebHeader(header->name(), header->value()));
}
@@ -161,12 +201,28 @@ String AsyncWebServerResponse::_assembleHead(uint8_t version){
return out;
}
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
bool AsyncWebServerResponse::_sourceValid() const { return false; }
void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); }
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; }
bool AsyncWebServerResponse::_started() const {
return _state > RESPONSE_SETUP;
}
bool AsyncWebServerResponse::_finished() const {
return _state > RESPONSE_WAIT_ACK;
}
bool AsyncWebServerResponse::_failed() const {
return _state == RESPONSE_FAILED;
}
bool AsyncWebServerResponse::_sourceValid() const {
return false;
}
void AsyncWebServerResponse::_respond(AsyncWebServerRequest * request) {
_state = RESPONSE_END;
request->client()->close();
}
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) {
(void)request;
(void)len;
(void)time;
return 0;
}
/*
* String/Code Response
@@ -249,8 +305,8 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint
* Abstract Response
* */
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback)
{
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback)
: _callback(callback) {
// In case of template processing, we're unable to determine real response size
if (callback) {
_contentLength = 0;
@@ -323,7 +379,8 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
return 0;
}
outLen = snprintf_P((char *)buf + headLen, sizeof(buf) - headLen - 2, PSTR("%x"), readLen) + headLen;
while(outLen < headLen + 4) buf[outLen++] = ' ';
while (outLen < headLen + 4)
buf[outLen++] = ' ';
buf[outLen++] = '\r';
buf[outLen++] = '\n';
outLen += readLen;
@@ -369,8 +426,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
return 0;
}
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len)
{
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t * data, const size_t len) {
// If we have something in cache, copy it to buffer
const size_t readFromCache = std::min(len, _cache.size());
if (readFromCache) {
@@ -383,8 +439,7 @@ size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const s
return readFromCache + readFromContent;
}
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len)
{
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t * data, size_t len) {
if (!_callback)
return _fillBuffer(data, len);
@@ -393,8 +448,10 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
// Now we've read 'len' bytes, either from cache or from file
// Search for template placeholders
uint8_t * pTemplateStart = data;
while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
while ((pTemplateStart < &data[len])
&& (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
uint8_t * pTemplateEnd =
(pTemplateStart < &data[len - 1]) ? (uint8_t *)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
// temporary buffer to hold parameter name
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
String paramName;
@@ -414,7 +471,8 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
}
} else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
const size_t readFromCacheOrContent =
_readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
if (readFromCacheOrContent) {
pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
if (pTemplateEnd) {
@@ -424,18 +482,15 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
// Copy remaining read-ahead data into cache
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
pTemplateEnd = &data[len - 1];
}
else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
} else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
{
// but first, store read file data in cache
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
++pTemplateStart;
}
}
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
++pTemplateStart;
}
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
++pTemplateStart;
if (paramName.length()) {
// call callback and replace with result.
@@ -492,29 +547,49 @@ void AsyncFileResponse::_setContentType(const String& path){
extern const __FlashStringHelper * getContentType(const String & path);
_contentType = getContentType(path);
#else
if (path.endsWith(F(".html"))) _contentType = F("text/html");
else if (path.endsWith(F(".htm"))) _contentType = F("text/html");
else if (path.endsWith(F(".css"))) _contentType = F("text/css");
else if (path.endsWith(F(".json"))) _contentType = F("application/json");
else if (path.endsWith(F(".js"))) _contentType = F("application/javascript");
else if (path.endsWith(F(".png"))) _contentType = F("image/png");
else if (path.endsWith(F(".gif"))) _contentType = F("image/gif");
else if (path.endsWith(F(".jpg"))) _contentType = F("image/jpeg");
else if (path.endsWith(F(".ico"))) _contentType = F("image/x-icon");
else if (path.endsWith(F(".svg"))) _contentType = F("image/svg+xml");
else if (path.endsWith(F(".eot"))) _contentType = F("font/eot");
else if (path.endsWith(F(".woff"))) _contentType = F("font/woff");
else if (path.endsWith(F(".woff2"))) _contentType = F("font/woff2");
else if (path.endsWith(F(".ttf"))) _contentType = F("font/ttf");
else if (path.endsWith(F(".xml"))) _contentType = F("text/xml");
else if (path.endsWith(F(".pdf"))) _contentType = F("application/pdf");
else if (path.endsWith(F(".zip"))) _contentType = F("application/zip");
else if(path.endsWith(F(".gz"))) _contentType = F("application/x-gzip");
else _contentType = F("text/plain");
if (path.endsWith(F(".html")))
_contentType = F("text/html");
else if (path.endsWith(F(".htm")))
_contentType = F("text/html");
else if (path.endsWith(F(".css")))
_contentType = F("text/css");
else if (path.endsWith(F(".json")))
_contentType = F("application/json");
else if (path.endsWith(F(".js")))
_contentType = F("application/javascript");
else if (path.endsWith(F(".png")))
_contentType = F("image/png");
else if (path.endsWith(F(".gif")))
_contentType = F("image/gif");
else if (path.endsWith(F(".jpg")))
_contentType = F("image/jpeg");
else if (path.endsWith(F(".ico")))
_contentType = F("image/x-icon");
else if (path.endsWith(F(".svg")))
_contentType = F("image/svg+xml");
else if (path.endsWith(F(".eot")))
_contentType = F("font/eot");
else if (path.endsWith(F(".woff")))
_contentType = F("font/woff");
else if (path.endsWith(F(".woff2")))
_contentType = F("font/woff2");
else if (path.endsWith(F(".ttf")))
_contentType = F("font/ttf");
else if (path.endsWith(F(".xml")))
_contentType = F("text/xml");
else if (path.endsWith(F(".pdf")))
_contentType = F("application/pdf");
else if (path.endsWith(F(".zip")))
_contentType = F("application/zip");
else if (path.endsWith(F(".gz")))
_contentType = F("application/x-gzip");
else
_contentType = F("text/plain");
#endif
}
AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
AsyncFileResponse::AsyncFileResponse(FS & fs, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200;
_path = path;
@@ -548,7 +623,8 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& c
addHeader(F("Content-Disposition"), buf);
}
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
AsyncFileResponse::AsyncFileResponse(File content, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200;
_path = path;
@@ -587,7 +663,8 @@ size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){
* Stream Response
* */
AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
AsyncStreamResponse::AsyncStreamResponse(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200;
_content = &stream;
_contentLength = len;
@@ -607,7 +684,8 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){
* Callback Response
* */
AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) {
AsyncCallbackResponse::AsyncCallbackResponse(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback)
: AsyncAbstractResponse(templateCallback) {
_code = 200;
_content = callback;
_contentLength = len;
@@ -629,7 +707,8 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
* Chunked Response
* */
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) {
AsyncChunkedResponse::AsyncChunkedResponse(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback)
: AsyncAbstractResponse(processorCallback) {
_code = 200;
_content = callback;
_contentLength = 0;
@@ -651,7 +730,8 @@ size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
* Progmem Response
* */
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = code;
_content = content;
_contentType = contentType;

View File

@@ -115,7 +115,7 @@ class HttpPostEndpoint {
response->setLength();
if (outcome == StateUpdateResult::CHANGED_RESTART) {
response->setCode(202); // added by proddy
response->setCode(205); // added by proddy, reboot required
}
request->send(response);
}

View File

@@ -16,7 +16,7 @@ void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) {
WiFi.scanDelete();
WiFi.scanNetworks(true);
}
request->send(202);
request->send(202); // special code to indicate scan in progress
}
void WiFiScanner::listNetworks(AsyncWebServerRequest * request) {
@@ -36,7 +36,7 @@ void WiFiScanner::listNetworks(AsyncWebServerRequest * request) {
response->setLength();
request->send(response);
} else if (numNetworks == -1) {
request->send(202);
request->send(202); // special code to indicate scan in progress
} else {
scanNetworks(request);
}

View File

@@ -480,7 +480,7 @@ const emsesp_sensordata = {
{ id: '28-233D-9497-0C03', n: 'Dallas 1', t: 25.7, o: 1.2, u: 1 },
{ id: '28-243D-7437-1E3A', n: 'Dallas 2 outside', t: 26.1, o: 0, u: 1 },
{ id: '28-243E-7437-1E3B', n: 'Zolder', t: 27.1, o: 0, u: 16 },
{ id: '28-183D-1892-0C33', n: 'Roof', o: 2, u: 1 }
{ id: '28-183D-1892-0C33', n: 'Roof', o: 2, u: 1 } // no temperature
],
// as: [],
as: [
@@ -512,95 +512,15 @@ const status = {
};
// Dashboard data
// 7 - Nefit Trendline boiler
// 1 - RC35 thermo
// 2 - RC20 thermo
// 3 - Buderus GB125 boiler
// 4 - RC100 themo
// 5 - Mixer MM10
// 6 - Solar SM10
// 7 - Nefit Trendline boiler
// 99 - Custom
const emsesp_devicedata_7 = {
data: [
{ v: '', u: 0, id: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] },
{ v: 'off', u: 0, id: '08heating active' },
{ v: 'off', u: 0, id: '04tapwater active' },
{ v: 5, u: 1, id: '04selected flow temperature', c: 'selflowtemp' },
{ v: 0, u: 3, id: '0Eburner selected max power', c: 'selburnpow' },
{ v: 0, u: 3, id: '00heating pump modulation' },
{ v: 53.4, u: 1, id: '00current flow temperature' },
{ v: 52.7, u: 1, id: '00return temperature' },
{ v: 1.3, u: 10, id: '00system pressure' },
{ v: 54.9, u: 1, id: '00actual boiler temperature' },
{ v: 'off', u: 0, id: '00gas' },
{ v: 'off', u: 0, id: '00gas stage 2' },
{ v: 0, u: 9, id: '00flame current' },
{ v: 'off', u: 0, id: '00heating pump' },
{ v: 'off', u: 0, id: '00fan' },
{ v: 'off', u: 0, id: '00ignition' },
{ v: 'off', u: 0, id: '00oil preheating' },
{ v: 'on', u: 0, id: '00heating activated', c: 'heatingactivated', l: ['off', 'on'] },
{ v: 80, u: 1, id: '00heating temperature', c: 'heatingtemp' },
{ v: 70, u: 3, id: '00burner pump max power', c: 'pumpmodmax' },
{ v: 30, u: 3, id: '00burner pump min power', c: 'pumpmodmin' },
{ v: 1, u: 8, id: '00pump delay', c: 'pumpdelay' },
{ v: 10, u: 8, id: '00burner min period', c: 'burnminperiod' },
{ v: 0, u: 3, id: '00burner min power', c: 'burnminpower' },
{ v: 50, u: 3, id: '00burner max power', c: 'burnmaxpower' },
{ v: -6, u: 2, id: '00hysteresis on temperature', c: 'boilhyston' },
{ v: 6, u: 2, id: '00hysteresis off temperature', c: 'boilhystoff' },
{ v: 0, u: 1, id: '00set flow temperature' },
{ v: 0, u: 3, id: '00burner set power' },
{ v: 0, u: 3, id: '00burner current power' },
{ v: 326323, u: 0, id: '00burner starts' },
{ v: 553437, u: 8, id: '00total burner operating time' },
{ v: 451286, u: 8, id: '00total heat operating time' },
{ v: 4672173, u: 8, id: '00total UBA operating time' },
{ v: '1C(210) 06.06.2020 12:07 (0 min)', u: 0, id: '00last error code' },
{ v: '0H', u: 0, id: '00service code' },
{ v: 203, u: 0, id: '00service code number' },
{ v: 'H00', u: 0, id: '00maintenance message' },
{ v: 'manual', u: 0, id: '00maintenance scheduled', c: 'maintenance', l: ['off', 'time', 'date', 'manual'] },
{ v: 6000, u: 7, id: '00time to next maintenance', c: 'maintenancetime' },
{ v: '01.01.2012', u: 0, id: '00next maintenance date', c: 'maintenancedate', o: 'Format: < dd.mm.yyyy >' },
{ v: 'on', u: 0, id: '00dhw turn on/off', c: 'wwtapactivated', l: ['off', 'on'] },
{ v: 62, u: 1, id: '00dhw set temperature' },
{ v: 60, u: 1, id: '00dhw selected temperature', c: 'wwseltemp' },
{ v: 'flow', u: 0, id: '00dhw type' },
{ v: 'hot', u: 0, id: '00dhw comfort', c: 'wwcomfort', l: ['hot', 'eco', 'intelligent'] },
{ v: 40, u: 2, id: '00dhw flow temperature offset', c: 'wwflowtempoffset' },
{ v: 100, u: 3, id: '00dhw max power', c: 'wwmaxpower' },
{ v: 'off', u: 0, id: '00dhw circulation pump available', c: 'wwcircpump', l: ['off', 'on'] },
{ v: '3-way valve', u: 0, id: '00dhw charging type' },
{ v: -5, u: 2, id: '00dhw hysteresis on temperature', c: 'wwhyston' },
{ v: 0, u: 2, id: '00dhw hysteresis off temperature', c: 'wwhystoff' },
{ v: 70, u: 1, id: '00dhw disinfection temperature', c: 'wwdisinfectiontemp' },
{
v: 'off',
u: 0,
id: '00dhw circulation pump mode',
c: 'wwcircmode',
l: ['off', '1x3min', '2x3min', '3x3min', '4x3min', '5x3min', '6x3min', 'continuous']
},
{ v: 'off', u: 0, id: '00dhw circulation active', c: 'wwcirc', l: ['off', 'on'] },
{ v: 47.3, u: 1, id: '00dhw current intern temperature' },
{ v: 0, u: 4, id: '00dhw current tap water flow' },
{ v: 47.3, u: 1, id: '00dhw storage intern temperature' },
{ v: 'on', u: 0, id: '00dhw activated', c: 'wwactivated', l: ['off', 'on'] },
{ v: 'off', u: 0, id: '00dhw one time charging', c: 'wwonetime', l: ['off', 'on'] },
{ v: 'off', u: 0, id: '00dhw disinfecting', c: 'wwdisinfecting', l: ['off', 'on'] },
{ v: 'off', u: 0, id: '00dhw charging' },
{ v: 'off', u: 0, id: '00dhw recharging' },
{ v: 'on', u: 0, id: '00dhw temperature ok' },
{ v: 'off', u: 0, id: '00dhw active' },
{ v: 'on', u: 0, id: '00dhw 3way valve active' },
{ v: 0, u: 3, id: '00dhw set pump power' },
{ v: 288768, u: 0, id: '00dhw starts' },
{ v: 102151, u: 8, id: '00dhw active time' }
]
};
const emsesp_devicedata_1 = {
data: [
{
@@ -1768,6 +1688,86 @@ const emsesp_devicedata_6 = {
]
};
const emsesp_devicedata_7 = {
data: [
{ v: '', u: 0, id: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] },
{ v: 'off', u: 0, id: '08heating active' },
{ v: 'off', u: 0, id: '04tapwater active' },
{ v: 5, u: 1, id: '04selected flow temperature', c: 'selflowtemp' },
{ v: 0, u: 3, id: '0Eburner selected max power', c: 'selburnpow' },
{ v: 0, u: 3, id: '00heating pump modulation' },
{ v: 53.4, u: 1, id: '00current flow temperature' },
{ v: 52.7, u: 1, id: '00return temperature' },
{ v: 1.3, u: 10, id: '00system pressure' },
{ v: 54.9, u: 1, id: '00actual boiler temperature' },
{ v: 'off', u: 0, id: '00gas' },
{ v: 'off', u: 0, id: '00gas stage 2' },
{ v: 0, u: 9, id: '00flame current' },
{ v: 'off', u: 0, id: '00heating pump' },
{ v: 'off', u: 0, id: '00fan' },
{ v: 'off', u: 0, id: '00ignition' },
{ v: 'off', u: 0, id: '00oil preheating' },
{ v: 'on', u: 0, id: '00heating activated', c: 'heatingactivated', l: ['off', 'on'] },
{ v: 80, u: 1, id: '00heating temperature', c: 'heatingtemp' },
{ v: 70, u: 3, id: '00burner pump max power', c: 'pumpmodmax' },
{ v: 30, u: 3, id: '00burner pump min power', c: 'pumpmodmin' },
{ v: 1, u: 8, id: '00pump delay', c: 'pumpdelay' },
{ v: 10, u: 8, id: '00burner min period', c: 'burnminperiod' },
{ v: 0, u: 3, id: '00burner min power', c: 'burnminpower' },
{ v: 50, u: 3, id: '00burner max power', c: 'burnmaxpower' },
{ v: -6, u: 2, id: '00hysteresis on temperature', c: 'boilhyston' },
{ v: 6, u: 2, id: '00hysteresis off temperature', c: 'boilhystoff' },
{ v: 0, u: 1, id: '00set flow temperature' },
{ v: 0, u: 3, id: '00burner set power' },
{ v: 0, u: 3, id: '00burner current power' },
{ v: 326323, u: 0, id: '00burner starts' },
{ v: 553437, u: 8, id: '00total burner operating time' },
{ v: 451286, u: 8, id: '00total heat operating time' },
{ v: 4672173, u: 8, id: '00total UBA operating time' },
{ v: '1C(210) 06.06.2020 12:07 (0 min)', u: 0, id: '00last error code' },
{ v: '0H', u: 0, id: '00service code' },
{ v: 203, u: 0, id: '00service code number' },
{ v: 'H00', u: 0, id: '00maintenance message' },
{ v: 'manual', u: 0, id: '00maintenance scheduled', c: 'maintenance', l: ['off', 'time', 'date', 'manual'] },
{ v: 6000, u: 7, id: '00time to next maintenance', c: 'maintenancetime' },
{ v: '01.01.2012', u: 0, id: '00next maintenance date', c: 'maintenancedate', o: 'Format: < dd.mm.yyyy >' },
{ v: 'on', u: 0, id: '00dhw turn on/off', c: 'wwtapactivated', l: ['off', 'on'] },
{ v: 62, u: 1, id: '00dhw set temperature' },
{ v: 60, u: 1, id: '00dhw selected temperature', c: 'wwseltemp' },
{ v: 'flow', u: 0, id: '00dhw type' },
{ v: 'hot', u: 0, id: '00dhw comfort', c: 'wwcomfort', l: ['hot', 'eco', 'intelligent'] },
{ v: 40, u: 2, id: '00dhw flow temperature offset', c: 'wwflowtempoffset' },
{ v: 100, u: 3, id: '00dhw max power', c: 'wwmaxpower' },
{ v: 'off', u: 0, id: '00dhw circulation pump available', c: 'wwcircpump', l: ['off', 'on'] },
{ v: '3-way valve', u: 0, id: '00dhw charging type' },
{ v: -5, u: 2, id: '00dhw hysteresis on temperature', c: 'wwhyston' },
{ v: 0, u: 2, id: '00dhw hysteresis off temperature', c: 'wwhystoff' },
{ v: 70, u: 1, id: '00dhw disinfection temperature', c: 'wwdisinfectiontemp' },
{
v: 'off',
u: 0,
id: '00dhw circulation pump mode',
c: 'wwcircmode',
l: ['off', '1x3min', '2x3min', '3x3min', '4x3min', '5x3min', '6x3min', 'continuous']
},
{ v: 'off', u: 0, id: '00dhw circulation active', c: 'wwcirc', l: ['off', 'on'] },
{ v: 47.3, u: 1, id: '00dhw current intern temperature' },
{ v: 0, u: 4, id: '00dhw current tap water flow' },
{ v: 47.3, u: 1, id: '00dhw storage intern temperature' },
{ v: 'on', u: 0, id: '00dhw activated', c: 'wwactivated', l: ['off', 'on'] },
{ v: 'off', u: 0, id: '00dhw one time charging', c: 'wwonetime', l: ['off', 'on'] },
{ v: 'off', u: 0, id: '00dhw disinfecting', c: 'wwdisinfecting', l: ['off', 'on'] },
{ v: 'off', u: 0, id: '00dhw charging' },
{ v: 'off', u: 0, id: '00dhw recharging' },
{ v: 'on', u: 0, id: '00dhw temperature ok' },
{ v: 'off', u: 0, id: '00dhw active' },
{ v: 'on', u: 0, id: '00dhw 3way valve active' },
{ v: 0, u: 3, id: '00dhw set pump power' },
{ v: 288768, u: 0, id: '00dhw starts' },
{ v: 102151, u: 8, id: '00dhw active time' }
]
};
const emsesp_devicedata_99 = {
data: [
{
@@ -2026,7 +2026,7 @@ rest_server.get(LIST_NETWORKS_ENDPOINT, (req, res) => {
res.json(list_networks);
});
rest_server.get(SCAN_NETWORKS_ENDPOINT, (req, res) => {
res.sendStatus(202);
res.sendStatus(202); // reboot required
});
// AP
@@ -2100,6 +2100,7 @@ rest_server.get(VERIFY_AUTHORIZATION_ENDPOINT, (req, res) => {
res.json(verify_authentication);
});
rest_server.post(RESTART_ENDPOINT, (req, res) => {
console.log('command: restart');
res.sendStatus(200);
});
rest_server.post(FACTORY_RESET_ENDPOINT, (req, res) => {
@@ -2128,7 +2129,7 @@ rest_server.get(EMSESP_SETTINGS_ENDPOINT, (req, res) => {
rest_server.post(EMSESP_SETTINGS_ENDPOINT, (req, res) => {
settings = req.body;
console.log('Write settings: ' + JSON.stringify(settings));
// res.status(202).json(settings); // restart needed
// res.status(205).json(settings); // restart needed
res.status(200).json(settings); // no restart needed
});
rest_server.get(EMSESP_CORE_DATA_ENDPOINT, (req, res) => {
@@ -2184,8 +2185,9 @@ rest_server.get(EMSESP_DEVICEDATA_ENDPOINT, (req, res) => {
res.end(null, 'binary');
});
rest_server.post(EMSESP_DEVICEENTITIES_ENDPOINT, (req, res) => {
const id = req.body.id;
rest_server.get(EMSESP_DEVICEENTITIES_ENDPOINT, (req, res) => {
const id = Number(req.query.id);
console.log('deviceentities for device ' + id + ' received');
let data = null;
if (id === 1) {
@@ -2233,6 +2235,7 @@ function updateMask(entity, de, dd) {
}
// find in dd, either looking for fullname or custom name
// console.log('looking for ' + fullname + ' in ' + dd.data);
dd_objIndex = dd.data.findIndex((obj) => obj.id.slice(2) === fullname);
if (dd_objIndex !== -1) {
let changed = new Boolean(false);
@@ -2432,10 +2435,12 @@ rest_server.post(EMSESP_WRITE_ANALOG_ENDPOINT, (req, res) => {
res.sendStatus(200);
});
rest_server.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => {
const board_profile = req.body.board_profile;
rest_server.get(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => {
const board_profile = req.query.boardProfile;
// default values
const data = {
board_profile: board_profile,
led_gpio: settings.led_gpio,
dallas_gpio: settings.dallas_gpio,
rx_gpio: settings.rx_gpio,
@@ -2559,9 +2564,10 @@ rest_server.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => {
data.eth_clock_mode = 0;
}
console.log('boardProfile POST. Sending back, profile: ' + board_profile + ', ' + 'data: ' + JSON.stringify(data));
console.log('boardProfile GET. Sending back, profile: ' + board_profile + ', ' + 'data: ' + JSON.stringify(data));
res.send(data);
// res.sendStatus(400); // send back an error, for testing
res.json(data);
});
// EMS-ESP API specific

View File

@@ -33,10 +33,12 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f
, _fsPersistence(WebCustomization::read, WebCustomization::update, this, fs, EMSESP_CUSTOMIZATION_FILE)
, _masked_entities_handler(CUSTOM_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::custom_entities, this, _1, _2),
AuthenticationPredicates::IS_AUTHENTICATED))
, _device_entities_handler(DEVICE_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::device_entities, this, _1, _2),
AuthenticationPredicates::IS_AUTHENTICATED)) {
server->on(DEVICE_ENTITIES_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebCustomizationService::device_entities, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
server->on(DEVICES_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebCustomizationService::devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
@@ -49,10 +51,6 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f
_masked_entities_handler.setMaxContentLength(2048);
_masked_entities_handler.setMaxJsonBufferSize(2048);
server->addHandler(&_masked_entities_handler);
_device_entities_handler.setMethod(HTTP_POST);
_device_entities_handler.setMaxContentLength(256);
server->addHandler(&_device_entities_handler);
}
// this creates the customization file, saving it to the FS
@@ -165,7 +163,7 @@ StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization &
void WebCustomizationService::reset_customization(AsyncWebServerRequest * request) {
#ifndef EMSESP_STANDALONE
if (LittleFS.remove(EMSESP_CUSTOMIZATION_FILE)) {
AsyncWebServerResponse * response = request->beginResponse(200); // OK
AsyncWebServerResponse * response = request->beginResponse(205); // restart needed
request->send(response);
EMSESP::system_.restart_requested(true);
return;
@@ -199,17 +197,21 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) {
}
// send back list of device entities
void WebCustomizationService::device_entities(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
void WebCustomizationService::device_entities(AsyncWebServerRequest * request) {
uint8_t id;
if (request->hasParam(F_(id))) {
id = Helpers::atoint(request->getParam(F_(id))->value().c_str()); // get id from url
size_t buffer = EMSESP_JSON_SIZE_XXXXLARGE;
auto * response = new MsgpackAsyncJsonResponse(true, buffer);
while (!response->getSize()) {
delete response;
buffer -= 1024;
response = new MsgpackAsyncJsonResponse(true, buffer);
}
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->unique_id() == json["id"]) {
if (emsdevice->unique_id() == id) {
#ifndef EMSESP_STANDALONE
JsonArray output = response->getRoot();
emsdevice->generate_values_web_customization(output);
@@ -319,7 +321,7 @@ void WebCustomizationService::custom_entities(AsyncWebServerRequest * request, J
}
}
AsyncWebServerResponse * response = request->beginResponse(need_reboot ? 201 : 200); // OK
AsyncWebServerResponse * response = request->beginResponse(need_reboot ? 205 : 200); // reboot or just OK
request->send(response);
}

View File

@@ -24,9 +24,9 @@
// GET
#define DEVICES_SERVICE_PATH "/rest/devices"
#define EMSESP_CUSTOMIZATION_SERVICE_PATH "/rest/customization"
#define DEVICE_ENTITIES_PATH "/rest/deviceEntities"
// POST
#define DEVICE_ENTITIES_PATH "/rest/deviceEntities"
#define CUSTOM_ENTITIES_PATH "/rest/customEntities"
#define RESET_CUSTOMIZATION_SERVICE_PATH "/rest/resetCustomizations"
@@ -91,13 +91,13 @@ class WebCustomizationService : public StatefulService<WebCustomization> {
// GET
void devices(AsyncWebServerRequest * request);
void device_entities(AsyncWebServerRequest * request);
// POST
void custom_entities(AsyncWebServerRequest * request, JsonVariant & json);
void device_entities(AsyncWebServerRequest * request, JsonVariant & json);
void reset_customization(AsyncWebServerRequest * request);
void reset_customization(AsyncWebServerRequest * request); // command
AsyncCallbackJsonWebHandler _masked_entities_handler, _device_entities_handler;
AsyncCallbackJsonWebHandler _masked_entities_handler;
};
} // namespace emsesp

View File

@@ -327,6 +327,7 @@ void WebDataService::write_temperature_sensor(AsyncWebServerRequest * request, J
if (EMSESP::system_.fahrenheit()) {
offset10 = offset / 0.18;
}
ok = EMSESP::temperaturesensor_.update(id, name, offset10);
}

View File

@@ -26,12 +26,11 @@ using namespace std::placeholders; // for `_1` etc
WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE)
, _boardProfileHandler(EMSESP_BOARD_PROFILE_SERVICE_PATH,
securityManager->wrapCallback(std::bind(&WebSettingsService::board_profile, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
_boardProfileHandler.setMethod(HTTP_POST);
_boardProfileHandler.setMaxContentLength(256);
server->addHandler(&_boardProfileHandler);
, _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE) {
// GET
server->on(EMSESP_BOARD_PROFILE_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebSettingsService::board_profile, this, _1), AuthenticationPredicates::IS_ADMIN));
addUpdateHandler([&](const String & originId) { onUpdate(); }, false);
}
@@ -342,15 +341,16 @@ void WebSettingsService::save() {
}
// build the json profile to send back
void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
void WebSettingsService::board_profile(AsyncWebServerRequest * request) {
if (request->hasParam("boardProfile")) {
std::string board_profile = request->getParam("boardProfile")->value().c_str();
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM);
JsonObject root = response->getRoot();
if (json.containsKey("board_profile")) {
String board_profile = json["board_profile"];
std::vector<int8_t> data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode
(void)System::load_board_profile(data, board_profile.c_str());
(void)System::load_board_profile(data, board_profile);
root["board_profile"] = board_profile;
root["led_gpio"] = data[0];
root["dallas_gpio"] = data[1];
root["rx_gpio"] = data[2];
@@ -365,7 +365,6 @@ void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVari
request->send(response);
return;
}
}
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);

View File

@@ -124,9 +124,8 @@ class WebSettingsService : public StatefulService<WebSettings> {
private:
HttpEndpoint<WebSettings> _httpEndpoint;
FSPersistence<WebSettings> _fsPersistence;
AsyncCallbackJsonWebHandler _boardProfileHandler;
void board_profile(AsyncWebServerRequest * request, JsonVariant & json);
void board_profile(AsyncWebServerRequest * request);
void onUpdate();
};