Merge pull request #1855 from proddy/dev

Implements Feature: message notification #1854
This commit is contained in:
Proddy
2024-07-14 23:46:16 +02:00
committed by GitHub
48 changed files with 5993 additions and 5558 deletions

View File

@@ -1,29 +1,29 @@
---
name: Problem Report
name: Problem Report/Change Request
about: Create a Report to help us improve
---
<!-- Thanks for reporting a problem for this project. READ THIS FIRST:
<!-- Thanks for reporting an issue for this project. READ THIS FIRST:
Please DO NOT OPEN AN ISSUE if your EMS-ESP version is not the latest from the dev branch, please update your device before submitting your issue. Your problem might already be solved. The latest precompiled binaries of EMS-ESP can be downloaded from https://github.com/emsesp/EMS-ESP32/releases/tag/latest
Please DO NOT OPEN AN ISSUE if your EMS-ESP version is not the latest from the dev branch, please update your device before submitting your issue. Your issue might already be solved. The latest precompiled binaries of EMS-ESP can be downloaded from https://github.com/emsesp/EMS-ESP32/releases/tag/latest
Please take a few minutes to complete the requested information below.
-->
### PROBLEM DESCRIPTION
### DESCRIPTION
_A clear and concise description of what the problem is._
_A clear and concise description of what the problem is or the change requested._
### REQUESTED INFORMATION
_Make sure your have performed every step and checked the applicable boxes before submitting your issue. Thank you!_
- [ ] Searched the problem in [issues](https://github.com/emsesp/EMS-ESP32/issues)
- [ ] Searched the problem in [discussions](https://github.com/emsesp/EMS-ESP32/discussions)
- [ ] Searched the problem in the [docs](https://emsesp.github.io/docs/Troubleshooting/)
- [ ] Searched the problem in the [chat](https://discord.gg/3J3GgnzpyT)
- [ ] Provide the output of http://ems-esp.local/api/system :
- [ ] Searched the issue in [issues](https://github.com/emsesp/EMS-ESP32/issues)
- [ ] Searched the issue in [discussions](https://github.com/emsesp/EMS-ESP32/discussions)
- [ ] Searched the issue in the [docs](https://emsesp.github.io/docs/Troubleshooting/)
- [ ] Searched the issue in the [chat](https://discord.gg/3J3GgnzpyT)
- [ ] Provide the output of <http://ems-esp.local/api/system> :
```lua
System information output here:
@@ -41,10 +41,10 @@ _A clear and concise description of what you expected to happen._
### SCREENSHOTS
_If applicable, add screenshots to help explain your problem._
_If applicable, add screenshots to help explain your issue._
### ADDITIONAL CONTEXT
_Add any other context about the problem here._
_Add any other context about the issue here._
**(Please, remember to close the issue when the problem has been addressed)**
**(Please, remember to close the issue when it has been addressed)**

View File

@@ -7,6 +7,7 @@
- new device WATER shows dhw entities from MM100 and SM100 in dhw setting
- renamed WWC to DHW, always create DHW nests/topics, remove ww prefix from mqtt names [#1634](https://github.com/emsesp/EMS-ESP32/issues/1634)
- change temperaturesensor id to underscore
- system/info API command has the word 'Info' removed from the object name for each section (E.g. 'Network Info' is now just 'Network')
## Added
@@ -28,6 +29,7 @@
- added extra pump characteristics (mode and pressure for EMS+) by @SLTKA [#1802](https://github.com/emsesp/EMS-ESP32/pull/1802)
- allow device name to be customized [#1174](https://github.com/emsesp/EMS-ESP32/issues/1174)
- Modbus support by @mheyse [#1744](https://github.com/emsesp/EMS-ESP32/issues/1744)
- System Message command [#1854](https://github.com/emsesp/EMS-ESP32/issues/1854)
## Fixed

File diff suppressed because it is too large Load Diff

View File

@@ -53,6 +53,7 @@ telegram_type_id,name,is_fetched,is_cmd
0xA3,RCOutdoorTemp, ,cmd
0xA5,IBASettings,fetched,cmd
0xA7,RC30Set, ,cmd
0xA9,RC30Vacation,fetched,cmd
0xAA,MMConfigMessage,fetched,cmd
0xAB,MMStatusMessage, ,cmd
0xAC,MMSetMessage, ,cmd
@@ -91,6 +92,7 @@ telegram_type_id,name,is_fetched,is_cmd
0x023E,PVSettings,fetched,cmd
0x0240,RC300Settings,fetched,cmd
0x0267,RC300Floordry, ,cmd
0x0269,RC300Holiday1,fetched,cmd
0x0291,HPMode,fetched,cmd
0x0292,HPMode,fetched,cmd
0x0293,HPMode,fetched,cmd
@@ -103,9 +105,9 @@ telegram_type_id,name,is_fetched,is_cmd
0x02A0,RC300Curves, ,cmd
0x02A1,RC300Curves, ,cmd
0x02A2,RC300Curves, ,cmd
0x02A5,RC300Monitor, ,cmd
0x02A5,CRFMonitor, ,cmd
0x02A6,RC300Monitor, ,cmd
0x02A7,CRFMonitor, ,cmd
0x02A7,RC300Monitor, ,cmd
0x02A8,RC300Monitor, ,cmd
0x02A9,RC300Monitor, ,cmd
0x02AA,RC300Monitor, ,cmd
@@ -127,14 +129,14 @@ telegram_type_id,name,is_fetched,is_cmd
0x02BE,RC300Set, ,cmd
0x02BF,RC300Set, ,cmd
0x02C0,RC300Set, ,cmd
0x02CC,HPPressure,fetched,cmd
0x02CD,MMPLUSSetMessage_HC,fetched,cmd
0x02CC,RC300Set2, ,cmd
0x02CD,MMPLUSConfigMessage,fetched,cmd
0x02CE,RC300Set2, ,cmd
0x02D0,RC300Set2, ,cmd
0x02D2,RC300Set2, ,cmd
0x02D5,MMPLUSSetMessage_HC,fetched,cmd
0x02D7,MMPLUSStatusMessage_HC, ,cmd
0x02DF,MMPLUSStatusMessage_HC, ,cmd
0x02D5,MMPLUSConfigMessage,fetched,cmd
0x02D7,MMPLUSStatusMessage, ,cmd
0x02DF,MMPLUSStatusMessage, ,cmd
0x02F5,RC300WWmode,fetched,cmd
0x02F6,RC300WW2mode,fetched,cmd
0x031B,RC300WWtemp,fetched,cmd
1 telegram_type_id name is_fetched is_cmd
53 0xA3 RCOutdoorTemp cmd
54 0xA5 IBASettings fetched cmd
55 0xA7 RC30Set cmd
56 0xA9 RC30Vacation fetched cmd
57 0xAA MMConfigMessage fetched cmd
58 0xAB MMStatusMessage cmd
59 0xAC MMSetMessage cmd
92 0x023E PVSettings fetched cmd
93 0x0240 RC300Settings fetched cmd
94 0x0267 RC300Floordry cmd
95 0x0269 RC300Holiday1 fetched cmd
96 0x0291 HPMode fetched cmd
97 0x0292 HPMode fetched cmd
98 0x0293 HPMode fetched cmd
105 0x02A0 RC300Curves cmd
106 0x02A1 RC300Curves cmd
107 0x02A2 RC300Curves cmd
108 0x02A5 RC300Monitor CRFMonitor cmd
109 0x02A6 RC300Monitor cmd
110 0x02A7 CRFMonitor RC300Monitor cmd
111 0x02A8 RC300Monitor cmd
112 0x02A9 RC300Monitor cmd
113 0x02AA RC300Monitor cmd
129 0x02BE RC300Set cmd
130 0x02BF RC300Set cmd
131 0x02C0 RC300Set cmd
132 0x02CC HPPressure RC300Set2 fetched cmd
133 0x02CD MMPLUSSetMessage_HC MMPLUSConfigMessage fetched cmd
134 0x02CE RC300Set2 cmd
135 0x02D0 RC300Set2 cmd
136 0x02D2 RC300Set2 cmd
137 0x02D5 MMPLUSSetMessage_HC MMPLUSConfigMessage fetched cmd
138 0x02D7 MMPLUSStatusMessage_HC MMPLUSStatusMessage cmd
139 0x02DF MMPLUSStatusMessage_HC MMPLUSStatusMessage cmd
140 0x02F5 RC300WWmode fetched cmd
141 0x02F6 RC300WW2mode fetched cmd
142 0x031B RC300WWtemp fetched cmd

View File

@@ -16,7 +16,7 @@ export default tseslint.config(
}
},
{
ignores: ['dist/*', '*.js', '**/*.cjs', '**/unpack.ts', 'i18*.ts']
ignores: ['dist/*', '*.js', '**/*.cjs', '**/unpack.ts', 'i18n*.*']
},
{
rules: {

View File

@@ -26,15 +26,15 @@
"@alova/scene-react": "^1.6.1",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.16.0",
"@mui/material": "^5.16.0",
"@mui/icons-material": "^5.16.1",
"@mui/material": "^5.16.1",
"@table-library/react-table-library": "4.1.7",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.14.10",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-router-dom": "^5.3.3",
"alova": "^2.21.3",
"alova": "^2.21.4",
"async-validator": "^4.2.5",
"history": "^5.3.0",
"jwt-decode": "^4.0.0",
@@ -50,19 +50,19 @@
"typescript": "^5.5.3"
},
"devDependencies": {
"@babel/core": "^7.24.7",
"@eslint/js": "^9.6.0",
"@babel/core": "^7.24.8",
"@eslint/js": "^9.7.0",
"@preact/compat": "^17.1.2",
"@preact/preset-vite": "^2.8.3",
"@preact/preset-vite": "^2.9.0",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/babel__core": "^7",
"concurrently": "^8.2.2",
"eslint": "^9.6.0",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
"preact": "^10.22.1",
"prettier": "^3.3.2",
"prettier": "^3.3.3",
"rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.31.1",
"terser": "^5.31.2",
"typescript-eslint": "8.0.0-alpha.10",
"vite": "^5.3.3",
"vite-plugin-imagemin": "^0.6.1",

View File

@@ -54,6 +54,8 @@ const levelLabel = (level: LogLevel) => {
return 'NOTICE';
case LogLevel.INFO:
return 'INFO';
case LogLevel.DEBUG:
return 'DEBUG';
case LogLevel.TRACE:
return 'TRACE';
default:
@@ -175,7 +177,7 @@ const SystemLog: FC = () => {
justifyContent="flex-start"
alignItems="center"
>
<Grid item xs={2}>
<Grid item xs={4}>
<TextField
name="level"
label={LL.LOG_LEVEL()}
@@ -194,7 +196,7 @@ const SystemLog: FC = () => {
<MenuItem value={9}>ALL</MenuItem>
</TextField>
</Grid>
<Grid item xs={2}>
<Grid item xs={4}>
<TextField
name="max_messages"
label={LL.BUFFER_SIZE()}
@@ -211,7 +213,7 @@ const SystemLog: FC = () => {
<MenuItem value={100}>100</MenuItem>
</TextField>
</Grid>
<Grid item>
<Grid item xs={2}>
<BlockFormControlLabel
control={
<Checkbox
@@ -226,8 +228,7 @@ const SystemLog: FC = () => {
<Box
sx={{
'& button, & a, & .MuiCard-root': {
mt: 3,
mx: 0.6
ml: 3
}
}}
>
@@ -266,8 +267,7 @@ const SystemLog: FC = () => {
{logEntries.map((e) => (
<LogEntryLine key={e.i}>
<span>{e.t}</span>
{data.compact && <span>{paddedLevelLabel(e.l)} </span>}
{!data.compact && <span>{paddedLevelLabel(e.l)}&nbsp;</span>}
<span>{paddedLevelLabel(e.l)}&nbsp;</span>
<span>{paddedIDLabel(e.i)} </span>
<span>{paddedNameLabel(e.n)} </span>
<span>{e.m}</span>

View File

@@ -2,7 +2,7 @@ import { type FC, useState } from 'react';
import { toast } from 'react-toastify';
import DownloadIcon from '@mui/icons-material/GetApp';
import { Box, Button, Link, Typography } from '@mui/material';
import { Box, Button, Divider, Link, Typography } from '@mui/material';
import * as SystemApi from 'api/system';
@@ -195,6 +195,8 @@ const UploadDownload: FC = () => {
<Box p={2} border="2px solid grey" borderRadius={2}>
{LL.VERSION_ON() + ' '}
<b>{data.emsesp_version}</b>&nbsp;({data.esp_platform})
{data.model.length > 0 && <p>Gateway Hardware:&nbsp;{data.model}</p>}
<Divider />
{latestVersion && (
<Box mt={2}>
{LL.THE_LATEST()}&nbsp;{LL.OFFICIAL()}&nbsp;{LL.RELEASE_IS()}

View File

@@ -244,7 +244,7 @@ const Scheduler: FC = () => {
data={{
nodes: schedule
.filter((si) => !si.deleted)
.sort((a, b) => a.name.localeCompare(b.name))
.sort((a, b) => a.flags - b.flags)
}}
theme={schedule_theme}
layout={{ custom: true }}

View File

@@ -344,7 +344,7 @@ const SchedulerDialog = ({
<ValidatedTextField
fieldErrors={fieldErrors}
name="name"
label={LL.NAME(0)}
label={LL.NAME(0) + ' (' + LL.OPTIONAL() + ')'}
value={editItem.name}
fullWidth
margin="normal"

View File

@@ -287,6 +287,7 @@ export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) =
callback: (error?: string) => void
) {
if (
name !== '' &&
(o_name === undefined || o_name !== name) &&
schedule.find((si) => si.name === name)
) {
@@ -303,10 +304,9 @@ export const schedulerItemValidation = (
) =>
new Schema({
name: [
{ required: true, message: 'Name is required' },
{
type: 'string',
pattern: /^[a-zA-Z0-9_\\.]{1,15}$/,
pattern: /^[a-zA-Z0-9_\\.]{0,15}$/,
message: "Must be <15 characters: alpha numeric, '_' or '.'"
},
...[uniqueNameValidator(schedule, scheduleItem.o_name)]
@@ -392,8 +392,8 @@ export const temperatureSensorItemValidation = () =>
n: [
{
type: 'string',
pattern: /^[a-zA-Z0-9_\\.]{0,15}$/,
message: "Must be <15 characters: alpha numeric, '_' or '.'"
pattern: /^[a-zA-Z0-9_\\.]{0,17}$/,
message: "Must be <18 characters: alpha numeric, '_' or '.'"
}
]
});

View File

@@ -24,6 +24,7 @@ export interface ESPSystemStatus {
psram_size?: number;
free_psram?: number;
has_loader: boolean;
model: string;
}
export interface SystemStatus {

View File

@@ -56,10 +56,10 @@ __metadata:
languageName: node
linkType: hard
"@babel/compat-data@npm:^7.24.7":
version: 7.24.7
resolution: "@babel/compat-data@npm:7.24.7"
checksum: 10c0/dcd93a5632b04536498fbe2be5af1057f635fd7f7090483d8e797878559037e5130b26862ceb359acbae93ed27e076d395ddb4663db6b28a665756ffd02d324f
"@babel/compat-data@npm:^7.24.8":
version: 7.24.8
resolution: "@babel/compat-data@npm:7.24.8"
checksum: 10c0/7f465e9d8e44c5b516eeb3001362a3cd9a6df51dd90d3ac9868e1e7fa631ac57fc781cec6700110d4f555ba37fe59c4a71927b445106fe0062e79e79ffe11091
languageName: node
linkType: hard
@@ -86,26 +86,26 @@ __metadata:
languageName: node
linkType: hard
"@babel/core@npm:^7.24.7":
version: 7.24.7
resolution: "@babel/core@npm:7.24.7"
"@babel/core@npm:^7.24.8":
version: 7.24.8
resolution: "@babel/core@npm:7.24.8"
dependencies:
"@ampproject/remapping": "npm:^2.2.0"
"@babel/code-frame": "npm:^7.24.7"
"@babel/generator": "npm:^7.24.7"
"@babel/helper-compilation-targets": "npm:^7.24.7"
"@babel/helper-module-transforms": "npm:^7.24.7"
"@babel/helpers": "npm:^7.24.7"
"@babel/parser": "npm:^7.24.7"
"@babel/generator": "npm:^7.24.8"
"@babel/helper-compilation-targets": "npm:^7.24.8"
"@babel/helper-module-transforms": "npm:^7.24.8"
"@babel/helpers": "npm:^7.24.8"
"@babel/parser": "npm:^7.24.8"
"@babel/template": "npm:^7.24.7"
"@babel/traverse": "npm:^7.24.7"
"@babel/types": "npm:^7.24.7"
"@babel/traverse": "npm:^7.24.8"
"@babel/types": "npm:^7.24.8"
convert-source-map: "npm:^2.0.0"
debug: "npm:^4.1.0"
gensync: "npm:^1.0.0-beta.2"
json5: "npm:^2.2.3"
semver: "npm:^6.3.1"
checksum: 10c0/4004ba454d3c20a46ea66264e06c15b82e9f6bdc35f88819907d24620da70dbf896abac1cb4cc4b6bb8642969e45f4d808497c9054a1388a386cf8c12e9b9e0d
checksum: 10c0/5e21b40cc69746deda3fe3d6540351d9cb0d1ad5aea055b7c319db26071ff5789fd9580d1aa47b114f07631e8e2109f4e71696ca11d7c7e60d157767022c1bd2
languageName: node
linkType: hard
@@ -144,6 +144,18 @@ __metadata:
languageName: node
linkType: hard
"@babel/generator@npm:^7.24.8":
version: 7.24.8
resolution: "@babel/generator@npm:7.24.8"
dependencies:
"@babel/types": "npm:^7.24.8"
"@jridgewell/gen-mapping": "npm:^0.3.5"
"@jridgewell/trace-mapping": "npm:^0.3.25"
jsesc: "npm:^2.5.1"
checksum: 10c0/e8a278e75a895f13a7b17dd79abe1e894fe82a5ed3abb127c33c14c66773d69993762521c094c6c364723f8f7375683b0d4a96097781175a29407baedf67b769
languageName: node
linkType: hard
"@babel/helper-annotate-as-pure@npm:^7.22.5":
version: 7.22.5
resolution: "@babel/helper-annotate-as-pure@npm:7.22.5"
@@ -166,16 +178,16 @@ __metadata:
languageName: node
linkType: hard
"@babel/helper-compilation-targets@npm:^7.24.7":
version: 7.24.7
resolution: "@babel/helper-compilation-targets@npm:7.24.7"
"@babel/helper-compilation-targets@npm:^7.24.8":
version: 7.24.8
resolution: "@babel/helper-compilation-targets@npm:7.24.8"
dependencies:
"@babel/compat-data": "npm:^7.24.7"
"@babel/helper-validator-option": "npm:^7.24.7"
browserslist: "npm:^4.22.2"
"@babel/compat-data": "npm:^7.24.8"
"@babel/helper-validator-option": "npm:^7.24.8"
browserslist: "npm:^4.23.1"
lru-cache: "npm:^5.1.1"
semver: "npm:^6.3.1"
checksum: 10c0/1d580a9bcacefe65e6bf02ba1dafd7ab278269fef45b5e281d8354d95c53031e019890464e7f9351898c01502dd2e633184eb0bcda49ed2ecd538675ce310f51
checksum: 10c0/2885c44ef6aaf82b7e4352b30089bb09fbe08ed5ec24eb452c2bdc3c021e2a65ab412f74b3d67ec1398da0356c730b33a2ceca1d67d34c85080d31ca6efa9aec
languageName: node
linkType: hard
@@ -267,9 +279,9 @@ __metadata:
languageName: node
linkType: hard
"@babel/helper-module-transforms@npm:^7.24.7":
version: 7.24.7
resolution: "@babel/helper-module-transforms@npm:7.24.7"
"@babel/helper-module-transforms@npm:^7.24.8":
version: 7.24.8
resolution: "@babel/helper-module-transforms@npm:7.24.8"
dependencies:
"@babel/helper-environment-visitor": "npm:^7.24.7"
"@babel/helper-module-imports": "npm:^7.24.7"
@@ -278,7 +290,7 @@ __metadata:
"@babel/helper-validator-identifier": "npm:^7.24.7"
peerDependencies:
"@babel/core": ^7.0.0
checksum: 10c0/4f311755fcc3b4cbdb689386309cdb349cf0575a938f0b9ab5d678e1a81bbb265aa34ad93174838245f2ac7ff6d5ddbd0104638a75e4e961958ed514355687b6
checksum: 10c0/b76496d5045af55be9de60e59e65e56a43033f01ccc746b26b7af911c358668c206b688ce70a23ab31ec04f9728f3a38e8d01073c85244115ab62f271a7fa3d1
languageName: node
linkType: hard
@@ -340,6 +352,13 @@ __metadata:
languageName: node
linkType: hard
"@babel/helper-string-parser@npm:^7.24.8":
version: 7.24.8
resolution: "@babel/helper-string-parser@npm:7.24.8"
checksum: 10c0/6361f72076c17fabf305e252bf6d580106429014b3ab3c1f5c4eb3e6d465536ea6b670cc0e9a637a77a9ad40454d3e41361a2909e70e305116a23d68ce094c08
languageName: node
linkType: hard
"@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.24.5":
version: 7.24.5
resolution: "@babel/helper-validator-identifier@npm:7.24.5"
@@ -361,10 +380,10 @@ __metadata:
languageName: node
linkType: hard
"@babel/helper-validator-option@npm:^7.24.7":
version: 7.24.7
resolution: "@babel/helper-validator-option@npm:7.24.7"
checksum: 10c0/21aea2b7bc5cc8ddfb828741d5c8116a84cbc35b4a3184ec53124f08e09746f1f67a6f9217850188995ca86059a7942e36d8965a6730784901def777b7e8a436
"@babel/helper-validator-option@npm:^7.24.8":
version: 7.24.8
resolution: "@babel/helper-validator-option@npm:7.24.8"
checksum: 10c0/73db93a34ae89201351288bee7623eed81a54000779462a986105b54ffe82069e764afd15171a428b82e7c7a9b5fec10b5d5603b216317a414062edf5c67a21f
languageName: node
linkType: hard
@@ -379,13 +398,13 @@ __metadata:
languageName: node
linkType: hard
"@babel/helpers@npm:^7.24.7":
version: 7.24.7
resolution: "@babel/helpers@npm:7.24.7"
"@babel/helpers@npm:^7.24.8":
version: 7.24.8
resolution: "@babel/helpers@npm:7.24.8"
dependencies:
"@babel/template": "npm:^7.24.7"
"@babel/types": "npm:^7.24.7"
checksum: 10c0/aa8e230f6668773e17e141dbcab63e935c514b4b0bf1fed04d2eaefda17df68e16b61a56573f7f1d4d1e605ce6cc162b5f7e9fdf159fde1fd9b77c920ae47d27
"@babel/types": "npm:^7.24.8"
checksum: 10c0/42b8939b0a0bf72d6df9721973eb0fd7cd48f42641c5c9c740916397faa586255c06d36c6e6a7e091860723096281c620f6ffaee0011a3bb254a6f5475d89a12
languageName: node
linkType: hard
@@ -431,6 +450,15 @@ __metadata:
languageName: node
linkType: hard
"@babel/parser@npm:^7.24.8":
version: 7.24.8
resolution: "@babel/parser@npm:7.24.8"
bin:
parser: ./bin/babel-parser.js
checksum: 10c0/ce69671de8fa6f649abf849be262707ac700b573b8b1ce1893c66cc6cd76aeb1294a19e8c290b0eadeb2f47d3f413a2e57a281804ffbe76bfb9fa50194cf3c52
languageName: node
linkType: hard
"@babel/plugin-syntax-jsx@npm:^7.23.3":
version: 7.24.1
resolution: "@babel/plugin-syntax-jsx@npm:7.24.1"
@@ -553,6 +581,24 @@ __metadata:
languageName: node
linkType: hard
"@babel/traverse@npm:^7.24.8":
version: 7.24.8
resolution: "@babel/traverse@npm:7.24.8"
dependencies:
"@babel/code-frame": "npm:^7.24.7"
"@babel/generator": "npm:^7.24.8"
"@babel/helper-environment-visitor": "npm:^7.24.7"
"@babel/helper-function-name": "npm:^7.24.7"
"@babel/helper-hoist-variables": "npm:^7.24.7"
"@babel/helper-split-export-declaration": "npm:^7.24.7"
"@babel/parser": "npm:^7.24.8"
"@babel/types": "npm:^7.24.8"
debug: "npm:^4.3.1"
globals: "npm:^11.1.0"
checksum: 10c0/67a5cc35824455cdb54fb9e196a44b3186283e29018a9c2331f51763921e18e891b3c60c283615a27540ec8eb4c8b89f41c237b91f732a7aa518b2eb7a0d434d
languageName: node
linkType: hard
"@babel/types@npm:7.17.0":
version: 7.17.0
resolution: "@babel/types@npm:7.17.0"
@@ -585,6 +631,17 @@ __metadata:
languageName: node
linkType: hard
"@babel/types@npm:^7.24.8":
version: 7.24.8
resolution: "@babel/types@npm:7.24.8"
dependencies:
"@babel/helper-string-parser": "npm:^7.24.8"
"@babel/helper-validator-identifier": "npm:^7.24.7"
to-fast-properties: "npm:^2.0.0"
checksum: 10c0/2d7bf561ae993e794cb052c5a81d3a6d1877da13e1e2eb2a59ae75a8fb1c965b618fb3e4abd42548f5f9a4587d3a149185a32d6c4c4ea82195da7dd86f2da0f1
languageName: node
linkType: hard
"@emotion/babel-plugin@npm:^11.11.0":
version: 11.11.0
resolution: "@emotion/babel-plugin@npm:11.11.0"
@@ -910,13 +967,20 @@ __metadata:
languageName: node
linkType: hard
"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.6.1":
"@eslint-community/regexpp@npm:^4.10.0":
version: 4.10.0
resolution: "@eslint-community/regexpp@npm:4.10.0"
checksum: 10c0/c5f60ef1f1ea7649fa7af0e80a5a79f64b55a8a8fa5086de4727eb4c86c652aedee407a9c143b8995d2c0b2d75c1222bec9ba5d73dbfc1f314550554f0979ef4
languageName: node
linkType: hard
"@eslint-community/regexpp@npm:^4.11.0":
version: 4.11.0
resolution: "@eslint-community/regexpp@npm:4.11.0"
checksum: 10c0/0f6328869b2741e2794da4ad80beac55cba7de2d3b44f796a60955b0586212ec75e6b0253291fd4aad2100ad471d1480d8895f2b54f1605439ba4c875e05e523
languageName: node
linkType: hard
"@eslint/config-array@npm:^0.17.0":
version: 0.17.0
resolution: "@eslint/config-array@npm:0.17.0"
@@ -945,10 +1009,10 @@ __metadata:
languageName: node
linkType: hard
"@eslint/js@npm:9.6.0, @eslint/js@npm:^9.6.0":
version: 9.6.0
resolution: "@eslint/js@npm:9.6.0"
checksum: 10c0/83967a7e59f2e958c9bbb3acd0929cad00d59d927ad786ed8e0d30b07f983c6bea3af6f4ad32da32145db40b7a741a816ba339bdd8960fc7fc8231716d943b7f
"@eslint/js@npm:9.7.0, @eslint/js@npm:^9.7.0":
version: 9.7.0
resolution: "@eslint/js@npm:9.7.0"
checksum: 10c0/73fc10666f6f4aed6f58e407e09f42ceb0d42fa60c52701c64ea9f59a81a6a8ad5caecdfd423d03088481515fe7ec17eb461acb4ef1ad70b649b6eae465b3164
languageName: node
linkType: hard
@@ -1099,16 +1163,16 @@ __metadata:
languageName: node
linkType: hard
"@mui/core-downloads-tracker@npm:^5.16.0":
version: 5.16.0
resolution: "@mui/core-downloads-tracker@npm:5.16.0"
checksum: 10c0/ed2c7e4e87637435d138026837436527934cd9c4c340721859f2f24d21d63e922231beb3b916070d549e2e6133310b2d3cca5b1d8bc1b8f88a10e6de913a363d
"@mui/core-downloads-tracker@npm:^5.16.1":
version: 5.16.1
resolution: "@mui/core-downloads-tracker@npm:5.16.1"
checksum: 10c0/2bdad71c53d65e59eba4889e6d3270b964200dddae448ae7d095de7130a4ec82e7fccdf8668488996e22993b653b91544195c10eeb996dc1c3a6c7a34c88c9c4
languageName: node
linkType: hard
"@mui/icons-material@npm:^5.16.0":
version: 5.16.0
resolution: "@mui/icons-material@npm:5.16.0"
"@mui/icons-material@npm:^5.16.1":
version: 5.16.1
resolution: "@mui/icons-material@npm:5.16.1"
dependencies:
"@babel/runtime": "npm:^7.23.9"
peerDependencies:
@@ -1118,25 +1182,25 @@ __metadata:
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/c4aa6ea2941efe6bf50f477657234a946579f0ee0592cacec03a6e2bacefef04af2073e43c06cb69b1372f1ffb89ff4e4fc75f909ed4c54a04974176562f52f0
checksum: 10c0/90c235ee065a18e07e2c3059dcfe15ef880c5f936cde538c41ba3dfab83bbb963093a28603852647e0ef05e0a233e50ea4cd932d9ae58dcf803dadc16dd603af
languageName: node
linkType: hard
"@mui/material@npm:^5.16.0":
version: 5.16.0
resolution: "@mui/material@npm:5.16.0"
"@mui/material@npm:^5.16.1":
version: 5.16.1
resolution: "@mui/material@npm:5.16.1"
dependencies:
"@babel/runtime": "npm:^7.23.9"
"@mui/base": "npm:5.0.0-beta.40"
"@mui/core-downloads-tracker": "npm:^5.16.0"
"@mui/system": "npm:^5.16.0"
"@mui/types": "npm:^7.2.14"
"@mui/utils": "npm:^5.16.0"
"@mui/core-downloads-tracker": "npm:^5.16.1"
"@mui/system": "npm:^5.16.1"
"@mui/types": "npm:^7.2.15"
"@mui/utils": "npm:^5.16.1"
"@types/react-transition-group": "npm:^4.4.10"
clsx: "npm:^2.1.0"
csstype: "npm:^3.1.3"
prop-types: "npm:^15.8.1"
react-is: "npm:^18.2.0"
react-is: "npm:^18.3.1"
react-transition-group: "npm:^4.4.5"
peerDependencies:
"@emotion/react": ^11.5.0
@@ -1151,16 +1215,16 @@ __metadata:
optional: true
"@types/react":
optional: true
checksum: 10c0/59a302ad3168a9768980f9bc8b18044876194ecebc51fd166ff002ad86df9c73f43409ed451916ed8cbbeb86447ec27b234c469f654d9d0313e36ef2d487a55a
checksum: 10c0/13baf645c276a97fade56ccce83ceaea95ee71cff56fea407cce576ff96b81115dacc5dd3d30a250047fc104e5ce63c28bdc095351e567407f9944af9d9daa83
languageName: node
linkType: hard
"@mui/private-theming@npm:^5.16.0":
version: 5.16.0
resolution: "@mui/private-theming@npm:5.16.0"
"@mui/private-theming@npm:^5.16.1":
version: 5.16.1
resolution: "@mui/private-theming@npm:5.16.1"
dependencies:
"@babel/runtime": "npm:^7.23.9"
"@mui/utils": "npm:^5.16.0"
"@mui/utils": "npm:^5.16.1"
prop-types: "npm:^15.8.1"
peerDependencies:
"@types/react": ^17.0.0 || ^18.0.0
@@ -1168,13 +1232,13 @@ __metadata:
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/cd7c1888f45da3db701806a1d127010514815bb4acf502c3cf711e63d2307b18387bdc7a9f3815031210814b6a1fd977722e5cef2133e077e9bc2ac0bdb5504f
checksum: 10c0/8a95c7d59be1fd3ba8e49addb76870d5efe675a8f61ecb1558306053f0c71abad22f08d551e55fc48d5c012b0d1f1aefe924e0d3395485451ad1fe92a47adab2
languageName: node
linkType: hard
"@mui/styled-engine@npm:^5.15.14":
version: 5.15.14
resolution: "@mui/styled-engine@npm:5.15.14"
"@mui/styled-engine@npm:^5.16.1":
version: 5.16.1
resolution: "@mui/styled-engine@npm:5.16.1"
dependencies:
"@babel/runtime": "npm:^7.23.9"
"@emotion/cache": "npm:^11.11.0"
@@ -1189,19 +1253,19 @@ __metadata:
optional: true
"@emotion/styled":
optional: true
checksum: 10c0/0d262ea0b3c117f865af1cd52b992592c24432e491b35e712159bb49adfd776ee9a532abbc4ab08889f308e75d30082a0fee809119d5d61a82b3277212655319
checksum: 10c0/fc011390c64ff970ad095e0a9e18f5ad777491fd49974233137b673fa9698d178c6b5f6346a28c1d8a1718dd453879ded4198bbc73e73dfcc8d5a90ea17760f6
languageName: node
linkType: hard
"@mui/system@npm:^5.16.0":
version: 5.16.0
resolution: "@mui/system@npm:5.16.0"
"@mui/system@npm:^5.16.1":
version: 5.16.1
resolution: "@mui/system@npm:5.16.1"
dependencies:
"@babel/runtime": "npm:^7.23.9"
"@mui/private-theming": "npm:^5.16.0"
"@mui/styled-engine": "npm:^5.15.14"
"@mui/types": "npm:^7.2.14"
"@mui/utils": "npm:^5.16.0"
"@mui/private-theming": "npm:^5.16.1"
"@mui/styled-engine": "npm:^5.16.1"
"@mui/types": "npm:^7.2.15"
"@mui/utils": "npm:^5.16.1"
clsx: "npm:^2.1.0"
csstype: "npm:^3.1.3"
prop-types: "npm:^15.8.1"
@@ -1217,7 +1281,7 @@ __metadata:
optional: true
"@types/react":
optional: true
checksum: 10c0/b5db097ba432653eba723424264204d3037ab1cdb30acc63b2d2fbcb98bae8999cbfd7ffff54173593dc2a4771558177602f01377da6f4134e7017f93a3624bf
checksum: 10c0/17b76d32d4ee1800910993d7309c36a8ea1982890ee952d1a301e5125a8ebe7bcdf3d8930fb385dd79712c4e3ea9e9e9547fac23b79dc874b60b33b0dbdffa8a
languageName: node
linkType: hard
@@ -1233,6 +1297,18 @@ __metadata:
languageName: node
linkType: hard
"@mui/types@npm:^7.2.15":
version: 7.2.15
resolution: "@mui/types@npm:7.2.15"
peerDependencies:
"@types/react": ^17.0.0 || ^18.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/26c39674fe6f653a4c7406890b081b772e62efbd5b2754ab28bb8346819265d7c6496db8a8923230a84252ffd890e3d0b41642c151b78fdf8505336c92d78e14
languageName: node
linkType: hard
"@mui/utils@npm:^5.15.14":
version: 5.15.14
resolution: "@mui/utils@npm:5.15.14"
@@ -1251,21 +1327,21 @@ __metadata:
languageName: node
linkType: hard
"@mui/utils@npm:^5.16.0":
version: 5.16.0
resolution: "@mui/utils@npm:5.16.0"
"@mui/utils@npm:^5.16.1":
version: 5.16.1
resolution: "@mui/utils@npm:5.16.1"
dependencies:
"@babel/runtime": "npm:^7.23.9"
"@types/prop-types": "npm:^15.7.11"
"@types/prop-types": "npm:^15.7.12"
prop-types: "npm:^15.8.1"
react-is: "npm:^18.2.0"
react-is: "npm:^18.3.1"
peerDependencies:
"@types/react": ^17.0.0 || ^18.0.0
react: ^17.0.0 || ^18.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/0b56f9265a8ea686b50195448f1dea41c637d504c30da44c5f3f315bc81d24598f3b2acb281dd24d15831f7756dcaab285b7b891883bbec5809c54ef24affb49
checksum: 10c0/9a64bbfcd84474c9d18a79f3e236c4145d43983c3b1810cbcb6cf9d37d36f1bfeeb5a4f9ebf907050364b0a991caba28c481d2e6b2818e4e1c22729eeed24505
languageName: node
linkType: hard
@@ -1341,9 +1417,9 @@ __metadata:
languageName: node
linkType: hard
"@preact/preset-vite@npm:^2.8.3":
version: 2.8.3
resolution: "@preact/preset-vite@npm:2.8.3"
"@preact/preset-vite@npm:^2.9.0":
version: 2.9.0
resolution: "@preact/preset-vite@npm:2.9.0"
dependencies:
"@babel/code-frame": "npm:^7.22.13"
"@babel/plugin-transform-react-jsx": "npm:^7.22.15"
@@ -1361,7 +1437,7 @@ __metadata:
peerDependencies:
"@babel/core": 7.x
vite: 2.x || 3.x || 4.x || 5.x
checksum: 10c0/984b47d24491d20ea9f0787adbef3515c3bd4cdfd77154cdb84e9038fabb5db09a0c76237557aff88b3da26cff6146ac616c2375579143fc511afcf7baf63927
checksum: 10c0/658e3dc048d1f1d8ad7cb1fef4a3db0f933be4e00d3d6cdfbd29fe7ec02341b3a26747520a5b261992923b3d3f49800c23a4d77da849e708a5c1ad9a920343b2
languageName: node
linkType: hard
@@ -1784,7 +1860,7 @@ __metadata:
languageName: node
linkType: hard
"@types/prop-types@npm:*, @types/prop-types@npm:^15.7.11":
"@types/prop-types@npm:*, @types/prop-types@npm:^15.7.11, @types/prop-types@npm:^15.7.12":
version: 15.7.12
resolution: "@types/prop-types@npm:15.7.12"
checksum: 10c0/1babcc7db6a1177779f8fde0ccc78d64d459906e6ef69a4ed4dd6339c920c2e05b074ee5a92120fe4e9d9f1a01c952f843ebd550bee2332fc2ef81d1706878f8
@@ -2002,14 +2078,14 @@ __metadata:
dependencies:
"@alova/adapter-xhr": "npm:^1.0.6"
"@alova/scene-react": "npm:^1.6.1"
"@babel/core": "npm:^7.24.7"
"@babel/core": "npm:^7.24.8"
"@emotion/react": "npm:^11.11.4"
"@emotion/styled": "npm:^11.11.5"
"@eslint/js": "npm:^9.6.0"
"@mui/icons-material": "npm:^5.16.0"
"@mui/material": "npm:^5.16.0"
"@eslint/js": "npm:^9.7.0"
"@mui/icons-material": "npm:^5.16.1"
"@mui/material": "npm:^5.16.1"
"@preact/compat": "npm:^17.1.2"
"@preact/preset-vite": "npm:^2.8.3"
"@preact/preset-vite": "npm:^2.9.0"
"@table-library/react-table-library": "npm:4.1.7"
"@trivago/prettier-plugin-sort-imports": "npm:^4.3.0"
"@types/babel__core": "npm:^7"
@@ -2018,17 +2094,17 @@ __metadata:
"@types/react": "npm:^18.3.3"
"@types/react-dom": "npm:^18.3.0"
"@types/react-router-dom": "npm:^5.3.3"
alova: "npm:^2.21.3"
alova: "npm:^2.21.4"
async-validator: "npm:^4.2.5"
concurrently: "npm:^8.2.2"
eslint: "npm:^9.6.0"
eslint: "npm:^9.7.0"
eslint-config-prettier: "npm:^9.1.0"
history: "npm:^5.3.0"
jwt-decode: "npm:^4.0.0"
lodash-es: "npm:^4.17.21"
mime-types: "npm:^2.1.35"
preact: "npm:^10.22.1"
prettier: "npm:^3.3.2"
prettier: "npm:^3.3.3"
react: "npm:latest"
react-dom: "npm:latest"
react-dropzone: "npm:^14.2.3"
@@ -2036,7 +2112,7 @@ __metadata:
react-router-dom: "npm:^6.24.1"
react-toastify: "npm:^10.0.5"
rollup-plugin-visualizer: "npm:^5.12.0"
terser: "npm:^5.31.1"
terser: "npm:^5.31.2"
typesafe-i18n: "npm:^5.26.2"
typescript: "npm:^5.5.3"
typescript-eslint: "npm:8.0.0-alpha.10"
@@ -2111,10 +2187,10 @@ __metadata:
languageName: node
linkType: hard
"alova@npm:^2.21.3":
version: 2.21.3
resolution: "alova@npm:2.21.3"
checksum: 10c0/ce03ced33660f15ec667e24df5c7fbeff223dace95a7d50d657dd038f21dc865d8a9290914edbc5a980c8573daa4354ac714f97ae58650ea5b8fefd8fc2d313c
"alova@npm:^2.21.4":
version: 2.21.4
resolution: "alova@npm:2.21.4"
checksum: 10c0/6600826747eecc85f750904cd5a9f75361bd5b542615b027fae072cafe72cb27b9f7b260a834063c0734a9b41a7aa8b15396a190f8ce9a46713d01b073296a50
languageName: node
linkType: hard
@@ -2373,6 +2449,20 @@ __metadata:
languageName: node
linkType: hard
"browserslist@npm:^4.23.1":
version: 4.23.2
resolution: "browserslist@npm:4.23.2"
dependencies:
caniuse-lite: "npm:^1.0.30001640"
electron-to-chromium: "npm:^1.4.820"
node-releases: "npm:^2.0.14"
update-browserslist-db: "npm:^1.1.0"
bin:
browserslist: cli.js
checksum: 10c0/0217d23c69ed61cdd2530c7019bf7c822cd74c51f8baab18dd62457fed3129f52499f8d3a6f809ae1fb7bb3050aa70caa9a529cc36c7478427966dbf429723a5
languageName: node
linkType: hard
"buffer-alloc-unsafe@npm:^1.1.0":
version: 1.1.0
resolution: "buffer-alloc-unsafe@npm:1.1.0"
@@ -2487,6 +2577,13 @@ __metadata:
languageName: node
linkType: hard
"caniuse-lite@npm:^1.0.30001640":
version: 1.0.30001641
resolution: "caniuse-lite@npm:1.0.30001641"
checksum: 10c0/a065b641cfcc84b36955ee909bfd7313ad103d6a299f0fd261e0e4160e8f1cec79d685c5a9f11097a77687cf47154eddb8133163f2a34bcb8d73c45033a014d2
languageName: node
linkType: hard
"caw@npm:^2.0.0, caw@npm:^2.0.1":
version: 2.0.1
resolution: "caw@npm:2.0.1"
@@ -3098,6 +3195,13 @@ __metadata:
languageName: node
linkType: hard
"electron-to-chromium@npm:^1.4.820":
version: 1.4.827
resolution: "electron-to-chromium@npm:1.4.827"
checksum: 10c0/e37719d8f13da78eb2bc68184cdf73d167ecf413abc28afef2b0a5c55866293752fda980d83a5f42b5780781bde418b24c12e1c38f2662d25ed1c2f71880bc24
languageName: node
linkType: hard
"emoji-regex@npm:^8.0.0":
version: 8.0.0
resolution: "emoji-regex@npm:8.0.0"
@@ -3493,13 +3597,13 @@ __metadata:
languageName: node
linkType: hard
"eslint-scope@npm:^8.0.1":
version: 8.0.1
resolution: "eslint-scope@npm:8.0.1"
"eslint-scope@npm:^8.0.2":
version: 8.0.2
resolution: "eslint-scope@npm:8.0.2"
dependencies:
esrecurse: "npm:^4.3.0"
estraverse: "npm:^5.2.0"
checksum: 10c0/0ec40ab284e58ac7ef064ecd23c127e03d339fa57173c96852336c73afc70ce5631da21dc1c772415a37a421291845538dd69db83c68d611044c0fde1d1fa269
checksum: 10c0/477f820647c8755229da913025b4567347fd1f0bf7cbdf3a256efff26a7e2e130433df052bd9e3d014025423dc00489bea47eb341002b15553673379c1a7dc36
languageName: node
linkType: hard
@@ -3517,15 +3621,15 @@ __metadata:
languageName: node
linkType: hard
"eslint@npm:^9.6.0":
version: 9.6.0
resolution: "eslint@npm:9.6.0"
"eslint@npm:^9.7.0":
version: 9.7.0
resolution: "eslint@npm:9.7.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.2.0"
"@eslint-community/regexpp": "npm:^4.6.1"
"@eslint-community/regexpp": "npm:^4.11.0"
"@eslint/config-array": "npm:^0.17.0"
"@eslint/eslintrc": "npm:^3.1.0"
"@eslint/js": "npm:9.6.0"
"@eslint/js": "npm:9.7.0"
"@humanwhocodes/module-importer": "npm:^1.0.1"
"@humanwhocodes/retry": "npm:^0.3.0"
"@nodelib/fs.walk": "npm:^1.2.8"
@@ -3534,7 +3638,7 @@ __metadata:
cross-spawn: "npm:^7.0.2"
debug: "npm:^4.3.2"
escape-string-regexp: "npm:^4.0.0"
eslint-scope: "npm:^8.0.1"
eslint-scope: "npm:^8.0.2"
eslint-visitor-keys: "npm:^4.0.0"
espree: "npm:^10.1.0"
esquery: "npm:^1.5.0"
@@ -3557,7 +3661,7 @@ __metadata:
text-table: "npm:^0.2.0"
bin:
eslint: bin/eslint.js
checksum: 10c0/82ea5ad3f28aaef89e2a98f4e6df0eae9d4e16ccd6d667c69977042e0b103fa5df98bf16d3df72d1ae77edd8c1dccfdf4afa2a55309aa8081a1bc54af6229826
checksum: 10c0/e2369a9534404f62f37ee5560e56fb84e0776a9c8f084550170017992772e7034d73571bdf4060e2fe9b836f136d45b07d50407d4b9361de720ee77794259274
languageName: node
linkType: hard
@@ -5999,12 +6103,12 @@ __metadata:
languageName: node
linkType: hard
"prettier@npm:^3.3.2":
version: 3.3.2
resolution: "prettier@npm:3.3.2"
"prettier@npm:^3.3.3":
version: 3.3.3
resolution: "prettier@npm:3.3.3"
bin:
prettier: bin/prettier.cjs
checksum: 10c0/39ed27d17f0238da6dd6571d63026566bd790d3d0edac57c285fbab525982060c8f1e01955fe38134ab10f0951a6076da37f015db8173c02f14bc7f0803a384c
checksum: 10c0/b85828b08e7505716324e4245549b9205c0cacb25342a030ba8885aba2039a115dbcf75a0b7ca3b37bc9d101ee61fab8113fc69ca3359f2a226f1ecc07ad2e26
languageName: node
linkType: hard
@@ -6140,7 +6244,7 @@ __metadata:
languageName: node
linkType: hard
"react-is@npm:^18.2.0":
"react-is@npm:^18.2.0, react-is@npm:^18.3.1":
version: 18.3.1
resolution: "react-is@npm:18.3.1"
checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072
@@ -7035,9 +7139,9 @@ __metadata:
languageName: node
linkType: hard
"terser@npm:^5.31.1":
version: 5.31.1
resolution: "terser@npm:5.31.1"
"terser@npm:^5.31.2":
version: 5.31.2
resolution: "terser@npm:5.31.2"
dependencies:
"@jridgewell/source-map": "npm:^0.3.3"
acorn: "npm:^8.8.2"
@@ -7045,7 +7149,7 @@ __metadata:
source-map-support: "npm:~0.5.20"
bin:
terser: bin/terser
checksum: 10c0/4d49a58f64c11f3742e779a0a03aff69972ca5739decb361d909d22c8f3f7d8e2ec982a928d987d56737ad50229e8ab3f62d8ba993e4b5f360a53ed487d3c06c
checksum: 10c0/5b72f58421f69267f67cb60cf4398282afcdec697e9b6f1909035cdf52d9960226fc1df5968e27ba96736b7a7ba76609d0b7b060ee5da7769553940726059b63
languageName: node
linkType: hard
@@ -7274,6 +7378,20 @@ __metadata:
languageName: node
linkType: hard
"update-browserslist-db@npm:^1.1.0":
version: 1.1.0
resolution: "update-browserslist-db@npm:1.1.0"
dependencies:
escalade: "npm:^3.1.2"
picocolors: "npm:^1.0.1"
peerDependencies:
browserslist: ">= 4.21.0"
bin:
update-browserslist-db: cli.js
checksum: 10c0/a7452de47785842736fb71547651c5bbe5b4dc1e3722ccf48a704b7b34e4dcf633991eaa8e4a6a517ffb738b3252eede3773bef673ef9021baa26b056d63a5b9
languageName: node
linkType: hard
"uri-js@npm:^4.2.2":
version: 4.4.1
resolution: "uri-js@npm:4.4.1"

View File

@@ -13,80 +13,83 @@ the LICENSE file.
namespace espMqttClientInternals {
ClientPosix::ClientPosix()
: _sockfd(-1)
, _host() {
// empty
: _sockfd(-1)
, _host() {
// empty
}
ClientPosix::~ClientPosix() {
ClientPosix::stop();
ClientPosix::stop();
}
bool ClientPosix::connect(IPAddress ip, uint16_t port) {
if (connected()) stop();
if (connected())
stop();
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0) {
emc_log_e("Error %d opening socket", errno);
}
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0) {
emc_log_e("Error %d opening socket", errno);
}
int flag = 1;
if (setsockopt(_sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)) < 0) {
emc_log_e("Error %d disabling nagle", errno);
}
int flag = 1;
if (setsockopt(_sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)) < 0) {
emc_log_e("Error %d disabling nagle", errno);
}
memset(&_host, 0, sizeof(_host));
_host.sin_family = AF_INET;
_host.sin_addr.s_addr = htonl(uint32_t(ip));
_host.sin_port = ::htons(port);
memset(&_host, 0, sizeof(_host));
_host.sin_family = AF_INET;
_host.sin_addr.s_addr = htonl(uint32_t(ip));
#ifndef __APPLE__
_host.sin_port = ::htons(port);
#endif
int ret = ::connect(_sockfd, reinterpret_cast<sockaddr*>(&_host), sizeof(_host));
int ret = ::connect(_sockfd, reinterpret_cast<sockaddr *>(&_host), sizeof(_host));
if (ret < 0) {
emc_log_e("Error connecting: %d - (%d) %s", ret, errno, strerror(errno));
if (ret < 0) {
emc_log_e("Error connecting: %d - (%d) %s", ret, errno, strerror(errno));
return false;
}
emc_log_i("Connected");
return true;
}
bool ClientPosix::connect(const char * host, uint16_t port) {
// tbi
(void)host;
(void)port;
return false;
}
emc_log_i("Connected");
return true;
}
bool ClientPosix::connect(const char* host, uint16_t port) {
// tbi
(void) host;
(void) port;
return false;
size_t ClientPosix::write(const uint8_t * buf, size_t size) {
return ::send(_sockfd, buf, size, 0);
}
size_t ClientPosix::write(const uint8_t* buf, size_t size) {
return ::send(_sockfd, buf, size, 0);
}
int ClientPosix::read(uint8_t* buf, size_t size) {
int ret = ::recv(_sockfd, buf, size, MSG_DONTWAIT);
/*
int ClientPosix::read(uint8_t * buf, size_t size) {
int ret = ::recv(_sockfd, buf, size, MSG_DONTWAIT);
/*
if (ret < 0) {
emc_log_e("Error reading: %s", strerror(errno));
}
*/
return ret;
return ret;
}
void ClientPosix::stop() {
if (_sockfd >= 0) {
::close(_sockfd);
_sockfd = -1;
}
if (_sockfd >= 0) {
::close(_sockfd);
_sockfd = -1;
}
}
bool ClientPosix::connected() {
return _sockfd >= 0;
return _sockfd >= 0;
}
bool ClientPosix::disconnected() {
return _sockfd < 0;
return _sockfd < 0;
}
} // namespace espMqttClientInternals
} // namespace espMqttClientInternals
#endif

View File

@@ -269,7 +269,7 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
newSettings.publish_time_solar = static_cast<uint16_t>(root["publish_time_solar"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_mixer = static_cast<uint16_t>(root["publish_time_mixer"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_water = static_cast<uint16_t>(root["publish_time_water"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_other = static_cast<uint16_t>(root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_other = static_cast<uint16_t>(root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME_OTHER);
newSettings.publish_time_sensor = static_cast<uint16_t>(root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME);
newSettings.publish_time_heartbeat = static_cast<uint16_t>(root["publish_time_heartbeat"] | EMSESP_DEFAULT_PUBLISH_HEARTBEAT);

View File

@@ -119,7 +119,7 @@ Commands::Execution Commands::execute_command(Shell & shell, CommandLine && comm
result.error = nullptr;
if (commands.exact.empty()) {
result.error = "Command not found";
result.error = "Command not found. Try 'help' for a list of commands.";
} else if (commands.exact.count(longest->first) == 1) {
auto & command = longest->second;
std::vector<std::string> arguments;
@@ -130,7 +130,7 @@ Commands::Execution Commands::execute_command(Shell & shell, CommandLine && comm
command_line.reset();
if (commands.partial.upper_bound(longest->first) != commands.partial.end() && !arguments.empty()) {
result.error = "Command not found";
result.error = "Command not found. Try 'help' for a list of commands.";
} else if (arguments.size() < command->minimum_arguments()) {
result.error = "Not enough arguments for command";
} else if (arguments.size() > command->maximum_arguments()) {

View File

@@ -47,7 +47,7 @@ rest_server.get(ES_LOG_ENDPOINT, (_req, res) => {
const interval = setInterval(() => {
const data = {
t: new Date().toISOString(),
l: 3, // error
l: 7, // debug
i: count,
n: 'system',
m: 'message #' + count++

View File

@@ -350,23 +350,26 @@ const GENERATE_TOKEN_ENDPOINT = REST_ENDPOINT_ROOT + 'generateToken';
const ESPsystem_status = {
emsesp_version: '3.7-demo',
esp_platform: 'ESP32',
max_alloc_heap: 89,
cpu_type: 'ESP32-S3',
cpu_rev: '0',
cpu_cores: 2,
max_alloc_heap: 89,
psram_size: 0,
free_psram: 0,
cpu_freq_mhz: 240,
free_heap: 143,
arduino_version: 'ESP32 Arduino v2.0.14',
sdk_version: 'v4.4.2',
partition: 'app0',
flash_chip_size: 4096,
flash_chip_speed: 40000000,
fs_used: 40,
fs_free: 24,
partition: 'app0',
app_used: 1863,
app_free: 121,
arduino_version: 'ESP32 Arduino v2.0.14'
fs_used: 40,
fs_free: 24,
free_mem: 100,
psram_size: 0,
free_psram: 0,
has_loader: true,
model: 'BBQKees Gateway Model E32V2 vE32V2.0P3/2024011'
};
const system_status = {

View File

@@ -213,14 +213,15 @@ build_unflags = ${common.unbuild_flags}
; to build and run: pio run -e native -t exec
[env:native]
platform = native
extra_scripts = pre:scripts/rename_prog.py
build_flags =
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
-DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.0-dev.0\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
-std=gnu++14 -Og -ggdb
-std=gnu++14 -Og -ggdb -D__linux__
build_src_flags =
-Wall -Wextra -Werror
-Wno-unused-parameter -Wno-sign-compare
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
-I./lib_standalone
-I./lib/uuid-common/src
-I./lib/uuid-console/src

View File

@@ -3,8 +3,7 @@
# creates an CSV file called "dump_telegrams.cvs" with all devices and their telegrams
# run from top folder like `sh ./scripts/dump_telegrams.sh`
rm -f dump_telegrams.csv
# make clean
# make
make clean
make ARGS=-DEMSESP_STANDALONE
echo "test telegram_dump" | ./emsesp | python3 ./scripts/strip_csv.py > dump_telegrams.csv
cat dump_telegrams.csv

View File

@@ -3,9 +3,11 @@
# This script generates c++ code for the modbus parameter definitions.
#
# Usage:
# - first, dump all entities to csv by running 'scripts/dump_entities.sh'
# - first, dump all entities to csv by running 'sh ./scripts/dump_entities.sh'
# - then run 'cat ../dump_entities.csv | python3 update_modbus_registers.py > ../src/modbus_entity_parameters.hpp'
# from the "scripts" folder
# OR
# run `sh ./scripts/update_modbus_registers.sh` from the root folder
import fileinput
import csv
@@ -114,6 +116,12 @@ device_type_names = [
cpp_file_template = Template('''#include "modbus.h"
#include "emsdevice.h"
/*
* This file is auto-generated by the update_modbus_registers.sh script. Do not modify.
*/
// clang-format off
namespace emsesp {
using dt = EMSdevice::DeviceType;
@@ -125,7 +133,10 @@ using dt = EMSdevice::DeviceType;
const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_mappings = {
$entries};
} // namespace emsesp''')
} // namespace emsesp
// clang-format off
''')
# cpp_entry_template = Template(
# ' {std::make_tuple($devtype, $tagtype, std::string(\"$shortname\")), {$registeroffset, $registercount}},\n')
cpp_entry_template = Template(

View File

@@ -3,7 +3,7 @@
# Update modbus parameters from entity definitions.
# This script generates c++ code for the modbus parameter definitions.
#
# Run this script from the EMS-ESP32 root directory with the command `scripts/update_modbus_registers.sh`.
# Run this script from the EMS-ESP32 root directory with the command `sh ./scripts/update_modbus_registers.sh`.
while [[ $# -gt 0 ]]; do
case $1 in
@@ -35,6 +35,12 @@ if [ "$RESET" = "YES" ]; then
#include "modbus.h"
#include "emsdevice.h"
/*
* This file is auto-generated by the update_modbus_registers.sh script. Do not modify.
*/
// clang-format off
namespace emsesp {
using dt = EMSdevice::DeviceType;
@@ -46,6 +52,9 @@ using dt = EMSdevice::DeviceType;
const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_mappings = {};
} // namespace emsesp
// clang-format on
EOL
fi
@@ -53,3 +62,4 @@ make clean
make ARGS=-DEMSESP_STANDALONE
echo "test entity_dump" | ./emsesp | python3 ./scripts/strip_csv.py | python3 ./scripts/update_modbus_registers.py > ./src/modbus_entity_parameters.hpp
echo "Modbus entity parameters written to ./src/modbus_entity_parameters.hpp."

View File

@@ -643,7 +643,7 @@ void AnalogSensor::publish_values(const bool force) {
// called from emsesp.cpp for commands
// searches sensor by name
bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int8_t id) {
// check of it a 'commmands' command
// check of it a 'commands' command
if (Helpers::toLower(cmd) == F_(commands)) {
return Command::list(EMSdevice::DeviceType::ANALOGSENSOR, output);
}
@@ -700,22 +700,18 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
// if we're filtering on an attribute, go find it
if (attribute_s) {
if (output.containsKey(attribute_s)) {
String data = output[attribute_s].as<String>();
std::string data = output[attribute_s].as<std::string>();
output.clear();
output["api_data"] = data;
output["api_data"] = data; // always as a string
return true;
} else {
char error[100];
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, sensor_name);
output.clear();
output["message"] = error;
return false;
}
return EMSESP::return_not_found(output, "attribute", sensor_name); // not found
}
return true; // found a match, exit
}
}
return false; // not found
return EMSESP::return_not_found(output, "analog sensor", cmd); // not found
}
void AnalogSensor::addSensorJson(JsonObject output, const Sensor & sensor) {

View File

@@ -79,7 +79,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
uint8_t device_type = EMSdevice::device_name_2_device_type(device_s);
if (!device_has_commands(device_type)) {
LOG_DEBUG("Command failed: unknown device '%s'", device_s);
return message(CommandRet::ERROR, "unknown device", output);
return message(CommandRet::NOT_FOUND, "unknown device", output);
}
// the next value on the path should be the command or entity name
@@ -161,21 +161,25 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
if (data_p == nullptr) {
return CommandRet::INVALID;
}
char data_s[COMMAND_MAX_LENGTH];
strlcpy(data_s, Helpers::toLower(data_p).c_str(), 30);
if (strstr(data_s, "/value") == nullptr) {
strcat(data_s, "/value");
}
uint8_t device_type = EMSdevice::device_name_2_device_type(device_p);
if (CommandRet::OK != Command::call(device_type, data_s, "", true, id_d, output)) {
return CommandRet::INVALID;
}
if (!output.containsKey("api_data")) {
return CommandRet::INVALID;
const char * api_data = output["api_data"];
if (api_data) {
output.clear();
return Command::call(device_type, command_p, api_data, is_admin, id_n, output);
}
String dat = output["api_data"].as<String>();
output.clear();
return Command::call(device_type, command_p, dat.c_str(), is_admin, id_n, output);
return CommandRet::INVALID;
}
}
}
@@ -296,11 +300,23 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
if (cmd == nullptr) {
return CommandRet::NOT_FOUND;
}
uint8_t return_code = CommandRet::OK;
auto dname = EMSdevice::device_type_2_device_name(device_type);
auto dname = EMSdevice::device_type_2_device_name(device_type); // device name, not translated
// check first if there is only a command being called without a value
// it could be an endpoint like a device's entity or attribute e.g. api/boiler/nrgheat or /api/boiler/nrgheat/value
// or a special command like 'info', 'values', 'commands', 'entities' etc
bool single_command = (!value || !strlen(value));
if (single_command) {
if (EMSESP::get_device_value_info(output, cmd, id, device_type)) { // entity = cmd
LOG_DEBUG("Fetched device entity/attributes for %s/%s", dname, cmd);
return CommandRet::OK;
}
}
uint8_t device_id = EMSESP::device_id_from_cmd(device_type, cmd, id);
// determine flags based on id (which is the tag)
uint8_t flag = CommandFlag::CMD_FLAG_DEFAULT;
int8_t tag = id;
if (tag >= DeviceValueTAG::TAG_HC1 && tag <= DeviceValueTAG::TAG_HC8) {
@@ -312,31 +328,19 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
} else if (tag >= DeviceValueTAG::TAG_AHS1 && tag <= DeviceValueTAG::TAG_AHS1) {
flag = CommandFlag::CMD_FLAG_AHS;
}
// see if there is a command registered
// first see if there is a command registered and it's valid
auto cf = find_command(device_type, device_id, cmd, flag);
// check if its a call to an end-point of a device
// this is used to fetch the attributes of the device entity, or call a command directly
// for example info, values, commands, etc
bool single_command = (!value || !strlen(value));
if (single_command) {
// exception: boiler coldshot command
bool get_attributes = (!cf || !cf->cmdfunction_json_) && (strcmp(cmd, F_(coldshot)) != 0);
if (get_attributes) {
LOG_DEBUG("Calling %s command '%s' to retrieve attributes", dname, cmd);
return EMSESP::get_device_value_info(output, cmd, id, device_type) ? CommandRet::OK : CommandRet::ERROR; // entity = cmd
}
}
// check if we have a matching command
if (!cf) {
// we didn't find the command, report error
LOG_WARNING("Command failed: invalid command '%s'", cmd ? cmd : "");
return message(CommandRet::NOT_FOUND, "invalid command", output);
// if we don't alread have a message set, set it to invalid command
if (!output["message"]) {
output["message"] = "invalid command";
}
return CommandRet::ERROR;
}
// check permissions and abort if not authorized
// before calling the command, check permissions and abort if not authorized
if (cf->has_flags(CommandFlag::ADMIN_ONLY) && !is_admin) {
LOG_WARNING("Command failed: authentication failed");
output["message"] = "authentication failed";
@@ -353,7 +357,8 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
} else {
snprintf(info_s, sizeof(info_s), "'%s/%s'", dname, cmd);
}
if ((value == nullptr) || (strlen(value) == 0)) {
if (single_command) {
LOG_DEBUG(("%sCalling command %s"), ro.c_str(), info_s);
} else {
if (id > 0) {
@@ -364,6 +369,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
}
// call the function based on type, either with a json package or no parameters
uint8_t return_code = CommandRet::OK;
if (cf->cmdfunction_json_) {
// JSON
return_code = ((cf->cmdfunction_json_)(value, id, output)) ? CommandRet::OK : CommandRet::ERROR;
@@ -378,7 +384,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
// report back. If not OK show output from error, other return the HTTP code
if (return_code != CommandRet::OK) {
if ((value == nullptr) || (strlen(value) == 0)) {
if (single_command) {
LOG_ERROR("Command '%s' failed with error '%s'", cmd, FL_(cmdRet)[return_code]);
} else {
LOG_ERROR("Command '%s: %s' failed with error '%s'", cmd, value, FL_(cmdRet)[return_code]);
@@ -543,7 +549,7 @@ void Command::show(uuid::console::Shell & shell, uint8_t device_type, bool verbo
// verbose mode
shell.printfln("\n%s%s %s:%s", COLOR_BOLD_ON, COLOR_YELLOW, EMSdevice::device_type_2_device_name(device_type), COLOR_RESET);
// we hard code 'info' and 'commmands' commands so print them first
// we hard code 'info' and 'commands' commands so print them first
if (show_info) {
shell.printf(" info:\t\t\t\t%slists all values %s*", COLOR_BRIGHT_CYAN, COLOR_BRIGHT_GREEN);
shell.println(COLOR_RESET);
@@ -641,7 +647,7 @@ void Command::show_devices(uuid::console::Shell & shell) {
shell.println();
}
// 'show commmands' : output list of all commands to console
// 'show commands' : output list of all commands to console
// calls show with verbose mode set
void Command::show_all(uuid::console::Shell & shell) {
shell.printfln("Showing all available commands (%s*%s=authentication not required):", COLOR_BRIGHT_GREEN, COLOR_RESET);

View File

@@ -563,15 +563,21 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
}
}
if (return_code == CommandRet::OK && json.size()) {
if (json.containsKey("api_data")) {
String data = json["api_data"].as<String>();
shell.println(data.c_str());
if (return_code == CommandRet::OK) {
if (json.size()) {
if (json.containsKey("api_data")) {
String data = json["api_data"].as<String>();
shell.println(data.c_str());
return;
}
serializeJsonPretty(doc, shell);
shell.println();
return;
} else {
// show message if no data returned (e.g. for analogsensor, temperaturesensor, custom)
shell.println("No data.");
return;
}
serializeJsonPretty(doc, shell);
shell.println();
return;
}
if (return_code == CommandRet::NOT_FOUND) {
@@ -637,7 +643,11 @@ void EMSESPShell::stopped() {
void EMSESPShell::display_banner() {
println();
printfln("┌───────────────────────────────────────┐");
#ifndef EMSESP_DEBUG
printfln("│ %sEMS-ESP version %-20s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF);
#else
printfln("│ %sEMS-ESP version %s%-8s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, " (D)", COLOR_BOLD_OFF);
#endif
printfln("│ │");
printfln("│ %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET);
printfln("│ %ssu%s to access admin commands │", COLOR_UNDERLINE, COLOR_RESET);
@@ -685,6 +695,9 @@ void EMSESPShell::end_of_transmission() {
}
void EMSESPShell::main_help_function(Shell & shell, const std::vector<std::string> & arguments) {
shell.println();
shell.printfln("%s%sEMS-ESP version %s%s", COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_RESET);
shell.println();
shell.print_all_available_commands();
}

View File

@@ -189,6 +189,11 @@
#define EMSESP_DEFAULT_PUBLISH_TIME 10
#endif
// default for scheduler etc
#ifndef EMSESP_DEFAULT_PUBLISH_TIME_OTHER
#define EMSESP_DEFAULT_PUBLISH_TIME_OTHER 60
#endif
#ifndef EMSESP_DEFAULT_PUBLISH_HEARTBEAT
#define EMSESP_DEFAULT_PUBLISH_HEARTBEAT 60
#endif

View File

@@ -100,7 +100,7 @@ const char * EMSdevice::brand_to_char() {
}
}
// returns the short name of the device, used in MQTT and console commands, all lowercase
// returns the short name of the device, used in MQTT and console commands, all lowercase, no translated
const char * EMSdevice::device_type_2_device_name(const uint8_t device_type) {
switch (device_type) {
case DeviceType::SYSTEM:
@@ -1425,7 +1425,7 @@ void EMSdevice::dump_telegram_info(std::vector<TelegramFunctionDump> & telegram_
}
#endif
// builds json for a specific device value / entity
// builds json for a specific EMS device value / entity
// cmd is the endpoint or name of the device entity
// returns false if failed, otherwise true
bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t tag) {
@@ -1582,6 +1582,9 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t
json["writeable"] = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
json["visible"] = !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE);
// TODO refactor to remove containsKey as it's costly and not advisable to use it
// https://arduinojson.org/v7/api/jsonobject/containskey/#avoid
// if there is no value, mention it
if (!json.containsKey(value)) {
json[value] = "not set";
@@ -1590,30 +1593,22 @@ bool EMSdevice::get_value_info(JsonObject output, const char * cmd, const int8_t
// if we're filtering on an attribute, go find it
if (attribute_s) {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Attribute '%s'", attribute_s);
EMSESP::logger().debug("[DEBUG] fetching single attribute '%s'", attribute_s);
#endif
if (json.containsKey(attribute_s)) {
String data = json[attribute_s].as<String>();
std::string data = json[attribute_s].as<std::string>();
output.clear();
output["api_data"] = data;
output["api_data"] = data; // always as string
return true;
} else {
char error[100];
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, command_s);
output.clear();
output["message"] = error;
return false;
}
return EMSESP::return_not_found(output, "attribute", command_s); // not found
}
return true;
}
}
char error[100];
snprintf(error, sizeof(error), "cannot find values for entity '%s'", cmd);
json["message"] = error;
return false;
return false; // not found, but don't return a message error yet
}
// mqtt publish all single values from one device (used for time schedule)
@@ -1941,7 +1936,7 @@ std::string EMSdevice::name() {
// returns true on success.
int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result) {
// find device value by shortname
// TODO linear search is inefficient
// TODO replace linear search which is inefficient
const auto & it = std::find_if(devicevalues_.begin(), devicevalues_.end(), [&](const DeviceValue & x) { return x.tag == tag && x.short_name == shortname; });
if (it == devicevalues_.end())
return -1;

View File

@@ -563,6 +563,7 @@ void EMSESP::publish_all(bool force) {
publish_device_values(EMSdevice::DeviceType::WATER);
publish_other_values(); // switch and heat pump, ...
publish_sensor_values(true); // includes temperature and analog sensors
system_.send_heartbeat();
}
}
@@ -683,10 +684,11 @@ void EMSESP::publish_other_values() {
publish_device_values(EMSdevice::DeviceType::EXTENSION);
publish_device_values(EMSdevice::DeviceType::ALERT);
publish_device_values(EMSdevice::DeviceType::POOL);
// other devices without values yet
// other EMS devices without values yet
// publish_device_values(EMSdevice::DeviceType::GATEWAY);
// publish_device_values(EMSdevice::DeviceType::CONNECT);
// publish_device_values(EMSdevice::DeviceType::GENERIC);
webSchedulerService.publish();
webCustomEntityService.publish();
}
@@ -740,19 +742,19 @@ void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
buffer = nullptr;
}
// builds json with the detail of each value, for an EMS device
// builds json with the detail of each value, for a given EMS device
// for other types like sensors, scheduler, custom entities it will process single commands like 'info', 'values', 'commands'...
bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8_t id, const uint8_t devicetype) {
// check first for EMS devices
for (const auto & emsdevice : emsdevices) {
if (emsdevice->device_type() == devicetype) {
if (emsdevice->get_value_info(root, cmd, id)) {
return true;
}
return emsdevice->get_value_info(root, cmd, id);
}
}
// temperaturesensor
// check for other devices...
// temperature sensor
if (devicetype == DeviceType::TEMPERATURESENSOR) {
return temperaturesensor_.get_value_info(root, cmd, id);
}
@@ -772,14 +774,20 @@ bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8
return webCustomEntityService.get_value_info(root, cmd);
}
// system
if (devicetype == DeviceType::SYSTEM) {
return system_.get_value_info(root, cmd);
}
char error[100];
snprintf(error, sizeof(error), "cannot find values for entity '%s'", cmd);
root["message"] = error;
return false; // not found
}
// sends JSON error message, used with API calls
bool EMSESP::return_not_found(JsonObject output, const char * msg, const char * cmd) {
output.clear();
char error[100];
snprintf(error, sizeof(error), "cannot find %s in '%s'", msg, cmd);
output["message"] = error;
return false;
}
@@ -1617,6 +1625,8 @@ void EMSESP::start() {
if (!nvs_.begin("ems-esp", false, "nvs1")) { // try bigger nvs partition on 16M flash first
nvs_.begin("ems-esp", false, "nvs"); // fallback to small nvs
}
LOG_DEBUG("NVS device information: %s", system_.getBBQKeesGatewayDetails().c_str());
#ifndef EMSESP_STANDALONE
LOG_INFO("Starting EMS-ESP version %s from %s partition", EMSESP_APP_VERSION, esp_ota_get_running_partition()->label); // welcome message
#else
@@ -1631,6 +1641,8 @@ void EMSESP::start() {
system_.system_restart();
};
webSettingsService.begin(); // load EMS-ESP Application settings...
// do any system upgrades

View File

@@ -213,6 +213,8 @@ class EMSESP {
static void scan_devices();
static void clear_all_devices();
static bool return_not_found(JsonObject output, const char * msg, const char * cmd);
static std::vector<std::unique_ptr<EMSdevice>> emsdevices;
// services

View File

@@ -67,6 +67,7 @@ MAKE_WORD(publish)
MAKE_WORD(board_profile)
MAKE_WORD(setvalue)
MAKE_WORD(service)
MAKE_WORD(message)
// for commands
MAKE_WORD(call)

View File

@@ -75,6 +75,7 @@ MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entit
MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "", "uzyskaj odpowiedź", "", "", "gelen cevap", "", "získať odpoveď") // TODO translate
MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "", "", "", "uruchom tryśnięcie zimnej wody", "", "", "soğuk su gönder", "", "pošlite studenú dávku vody") // TODO translate
MAKE_WORD_TRANSLATION(allvalues_cmd, "output all values", "", "", "", "wyświetl wszystkie wartości", "", "", "", "", "vypísať všetky hodnoty") // TODO translate
MAKE_WORD_TRANSLATION(message_cmd, "send a message", "", "", "", "", "", "", "", "", "") // TODO translate
// tags
MAKE_WORD_TRANSLATION(tag_hc1, "hc1", "HK1", "hc1", "VK1", "OG1", "hc1", "hc1", "ID1", "hc1", "hc1")

View File

@@ -68,7 +68,7 @@ bool Modbus::check_parameter_order() {
} else if (prev == nullptr) {
LOG_ERROR("Error checking modbus parameters %s.", mi.short_name);
return false;
} else if(!prev->isLessThan(mi)) {
} else if (!prev->isLessThan(mi)) {
LOG_ERROR("Error in modbus parameters: %s must be listed before %s.", mi.short_name, prev->short_name);
return false;
}

View File

@@ -1,6 +1,12 @@
#include "modbus.h"
#include "emsdevice.h"
/*
* This file is auto-generated by the update_modbus_registers.sh script. Do not modify.
*/
// clang-format off
namespace emsesp {
using dt = EMSdevice::DeviceType;
@@ -513,3 +519,6 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
};
} // namespace emsesp
// clang-format off

View File

@@ -226,6 +226,18 @@ bool System::command_syslog_level(const char * value, const int8_t id) {
}
*/
// send message - to log and MQTT
bool System::command_message(const char * value, const int8_t id) {
if (value == nullptr || value[0] == '\0') {
return false; // must have a string value
}
LOG_INFO("Message: %s", value);
Mqtt::queue_publish(F_(message), value);
return true;
}
// watch
bool System::command_watch(const char * value, const int8_t id) {
uint8_t w = 0xff;
@@ -831,10 +843,10 @@ void System::commands_init() {
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, FL_(send_cmd), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, FL_(fetch_cmd), CommandFlag::ADMIN_ONLY);
// restart and watch (and test) are also exposed as Console commands
// restart, watch, message (and test) are also exposed as Console commands
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_cmd), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), System::command_watch, FL_(watch_cmd));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(message), System::command_message, FL_(message_cmd));
#if defined(EMSESP_TEST)
Command::add(EMSdevice::DeviceType::SYSTEM, ("test"), System::command_test, FL_(test_cmd));
#endif
@@ -1107,7 +1119,7 @@ bool System::check_restore() {
#ifndef EMSESP_STANDALONE
// see if we have a temp file, if so try and read it
// TODO find a nicer way to see if a file exists without reporting an error
// TODO find a nicer way to see if a file exists without reporting an error, like using lfs_stat. exists() uses open so same problem.
File new_file = LittleFS.open(TEMP_FILENAME_PATH);
if (new_file) {
JsonDocument jsonDocument;
@@ -1288,52 +1300,61 @@ bool System::get_value_info(JsonObject root, const char * command) {
LOG_ERROR("empty system command");
return false;
}
char cmd[COMMAND_MAX_LENGTH];
strlcpy(cmd, command, sizeof(cmd));
char * val = strstr(cmd, "/value");
if (val) {
val[0] = '\0';
}
char * dash = strchr(cmd, '/');
if (dash) {
*dash = '\0';
dash++;
char * slash = strchr(cmd, '/');
if (slash) {
*slash = '\0';
slash++;
}
if (command_info("", 0, root)) {
std::string s;
// Loop through all the key-value pairs in root to find the key case independent
if (dash) { // search the nest first
for (JsonPair p : root) {
if (p.value().is<JsonObject>() && Helpers::toLower(p.key().c_str()) == cmd) {
for (JsonPair p1 : p.value().as<JsonObject>()) {
if (Helpers::toLower(p1.key().c_str()) == dash && !p1.value().is<JsonObject>()) {
s = p1.value().as<std::string>();
break;
}
// fetch all the data from the system
(void)command_info("", 0, root);
// check for hardcoded "info"
if (Helpers::toLower(cmd) == F_(info)) {
return true;
}
std::string s;
// Loop through all the key-value pairs in root to find the key, case independent
if (slash) { // search the top level first
for (JsonPair p : root) {
if (p.value().is<JsonObject>() && Helpers::toLower(p.key().c_str()) == cmd) {
for (JsonPair p1 : p.value().as<JsonObject>()) {
if (Helpers::toLower(p1.key().c_str()) == slash && !p1.value().is<JsonObject>()) {
s = p1.value().as<std::string>();
break;
}
}
}
} else {
for (JsonPair p : root) {
if (Helpers::toLower(p.key().c_str()) == cmd && !p.value().is<JsonObject>()) {
s = p.value().as<std::string>();
break;
}
}
}
if (!s.empty()) {
root.clear();
if (val) {
root["api_data"] = s;
} else {
root["value"] = s;
} else {
for (JsonPair p : root) {
if (Helpers::toLower(p.key().c_str()) == cmd && !p.value().is<JsonObject>()) {
s = p.value().as<std::string>();
break;
}
return true;
}
}
root.clear();
LOG_ERROR("system command '%s' not found", command);
return false;
if (!s.empty()) {
root.clear();
if (val) {
root["api_data"] = s;
} else {
root["value"] = s;
}
return true; // found
}
return EMSESP::return_not_found(root, "data", command); // not found
}
// export status information including the device information
@@ -1342,7 +1363,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
JsonObject node;
// System
node = output["System Info"].to<JsonObject>();
node = output["System"].to<JsonObject>();
node["version"] = EMSESP_APP_VERSION;
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["uptime (seconds)"] = uuid::get_uptime_sec();
@@ -1359,9 +1380,9 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
#endif
node["reset reason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);
#ifndef EMSESP_STANDALONE
// Network Status
node = output["Network Info"].to<JsonObject>();
node = output["Network"].to<JsonObject>();
#ifndef EMSESP_STANDALONE
if (EMSESP::system_.ethernet_connected()) {
node["network"] = "Ethernet";
node["hostname"] = ETH.getHostname();
@@ -1384,10 +1405,15 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
// node["IPv6 address"] = uuid::printable_to_string(WiFi.localIPv6());
// }
}
#else
// for testing
node["network"] = "WiFi";
node["hostname"] = "ems-esp";
node["RSSI"] = -23;
#endif
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) {
if (WiFi.status() == WL_CONNECTED && !settings.bssid.isEmpty()) {
node["BSSID"] = "set";
node["BSSID"] = "set"; // TODO why is this not the actual value?
}
node["TxPower setting"] = settings.tx_power;
node["static ip config"] = settings.staticIPConfig;
@@ -1409,7 +1435,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
#endif
// NTP status
node = output["NTP Info"].to<JsonObject>();
node = output["NTP"].to<JsonObject>();
#ifndef EMSESP_STANDALONE
node["NTP status"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected";
EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
@@ -1421,7 +1447,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
#endif
// MQTT Status
node = output["MQTT Info"].to<JsonObject>();
node = output["MQTT"].to<JsonObject>();
node["MQTT status"] = Mqtt::connected() ? F_(connected) : F_(disconnected);
if (Mqtt::enabled()) {
node["MQTT publishes"] = Mqtt::publish_count();
@@ -1456,7 +1482,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
});
// Syslog Status
node = output["Syslog Info"].to<JsonObject>();
node = output["Syslog"].to<JsonObject>();
node["enabled"] = EMSESP::system_.syslog_enabled_;
#ifndef EMSESP_STANDALONE
if (EMSESP::system_.syslog_enabled_) {
@@ -1468,7 +1494,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
#endif
// Sensor Status
node = output["Sensor Info"].to<JsonObject>();
node = output["Sensor"].to<JsonObject>();
if (EMSESP::sensor_enabled()) {
node["temperature sensors"] = EMSESP::temperaturesensor_.no_sensors();
node["temperature sensor reads"] = EMSESP::temperaturesensor_.reads();
@@ -1481,12 +1507,12 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
}
// API Status
node = output["API Info"].to<JsonObject>();
node = output["API"].to<JsonObject>();
node["API calls"] = WebAPIService::api_count();
node["API fails"] = WebAPIService::api_fails();
// EMS Bus Status
node = output["Bus Info"].to<JsonObject>();
node = output["Bus"].to<JsonObject>();
switch (EMSESP::bus_status()) {
case EMSESP::BUS_STATUS_OFFLINE:
node["bus status"] = "disconnected";
@@ -1589,7 +1615,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
}
}
return true;
return true; // this function always returns true!
}
#if defined(EMSESP_TEST)
@@ -1728,4 +1754,18 @@ bool System::ntp_connected() {
return ntp_connected_;
}
String System::getBBQKeesGatewayDetails() {
#ifndef EMSESP_STANDALONE
if (!EMSESP::nvs_.isKey("mfg")) {
return "";
}
if (EMSESP::nvs_.getString("mfg") != "BBQKees") {
return "";
}
return "BBQKees Gateway Model " + EMSESP::nvs_.getString("model") + " v" + EMSESP::nvs_.getString("hwrevision") + "/" + EMSESP::nvs_.getString("batch");
#else
return "";
#endif
}
} // namespace emsesp

View File

@@ -57,6 +57,7 @@ class System {
static bool command_restart(const char * value, const int8_t id);
static bool command_syslog_level(const char * value, const int8_t id);
static bool command_watch(const char * value, const int8_t id);
static bool command_message(const char * value, const int8_t id);
static bool command_info(const char * value, const int8_t id, JsonObject output);
static bool command_commands(const char * value, const int8_t id, JsonObject output);
static bool command_response(const char * value, const int8_t id, JsonObject output);
@@ -97,6 +98,8 @@ class System {
}
#endif
String getBBQKeesGatewayDetails();
void led_init(bool refresh);
void network_init(bool refresh);
void button_init(bool refresh);

View File

@@ -343,13 +343,14 @@ bool TemperatureSensor::updated_values() {
// called from emsesp.cpp for commands
bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, const int8_t id) {
// check of it a 'commmands' command
// check of it a 'commands' command
if (Helpers::toLower(cmd) == F_(commands)) {
return Command::list(EMSdevice::DeviceType::TEMPERATURESENSOR, output);
}
// return empty json if there are no sensors
if (sensors_.empty()) {
return true; // no sensors, return true
return true;
}
uint8_t show_all = 0;
@@ -391,28 +392,23 @@ bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, cons
for (const auto & sensor : sensors_) {
// match custom name or sensor ID
if (sensor_name == Helpers::toLower(sensor.name()) || sensor_name == Helpers::toLower(sensor.id())) {
// add values
// add all the data elements
addSensorJson(output, sensor);
// if we're filtering on an attribute, go find it
if (attribute_s) {
if (output.containsKey(attribute_s)) {
String data = output[attribute_s].as<String>();
std::string data = output[attribute_s].as<std::string>();
output.clear();
output["api_data"] = data;
output["api_data"] = data; // always as string
return true;
} else {
char error[100];
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, sensor_name);
output.clear();
output["message"] = error;
return false;
}
return EMSESP::return_not_found(output, "attribute", sensor_name); // not found
}
return true; // found a match, exit
}
}
return false; // not found
return EMSESP::return_not_found(output, "temperature sensor", cmd); // not found
}
void TemperatureSensor::addSensorJson(JsonObject output, const Sensor & sensor) {
@@ -629,15 +625,15 @@ void TemperatureSensor::test() {
uint8_t addr[ADDR_LEN] = {1, 2, 3, 4, 5, 6, 7, 8};
sensors_.emplace_back(addr);
sensors_.back().apply_customization();
sensors_.back().temperature_c = 123;
sensors_.back().temperature_c = 123; // 12.3
sensors_.back().read = true;
publish_sensor(sensors_.back()); // call publish single
// Sensor ID: 0B-0C0D-0E0F-1011
// Sensor ID: 0B_0C0D_0E0F_1011
uint8_t addr2[ADDR_LEN] = {11, 12, 13, 14, 15, 16, 17, 18};
sensors_.emplace_back(addr2);
sensors_.back().apply_customization();
sensors_.back().temperature_c = 456;
sensors_.back().temperature_c = 456; // 45.6
sensors_.back().read = true;
publish_sensor(sensors_.back()); // call publish single
}

View File

@@ -63,7 +63,7 @@ class TemperatureSensor {
bool apply_customization();
// initial values
int16_t temperature_c = EMS_VALUE_INT16_NOTSET;
int16_t temperature_c = EMS_VALUE_INT16_NOTSET; // value is *10
bool read = false;
bool ha_registered = false;

View File

@@ -315,7 +315,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.printfln("Testing adding a boiler, thermostat, all sensors, scheduler and custom entities...");
// setup fake data
EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the file
EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS
// add devices
test("general");
@@ -324,7 +324,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::webSchedulerService.test(); // add scheduler items
EMSESP::webCustomEntityService.test(); // add custom entities
shell.invoke_command("show devices");
// shell.invoke_command("show devices");
// shell.invoke_command("show values");
// shell.invoke_command("call system allvalues");
// shell.invoke_command("call system publish");
@@ -944,37 +944,149 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
ok = true;
}
if (command == "api_values") {
shell.printfln("Testing API getting values");
Mqtt::ha_enabled(false);
Mqtt::nested_format(1);
// Mqtt::send_response(false);
// EMSESP::bool_format(BOOL_FORMAT_10); // BOOL_FORMAT_10_STR
if (command == "api3") {
shell.printfln("Testing API getting values from system");
EMSESP::system_.bool_format(BOOL_FORMAT_TRUEFALSE); // BOOL_FORMAT_TRUEFALSE_STR
test("boiler");
test("thermostat");
ok = true;
bool single;
// single = true;
single = false;
AsyncWebServerRequest request;
JsonDocument doc;
JsonVariant json;
request.method(HTTP_GET);
request.url("/api/boiler/values");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/dhw/circ");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/dhw/circ/fullname");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/selburnpow/value");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/dhw/chargetype/writeable");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/flamecurr/value");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/flamecurr/bad");
EMSESP::webAPIService.webAPIService(&request);
ok = true;
// load devices
test("boiler");
// test("thermostat");
if (single) {
// run dedicated tests only
EMSESP::webCustomEntityService.test(); // custom entities
EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS
EMSESP::temperaturesensor_.test(); // add temperature sensors
EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions
// request.url("/api/analogsensor/test_analog10/bad");
// EMSESP::webAPIService.webAPIService(&request);
} else {
EMSESP::webCustomEntityService.test(); // custom entities
EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS
EMSESP::temperaturesensor_.test(); // add temperature sensors
EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions
// boiler
request.url("/api/boiler");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/commands");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/values");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/info");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/entities");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/comfort");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/comfort/value");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/comfort/fullname");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/outdoortemp");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/dhw/chargetype/writeable");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/flamecurr/value");
EMSESP::webAPIService.webAPIService(&request);
// custom
request.url("/api/custom");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/custom/seltemp");
EMSESP::webAPIService.webAPIService(&request);
// system
request.url("/api/system");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/system/settings/locale");
EMSESP::webAPIService.webAPIService(&request);
// scheduler
request.url("/api/scheduler/info");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/scheduler/test_scheduler");
EMSESP::webAPIService.webAPIService(&request);
// temperaturesensor
request.url("/api/temperaturesensor/test_sensor2");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/temperaturesensor/0B_0C0D_0E0F_1011");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/temperaturesensor/test_sensor2/value");
EMSESP::webAPIService.webAPIService(&request);
// analogsensor
request.url("/api/analogsensor");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/analogsensor/test_analog1");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/analogsensor/test_analog1/offset");
EMSESP::webAPIService.webAPIService(&request);
//
// This next batch should all fail
//
Serial.printf("%s**** Testing bad urls ****\n%s", COLOR_RED, COLOR_RESET);
Serial.println();
// boiler
request.url("/api/boiler/bad");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/bad/value");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler2/bad");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/bad");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/bad/value");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/boiler/comfort/valu");
EMSESP::webAPIService.webAPIService(&request);
// system
request.url("/api/system/settings/locale2");
EMSESP::webAPIService.webAPIService(&request);
// scheduler
request.url("/api/scheduler/test_scheduler2");
EMSESP::webAPIService.webAPIService(&request);
// custom
request.url("/api/custom/seltemp2");
EMSESP::webAPIService.webAPIService(&request);
// temperaturesensor
request.url("/api/temperaturesensor/test_sensor20");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/temperaturesensor/0B_0C0D_0E0F_XXXX");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/temperaturesensor/test_sensor2/bad");
EMSESP::webAPIService.webAPIService(&request);
// analogsensor
request.url("/api/analogsensor/test_analog1/bad");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/analogsensor/test_analog10");
EMSESP::webAPIService.webAPIService(&request);
request.url("/api/analogsensor/test_analog10/bad");
EMSESP::webAPIService.webAPIService(&request);
}
}
if (command == "mqtt_post") {

View File

@@ -41,13 +41,13 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "310"
// #define EMSESP_DEBUG_DEFAULT "render"
// #define EMSESP_DEBUG_DEFAULT "api"
#define EMSESP_DEBUG_DEFAULT "api3"
// #define EMSESP_DEBUG_DEFAULT "crash"
// #define EMSESP_DEBUG_DEFAULT "dv"
// #define EMSESP_DEBUG_DEFAULT "lastcode"
// #define EMSESP_DEBUG_DEFAULT "2thermostats"
// #define EMSESP_DEBUG_DEFAULT "temperature"
// #define EMSESP_DEBUG_DEFAULT "analog"
// #define EMSESP_DEBUG_DEFAULT "api_values"
// #define EMSESP_DEBUG_DEFAULT "mqtt_post"
// #define EMSESP_DEBUG_DEFAULT "api_wwmode"
// #define EMSESP_DEBUG_DEFAULT "customization"

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.7.0-dev.23"
#define EMSESP_APP_VERSION "3.7.0-dev.25"

View File

@@ -128,9 +128,9 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
if (return_code != CommandRet::OK) {
char error[100];
if (output.size()) {
snprintf(error, sizeof(error), "API failed with error %s (%s)", (const char *)output["message"], Command::return_code_string(return_code).c_str());
snprintf(error, sizeof(error), "API call failed. %s (%s)", (const char *)output["message"], Command::return_code_string(return_code).c_str());
} else {
snprintf(error, sizeof(error), "API failed with error %s", Command::return_code_string(return_code).c_str());
snprintf(error, sizeof(error), "API call failed (%s)", Command::return_code_string(return_code).c_str());
}
emsesp::EMSESP::logger().err(error);
api_fails_++;
@@ -138,17 +138,27 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
// if we're returning single values, just sent as plain text
// https://github.com/emsesp/EMS-ESP32/issues/462#issuecomment-1093877210
if (output.containsKey("api_data")) {
String data = output["api_data"].as<String>();
request->send(200, "text/plain; charset=utf-8", data);
const char * api_data = output["api_data"];
if (api_data) {
request->send(200, "text/plain; charset=utf-8", api_data);
#if defined(EMSESP_STANDALONE)
Serial.printf("%sweb output: %s[%s] %s(200)%s ", COLOR_WHITE, COLOR_BRIGHT_CYAN, request->url().c_str(), COLOR_BRIGHT_GREEN, COLOR_MAGENTA);
serializeJson(output, Serial);
Serial.println(COLOR_RESET);
Serial.println();
#endif
api_count_++;
delete response;
return;
}
// send the json that came back from the command call
// FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED = 400 (bad request), 200 (OK), 400 (not found), 400 (bad request), 401 (unauthorized)
int ret_codes[6] = {400, 200, 400, 400, 401, 400};
// sequence is FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED, INVALID
// 400 (bad request)
// 200 (OK)
// 404 (not found)
// 401 (unauthorized)
int ret_codes[6] = {400, 200, 404, 400, 401, 400};
response->setCode(ret_codes[return_code]);
response->setLength();
response->setContentType("application/json; charset=utf-8");
@@ -156,15 +166,11 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
api_count_++;
#if defined(EMSESP_STANDALONE)
Serial.print(COLOR_YELLOW);
Serial.print("data: ");
if (output.size()) {
serializeJson(output, Serial);
}
Serial.print(" (response code ");
Serial.print(ret_codes[return_code]);
Serial.println(")");
Serial.print(COLOR_RESET);
Serial.printf("%sweb output: %s[%s]", COLOR_WHITE, COLOR_BRIGHT_CYAN, request->url().c_str());
Serial.printf(" %s(%d)%s ", ret_codes[return_code] == 200 ? COLOR_BRIGHT_GREEN : COLOR_BRIGHT_RED, ret_codes[return_code], COLOR_YELLOW);
serializeJson(output, Serial);
Serial.println(COLOR_RESET);
Serial.println();
#endif
}

View File

@@ -237,13 +237,12 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem en
}
break;
case DeviceValueType::STRING:
default:
// if no type treat it as a string
if (entity.data.length() > 0) {
output[name] = entity.data;
}
break;
default:
// EMSESP::logger().warning("unknown value type");
break;
}
}
@@ -269,7 +268,8 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
return true;
}
// if no entries, return empty json
// if no custom entries, return empty json
// even if we're looking for a specific entity
// https://github.com/emsesp/EMS-ESP32/issues/1297
if (customEntityItems_->size() == 0) {
return true;
@@ -287,6 +287,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
char command_s[COMMAND_MAX_LENGTH];
strlcpy(command_s, Helpers::toLower(cmd).c_str(), sizeof(command_s));
char * attribute_s = nullptr;
// check specific attribute to fetch instead of the complete record
char * breakp = strchr(command_s, '/');
if (breakp) {
@@ -315,21 +316,16 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
output["bytes"] = (uint8_t)entity.factor;
}
}
render_value(output, entity, true);
render_value(output, entity, true); // create the "value" field
if (attribute_s) {
if (output.containsKey(attribute_s)) {
String data = output[attribute_s].as<String>();
std::string data = output[attribute_s].as<std::string>();
output.clear();
output["api_data"] = data;
output["api_data"] = data; // always as string
return true;
} else {
char error[100];
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, command_s);
output.clear();
output["message"] = error;
return false;
}
return EMSESP::return_not_found(output, "attribute", command_s); // not found
}
}
@@ -338,8 +334,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
}
}
output["message"] = "unknown command";
return false;
return EMSESP::return_not_found(output, "custom entity", cmd); // not found
}
// publish single value
@@ -641,8 +636,10 @@ bool WebCustomEntityService::get_value(std::shared_ptr<const Telegram> telegram)
void WebCustomEntityService::test() {
update([&](WebCustomEntity & webCustomEntity) {
webCustomEntity.customEntityItems.clear();
auto entityItem = CustomEntityItem();
// test 1
auto entityItem = CustomEntityItem();
entityItem.id = 1;
entityItem.ram = 0;
entityItem.device_id = 8;
entityItem.type_id = 24;
@@ -656,6 +653,7 @@ void WebCustomEntityService::test() {
webCustomEntity.customEntityItems.push_back(entityItem);
// test 2
entityItem.id = 2;
entityItem.ram = 0;
entityItem.device_id = 24;
entityItem.type_id = 677;
@@ -668,7 +666,8 @@ void WebCustomEntityService::test() {
entityItem.data = "48";
webCustomEntity.customEntityItems.push_back(entityItem);
// test 2
// test 3
entityItem.id = 3;
entityItem.ram = 1;
entityItem.device_id = 0;
entityItem.type_id = 0;
@@ -681,6 +680,21 @@ void WebCustomEntityService::test() {
entityItem.data = "14";
webCustomEntity.customEntityItems.push_back(entityItem);
// test 4
entityItem.id = 4;
entityItem.ram = 1;
entityItem.device_id = 0;
entityItem.type_id = 0;
entityItem.offset = 0;
entityItem.factor = 1;
entityItem.name = "seltemp";
entityItem.uom = 0;
entityItem.value_type = 8;
entityItem.writeable = true;
entityItem.data = "14";
entityItem.value = 12;
webCustomEntity.customEntityItems.push_back(entityItem);
return StateUpdateResult::CHANGED; // persist the changes
});
}

View File

@@ -158,6 +158,7 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques
EMSESP::system_.restart_requested(true);
return;
}
// failed
AsyncWebServerResponse * response = request->beginResponse(400); // bad request
request->send(response);

View File

@@ -212,7 +212,7 @@ void WebDataService::device_data(AsyncWebServerRequest * request) {
}
// invalid
AsyncWebServerResponse * response = request->beginResponse(400);
AsyncWebServerResponse * response = request->beginResponse(400); // bad request
request->send(response);
}

View File

@@ -128,7 +128,7 @@ bool WebSchedulerService::command_setvalue(const char * value, const int8_t id,
// process json output for info/commands and value_info
bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
// check of it a 'commmands' command
// check of it a 'commands' command
if (Helpers::toLower(cmd) == F_(commands)) {
output[F_(info)] = Helpers::translated_word(FL_(info_cmd));
output[F_(commands)] = Helpers::translated_word(FL_(commands_cmd));
@@ -161,7 +161,11 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
}
}
return (output.size() > 0);
if (output.size()) {
return true;
}
return EMSESP::return_not_found(output, "schedule", cmd); // not found
}
char command_s[COMMAND_MAX_LENGTH];
@@ -196,17 +200,17 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
}
if (attribute_s && output.containsKey(attribute_s)) {
String data = output[attribute_s].as<String>();
std::string data = output[attribute_s].as<std::string>();
output.clear();
output["api_data"] = data;
output["api_data"] = data; // always as a string
return true;
}
if (output.size()) {
return true;
}
output["message"] = "unknown command";
return false;
return EMSESP::return_not_found(output, "schedule", cmd); // not found
}
// publish single value
@@ -304,8 +308,10 @@ void WebSchedulerService::publish(const bool force) {
}
}
}
ha_registered_ = ha_created;
if (doc.size() > 0) {
if (!doc.isNull()) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "%s_data", F_(scheduler));
Mqtt::queue_publish(topic, doc.as<JsonObject>());
@@ -340,6 +346,7 @@ bool WebSchedulerService::command(const char * cmd, const char * data) {
// prefix "api/" to command string
char command_str[COMMAND_MAX_LENGTH];
snprintf(command_str, sizeof(command_str), "/api/%s", cmd);
uint8_t return_code = Command::process(command_str, true, input, output); // admin set
if (return_code == CommandRet::OK) {
@@ -471,23 +478,69 @@ void WebSchedulerService::loop() {
// hard coded tests
#if defined(EMSESP_TEST)
void WebSchedulerService::test() {
update([&](WebScheduler & webScheduler) {
webScheduler.scheduleItems.clear();
// test 1
auto si = ScheduleItem();
si.active = true;
si.flags = 1;
si.time = "12:00";
si.cmd = "system/fetch";
si.value = "10";
si.name = "test_scheduler";
si.elapsed_min = 0;
si.retry_cnt = 0xFF; // no startup retries
static bool already_added = false;
if (!already_added) {
update([&](WebScheduler & webScheduler) {
// webScheduler.scheduleItems.clear();
// test 1
auto si = ScheduleItem();
si.active = true;
si.flags = 1;
si.time = "12:00";
si.cmd = "system/fetch";
si.value = "10";
si.name = "test_scheduler";
si.elapsed_min = 0;
si.retry_cnt = 0xFF; // no startup retries
webScheduler.scheduleItems.push_back(si);
webScheduler.scheduleItems.push_back(si);
already_added = true;
return StateUpdateResult::CHANGED; // persist the changes
});
return StateUpdateResult::CHANGED; // persist the changes
});
}
// test shunting yard
std::string test_cmd = "system/message";
std::string test_value;
// should output 'locale is en'
test_value = "\"locale is \"system/settings/locale";
command(test_cmd.c_str(), compute(test_value).c_str());
// test with negative value
// should output 'rssi is -23'
test_value = "\"rssi is \"0+system/network/rssi";
command(test_cmd.c_str(), compute(test_value).c_str());
// should output 'rssi is -23 dbm'
test_value = "\"rssi is \"(system/network/rssi)\" dBm\"";
command(test_cmd.c_str(), compute(test_value).c_str());
test_value = "(custom/seltemp/value)";
command(test_cmd.c_str(), compute(test_value).c_str());
test_value = "\"seltemp=\"(custom/seltemp/value)";
command(test_cmd.c_str(), compute(test_value).c_str());
test_value = "(custom/seltemp)";
command(test_cmd.c_str(), compute(test_value).c_str());
test_value = "(boiler/outdoortemp)";
command(test_cmd.c_str(), compute(test_value).c_str());
test_value = "boiler/flowtempoffset";
command(test_cmd.c_str(), compute(test_value).c_str());
test_value = "(boiler/flowtempoffset/value)";
command(test_cmd.c_str(), compute(test_value).c_str());
test_value = "(boiler/storagetemp1/value)";
command(test_cmd.c_str(), compute(test_value).c_str());
// (14 - 40) * 2.8 + 5 = -67.8
test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5";
command(test_cmd.c_str(), compute(test_value).c_str());
}
#endif

View File

@@ -136,6 +136,9 @@ void WebStatusService::ESPsystemStatus(AsyncWebServerRequest * request) {
root["has_loader"] = (buffer != 0xFFFFFFFFFFFFFFFF && running->size != partition->size);
}
}
root["model"] = EMSESP::system_.getBBQKeesGatewayDetails();
#endif
response->setLength();

View File

@@ -386,9 +386,8 @@ std::string to_string(double d) {
// RPN calculator
std::string calculate(const std::string & expr) {
auto expr_new = emsesp::Helpers::toLower(expr);
// emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str());
commands(expr_new);
// emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str());
const auto tokens = exprToTokens(expr_new);
if (tokens.empty()) {
return "";
@@ -397,6 +396,16 @@ std::string calculate(const std::string & expr) {
if (queue.empty()) {
return "";
}
/*
// debug only print tokens
#ifdef EMSESP_STANDALONE
for (const auto & t : queue) {
emsesp::EMSESP::logger().debug("shunt token: %s(%d)", t.str.c_str(), t.type);
}
#endif
*/
std::vector<std::string> stack;
while (!queue.empty()) {
@@ -556,7 +565,16 @@ std::string calculate(const std::string & expr) {
break;
}
}
return stack.back();
// 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();
}
// check for multiple instances of <cond> ? <expr1> : <expr2>
@@ -586,5 +604,6 @@ std::string compute(const std::string & expr) {
}
q = expr_new.find_first_of("?"); // search next instance
}
return calculate(expr_new);
}