From c44903e1b0b45705729af52160b4cd400faa02ad Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 13 Jun 2023 23:36:50 +0200 Subject: [PATCH] update #6 --- interface/package.json | 16 +- interface/src/api/ap.ts | 1 + interface/src/api/endpoints.ts | 7 +- interface/src/framework/ap/APStatusForm.tsx | 1 - .../src/framework/mqtt/MqttStatusForm.tsx | 4 +- .../framework/network/NetworkStatusForm.tsx | 3 +- interface/src/framework/ntp/NTPStatusForm.tsx | 4 +- interface/src/framework/system/SystemLog.tsx | 3 +- .../src/framework/system/SystemStatusForm.tsx | 3 +- interface/src/project/DashboardDevices.tsx | 4 +- interface/src/project/DashboardSensors.tsx | 118 +- interface/src/project/DashboardStatus.tsx | 37 +- interface/src/project/SettingsApplication.tsx | 85 +- .../src/project/SettingsCustomization.tsx | 195 ++- interface/src/project/api.ts | 83 +- interface/src/project/types.ts | 16 - interface/src/utils/binding.ts | 11 +- interface/src/utils/endpoints.ts | 2 +- interface/src/utils/index.ts | 2 +- interface/src/utils/useRest.ts | 4 +- interface/src/utils/useRest2.ts | 18 +- interface/yarn.lock | 170 +-- lib/ESPAsyncWebServer/WebResponses.cpp | 1168 +++++++++-------- lib/framework/HttpEndpoint.h | 2 +- lib/framework/WiFiScanner.cpp | 4 +- mock-api/server.js | 186 +-- src/web/WebCustomizationService.cpp | 26 +- src/web/WebCustomizationService.h | 8 +- src/web/WebDataService.cpp | 1 + src/web/WebSettingsService.cpp | 49 +- src/web/WebSettingsService.h | 7 +- 31 files changed, 1132 insertions(+), 1106 deletions(-) diff --git a/interface/package.json b/interface/package.json index c72938554..dc09f548f 100644 --- a/interface/package.json +++ b/interface/package.json @@ -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" diff --git a/interface/src/api/ap.ts b/interface/src/api/ap.ts index f369d1b61..f54ad1f59 100644 --- a/interface/src/api/ap.ts +++ b/interface/src/api/ap.ts @@ -5,6 +5,7 @@ import type { APSettings, APStatus } from 'types'; export const readAPStatus = () => alovaInstance.Get('/apStatus'); +// TODO change AXIOS to Alova export function readAPSettings(): AxiosPromise { return AXIOS.get('/apSettings'); } diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index ced2bc964..3af7431db 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -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; diff --git a/interface/src/framework/ap/APStatusForm.tsx b/interface/src/framework/ap/APStatusForm.tsx index be15f9f7f..611353513 100644 --- a/interface/src/framework/ap/APStatusForm.tsx +++ b/interface/src/framework/ap/APStatusForm.tsx @@ -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(); diff --git a/interface/src/framework/mqtt/MqttStatusForm.tsx b/interface/src/framework/mqtt/MqttStatusForm.tsx index f7af8bd88..4c2daa4c6 100644 --- a/interface/src/framework/mqtt/MqttStatusForm.tsx +++ b/interface/src/framework/mqtt/MqttStatusForm.tsx @@ -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({ read: MqttApi.readMqttStatus }); const { LL } = useI18nContext(); diff --git a/interface/src/framework/network/NetworkStatusForm.tsx b/interface/src/framework/network/NetworkStatusForm.tsx index 93d1ce840..5a720fbaf 100644 --- a/interface/src/framework/network/NetworkStatusForm.tsx +++ b/interface/src/framework/network/NetworkStatusForm.tsx @@ -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({ read: NetworkApi.readNetworkStatus }); const { LL } = useI18nContext(); diff --git a/interface/src/framework/ntp/NTPStatusForm.tsx b/interface/src/framework/ntp/NTPStatusForm.tsx index 903ee6025..971246c29 100644 --- a/interface/src/framework/ntp/NTPStatusForm.tsx +++ b/interface/src/framework/ntp/NTPStatusForm.tsx @@ -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({ read: NTPApi.readNTPStatus }); + const [localTime, setLocalTime] = useState(''); const [settingTime, setSettingTime] = useState(false); const [processing, setProcessing] = useState(false); diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index dffcdeb0b..e7a1c761c 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -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({ read: SystemApi.readLogSettings }); diff --git a/interface/src/framework/system/SystemStatusForm.tsx b/interface/src/framework/system/SystemStatusForm.tsx index f7f8043a3..719fd5a22 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -53,8 +53,7 @@ const SystemStatusForm: FC = () => { const { LL } = useI18nContext(); const [restarting, setRestarting] = useState(); - // TODO missing update! - + // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); const { loadData, data, errorMessage } = useRest({ read: SystemApi.readSystemStatus }); const { me } = useContext(AuthenticatedContext); diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index b1223a366..699a63995 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -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; } diff --git a/interface/src/project/DashboardSensors.tsx b/interface/src/project/DashboardSensors.tsx index 964110507..55f5ba4ba 100644 --- a/interface/src/project/DashboardSensors.tsx +++ b/interface/src/project/DashboardSensors.tsx @@ -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({ ts: [], as: [], analog_enabled: false }); const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState(); const [selectedAnalogSensor, setSelectedAnalogSensor] = useState(); const [temperatureDialogOpen, setTemperatureDialogOpen] = useState(false); const [analogDialogOpen, setAnalogDialogOpen] = useState(false); const [creating, setCreating] = useState(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 ; @@ -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 { - setTemperatureDialogOpen(false); - setSelectedTemperatureSensor(undefined); - await fetchSensorData(); - } + }) + .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,33 +275,27 @@ const DashboardSensors: FC = () => { }; const onAnalogDialogSave = async (as: AnalogSensor) => { - try { - const response = await EMSESP.writeAnalogSensor({ - id: as.id, - gpio: as.g, - name: as.n, - offset: as.o, - factor: as.f, - 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 { + await writeAnalogSensor({ + id: as.id, + gpio: as.g, + name: as.n, + offset: as.o, + factor: as.f, + uom: as.u, + type: as.t, + deleted: as.d + }) + .then(() => { toast.success(LL.UPDATED_OF(LL.ANALOG_SENSOR(2))); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } finally { - setAnalogDialogOpen(false); - setSelectedAnalogSensor(undefined); - await fetchSensorData(); - } + }) + .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 && ( <> - {LL.ANALOG_SENSORS(0)} + {LL.ANALOG_SENSORS()} {selectedAnalogSensor && ( diff --git a/interface/src/project/DashboardStatus.tsx b/interface/src/project/DashboardStatus.tsx index 9e3cd0c68..9ef093b98 100644 --- a/interface/src/project/DashboardStatus.tsx +++ b/interface/src/project/DashboardStatus.tsx @@ -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({ 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(); - toast.info(LL.SCANNING() + '...'); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } finally { - setConfirmScan(false); - } + await scanDevices() + .then(() => { + toast.info(LL.SCANNING() + '...'); + }) + .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 ; + return ; } return ( diff --git a/interface/src/project/SettingsApplication.tsx b/interface/src/project/SettingsApplication.tsx index 8c871b5a7..06662cbd2 100644 --- a/interface/src/project/SettingsApplication.tsx +++ b/interface/src/project/SettingsApplication.tsx @@ -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(); const { LL } = useI18nContext(); - const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData); + const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue); const [fieldErrors, setFieldErrors] = useState(); - const [processingBoard, setProcessingBoard] = useState(false); - // TODO remove - just for testing loaddata - // useEffect(() => { - // void loadData(); - // }, []); + const { + loading: processingBoard, + send: readBoardProfile, + onSuccess: onSuccessBoardProfile + } = useRequest((boardProfile) => EMSESP.getBoardProfile(boardProfile), { + immediate: false + }); - // 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 - }); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } finally { - setProcessingBoard(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); + }); }; 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(); - setRestarting(true); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } + await restartCommand().catch((error) => { + toast.error(error.message); + }); + setRestarting(true); }; return ( diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index c7658ed46..24fa60ecc 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -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(0); const blocker = useBlocker(numChanges !== 0); + const [restarting, setRestarting] = useState(false); const [restartNeeded, setRestartNeeded] = useState(false); - const [deviceEntities, setDeviceEntities] = useState(); - const [devices, setDevices] = useState(); - const [errorMessage, setErrorMessage] = useState(); + const [deviceEntities, setDeviceEntities] = useState([]); const [selectedDevice, setSelectedDevice] = useState(-1); const [confirmReset, setConfirmReset] = useState(false); const [selectedFilters, setSelectedFilters] = useState(0); @@ -58,6 +57,36 @@ const SettingsCustomization: FC = () => { const [selectedDeviceEntity, setSelectedDeviceEntity] = useState(); const [dialogOpen, setDialogOpen] = useState(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,72 +320,57 @@ 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) { - setRestartNeeded(true); - } else { - toast.error(LL.PROBLEM_UPDATING()); + await writeCustomEntities({ id: devices?.devices[selectedDevice].i, entity_ids: masked_entities }).catch( + (error) => { + if (error.message === 'Reboot required') { + setRestartNeeded(true); + } else { + 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 ; - } - - return ( - <> - - {LL.CUSTOMIZATIONS_HELP_1()}. - - ={LL.CUSTOMIZATIONS_HELP_2()}   - ={LL.CUSTOMIZATIONS_HELP_3()}   - ={LL.CUSTOMIZATIONS_HELP_4()}   - ={LL.CUSTOMIZATIONS_HELP_5()}   - ={LL.CUSTOMIZATIONS_HELP_6()} - - - - - {LL.SELECT_DEVICE()}... + const renderDeviceList = () => ( + <> + + {LL.CUSTOMIZATIONS_HELP_1()}. + + ={LL.CUSTOMIZATIONS_HELP_2()}   + ={LL.CUSTOMIZATIONS_HELP_3()}   + ={LL.CUSTOMIZATIONS_HELP_4()}   + ={LL.CUSTOMIZATIONS_HELP_5()}   + ={LL.CUSTOMIZATIONS_HELP_6()} + + + + + {LL.SELECT_DEVICE()}... + + {devices.devices.map((device: DeviceShort, index) => ( + + {device.s} - {devices.devices.map((device: DeviceShort, index) => ( - - {device.s} - - ))} - - - ); - }; + ))} + + + ); 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 = () => { {LL.DEVICE_ENTITIES()} - {renderDeviceList()} + {devices && renderDeviceList()} {renderDeviceData()} {restartNeeded && ( @@ -539,7 +526,7 @@ const SettingsCustomization: FC = () => { startIcon={} variant="outlined" color="secondary" - onClick={() => devices && fetchDeviceEntities(devices.devices[selectedDevice].i)} + onClick={() => devices && readDeviceEntities(devices.devices[selectedDevice].i)} > {LL.CANCEL()} diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index 2b7515142..b925027c1 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -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`); - export const readDeviceData = (id: number) => alovaInstance.Get('/deviceData', { params: { id }, responseType: 'arraybuffer' // uses msgpack }); - export const writeDeviceValue = (data: any) => alovaInstance.Post('/writeDeviceValue', data); // SettingsApplication export const readSettings = () => alovaInstance.Get('/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('/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'); +export const resetCustomizations = () => alovaInstance.Post('/resetCustomizations'); +export const writeCustomEntities = (data: any) => alovaInstance.Post('/customEntities', data); -export function restart(): AxiosPromise { - return AXIOS.post('/restart'); -} +// DashboardSensors +export const readSensorData = () => alovaInstance.Get('/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 { - return AXIOS.post('/boardProfile', boardProfile); -} -// TODO change to GET -export function readDeviceEntities(unique_id: UniqueID): AxiosPromise { - return AXIOS_BIN.post('/deviceEntities', unique_id); -} +// TODO think about naming, get... and not get etc... -export function readStatus(): AxiosPromise { - return AXIOS.get('/status'); -} +// DashboardStatus +export const readStatus = () => alovaInstance.Get('/status'); +export const scanDevices = () => alovaInstance.Post('/scanDevices'); -export function readDevices(): AxiosPromise { - return AXIOS.get('/devices'); -} - -export function scanDevices(): AxiosPromise { - return AXIOS.post('/scanDevices'); // call command -} - -export function readSensorData(): AxiosPromise { - return AXIOS.get('/sensorData'); -} - -export function writeCustomEntities(customEntities: CustomEntities): AxiosPromise { - return AXIOS.post('/customEntities', customEntities); -} - -export function writeTemperatureSensor(ts: WriteTemperatureSensor): AxiosPromise { - return AXIOS.post('/writeTemperatureSensor', ts); -} - -export function writeAnalogSensor(as: WriteAnalogSensor): AxiosPromise { - return AXIOS.post('/writeAnalogSensor', as); -} - -export function resetCustomizations(): AxiosPromise { - return AXIOS.post('/resetCustomizations'); // command -} +// ALOVA goes here.... export function API(apiCall: APIcall): AxiosPromise { return AXIOS_API.post('/', apiCall); // command diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 5f80d7bd2..a50b0281c 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -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; diff --git a/interface/src/utils/binding.ts b/interface/src/utils/binding.ts index 190584526..74a755a52 100644 --- a/interface/src/utils/binding.ts +++ b/interface/src/utils/binding.ts @@ -23,14 +23,13 @@ export const updateValue = }; export const updateValueDirty = - (origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: any) => - // (origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: UpdateEntity) => + (origData: any, dirtyFlags: any, setDirtyFlags: any, updateDataValue: any) => (event: React.ChangeEvent) => { 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)) { diff --git a/interface/src/utils/endpoints.ts b/interface/src/utils/endpoints.ts index 98b9c55ac..d0de377fe 100644 --- a/interface/src/utils/endpoints.ts +++ b/interface/src/utils/endpoints.ts @@ -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 + ')'; diff --git a/interface/src/utils/index.ts b/interface/src/utils/index.ts index 245acb6c7..c17d6949b 100644 --- a/interface/src/utils/index.ts +++ b/interface/src/utils/index.ts @@ -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'; diff --git a/interface/src/utils/useRest.ts b/interface/src/utils/useRest.ts index 9a5378802..1d71f1a07 100644 --- a/interface/src/utils/useRest.ts +++ b/interface/src/utils/useRest.ts @@ -52,8 +52,8 @@ export const useRest = ({ read, update }: RestRequestOptions) => { 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())); } diff --git a/interface/src/utils/useRest2.ts b/interface/src/utils/useRest2.ts index 29cae6065..7e8688da0 100644 --- a/interface/src/utils/useRest2.ts +++ b/interface/src/utils/useRest2.ts @@ -7,14 +7,15 @@ import type { AlovaXHRRequestConfig, AlovaXHRResponse, AlovaXHRResponseHeaders } import { useI18nContext } from 'i18n/i18n-react'; -export interface RestRequestOptions { +export interface RestRequestOptions2 { read: () => Method, AlovaXHRResponseHeaders>; update: ( value: D ) => Method, AlovaXHRResponseHeaders>; } -export const useRest2 = ({ read, update }: RestRequestOptions) => { +// TODO rename back to useRest +export const useRest2 = ({ read, update }: RestRequestOptions2) => { const { LL } = useI18nContext(); const [errorMessage, setErrorMessage] = useState(); @@ -32,12 +33,8 @@ export const useRest2 = ({ read, update }: RestRequestOptions) => { 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 = ({ read, update }: RestRequestOptions) => { }); onReadComplete((event) => { - setOrigData(event.data); // make a copy + setOrigData(event.data); }); const loadData = async () => { @@ -62,7 +59,6 @@ export const useRest2 = ({ read, update }: RestRequestOptions) => { 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 = ({ read, update }: RestRequestOptions) => { loadData, saveData, saving, - setData, + updateDataValue, data, origData, dirtyFlags, diff --git a/interface/yarn.lock b/interface/yarn.lock index c024a8686..ca56b2b5d 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -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 diff --git a/lib/ESPAsyncWebServer/WebResponses.cpp b/lib/ESPAsyncWebServer/WebResponses.cpp index d2c2f78a9..63bdcd0ff 100644 --- a/lib/ESPAsyncWebServer/WebResponses.cpp +++ b/lib/ESPAsyncWebServer/WebResponses.cpp @@ -23,225 +23,281 @@ #include "cbuf.h" // Since ESP8266 does not link memchr by default, here's its implementation. -void* memchr(void* ptr, int ch, size_t count) -{ - unsigned char* p = static_cast(ptr); - while(count--) - if(*p++ == static_cast(ch)) - return --p; - return nullptr; +void * memchr(void * ptr, int ch, size_t count) { + unsigned char * p = static_cast(ptr); + while (count--) + if (*p++ == static_cast(ch)) + return --p; + return nullptr; } /* * Abstract Response * */ -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 (""); - } +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"); // 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 (""); + } } -const __FlashStringHelper *AsyncWebServerResponse::responseCodeToString(int code) { - return reinterpret_cast(responseCodeToString(code)); +const __FlashStringHelper * AsyncWebServerResponse::responseCodeToString(int code) { + return reinterpret_cast(responseCodeToString(code)); } AsyncWebServerResponse::AsyncWebServerResponse() - : _code(0) - , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) - , _contentType() - , _contentLength(0) - , _sendContentLength(true) - , _chunked(false) - , _headLength(0) - , _sentLength(0) - , _ackedLength(0) - , _writtenLength(0) - , _state(RESPONSE_SETUP) -{ - for(auto header: DefaultHeaders::Instance()) { - _headers.add(new AsyncWebHeader(header->name(), header->value())); - } + : _code(0) + , _headers(LinkedList([](AsyncWebHeader * h) { delete h; })) + , _contentType() + , _contentLength(0) + , _sendContentLength(true) + , _chunked(false) + , _headLength(0) + , _sentLength(0) + , _ackedLength(0) + , _writtenLength(0) + , _state(RESPONSE_SETUP) { + for (auto header : DefaultHeaders::Instance()) { + _headers.add(new AsyncWebHeader(header->name(), header->value())); + } } -AsyncWebServerResponse::~AsyncWebServerResponse(){ - _headers.free(); +AsyncWebServerResponse::~AsyncWebServerResponse() { + _headers.free(); } -void AsyncWebServerResponse::setCode(int code){ - if(_state == RESPONSE_SETUP) - _code = code; +void AsyncWebServerResponse::setCode(int code) { + if (_state == RESPONSE_SETUP) + _code = code; } -void AsyncWebServerResponse::setContentLength(size_t len){ - if(_state == RESPONSE_SETUP) - _contentLength = len; +void AsyncWebServerResponse::setContentLength(size_t len) { + if (_state == RESPONSE_SETUP) + _contentLength = len; } -void AsyncWebServerResponse::setContentType(const String& type){ - if(_state == RESPONSE_SETUP) - _contentType = type; +void AsyncWebServerResponse::setContentType(const String & type) { + if (_state == RESPONSE_SETUP) + _contentType = type; } -void AsyncWebServerResponse::addHeader(const String& name, const String& value){ - _headers.add(new AsyncWebHeader(name, value)); +void AsyncWebServerResponse::addHeader(const String & name, const String & value) { + _headers.add(new AsyncWebHeader(name, value)); } -String AsyncWebServerResponse::_assembleHead(uint8_t version){ - if(version){ - addHeader(F("Accept-Ranges"), F("none")); - if(_chunked) - addHeader(F("Transfer-Encoding"), F("chunked")); - } - String out = String(); - int bufSize = 300; - char buf[bufSize]; +String AsyncWebServerResponse::_assembleHead(uint8_t version) { + if (version) { + addHeader(F("Accept-Ranges"), F("none")); + if (_chunked) + addHeader(F("Transfer-Encoding"), F("chunked")); + } + String out = String(); + int bufSize = 300; + char buf[bufSize]; - snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); - out.concat(buf); - - if(_sendContentLength) { - snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); + snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); out.concat(buf); - } - if(_contentType.length()) { - snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); - out.concat(buf); - } - for(const auto& header: _headers){ - snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header->name().c_str(), header->value().c_str()); - out.concat(buf); - } - _headers.free(); + if (_sendContentLength) { + snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); + out.concat(buf); + } + if (_contentType.length()) { + snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); + out.concat(buf); + } - out.concat(F("\r\n")); - _headLength = out.length(); - return out; + for (const auto & header : _headers) { + snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header->name().c_str(), header->value().c_str()); + out.concat(buf); + } + _headers.free(); + + out.concat(F("\r\n")); + _headLength = out.length(); + 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 * */ -AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){ - _code = code; - _content = content; - _contentType = contentType; - if(_content.length()){ - _contentLength = _content.length(); - if(!_contentType.length()) - _contentType = F("text/plain"); - } - addHeader(F("Connection"), F("close")); +AsyncBasicResponse::AsyncBasicResponse(int code, const String & contentType, const String & content) { + _code = code; + _content = content; + _contentType = contentType; + if (_content.length()) { + _contentLength = _content.length(); + if (!_contentType.length()) + _contentType = F("text/plain"); + } + addHeader(F("Connection"), F("close")); } -void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ - _state = RESPONSE_HEADERS; - String out = _assembleHead(request->version()); - size_t outLen = out.length(); - size_t space = request->client()->space(); - if(!_contentLength && space >= outLen){ - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if(_contentLength && space >= outLen + _contentLength){ - out += _content; - outLen += _contentLength; - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if(space && space < outLen){ - String partial = out.substring(0, space); - _content = out.substring(space) + _content; - _contentLength += outLen - space; - _writtenLength += request->client()->write(partial.c_str(), partial.length()); - _state = RESPONSE_CONTENT; - } else if(space > outLen && space < (outLen + _contentLength)){ - size_t shift = space - outLen; - outLen += shift; - _sentLength += shift; - out += _content.substring(0, shift); - _content = _content.substring(shift); - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_CONTENT; - } else { - _content = out + _content; - _contentLength += outLen; - _state = RESPONSE_CONTENT; - } +void AsyncBasicResponse::_respond(AsyncWebServerRequest * request) { + _state = RESPONSE_HEADERS; + String out = _assembleHead(request->version()); + size_t outLen = out.length(); + size_t space = request->client()->space(); + if (!_contentLength && space >= outLen) { + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if (_contentLength && space >= outLen + _contentLength) { + out += _content; + outLen += _contentLength; + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if (space && space < outLen) { + String partial = out.substring(0, space); + _content = out.substring(space) + _content; + _contentLength += outLen - space; + _writtenLength += request->client()->write(partial.c_str(), partial.length()); + _state = RESPONSE_CONTENT; + } else if (space > outLen && space < (outLen + _contentLength)) { + size_t shift = space - outLen; + outLen += shift; + _sentLength += shift; + out += _content.substring(0, shift); + _content = _content.substring(shift); + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_CONTENT; + } else { + _content = out + _content; + _contentLength += outLen; + _state = RESPONSE_CONTENT; + } } -size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ - (void)time; - _ackedLength += len; - if(_state == RESPONSE_CONTENT){ - size_t available = _contentLength - _sentLength; - size_t space = request->client()->space(); - //we can fit in this packet - if(space > available){ - _writtenLength += request->client()->write(_content.c_str(), available); - _content = String(); - _state = RESPONSE_WAIT_ACK; - return available; +size_t AsyncBasicResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { + (void)time; + _ackedLength += len; + if (_state == RESPONSE_CONTENT) { + size_t available = _contentLength - _sentLength; + size_t space = request->client()->space(); + //we can fit in this packet + if (space > available) { + _writtenLength += request->client()->write(_content.c_str(), available); + _content = String(); + _state = RESPONSE_WAIT_ACK; + return available; + } + //send some data, the rest on ack + String out = _content.substring(0, space); + _content = _content.substring(space); + _sentLength += space; + _writtenLength += request->client()->write(out.c_str(), space); + return space; + } else if (_state == RESPONSE_WAIT_ACK) { + if (_ackedLength >= _writtenLength) { + _state = RESPONSE_END; + } } - //send some data, the rest on ack - String out = _content.substring(0, space); - _content = _content.substring(space); - _sentLength += space; - _writtenLength += request->client()->write(out.c_str(), space); - return space; - } else if(_state == RESPONSE_WAIT_ACK){ - if(_ackedLength >= _writtenLength){ - _state = RESPONSE_END; - } - } - return 0; + return 0; } @@ -249,232 +305,231 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint * Abstract Response * */ -AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback) -{ - // In case of template processing, we're unable to determine real response size - if(callback) { - _contentLength = 0; - _sendContentLength = false; - _chunked = true; - } -} - -void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ - addHeader(F("Connection"), F("close")); - _head = _assembleHead(request->version()); - _state = RESPONSE_HEADERS; - _ack(request, 0, 0); -} - -size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ - (void)time; - if(!_sourceValid()){ - _state = RESPONSE_FAILED; - request->client()->close(); - return 0; - } - _ackedLength += len; - size_t space = request->client()->space(); - - size_t headLen = _head.length(); - if(_state == RESPONSE_HEADERS){ - if(space >= headLen){ - _state = RESPONSE_CONTENT; - space -= headLen; - } else { - String out = _head.substring(0, space); - _head = _head.substring(space); - _writtenLength += request->client()->write(out.c_str(), out.length()); - return out.length(); +AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) + : _callback(callback) { + // In case of template processing, we're unable to determine real response size + if (callback) { + _contentLength = 0; + _sendContentLength = false; + _chunked = true; } - } +} - if(_state == RESPONSE_CONTENT){ - size_t outLen; - if(_chunked){ - if(space <= 8){ +void AsyncAbstractResponse::_respond(AsyncWebServerRequest * request) { + addHeader(F("Connection"), F("close")); + _head = _assembleHead(request->version()); + _state = RESPONSE_HEADERS; + _ack(request, 0, 0); +} + +size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { + (void)time; + if (!_sourceValid()) { + _state = RESPONSE_FAILED; + request->client()->close(); return 0; - } - outLen = space; - } else if(!_sendContentLength){ - outLen = space; - } else { - outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength); + } + _ackedLength += len; + size_t space = request->client()->space(); + + size_t headLen = _head.length(); + if (_state == RESPONSE_HEADERS) { + if (space >= headLen) { + _state = RESPONSE_CONTENT; + space -= headLen; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + _writtenLength += request->client()->write(out.c_str(), out.length()); + return out.length(); + } } - uint8_t *buf = (uint8_t *)malloc(outLen+headLen); - if (!buf) { - // os_printf("_ack malloc %d failed\n", outLen+headLen); - return 0; + if (_state == RESPONSE_CONTENT) { + size_t outLen; + if (_chunked) { + if (space <= 8) { + return 0; + } + outLen = space; + } else if (!_sendContentLength) { + outLen = space; + } else { + outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength); + } + + uint8_t * buf = (uint8_t *)malloc(outLen + headLen); + if (!buf) { + // os_printf("_ack malloc %d failed\n", outLen+headLen); + return 0; + } + + if (headLen) { + memcpy(buf, _head.c_str(), _head.length()); + } + + size_t readLen = 0; + + if (_chunked) { + // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. + // See RFC2616 sections 2, 3.6.1. + readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8); + if (readLen == RESPONSE_TRY_AGAIN) { + free(buf); + return 0; + } + outLen = snprintf_P((char *)buf + headLen, sizeof(buf) - headLen - 2, PSTR("%x"), readLen) + headLen; + while (outLen < headLen + 4) + buf[outLen++] = ' '; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + outLen += readLen; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + } else { + readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen); + if (readLen == RESPONSE_TRY_AGAIN) { + free(buf); + return 0; + } + outLen = readLen + headLen; + } + + if (headLen) { + _head = String(); + } + + if (outLen) { + _writtenLength += request->client()->write((const char *)buf, outLen); + } + + if (_chunked) { + _sentLength += readLen; + } else { + _sentLength += outLen - headLen; + } + + free(buf); + + if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) { + _state = RESPONSE_WAIT_ACK; + } + return outLen; + + } else if (_state == RESPONSE_WAIT_ACK) { + if (!_sendContentLength || _ackedLength >= _writtenLength) { + _state = RESPONSE_END; + if (!_chunked && !_sendContentLength) + request->client()->close(true); + } } - - if(headLen){ - memcpy(buf, _head.c_str(), _head.length()); - } - - size_t readLen = 0; - - if(_chunked){ - // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. - // See RFC2616 sections 2, 3.6.1. - readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8); - if(readLen == RESPONSE_TRY_AGAIN){ - free(buf); - return 0; - } - outLen = snprintf_P((char*)buf+headLen, sizeof(buf)-headLen-2, PSTR("%x"), readLen) + headLen; - while(outLen < headLen + 4) buf[outLen++] = ' '; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - outLen += readLen; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - } else { - readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen); - if(readLen == RESPONSE_TRY_AGAIN){ - free(buf); - return 0; - } - outLen = readLen + headLen; - } - - if(headLen){ - _head = String(); - } - - if(outLen){ - _writtenLength += request->client()->write((const char*)buf, outLen); - } - - if(_chunked){ - _sentLength += readLen; - } else { - _sentLength += outLen - headLen; - } - - free(buf); - - if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){ - _state = RESPONSE_WAIT_ACK; - } - return outLen; - - } else if(_state == RESPONSE_WAIT_ACK){ - if(!_sendContentLength || _ackedLength >= _writtenLength){ - _state = RESPONSE_END; - if(!_chunked && !_sendContentLength) - request->client()->close(true); - } - } - return 0; + 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) { - memcpy(data, _cache.data(), readFromCache); - _cache.erase(_cache.begin(), _cache.begin() + readFromCache); + if (readFromCache) { + memcpy(data, _cache.data(), readFromCache); + _cache.erase(_cache.begin(), _cache.begin() + readFromCache); } // If we need to read more... - const size_t needFromFile = len - readFromCache; + const size_t needFromFile = len - readFromCache; const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); return readFromCache + readFromContent; } -size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) -{ - if(!_callback) - return _fillBuffer(data, len); +size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t * data, size_t len) { + if (!_callback) + return _fillBuffer(data, len); - const size_t originalLen = len; - len = _readDataFromCacheOrContent(data, len); - // 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; - // temporary buffer to hold parameter name - uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; - String paramName; - // If closing placeholder is found: - if(pTemplateEnd) { - // prepare argument to callback - const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); - if(paramNameLength) { - memcpy(buf, pTemplateStart + 1, paramNameLength); - buf[paramNameLength] = 0; - paramName = String(reinterpret_cast(buf)); - } else { // double percent sign encountered, this is single percent sign escaped. - // remove the 2nd percent sign - memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; - ++pTemplateStart; - } - } 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)); - if(readFromCacheOrContent) { - pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); - if(pTemplateEnd) { - // prepare argument to callback - *pTemplateEnd = 0; - paramName = String(reinterpret_cast(buf)); - // Copy remaining read-ahead data into cache - _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); - pTemplateEnd = &data[len - 1]; + const size_t originalLen = len; + len = _readDataFromCacheOrContent(data, len); + // 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; + // temporary buffer to hold parameter name + uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; + String paramName; + // If closing placeholder is found: + if (pTemplateEnd) { + // prepare argument to callback + const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); + if (paramNameLength) { + memcpy(buf, pTemplateStart + 1, paramNameLength); + buf[paramNameLength] = 0; + paramName = String(reinterpret_cast(buf)); + } else { // double percent sign encountered, this is single percent sign escaped. + // remove the 2nd percent sign + memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; + ++pTemplateStart; + } + } 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)); + if (readFromCacheOrContent) { + pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); + if (pTemplateEnd) { + // prepare argument to callback + *pTemplateEnd = 0; + paramName = String(reinterpret_cast(buf)); + // 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 + { + // 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 + ++pTemplateStart; + } 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. + // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. + // Data after pTemplateEnd may need to be moved. + // The first byte of data after placeholder is located at pTemplateEnd + 1. + // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). + const String paramValue(_callback(paramName)); + const char * pvstr = paramValue.c_str(); + const unsigned int pvlen = paramValue.length(); + const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); + // make room for param value + // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store + if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { + _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); + //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); + len = originalLen; // fix issue with truncated data, not sure if it has any side effects + } else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied) + //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. + // Move the entire data after the placeholder + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + // 3. replace placeholder with actual value + memcpy(pTemplateStart, pvstr, numBytesCopied); + // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) + if (numBytesCopied < pvlen) { + _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); + } else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... + // there is some free room, fill it from cache + const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; + const size_t totalFreeRoom = originalLen - len + roomFreed; + len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; + } else { // result is copied fully; it is longer than placeholder text + const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; + len = std::min(len + roomTaken, originalLen); + } } - 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 - ++pTemplateStart; - } - 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. - // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. - // Data after pTemplateEnd may need to be moved. - // The first byte of data after placeholder is located at pTemplateEnd + 1. - // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). - const String paramValue(_callback(paramName)); - const char* pvstr = paramValue.c_str(); - const unsigned int pvlen = paramValue.length(); - const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); - // make room for param value - // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store - if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { - _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); - //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); - len = originalLen; // fix issue with truncated data, not sure if it has any side effects - } else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied) - //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. - // Move the entire data after the placeholder - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - // 3. replace placeholder with actual value - memcpy(pTemplateStart, pvstr, numBytesCopied); - // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) - if(numBytesCopied < pvlen) { - _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); - } else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... - // there is some free room, fill it from cache - const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; - const size_t totalFreeRoom = originalLen - len + roomFreed; - len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; - } else { // result is copied fully; it is longer than placeholder text - const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; - len = std::min(len + roomTaken, originalLen); - } - } - } // while(pTemplateStart) - return len; + } // while(pTemplateStart) + return len; } @@ -482,193 +537,218 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size * File Response * */ -AsyncFileResponse::~AsyncFileResponse(){ - if(_content) - _content.close(); +AsyncFileResponse::~AsyncFileResponse() { + if (_content) + _content.close(); } -void AsyncFileResponse::_setContentType(const String& path){ +void AsyncFileResponse::_setContentType(const String & path) { #if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION - extern const __FlashStringHelper *getContentType(const String &path); - _contentType = getContentType(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){ - _code = 200; - _path = path; +AsyncFileResponse::AsyncFileResponse(FS & fs, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = 200; + _path = path; - if(!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))){ - _path = _path + F(".gz"); - addHeader(F("Content-Encoding"), F("gzip")); - _callback = nullptr; // Unable to process zipped templates - _sendContentLength = true; - _chunked = false; - } + if (!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))) { + _path = _path + F(".gz"); + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process zipped templates + _sendContentLength = true; + _chunked = false; + } - _content = fs.open(_path, fs::FileOpenMode::read); - _contentLength = _content.size(); + _content = fs.open(_path, fs::FileOpenMode::read); + _contentLength = _content.size(); - if(contentType.length() == 0) - _setContentType(path); - else - _contentType = contentType; + if (contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26 + path.length() - filenameStart]; + char * filename = (char *)path.c_str() + filenameStart; - if(download) { - // set filename and force download - snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - // set filename and force rendering - snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename); - } - addHeader(F("Content-Disposition"), buf); + if (download) { + // set filename and force download + snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + // set filename and force rendering + snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename); + } + addHeader(F("Content-Disposition"), buf); } -AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ - _code = 200; - _path = path; +AsyncFileResponse::AsyncFileResponse(File content, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = 200; + _path = path; - if(!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))){ - addHeader(F("Content-Encoding"), F("gzip")); - _callback = nullptr; // Unable to process gzipped templates - _sendContentLength = true; - _chunked = false; - } + if (!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))) { + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process gzipped templates + _sendContentLength = true; + _chunked = false; + } - _content = content; - _contentLength = _content.size(); + _content = content; + _contentLength = _content.size(); - if(contentType.length() == 0) - _setContentType(path); - else - _contentType = contentType; + if (contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26 + path.length() - filenameStart]; + char * filename = (char *)path.c_str() + filenameStart; - if(download) { - snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename); - } - addHeader(F("Content-Disposition"), buf); + if (download) { + snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename); + } + addHeader(F("Content-Disposition"), buf); } -size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ - return _content.read(data, len); +size_t AsyncFileResponse::_fillBuffer(uint8_t * data, size_t len) { + return _content.read(data, len); } /* * Stream Response * */ -AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { - _code = 200; - _content = &stream; - _contentLength = len; - _contentType = contentType; +AsyncStreamResponse::AsyncStreamResponse(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = 200; + _content = &stream; + _contentLength = len; + _contentType = contentType; } -size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t available = _content->available(); - size_t outLen = (available > len)?len:available; - size_t i; - for(i=0;iread(); - return outLen; +size_t AsyncStreamResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t available = _content->available(); + size_t outLen = (available > len) ? len : available; + size_t i; + for (i = 0; i < outLen; i++) + data[i] = _content->read(); + return outLen; } /* * Callback Response * */ -AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) { - _code = 200; - _content = callback; - _contentLength = len; - if(!len) - _sendContentLength = false; - _contentType = contentType; - _filledLength = 0; +AsyncCallbackResponse::AsyncCallbackResponse(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) + : AsyncAbstractResponse(templateCallback) { + _code = 200; + _content = callback; + _contentLength = len; + if (!len) + _sendContentLength = false; + _contentType = contentType; + _filledLength = 0; } -size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t ret = _content(data, len, _filledLength); - if(ret != RESPONSE_TRY_AGAIN){ - _filledLength += ret; - } - return ret; +size_t AsyncCallbackResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t ret = _content(data, len, _filledLength); + if (ret != RESPONSE_TRY_AGAIN) { + _filledLength += ret; + } + return ret; } /* * Chunked Response * */ -AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) { - _code = 200; - _content = callback; - _contentLength = 0; - _contentType = contentType; - _sendContentLength = false; - _chunked = true; - _filledLength = 0; +AsyncChunkedResponse::AsyncChunkedResponse(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) + : AsyncAbstractResponse(processorCallback) { + _code = 200; + _content = callback; + _contentLength = 0; + _contentType = contentType; + _sendContentLength = false; + _chunked = true; + _filledLength = 0; } -size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t ret = _content(data, len, _filledLength); - if(ret != RESPONSE_TRY_AGAIN){ - _filledLength += ret; - } - return ret; +size_t AsyncChunkedResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t ret = _content(data, len, _filledLength); + if (ret != RESPONSE_TRY_AGAIN) { + _filledLength += ret; + } + return ret; } /* * Progmem Response * */ -AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { - _code = code; - _content = content; - _contentType = contentType; - _contentLength = len; - _readLength = 0; +AsyncProgmemResponse::AsyncProgmemResponse(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = code; + _content = content; + _contentType = contentType; + _contentLength = len; + _readLength = 0; } -size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t left = _contentLength - _readLength; - if (left > len) { - memcpy_P(data, _content + _readLength, len); - _readLength += len; - return len; - } - memcpy_P(data, _content + _readLength, left); - _readLength += left; - return left; +size_t AsyncProgmemResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t left = _contentLength - _readLength; + if (left > len) { + memcpy_P(data, _content + _readLength, len); + _readLength += len; + return len; + } + memcpy_P(data, _content + _readLength, left); + _readLength += left; + return left; } @@ -676,34 +756,34 @@ size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ * Response Stream (You can print/write/printf to it, up to the contentLen bytes) * */ -AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize){ - _code = 200; - _contentLength = 0; - _contentType = contentType; - _content = new cbuf(bufferSize); +AsyncResponseStream::AsyncResponseStream(const String & contentType, size_t bufferSize) { + _code = 200; + _contentLength = 0; + _contentType = contentType; + _content = new cbuf(bufferSize); } -AsyncResponseStream::~AsyncResponseStream(){ - delete _content; +AsyncResponseStream::~AsyncResponseStream() { + delete _content; } -size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){ - return _content->read((char*)buf, maxLen); +size_t AsyncResponseStream::_fillBuffer(uint8_t * buf, size_t maxLen) { + return _content->read((char *)buf, maxLen); } -size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ - if(_started()) - return 0; +size_t AsyncResponseStream::write(const uint8_t * data, size_t len) { + if (_started()) + return 0; - if(len > _content->room()){ - size_t needed = len - _content->room(); - _content->resizeAdd(needed); - } - size_t written = _content->write((const char*)data, len); - _contentLength += written; - return written; + if (len > _content->room()) { + size_t needed = len - _content->room(); + _content->resizeAdd(needed); + } + size_t written = _content->write((const char *)data, len); + _contentLength += written; + return written; } -size_t AsyncResponseStream::write(uint8_t data){ - return write(&data, 1); +size_t AsyncResponseStream::write(uint8_t data) { + return write(&data, 1); } diff --git a/lib/framework/HttpEndpoint.h b/lib/framework/HttpEndpoint.h index 21ac7e610..0e0014049 100644 --- a/lib/framework/HttpEndpoint.h +++ b/lib/framework/HttpEndpoint.h @@ -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); } diff --git a/lib/framework/WiFiScanner.cpp b/lib/framework/WiFiScanner.cpp index b7ac8f718..708179686 100644 --- a/lib/framework/WiFiScanner.cpp +++ b/lib/framework/WiFiScanner.cpp @@ -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); } diff --git a/mock-api/server.js b/mock-api/server.js index 04ede86d1..c805f7bc6 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -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 diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index 0b6233f08..2a1932778 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -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()) { +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); } diff --git a/src/web/WebCustomizationService.h b/src/web/WebCustomizationService.h index 7e977959a..85435496c 100644 --- a/src/web/WebCustomizationService.h +++ b/src/web/WebCustomizationService.h @@ -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 { // 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 diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index 85e6a0353..77ec4566b 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -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); } diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp index 759d47a32..51d0537cc 100644 --- a/src/web/WebSettingsService.cpp +++ b/src/web/WebSettingsService.cpp @@ -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,29 +341,29 @@ void WebSettingsService::save() { } // build the json profile to send back -void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVariant & json) { - if (json.is()) { +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 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()); - root["led_gpio"] = data[0]; - root["dallas_gpio"] = data[1]; - root["rx_gpio"] = data[2]; - root["tx_gpio"] = data[3]; - root["pbutton_gpio"] = data[4]; - root["phy_type"] = data[5]; - root["eth_power"] = data[6]; - root["eth_phy_addr"] = data[7]; - root["eth_clock_mode"] = data[8]; + std::vector data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode + (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]; + root["tx_gpio"] = data[3]; + root["pbutton_gpio"] = data[4]; + root["phy_type"] = data[5]; + root["eth_power"] = data[6]; + root["eth_phy_addr"] = data[7]; + root["eth_clock_mode"] = data[8]; - response->setLength(); - request->send(response); - return; - } + response->setLength(); + request->send(response); + return; } AsyncWebServerResponse * response = request->beginResponse(200); diff --git a/src/web/WebSettingsService.h b/src/web/WebSettingsService.h index 61af4161b..0e892328e 100644 --- a/src/web/WebSettingsService.h +++ b/src/web/WebSettingsService.h @@ -122,11 +122,10 @@ class WebSettingsService : public StatefulService { void save(); private: - HttpEndpoint _httpEndpoint; - FSPersistence _fsPersistence; - AsyncCallbackJsonWebHandler _boardProfileHandler; + HttpEndpoint _httpEndpoint; + FSPersistence _fsPersistence; - void board_profile(AsyncWebServerRequest * request, JsonVariant & json); + void board_profile(AsyncWebServerRequest * request); void onUpdate(); };