Merge pull request #1874 from proddy/dev

web updates and scheduler
This commit is contained in:
Proddy
2024-07-20 15:49:55 +02:00
committed by GitHub
18 changed files with 184 additions and 84 deletions

View File

@@ -24,8 +24,8 @@
"dependencies": {
"@alova/adapter-xhr": "^1.0.6",
"@alova/scene-react": "^1.6.1",
"@emotion/react": "^11.12.0",
"@emotion/styled": "^11.12.0",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@mui/icons-material": "^5.16.4",
"@mui/material": "^5.16.4",
"@table-library/react-table-library": "4.1.7",

View File

@@ -31,13 +31,22 @@ import type { LogEntry, LogSettings } from 'types';
import { LogLevel } from 'types';
import { updateValueDirty, useRest } from 'utils';
const LogEntryLine = styled('div')(() => ({
color: '#bbbbbb',
fontFamily: 'monospace',
fontSize: '14px',
letterSpacing: 'normal',
whiteSpace: 'nowrap'
}));
const ButtonTextColors = {
[LogLevel.ERROR]: '#ff0000', // red
[LogLevel.WARNING]: '#ffcc00', // yellow
[LogLevel.NOTICE]: '#ffffff', // white
[LogLevel.INFO]: '#ffffff', // yellow
[LogLevel.DEBUG]: '#00ffff', // cyan
[LogLevel.TRACE]: '#00ffff' // cyan
};
const LogEntryLine = styled('div')(
({ details: { level } }: { details: { level: LogLevel } }) => ({
color: ButtonTextColors[level],
font: '14px monospace',
whiteSpace: 'nowrap'
})
);
const topOffset = () =>
document.getElementById('log-window')?.getBoundingClientRect().bottom || 0;
@@ -265,7 +274,7 @@ const SystemLog: FC = () => {
}}
>
{logEntries.map((e) => (
<LogEntryLine key={e.i}>
<LogEntryLine details={{ level: e.l }} key={e.i}>
<span>{e.t}</span>
<span>{paddedLevelLabel(e.l)}&nbsp;</span>
<span>{paddedIDLabel(e.i)} </span>

View File

@@ -64,8 +64,10 @@ const CustomEntitiesDialog = ({
}
}, [open, selectedItem]);
const close = () => {
onClose();
const handleClose = (event: {}, reason: 'backdropClick' | 'escapeKeyDown') => {
if (reason !== 'backdropClick') {
onClose();
}
};
const save = async () => {
@@ -90,7 +92,7 @@ const CustomEntitiesDialog = ({
};
return (
<Dialog sx={dialogStyle} open={open} onClose={close}>
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
<DialogTitle>
{creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()}&nbsp;{LL.ENTITY()}
</DialogTitle>
@@ -314,7 +316,7 @@ const CustomEntitiesDialog = ({
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={close}
onClick={onClose}
color="secondary"
>
{LL.CANCEL()}

View File

@@ -54,8 +54,10 @@ const CustomizationDialog = ({
}
}, [open, selectedItem]);
const close = () => {
onClose();
const handleClose = (event: {}, reason: 'backdropClick' | 'escapeKeyDown') => {
if (reason !== 'backdropClick') {
onClose();
}
};
const save = () => {
@@ -76,7 +78,7 @@ const CustomizationDialog = ({
};
return (
<Dialog sx={dialogStyle} open={open} onClose={close}>
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
<DialogTitle>{LL.EDIT() + ' ' + LL.ENTITY()}</DialogTitle>
<DialogContent dividers>
<Grid container direction="row">
@@ -115,7 +117,7 @@ const CustomizationDialog = ({
name="cn"
label={LL.NEW_NAME_OF(LL.ENTITY())}
value={editItem.cn}
autoFocus
// autoFocus
sx={{ width: '30ch' }}
onChange={updateFormValue}
/>
@@ -155,7 +157,7 @@ const CustomizationDialog = ({
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={close}
onClick={onClose}
color="secondary"
>
{LL.CANCEL()}

View File

@@ -120,7 +120,7 @@ const DevicesDialog = ({
label={LL.VALUE(0)}
value={editItem.v}
disabled={!writeable}
autoFocus
// autoFocus
sx={{ width: '30ch' }}
select
onChange={updateFormValue}
@@ -162,7 +162,7 @@ const DevicesDialog = ({
label={LL.VALUE(0)}
value={editItem.v}
disabled={!writeable}
autoFocus
// autoFocus
sx={{ width: '30ch' }}
multiline={editItem.u ? false : true}
onChange={updateFormValue}

View File

@@ -62,10 +62,6 @@ const SchedulerDialog = ({
}
}, [open, selectedItem]);
const close = () => {
onClose();
};
const save = async () => {
try {
setFieldErrors(undefined);
@@ -151,8 +147,14 @@ const SchedulerDialog = ({
</Typography>
);
const handleClose = (event: {}, reason: 'backdropClick' | 'escapeKeyDown') => {
if (reason !== 'backdropClick') {
onClose();
}
};
return (
<Dialog sx={dialogStyle} open={open} onClose={close}>
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
<DialogTitle>
{creating ? LL.ADD(1) + ' ' + LL.NEW(0) : LL.EDIT()}&nbsp;
{LL.SCHEDULE(1)}
@@ -294,6 +296,7 @@ const SchedulerDialog = ({
<TextField
name="time"
label={isCondition ? 'Condition' : 'On Change Value'}
multiline
fullWidth
value={
editItem.time == '00:00' ? (editItem.time = '') : editItem.time
@@ -327,6 +330,7 @@ const SchedulerDialog = ({
fieldErrors={fieldErrors}
name="cmd"
label={LL.COMMAND(0)}
multiline
fullWidth
value={editItem.cmd}
margin="normal"
@@ -369,7 +373,7 @@ const SchedulerDialog = ({
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={close}
onClick={onClose}
color="secondary"
>
{LL.CANCEL()}

View File

@@ -57,8 +57,10 @@ const SensorsAnalogDialog = ({
}
}, [open, selectedItem]);
const close = () => {
onClose();
const handleClose = (event: {}, reason: 'backdropClick' | 'escapeKeyDown') => {
if (reason !== 'backdropClick') {
onClose();
}
};
const save = async () => {
@@ -77,7 +79,7 @@ const SensorsAnalogDialog = ({
};
return (
<Dialog sx={dialogStyle} open={open} onClose={close}>
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
<DialogTitle>
{creating ? LL.ADD(1) + ' ' + LL.NEW(0) : LL.EDIT()}&nbsp;
{LL.ANALOG_SENSOR(0)}
@@ -318,7 +320,7 @@ const SensorsAnalogDialog = ({
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={close}
onClick={onClose}
color="secondary"
>
{LL.CANCEL()}

View File

@@ -52,8 +52,10 @@ const SensorsTemperatureDialog = ({
}
}, [open, selectedItem]);
const close = () => {
onClose();
const handleClose = (event: {}, reason: 'backdropClick' | 'escapeKeyDown') => {
if (reason !== 'backdropClick') {
onClose();
}
};
const save = async () => {
@@ -67,7 +69,7 @@ const SensorsTemperatureDialog = ({
};
return (
<Dialog sx={dialogStyle} open={open} onClose={close}>
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
<DialogTitle>
{LL.EDIT()}&nbsp;{LL.TEMP_SENSOR()}
</DialogTitle>
@@ -84,7 +86,7 @@ const SensorsTemperatureDialog = ({
name="n"
label={LL.NAME(0)}
value={editItem.n}
autoFocus
// autoFocus
sx={{ width: '30ch' }}
onChange={updateFormValue}
/>
@@ -110,7 +112,7 @@ const SensorsTemperatureDialog = ({
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={close}
onClick={onClose}
color="secondary"
>
{LL.CANCEL()}

View File

@@ -308,7 +308,7 @@ export const schedulerItemValidation = (
{
type: 'string',
pattern: /^[a-zA-Z0-9_\\.]{0,19}$/,
message: "Must be <20 characters: alpha numeric, '_' or '.'"
message: "Must be <20 characters: alphanumeric, '_' or '.'"
},
...[uniqueNameValidator(schedule, scheduleItem.o_name)]
],
@@ -317,8 +317,8 @@ export const schedulerItemValidation = (
{
type: 'string',
min: 1,
max: 64,
message: 'Command must be 1-64 characters'
max: 300,
message: 'Command must be 1-300 characters'
}
]
});
@@ -350,7 +350,7 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte
{
type: 'string',
pattern: /^[a-zA-Z0-9_\\.]{1,19}$/,
message: "Must be <20 characters: alpha numeric, '_' or '.'"
message: "Must be <20 characters: alphanumeric, '_' or '.'"
},
...[uniqueCustomNameValidator(entity, entityItem.o_name)]
],
@@ -404,7 +404,7 @@ export const temperatureSensorItemValidation = (sensors: TemperatureSensor[]) =>
{
type: 'string',
pattern: /^[a-zA-Z0-9_\\.]{0,19}$/,
message: "Must be <20 characters: alpha numeric, '_' or '.'"
message: "Must be <20 characters: alphanumeric, '_' or '.'"
},
...[uniqueTemperatureNameValidator(sensors)]
]
@@ -444,7 +444,7 @@ export const analogSensorItemValidation = (
{
type: 'string',
pattern: /^[a-zA-Z0-9_\\.]{0,19}$/,
message: "Must be <20 characters: alpha numeric, '_' or '.'"
message: "Must be <20 characters: alphanumeric, '_' or '.'"
},
...[uniqueAnalogNameValidator(sensors)]
],

View File

@@ -35,7 +35,7 @@ export const createUserValidator = (users: UserType[], creating: boolean) =>
{
type: 'string',
pattern: /^[a-zA-Z0-9_\\.]{1,24}$/,
message: "Must be 1-24 characters: alpha numeric, '_' or '.'"
message: "Must be 1-24 characters: alphanumeric, '_' or '.'"
},
...(creating ? [createUniqueUsernameValidator(users)] : [])
],

View File

@@ -697,16 +697,16 @@ __metadata:
languageName: node
linkType: hard
"@emotion/cache@npm:^11.12.0":
version: 11.12.0
resolution: "@emotion/cache@npm:11.12.0"
"@emotion/cache@npm:^11.13.0":
version: 11.13.0
resolution: "@emotion/cache@npm:11.13.0"
dependencies:
"@emotion/memoize": "npm:^0.9.0"
"@emotion/sheet": "npm:^1.3.0"
"@emotion/utils": "npm:^1.3.0"
"@emotion/sheet": "npm:^1.4.0"
"@emotion/utils": "npm:^1.4.0"
"@emotion/weak-memoize": "npm:^0.4.0"
stylis: "npm:4.2.0"
checksum: 10c0/b6909597a41dfc1d07ada1b8a719697d2c0155a8f71c14079b7a089e281093a4c0710933592c864185e9176621ce6eff96e5ce2eb6774ba7959235232892d3c8
checksum: 10c0/24cfba12a3494fb49b1ce522cba7ffdca45794a412e8a0da7658c5cb70b4182a852078116e4f66252f167ccd58e5876cfc4f9d1bfcb2618b1887febf835f816a
languageName: node
linkType: hard
@@ -740,16 +740,16 @@ __metadata:
languageName: node
linkType: hard
"@emotion/react@npm:^11.12.0":
version: 11.12.0
resolution: "@emotion/react@npm:11.12.0"
"@emotion/react@npm:^11.13.0":
version: 11.13.0
resolution: "@emotion/react@npm:11.13.0"
dependencies:
"@babel/runtime": "npm:^7.18.3"
"@emotion/babel-plugin": "npm:^11.12.0"
"@emotion/cache": "npm:^11.12.0"
"@emotion/serialize": "npm:^1.2.0"
"@emotion/use-insertion-effect-with-fallbacks": "npm:^1.0.1"
"@emotion/utils": "npm:^1.3.0"
"@emotion/cache": "npm:^11.13.0"
"@emotion/serialize": "npm:^1.3.0"
"@emotion/use-insertion-effect-with-fallbacks": "npm:^1.1.0"
"@emotion/utils": "npm:^1.4.0"
"@emotion/weak-memoize": "npm:^0.4.0"
hoist-non-react-statics: "npm:^3.3.1"
peerDependencies:
@@ -757,7 +757,7 @@ __metadata:
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/a2035e0a5788ec6748b595ae898732aa845515dea52657fcf2661288d01c7da16133588fdaa4288c05175dc4c39938546af265bb039065efca20a729c486da9b
checksum: 10c0/28ee0ba6818ccf2726b31da0ecf3a6ac091983c8e03b3f5d6d2eb02165e3b3d1b95c267fccd08bff2f9769e0ff7361d791810583b858a9dd788de7cf82f6667d
languageName: node
linkType: hard
@@ -774,6 +774,19 @@ __metadata:
languageName: node
linkType: hard
"@emotion/serialize@npm:^1.3.0":
version: 1.3.0
resolution: "@emotion/serialize@npm:1.3.0"
dependencies:
"@emotion/hash": "npm:^0.9.2"
"@emotion/memoize": "npm:^0.9.0"
"@emotion/unitless": "npm:^0.9.0"
"@emotion/utils": "npm:^1.4.0"
csstype: "npm:^3.0.2"
checksum: 10c0/dd3f9041b05e79664c27188d8aad0cf726baee6da934ac31fd96c03691ce2a2e222669252c8cd623f2b0e488c7f8cfe384798153f36685d48b98340e63655813
languageName: node
linkType: hard
"@emotion/sheet@npm:^1.2.2":
version: 1.2.2
resolution: "@emotion/sheet@npm:1.2.2"
@@ -781,30 +794,30 @@ __metadata:
languageName: node
linkType: hard
"@emotion/sheet@npm:^1.3.0":
version: 1.3.0
resolution: "@emotion/sheet@npm:1.3.0"
checksum: 10c0/99ce618d426e0646f58591200619cf3cd6f452e829c0b0339f66b3e4bfd7d51773f49d0200e14334861bee9db1dfb5d46328b20eafbfdd28f857584688fad8f4
"@emotion/sheet@npm:^1.4.0":
version: 1.4.0
resolution: "@emotion/sheet@npm:1.4.0"
checksum: 10c0/3ca72d1650a07d2fbb7e382761b130b4a887dcd04e6574b2d51ce578791240150d7072a9bcb4161933abbcd1e38b243a6fb4464a7fe991d700c17aa66bb5acc7
languageName: node
linkType: hard
"@emotion/styled@npm:^11.12.0":
version: 11.12.0
resolution: "@emotion/styled@npm:11.12.0"
"@emotion/styled@npm:^11.13.0":
version: 11.13.0
resolution: "@emotion/styled@npm:11.13.0"
dependencies:
"@babel/runtime": "npm:^7.18.3"
"@emotion/babel-plugin": "npm:^11.12.0"
"@emotion/is-prop-valid": "npm:^1.3.0"
"@emotion/serialize": "npm:^1.2.0"
"@emotion/use-insertion-effect-with-fallbacks": "npm:^1.0.1"
"@emotion/utils": "npm:^1.3.0"
"@emotion/serialize": "npm:^1.3.0"
"@emotion/use-insertion-effect-with-fallbacks": "npm:^1.1.0"
"@emotion/utils": "npm:^1.4.0"
peerDependencies:
"@emotion/react": ^11.0.0-rc.0
react: ">=16.8.0"
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/f87466ade3c872c6452091969f2784b97dcec821f812f641088e1289ad039ddea7165e8b1221895911f199469d07a9ffc786434e496ae3cb62156f72a5686ad7
checksum: 10c0/5e2cc85c8a2f6e7bd012731cf0b6da3aef5906225e87e8d4a5c19da50572e24d9aaf92615aa36aa863f0fe6b62a121033356e1cad62617c48bfdaa2c3cf0d8a4
languageName: node
linkType: hard
@@ -815,12 +828,12 @@ __metadata:
languageName: node
linkType: hard
"@emotion/use-insertion-effect-with-fallbacks@npm:^1.0.1":
version: 1.0.1
resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.0.1"
"@emotion/use-insertion-effect-with-fallbacks@npm:^1.1.0":
version: 1.1.0
resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.1.0"
peerDependencies:
react: ">=16.8.0"
checksum: 10c0/a15b2167940e3a908160687b73fc4fcd81e59ab45136b6967f02c7c419d9a149acd22a416b325c389642d4f1c3d33cf4196cad6b618128b55b7c74f6807a240b
checksum: 10c0/a883480f3a7139fb4a43e71d3114ca57e2b7ae5ff204e05cd9e59251a113773b8f64eb75d3997726250aca85eb73447638c8f51930734bdd16b96762b65e58c3
languageName: node
linkType: hard
@@ -838,6 +851,13 @@ __metadata:
languageName: node
linkType: hard
"@emotion/utils@npm:^1.4.0":
version: 1.4.0
resolution: "@emotion/utils@npm:1.4.0"
checksum: 10c0/b2ae698d6e935f4961a8349286b5b0a6117a16e179459cbf9c8d97d5daa7d96c99876b950f09b1a793d6b295713b2c8f89544bd8c3f26b8e4db60a218a0d4c42
languageName: node
linkType: hard
"@emotion/weak-memoize@npm:^0.3.1":
version: 0.3.1
resolution: "@emotion/weak-memoize@npm:0.3.1"
@@ -2054,8 +2074,8 @@ __metadata:
"@alova/adapter-xhr": "npm:^1.0.6"
"@alova/scene-react": "npm:^1.6.1"
"@babel/core": "npm:^7.24.9"
"@emotion/react": "npm:^11.12.0"
"@emotion/styled": "npm:^11.12.0"
"@emotion/react": "npm:^11.13.0"
"@emotion/styled": "npm:^11.13.0"
"@eslint/js": "npm:^9.7.0"
"@mui/icons-material": "npm:^5.16.4"
"@mui/material": "npm:^5.16.4"

View File

@@ -0,0 +1,27 @@
#ifndef HTTPClient_H_
#define HTTPClient_H_
#include "WString.h"
class HTTPClient {
public:
// HTTPClient();
// ~HTTPClient();
bool begin(String url) {
return true;
};
void end(void) {};
int GET() {
return 200;
};
int POST(String payload) {
return 200;
};
void addHeader(const String & name, const String & value, bool first = false, bool replace = true) {};
String getString(void) {
return "Hello, World!";
};
};
#endif /* HTTPClient_H_ */

View File

@@ -69,4 +69,4 @@ size_t strlcat(char * dst, const char * src, size_t siz) {
return (dlen + (s - src)); /* count does not include NUL */
}
#endif
#endif

View File

@@ -53,6 +53,14 @@ class String {
return 1;
}
int len() const {
return _str.size();
}
bool startsWith(const char * prefix) const {
return _str.find(prefix) == 0;
}
private:
std::string _str;
};

View File

@@ -47,7 +47,7 @@ rest_server.get(ES_LOG_ENDPOINT, (_req, res) => {
const interval = setInterval(() => {
const data = {
t: new Date().toISOString(),
l: 7, // debug
l: (3 + (count % 6)),
i: count,
n: 'system',
m: 'message #' + count++
@@ -55,6 +55,8 @@ rest_server.get(ES_LOG_ENDPOINT, (_req, res) => {
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, INTERVAL);
// if client closes connection
res.on('close', () => {
console.log('Closing ES connection');

View File

@@ -985,7 +985,7 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
// some devices store the protocol type (HT3, Buderus) in the last byte
uint8_t brand;
if (telegram->message_length >= 10) {
brand = EMSdevice::decode_brand(telegram->message_data[9]);
brand = EMSdevice::decode_brand(telegram->message_data[9]); // TODO should be offset + 9?
} else {
brand = EMSdevice::Brand::NO_BRAND; // unknown
}

View File

@@ -342,22 +342,44 @@ bool WebSchedulerService::command(const char * name, const char * cmd, const cha
HTTPClient http;
String url = doc["url"];
if (http.begin(url)) {
// It's an HTTP call
// add any given headers
for (JsonPair p : doc["header"].as<JsonObject>()) {
http.addHeader(p.key().c_str(), p.value().as<String>().c_str());
}
String value = doc["value"] | "";
String value = doc["value"] | data; // extract value if its in the command, or take the data
String method = doc["method"] | "GET"; // default GET
// if there is data, force a POST
if (value.length()) {
if (value.startsWith("{")) {
http.addHeader("Content-Type", "application/json"); // auto-set to JSON
}
httpResult = http.POST(value);
} else if (data && data[0] != '\0') { // post
httpResult = http.POST(String(data));
} else {
httpResult = http.GET();
// no value, but check if it still a POST request
if (method == "POST") {
httpResult = http.POST(value);
} else {
httpResult = http.GET(); // normal GET
}
}
http.end();
// check HTTP return code
if (httpResult != 200) {
char error[100];
snprintf(error, sizeof(error), "Schedule %s: URL command failed with http code %d", name, httpResult);
emsesp::EMSESP::logger().warning(error);
return false;
}
return true;
}
}
return httpResult > 0;
}
JsonDocument doc_input;
JsonObject input = doc_input.to<JsonObject>();
if (strlen(data)) { // empty data queries a value
@@ -562,6 +584,8 @@ void WebSchedulerService::test() {
// (14 - 40) * 2.8 + 5 = -67.8
test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5";
command("test", test_cmd.c_str(), compute(test_value).c_str());
// TODO add some HTTP/URI tests
}
#endif

View File

@@ -583,13 +583,11 @@ std::string calculate(const std::string & expr) {
}
// concatenate all elements in stack to a single string, separated by spaces and return
// experimental - for MDvP to check
std::string result = "";
for (const auto & s : stack) {
result += s;
}
return result;
// return stack.back();
}