mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-06-19 06:16:35 +03:00
@@ -8,6 +8,7 @@ For more details go to [emsesp.org](https://emsesp.org/).
|
|||||||
|
|
||||||
- user-requested LED blink [#3063](https://github.com/emsesp/EMS-ESP32/issues/3063)
|
- user-requested LED blink [#3063](https://github.com/emsesp/EMS-ESP32/issues/3063)
|
||||||
- KM300 at address 0x4A [#3084](https://github.com/emsesp/EMS-ESP32/issues/3084)
|
- KM300 at address 0x4A [#3084](https://github.com/emsesp/EMS-ESP32/issues/3084)
|
||||||
|
- Commands Service that can be called via MQTT or API or used in the Scheduler Service
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -19,15 +19,15 @@
|
|||||||
"typesafe-i18n": "typesafe-i18n --no-watch",
|
"typesafe-i18n": "typesafe-i18n --no-watch",
|
||||||
"build-webUI": "typesafe-i18n --no-watch && vite build && node progmem-generator.js",
|
"build-webUI": "typesafe-i18n --no-watch && vite build && node progmem-generator.js",
|
||||||
"format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'",
|
"format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'",
|
||||||
"lint": "eslint . --fix",
|
"lint": "typesafe-i18n --no-watch && eslint . --fix",
|
||||||
"standalone-devcontainer": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite --host\""
|
"standalone-devcontainer": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite --host\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alova/adapter-xhr": "2.3.1",
|
"@alova/adapter-xhr": "2.3.1",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/icons-material": "^9.0.1",
|
"@mui/icons-material": "^9.1.1",
|
||||||
"@mui/material": "^9.0.1",
|
"@mui/material": "^9.1.1",
|
||||||
"@table-library/react-table-library": "4.1.15",
|
"@table-library/react-table-library": "4.1.15",
|
||||||
"alova": "^3.5.1",
|
"alova": "^3.5.1",
|
||||||
"async-validator": "^4.2.5",
|
"async-validator": "^4.2.5",
|
||||||
@@ -47,18 +47,18 @@
|
|||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@preact/preset-vite": "^2.10.5",
|
"@preact/preset-vite": "^2.10.5",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||||
"@types/node": "^25.9.2",
|
"@types/node": "^25.9.3",
|
||||||
"@types/react": "^19.2.17",
|
"@types/react": "^19.2.17",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"concurrently": "^10.0.3",
|
"concurrently": "^10.0.3",
|
||||||
"eslint": "^10.4.1",
|
"eslint": "^10.4.1",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"prettier": "^3.8.3",
|
"prettier": "^3.8.4",
|
||||||
"rollup-plugin-visualizer": "^7.0.1",
|
"rollup-plugin-visualizer": "^7.0.1",
|
||||||
"terser": "^5.48.0",
|
"terser": "^5.48.0",
|
||||||
"typescript-eslint": "^8.60.1",
|
"typescript-eslint": "^8.61.0",
|
||||||
"vite": "^8.0.16",
|
"vite": "^8.0.16",
|
||||||
"vite-plugin-imagemin": "^0.6.1"
|
"vite-plugin-imagemin": "^0.6.1"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.5.2+sha512.71c631e382066efc25625d5cf029075de07b61b37f6e27350fbd84b1bda5864c8c1967adc280776b45c30a715c0359a3be08fef42d5bb09e2b99029979692916"
|
"packageManager": "pnpm@11.6.0+sha512.9a36518224080c6fe5165afdcfe79bfa118c29be703f3f462b1e32efe1e98e47e8750b148e08286250aad4113cc7993ca413c4e2cd447752708c2ee5751bc95f"
|
||||||
}
|
}
|
||||||
|
|||||||
316
interface/pnpm-lock.yaml
generated
316
interface/pnpm-lock.yaml
generated
@@ -18,11 +18,11 @@ importers:
|
|||||||
specifier: ^11.14.1
|
specifier: ^11.14.1
|
||||||
version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
||||||
'@mui/icons-material':
|
'@mui/icons-material':
|
||||||
specifier: ^9.0.1
|
specifier: ^9.1.1
|
||||||
version: 9.0.1(@mui/material@9.0.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
version: 9.1.1(@mui/material@9.1.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
||||||
'@mui/material':
|
'@mui/material':
|
||||||
specifier: ^9.0.1
|
specifier: ^9.1.1
|
||||||
version: 9.0.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
version: 9.1.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
'@table-library/react-table-library':
|
'@table-library/react-table-library':
|
||||||
specifier: 4.1.15
|
specifier: 4.1.15
|
||||||
version: 4.1.15(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
version: 4.1.15(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
@@ -71,13 +71,13 @@ importers:
|
|||||||
version: 10.0.1(eslint@10.4.1)
|
version: 10.0.1(eslint@10.4.1)
|
||||||
'@preact/preset-vite':
|
'@preact/preset-vite':
|
||||||
specifier: ^2.10.5
|
specifier: ^2.10.5
|
||||||
version: 2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@25.9.2)(terser@5.48.0))
|
version: 2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
|
||||||
'@trivago/prettier-plugin-sort-imports':
|
'@trivago/prettier-plugin-sort-imports':
|
||||||
specifier: ^6.0.2
|
specifier: ^6.0.2
|
||||||
version: 6.0.2(prettier@3.8.3)
|
version: 6.0.2(prettier@3.8.4)
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.9.2
|
specifier: ^25.9.3
|
||||||
version: 25.9.2
|
version: 25.9.3
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^19.2.17
|
specifier: ^19.2.17
|
||||||
version: 19.2.17
|
version: 19.2.17
|
||||||
@@ -94,8 +94,8 @@ importers:
|
|||||||
specifier: ^10.1.8
|
specifier: ^10.1.8
|
||||||
version: 10.1.8(eslint@10.4.1)
|
version: 10.1.8(eslint@10.4.1)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.8.3
|
specifier: ^3.8.4
|
||||||
version: 3.8.3
|
version: 3.8.4
|
||||||
rollup-plugin-visualizer:
|
rollup-plugin-visualizer:
|
||||||
specifier: ^7.0.1
|
specifier: ^7.0.1
|
||||||
version: 7.0.1(rolldown@1.0.3)
|
version: 7.0.1(rolldown@1.0.3)
|
||||||
@@ -103,14 +103,14 @@ importers:
|
|||||||
specifier: ^5.48.0
|
specifier: ^5.48.0
|
||||||
version: 5.48.0
|
version: 5.48.0
|
||||||
typescript-eslint:
|
typescript-eslint:
|
||||||
specifier: ^8.60.1
|
specifier: ^8.61.0
|
||||||
version: 8.60.1(eslint@10.4.1)(typescript@6.0.3)
|
version: 8.61.0(eslint@10.4.1)(typescript@6.0.3)
|
||||||
vite:
|
vite:
|
||||||
specifier: ^8.0.16
|
specifier: ^8.0.16
|
||||||
version: 8.0.16(@types/node@25.9.2)(terser@5.48.0)
|
version: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
|
||||||
vite-plugin-imagemin:
|
vite-plugin-imagemin:
|
||||||
specifier: ^0.6.1
|
specifier: ^0.6.1
|
||||||
version: 0.6.1(vite@8.0.16(@types/node@25.9.2)(terser@5.48.0))
|
version: 0.6.1(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -366,27 +366,27 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.31':
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||||
|
|
||||||
'@mui/core-downloads-tracker@9.0.1':
|
'@mui/core-downloads-tracker@9.1.1':
|
||||||
resolution: {integrity: sha512-GzamIIhZ1bH77dq7eKaeyRgJdkypsxin4jBFq2EMs4lBWRR0LFO1CSVMsoebn/VvjcNrnrOrjy48MkrkQUK2iw==}
|
resolution: {integrity: sha512-AupmMICbdJHqAh6FfOMaaiiIr7dfEgZJn5DFfiPuGNrbs+ZZy9cD1APwO0TSVBz5j08MJEEY6n7iC76/2wjMEA==}
|
||||||
|
|
||||||
'@mui/icons-material@9.0.1':
|
'@mui/icons-material@9.1.1':
|
||||||
resolution: {integrity: sha512-5PRpQjVLTNLyV/2J9J53Yz4R0tVbodG0BQDN2zQI1QBG1OPYM25ar+4N20eyFOfJT6zKglLzsnU70+zdVLaTkw==}
|
resolution: {integrity: sha512-OXhm9DajemStb58AumM06DuPhHTa3XD36TFD4yf6WtJyNRO5DfEZbbnHlBg/US2Y2oOXwM/XurMTBOD6L/YYZw==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@mui/material': ^9.0.1
|
'@mui/material': ^9.1.1
|
||||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@mui/material@9.0.1':
|
'@mui/material@9.1.1':
|
||||||
resolution: {integrity: sha512-voyCpeUxcSWLN7KPZuq0pGCIt726T9K6kiVM3XUcywZDAlZSarLHaUxJVQpospbjjOzN53hwyjo8s6KoWl6utw==}
|
resolution: {integrity: sha512-Wv+gInjrpf99l1Q0oHe0eOWGTnlbkzs5nowClX65KCT/2fyPMwcbFEEkUsOHdpcHhB5UAbz/d7jlwt5ajWVvlA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': ^11.5.0
|
'@emotion/react': ^11.5.0
|
||||||
'@emotion/styled': ^11.3.0
|
'@emotion/styled': ^11.3.0
|
||||||
'@mui/material-pigment-css': ^9.0.1
|
'@mui/material-pigment-css': ^9.1.1
|
||||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
@@ -400,8 +400,8 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@mui/private-theming@9.0.1':
|
'@mui/private-theming@9.1.1':
|
||||||
resolution: {integrity: sha512-pSIGq4Yw749KHEwlkYZWVERgHgwJELP6ODtBNUfV8V4oIb5H+h7IQDFXuk/b2oQccODK1enJAtiEzlgLZmq+8g==}
|
resolution: {integrity: sha512-oH6c+d6sJ1CZT0Vg2/fHdUQ5zvo9Pn+f+WWk0tlQliHqqIRdN32DZ7UxjalW3LUj4OkHbdWR31biWuLxK9i7Cg==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
@@ -410,8 +410,8 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@mui/styled-engine@9.0.0':
|
'@mui/styled-engine@9.1.1':
|
||||||
resolution: {integrity: sha512-9RLGdX4Jg0aQPRuvqh/OLzYSPlgd5zyEw5/1HIRfdavSiOd03WtUaGZH9/w1RoTYuRKwpgy0hpIFaMHIqPVIWg==}
|
resolution: {integrity: sha512-neaYKdJfvEG54q8efHLJR7swpHG/gfSv9xGqW5iTSMsubD7yPCPFrhVBt284j1DOF3uZaaDJSHQL7gz6jGF21Q==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': ^11.4.1
|
'@emotion/react': ^11.4.1
|
||||||
@@ -423,8 +423,8 @@ packages:
|
|||||||
'@emotion/styled':
|
'@emotion/styled':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@mui/system@9.0.1':
|
'@mui/system@9.1.1':
|
||||||
resolution: {integrity: sha512-WvlioaLxk6ewUIOfh0StxUvOPDS1mCfzaulcudsL1brZNXuh0N9FMk7RpH7ImJKjEz412SEy/V/yvqmtxbqxCQ==}
|
resolution: {integrity: sha512-q+aqNa58QZUwmmyUvJKKrStrej+4BcWFw4M0Ug+zRylPIQgR64cqvBnE3QTfLZm4OXulydp8Hl3zwKxMayrdsA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': ^11.5.0
|
'@emotion/react': ^11.5.0
|
||||||
@@ -439,16 +439,16 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@mui/types@9.0.0':
|
'@mui/types@9.1.1':
|
||||||
resolution: {integrity: sha512-i1cuFCAWN44b3AJWO7mh7tuh1sqbQSeVr/94oG0TX5uXivac8XalgE4/6fQZcmGZigzbQ35IXxj/4jLpRIBYZg==}
|
resolution: {integrity: sha512-Zjt7u8wNvDg40rPTGoL+TnfkpuSKjwubsNSFRH1KAVZLcaV4I3AFNHIFbvH7p4F3alEibSbdd90xAgn5Rnfndg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@mui/utils@9.0.1':
|
'@mui/utils@9.1.1':
|
||||||
resolution: {integrity: sha512-f3UO3jNN1pYg5zxqXC81Bvv8hx5ACcYc0387382ZI7M5ono1heIwHYLrKsz85myguWdeVKPRZGmDdynWUBjK2g==}
|
resolution: {integrity: sha512-qSNfnkzZMptaaWFFklpDf4NPJztgwsMDVfM/sSDt+wq4ssYSBhLYwwjuB6eS/+p2IUYbeRzHluzXbw0Zn7aI4A==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
@@ -457,8 +457,8 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.1.4':
|
'@napi-rs/wasm-runtime@1.1.5':
|
||||||
resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==}
|
resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emnapi/core': ^1.7.1
|
'@emnapi/core': ^1.7.1
|
||||||
'@emnapi/runtime': ^1.7.1
|
'@emnapi/runtime': ^1.7.1
|
||||||
@@ -688,8 +688,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==}
|
resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==}
|
||||||
deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed.
|
deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed.
|
||||||
|
|
||||||
'@types/node@25.9.2':
|
'@types/node@25.9.3':
|
||||||
resolution: {integrity: sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==}
|
resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==}
|
||||||
|
|
||||||
'@types/parse-json@4.0.2':
|
'@types/parse-json@4.0.2':
|
||||||
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
|
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
|
||||||
@@ -716,63 +716,63 @@ packages:
|
|||||||
'@types/svgo@2.6.4':
|
'@types/svgo@2.6.4':
|
||||||
resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==}
|
resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.60.1':
|
'@typescript-eslint/eslint-plugin@8.61.0':
|
||||||
resolution: {integrity: sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==}
|
resolution: {integrity: sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/parser': ^8.60.1
|
'@typescript-eslint/parser': ^8.61.0
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.60.1':
|
'@typescript-eslint/parser@8.61.0':
|
||||||
resolution: {integrity: sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==}
|
resolution: {integrity: sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/project-service@8.60.1':
|
'@typescript-eslint/project-service@8.61.0':
|
||||||
resolution: {integrity: sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==}
|
resolution: {integrity: sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/scope-manager@8.60.1':
|
'@typescript-eslint/scope-manager@8.61.0':
|
||||||
resolution: {integrity: sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==}
|
resolution: {integrity: sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.60.1':
|
'@typescript-eslint/tsconfig-utils@8.61.0':
|
||||||
resolution: {integrity: sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==}
|
resolution: {integrity: sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.60.1':
|
'@typescript-eslint/type-utils@8.61.0':
|
||||||
resolution: {integrity: sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==}
|
resolution: {integrity: sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/types@8.60.1':
|
'@typescript-eslint/types@8.61.0':
|
||||||
resolution: {integrity: sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==}
|
resolution: {integrity: sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.60.1':
|
'@typescript-eslint/typescript-estree@8.61.0':
|
||||||
resolution: {integrity: sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==}
|
resolution: {integrity: sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.60.1':
|
'@typescript-eslint/utils@8.61.0':
|
||||||
resolution: {integrity: sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==}
|
resolution: {integrity: sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/visitor-keys@8.60.1':
|
'@typescript-eslint/visitor-keys@8.61.0':
|
||||||
resolution: {integrity: sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==}
|
resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
acorn-jsx@5.3.2:
|
acorn-jsx@5.3.2:
|
||||||
@@ -853,8 +853,8 @@ packages:
|
|||||||
base64-js@1.5.1:
|
base64-js@1.5.1:
|
||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
|
|
||||||
baseline-browser-mapping@2.10.34:
|
baseline-browser-mapping@2.10.35:
|
||||||
resolution: {integrity: sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==}
|
resolution: {integrity: sha512-honAfLBde0HAFLdNyBEfuuENkF6zR+ozxqxa/2zJKHBe1qzLqyTSeRKpdPEHAP03rlDGyQOPnCSxnVpVqQo9Mg==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -952,8 +952,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==}
|
resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001797:
|
caniuse-lite@1.0.30001799:
|
||||||
resolution: {integrity: sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==}
|
resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==}
|
||||||
|
|
||||||
caw@2.0.1:
|
caw@2.0.1:
|
||||||
resolution: {integrity: sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==}
|
resolution: {integrity: sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==}
|
||||||
@@ -1185,8 +1185,8 @@ packages:
|
|||||||
duplexer3@0.1.5:
|
duplexer3@0.1.5:
|
||||||
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
|
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
|
||||||
|
|
||||||
electron-to-chromium@1.5.368:
|
electron-to-chromium@1.5.371:
|
||||||
resolution: {integrity: sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==}
|
resolution: {integrity: sha512-e9htk9mAYL6AzmkEhSvVVw7IWGSBJ/Bqdn2eRyRLrj1g6sncN4WbFt5qnILYoCktktr45pyjIrOiRvBThQ808w==}
|
||||||
|
|
||||||
emoji-regex@10.6.0:
|
emoji-regex@10.6.0:
|
||||||
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
|
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
|
||||||
@@ -2388,8 +2388,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==}
|
resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
prettier@3.8.3:
|
prettier@3.8.4:
|
||||||
resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==}
|
resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -2582,8 +2582,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
semver@7.8.2:
|
semver@7.8.4:
|
||||||
resolution: {integrity: sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==}
|
resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -2827,8 +2827,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=3.5.1'
|
typescript: '>=3.5.1'
|
||||||
|
|
||||||
typescript-eslint@8.60.1:
|
typescript-eslint@8.61.0:
|
||||||
resolution: {integrity: sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==}
|
resolution: {integrity: sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
@@ -3314,23 +3314,23 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
'@mui/core-downloads-tracker@9.0.1': {}
|
'@mui/core-downloads-tracker@9.1.1': {}
|
||||||
|
|
||||||
'@mui/icons-material@9.0.1(@mui/material@9.0.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)':
|
'@mui/icons-material@9.1.1(@mui/material@9.1.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.7
|
'@babel/runtime': 7.29.7
|
||||||
'@mui/material': 9.0.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
'@mui/material': 9.1.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
react: 19.2.7
|
react: 19.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.17
|
'@types/react': 19.2.17
|
||||||
|
|
||||||
'@mui/material@9.0.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
'@mui/material@9.1.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.7
|
'@babel/runtime': 7.29.7
|
||||||
'@mui/core-downloads-tracker': 9.0.1
|
'@mui/core-downloads-tracker': 9.1.1
|
||||||
'@mui/system': 9.0.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
'@mui/system': 9.1.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
||||||
'@mui/types': 9.0.0(@types/react@19.2.17)
|
'@mui/types': 9.1.1(@types/react@19.2.17)
|
||||||
'@mui/utils': 9.0.1(@types/react@19.2.17)(react@19.2.7)
|
'@mui/utils': 9.1.1(@types/react@19.2.17)(react@19.2.7)
|
||||||
'@popperjs/core': 2.11.8
|
'@popperjs/core': 2.11.8
|
||||||
'@types/react-transition-group': 4.4.12(@types/react@19.2.17)
|
'@types/react-transition-group': 4.4.12(@types/react@19.2.17)
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
@@ -3345,16 +3345,16 @@ snapshots:
|
|||||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
||||||
'@types/react': 19.2.17
|
'@types/react': 19.2.17
|
||||||
|
|
||||||
'@mui/private-theming@9.0.1(@types/react@19.2.17)(react@19.2.7)':
|
'@mui/private-theming@9.1.1(@types/react@19.2.17)(react@19.2.7)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.7
|
'@babel/runtime': 7.29.7
|
||||||
'@mui/utils': 9.0.1(@types/react@19.2.17)(react@19.2.7)
|
'@mui/utils': 9.1.1(@types/react@19.2.17)(react@19.2.7)
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
react: 19.2.7
|
react: 19.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.17
|
'@types/react': 19.2.17
|
||||||
|
|
||||||
'@mui/styled-engine@9.0.0(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(react@19.2.7)':
|
'@mui/styled-engine@9.1.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(react@19.2.7)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.7
|
'@babel/runtime': 7.29.7
|
||||||
'@emotion/cache': 11.14.0
|
'@emotion/cache': 11.14.0
|
||||||
@@ -3367,13 +3367,13 @@ snapshots:
|
|||||||
'@emotion/react': 11.14.0(@types/react@19.2.17)(react@19.2.7)
|
'@emotion/react': 11.14.0(@types/react@19.2.17)(react@19.2.7)
|
||||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
||||||
|
|
||||||
'@mui/system@9.0.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)':
|
'@mui/system@9.1.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.7
|
'@babel/runtime': 7.29.7
|
||||||
'@mui/private-theming': 9.0.1(@types/react@19.2.17)(react@19.2.7)
|
'@mui/private-theming': 9.1.1(@types/react@19.2.17)(react@19.2.7)
|
||||||
'@mui/styled-engine': 9.0.0(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(react@19.2.7)
|
'@mui/styled-engine': 9.1.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(react@19.2.7)
|
||||||
'@mui/types': 9.0.0(@types/react@19.2.17)
|
'@mui/types': 9.1.1(@types/react@19.2.17)
|
||||||
'@mui/utils': 9.0.1(@types/react@19.2.17)(react@19.2.7)
|
'@mui/utils': 9.1.1(@types/react@19.2.17)(react@19.2.7)
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
csstype: 3.2.3
|
csstype: 3.2.3
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
@@ -3383,16 +3383,16 @@ snapshots:
|
|||||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)
|
||||||
'@types/react': 19.2.17
|
'@types/react': 19.2.17
|
||||||
|
|
||||||
'@mui/types@9.0.0(@types/react@19.2.17)':
|
'@mui/types@9.1.1(@types/react@19.2.17)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.7
|
'@babel/runtime': 7.29.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.17
|
'@types/react': 19.2.17
|
||||||
|
|
||||||
'@mui/utils@9.0.1(@types/react@19.2.17)(react@19.2.7)':
|
'@mui/utils@9.1.1(@types/react@19.2.17)(react@19.2.7)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.7
|
'@babel/runtime': 7.29.7
|
||||||
'@mui/types': 9.0.0(@types/react@19.2.17)
|
'@mui/types': 9.1.1(@types/react@19.2.17)
|
||||||
'@types/prop-types': 15.7.15
|
'@types/prop-types': 15.7.15
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
@@ -3401,7 +3401,7 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.17
|
'@types/react': 19.2.17
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
|
'@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.10.0
|
'@emnapi/core': 1.10.0
|
||||||
'@emnapi/runtime': 1.10.0
|
'@emnapi/runtime': 1.10.0
|
||||||
@@ -3424,19 +3424,19 @@ snapshots:
|
|||||||
|
|
||||||
'@popperjs/core@2.11.8': {}
|
'@popperjs/core@2.11.8': {}
|
||||||
|
|
||||||
'@preact/preset-vite@2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@25.9.2)(terser@5.48.0))':
|
'@preact/preset-vite@2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7
|
||||||
'@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7)
|
'@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7)
|
||||||
'@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7)
|
'@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7)
|
||||||
'@prefresh/vite': 2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@25.9.2)(terser@5.48.0))
|
'@prefresh/vite': 2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
|
||||||
'@rollup/pluginutils': 5.4.0
|
'@rollup/pluginutils': 5.4.0
|
||||||
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.7)
|
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.7)
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
vite: 8.0.16(@types/node@25.9.2)(terser@5.48.0)
|
vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
|
||||||
vite-prerender-plugin: 0.5.13(vite@8.0.16(@types/node@25.9.2)(terser@5.48.0))
|
vite-prerender-plugin: 0.5.13(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
|
||||||
zimmerframe: 1.1.4
|
zimmerframe: 1.1.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- preact
|
- preact
|
||||||
@@ -3451,7 +3451,7 @@ snapshots:
|
|||||||
|
|
||||||
'@prefresh/utils@1.2.1': {}
|
'@prefresh/utils@1.2.1': {}
|
||||||
|
|
||||||
'@prefresh/vite@2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@25.9.2)(terser@5.48.0))':
|
'@prefresh/vite@2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7
|
||||||
'@prefresh/babel-plugin': 0.5.3
|
'@prefresh/babel-plugin': 0.5.3
|
||||||
@@ -3459,7 +3459,7 @@ snapshots:
|
|||||||
'@prefresh/utils': 1.2.1
|
'@prefresh/utils': 1.2.1
|
||||||
'@rollup/pluginutils': 4.2.1
|
'@rollup/pluginutils': 4.2.1
|
||||||
preact: 10.29.2
|
preact: 10.29.2
|
||||||
vite: 8.0.16(@types/node@25.9.2)(terser@5.48.0)
|
vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -3503,7 +3503,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.10.0
|
'@emnapi/core': 1.10.0
|
||||||
'@emnapi/runtime': 1.10.0
|
'@emnapi/runtime': 1.10.0
|
||||||
'@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
|
'@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-win32-arm64-msvc@1.0.3':
|
'@rolldown/binding-win32-arm64-msvc@1.0.3':
|
||||||
@@ -3536,7 +3536,7 @@ snapshots:
|
|||||||
react-virtualized-auto-sizer: 1.0.26(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
react-virtualized-auto-sizer: 1.0.26(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
react-window: 1.8.11(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
react-window: 1.8.11(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
|
|
||||||
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.8.3)':
|
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.8.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/generator': 7.29.7
|
'@babel/generator': 7.29.7
|
||||||
'@babel/parser': 7.29.7
|
'@babel/parser': 7.29.7
|
||||||
@@ -3546,7 +3546,7 @@ snapshots:
|
|||||||
lodash-es: 4.18.1
|
lodash-es: 4.18.1
|
||||||
minimatch: 9.0.9
|
minimatch: 9.0.9
|
||||||
parse-imports-exports: 0.2.4
|
parse-imports-exports: 0.2.4
|
||||||
prettier: 3.8.3
|
prettier: 3.8.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -3562,7 +3562,7 @@ snapshots:
|
|||||||
'@types/glob@7.2.0':
|
'@types/glob@7.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/minimatch': 6.0.0
|
'@types/minimatch': 6.0.0
|
||||||
'@types/node': 25.9.2
|
'@types/node': 25.9.3
|
||||||
|
|
||||||
'@types/imagemin-gifsicle@7.0.4':
|
'@types/imagemin-gifsicle@7.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3591,19 +3591,19 @@ snapshots:
|
|||||||
|
|
||||||
'@types/imagemin@7.0.1':
|
'@types/imagemin@7.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.9.2
|
'@types/node': 25.9.3
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/keyv@3.1.4':
|
'@types/keyv@3.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.9.2
|
'@types/node': 25.9.3
|
||||||
|
|
||||||
'@types/minimatch@6.0.0':
|
'@types/minimatch@6.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
minimatch: 10.2.5
|
minimatch: 10.2.5
|
||||||
|
|
||||||
'@types/node@25.9.2':
|
'@types/node@25.9.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.24.6
|
undici-types: 7.24.6
|
||||||
|
|
||||||
@@ -3625,20 +3625,20 @@ snapshots:
|
|||||||
|
|
||||||
'@types/responselike@1.0.3':
|
'@types/responselike@1.0.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.9.2
|
'@types/node': 25.9.3
|
||||||
|
|
||||||
'@types/svgo@2.6.4':
|
'@types/svgo@2.6.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.9.2
|
'@types/node': 25.9.3
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1)(typescript@6.0.3))(eslint@10.4.1)(typescript@6.0.3)':
|
'@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1)(typescript@6.0.3))(eslint@10.4.1)(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
'@typescript-eslint/parser': 8.60.1(eslint@10.4.1)(typescript@6.0.3)
|
'@typescript-eslint/parser': 8.61.0(eslint@10.4.1)(typescript@6.0.3)
|
||||||
'@typescript-eslint/scope-manager': 8.60.1
|
'@typescript-eslint/scope-manager': 8.61.0
|
||||||
'@typescript-eslint/type-utils': 8.60.1(eslint@10.4.1)(typescript@6.0.3)
|
'@typescript-eslint/type-utils': 8.61.0(eslint@10.4.1)(typescript@6.0.3)
|
||||||
'@typescript-eslint/utils': 8.60.1(eslint@10.4.1)(typescript@6.0.3)
|
'@typescript-eslint/utils': 8.61.0(eslint@10.4.1)(typescript@6.0.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.60.1
|
'@typescript-eslint/visitor-keys': 8.61.0
|
||||||
eslint: 10.4.1
|
eslint: 10.4.1
|
||||||
ignore: 7.0.5
|
ignore: 7.0.5
|
||||||
natural-compare: 1.4.0
|
natural-compare: 1.4.0
|
||||||
@@ -3647,41 +3647,41 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.60.1(eslint@10.4.1)(typescript@6.0.3)':
|
'@typescript-eslint/parser@8.61.0(eslint@10.4.1)(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 8.60.1
|
'@typescript-eslint/scope-manager': 8.61.0
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.61.0
|
||||||
'@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3)
|
'@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.60.1
|
'@typescript-eslint/visitor-keys': 8.61.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 10.4.1
|
eslint: 10.4.1
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/project-service@8.60.1(typescript@6.0.3)':
|
'@typescript-eslint/project-service@8.61.0(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.60.1(typescript@6.0.3)
|
'@typescript-eslint/tsconfig-utils': 8.61.0(typescript@6.0.3)
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.61.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/scope-manager@8.60.1':
|
'@typescript-eslint/scope-manager@8.61.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.61.0
|
||||||
'@typescript-eslint/visitor-keys': 8.60.1
|
'@typescript-eslint/visitor-keys': 8.61.0
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.60.1(typescript@6.0.3)':
|
'@typescript-eslint/tsconfig-utils@8.61.0(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.60.1(eslint@10.4.1)(typescript@6.0.3)':
|
'@typescript-eslint/type-utils@8.61.0(eslint@10.4.1)(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.61.0
|
||||||
'@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3)
|
'@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3)
|
||||||
'@typescript-eslint/utils': 8.60.1(eslint@10.4.1)(typescript@6.0.3)
|
'@typescript-eslint/utils': 8.61.0(eslint@10.4.1)(typescript@6.0.3)
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 10.4.1
|
eslint: 10.4.1
|
||||||
ts-api-utils: 2.5.0(typescript@6.0.3)
|
ts-api-utils: 2.5.0(typescript@6.0.3)
|
||||||
@@ -3689,37 +3689,37 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/types@8.60.1': {}
|
'@typescript-eslint/types@8.61.0': {}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.60.1(typescript@6.0.3)':
|
'@typescript-eslint/typescript-estree@8.61.0(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/project-service': 8.60.1(typescript@6.0.3)
|
'@typescript-eslint/project-service': 8.61.0(typescript@6.0.3)
|
||||||
'@typescript-eslint/tsconfig-utils': 8.60.1(typescript@6.0.3)
|
'@typescript-eslint/tsconfig-utils': 8.61.0(typescript@6.0.3)
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.61.0
|
||||||
'@typescript-eslint/visitor-keys': 8.60.1
|
'@typescript-eslint/visitor-keys': 8.61.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
minimatch: 10.2.5
|
minimatch: 10.2.5
|
||||||
semver: 7.8.2
|
semver: 7.8.4
|
||||||
tinyglobby: 0.2.17
|
tinyglobby: 0.2.17
|
||||||
ts-api-utils: 2.5.0(typescript@6.0.3)
|
ts-api-utils: 2.5.0(typescript@6.0.3)
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.60.1(eslint@10.4.1)(typescript@6.0.3)':
|
'@typescript-eslint/utils@8.61.0(eslint@10.4.1)(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1)
|
'@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1)
|
||||||
'@typescript-eslint/scope-manager': 8.60.1
|
'@typescript-eslint/scope-manager': 8.61.0
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.61.0
|
||||||
'@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3)
|
'@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3)
|
||||||
eslint: 10.4.1
|
eslint: 10.4.1
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/visitor-keys@8.60.1':
|
'@typescript-eslint/visitor-keys@8.61.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.61.0
|
||||||
eslint-visitor-keys: 5.0.1
|
eslint-visitor-keys: 5.0.1
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.16.0):
|
acorn-jsx@5.3.2(acorn@8.16.0):
|
||||||
@@ -3784,7 +3784,7 @@ snapshots:
|
|||||||
|
|
||||||
base64-js@1.5.1: {}
|
base64-js@1.5.1: {}
|
||||||
|
|
||||||
baseline-browser-mapping@2.10.34: {}
|
baseline-browser-mapping@2.10.35: {}
|
||||||
|
|
||||||
bin-build@3.0.0:
|
bin-build@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3845,9 +3845,9 @@ snapshots:
|
|||||||
|
|
||||||
browserslist@4.28.2:
|
browserslist@4.28.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
baseline-browser-mapping: 2.10.34
|
baseline-browser-mapping: 2.10.35
|
||||||
caniuse-lite: 1.0.30001797
|
caniuse-lite: 1.0.30001799
|
||||||
electron-to-chromium: 1.5.368
|
electron-to-chromium: 1.5.371
|
||||||
node-releases: 2.0.47
|
node-releases: 2.0.47
|
||||||
update-browserslist-db: 1.2.3(browserslist@4.28.2)
|
update-browserslist-db: 1.2.3(browserslist@4.28.2)
|
||||||
|
|
||||||
@@ -3909,7 +3909,7 @@ snapshots:
|
|||||||
|
|
||||||
camelcase@2.1.1: {}
|
camelcase@2.1.1: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001797: {}
|
caniuse-lite@1.0.30001799: {}
|
||||||
|
|
||||||
caw@2.0.1:
|
caw@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4202,7 +4202,7 @@ snapshots:
|
|||||||
|
|
||||||
duplexer3@0.1.5: {}
|
duplexer3@0.1.5: {}
|
||||||
|
|
||||||
electron-to-chromium@1.5.368: {}
|
electron-to-chromium@1.5.371: {}
|
||||||
|
|
||||||
emoji-regex@10.6.0: {}
|
emoji-regex@10.6.0: {}
|
||||||
|
|
||||||
@@ -5334,7 +5334,7 @@ snapshots:
|
|||||||
|
|
||||||
prepend-http@2.0.0: {}
|
prepend-http@2.0.0: {}
|
||||||
|
|
||||||
prettier@3.8.3: {}
|
prettier@3.8.4: {}
|
||||||
|
|
||||||
process-nextick-args@2.0.1: {}
|
process-nextick-args@2.0.1: {}
|
||||||
|
|
||||||
@@ -5528,7 +5528,7 @@ snapshots:
|
|||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
||||||
semver@7.8.2: {}
|
semver@7.8.4: {}
|
||||||
|
|
||||||
set-cookie-parser@2.7.2: {}
|
set-cookie-parser@2.7.2: {}
|
||||||
|
|
||||||
@@ -5753,12 +5753,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
typescript-eslint@8.60.1(eslint@10.4.1)(typescript@6.0.3):
|
typescript-eslint@8.61.0(eslint@10.4.1)(typescript@6.0.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1)(typescript@6.0.3))(eslint@10.4.1)(typescript@6.0.3)
|
'@typescript-eslint/eslint-plugin': 8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1)(typescript@6.0.3))(eslint@10.4.1)(typescript@6.0.3)
|
||||||
'@typescript-eslint/parser': 8.60.1(eslint@10.4.1)(typescript@6.0.3)
|
'@typescript-eslint/parser': 8.61.0(eslint@10.4.1)(typescript@6.0.3)
|
||||||
'@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3)
|
'@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3)
|
||||||
'@typescript-eslint/utils': 8.60.1(eslint@10.4.1)(typescript@6.0.3)
|
'@typescript-eslint/utils': 8.61.0(eslint@10.4.1)(typescript@6.0.3)
|
||||||
eslint: 10.4.1
|
eslint: 10.4.1
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -5804,7 +5804,7 @@ snapshots:
|
|||||||
spdx-correct: 3.2.0
|
spdx-correct: 3.2.0
|
||||||
spdx-expression-parse: 3.0.1
|
spdx-expression-parse: 3.0.1
|
||||||
|
|
||||||
vite-plugin-imagemin@0.6.1(vite@8.0.16(@types/node@25.9.2)(terser@5.48.0)):
|
vite-plugin-imagemin@0.6.1(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/imagemin': 7.0.1
|
'@types/imagemin': 7.0.1
|
||||||
'@types/imagemin-gifsicle': 7.0.4
|
'@types/imagemin-gifsicle': 7.0.4
|
||||||
@@ -5829,11 +5829,11 @@ snapshots:
|
|||||||
imagemin-webp: 6.1.0
|
imagemin-webp: 6.1.0
|
||||||
jpegtran-bin: 6.0.1
|
jpegtran-bin: 6.0.1
|
||||||
pathe: 0.2.0
|
pathe: 0.2.0
|
||||||
vite: 8.0.16(@types/node@25.9.2)(terser@5.48.0)
|
vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
vite-prerender-plugin@0.5.13(vite@8.0.16(@types/node@25.9.2)(terser@5.48.0)):
|
vite-prerender-plugin@0.5.13(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
kolorist: 1.8.0
|
kolorist: 1.8.0
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
@@ -5841,9 +5841,9 @@ snapshots:
|
|||||||
simple-code-frame: 1.3.0
|
simple-code-frame: 1.3.0
|
||||||
source-map: 0.7.6
|
source-map: 0.7.6
|
||||||
stack-trace: 1.0.0
|
stack-trace: 1.0.0
|
||||||
vite: 8.0.16(@types/node@25.9.2)(terser@5.48.0)
|
vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
|
||||||
|
|
||||||
vite@8.0.16(@types/node@25.9.2)(terser@5.48.0):
|
vite@8.0.16(@types/node@25.9.3)(terser@5.48.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
lightningcss: 1.32.0
|
lightningcss: 1.32.0
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
@@ -5851,7 +5851,7 @@ snapshots:
|
|||||||
rolldown: 1.0.3
|
rolldown: 1.0.3
|
||||||
tinyglobby: 0.2.17
|
tinyglobby: 0.2.17
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.9.2
|
'@types/node': 25.9.3
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
terser: 5.48.0
|
terser: 5.48.0
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { memo, useContext } from 'react';
|
import { memo, useContext } from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router';
|
import { Navigate, Route, Routes } from 'react-router';
|
||||||
|
|
||||||
|
import Commands from 'app/main/Commands';
|
||||||
import CustomEntities from 'app/main/CustomEntities';
|
import CustomEntities from 'app/main/CustomEntities';
|
||||||
import Customizations from 'app/main/Customizations';
|
import Customizations from 'app/main/Customizations';
|
||||||
import Dashboard from 'app/main/Dashboard';
|
import Dashboard from 'app/main/Dashboard';
|
||||||
@@ -65,6 +66,7 @@ const AuthenticatedRouting = memo(() => {
|
|||||||
<Route path="/settings/security/*" element={<Security />} />
|
<Route path="/settings/security/*" element={<Security />} />
|
||||||
|
|
||||||
<Route path="/customizations" element={<Customizations />} />
|
<Route path="/customizations" element={<Customizations />} />
|
||||||
|
<Route path="/commands" element={<Commands />} />
|
||||||
<Route path="/scheduler" element={<Scheduler />} />
|
<Route path="/scheduler" element={<Scheduler />} />
|
||||||
<Route path="/customentities" element={<CustomEntities />} />
|
<Route path="/customentities" element={<CustomEntities />} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import type {
|
|||||||
APIcall,
|
APIcall,
|
||||||
Action,
|
Action,
|
||||||
Activity,
|
Activity,
|
||||||
|
CommandItem,
|
||||||
|
Commands,
|
||||||
CoreData,
|
CoreData,
|
||||||
DashboardData,
|
DashboardData,
|
||||||
DeviceData,
|
DeviceData,
|
||||||
@@ -102,8 +104,7 @@ export const readSchedule = () =>
|
|||||||
o_deleted: si.deleted,
|
o_deleted: si.deleted,
|
||||||
o_flags: si.flags,
|
o_flags: si.flags,
|
||||||
o_time: si.time,
|
o_time: si.time,
|
||||||
o_cmd: si.cmd,
|
o_cmd_name: si.cmd_name,
|
||||||
o_value: si.value,
|
|
||||||
o_name: si.name
|
o_name: si.name
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -111,6 +112,24 @@ export const readSchedule = () =>
|
|||||||
export const writeSchedule = (data: Schedule) =>
|
export const writeSchedule = (data: Schedule) =>
|
||||||
alovaInstance.Post('/rest/schedule', data);
|
alovaInstance.Post('/rest/schedule', data);
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
export const readCommands = () =>
|
||||||
|
alovaInstance.Get<CommandItem[]>('/rest/commands', {
|
||||||
|
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
|
||||||
|
transform(data) {
|
||||||
|
const commands = (data as Commands).commands;
|
||||||
|
return commands.map((ci) => ({
|
||||||
|
...ci,
|
||||||
|
o_id: ci.id,
|
||||||
|
o_cmd: ci.cmd,
|
||||||
|
o_value: ci.value,
|
||||||
|
o_name: ci.name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export const writeCommands = (data: Commands) =>
|
||||||
|
alovaInstance.Post('/rest/commands', data);
|
||||||
|
|
||||||
// Modules
|
// Modules
|
||||||
export const readModules = () =>
|
export const readModules = () =>
|
||||||
alovaInstance.Get<ModuleItem[]>('/rest/modules', {
|
alovaInstance.Get<ModuleItem[]>('/rest/modules', {
|
||||||
|
|||||||
284
interface/src/app/main/Commands.tsx
Normal file
284
interface/src/app/main/Commands.tsx
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { useBlocker } from 'react-router';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import { Box, Button, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Cell,
|
||||||
|
Header,
|
||||||
|
HeaderCell,
|
||||||
|
HeaderRow,
|
||||||
|
Row,
|
||||||
|
Table
|
||||||
|
} from '@table-library/react-table-library/table';
|
||||||
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
|
import { updateState, useRequest } from 'alova/client';
|
||||||
|
import {
|
||||||
|
BlockNavigation,
|
||||||
|
ButtonRow,
|
||||||
|
FormLoader,
|
||||||
|
SectionContent,
|
||||||
|
useLayoutTitle
|
||||||
|
} from 'components';
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
|
import { readCommands, writeCommands } from '../../api/app';
|
||||||
|
import CommandsDialog from './CommandsDialog';
|
||||||
|
import type { CommandItem, Commands as CommandsType } from './types';
|
||||||
|
import { commandItemValidation } from './validators';
|
||||||
|
|
||||||
|
const INTERVAL_DELAY = 30000;
|
||||||
|
const MIN_ID = -100;
|
||||||
|
const MAX_ID = 100;
|
||||||
|
|
||||||
|
const DEFAULT_COMMAND_ITEM: Omit<CommandItem, 'id'> = {
|
||||||
|
cmd: '',
|
||||||
|
value: '',
|
||||||
|
name: '',
|
||||||
|
deleted: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const commandsTheme = {
|
||||||
|
Table: `
|
||||||
|
--data-table-library_grid-template-columns: repeat(1, minmax(100px, 1fr)) repeat(1, minmax(100px, 1fr)) 160px;
|
||||||
|
`,
|
||||||
|
BaseRow: `
|
||||||
|
font-size: 14px;
|
||||||
|
.td {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
BaseCell: `
|
||||||
|
&:nth-of-type(1) {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
HeaderRow: `
|
||||||
|
text-transform: uppercase;
|
||||||
|
background-color: black;
|
||||||
|
color: #90CAF9;
|
||||||
|
.th {
|
||||||
|
border-bottom: 1px solid #565656;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Row: `
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
.td {
|
||||||
|
border-bottom: 1px solid #565656;
|
||||||
|
}
|
||||||
|
&:hover .td {
|
||||||
|
background-color: #177ac9;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
const CommandsPage = () => {
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
const [numChanges, setNumChanges] = useState<number>(0);
|
||||||
|
const blocker = useBlocker(numChanges !== 0);
|
||||||
|
const [selectedItem, setSelectedItem] = useState<CommandItem>();
|
||||||
|
const [creating, setCreating] = useState<boolean>(false);
|
||||||
|
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useLayoutTitle(LL.COMMANDS());
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: commands,
|
||||||
|
send: fetchCommands,
|
||||||
|
error
|
||||||
|
} = useRequest(readCommands, {
|
||||||
|
initialData: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const { send: updateCommands } = useRequest(
|
||||||
|
(data: CommandsType) => writeCommands(data),
|
||||||
|
{ immediate: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasChanged = (ci: CommandItem) =>
|
||||||
|
ci.id !== ci.o_id ||
|
||||||
|
(ci.name || '') !== (ci.o_name || '') ||
|
||||||
|
ci.cmd !== ci.o_cmd ||
|
||||||
|
ci.value !== ci.o_value ||
|
||||||
|
ci.deleted !== ci.o_deleted;
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
if (numChanges === 0) {
|
||||||
|
void fetchCommands();
|
||||||
|
}
|
||||||
|
}, INTERVAL_DELAY);
|
||||||
|
|
||||||
|
const theme = useTheme(commandsTheme);
|
||||||
|
|
||||||
|
const saveCommands = async () => {
|
||||||
|
try {
|
||||||
|
await updateCommands({
|
||||||
|
commands: commands
|
||||||
|
.filter((ci: CommandItem) => !ci.deleted)
|
||||||
|
.map((ci: CommandItem) => ({
|
||||||
|
id: ci.id,
|
||||||
|
cmd: ci.cmd,
|
||||||
|
value: ci.value,
|
||||||
|
name: ci.name
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
toast.success(LL.UPDATED_OF(LL.COMMANDS()));
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
toast.error(message);
|
||||||
|
} finally {
|
||||||
|
await fetchCommands();
|
||||||
|
setNumChanges(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const editItem = (ci: CommandItem) => {
|
||||||
|
setCreating(false);
|
||||||
|
setSelectedItem(ci);
|
||||||
|
setDialogOpen(true);
|
||||||
|
if (ci.o_name === undefined) {
|
||||||
|
ci.o_name = ci.name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDialogClose = () => {
|
||||||
|
setDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDialogCancel = async () => {
|
||||||
|
await fetchCommands().then(() => {
|
||||||
|
setNumChanges(0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDialogSave = (updatedItem: CommandItem) => {
|
||||||
|
setDialogOpen(false);
|
||||||
|
void updateState(readCommands(), (data: CommandItem[]) => {
|
||||||
|
const new_data = creating
|
||||||
|
? [...data, updatedItem]
|
||||||
|
: data.map((ci) =>
|
||||||
|
ci.id === updatedItem.id ? { ...ci, ...updatedItem } : ci
|
||||||
|
);
|
||||||
|
setNumChanges(new_data.filter((ci) => hasChanged(ci)).length);
|
||||||
|
return new_data;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
setCreating(true);
|
||||||
|
const newItem: CommandItem = {
|
||||||
|
id: Math.floor(Math.random() * (MAX_ID - MIN_ID) + MIN_ID),
|
||||||
|
...DEFAULT_COMMAND_ITEM
|
||||||
|
};
|
||||||
|
setSelectedItem(newItem);
|
||||||
|
setDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredCommands = commands.filter((ci: CommandItem) => !ci.deleted);
|
||||||
|
|
||||||
|
const renderCommands = () => {
|
||||||
|
if (!commands) {
|
||||||
|
return (
|
||||||
|
<FormLoader onRetry={fetchCommands} errorMessage={error?.message || ''} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
data={{ nodes: filteredCommands }}
|
||||||
|
theme={theme}
|
||||||
|
layout={{ custom: true }}
|
||||||
|
>
|
||||||
|
{(tableList: CommandItem[]) => (
|
||||||
|
<>
|
||||||
|
<Header>
|
||||||
|
<HeaderRow>
|
||||||
|
<HeaderCell stiff>{LL.COMMAND(0)}</HeaderCell>
|
||||||
|
<HeaderCell stiff>{LL.VALUE(0)}</HeaderCell>
|
||||||
|
<HeaderCell stiff>{LL.NAME(0)}</HeaderCell>
|
||||||
|
</HeaderRow>
|
||||||
|
</Header>
|
||||||
|
<Body>
|
||||||
|
{tableList.map((ci: CommandItem) => (
|
||||||
|
<Row key={ci.id} item={ci} onClick={() => editItem(ci)}>
|
||||||
|
<Cell>{ci.cmd}</Cell>
|
||||||
|
<Cell>{ci.value}</Cell>
|
||||||
|
<Cell>{ci.name}</Cell>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</Body>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SectionContent>
|
||||||
|
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||||
|
<Typography sx={{ mb: 2 }} color="warning" variant="body1">
|
||||||
|
{LL.COMMANDS_HELP_1()}.
|
||||||
|
</Typography>
|
||||||
|
{renderCommands()}
|
||||||
|
|
||||||
|
{selectedItem && (
|
||||||
|
<CommandsDialog
|
||||||
|
open={dialogOpen}
|
||||||
|
creating={creating}
|
||||||
|
onClose={onDialogClose}
|
||||||
|
onSave={onDialogSave}
|
||||||
|
selectedItem={selectedItem}
|
||||||
|
validator={commandItemValidation(commands, selectedItem)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
|
||||||
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
|
{numChanges !== 0 && (
|
||||||
|
<ButtonRow>
|
||||||
|
<Button
|
||||||
|
startIcon={<CancelIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
onClick={onDialogCancel}
|
||||||
|
color="secondary"
|
||||||
|
>
|
||||||
|
{LL.CANCEL()}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
startIcon={<WarningIcon color="warning" />}
|
||||||
|
variant="contained"
|
||||||
|
color="info"
|
||||||
|
onClick={saveCommands}
|
||||||
|
>
|
||||||
|
{LL.APPLY_CHANGES(numChanges)}
|
||||||
|
</Button>
|
||||||
|
</ButtonRow>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ flexWrap: 'nowrap', whiteSpace: 'nowrap' }}>
|
||||||
|
<ButtonRow>
|
||||||
|
<Button
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
onClick={addItem}
|
||||||
|
>
|
||||||
|
{LL.ADD(0)}
|
||||||
|
</Button>
|
||||||
|
</ButtonRow>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</SectionContent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CommandsPage;
|
||||||
206
interface/src/app/main/CommandsDialog.tsx
Normal file
206
interface/src/app/main/CommandsDialog.tsx
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import DoneIcon from '@mui/icons-material/Done';
|
||||||
|
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||||
|
import RemoveIcon from '@mui/icons-material/RemoveCircleOutlined';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
TextField
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import { callAction } from '@/api/app';
|
||||||
|
import { dialogStyle } from 'CustomTheme';
|
||||||
|
import { useRequest } from 'alova/client';
|
||||||
|
import type Schema from 'async-validator';
|
||||||
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
import { ValidatedTextField } from 'components';
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import { updateValue } from 'utils';
|
||||||
|
import { ValidationError, validate } from 'validators';
|
||||||
|
|
||||||
|
import type { CommandItem } from './types';
|
||||||
|
|
||||||
|
interface CommandsDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
creating: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave: (ci: CommandItem) => void;
|
||||||
|
selectedItem: CommandItem;
|
||||||
|
validator: Schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommandsDialog = ({
|
||||||
|
open,
|
||||||
|
creating,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
selectedItem,
|
||||||
|
validator
|
||||||
|
}: CommandsDialogProps) => {
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
const [hasChanges, setHasChanges] = useState<boolean>(false);
|
||||||
|
const [editItem, setEditItem] = useState<CommandItem>(selectedItem);
|
||||||
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
|
const updateFormValue = updateValue(
|
||||||
|
setEditItem as unknown as React.Dispatch<
|
||||||
|
React.SetStateAction<Record<string, unknown>>
|
||||||
|
>
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setFieldErrors(undefined);
|
||||||
|
setEditItem(selectedItem);
|
||||||
|
}
|
||||||
|
}, [open, selectedItem]);
|
||||||
|
|
||||||
|
const hasChanged = (ci: CommandItem) =>
|
||||||
|
ci.id !== ci.o_id ||
|
||||||
|
(ci.name || '') !== (ci.o_name || '') ||
|
||||||
|
ci.cmd !== ci.o_cmd ||
|
||||||
|
ci.value !== ci.o_value ||
|
||||||
|
ci.deleted !== ci.o_deleted;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setHasChanges(hasChanged(editItem));
|
||||||
|
}, [editItem]);
|
||||||
|
|
||||||
|
const handleSave = async (itemToSave: CommandItem) => {
|
||||||
|
try {
|
||||||
|
setFieldErrors(undefined);
|
||||||
|
await validate(validator, itemToSave);
|
||||||
|
onSave(itemToSave);
|
||||||
|
} catch (error) {
|
||||||
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
|
} finally {
|
||||||
|
setHasChanges(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
await handleSave(editItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { send: executeCommand } = useRequest(
|
||||||
|
(id: string) => callAction({ action: 'executeCommand', param: id }),
|
||||||
|
{ immediate: false }
|
||||||
|
)
|
||||||
|
.onSuccess(() => {
|
||||||
|
toast.success(LL.EXECUTE_COMMAND_SENT());
|
||||||
|
})
|
||||||
|
.onError((error) => {
|
||||||
|
toast.error(String(error.error?.message || 'An error occurred'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const execute = async () => {
|
||||||
|
await executeCommand(editItem.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = () => {
|
||||||
|
onSave({ ...editItem, deleted: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = (
|
||||||
|
_event: React.SyntheticEvent,
|
||||||
|
reason: 'backdropClick' | 'escapeKeyDown'
|
||||||
|
) => {
|
||||||
|
if (reason !== 'backdropClick') {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
|
||||||
|
<DialogTitle>
|
||||||
|
{creating ? `${LL.ADD(1)} ${LL.NEW(0)}` : LL.EDIT()}
|
||||||
|
{LL.COMMAND(1)}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<ValidatedTextField
|
||||||
|
fieldErrors={fieldErrors || {}}
|
||||||
|
name="cmd"
|
||||||
|
label={LL.COMMAND(0)}
|
||||||
|
multiline
|
||||||
|
fullWidth
|
||||||
|
value={editItem.cmd}
|
||||||
|
margin="normal"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
name="value"
|
||||||
|
label={LL.VALUE(0)}
|
||||||
|
multiline
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
value={editItem.value}
|
||||||
|
onChange={updateFormValue}
|
||||||
|
/>
|
||||||
|
<ValidatedTextField
|
||||||
|
fieldErrors={fieldErrors || {}}
|
||||||
|
name="name"
|
||||||
|
label={LL.NAME(0)}
|
||||||
|
value={editItem.name}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions>
|
||||||
|
{!creating && (
|
||||||
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
|
<Button
|
||||||
|
startIcon={<RemoveIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
color="warning"
|
||||||
|
onClick={remove}
|
||||||
|
>
|
||||||
|
{LL.REMOVE()}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
startIcon={<CancelIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
onClick={onClose}
|
||||||
|
color="secondary"
|
||||||
|
>
|
||||||
|
{LL.CANCEL()}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{hasChanges && (
|
||||||
|
<Button
|
||||||
|
startIcon={creating ? <AddIcon /> : <DoneIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
onClick={save}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{creating ? LL.ADD(0) : LL.UPDATE()}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!creating && !hasChanges && editItem.cmd !== '' && (
|
||||||
|
<Button
|
||||||
|
startIcon={<PlayArrowIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
onClick={execute}
|
||||||
|
color="success"
|
||||||
|
>
|
||||||
|
{LL.EXECUTE()}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CommandsDialog;
|
||||||
@@ -180,6 +180,8 @@ const Dashboard = memo(() => {
|
|||||||
return LL.ANALOG_SENSORS();
|
return LL.ANALOG_SENSORS();
|
||||||
case DeviceType.TEMPERATURESENSOR:
|
case DeviceType.TEMPERATURESENSOR:
|
||||||
return LL.TEMP_SENSORS();
|
return LL.TEMP_SENSORS();
|
||||||
|
case DeviceType.COMMAND:
|
||||||
|
return LL.COMMANDS();
|
||||||
case DeviceType.SCHEDULER:
|
case DeviceType.SCHEDULER:
|
||||||
return LL.SCHEDULER();
|
return LL.SCHEDULER();
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const deviceIconLookup: Record<DeviceType, IconType | null> = {
|
|||||||
[DeviceType.CUSTOM]: MdPlaylistAdd,
|
[DeviceType.CUSTOM]: MdPlaylistAdd,
|
||||||
[DeviceType.UNKNOWN]: MdOutlineSensors,
|
[DeviceType.UNKNOWN]: MdOutlineSensors,
|
||||||
[DeviceType.SYSTEM]: null,
|
[DeviceType.SYSTEM]: null,
|
||||||
|
[DeviceType.COMMAND]: MdPlaylistAdd,
|
||||||
[DeviceType.SCHEDULER]: MdMoreTime,
|
[DeviceType.SCHEDULER]: MdMoreTime,
|
||||||
[DeviceType.GENERIC]: MdOutlineSensors,
|
[DeviceType.GENERIC]: MdOutlineSensors,
|
||||||
[DeviceType.VENTILATION]: PiFan
|
[DeviceType.VENTILATION]: PiFan
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -64,12 +65,12 @@ const DevicesDialog = ({
|
|||||||
}
|
}
|
||||||
}, [open, selectedItem]);
|
}, [open, selectedItem]);
|
||||||
|
|
||||||
const { send: executeSchedule } = useRequest(
|
const { send: executeCommand } = useRequest(
|
||||||
(id: string) => callAction({ action: 'executeSchedule', param: id }),
|
(id: string) => callAction({ action: 'executeCommand', param: id }),
|
||||||
{ immediate: false }
|
{ immediate: false }
|
||||||
)
|
)
|
||||||
.onSuccess(() => {
|
.onSuccess(() => {
|
||||||
toast.success(LL.EXECUTE_SCHEDULE_SENT());
|
toast.success(LL.EXECUTE_COMMAND_SENT());
|
||||||
})
|
})
|
||||||
.onError((error) => {
|
.onError((error) => {
|
||||||
toast.error(String(error.error?.message || 'An error occurred'));
|
toast.error(String(error.error?.message || 'An error occurred'));
|
||||||
@@ -79,7 +80,7 @@ const DevicesDialog = ({
|
|||||||
try {
|
try {
|
||||||
setFieldErrors(undefined);
|
setFieldErrors(undefined);
|
||||||
if (editItem.v === undefined && editItem.c !== undefined) {
|
if (editItem.v === undefined && editItem.c !== undefined) {
|
||||||
await executeSchedule(editItem.c);
|
await executeCommand(editItem.c);
|
||||||
} else {
|
} else {
|
||||||
await validate(validator, editItem);
|
await validate(validator, editItem);
|
||||||
}
|
}
|
||||||
@@ -226,10 +227,12 @@ const DevicesDialog = ({
|
|||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
startIcon={<WarningIcon color="warning" />}
|
startIcon={
|
||||||
|
isCommand ? <PlayArrowIcon /> : <WarningIcon color="warning" />
|
||||||
|
}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={doAction}
|
onClick={doAction}
|
||||||
color="primary"
|
color={isCommand ? 'success' : 'primary'}
|
||||||
>
|
>
|
||||||
{buttonLabel}
|
{buttonLabel}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { useInterval } from 'utils';
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
import { readSchedule, writeSchedule } from '../../api/app';
|
import { readCommands, readSchedule, writeSchedule } from '../../api/app';
|
||||||
import SettingsSchedulerDialog from './SchedulerDialog';
|
import SettingsSchedulerDialog from './SchedulerDialog';
|
||||||
import { ScheduleFlag } from './types';
|
import { ScheduleFlag } from './types';
|
||||||
import type { Schedule, ScheduleItem } from './types';
|
import type { Schedule, ScheduleItem } from './types';
|
||||||
@@ -54,14 +54,13 @@ const DEFAULT_SCHEDULE_ITEM: Omit<ScheduleItem, 'id' | 'o_id'> = {
|
|||||||
deleted: false,
|
deleted: false,
|
||||||
flags: FLAG_ALL_DAYS,
|
flags: FLAG_ALL_DAYS,
|
||||||
time: '',
|
time: '',
|
||||||
cmd: '',
|
cmd_name: '',
|
||||||
value: '',
|
|
||||||
name: ''
|
name: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
const scheduleTheme = {
|
const scheduleTheme = {
|
||||||
Table: `
|
Table: `
|
||||||
--data-table-library_grid-template-columns: 36px 210px 100px 192px repeat(1, minmax(100px, 1fr)) 160px;
|
--data-table-library_grid-template-columns: 36px 220px repeat(1, minmax(20px, 1fr)) 192px 160px;
|
||||||
`,
|
`,
|
||||||
BaseRow: `
|
BaseRow: `
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -70,11 +69,8 @@ const scheduleTheme = {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
BaseCell: `
|
BaseCell: `
|
||||||
&:nth-of-type(2) {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
&:nth-of-type(1) {
|
&:nth-of-type(1) {
|
||||||
text-align: center;
|
text-align: 8px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
HeaderRow: `
|
HeaderRow: `
|
||||||
@@ -100,7 +96,6 @@ const scheduleTheme = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const scheduleTypeLabels: Record<number, string> = {
|
const scheduleTypeLabels: Record<number, string> = {
|
||||||
[ScheduleFlag.SCHEDULE_IMMEDIATE]: 'Immediate',
|
|
||||||
[ScheduleFlag.SCHEDULE_TIMER]: 'Timer',
|
[ScheduleFlag.SCHEDULE_TIMER]: 'Timer',
|
||||||
[ScheduleFlag.SCHEDULE_CONDITION]: 'Condition',
|
[ScheduleFlag.SCHEDULE_CONDITION]: 'Condition',
|
||||||
[ScheduleFlag.SCHEDULE_ONCHANGE]: 'On Change'
|
[ScheduleFlag.SCHEDULE_ONCHANGE]: 'On Change'
|
||||||
@@ -125,6 +120,11 @@ const Scheduler = () => {
|
|||||||
initialData: []
|
initialData: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: commandNames } = useRequest(readCommands, {
|
||||||
|
initialData: [],
|
||||||
|
initializing: true
|
||||||
|
});
|
||||||
|
|
||||||
const { send: updateSchedule } = useRequest(
|
const { send: updateSchedule } = useRequest(
|
||||||
(data: Schedule) => writeSchedule(data),
|
(data: Schedule) => writeSchedule(data),
|
||||||
{
|
{
|
||||||
@@ -140,8 +140,7 @@ const Scheduler = () => {
|
|||||||
si.deleted !== si.o_deleted ||
|
si.deleted !== si.o_deleted ||
|
||||||
si.flags !== si.o_flags ||
|
si.flags !== si.o_flags ||
|
||||||
si.time !== si.o_time ||
|
si.time !== si.o_time ||
|
||||||
si.cmd !== si.o_cmd ||
|
si.cmd_name !== si.o_cmd_name
|
||||||
si.value !== si.o_value
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -177,8 +176,7 @@ const Scheduler = () => {
|
|||||||
active: condensed_si.active,
|
active: condensed_si.active,
|
||||||
flags: condensed_si.flags,
|
flags: condensed_si.flags,
|
||||||
time: condensed_si.time,
|
time: condensed_si.time,
|
||||||
cmd: condensed_si.cmd,
|
cmd_name: condensed_si.cmd_name,
|
||||||
value: condensed_si.value,
|
|
||||||
name: condensed_si.name
|
name: condensed_si.name
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
@@ -287,9 +285,10 @@ const Scheduler = () => {
|
|||||||
<HeaderRow>
|
<HeaderRow>
|
||||||
<HeaderCell />
|
<HeaderCell />
|
||||||
<HeaderCell stiff>{LL.SCHEDULE(0)}</HeaderCell>
|
<HeaderCell stiff>{LL.SCHEDULE(0)}</HeaderCell>
|
||||||
<HeaderCell stiff>{LL.TIME(0)}/Cond.</HeaderCell>
|
<HeaderCell stiff>
|
||||||
|
{LL.TIME(0)}/{LL.CONDITION()}
|
||||||
|
</HeaderCell>
|
||||||
<HeaderCell stiff>{LL.COMMAND(0)}</HeaderCell>
|
<HeaderCell stiff>{LL.COMMAND(0)}</HeaderCell>
|
||||||
<HeaderCell stiff>{LL.VALUE(0)}</HeaderCell>
|
|
||||||
<HeaderCell stiff>{LL.NAME(0)}</HeaderCell>
|
<HeaderCell stiff>{LL.NAME(0)}</HeaderCell>
|
||||||
</HeaderRow>
|
</HeaderRow>
|
||||||
</Header>
|
</Header>
|
||||||
@@ -297,12 +296,15 @@ const Scheduler = () => {
|
|||||||
{tableList.map((si: ScheduleItem) => (
|
{tableList.map((si: ScheduleItem) => (
|
||||||
<Row key={si.id} item={si} onClick={() => editScheduleItem(si)}>
|
<Row key={si.id} item={si} onClick={() => editScheduleItem(si)}>
|
||||||
<Cell stiff>
|
<Cell stiff>
|
||||||
{si.flags !== ScheduleFlag.SCHEDULE_IMMEDIATE && (
|
<CircleIcon
|
||||||
<CircleIcon
|
color={si.active ? 'success' : 'error'}
|
||||||
color={si.active ? 'success' : 'error'}
|
sx={{
|
||||||
sx={{ fontSize: ICON_SIZE, verticalAlign: 'middle' }}
|
fontSize: ICON_SIZE,
|
||||||
/>
|
ml: '4px',
|
||||||
)}
|
position: 'relative',
|
||||||
|
top: '2px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell stiff>
|
<Cell stiff>
|
||||||
<Stack spacing={0.5} direction="row">
|
<Stack spacing={0.5} direction="row">
|
||||||
@@ -321,9 +323,8 @@ const Scheduler = () => {
|
|||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell>{si.time}</Cell>
|
<Cell>{si.time === '' ? LL.SCHEDULER_HELP_2() : si.time}</Cell>
|
||||||
<Cell>{si.cmd}</Cell>
|
<Cell>{si.cmd_name}</Cell>
|
||||||
<Cell>{si.value}</Cell>
|
|
||||||
<Cell>{si.name}</Cell>
|
<Cell>{si.name}</Cell>
|
||||||
</Row>
|
</Row>
|
||||||
))}
|
))}
|
||||||
@@ -351,6 +352,7 @@ const Scheduler = () => {
|
|||||||
selectedItem={selectedScheduleItem}
|
selectedItem={selectedScheduleItem}
|
||||||
validator={schedulerItemValidation(schedule, selectedScheduleItem)}
|
validator={schedulerItemValidation(schedule, selectedScheduleItem)}
|
||||||
dow={dow}
|
dow={dow}
|
||||||
|
commandNames={commandNames.map((ci) => ci.name)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import CircleIcon from '@mui/icons-material/Circle';
|
||||||
import DoneIcon from '@mui/icons-material/Done';
|
import DoneIcon from '@mui/icons-material/Done';
|
||||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
|
||||||
import RemoveIcon from '@mui/icons-material/RemoveCircleOutlined';
|
import RemoveIcon from '@mui/icons-material/RemoveCircleOutlined';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -15,15 +14,14 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Grid,
|
Grid,
|
||||||
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
ToggleButton,
|
ToggleButton,
|
||||||
ToggleButtonGroup,
|
ToggleButtonGroup,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { callAction } from '@/api/app';
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import { useRequest } from 'alova/client';
|
|
||||||
import type Schema from 'async-validator';
|
import type Schema from 'async-validator';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
||||||
@@ -77,6 +75,7 @@ interface SchedulerDialogProps {
|
|||||||
selectedItem: ScheduleItem;
|
selectedItem: ScheduleItem;
|
||||||
validator: Schema;
|
validator: Schema;
|
||||||
dow: string[];
|
dow: string[];
|
||||||
|
commandNames: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const SchedulerDialog = ({
|
const SchedulerDialog = ({
|
||||||
@@ -86,7 +85,8 @@ const SchedulerDialog = ({
|
|||||||
onSave,
|
onSave,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
validator,
|
validator,
|
||||||
dow
|
dow,
|
||||||
|
commandNames
|
||||||
}: SchedulerDialogProps) => {
|
}: SchedulerDialogProps) => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [editItem, setEditItem] = useState<ScheduleItem>(selectedItem);
|
const [editItem, setEditItem] = useState<ScheduleItem>(selectedItem);
|
||||||
@@ -103,12 +103,6 @@ const SchedulerDialog = ({
|
|||||||
if (open) {
|
if (open) {
|
||||||
setFieldErrors(undefined);
|
setFieldErrors(undefined);
|
||||||
setEditItem(selectedItem);
|
setEditItem(selectedItem);
|
||||||
// Set the flags based on type when page is loaded:
|
|
||||||
// 0-127 is day schedule
|
|
||||||
// 128 is timer
|
|
||||||
// 129 is on change
|
|
||||||
// 130 is on condition
|
|
||||||
// 132 is immediate
|
|
||||||
setScheduleType(
|
setScheduleType(
|
||||||
selectedItem.flags <= SCHEDULE_TYPE_THRESHOLD
|
selectedItem.flags <= SCHEDULE_TYPE_THRESHOLD
|
||||||
? ScheduleFlag.SCHEDULE_DAY
|
? ScheduleFlag.SCHEDULE_DAY
|
||||||
@@ -131,21 +125,6 @@ const SchedulerDialog = ({
|
|||||||
await handleSave(editItem);
|
await handleSave(editItem);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { send: executeSchedule } = useRequest(
|
|
||||||
(id: string) => callAction({ action: 'executeSchedule', param: id }),
|
|
||||||
{ immediate: false }
|
|
||||||
)
|
|
||||||
.onSuccess(() => {
|
|
||||||
toast.success(LL.EXECUTE_SCHEDULE_SENT());
|
|
||||||
})
|
|
||||||
.onError((error) => {
|
|
||||||
toast.error(String(error.error?.message || 'An error occurred'));
|
|
||||||
});
|
|
||||||
|
|
||||||
const execute = async () => {
|
|
||||||
await executeSchedule(editItem.name);
|
|
||||||
};
|
|
||||||
|
|
||||||
const remove = () => {
|
const remove = () => {
|
||||||
onSave({ ...editItem, deleted: true });
|
onSave({ ...editItem, deleted: true });
|
||||||
};
|
};
|
||||||
@@ -197,7 +176,6 @@ const SchedulerDialog = ({
|
|||||||
|
|
||||||
const isDaySchedule = scheduleType === ScheduleFlag.SCHEDULE_DAY;
|
const isDaySchedule = scheduleType === ScheduleFlag.SCHEDULE_DAY;
|
||||||
const isTimerSchedule = scheduleType === ScheduleFlag.SCHEDULE_TIMER;
|
const isTimerSchedule = scheduleType === ScheduleFlag.SCHEDULE_TIMER;
|
||||||
const isImmediateSchedule = scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE;
|
|
||||||
const needsTimeField = isDaySchedule || isTimerSchedule;
|
const needsTimeField = isDaySchedule || isTimerSchedule;
|
||||||
|
|
||||||
const dowFlags = getFlagDOWstring(editItem.flags);
|
const dowFlags = getFlagDOWstring(editItem.flags);
|
||||||
@@ -214,7 +192,6 @@ const SchedulerDialog = ({
|
|||||||
if (scheduleType === ScheduleFlag.SCHEDULE_TIMER) return LL.TIMER(1);
|
if (scheduleType === ScheduleFlag.SCHEDULE_TIMER) return LL.TIMER(1);
|
||||||
if (scheduleType === ScheduleFlag.SCHEDULE_CONDITION) return LL.CONDITION();
|
if (scheduleType === ScheduleFlag.SCHEDULE_CONDITION) return LL.CONDITION();
|
||||||
if (scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE) return LL.ONCHANGE();
|
if (scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE) return LL.ONCHANGE();
|
||||||
if (scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE) return LL.IMMEDIATE();
|
|
||||||
return LL.TIME(1);
|
return LL.TIME(1);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -269,14 +246,6 @@ const SchedulerDialog = ({
|
|||||||
{LL.CONDITION()}
|
{LL.CONDITION()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<ToggleButton value={ScheduleFlag.SCHEDULE_IMMEDIATE}>
|
|
||||||
<Typography
|
|
||||||
sx={{ fontSize: TYPOGRAPHY_FONT_SIZE }}
|
|
||||||
color={isImmediateSchedule ? 'primary' : 'grey'}
|
|
||||||
>
|
|
||||||
{LL.IMMEDIATE()}
|
|
||||||
</Typography>
|
|
||||||
</ToggleButton>
|
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
|
|
||||||
{isDaySchedule && (
|
{isDaySchedule && (
|
||||||
@@ -294,74 +263,70 @@ const SchedulerDialog = ({
|
|||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isImmediateSchedule && (
|
<Grid container>
|
||||||
<>
|
<BlockFormControlLabel
|
||||||
<Grid container>
|
control={
|
||||||
<BlockFormControlLabel
|
<Checkbox
|
||||||
control={
|
checked={editItem.active}
|
||||||
<Checkbox
|
onChange={updateFormValue}
|
||||||
checked={editItem.active}
|
name="active"
|
||||||
onChange={updateFormValue}
|
|
||||||
name="active"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={LL.ACTIVE()}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
}
|
||||||
<Grid container>
|
label={LL.ACTIVE()}
|
||||||
{needsTimeField ? (
|
/>
|
||||||
<>
|
<CircleIcon
|
||||||
<TextField
|
color={editItem.active ? 'success' : 'error'}
|
||||||
name="time"
|
sx={{
|
||||||
type="time"
|
fontSize: 16,
|
||||||
label={timeFieldLabel}
|
mt: '12px'
|
||||||
value={timeFieldValue}
|
}}
|
||||||
margin="normal"
|
/>
|
||||||
onChange={updateFormValue}
|
</Grid>
|
||||||
/>
|
|
||||||
{isTimerSchedule && (
|
<Grid container>
|
||||||
<Typography
|
{needsTimeField ? (
|
||||||
sx={{ ml: 2, mt: 4 }}
|
<>
|
||||||
color="warning"
|
<TextField
|
||||||
variant="body2"
|
name="time"
|
||||||
>
|
type="time"
|
||||||
{LL.SCHEDULER_HELP_2()}
|
label={timeFieldLabel}
|
||||||
</Typography>
|
value={timeFieldValue}
|
||||||
)}
|
margin="normal"
|
||||||
</>
|
onChange={updateFormValue}
|
||||||
) : (
|
/>
|
||||||
<TextField
|
{isTimerSchedule && (
|
||||||
name="time"
|
<Typography sx={{ ml: 2, mt: 4 }} color="warning" variant="body2">
|
||||||
label={timeFieldLabel}
|
00:00 = {LL.SCHEDULER_HELP_2()}
|
||||||
multiline
|
</Typography>
|
||||||
fullWidth
|
|
||||||
value={timeFieldValue}
|
|
||||||
margin="normal"
|
|
||||||
onChange={updateFormValue}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</>
|
||||||
</>
|
) : (
|
||||||
)}
|
<TextField
|
||||||
<ValidatedTextField
|
name="time"
|
||||||
fieldErrors={fieldErrors || {}}
|
label={timeFieldLabel}
|
||||||
name="cmd"
|
multiline
|
||||||
label={LL.COMMAND(0)}
|
fullWidth
|
||||||
multiline
|
value={timeFieldValue}
|
||||||
fullWidth
|
margin="normal"
|
||||||
value={editItem.cmd}
|
onChange={updateFormValue}
|
||||||
margin="normal"
|
/>
|
||||||
onChange={updateFormValue}
|
)}
|
||||||
/>
|
</Grid>
|
||||||
<TextField
|
<TextField
|
||||||
name="value"
|
name="cmd_name"
|
||||||
label={LL.VALUE(0)}
|
label={LL.COMMAND(0)}
|
||||||
multiline
|
value={editItem.cmd_name}
|
||||||
margin="normal"
|
|
||||||
fullWidth
|
fullWidth
|
||||||
value={editItem.value}
|
select
|
||||||
|
margin="normal"
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
/>
|
>
|
||||||
|
{commandNames.map((name) => (
|
||||||
|
<MenuItem key={name} value={name}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors || {}}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="name"
|
name="name"
|
||||||
@@ -402,16 +367,6 @@ const SchedulerDialog = ({
|
|||||||
>
|
>
|
||||||
{creating ? LL.ADD(0) : LL.UPDATE()}
|
{creating ? LL.ADD(0) : LL.UPDATE()}
|
||||||
</Button>
|
</Button>
|
||||||
{isImmediateSchedule && !creating && editItem.cmd !== '' && (
|
|
||||||
<Button
|
|
||||||
startIcon={<PlayArrowIcon />}
|
|
||||||
variant="outlined"
|
|
||||||
onClick={execute}
|
|
||||||
color="success"
|
|
||||||
>
|
|
||||||
{LL.EXECUTE()}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -354,16 +354,14 @@ export interface ScheduleItem {
|
|||||||
deleted?: boolean;
|
deleted?: boolean;
|
||||||
flags: number;
|
flags: number;
|
||||||
time: string; // also used for Condition and On Change
|
time: string; // also used for Condition and On Change
|
||||||
cmd: string;
|
cmd_name: string; // references a named Command
|
||||||
value: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
o_id?: number;
|
o_id?: number;
|
||||||
o_active?: boolean;
|
o_active?: boolean;
|
||||||
o_deleted?: boolean;
|
o_deleted?: boolean;
|
||||||
o_flags?: number;
|
o_flags?: number;
|
||||||
o_time?: string;
|
o_time?: string;
|
||||||
o_cmd?: string;
|
o_cmd_name?: string;
|
||||||
o_value?: string;
|
|
||||||
o_name?: string;
|
o_name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,6 +369,23 @@ export interface Schedule {
|
|||||||
readonly schedule: readonly ScheduleItem[];
|
readonly schedule: readonly ScheduleItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CommandItem {
|
||||||
|
id: number;
|
||||||
|
cmd: string;
|
||||||
|
value: string;
|
||||||
|
name: string;
|
||||||
|
deleted?: boolean;
|
||||||
|
o_id?: number;
|
||||||
|
o_cmd?: string;
|
||||||
|
o_value?: string;
|
||||||
|
o_name?: string;
|
||||||
|
o_deleted?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Commands {
|
||||||
|
readonly commands: readonly CommandItem[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface ModuleItem {
|
export interface ModuleItem {
|
||||||
id: number; // unique index
|
id: number; // unique index
|
||||||
key: string;
|
key: string;
|
||||||
@@ -401,8 +416,7 @@ export enum ScheduleFlag {
|
|||||||
SCHEDULE_DAY = 0, // no bits set
|
SCHEDULE_DAY = 0, // no bits set
|
||||||
SCHEDULE_TIMER = 128, // bit 8
|
SCHEDULE_TIMER = 128, // bit 8
|
||||||
SCHEDULE_ONCHANGE = 129, // bit 1
|
SCHEDULE_ONCHANGE = 129, // bit 1
|
||||||
SCHEDULE_CONDITION = 130, // bit 2
|
SCHEDULE_CONDITION = 130 // bit 2
|
||||||
SCHEDULE_IMMEDIATE = 132 // bit 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityItem {
|
export interface EntityItem {
|
||||||
@@ -445,6 +459,7 @@ export const enum DeviceType {
|
|||||||
ANALOGSENSOR = 2,
|
ANALOGSENSOR = 2,
|
||||||
SCHEDULER = 3,
|
SCHEDULER = 3,
|
||||||
CUSTOM = 4,
|
CUSTOM = 4,
|
||||||
|
COMMAND = 5,
|
||||||
BOILER,
|
BOILER,
|
||||||
THERMOSTAT,
|
THERMOSTAT,
|
||||||
MIXER,
|
MIXER,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
AnalogSensor,
|
AnalogSensor,
|
||||||
|
CommandItem,
|
||||||
DeviceValue,
|
DeviceValue,
|
||||||
EntityItem,
|
EntityItem,
|
||||||
ScheduleItem,
|
ScheduleItem,
|
||||||
@@ -237,6 +238,24 @@ export const schedulerItemValidation = (
|
|||||||
NAME_PATTERN_REQUIRED,
|
NAME_PATTERN_REQUIRED,
|
||||||
uniqueNameValidator(schedule, scheduleItem.o_name)
|
uniqueNameValidator(schedule, scheduleItem.o_name)
|
||||||
],
|
],
|
||||||
|
cmd_name: [{ required: true, message: 'Command is required' }]
|
||||||
|
});
|
||||||
|
|
||||||
|
export const uniqueCommandNameValidator = (
|
||||||
|
commands: CommandItem[],
|
||||||
|
o_name?: string
|
||||||
|
) => createUniqueNameValidator(commands, o_name);
|
||||||
|
|
||||||
|
export const commandItemValidation = (
|
||||||
|
commands: CommandItem[],
|
||||||
|
commandItem: CommandItem
|
||||||
|
) =>
|
||||||
|
new Schema({
|
||||||
|
name: [
|
||||||
|
{ required: true, message: 'Name is required' },
|
||||||
|
NAME_PATTERN_REQUIRED,
|
||||||
|
uniqueCommandNameValidator(commands, commandItem.o_name)
|
||||||
|
],
|
||||||
cmd: [
|
cmd: [
|
||||||
{ required: true, message: 'Command is required' },
|
{ required: true, message: 'Command is required' },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
|
|||||||
import LiveHelpIcon from '@mui/icons-material/LiveHelp';
|
import LiveHelpIcon from '@mui/icons-material/LiveHelp';
|
||||||
import MoreTimeIcon from '@mui/icons-material/MoreTime';
|
import MoreTimeIcon from '@mui/icons-material/MoreTime';
|
||||||
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
||||||
|
import PlaylistPlayIcon from '@mui/icons-material/PlaylistPlay';
|
||||||
import SensorsIcon from '@mui/icons-material/Sensors';
|
import SensorsIcon from '@mui/icons-material/Sensors';
|
||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
import StarIcon from '@mui/icons-material/Star';
|
import StarIcon from '@mui/icons-material/Star';
|
||||||
@@ -80,6 +81,12 @@ const LayoutMenuComponent = () => {
|
|||||||
disabled={!me.admin}
|
disabled={!me.admin}
|
||||||
to={`/customizations`}
|
to={`/customizations`}
|
||||||
/>
|
/>
|
||||||
|
<LayoutMenuItem
|
||||||
|
icon={PlaylistPlayIcon}
|
||||||
|
label={LL.COMMANDS()}
|
||||||
|
disabled={!me.admin}
|
||||||
|
to={`/commands`}
|
||||||
|
/>
|
||||||
<LayoutMenuItem
|
<LayoutMenuItem
|
||||||
icon={MoreTimeIcon}
|
icon={MoreTimeIcon}
|
||||||
label={LL.SCHEDULER()}
|
label={LL.SCHEDULER()}
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const cz: Translation = {
|
|||||||
ID_OF: '{0} ID',
|
ID_OF: '{0} ID',
|
||||||
DEVICE: 'Zařízení',
|
DEVICE: 'Zařízení',
|
||||||
PRODUCT: 'Produkt',
|
PRODUCT: 'Produkt',
|
||||||
VERSION: 'Verze',
|
|
||||||
BRAND: 'Značka',
|
BRAND: 'Značka',
|
||||||
|
VERSION: 'Verze',
|
||||||
ENTITY_NAME: 'Název entity',
|
ENTITY_NAME: 'Název entity',
|
||||||
VALUE: '{{value|Value}}',
|
VALUE: '{{value|Value}}',
|
||||||
DEVICES: 'Zařízení',
|
DEVICES: 'Zařízení',
|
||||||
@@ -102,8 +102,8 @@ const cz: Translation = {
|
|||||||
PHY_TYPE: 'Typ Eth PHY',
|
PHY_TYPE: 'Typ Eth PHY',
|
||||||
DISABLED: 'zakázáno',
|
DISABLED: 'zakázáno',
|
||||||
TX_MODE: 'EMS Tx režim',
|
TX_MODE: 'EMS Tx režim',
|
||||||
HARDWARE: 'Hardware',
|
|
||||||
EMS_BUS: '{{Bus|EMS Bus}}',
|
EMS_BUS: '{{Bus|EMS Bus}}',
|
||||||
|
HARDWARE: 'Hardware',
|
||||||
GENERAL_OPTIONS: 'Obecné možnosti',
|
GENERAL_OPTIONS: 'Obecné možnosti',
|
||||||
LANGUAGE_ENTITIES: 'Jazyk (pro entity zařízení)',
|
LANGUAGE_ENTITIES: 'Jazyk (pro entity zařízení)',
|
||||||
HIDE_LED: 'Skrýt LED',
|
HIDE_LED: 'Skrýt LED',
|
||||||
@@ -286,14 +286,13 @@ const cz: Translation = {
|
|||||||
STAY: 'Zůstat',
|
STAY: 'Zůstat',
|
||||||
LEAVE: 'Odejít',
|
LEAVE: 'Odejít',
|
||||||
SCHEDULER: 'Plánovač',
|
SCHEDULER: 'Plánovač',
|
||||||
SCHEDULER_HELP_1: 'Automatizujte příkazy přidáním naplánovaných událostí níže. Nastavte jedinečný název pro povolení/zakázání aktivace přes API/MQTT',
|
SCHEDULER_HELP_1: 'Automatizujte příkazy přidáním naplánovaných událostí níže',
|
||||||
SCHEDULER_HELP_2: 'Použijte 00:00 pro spuštění při startu',
|
SCHEDULER_HELP_2: 'při startu',
|
||||||
SCHEDULE: 'Harmonogram',
|
SCHEDULE: 'Harmonogram',
|
||||||
TIME: 'Čas',
|
TIME: 'Čas',
|
||||||
TIMER: 'Časovač',
|
TIMER: 'Časovač',
|
||||||
ONCHANGE: 'Při změně',
|
ONCHANGE: 'Při změně',
|
||||||
CONDITION: 'Podmínka',
|
CONDITION: 'Podmínka',
|
||||||
IMMEDIATE: 'Ihned',
|
|
||||||
SCHEDULE_UPDATED: 'Harmonogram aktualizován',
|
SCHEDULE_UPDATED: 'Harmonogram aktualizován',
|
||||||
SCHEDULE_TIMER_1: 'při startu',
|
SCHEDULE_TIMER_1: 'při startu',
|
||||||
SCHEDULE_TIMER_2: 'každou minutu',
|
SCHEDULE_TIMER_2: 'každou minutu',
|
||||||
@@ -365,7 +364,10 @@ const cz: Translation = {
|
|||||||
WARNING_SYSTEM_BACKUP: 'Toto vytvoří zálohu vašich celých systémových konfigurací a nastavení. Všechna hesla budou v zálohovém souboru čitelná. Buďte opatrní při sdílení! Opravdu chcete pokračovat?',
|
WARNING_SYSTEM_BACKUP: 'Toto vytvoří zálohu vašich celých systémových konfigurací a nastavení. Všechna hesla budou v zálohovém souboru čitelná. Buďte opatrní při sdílení! Opravdu chcete pokračovat?',
|
||||||
TEST_EMAIL_SUCCESSFUL: 'Test email byl úspěšně odeslán',
|
TEST_EMAIL_SUCCESSFUL: 'Test email byl úspěšně odeslán',
|
||||||
SYSTEM_NAME: 'Název systému',
|
SYSTEM_NAME: 'Název systému',
|
||||||
EXECUTE_SCHEDULE_SENT: 'Plán byl úspěšně proveden'
|
COMMANDS: 'Příkazy',
|
||||||
|
COMMANDS_UPDATED: 'Příkazy byly aktualizovány',
|
||||||
|
COMMANDS_HELP_1: 'Definujte vlastní příkazy pro magistrali EMS',
|
||||||
|
EXECUTE_COMMAND_SENT: 'Příkaz odeslán',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default cz;
|
export default cz;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const de: Translation = {
|
|||||||
ID_OF: '{0}-ID',
|
ID_OF: '{0}-ID',
|
||||||
DEVICE: 'Geräte',
|
DEVICE: 'Geräte',
|
||||||
PRODUCT: 'Produkt',
|
PRODUCT: 'Produkt',
|
||||||
VERSION: 'Version',
|
|
||||||
BRAND: 'Marke',
|
BRAND: 'Marke',
|
||||||
|
VERSION: 'Version',
|
||||||
ENTITY_NAME: 'Entitätsname',
|
ENTITY_NAME: 'Entitätsname',
|
||||||
VALUE: '{{wert|Wert}}',
|
VALUE: '{{wert|Wert}}',
|
||||||
DEVICES: 'Geräte',
|
DEVICES: 'Geräte',
|
||||||
@@ -102,8 +102,8 @@ const de: Translation = {
|
|||||||
PHY_TYPE: 'Eth PHY Typ',
|
PHY_TYPE: 'Eth PHY Typ',
|
||||||
DISABLED: 'deaktiviert',
|
DISABLED: 'deaktiviert',
|
||||||
TX_MODE: 'EMS Tx-Modus',
|
TX_MODE: 'EMS Tx-Modus',
|
||||||
HARDWARE: 'Hardware',
|
|
||||||
EMS_BUS: '{{Bus|EMS Bus}}',
|
EMS_BUS: '{{Bus|EMS Bus}}',
|
||||||
|
HARDWARE: 'Hardware',
|
||||||
GENERAL_OPTIONS: 'Allgemeine Optionen',
|
GENERAL_OPTIONS: 'Allgemeine Optionen',
|
||||||
LANGUAGE_ENTITIES: 'Sprache (für Geräteentitäten)',
|
LANGUAGE_ENTITIES: 'Sprache (für Geräteentitäten)',
|
||||||
HIDE_LED: 'LED ausblenden',
|
HIDE_LED: 'LED ausblenden',
|
||||||
@@ -286,14 +286,13 @@ const de: Translation = {
|
|||||||
STAY: 'Bleiben',
|
STAY: 'Bleiben',
|
||||||
LEAVE: 'Verlassen',
|
LEAVE: 'Verlassen',
|
||||||
SCHEDULER: 'Planer',
|
SCHEDULER: 'Planer',
|
||||||
SCHEDULER_HELP_1: 'Fügen Sie eigene geplante Befehle zur Automatisierung hinzu. Vergeben Sie einen Entitätsnamen, um die Aktivierung über API/Mqtt zu steuern',
|
SCHEDULER_HELP_1: 'Fügen Sie eigene geplante Befehle zur Automatisierung hinzu',
|
||||||
SCHEDULER_HELP_2: '00:00 aktiviert einmalige Ausführung beim Start.',
|
SCHEDULER_HELP_2: 'beim Start',
|
||||||
SCHEDULE: 'Zeitplan',
|
SCHEDULE: 'Zeitplan',
|
||||||
TIME: 'Zeit',
|
TIME: 'Zeit',
|
||||||
TIMER: 'Timer',
|
TIMER: 'Timer',
|
||||||
ONCHANGE: 'Bei Änderung',
|
ONCHANGE: 'Bei Änderung',
|
||||||
CONDITION: 'Zustand',
|
CONDITION: 'Zustand',
|
||||||
IMMEDIATE: 'Sofort',
|
|
||||||
SCHEDULE_UPDATED: 'Zeitplan aktualisiert',
|
SCHEDULE_UPDATED: 'Zeitplan aktualisiert',
|
||||||
SCHEDULE_TIMER_1: 'beim Start',
|
SCHEDULE_TIMER_1: 'beim Start',
|
||||||
SCHEDULE_TIMER_2: 'jede Minute',
|
SCHEDULE_TIMER_2: 'jede Minute',
|
||||||
@@ -365,7 +364,10 @@ const de: Translation = {
|
|||||||
WARNING_SYSTEM_BACKUP: 'Dies wird eine Sicherung Ihrer vollständigen Systemkonfiguration und Einstellungen erstellen. Alle Passwörter werden in dieser Sicherungsdatei lesbar sein. Seien Sie vorsichtig beim Teilen! Möchten Sie fortfahren?',
|
WARNING_SYSTEM_BACKUP: 'Dies wird eine Sicherung Ihrer vollständigen Systemkonfiguration und Einstellungen erstellen. Alle Passwörter werden in dieser Sicherungsdatei lesbar sein. Seien Sie vorsichtig beim Teilen! Möchten Sie fortfahren?',
|
||||||
TEST_EMAIL_SUCCESSFUL: 'Test email erfolgreich gesendet',
|
TEST_EMAIL_SUCCESSFUL: 'Test email erfolgreich gesendet',
|
||||||
SYSTEM_NAME: 'Systemname',
|
SYSTEM_NAME: 'Systemname',
|
||||||
EXECUTE_SCHEDULE_SENT: 'Zeitplan erfolgreich ausgeführt'
|
COMMANDS: 'Befehle',
|
||||||
|
COMMANDS_UPDATED: 'Befehle wurden aktualisiert',
|
||||||
|
COMMANDS_HELP_1: 'Definieren Sie eigene Befehle für die EMS-Magistral',
|
||||||
|
EXECUTE_COMMAND_SENT: 'Befehl gesendet',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default de;
|
export default de;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const en: Translation = {
|
|||||||
ID_OF: '{0} ID',
|
ID_OF: '{0} ID',
|
||||||
DEVICE: 'Device',
|
DEVICE: 'Device',
|
||||||
PRODUCT: 'Product',
|
PRODUCT: 'Product',
|
||||||
VERSION: 'Version',
|
|
||||||
BRAND: 'Brand',
|
BRAND: 'Brand',
|
||||||
|
VERSION: 'Version',
|
||||||
ENTITY_NAME: 'Entity Name',
|
ENTITY_NAME: 'Entity Name',
|
||||||
VALUE: '{{value|Value}}',
|
VALUE: '{{value|Value}}',
|
||||||
DEVICES: 'Devices',
|
DEVICES: 'Devices',
|
||||||
@@ -102,8 +102,8 @@ const en: Translation = {
|
|||||||
PHY_TYPE: 'Eth PHY Type',
|
PHY_TYPE: 'Eth PHY Type',
|
||||||
DISABLED: 'disabled',
|
DISABLED: 'disabled',
|
||||||
TX_MODE: 'EMS Tx Mode',
|
TX_MODE: 'EMS Tx Mode',
|
||||||
HARDWARE: 'Hardware',
|
|
||||||
EMS_BUS: '{{Bus|EMS Bus}}',
|
EMS_BUS: '{{Bus|EMS Bus}}',
|
||||||
|
HARDWARE: 'Hardware',
|
||||||
GENERAL_OPTIONS: 'General Options',
|
GENERAL_OPTIONS: 'General Options',
|
||||||
LANGUAGE_ENTITIES: 'Language (for device entities)',
|
LANGUAGE_ENTITIES: 'Language (for device entities)',
|
||||||
HIDE_LED: 'Hide LED',
|
HIDE_LED: 'Hide LED',
|
||||||
@@ -286,21 +286,18 @@ const en: Translation = {
|
|||||||
STAY: 'Stay',
|
STAY: 'Stay',
|
||||||
LEAVE: 'Leave',
|
LEAVE: 'Leave',
|
||||||
SCHEDULER: 'Scheduler',
|
SCHEDULER: 'Scheduler',
|
||||||
SCHEDULER_HELP_1: 'Automate commands by adding scheduled events below. Set a unique Name to enable/disable activation via API/MQTT',
|
SCHEDULER_HELP_1: 'Automate commands by adding scheduled events below',
|
||||||
SCHEDULER_HELP_2: 'Use 00:00 to trigger once on start-up',
|
SCHEDULER_HELP_2: 'at start-up',
|
||||||
SCHEDULE: 'Schedule',
|
SCHEDULE: 'Schedule',
|
||||||
TIME: 'Time',
|
TIME: 'Time',
|
||||||
TIMER: 'Timer',
|
TIMER: 'Timer',
|
||||||
ONCHANGE: 'On Change',
|
ONCHANGE: 'On Change',
|
||||||
CONDITION: 'Condition',
|
CONDITION: 'Condition',
|
||||||
IMMEDIATE: 'Immediate',
|
|
||||||
SCHEDULE_UPDATED: 'Schedule updated',
|
|
||||||
SCHEDULE_TIMER_1: 'on startup',
|
SCHEDULE_TIMER_1: 'on startup',
|
||||||
SCHEDULE_TIMER_2: 'every minute',
|
SCHEDULE_TIMER_2: 'every minute',
|
||||||
SCHEDULE_TIMER_3: 'every hour',
|
SCHEDULE_TIMER_3: 'every hour',
|
||||||
CUSTOM_ENTITIES: 'Custom Entities',
|
CUSTOM_ENTITIES: 'Custom Entities',
|
||||||
ENTITIES_HELP_1: 'Define custom EMS entities or dynamic user variables',
|
ENTITIES_HELP_1: 'Define custom EMS entities or dynamic user variables',
|
||||||
ENTITIES_UPDATED: 'Entities Updated',
|
|
||||||
WRITEABLE: 'Writeable',
|
WRITEABLE: 'Writeable',
|
||||||
SHOWING: 'Showing',
|
SHOWING: 'Showing',
|
||||||
SEARCH: 'Search',
|
SEARCH: 'Search',
|
||||||
@@ -321,7 +318,6 @@ const en: Translation = {
|
|||||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware',
|
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware',
|
||||||
MODULES: 'Modules',
|
MODULES: 'Modules',
|
||||||
MODULES_1: 'Activate or deactivate external modules',
|
MODULES_1: 'Activate or deactivate external modules',
|
||||||
MODULES_UPDATED: 'Modules updated',
|
|
||||||
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules',
|
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules',
|
||||||
MODULES_NONE: 'No external modules detected',
|
MODULES_NONE: 'No external modules detected',
|
||||||
RENAME: 'Rename',
|
RENAME: 'Rename',
|
||||||
@@ -340,11 +336,11 @@ const en: Translation = {
|
|||||||
RESTARTING_POST: 'Preparing',
|
RESTARTING_POST: 'Preparing',
|
||||||
AUTO_SCROLL: 'Auto Scroll',
|
AUTO_SCROLL: 'Auto Scroll',
|
||||||
DASHBOARD: 'Dashboard',
|
DASHBOARD: 'Dashboard',
|
||||||
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.',
|
|
||||||
DEVELOPER_MODE: 'Developer Mode',
|
DEVELOPER_MODE: 'Developer Mode',
|
||||||
BYTES: 'Bytes',
|
BYTES: 'Bytes',
|
||||||
BITMASK: 'Bit Mask',
|
BITMASK: 'Bit Mask',
|
||||||
DUPLICATE: 'Duplicate',
|
DUPLICATE: 'Duplicate',
|
||||||
|
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.',
|
||||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the',
|
NO_DATA_1: 'No favorite EMS entities found yet. Use the',
|
||||||
NO_DATA_2: 'module to mark them.',
|
NO_DATA_2: 'module to mark them.',
|
||||||
NO_DATA_3: 'To see all available entities go to',
|
NO_DATA_3: 'To see all available entities go to',
|
||||||
@@ -365,7 +361,10 @@ const en: Translation = {
|
|||||||
WARNING_SYSTEM_BACKUP: 'This will create a backup of your full system configuration and settings. All passwords will be readable in the backup file. Be careful with sharing! Do you want to continue?',
|
WARNING_SYSTEM_BACKUP: 'This will create a backup of your full system configuration and settings. All passwords will be readable in the backup file. Be careful with sharing! Do you want to continue?',
|
||||||
TEST_EMAIL_SUCCESSFUL: 'Test email sent successfully',
|
TEST_EMAIL_SUCCESSFUL: 'Test email sent successfully',
|
||||||
SYSTEM_NAME: 'System Name',
|
SYSTEM_NAME: 'System Name',
|
||||||
EXECUTE_SCHEDULE_SENT: 'Schedule executed successfully'
|
COMMANDS: 'Commands',
|
||||||
|
COMMANDS_UPDATED: 'Commands updated',
|
||||||
|
COMMANDS_HELP_1: 'Define reusable named commands below. These can be executed from the console, API/MQTT, or referenced by the Scheduler',
|
||||||
|
EXECUTE_COMMAND_SENT: 'Command sent',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default en;
|
export default en;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const fr: Translation = {
|
|||||||
ID_OF: 'ID {0}',
|
ID_OF: 'ID {0}',
|
||||||
DEVICE: 'Appareil',
|
DEVICE: 'Appareil',
|
||||||
PRODUCT: 'Produit',
|
PRODUCT: 'Produit',
|
||||||
VERSION: 'Version',
|
|
||||||
BRAND: 'Marque',
|
BRAND: 'Marque',
|
||||||
|
VERSION: 'Version',
|
||||||
ENTITY_NAME: "Nom de l'entité",
|
ENTITY_NAME: "Nom de l'entité",
|
||||||
VALUE: 'Valeur',
|
VALUE: 'Valeur',
|
||||||
DEVICES: 'Appareils',
|
DEVICES: 'Appareils',
|
||||||
@@ -102,8 +102,8 @@ const fr: Translation = {
|
|||||||
PHY_TYPE: 'Eth PHY Type',
|
PHY_TYPE: 'Eth PHY Type',
|
||||||
DISABLED: 'désactivé',
|
DISABLED: 'désactivé',
|
||||||
TX_MODE: 'EMS Tx Mode',
|
TX_MODE: 'EMS Tx Mode',
|
||||||
HARDWARE: 'Hardware',
|
|
||||||
EMS_BUS: '{{Bus|EMS Bus}}',
|
EMS_BUS: '{{Bus|EMS Bus}}',
|
||||||
|
HARDWARE: 'Hardware',
|
||||||
GENERAL_OPTIONS: 'Options générales',
|
GENERAL_OPTIONS: 'Options générales',
|
||||||
LANGUAGE_ENTITIES: 'Langue (pour les entités du matériel)',
|
LANGUAGE_ENTITIES: 'Langue (pour les entités du matériel)',
|
||||||
HIDE_LED: 'Cacher la LED',
|
HIDE_LED: 'Cacher la LED',
|
||||||
@@ -113,9 +113,9 @@ const fr: Translation = {
|
|||||||
BYPASS_TOKEN: "Contourner l'autorisation du jeton d'accès sur les appels API",
|
BYPASS_TOKEN: "Contourner l'autorisation du jeton d'accès sur les appels API",
|
||||||
READONLY: 'Activer le mode lecture uniquement (bloque toutes les commandes EMS sortantes en écriture Tx)',
|
READONLY: 'Activer le mode lecture uniquement (bloque toutes les commandes EMS sortantes en écriture Tx)',
|
||||||
UNDERCLOCK_CPU: 'Underclock du CPU',
|
UNDERCLOCK_CPU: 'Underclock du CPU',
|
||||||
HEATINGOFF: 'Démarrer le chauffage avec le chauffage forcé éteint',
|
|
||||||
REMOTE_TIMEOUT: 'Remote timeout',
|
REMOTE_TIMEOUT: 'Remote timeout',
|
||||||
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature',
|
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature',
|
||||||
|
HEATINGOFF: 'Démarrer le chauffage avec le chauffage forcé éteint',
|
||||||
MIN_DURATION: 'Wait time',
|
MIN_DURATION: 'Wait time',
|
||||||
ENABLE_SHOWER_TIMER: 'Activer la minuterie de la douche',
|
ENABLE_SHOWER_TIMER: 'Activer la minuterie de la douche',
|
||||||
ENABLE_SHOWER_ALERT: 'Activer les alertes de durée de douche',
|
ENABLE_SHOWER_ALERT: 'Activer les alertes de durée de douche',
|
||||||
@@ -286,14 +286,13 @@ const fr: Translation = {
|
|||||||
STAY: 'Rester',
|
STAY: 'Rester',
|
||||||
LEAVE: 'Quitter',
|
LEAVE: 'Quitter',
|
||||||
SCHEDULER: 'Scheduler',
|
SCHEDULER: 'Scheduler',
|
||||||
SCHEDULER_HELP_1: 'Automatiser les commandes en ajoutant des événements programmés ci-dessous. Définissez un nom unique pour activer/désactiver l\'activation via API/MQTT',
|
SCHEDULER_HELP_1: 'Automatiser les commandes en ajoutant des événements programmés ci-dessous',
|
||||||
SCHEDULER_HELP_2: 'Utiliser 00:00 pour déclencher une fois au démarrage',
|
SCHEDULER_HELP_2: 'au démarrage',
|
||||||
SCHEDULE: 'Programme',
|
SCHEDULE: 'Programme',
|
||||||
TIME: 'Temps',
|
TIME: 'Temps',
|
||||||
TIMER: 'Minuteur',
|
TIMER: 'Minuteur',
|
||||||
ONCHANGE: 'Sur le changement',
|
ONCHANGE: 'Sur le changement',
|
||||||
CONDITION: 'Condition',
|
CONDITION: 'Condition',
|
||||||
IMMEDIATE: 'Immédiat',
|
|
||||||
SCHEDULE_UPDATED: 'Programme mis à jour',
|
SCHEDULE_UPDATED: 'Programme mis à jour',
|
||||||
SCHEDULE_TIMER_1: 'au démarrage',
|
SCHEDULE_TIMER_1: 'au démarrage',
|
||||||
SCHEDULE_TIMER_2: 'toutes les minutes',
|
SCHEDULE_TIMER_2: 'toutes les minutes',
|
||||||
@@ -365,7 +364,10 @@ const fr: Translation = {
|
|||||||
WARNING_SYSTEM_BACKUP: 'Cela créera une sauvegarde de votre configuration et paramètres complets. Tous les mots de passe seront lisibles dans le fichier de sauvegarde. Soyez prudent avec le partage ! Voulez-vous continuer ?',
|
WARNING_SYSTEM_BACKUP: 'Cela créera une sauvegarde de votre configuration et paramètres complets. Tous les mots de passe seront lisibles dans le fichier de sauvegarde. Soyez prudent avec le partage ! Voulez-vous continuer ?',
|
||||||
TEST_EMAIL_SUCCESSFUL: 'Test email envoyé avec succès',
|
TEST_EMAIL_SUCCESSFUL: 'Test email envoyé avec succès',
|
||||||
SYSTEM_NAME: 'Nom du système',
|
SYSTEM_NAME: 'Nom du système',
|
||||||
EXECUTE_SCHEDULE_SENT: 'Planlegger exécuté avec succès'
|
COMMANDS: 'Commandes',
|
||||||
|
COMMANDS_UPDATED: 'Commandes mises à jour',
|
||||||
|
COMMANDS_HELP_1: 'Définir des commandes personnalisées pour la magistral EMS',
|
||||||
|
EXECUTE_COMMAND_SENT: 'Commande envoyée',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default fr;
|
export default fr;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const it: Translation = {
|
|||||||
ID_OF: '{0} ID',
|
ID_OF: '{0} ID',
|
||||||
DEVICE: 'Dispositivo',
|
DEVICE: 'Dispositivo',
|
||||||
PRODUCT: 'Prodotto',
|
PRODUCT: 'Prodotto',
|
||||||
VERSION: 'Versione',
|
|
||||||
BRAND: 'Marca',
|
BRAND: 'Marca',
|
||||||
|
VERSION: 'Versione',
|
||||||
ENTITY_NAME: 'Nome Entità',
|
ENTITY_NAME: 'Nome Entità',
|
||||||
VALUE: '{{valore|Valore}}',
|
VALUE: '{{valore|Valore}}',
|
||||||
DEVICES: 'Dispositivi',
|
DEVICES: 'Dispositivi',
|
||||||
@@ -102,8 +102,8 @@ const it: Translation = {
|
|||||||
PHY_TYPE: 'Eth PHY Type',
|
PHY_TYPE: 'Eth PHY Type',
|
||||||
DISABLED: 'disattivato',
|
DISABLED: 'disattivato',
|
||||||
TX_MODE: 'EMS Modo Tx ',
|
TX_MODE: 'EMS Modo Tx ',
|
||||||
HARDWARE: 'Hardware',
|
|
||||||
EMS_BUS: '{{Bus|EMS Bus}}',
|
EMS_BUS: '{{Bus|EMS Bus}}',
|
||||||
|
HARDWARE: 'Hardware',
|
||||||
GENERAL_OPTIONS: 'Opzioni Generali',
|
GENERAL_OPTIONS: 'Opzioni Generali',
|
||||||
LANGUAGE_ENTITIES: 'Lingua (per entità dispositivi)',
|
LANGUAGE_ENTITIES: 'Lingua (per entità dispositivi)',
|
||||||
HIDE_LED: 'Nascondi LED',
|
HIDE_LED: 'Nascondi LED',
|
||||||
@@ -286,14 +286,13 @@ const it: Translation = {
|
|||||||
STAY: 'Stai',
|
STAY: 'Stai',
|
||||||
LEAVE: 'Esci',
|
LEAVE: 'Esci',
|
||||||
SCHEDULER: 'Programma eventi',
|
SCHEDULER: 'Programma eventi',
|
||||||
SCHEDULER_HELP_1: "Automatizza i comandi aggiungendo gli eventi programmati di seguito. Imposta un nome univoco per abilitare/disabilitare l'attivazione tramite API/MQTT",
|
SCHEDULER_HELP_1: "Automatizza i comandi aggiungendo gli eventi programmati di seguito",
|
||||||
SCHEDULER_HELP_2: "per attivare una volta all'avvio",
|
SCHEDULER_HELP_2: "all'avvio",
|
||||||
SCHEDULE: 'Programma',
|
SCHEDULE: 'Programma',
|
||||||
TIME: 'Ora',
|
TIME: 'Ora',
|
||||||
TIMER: 'Orologio',
|
TIMER: 'Orologio',
|
||||||
ONCHANGE: 'Sul cambiamento',
|
ONCHANGE: 'Sul cambiamento',
|
||||||
CONDITION: 'Condizione',
|
CONDITION: 'Condizione',
|
||||||
IMMEDIATE: 'Immediata',
|
|
||||||
SCHEDULE_UPDATED: 'Calendario aggiornato',
|
SCHEDULE_UPDATED: 'Calendario aggiornato',
|
||||||
SCHEDULE_TIMER_1: 'All avvio',
|
SCHEDULE_TIMER_1: 'All avvio',
|
||||||
SCHEDULE_TIMER_2: 'Ogni minuto',
|
SCHEDULE_TIMER_2: 'Ogni minuto',
|
||||||
@@ -365,7 +364,10 @@ const it: Translation = {
|
|||||||
WARNING_SYSTEM_BACKUP: 'Questo creerà un backup delle tue configurazioni e impostazioni complete. Tutte le password saranno leggibili nel file di backup. Sei sicuro di voler continuare?',
|
WARNING_SYSTEM_BACKUP: 'Questo creerà un backup delle tue configurazioni e impostazioni complete. Tutte le password saranno leggibili nel file di backup. Sei sicuro di voler continuare?',
|
||||||
TEST_EMAIL_SUCCESSFUL: 'Test email inviata con successo',
|
TEST_EMAIL_SUCCESSFUL: 'Test email inviata con successo',
|
||||||
SYSTEM_NAME: 'Nome del sistema',
|
SYSTEM_NAME: 'Nome del sistema',
|
||||||
EXECUTE_SCHEDULE_SENT: 'Programma eseguito con successo'
|
COMMANDS: 'Comandi',
|
||||||
|
COMMANDS_UPDATED: 'Comandi aggiornati',
|
||||||
|
COMMANDS_HELP_1: 'Definisci comandi personalizzati per la magistrali EMS',
|
||||||
|
EXECUTE_COMMAND_SENT: 'Comando inviato',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default it;
|
export default it;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const nl: Translation = {
|
|||||||
ID_OF: '{0} ID',
|
ID_OF: '{0} ID',
|
||||||
DEVICE: 'Apparaat',
|
DEVICE: 'Apparaat',
|
||||||
PRODUCT: 'Product',
|
PRODUCT: 'Product',
|
||||||
VERSION: 'Versie',
|
|
||||||
BRAND: 'Merk',
|
BRAND: 'Merk',
|
||||||
|
VERSION: 'Versie',
|
||||||
ENTITY_NAME: 'Entiteit',
|
ENTITY_NAME: 'Entiteit',
|
||||||
VALUE: '{{waarde|Waarde}}',
|
VALUE: '{{waarde|Waarde}}',
|
||||||
DEVICES: 'Apparaten',
|
DEVICES: 'Apparaten',
|
||||||
@@ -100,10 +100,10 @@ const nl: Translation = {
|
|||||||
BUTTON: 'Toets',
|
BUTTON: 'Toets',
|
||||||
TEMPERATURE: 'Temperatuur',
|
TEMPERATURE: 'Temperatuur',
|
||||||
PHY_TYPE: 'Eth PHY Type',
|
PHY_TYPE: 'Eth PHY Type',
|
||||||
TX_MODE: 'EMS Tx Mode',
|
|
||||||
HARDWARE: 'Hardware',
|
|
||||||
EMS_BUS: '{{Bus|EMS Bus}}',
|
|
||||||
DISABLED: 'Uitgeschakeld',
|
DISABLED: 'Uitgeschakeld',
|
||||||
|
TX_MODE: 'EMS Tx Mode',
|
||||||
|
EMS_BUS: '{{Bus|EMS Bus}}',
|
||||||
|
HARDWARE: 'Hardware',
|
||||||
GENERAL_OPTIONS: 'Algemene Opties',
|
GENERAL_OPTIONS: 'Algemene Opties',
|
||||||
LANGUAGE_ENTITIES: 'Taal (voor apparaat entiteiten)',
|
LANGUAGE_ENTITIES: 'Taal (voor apparaat entiteiten)',
|
||||||
HIDE_LED: 'Verberg LED',
|
HIDE_LED: 'Verberg LED',
|
||||||
@@ -286,14 +286,13 @@ const nl: Translation = {
|
|||||||
STAY: 'Blijven',
|
STAY: 'Blijven',
|
||||||
LEAVE: 'Verlaten',
|
LEAVE: 'Verlaten',
|
||||||
SCHEDULER: 'Scheduler',
|
SCHEDULER: 'Scheduler',
|
||||||
SCHEDULER_HELP_1: 'Automatiseer opdrachten door hieronder geplande gebeurtenissen toe te voegen. Stel een unieke naam in om activering via API/MQTT in/uit te schakelen',
|
SCHEDULER_HELP_1: 'Automatiseer opdrachten door hieronder geplande gebeurtenissen toe te voegen',
|
||||||
SCHEDULER_HELP_2: 'Gebruik 00:00 om eenmaal te activeren bij het opstarten',
|
SCHEDULER_HELP_2: 'bij het opstarten',
|
||||||
SCHEDULE: 'Schedule',
|
SCHEDULE: 'Schedule',
|
||||||
TIME: 'Tijd',
|
TIME: 'Tijd',
|
||||||
TIMER: 'Timer',
|
TIMER: 'Timer',
|
||||||
ONCHANGE: 'Op verandering',
|
ONCHANGE: 'Op verandering',
|
||||||
CONDITION: 'Voorwaarde',
|
CONDITION: 'Voorwaarde',
|
||||||
IMMEDIATE: 'Onmiddellijk',
|
|
||||||
SCHEDULE_UPDATED: 'Schema bijgewerkt',
|
SCHEDULE_UPDATED: 'Schema bijgewerkt',
|
||||||
SCHEDULE_TIMER_1: 'bij het opstarten',
|
SCHEDULE_TIMER_1: 'bij het opstarten',
|
||||||
SCHEDULE_TIMER_2: 'elke minuut',
|
SCHEDULE_TIMER_2: 'elke minuut',
|
||||||
@@ -365,7 +364,10 @@ const nl: Translation = {
|
|||||||
WARNING_SYSTEM_BACKUP: 'Dit zal een back-up van uw volledige systeemconfiguratie en instellingen maken. Alle wachtwoorden zijn leesbaar in het back-upbestand. Wees voorzichtig bij delen! Wilt u doorgaan?',
|
WARNING_SYSTEM_BACKUP: 'Dit zal een back-up van uw volledige systeemconfiguratie en instellingen maken. Alle wachtwoorden zijn leesbaar in het back-upbestand. Wees voorzichtig bij delen! Wilt u doorgaan?',
|
||||||
TEST_EMAIL_SUCCESSFUL: 'Test email verzonden succesvol',
|
TEST_EMAIL_SUCCESSFUL: 'Test email verzonden succesvol',
|
||||||
SYSTEM_NAME: 'Systeemnaam',
|
SYSTEM_NAME: 'Systeemnaam',
|
||||||
EXECUTE_SCHEDULE_SENT: 'Planlegger uitgevoerd succesvol'
|
COMMANDS: 'Commando\'s',
|
||||||
|
COMMANDS_UPDATED: 'Commando\'s bijgewerkt',
|
||||||
|
COMMANDS_HELP_1: 'Definieer hergebruikbare benoemde commando\'s hieronder. Deze kunnen worden uitgevoerd vanuit de console, API/MQTT, of worden aangeroepen door de Scheduler',
|
||||||
|
EXECUTE_COMMAND_SENT: 'Commando verzonden',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nl;
|
export default nl;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const no: Translation = {
|
|||||||
ID_OF: '{0}-ID',
|
ID_OF: '{0}-ID',
|
||||||
DEVICE: 'Enhets',
|
DEVICE: 'Enhets',
|
||||||
PRODUCT: 'Produkt',
|
PRODUCT: 'Produkt',
|
||||||
VERSION: 'Versjon',
|
|
||||||
BRAND: 'Fabrikat',
|
BRAND: 'Fabrikat',
|
||||||
|
VERSION: 'Versjon',
|
||||||
ENTITY_NAME: 'Objektsnavn',
|
ENTITY_NAME: 'Objektsnavn',
|
||||||
VALUE: '{{verdi|Verdi}}',
|
VALUE: '{{verdi|Verdi}}',
|
||||||
DEVICES: 'Enheter',
|
DEVICES: 'Enheter',
|
||||||
@@ -102,8 +102,8 @@ const no: Translation = {
|
|||||||
PHY_TYPE: 'Eth PHY Type',
|
PHY_TYPE: 'Eth PHY Type',
|
||||||
DISABLED: 'avslått',
|
DISABLED: 'avslått',
|
||||||
TX_MODE: 'EMS Tx Mode',
|
TX_MODE: 'EMS Tx Mode',
|
||||||
HARDWARE: 'Hardware',
|
|
||||||
EMS_BUS: '{{Bus|EMS Bus}}',
|
EMS_BUS: '{{Bus|EMS Bus}}',
|
||||||
|
HARDWARE: 'Hardware',
|
||||||
GENERAL_OPTIONS: 'Generelle Innstillinger',
|
GENERAL_OPTIONS: 'Generelle Innstillinger',
|
||||||
LANGUAGE_ENTITIES: 'Språk (for objekter)',
|
LANGUAGE_ENTITIES: 'Språk (for objekter)',
|
||||||
HIDE_LED: 'Skjul LED',
|
HIDE_LED: 'Skjul LED',
|
||||||
@@ -286,14 +286,13 @@ const no: Translation = {
|
|||||||
STAY: 'Bli her',
|
STAY: 'Bli her',
|
||||||
LEAVE: 'Forlat',
|
LEAVE: 'Forlat',
|
||||||
SCHEDULER: 'Planlegger',
|
SCHEDULER: 'Planlegger',
|
||||||
SCHEDULER_HELP_1: 'Automatiser kommandoer ved å legge til skedulerte hendelser nedenfor. Sett et unikt navn for å slå på/av aktivering via API/MQTT',
|
SCHEDULER_HELP_1: 'Automatiser kommandoer ved å legge til skedulerte hendelser nedenfor',
|
||||||
SCHEDULER_HELP_2: 'Bruk 00:00 for å kjøre en gang ved oppstart',
|
SCHEDULER_HELP_2: 'ved oppstart',
|
||||||
SCHEDULE: 'Planlegg',
|
SCHEDULE: 'Planlegg',
|
||||||
TIME: 'Tid',
|
TIME: 'Tid',
|
||||||
TIMER: 'Timer',
|
TIMER: 'Timer',
|
||||||
ONCHANGE: 'På endring',
|
ONCHANGE: 'På endring',
|
||||||
CONDITION: 'Betingelse',
|
CONDITION: 'Betingelse',
|
||||||
IMMEDIATE: 'Umiddelbar',
|
|
||||||
SCHEDULE_UPDATED: 'Planlegger er oppdatert',
|
SCHEDULE_UPDATED: 'Planlegger er oppdatert',
|
||||||
SCHEDULE_TIMER_1: 'ved oppstart',
|
SCHEDULE_TIMER_1: 'ved oppstart',
|
||||||
SCHEDULE_TIMER_2: 'hvert minutt',
|
SCHEDULE_TIMER_2: 'hvert minutt',
|
||||||
@@ -365,7 +364,10 @@ const no: Translation = {
|
|||||||
WARNING_SYSTEM_BACKUP: 'Dette vil lage en sikkerhetskopi av din fullstendige systemkonfigurasjon og innstillinger. Alle passord vil være lesbare i sikkerhetskopien. Vær forsiktig med deling! Vil du fortsette?',
|
WARNING_SYSTEM_BACKUP: 'Dette vil lage en sikkerhetskopi av din fullstendige systemkonfigurasjon og innstillinger. Alle passord vil være lesbare i sikkerhetskopien. Vær forsiktig med deling! Vil du fortsette?',
|
||||||
TEST_EMAIL_SUCCESSFUL: 'Test email sendt suksessfullt',
|
TEST_EMAIL_SUCCESSFUL: 'Test email sendt suksessfullt',
|
||||||
SYSTEM_NAME: 'Systemnavn',
|
SYSTEM_NAME: 'Systemnavn',
|
||||||
EXECUTE_SCHEDULE_SENT: 'Planlegger utført suksessfullt'
|
COMMANDS: 'Kommandoer',
|
||||||
|
COMMANDS_UPDATED: 'Kommandoer oppdatert',
|
||||||
|
COMMANDS_HELP_1: 'Definer egne kommandoer for EMS-Magistral',
|
||||||
|
EXECUTE_COMMAND_SENT: 'Kommando sendt',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default no;
|
export default no;
|
||||||
|
|||||||
@@ -286,14 +286,13 @@ const pl: BaseTranslation = {
|
|||||||
STAY: 'Pozostań',
|
STAY: 'Pozostań',
|
||||||
LEAVE: 'Opuść',
|
LEAVE: 'Opuść',
|
||||||
SCHEDULER: 'Harmonogram',
|
SCHEDULER: 'Harmonogram',
|
||||||
SCHEDULER_HELP_1: 'Zautomatyzuj wykonywanie komend, dodając poniżej harmonogram zdarzeń. Nadaj mu unikalną nazwę, aby móc go aktywować/dezaktywować przez API/MQTT',
|
SCHEDULER_HELP_1: 'Zautomatyzuj wykonywanie komend, dodając poniżej harmonogram zdarzeń',
|
||||||
SCHEDULER_HELP_2: 'Wpisz 00:00 aby wykonywać jednorazowo przy starcie.',
|
SCHEDULER_HELP_2: 'przy starcie',
|
||||||
SCHEDULE: '{{H|h|}}armonogram{{|u|}}',
|
SCHEDULE: '{{H|h|}}armonogram{{|u|}}',
|
||||||
TIME: '{{Czas|Godzina|}}',
|
TIME: '{{Czas|Godzina|}}',
|
||||||
TIMER: '{{m|M|}}inutnik',
|
TIMER: '{{m|M|}}inutnik',
|
||||||
ONCHANGE: 'O zmianie',
|
ONCHANGE: 'O zmianie',
|
||||||
CONDITION: 'Stan',
|
CONDITION: 'Stan',
|
||||||
IMMEDIATE: 'Natychmiastowy',
|
|
||||||
SCHEDULE_UPDATED: 'Harmonogram został uaktualniony.',
|
SCHEDULE_UPDATED: 'Harmonogram został uaktualniony.',
|
||||||
SCHEDULE_TIMER_1: 'przy starcie',
|
SCHEDULE_TIMER_1: 'przy starcie',
|
||||||
SCHEDULE_TIMER_2: 'co minutę',
|
SCHEDULE_TIMER_2: 'co minutę',
|
||||||
@@ -365,7 +364,10 @@ const pl: BaseTranslation = {
|
|||||||
WARNING_SYSTEM_BACKUP: 'To spowoduje utworzenie kopii zapasowej całej konfiguracji i ustawień systemu. Wszystkie hasła będą widoczne w pliku kopii zapasowej. Bądź ostrożny przy udostępnianiu! Chcesz kontynuować?',
|
WARNING_SYSTEM_BACKUP: 'To spowoduje utworzenie kopii zapasowej całej konfiguracji i ustawień systemu. Wszystkie hasła będą widoczne w pliku kopii zapasowej. Bądź ostrożny przy udostępnianiu! Chcesz kontynuować?',
|
||||||
TEST_EMAIL_SUCCESSFUL: 'Test email wysłany pomyślnie',
|
TEST_EMAIL_SUCCESSFUL: 'Test email wysłany pomyślnie',
|
||||||
SYSTEM_NAME: 'Nazwa systemu',
|
SYSTEM_NAME: 'Nazwa systemu',
|
||||||
EXECUTE_SCHEDULE_SENT: 'Harmonogram wykonany pomyślnie'
|
COMMANDS: 'Komendy',
|
||||||
|
COMMANDS_UPDATED: 'Komendy zostały zaktualizowane',
|
||||||
|
COMMANDS_HELP_1: 'Zdefiniuj niestandardowe komendy dla magistrali EMS',
|
||||||
|
EXECUTE_COMMAND_SENT: 'Komenda wysłana',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default pl;
|
export default pl;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const sk: Translation = {
|
|||||||
ID_OF: '{0} ID',
|
ID_OF: '{0} ID',
|
||||||
DEVICE: 'Zariadenie',
|
DEVICE: 'Zariadenie',
|
||||||
PRODUCT: 'Produkt',
|
PRODUCT: 'Produkt',
|
||||||
VERSION: 'Verzia',
|
|
||||||
BRAND: 'Značka',
|
BRAND: 'Značka',
|
||||||
|
VERSION: 'Verzia',
|
||||||
ENTITY_NAME: 'Názov entity',
|
ENTITY_NAME: 'Názov entity',
|
||||||
VALUE: '{{hodnota|Hodnota}}',
|
VALUE: '{{hodnota|Hodnota}}',
|
||||||
DEVICES: 'Zariadenia',
|
DEVICES: 'Zariadenia',
|
||||||
@@ -102,8 +102,8 @@ const sk: Translation = {
|
|||||||
PHY_TYPE: 'Eth PHY Typ',
|
PHY_TYPE: 'Eth PHY Typ',
|
||||||
DISABLED: 'zakázané',
|
DISABLED: 'zakázané',
|
||||||
TX_MODE: 'EMS Tx režim',
|
TX_MODE: 'EMS Tx režim',
|
||||||
HARDWARE: 'Hardware',
|
|
||||||
EMS_BUS: '{{Bus|EMS Bus}}',
|
EMS_BUS: '{{Bus|EMS Bus}}',
|
||||||
|
HARDWARE: 'Hardware',
|
||||||
GENERAL_OPTIONS: 'Všeobecné možnosti',
|
GENERAL_OPTIONS: 'Všeobecné možnosti',
|
||||||
LANGUAGE_ENTITIES: 'Jazyk (pre entity zariadenia)',
|
LANGUAGE_ENTITIES: 'Jazyk (pre entity zariadenia)',
|
||||||
HIDE_LED: 'Skryť LED',
|
HIDE_LED: 'Skryť LED',
|
||||||
@@ -286,14 +286,13 @@ const sk: Translation = {
|
|||||||
STAY: 'Zostať',
|
STAY: 'Zostať',
|
||||||
LEAVE: 'Opustiť',
|
LEAVE: 'Opustiť',
|
||||||
SCHEDULER: 'Plánovač',
|
SCHEDULER: 'Plánovač',
|
||||||
SCHEDULER_HELP_1: 'Automatizujte príkazy pridaním naplánovaných udalostí nižšie. Nastavte jedinečné meno na aktiváciu/deaktiváciu cez API/MQTT',
|
SCHEDULER_HELP_1: 'Automatizujte príkazy pridaním naplánovaných udalostí nižšie',
|
||||||
SCHEDULER_HELP_2: 'Použite 00:00 na jednorazové spustenie pri štarte',
|
SCHEDULER_HELP_2: 'pri štarte',
|
||||||
SCHEDULE: 'Plánovač',
|
SCHEDULE: 'Plánovač',
|
||||||
TIME: 'Čas',
|
TIME: 'Čas',
|
||||||
TIMER: 'Časovač',
|
TIMER: 'Časovač',
|
||||||
ONCHANGE: 'Pri zmene',
|
ONCHANGE: 'Pri zmene',
|
||||||
CONDITION: 'Podmienka',
|
CONDITION: 'Podmienka',
|
||||||
IMMEDIATE: 'Okamžite',
|
|
||||||
SCHEDULE_UPDATED: 'Plánovanie aktualizované',
|
SCHEDULE_UPDATED: 'Plánovanie aktualizované',
|
||||||
SCHEDULE_TIMER_1: 'pri spustení',
|
SCHEDULE_TIMER_1: 'pri spustení',
|
||||||
SCHEDULE_TIMER_2: 'každú minútu',
|
SCHEDULE_TIMER_2: 'každú minútu',
|
||||||
@@ -365,7 +364,10 @@ const sk: Translation = {
|
|||||||
WARNING_SYSTEM_BACKUP: 'Toto vytvorí zálohu všetkých vašich celých systémových konfigurácií a nastavení. Všetky hesla budú čitateľné v zálohovom súbore. Buďte opatrní pri zdieľaní! Chcete pokračovať?',
|
WARNING_SYSTEM_BACKUP: 'Toto vytvorí zálohu všetkých vašich celých systémových konfigurácií a nastavení. Všetky hesla budú čitateľné v zálohovom súbore. Buďte opatrní pri zdieľaní! Chcete pokračovať?',
|
||||||
TEST_EMAIL_SUCCESSFUL: 'Test email bol úspešne odoslaný',
|
TEST_EMAIL_SUCCESSFUL: 'Test email bol úspešne odoslaný',
|
||||||
SYSTEM_NAME: 'Názov systému',
|
SYSTEM_NAME: 'Názov systému',
|
||||||
EXECUTE_SCHEDULE_SENT: 'Plán bol úspešne vykonaný'
|
COMMANDS: 'Príkazy',
|
||||||
|
COMMANDS_UPDATED: 'Príkazy aktualizované',
|
||||||
|
COMMANDS_HELP_1: 'Definujte vlastné príkazy pre magistrali EMS',
|
||||||
|
EXECUTE_COMMAND_SENT: 'Príkaz odoslaný',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default sk;
|
export default sk;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const sv: Translation = {
|
|||||||
ID_OF: '{0}-ID',
|
ID_OF: '{0}-ID',
|
||||||
DEVICE: 'Enhet',
|
DEVICE: 'Enhet',
|
||||||
PRODUCT: 'Produkt',
|
PRODUCT: 'Produkt',
|
||||||
VERSION: 'Version',
|
|
||||||
BRAND: 'Fabrikat',
|
BRAND: 'Fabrikat',
|
||||||
|
VERSION: 'Version',
|
||||||
ENTITY_NAME: 'Entitetsnamn',
|
ENTITY_NAME: 'Entitetsnamn',
|
||||||
VALUE: '{{värde|Värde}}',
|
VALUE: '{{värde|Värde}}',
|
||||||
DEVICES: 'Enheter',
|
DEVICES: 'Enheter',
|
||||||
@@ -102,8 +102,8 @@ const sv: Translation = {
|
|||||||
PHY_TYPE: 'Eth PHY-typ',
|
PHY_TYPE: 'Eth PHY-typ',
|
||||||
DISABLED: 'inaktiverad',
|
DISABLED: 'inaktiverad',
|
||||||
TX_MODE: 'EMS Tx-läge',
|
TX_MODE: 'EMS Tx-läge',
|
||||||
HARDWARE: 'Hårdvara',
|
|
||||||
EMS_BUS: '{{Buss|EMS-Buss}}',
|
EMS_BUS: '{{Buss|EMS-Buss}}',
|
||||||
|
HARDWARE: 'Hårdvara',
|
||||||
GENERAL_OPTIONS: 'Allmänna inställningar',
|
GENERAL_OPTIONS: 'Allmänna inställningar',
|
||||||
LANGUAGE_ENTITIES: 'Språk (för entiteter)',
|
LANGUAGE_ENTITIES: 'Språk (för entiteter)',
|
||||||
HIDE_LED: 'Inaktivera LED',
|
HIDE_LED: 'Inaktivera LED',
|
||||||
@@ -286,14 +286,13 @@ const sv: Translation = {
|
|||||||
STAY: 'Stanna',
|
STAY: 'Stanna',
|
||||||
LEAVE: 'Lämna',
|
LEAVE: 'Lämna',
|
||||||
SCHEDULER: 'Schemaläggning',
|
SCHEDULER: 'Schemaläggning',
|
||||||
SCHEDULER_HELP_1: 'Automatisera kommandon genom att lägga till schemahändelser nedan. Ange ett unikt namn för att aktivera/avaktivera aktivering via API/MQTT',
|
SCHEDULER_HELP_1: 'Automatisera kommandon genom att lägga till schemahändelser nedan',
|
||||||
SCHEDULER_HELP_2: 'Använd 00:00 för att trigga en gång vid uppstart',
|
SCHEDULER_HELP_2: 'vid uppstart',
|
||||||
SCHEDULE: 'schema',
|
SCHEDULE: 'schema',
|
||||||
TIME: 'Tid',
|
TIME: 'Tid',
|
||||||
TIMER: 'Timer',
|
TIMER: 'Timer',
|
||||||
ONCHANGE: 'Vid förändring',
|
ONCHANGE: 'Vid förändring',
|
||||||
CONDITION: 'Villkor',
|
CONDITION: 'Villkor',
|
||||||
IMMEDIATE: 'Omedelbar',
|
|
||||||
SCHEDULE_UPDATED: 'Schema uppdaterat',
|
SCHEDULE_UPDATED: 'Schema uppdaterat',
|
||||||
SCHEDULE_TIMER_1: 'vid uppstart',
|
SCHEDULE_TIMER_1: 'vid uppstart',
|
||||||
SCHEDULE_TIMER_2: 'varje minut',
|
SCHEDULE_TIMER_2: 'varje minut',
|
||||||
@@ -365,7 +364,10 @@ const sv: Translation = {
|
|||||||
WARNING_SYSTEM_BACKUP: 'Detta kommer att skapa en säkerhetskopia av din fullständiga systemkonfiguration och inställningar. Alla lösenord kommer att vara läsbara i säkerhetskopien. Var försiktig med att dela! Vill du fortsätta?',
|
WARNING_SYSTEM_BACKUP: 'Detta kommer att skapa en säkerhetskopia av din fullständiga systemkonfiguration och inställningar. Alla lösenord kommer att vara läsbara i säkerhetskopien. Var försiktig med att dela! Vill du fortsätta?',
|
||||||
TEST_EMAIL_SUCCESSFUL: 'Test email skickad lyckades',
|
TEST_EMAIL_SUCCESSFUL: 'Test email skickad lyckades',
|
||||||
SYSTEM_NAME: 'Systemnamn',
|
SYSTEM_NAME: 'Systemnamn',
|
||||||
EXECUTE_SCHEDULE_SENT: 'Schema utfört'
|
COMMANDS: 'Kommandon',
|
||||||
|
COMMANDS_UPDATED: 'Kommandon uppdaterade',
|
||||||
|
COMMANDS_HELP_1: 'Definiera egna kommandon för EMS-Magistral',
|
||||||
|
EXECUTE_COMMAND_SENT: 'Kommando skickat',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default sv;
|
export default sv;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const tr: Translation = {
|
|||||||
ID_OF: 'Kimlik {0}',
|
ID_OF: 'Kimlik {0}',
|
||||||
DEVICE: 'Cihaz',
|
DEVICE: 'Cihaz',
|
||||||
PRODUCT: 'Ürün',
|
PRODUCT: 'Ürün',
|
||||||
VERSION: 'Sürüm',
|
|
||||||
BRAND: 'Marka',
|
BRAND: 'Marka',
|
||||||
|
VERSION: 'Sürüm',
|
||||||
ENTITY_NAME: 'Valık Adı',
|
ENTITY_NAME: 'Valık Adı',
|
||||||
VALUE: '{{değer|Değer}}',
|
VALUE: '{{değer|Değer}}',
|
||||||
DEVICES: 'Cihazlar',
|
DEVICES: 'Cihazlar',
|
||||||
@@ -102,8 +102,8 @@ const tr: Translation = {
|
|||||||
PHY_TYPE: 'Eth PHY Tipi',
|
PHY_TYPE: 'Eth PHY Tipi',
|
||||||
DISABLED: 'devre dışı',
|
DISABLED: 'devre dışı',
|
||||||
TX_MODE: 'EMS Tx Modu',
|
TX_MODE: 'EMS Tx Modu',
|
||||||
HARDWARE: 'Donanım',
|
|
||||||
EMS_BUS: '{{Hat|EMS Hatti}}',
|
EMS_BUS: '{{Hat|EMS Hatti}}',
|
||||||
|
HARDWARE: 'Donanım',
|
||||||
GENERAL_OPTIONS: 'Genel Seçenekler',
|
GENERAL_OPTIONS: 'Genel Seçenekler',
|
||||||
LANGUAGE_ENTITIES: 'Dil (cihaz varlıkları için)',
|
LANGUAGE_ENTITIES: 'Dil (cihaz varlıkları için)',
|
||||||
HIDE_LED: 'LEDi kapa',
|
HIDE_LED: 'LEDi kapa',
|
||||||
@@ -286,14 +286,13 @@ const tr: Translation = {
|
|||||||
STAY: 'Kal',
|
STAY: 'Kal',
|
||||||
LEAVE: 'Çık',
|
LEAVE: 'Çık',
|
||||||
SCHEDULER: 'Zamanlayıcı',
|
SCHEDULER: 'Zamanlayıcı',
|
||||||
SCHEDULER_HELP_1: 'Komutları zamanlayarak otomatikleştirin. Benzersiz bir ad belirtin API/MQTT aracılığıyla etkinleştirmek/devre dışı bırakma',
|
SCHEDULER_HELP_1: 'Komutları zamanlayarak otomatikleştirin',
|
||||||
SCHEDULER_HELP_2: 'Başlangıçta bir kere tetiklemek için 00:00 kullanın',
|
SCHEDULER_HELP_2: 'başlangıçta',
|
||||||
SCHEDULE: 'Zamanlama',
|
SCHEDULE: 'Zamanlama',
|
||||||
TIME: 'Zaman',
|
TIME: 'Zaman',
|
||||||
TIMER: 'Zamanlayıcı',
|
TIMER: 'Zamanlayıcı',
|
||||||
ONCHANGE: 'Değişimde',
|
ONCHANGE: 'Değişimde',
|
||||||
CONDITION: 'Durum',
|
CONDITION: 'Durum',
|
||||||
IMMEDIATE: 'hemen',
|
|
||||||
SCHEDULE_UPDATED: 'Zamanlama güncellendi',
|
SCHEDULE_UPDATED: 'Zamanlama güncellendi',
|
||||||
SCHEDULE_TIMER_1: 'Başlangıçta',
|
SCHEDULE_TIMER_1: 'Başlangıçta',
|
||||||
SCHEDULE_TIMER_2: 'her dakikada',
|
SCHEDULE_TIMER_2: 'her dakikada',
|
||||||
@@ -365,7 +364,10 @@ const tr: Translation = {
|
|||||||
WARNING_SYSTEM_BACKUP: 'Bu, sistem yapılandırmanızı ve ayarlarınızın bir yedeklemesi oluşturacaktır. Tüm şifreler yedekleme dosyasında okunabilir olacaktır. Paylaşırken dikkatli olun! Devam etmek istediğinize emin misiniz?',
|
WARNING_SYSTEM_BACKUP: 'Bu, sistem yapılandırmanızı ve ayarlarınızın bir yedeklemesi oluşturacaktır. Tüm şifreler yedekleme dosyasında okunabilir olacaktır. Paylaşırken dikkatli olun! Devam etmek istediğinize emin misiniz?',
|
||||||
TEST_EMAIL_SUCCESSFUL: 'Test email başarıyla gönderildi',
|
TEST_EMAIL_SUCCESSFUL: 'Test email başarıyla gönderildi',
|
||||||
SYSTEM_NAME: 'Sistem Adı',
|
SYSTEM_NAME: 'Sistem Adı',
|
||||||
EXECUTE_SCHEDULE_SENT: 'Zamanlama başarıyla uygulandı'
|
COMMANDS: 'Komutlar',
|
||||||
|
COMMANDS_UPDATED: 'Komutlar güncellendi',
|
||||||
|
COMMANDS_HELP_1: 'Özel komutları EMS hattına tanımlayın',
|
||||||
|
EXECUTE_COMMAND_SENT: 'Komut gönderildi',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default tr;
|
export default tr;
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
"@msgpack/msgpack": "^3.1.3",
|
"@msgpack/msgpack": "^3.1.3",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||||
"formidable": "^3.5.4",
|
"formidable": "^3.5.4",
|
||||||
"itty-router": "^5.0.23",
|
"itty-router": "^5.0.24",
|
||||||
"prettier": "^3.8.3"
|
"prettier": "^3.8.4"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.5.2+sha512.71c631e382066efc25625d5cf029075de07b61b37f6e27350fbd84b1bda5864c8c1967adc280776b45c30a715c0359a3be08fef42d5bb09e2b99029979692916"
|
"packageManager": "pnpm@11.6.0+sha512.9a36518224080c6fe5165afdcfe79bfa118c29be703f3f462b1e32efe1e98e47e8750b148e08286250aad4113cc7993ca413c4e2cd447752708c2ee5751bc95f"
|
||||||
}
|
}
|
||||||
|
|||||||
26
mock-api/pnpm-lock.yaml
generated
26
mock-api/pnpm-lock.yaml
generated
@@ -13,16 +13,16 @@ importers:
|
|||||||
version: 3.1.3
|
version: 3.1.3
|
||||||
'@trivago/prettier-plugin-sort-imports':
|
'@trivago/prettier-plugin-sort-imports':
|
||||||
specifier: ^6.0.2
|
specifier: ^6.0.2
|
||||||
version: 6.0.2(prettier@3.8.3)
|
version: 6.0.2(prettier@3.8.4)
|
||||||
formidable:
|
formidable:
|
||||||
specifier: ^3.5.4
|
specifier: ^3.5.4
|
||||||
version: 3.5.4
|
version: 3.5.4
|
||||||
itty-router:
|
itty-router:
|
||||||
specifier: ^5.0.23
|
specifier: ^5.0.24
|
||||||
version: 5.0.23
|
version: 5.0.24
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.8.3
|
specifier: ^3.8.4
|
||||||
version: 3.8.3
|
version: 3.8.4
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -131,8 +131,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==}
|
resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
itty-router@5.0.23:
|
itty-router@5.0.24:
|
||||||
resolution: {integrity: sha512-i49WU+SNPrwOZA4Z61En1RYd5h2Lcqa+5IvCpMrNi4dxymzJK15ozUUnRrWIUAv95Zamd4eJPAot2UvHRrQg7w==}
|
resolution: {integrity: sha512-PXij1qGKtE6jg3dLgrTRdh/Frovl5Wc4Av67sD2xfhDPrgNt4u79++/il8/c/OfZGkhD/8uYJub8tjo+H165rA==}
|
||||||
|
|
||||||
javascript-natural-sort@0.7.1:
|
javascript-natural-sort@0.7.1:
|
||||||
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
|
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
|
||||||
@@ -167,8 +167,8 @@ packages:
|
|||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
prettier@3.8.3:
|
prettier@3.8.4:
|
||||||
resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==}
|
resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@noble/hashes': 1.8.0
|
'@noble/hashes': 1.8.0
|
||||||
|
|
||||||
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.8.3)':
|
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.8.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/generator': 7.29.7
|
'@babel/generator': 7.29.7
|
||||||
'@babel/parser': 7.29.7
|
'@babel/parser': 7.29.7
|
||||||
@@ -256,7 +256,7 @@ snapshots:
|
|||||||
lodash-es: 4.18.1
|
lodash-es: 4.18.1
|
||||||
minimatch: 9.0.9
|
minimatch: 9.0.9
|
||||||
parse-imports-exports: 0.2.4
|
parse-imports-exports: 0.2.4
|
||||||
prettier: 3.8.3
|
prettier: 3.8.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -283,7 +283,7 @@ snapshots:
|
|||||||
dezalgo: 1.0.4
|
dezalgo: 1.0.4
|
||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
|
|
||||||
itty-router@5.0.23: {}
|
itty-router@5.0.24: {}
|
||||||
|
|
||||||
javascript-natural-sort@0.7.1: {}
|
javascript-natural-sort@0.7.1: {}
|
||||||
|
|
||||||
@@ -311,6 +311,6 @@ snapshots:
|
|||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
prettier@3.8.3: {}
|
prettier@3.8.4: {}
|
||||||
|
|
||||||
wrappy@1.0.2: {}
|
wrappy@1.0.2: {}
|
||||||
|
|||||||
@@ -241,8 +241,7 @@ const enum ScheduleFlag {
|
|||||||
SCHEDULE_DAY = 0,
|
SCHEDULE_DAY = 0,
|
||||||
SCHEDULE_TIMER = 128,
|
SCHEDULE_TIMER = 128,
|
||||||
SCHEDULE_ONCHANGE = 129,
|
SCHEDULE_ONCHANGE = 129,
|
||||||
SCHEDULE_CONDITION = 130,
|
SCHEDULE_CONDITION = 130
|
||||||
SCHEDULE_IMMEDIATE = 132
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const enum DeviceType {
|
const enum DeviceType {
|
||||||
@@ -251,6 +250,7 @@ const enum DeviceType {
|
|||||||
ANALOGSENSOR,
|
ANALOGSENSOR,
|
||||||
SCHEDULER,
|
SCHEDULER,
|
||||||
CUSTOM,
|
CUSTOM,
|
||||||
|
COMMAND,
|
||||||
BOILER,
|
BOILER,
|
||||||
THERMOSTAT,
|
THERMOSTAT,
|
||||||
MIXER,
|
MIXER,
|
||||||
@@ -271,6 +271,7 @@ const enum DeviceType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const enum DeviceTypeUniqueID {
|
const enum DeviceTypeUniqueID {
|
||||||
|
COMMAND_UID = 95,
|
||||||
SCHEDULER_UID = 96,
|
SCHEDULER_UID = 96,
|
||||||
ANALOGSENSOR_UID = 97,
|
ANALOGSENSOR_UID = 97,
|
||||||
TEMPERATURESENSOR_UID = 98,
|
TEMPERATURESENSOR_UID = 98,
|
||||||
@@ -374,6 +375,8 @@ function export_data(type: string) {
|
|||||||
return emsesp_customentities;
|
return emsesp_customentities;
|
||||||
case 'schedule':
|
case 'schedule':
|
||||||
return emsesp_schedule;
|
return emsesp_schedule;
|
||||||
|
case 'commands':
|
||||||
|
return emsesp_commands;
|
||||||
case 'modules':
|
case 'modules':
|
||||||
return emsesp_modules;
|
return emsesp_modules;
|
||||||
case 'allvalues':
|
case 'allvalues':
|
||||||
@@ -409,9 +412,9 @@ function custom_support() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// run a schedule
|
// run a command
|
||||||
function executeSchedule(name: string) {
|
function executeCommand(name: string) {
|
||||||
console.log('executing schedule', name);
|
console.log('executing command', name);
|
||||||
return status(200);
|
return status(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,6 +754,7 @@ const EMSESP_RESET_CUSTOMIZATIONS_ENDPOINT =
|
|||||||
REST_ENDPOINT_ROOT + 'resetCustomizations';
|
REST_ENDPOINT_ROOT + 'resetCustomizations';
|
||||||
|
|
||||||
const EMSESP_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'schedule';
|
const EMSESP_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'schedule';
|
||||||
|
const EMSESP_COMMANDS_ENDPOINT = REST_ENDPOINT_ROOT + 'commands';
|
||||||
const EMSESP_CUSTOMENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'customEntities';
|
const EMSESP_CUSTOMENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'customEntities';
|
||||||
const EMSESP_MODULES_ENDPOINT = REST_ENDPOINT_ROOT + 'modules';
|
const EMSESP_MODULES_ENDPOINT = REST_ENDPOINT_ROOT + 'modules';
|
||||||
const EMSESP_ACTION_ENDPOINT = REST_ENDPOINT_ROOT + 'action';
|
const EMSESP_ACTION_ENDPOINT = REST_ENDPOINT_ROOT + 'action';
|
||||||
@@ -4147,6 +4151,48 @@ let emsesp_customentities = {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// COMMANDS
|
||||||
|
let emsesp_commands = {
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
cmd: 'hc1/mode',
|
||||||
|
value: 'day',
|
||||||
|
name: 'set_day_mode'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
cmd: 'hc1/mode',
|
||||||
|
value: 'night',
|
||||||
|
name: 'set_night_mode'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
cmd: 'thermostat/hc2/seltemp',
|
||||||
|
value: '20',
|
||||||
|
name: 'set_temp_20'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
cmd: 'system/restart',
|
||||||
|
value: '',
|
||||||
|
name: 'restart_system'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
cmd: 'boiler/selflowtemp',
|
||||||
|
value: '(custom/setpoint - boiler/outdoortemp) * 2.8 + 3',
|
||||||
|
name: 'heatingcurve'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
cmd: 'system/message',
|
||||||
|
value: '"hello world"',
|
||||||
|
name: 'send_message'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
// SCHEDULE
|
// SCHEDULE
|
||||||
let emsesp_schedule = {
|
let emsesp_schedule = {
|
||||||
schedule: [
|
schedule: [
|
||||||
@@ -4155,8 +4201,7 @@ let emsesp_schedule = {
|
|||||||
active: true,
|
active: true,
|
||||||
flags: 6,
|
flags: 6,
|
||||||
time: '07:30',
|
time: '07:30',
|
||||||
cmd: 'hc1/mode',
|
cmd_name: 'set_day_mode',
|
||||||
value: 'day',
|
|
||||||
name: 'day_mode'
|
name: 'day_mode'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4164,8 +4209,7 @@ let emsesp_schedule = {
|
|||||||
active: true,
|
active: true,
|
||||||
flags: 31,
|
flags: 31,
|
||||||
time: '23:00',
|
time: '23:00',
|
||||||
cmd: 'hc1/mode',
|
cmd_name: 'set_night_mode',
|
||||||
value: 'night',
|
|
||||||
name: 'night_mode'
|
name: 'night_mode'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4173,8 +4217,7 @@ let emsesp_schedule = {
|
|||||||
active: true,
|
active: true,
|
||||||
flags: 10,
|
flags: 10,
|
||||||
time: '00:00',
|
time: '00:00',
|
||||||
cmd: 'thermostat/hc2/seltemp',
|
cmd_name: 'set_temp_20',
|
||||||
value: '20',
|
|
||||||
name: 'temp_20'
|
name: 'temp_20'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4182,8 +4225,7 @@ let emsesp_schedule = {
|
|||||||
active: false,
|
active: false,
|
||||||
flags: ScheduleFlag.SCHEDULE_TIMER,
|
flags: ScheduleFlag.SCHEDULE_TIMER,
|
||||||
time: '04:00',
|
time: '04:00',
|
||||||
cmd: 'system/restart',
|
cmd_name: 'restart_system',
|
||||||
value: '',
|
|
||||||
name: 'auto_restart'
|
name: 'auto_restart'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4191,8 +4233,7 @@ let emsesp_schedule = {
|
|||||||
active: false,
|
active: false,
|
||||||
flags: ScheduleFlag.SCHEDULE_CONDITION,
|
flags: ScheduleFlag.SCHEDULE_CONDITION,
|
||||||
time: 'system/network info/rssi < -70',
|
time: 'system/network info/rssi < -70',
|
||||||
cmd: 'system/restart',
|
cmd_name: 'restart_system',
|
||||||
value: '',
|
|
||||||
name: 'bad_wifi'
|
name: 'bad_wifi'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4200,18 +4241,16 @@ let emsesp_schedule = {
|
|||||||
active: false,
|
active: false,
|
||||||
flags: ScheduleFlag.SCHEDULE_ONCHANGE,
|
flags: ScheduleFlag.SCHEDULE_ONCHANGE,
|
||||||
time: 'boiler/outdoortemp',
|
time: 'boiler/outdoortemp',
|
||||||
cmd: 'boiler/selflowtemp',
|
cmd_name: 'heatingcurve',
|
||||||
value: '(custom/setpoint - boiler/outdoortemp) * 2.8 + 3',
|
|
||||||
name: 'heatingcurve'
|
name: 'heatingcurve'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 7,
|
id: 7,
|
||||||
active: false,
|
active: true,
|
||||||
flags: ScheduleFlag.SCHEDULE_IMMEDIATE,
|
flags: ScheduleFlag.SCHEDULE_TIMER,
|
||||||
time: '',
|
time: '',
|
||||||
cmd: 'system/message',
|
cmd_name: 'send_message',
|
||||||
value: '"hello world"',
|
name: 'on_boot'
|
||||||
name: 'send_message'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -4765,20 +4804,17 @@ router
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add the scheduler data
|
// add the scheduler data
|
||||||
// filter emsesp_schedule with only if it has a name and create data in one pass
|
const namedSchedules = emsesp_schedule.schedule.filter(
|
||||||
const namedSchedules = emsesp_schedule.schedule.filter((item) => item.name);
|
(item: any) => item.name
|
||||||
|
);
|
||||||
if (namedSchedules.length > 0) {
|
if (namedSchedules.length > 0) {
|
||||||
const scheduler_data = namedSchedules.map((item, index) => ({
|
const scheduler_data = namedSchedules.map((item: any, index: number) => ({
|
||||||
id: DeviceTypeUniqueID.SCHEDULER_UID * 100 + index,
|
id: DeviceTypeUniqueID.SCHEDULER_UID * 100 + index,
|
||||||
dv: {
|
dv: {
|
||||||
id: '00' + item.name,
|
id: '00' + item.name,
|
||||||
c: item.name,
|
c: item.name,
|
||||||
...(item.flags === ScheduleFlag.SCHEDULE_IMMEDIATE
|
v: item.active ? 'on' : 'off',
|
||||||
? {}
|
l: ['off', 'on']
|
||||||
: {
|
|
||||||
v: item.active ? 'on' : 'off',
|
|
||||||
l: ['off', 'on']
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
dashboard_object = {
|
dashboard_object = {
|
||||||
@@ -4788,6 +4824,26 @@ router
|
|||||||
};
|
};
|
||||||
dashboard_nodes.push(dashboard_object);
|
dashboard_nodes.push(dashboard_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add the command items (executable from dashboard)
|
||||||
|
const namedCommands = emsesp_commands.commands.filter(
|
||||||
|
(item: any) => item.name
|
||||||
|
);
|
||||||
|
if (namedCommands.length > 0) {
|
||||||
|
const command_data = namedCommands.map((item: any, index: number) => ({
|
||||||
|
id: DeviceTypeUniqueID.COMMAND_UID * 100 + index,
|
||||||
|
dv: {
|
||||||
|
id: '00' + item.name,
|
||||||
|
c: item.name
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
dashboard_object = {
|
||||||
|
id: DeviceTypeUniqueID.COMMAND_UID,
|
||||||
|
t: DeviceType.COMMAND,
|
||||||
|
nodes: command_data
|
||||||
|
};
|
||||||
|
dashboard_nodes.push(dashboard_object);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// for testing only
|
// for testing only
|
||||||
// add the custom entity data
|
// add the custom entity data
|
||||||
@@ -4877,6 +4933,15 @@ router
|
|||||||
return status(200);
|
return status(200);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
.post(EMSESP_COMMANDS_ENDPOINT, async (request: any) => {
|
||||||
|
const content = await request.json();
|
||||||
|
emsesp_commands = content;
|
||||||
|
console.log('commands saved', emsesp_commands);
|
||||||
|
return status(200);
|
||||||
|
})
|
||||||
|
.get(EMSESP_COMMANDS_ENDPOINT, () => emsesp_commands)
|
||||||
|
|
||||||
// Scheduler
|
// Scheduler
|
||||||
.post(EMSESP_SCHEDULE_ENDPOINT, async (request: any) => {
|
.post(EMSESP_SCHEDULE_ENDPOINT, async (request: any) => {
|
||||||
const content = await request.json();
|
const content = await request.json();
|
||||||
@@ -4977,15 +5042,17 @@ router
|
|||||||
}
|
}
|
||||||
if (id === DeviceTypeUniqueID.SCHEDULER_UID) {
|
if (id === DeviceTypeUniqueID.SCHEDULER_UID) {
|
||||||
// toggle scheduler
|
// toggle scheduler
|
||||||
// find the schedule in emsesp_schedule via the name and toggle the active
|
|
||||||
const objIndex = emsesp_schedule.schedule.findIndex(
|
const objIndex = emsesp_schedule.schedule.findIndex(
|
||||||
(obj) => obj.name === command
|
(obj: any) => obj.name === command
|
||||||
);
|
);
|
||||||
if (objIndex !== -1) {
|
if (objIndex !== -1) {
|
||||||
emsesp_schedule.schedule[objIndex].active = value;
|
emsesp_schedule.schedule[objIndex].active = value;
|
||||||
console.log("Toggle schedule '" + command + "' to " + value);
|
console.log("Toggle schedule '" + command + "' to " + value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (id === DeviceTypeUniqueID.COMMAND_UID) {
|
||||||
|
console.log("Execute command '" + command + "'");
|
||||||
|
}
|
||||||
|
|
||||||
// await delay(1000); // wait to show spinner
|
// await delay(1000); // wait to show spinner
|
||||||
// console.log(
|
// console.log(
|
||||||
@@ -5246,9 +5313,9 @@ router
|
|||||||
} else if (action === 'upgradeImportantMessages') {
|
} else if (action === 'upgradeImportantMessages') {
|
||||||
// check upgrade important messages
|
// check upgrade important messages
|
||||||
return upgradeImportantMessages(content.param);
|
return upgradeImportantMessages(content.param);
|
||||||
} else if (action === 'executeSchedule') {
|
} else if (action === 'executeCommand') {
|
||||||
// execute schedule
|
// execute command
|
||||||
return executeSchedule(content.param);
|
return executeCommand(content.param);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return status(404); // cmd not found
|
return status(404); // cmd not found
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ build_flags =
|
|||||||
; ESPAsyncWebServer
|
; ESPAsyncWebServer
|
||||||
; -D WS_MAX_QUEUED_MESSAGES=0 ; not used, default 8
|
; -D WS_MAX_QUEUED_MESSAGES=0 ; not used, default 8
|
||||||
; -D SSE_MAX_QUEUED_MESSAGES=1 ; for log messages, default 32
|
; -D SSE_MAX_QUEUED_MESSAGES=1 ; for log messages, default 32
|
||||||
-D EMSESP_SCHEDULER_RUNNING_CORE=1
|
|
||||||
-D EMSESP_SCHEDULER_STACKSIZE=8192 ; 8KB
|
|
||||||
-D EMSESP_MQTT_RUNNING_CORE=1 ; default 1
|
-D EMSESP_MQTT_RUNNING_CORE=1 ; default 1
|
||||||
; -D EMSESP_MQTT_STACKSIZE=5120 ; default
|
; -D EMSESP_MQTT_STACKSIZE=5120 ; default
|
||||||
-D EMSESP_UART_RUNNING_CORE=1 ; default any core
|
-D EMSESP_UART_RUNNING_CORE=1 ; default any core
|
||||||
@@ -92,7 +90,7 @@ board_build.littlefs_version = 2.0
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
bblanchon/ArduinoJson @ 7.4.3
|
bblanchon/ArduinoJson @ 7.4.3
|
||||||
ESP32Async/AsyncTCP @ 3.4.10
|
ESP32Async/AsyncTCP @ 3.4.10
|
||||||
ESP32Async/ESPAsyncWebServer @ 3.11.0
|
ESP32Async/ESPAsyncWebServer @ 3.11.1
|
||||||
https://github.com/mobizt/ReadyMail.git @ 0.4.2
|
https://github.com/mobizt/ReadyMail.git @ 0.4.2
|
||||||
https://github.com/mobizt/ESP_SSLClient.git @ 3.1.3
|
https://github.com/mobizt/ESP_SSLClient.git @ 3.1.3
|
||||||
; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
|
; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
|
||||||
|
|||||||
@@ -1341,4 +1341,5 @@ serialises
|
|||||||
SPIRAM
|
SPIRAM
|
||||||
optimisations
|
optimisations
|
||||||
IILE
|
IILE
|
||||||
Sumr
|
Sumr
|
||||||
|
eraseap
|
||||||
@@ -6,6 +6,8 @@ Workflow:
|
|||||||
2. Extract everything between the START/END "CUT HERE" markers.
|
2. Extract everything between the START/END "CUT HERE" markers.
|
||||||
3. Write that block to test/test_api/test_api.h.
|
3. Write that block to test/test_api/test_api.h.
|
||||||
4. Run `pio run -e native-test -t exec`.
|
4. Run `pio run -e native-test -t exec`.
|
||||||
|
|
||||||
|
run with `python3 scripts/generate_test_api.py`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ uuid::log::Logger AnalogSensor::logger_{F_(analogsensor), uuid::log::Facility
|
|||||||
std::vector<uint8_t> AnalogSensor::exclude_types_;
|
std::vector<uint8_t> AnalogSensor::exclude_types_;
|
||||||
|
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
|
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
volatile unsigned long AnalogSensor::edge[] = {0, 0, 0};
|
volatile unsigned long AnalogSensor::edge[] = {0, 0, 0};
|
||||||
volatile unsigned long AnalogSensor::edgecnt[] = {0, 0, 0};
|
volatile unsigned long AnalogSensor::edgecnt[] = {0, 0, 0};
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ void AnalogSensor::start(const bool factory_settings) {
|
|||||||
Command::add(
|
Command::add(
|
||||||
EMSdevice::DeviceType::ANALOGSENSOR,
|
EMSdevice::DeviceType::ANALOGSENSOR,
|
||||||
F_(setvalue),
|
F_(setvalue),
|
||||||
[&](const char * value, const int8_t id) { return command_setvalue(value, id); },
|
[&](const char * value, const int8_t id, JsonObject output) { return command_setvalue(value, id); },
|
||||||
FL_(setiovalue_cmd),
|
FL_(setiovalue_cmd),
|
||||||
CommandFlag::ADMIN_ONLY);
|
CommandFlag::ADMIN_ONLY);
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ void AnalogSensor::reload(bool get_nvs) {
|
|||||||
Command::add(
|
Command::add(
|
||||||
EMSdevice::DeviceType::ANALOGSENSOR,
|
EMSdevice::DeviceType::ANALOGSENSOR,
|
||||||
sensor.name,
|
sensor.name,
|
||||||
[&](const char * value, const int8_t id) { return command_setvalue(value, sensor.gpio); },
|
[&](const char * value, const int8_t id, JsonObject output) { return command_setvalue(value, sensor.gpio); },
|
||||||
sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2) ? FL_(counter)
|
sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2) ? FL_(counter)
|
||||||
: sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out)
|
: sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out)
|
||||||
: sensor.type == AnalogType::RGB ? FL_(RGB)
|
: sensor.type == AnalogType::RGB ? FL_(RGB)
|
||||||
@@ -671,7 +671,7 @@ void AnalogSensor::publish_values(const bool force) {
|
|||||||
publish_sensor(sensor);
|
publish_sensor(sensor);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (!EMSESP::mqtt_.get_publish_onchange(0)) {
|
} else if (!EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
|
||||||
return; // wait for first time period
|
return; // wait for first time period
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -378,7 +378,12 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
|
|||||||
if (!strcmp(cmd, F_(commands))) {
|
if (!strcmp(cmd, F_(commands))) {
|
||||||
return Command::list(device_type, output);
|
return Command::list(device_type, output);
|
||||||
}
|
}
|
||||||
if (EMSESP::get_device_value_info(output, cmd, id, device_type)) { // entity = cmd
|
// for the Commands device, calling a named command executes it (using its stored value)
|
||||||
|
// rather than returning its definition, so skip the value-info lookup and fall through
|
||||||
|
// to the registered command function. The list keywords above are still handled.
|
||||||
|
bool is_named_command = (device_type == EMSdevice::DeviceType::COMMAND) && strcmp(cmd, F_(info)) && strcmp(cmd, F_(values))
|
||||||
|
&& strcmp(cmd, F_(entities)) && strcmp(cmd, F_(metrics));
|
||||||
|
if (!is_named_command && EMSESP::get_device_value_info(output, cmd, id, device_type)) { // entity = cmd
|
||||||
LOG_DEBUG("Fetched device entity/attributes for %s/%s (id=%d)", dname, cmd, id);
|
LOG_DEBUG("Fetched device entity/attributes for %s/%s (id=%d)", dname, cmd, id);
|
||||||
return CommandRet::OK;
|
return CommandRet::OK;
|
||||||
}
|
}
|
||||||
@@ -438,16 +443,13 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
|
|||||||
// call the function based on command function type
|
// call the function based on command function type
|
||||||
// commands return true or false only (bool)
|
// commands return true or false only (bool)
|
||||||
uint8_t return_code = CommandRet::OK;
|
uint8_t return_code = CommandRet::OK;
|
||||||
if (cf->cmdfunction_json_) {
|
if (cf->cmdfunction_) {
|
||||||
// handle commands that report back a JSON body
|
// JSON-output commands bypass the readonly check; for the rest, reject a write to a read-only entity
|
||||||
return_code = ((cf->cmdfunction_json_)(value, id, output)) ? CommandRet::OK : CommandRet::ERROR;
|
if (!cf->has_json_output_ && !single_command && EMSESP::cmd_is_readonly(device_type, device_id, cmd, id)) {
|
||||||
} else if (cf->cmdfunction_) {
|
|
||||||
// if it's a read only command and we're trying to set a value, return an error
|
|
||||||
if (!single_command && EMSESP::cmd_is_readonly(device_type, device_id, cmd, id)) {
|
|
||||||
return_code = CommandRet::INVALID; // error on readonly or invalid hc
|
return_code = CommandRet::INVALID; // error on readonly or invalid hc
|
||||||
} else {
|
} else {
|
||||||
// call the command...
|
// call the command (the output object is ignored by non-JSON commands)
|
||||||
return_code = ((cf->cmdfunction_)(value, id)) ? CommandRet::OK : CommandRet::ERROR;
|
return_code = ((cf->cmdfunction_)(value, id, output)) ? CommandRet::OK : CommandRet::ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,7 +503,7 @@ void Command::add(const uint8_t device_type, const uint8_t device_id, const char
|
|||||||
flags |= CommandFlag::HIDDEN;
|
flags |= CommandFlag::HIDDEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdfunctions_.emplace_back(device_type, device_id, flags, cmd, cb, nullptr, description); // callback for json is nullptr
|
cmdfunctions_.emplace_back(device_type, device_id, flags, false, cmd, cb, description); // not a json-output command
|
||||||
}
|
}
|
||||||
|
|
||||||
// add a command with no json output
|
// add a command with no json output
|
||||||
@@ -511,13 +513,14 @@ void Command::add(const uint8_t device_type, const char * cmd, const cmd_functio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add a command to the list, which does return a json object as output
|
// add a command to the list, which does return a json object as output
|
||||||
void Command::add(const uint8_t device_type, const char * cmd, const cmd_json_function_p cb, const char * const * description, uint8_t flags) {
|
// these commands bypass the readonly check (they are actions, not entity setters)
|
||||||
|
void Command::add_json(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags) {
|
||||||
// if the command already exists for that device type don't add it
|
// if the command already exists for that device type don't add it
|
||||||
if (find_command(device_type, 0, cmd, flags) != nullptr) {
|
if (find_command(device_type, 0, cmd, flags) != nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdfunctions_.emplace_back(device_type, 0, flags, cmd, nullptr, cb, description); // callback for json is included
|
cmdfunctions_.emplace_back(device_type, 0, flags, true, cmd, cb, description); // json-output command
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if a command exists for that device type
|
// see if a command exists for that device type
|
||||||
@@ -716,6 +719,10 @@ bool Command::device_has_commands(const uint8_t device_type) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (device_type == EMSdevice::DeviceType::COMMAND) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (device_type == EMSdevice::DeviceType::CUSTOM) {
|
if (device_type == EMSdevice::DeviceType::CUSTOM) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -741,6 +748,7 @@ bool Command::device_has_commands(const uint8_t device_type) {
|
|||||||
void Command::show_devices(uuid::console::Shell & shell) {
|
void Command::show_devices(uuid::console::Shell & shell) {
|
||||||
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SYSTEM));
|
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SYSTEM));
|
||||||
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::CUSTOM));
|
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::CUSTOM));
|
||||||
|
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::COMMAND));
|
||||||
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SCHEDULER));
|
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SCHEDULER));
|
||||||
if (EMSESP::sensor_enabled()) {
|
if (EMSESP::sensor_enabled()) {
|
||||||
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::TEMPERATURESENSOR));
|
shell.printf("%s ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::TEMPERATURESENSOR));
|
||||||
@@ -779,6 +787,7 @@ void Command::show_all(uuid::console::Shell & shell) {
|
|||||||
// show system ones first
|
// show system ones first
|
||||||
show(shell, EMSdevice::DeviceType::SYSTEM, true);
|
show(shell, EMSdevice::DeviceType::SYSTEM, true);
|
||||||
show(shell, EMSdevice::DeviceType::CUSTOM, true);
|
show(shell, EMSdevice::DeviceType::CUSTOM, true);
|
||||||
|
show(shell, EMSdevice::DeviceType::COMMAND, true);
|
||||||
show(shell, EMSdevice::DeviceType::SCHEDULER, true);
|
show(shell, EMSdevice::DeviceType::SCHEDULER, true);
|
||||||
|
|
||||||
// then sensors
|
// then sensors
|
||||||
|
|||||||
@@ -54,33 +54,32 @@ enum CommandRet : uint8_t {
|
|||||||
NO_VALUE // 6 - no value
|
NO_VALUE // 6 - no value
|
||||||
};
|
};
|
||||||
|
|
||||||
using cmd_function_p = std::function<bool(const char * data, const int8_t id)>;
|
using cmd_function_p = std::function<bool(const char * data, const int8_t id, JsonObject output)>;
|
||||||
using cmd_json_function_p = std::function<bool(const char * data, const int8_t id, JsonObject output)>;
|
|
||||||
|
|
||||||
class Command {
|
class Command {
|
||||||
public:
|
public:
|
||||||
struct CmdFunction {
|
struct CmdFunction {
|
||||||
uint8_t device_type_; // DeviceType::
|
uint8_t device_type_; // DeviceType::
|
||||||
uint8_t device_id_;
|
uint8_t device_id_;
|
||||||
uint8_t flags_; // mqtt flags for command subscriptions
|
uint8_t flags_; // mqtt flags for command subscriptions
|
||||||
|
bool has_json_output_; // true if the command writes JSON output; such commands bypass the readonly check
|
||||||
const char * cmd_;
|
const char * cmd_;
|
||||||
cmd_function_p cmdfunction_;
|
cmd_function_p cmdfunction_;
|
||||||
cmd_json_function_p cmdfunction_json_;
|
|
||||||
const char * const * description_;
|
const char * const * description_;
|
||||||
|
|
||||||
CmdFunction(const uint8_t device_type,
|
CmdFunction(const uint8_t device_type,
|
||||||
const uint8_t device_id,
|
const uint8_t device_id,
|
||||||
const uint8_t flags,
|
const uint8_t flags,
|
||||||
const char * cmd,
|
const bool has_json_output,
|
||||||
const cmd_function_p cmdfunction,
|
const char * cmd,
|
||||||
const cmd_json_function_p cmdfunction_json,
|
const cmd_function_p cmdfunction,
|
||||||
const char * const * description)
|
const char * const * description)
|
||||||
: device_type_(device_type)
|
: device_type_(device_type)
|
||||||
, device_id_(device_id)
|
, device_id_(device_id)
|
||||||
, flags_(flags)
|
, flags_(flags)
|
||||||
|
, has_json_output_(has_json_output)
|
||||||
, cmd_(cmd)
|
, cmd_(cmd)
|
||||||
, cmdfunction_(cmdfunction)
|
, cmdfunction_(cmdfunction)
|
||||||
, cmdfunction_json_(cmdfunction_json)
|
|
||||||
, description_(description) {
|
, description_(description) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,12 +115,13 @@ class Command {
|
|||||||
// same for system/temperature/analog devices
|
// same for system/temperature/analog devices
|
||||||
static void
|
static void
|
||||||
add(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
|
add(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
|
||||||
// callback function taking value, id and a json object for its output
|
// command that writes a JSON object as its output; bypasses the readonly check
|
||||||
static void add(const uint8_t device_type,
|
static void
|
||||||
const char * cmd,
|
add_json(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
|
||||||
const cmd_json_function_p cb,
|
|
||||||
const char * const * description,
|
static void reserve(size_t num) {
|
||||||
uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
|
cmdfunctions_.reserve(num);
|
||||||
|
}
|
||||||
|
|
||||||
static void show_all(uuid::console::Shell & shell);
|
static void show_all(uuid::console::Shell & shell);
|
||||||
static Command::CmdFunction * find_command(const uint8_t device_type, const uint8_t device_id, const char * cmd, const uint8_t flag);
|
static Command::CmdFunction * find_command(const uint8_t device_type, const uint8_t device_id, const char * cmd, const uint8_t flag);
|
||||||
|
|||||||
@@ -624,15 +624,15 @@ void EMSESPShell::stopped() {
|
|||||||
// show welcome banner
|
// show welcome banner
|
||||||
void EMSESPShell::display_banner() {
|
void EMSESPShell::display_banner() {
|
||||||
println();
|
println();
|
||||||
printfln("┌───────────────────────────────────────┐");
|
printfln("┌─────────────────────────────────────┐");
|
||||||
printfln("│ %sEMS-ESP version %-20s%s │", COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF);
|
printfln("│ EMS-ESP version %-18s │", EMSESP_APP_VERSION);
|
||||||
printfln("│ │");
|
printfln("│ │");
|
||||||
printfln("│ %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET);
|
printfln("│ %shelp%s to show available commands │", COLOR_UNDERLINE, COLOR_RESET);
|
||||||
printfln("│ %ssu%s to access admin commands │", COLOR_UNDERLINE, COLOR_RESET);
|
printfln("│ %ssu%s to access admin commands │", COLOR_UNDERLINE, COLOR_RESET);
|
||||||
printfln("│ │");
|
printfln("│ │");
|
||||||
printfln("│ %s%shttps://github.com/emsesp/EMS-ESP32%s │", COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET);
|
printfln("│ %s%shttps://emsesp.org%s │", COLOR_GREEN, COLOR_UNDERLINE, COLOR_RESET);
|
||||||
printfln("│ │");
|
printfln("│ │");
|
||||||
printfln("└───────────────────────────────────────┘");
|
printfln("└─────────────────────────────────────┘");
|
||||||
println();
|
println();
|
||||||
|
|
||||||
// set console name
|
// set console name
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ const char * EMSdevice::device_type_2_device_name(const uint8_t device_type) {
|
|||||||
return F_(scheduler);
|
return F_(scheduler);
|
||||||
case DeviceType::CUSTOM:
|
case DeviceType::CUSTOM:
|
||||||
return F_(custom);
|
return F_(custom);
|
||||||
|
case DeviceType::COMMAND:
|
||||||
|
return F_(commands);
|
||||||
case DeviceType::BOILER:
|
case DeviceType::BOILER:
|
||||||
return F_(boiler);
|
return F_(boiler);
|
||||||
case DeviceType::THERMOSTAT:
|
case DeviceType::THERMOSTAT:
|
||||||
@@ -297,6 +299,9 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
|
|||||||
if (!strcmp(lowtopic, F_(scheduler))) {
|
if (!strcmp(lowtopic, F_(scheduler))) {
|
||||||
return DeviceType::SCHEDULER;
|
return DeviceType::SCHEDULER;
|
||||||
}
|
}
|
||||||
|
if (!strcmp(lowtopic, F_(commands))) {
|
||||||
|
return DeviceType::COMMAND;
|
||||||
|
}
|
||||||
if (!strcmp(lowtopic, F_(system))) {
|
if (!strcmp(lowtopic, F_(system))) {
|
||||||
return DeviceType::SYSTEM;
|
return DeviceType::SYSTEM;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -405,6 +405,7 @@ class EMSdevice {
|
|||||||
// Unique Identifiers for each Device type, used in Dashboard table
|
// Unique Identifiers for each Device type, used in Dashboard table
|
||||||
// 100 and above is reserved for DeviceType
|
// 100 and above is reserved for DeviceType
|
||||||
enum DeviceTypeUniqueID : uint8_t {
|
enum DeviceTypeUniqueID : uint8_t {
|
||||||
|
COMMAND_UID = 95,
|
||||||
SCHEDULER_UID = 96,
|
SCHEDULER_UID = 96,
|
||||||
ANALOGSENSOR_UID = 97,
|
ANALOGSENSOR_UID = 97,
|
||||||
TEMPERATURESENSOR_UID = 98,
|
TEMPERATURESENSOR_UID = 98,
|
||||||
@@ -417,6 +418,7 @@ class EMSdevice {
|
|||||||
ANALOGSENSOR, // for internal analog sensors
|
ANALOGSENSOR, // for internal analog sensors
|
||||||
SCHEDULER, // for internal schedule
|
SCHEDULER, // for internal schedule
|
||||||
CUSTOM, // for user defined entities
|
CUSTOM, // for user defined entities
|
||||||
|
COMMAND, // for user defined commands
|
||||||
BOILER, // from here on enum the ems-devices
|
BOILER, // from here on enum the ems-devices
|
||||||
THERMOSTAT,
|
THERMOSTAT,
|
||||||
MIXER,
|
MIXER,
|
||||||
|
|||||||
@@ -51,22 +51,20 @@ uint32_t EMSESP::last_fetch_ = 0;
|
|||||||
AsyncWebServer webServer(80);
|
AsyncWebServer webServer(80);
|
||||||
|
|
||||||
#if defined(EMSESP_STANDALONE)
|
#if defined(EMSESP_STANDALONE)
|
||||||
FS dummyFS;
|
FS dummyFS;
|
||||||
ESP32React EMSESP::esp32React(&webServer, &dummyFS);
|
auto & fsRef = dummyFS;
|
||||||
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &dummyFS, EMSESP::esp32React.getSecurityManager());
|
|
||||||
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &dummyFS, EMSESP::esp32React.getSecurityManager());
|
|
||||||
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &dummyFS, EMSESP::esp32React.getSecurityManager());
|
|
||||||
WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, &dummyFS, EMSESP::esp32React.getSecurityManager());
|
|
||||||
WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, &dummyFS, EMSESP::esp32React.getSecurityManager());
|
|
||||||
#else
|
#else
|
||||||
ESP32React EMSESP::esp32React(&webServer, &LittleFS);
|
auto & fsRef = LittleFS;
|
||||||
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &LittleFS, EMSESP::esp32React.getSecurityManager());
|
|
||||||
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &LittleFS, EMSESP::esp32React.getSecurityManager());
|
|
||||||
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &LittleFS, EMSESP::esp32React.getSecurityManager());
|
|
||||||
WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, &LittleFS, EMSESP::esp32React.getSecurityManager());
|
|
||||||
WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, &LittleFS, EMSESP::esp32React.getSecurityManager());
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
ESP32React EMSESP::esp32React(&webServer, &fsRef);
|
||||||
|
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||||
|
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||||
|
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||||
|
WebCommandService EMSESP::webCommandService = WebCommandService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||||
|
WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||||
|
WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager());
|
||||||
|
|
||||||
WebActivityService EMSESP::webActivityService = WebActivityService(&webServer, EMSESP::esp32React.getSecurityManager());
|
WebActivityService EMSESP::webActivityService = WebActivityService(&webServer, EMSESP::esp32React.getSecurityManager());
|
||||||
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp32React.getSecurityManager());
|
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp32React.getSecurityManager());
|
||||||
WebDataService EMSESP::webDataService = WebDataService(&webServer, EMSESP::esp32React.getSecurityManager());
|
WebDataService EMSESP::webDataService = WebDataService(&webServer, EMSESP::esp32React.getSecurityManager());
|
||||||
@@ -682,6 +680,7 @@ void EMSESP::publish_other_values() {
|
|||||||
// publish_device_values(EMSdevice::DeviceType::GENERIC);
|
// publish_device_values(EMSdevice::DeviceType::GENERIC);
|
||||||
|
|
||||||
webSchedulerService.publish();
|
webSchedulerService.publish();
|
||||||
|
webCommandService.publish();
|
||||||
webCustomEntityService.publish();
|
webCustomEntityService.publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -788,6 +787,11 @@ bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8
|
|||||||
return webSchedulerService.get_value_info(root, cmd);
|
return webSchedulerService.get_value_info(root, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// commands
|
||||||
|
if (devicetype == DeviceType::COMMAND) {
|
||||||
|
return webCommandService.get_value_info(root, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
// custom entities
|
// custom entities
|
||||||
if (devicetype == DeviceType::CUSTOM) {
|
if (devicetype == DeviceType::CUSTOM) {
|
||||||
return webCustomEntityService.get_value_info(root, cmd);
|
return webCustomEntityService.get_value_info(root, cmd);
|
||||||
@@ -915,7 +919,6 @@ std::string EMSESP::pretty_telegram(const std::shared_ptr<const Telegram> & tele
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimized: Use stack buffer and build string once to avoid multiple temporary allocations
|
|
||||||
char buf[250];
|
char buf[250];
|
||||||
if (telegram->operation == Telegram::Operation::RX_READ) {
|
if (telegram->operation == Telegram::Operation::RX_READ) {
|
||||||
auto pos = snprintf(buf,
|
auto pos = snprintf(buf,
|
||||||
@@ -1128,7 +1131,7 @@ bool EMSESP::process_telegram(const std::shared_ptr<const Telegram> & telegram)
|
|||||||
wait_validate_ = 0;
|
wait_validate_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for custom entities reding this telegram
|
// check for custom entities reding this telegram
|
||||||
webCustomEntityService.get_value(telegram);
|
webCustomEntityService.get_value(telegram);
|
||||||
|
|
||||||
// check for common types, like the Version(0x02)
|
// check for common types, like the Version(0x02)
|
||||||
@@ -1179,6 +1182,7 @@ bool EMSESP::process_telegram(const std::shared_ptr<const Telegram> & telegram)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle unknown telegrams
|
// handle unknown telegrams
|
||||||
if (!telegram_found) {
|
if (!telegram_found) {
|
||||||
// mark nonempty telegrams as ignored
|
// mark nonempty telegrams as ignored
|
||||||
@@ -1760,6 +1764,7 @@ void EMSESP::start() {
|
|||||||
// start the core web services, as this loads the settings from the filesystem
|
// start the core web services, as this loads the settings from the filesystem
|
||||||
// this will also handle any MQTT subscriptions
|
// this will also handle any MQTT subscriptions
|
||||||
webCustomizationService.begin(); // load the customizations
|
webCustomizationService.begin(); // load the customizations
|
||||||
|
webCommandService.begin(); // load the user commands
|
||||||
webSchedulerService.begin(); // load the scheduler events
|
webSchedulerService.begin(); // load the scheduler events
|
||||||
webCustomEntityService.begin(); // load the custom telegram reads
|
webCustomEntityService.begin(); // load the custom telegram reads
|
||||||
|
|
||||||
@@ -1853,9 +1858,7 @@ void EMSESP::loop() {
|
|||||||
publish_all_loop(); // with HA messages in parts to avoid flooding the MQTT queue
|
publish_all_loop(); // with HA messages in parts to avoid flooding the MQTT queue
|
||||||
mqtt_.loop(); // sends out anything in the MQTT queue
|
mqtt_.loop(); // sends out anything in the MQTT queue
|
||||||
webModulesService.loop(); // loop through the external library modules
|
webModulesService.loop(); // loop through the external library modules
|
||||||
if (system_.PSram() == 0) { // run non-async if there is no PSRAM available
|
webSchedulerService.loop(); // scheduler timing logic; command execution is offloaded to WebCommandService's worker task
|
||||||
webSchedulerService.loop();
|
|
||||||
}
|
|
||||||
scheduled_fetch_values(); // force a query on the EMS devices to fetch latest data at a set interval (1 min)
|
scheduled_fetch_values(); // force a query on the EMS devices to fetch latest data at a set interval (1 min)
|
||||||
}
|
}
|
||||||
// check for GPIO Errors - this is called once when booting
|
// check for GPIO Errors - this is called once when booting
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
#include "../web/WebSettingsService.h"
|
#include "../web/WebSettingsService.h"
|
||||||
#include "../web/WebCustomizationService.h"
|
#include "../web/WebCustomizationService.h"
|
||||||
#include "../web/WebSchedulerService.h"
|
#include "../web/WebSchedulerService.h"
|
||||||
|
#include "../web/WebCommandService.h"
|
||||||
#include "../web/WebAPIService.h"
|
#include "../web/WebAPIService.h"
|
||||||
#include "../web/WebLogService.h"
|
#include "../web/WebLogService.h"
|
||||||
#include "../web/WebCustomEntityService.h"
|
#include "../web/WebCustomEntityService.h"
|
||||||
@@ -97,7 +98,9 @@ class Module {}; // forward declaration
|
|||||||
return +[](emsesp::EMSdevice * dev, const std::shared_ptr<const Telegram> & t) { static_cast<SelfT *>(dev)->__f(t); }; \
|
return +[](emsesp::EMSdevice * dev, const std::shared_ptr<const Telegram> & t) { static_cast<SelfT *>(dev)->__f(t); }; \
|
||||||
}())
|
}())
|
||||||
|
|
||||||
#define MAKE_CF_CB(__f) [&](const char * value, const int8_t id) { return __f(value, id); } // for Command Function callbacks Command::cmd_function_p
|
// for Command Function callbacks (Command::cmd_function_p). The unified callback takes a JsonObject
|
||||||
|
// output which entity/setter commands ignore.
|
||||||
|
#define MAKE_CF_CB(__f) [&](const char * value, const int8_t id, JsonObject output) { return __f(value, id); }
|
||||||
|
|
||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
|
|
||||||
@@ -260,6 +263,7 @@ class EMSESP {
|
|||||||
static WebLogService webLogService;
|
static WebLogService webLogService;
|
||||||
static WebCustomizationService webCustomizationService;
|
static WebCustomizationService webCustomizationService;
|
||||||
static WebSchedulerService webSchedulerService;
|
static WebSchedulerService webSchedulerService;
|
||||||
|
static WebCommandService webCommandService;
|
||||||
static WebCustomEntityService webCustomEntityService;
|
static WebCustomEntityService webCustomEntityService;
|
||||||
static WebModulesService webModulesService;
|
static WebModulesService webModulesService;
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "firmwareVersion.h"
|
#include "firmwareVersion.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
@@ -47,6 +48,66 @@ const std::string & FirmwareVersion::prerelease() const {
|
|||||||
return prerelease_;
|
return prerelease_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// semver prerelease ordering: a release (empty tag) ranks higher than any prerelease,
|
||||||
|
// and dot-separated numeric identifiers are compared numerically (so dev.9 < dev.12).
|
||||||
|
// returns <0, 0 or >0
|
||||||
|
static int compare_prerelease(const std::string & a, const std::string & b) {
|
||||||
|
if (a == b) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (a.empty()) {
|
||||||
|
return 1; // release > prerelease
|
||||||
|
}
|
||||||
|
if (b.empty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ia = 0;
|
||||||
|
size_t ib = 0;
|
||||||
|
while (ia < a.size() && ib < b.size()) {
|
||||||
|
size_t ea = a.find('.', ia);
|
||||||
|
size_t eb = b.find('.', ib);
|
||||||
|
if (ea == std::string::npos) {
|
||||||
|
ea = a.size();
|
||||||
|
}
|
||||||
|
if (eb == std::string::npos) {
|
||||||
|
eb = b.size();
|
||||||
|
}
|
||||||
|
std::string id_a = a.substr(ia, ea - ia);
|
||||||
|
std::string id_b = b.substr(ib, eb - ib);
|
||||||
|
|
||||||
|
bool num_a = !id_a.empty() && id_a.find_first_not_of("0123456789") == std::string::npos;
|
||||||
|
bool num_b = !id_b.empty() && id_b.find_first_not_of("0123456789") == std::string::npos;
|
||||||
|
|
||||||
|
if (num_a && num_b) {
|
||||||
|
long va = atol(id_a.c_str());
|
||||||
|
long vb = atol(id_b.c_str());
|
||||||
|
if (va != vb) {
|
||||||
|
return (va < vb) ? -1 : 1;
|
||||||
|
}
|
||||||
|
} else if (num_a != num_b) {
|
||||||
|
return num_a ? -1 : 1; // numeric identifiers rank lower than alphanumeric ones
|
||||||
|
} else {
|
||||||
|
int cmp = id_a.compare(id_b);
|
||||||
|
if (cmp != 0) {
|
||||||
|
return (cmp < 0) ? -1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ia = ea + 1;
|
||||||
|
ib = eb + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all shared identifiers are equal; the one with more identifiers ranks higher
|
||||||
|
if (ia < a.size()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (ib < b.size()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool operator<(const FirmwareVersion & a, const FirmwareVersion & b) {
|
bool operator<(const FirmwareVersion & a, const FirmwareVersion & b) {
|
||||||
if (a.major_ != b.major_)
|
if (a.major_ != b.major_)
|
||||||
return a.major_ < b.major_;
|
return a.major_ < b.major_;
|
||||||
@@ -54,7 +115,7 @@ bool operator<(const FirmwareVersion & a, const FirmwareVersion & b) {
|
|||||||
return a.minor_ < b.minor_;
|
return a.minor_ < b.minor_;
|
||||||
if (a.patch_ != b.patch_)
|
if (a.patch_ != b.patch_)
|
||||||
return a.patch_ < b.patch_;
|
return a.patch_ < b.patch_;
|
||||||
return a.prerelease_ < b.prerelease_;
|
return compare_prerelease(a.prerelease_, b.prerelease_) < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator>(const FirmwareVersion & a, const FirmwareVersion & b) {
|
bool operator>(const FirmwareVersion & a, const FirmwareVersion & b) {
|
||||||
@@ -62,7 +123,7 @@ bool operator>(const FirmwareVersion & a, const FirmwareVersion & b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const FirmwareVersion & a, const FirmwareVersion & b) {
|
bool operator==(const FirmwareVersion & a, const FirmwareVersion & b) {
|
||||||
return a.major_ == b.major_ && a.minor_ == b.minor_ && a.patch_ == b.patch_ && a.prerelease_ == b.prerelease_;
|
return a.major_ == b.major_ && a.minor_ == b.minor_ && a.patch_ == b.patch_ && compare_prerelease(a.prerelease_, b.prerelease_) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const FirmwareVersion & a, const FirmwareVersion & b) {
|
bool operator!=(const FirmwareVersion & a, const FirmwareVersion & b) {
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class FirmwareVersion {
|
|||||||
int patch() const;
|
int patch() const;
|
||||||
const std::string & prerelease() const;
|
const std::string & prerelease() const;
|
||||||
|
|
||||||
// Numeric-only comparison (major.minor.patch). Prerelease tags are ignored on purpose.
|
|
||||||
friend bool operator<(const FirmwareVersion & a, const FirmwareVersion & b);
|
friend bool operator<(const FirmwareVersion & a, const FirmwareVersion & b);
|
||||||
friend bool operator>(const FirmwareVersion & a, const FirmwareVersion & b);
|
friend bool operator>(const FirmwareVersion & a, const FirmwareVersion & b);
|
||||||
friend bool operator==(const FirmwareVersion & a, const FirmwareVersion & b);
|
friend bool operator==(const FirmwareVersion & a, const FirmwareVersion & b);
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ char * Helpers::hextoa(char * result, const uint8_t value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// same as hextoa but uses to a hex std::string
|
// same as hextoa but uses to a hex std::string
|
||||||
// Optimized: Avoid string concatenation to reduce temporary allocations
|
|
||||||
std::string Helpers::hextoa(const uint8_t value, bool prefix) {
|
std::string Helpers::hextoa(const uint8_t value, bool prefix) {
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
char buf[5]; // "0x" + 2 hex chars + null
|
char buf[5]; // "0x" + 2 hex chars + null
|
||||||
@@ -60,7 +59,6 @@ char * Helpers::hextoa(char * result, const uint16_t value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// same as above but to a hex string
|
// same as above but to a hex string
|
||||||
// Optimized: Avoid string concatenation to reduce temporary allocations
|
|
||||||
std::string Helpers::hextoa(const uint16_t value, bool prefix) {
|
std::string Helpers::hextoa(const uint16_t value, bool prefix) {
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
char buf[7]; // "0x" + 4 hex chars + null
|
char buf[7]; // "0x" + 4 hex chars + null
|
||||||
@@ -114,7 +112,6 @@ char * Helpers::ultostr(char * ptr, uint32_t value, const uint8_t base) {
|
|||||||
|
|
||||||
// fast itoa returning a std::string
|
// fast itoa returning a std::string
|
||||||
// http://www.strudel.org.uk/itoa/
|
// http://www.strudel.org.uk/itoa/
|
||||||
// Optimized: Use stack buffer to avoid heap allocation, then create string once
|
|
||||||
std::string Helpers::itoa(int16_t value) {
|
std::string Helpers::itoa(int16_t value) {
|
||||||
// int16_t max: -32768 to 32767 = max 6 chars + null
|
// int16_t max: -32768 to 32767 = max 6 chars + null
|
||||||
char buf[8];
|
char buf[8];
|
||||||
@@ -140,7 +137,7 @@ std::string Helpers::itoa(int16_t value) {
|
|||||||
/*
|
/*
|
||||||
* fast itoa
|
* fast itoa
|
||||||
* written by Lukás Chmela, Released under GPLv3. http://www.strudel.org.uk/itoa/ version 0.4
|
* written by Lukás Chmela, Released under GPLv3. http://www.strudel.org.uk/itoa/ version 0.4
|
||||||
* optimized for ESP32
|
* optimized for ESP32 for EMS-ESP
|
||||||
*/
|
*/
|
||||||
char * Helpers::itoa(int32_t value, char * result, const uint8_t base) {
|
char * Helpers::itoa(int32_t value, char * result, const uint8_t base) {
|
||||||
// check that the base if valid
|
// check that the base if valid
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ MAKE_WORD_TRANSLATION(format_cmd, "factory reset EMS-ESP", "EMS-ESP auf Werksein
|
|||||||
MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Beobachte eingehende Telegramme", "inkomende telegrammen bekijken", "visa inkommande telegram", "obserwuj przyczodzące telegramy", "se innkommende telegrammer", "surveiller les télégrammes entrants", "Gelen telegramları izle", "guardare i telegrammi in arrivo", "sledovať prichádzajúce telegramy", "sledovat příchozí telegramy")
|
MAKE_WORD_TRANSLATION(watch_cmd, "watch incoming telegrams", "Beobachte eingehende Telegramme", "inkomende telegrammen bekijken", "visa inkommande telegram", "obserwuj przyczodzące telegramy", "se innkommende telegrammer", "surveiller les télégrammes entrants", "Gelen telegramları izle", "guardare i telegrammi in arrivo", "sledovať prichádzajúce telegramy", "sledovat příchozí telegramy")
|
||||||
MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "publiceer alles naar MQTT", "publicera allt till MQTT", "opublikuj wszystko na MQTT", "Publiser alt til MQTT", "publier tout vers MQTT", "Hepsini MQTTye gönder", "pubblica tutto su MQTT", "zverejniť všetko na MQTT", "publikovat vše do MQTT")
|
MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "publiceer alles naar MQTT", "publicera allt till MQTT", "opublikuj wszystko na MQTT", "Publiser alt til MQTT", "publier tout vers MQTT", "Hepsini MQTTye gönder", "pubblica tutto su MQTT", "zverejniť všetko na MQTT", "publikovat vše do MQTT")
|
||||||
MAKE_WORD_TRANSLATION(system_info_cmd, "show system info", "Zeige Systeminformationen", "toon systeemstatus", "visa systeminformation", "pokaż status systemu", "vis system status", "afficher les informations système", "Sistem Durumunu Göster", "visualizza stati di sistema", "zobraziť stav systému", "zobrazit informace o systému")
|
MAKE_WORD_TRANSLATION(system_info_cmd, "show system info", "Zeige Systeminformationen", "toon systeemstatus", "visa systeminformation", "pokaż status systemu", "vis system status", "afficher les informations système", "Sistem Durumunu Göster", "visualizza stati di sistema", "zobraziť stav systému", "zobrazit informace o systému")
|
||||||
MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplanelemente", "activeer tijdschema item", "aktivera schemalagt objekt", "aktywuj wybrany harmonogram", "aktiver planlagt element", "activer élément programmé", "program öğesini etkinleştir", "abilitare l'elemento programmato", "povoliť položku plánovania", "povolit položku plánování")
|
MAKE_WORD_TRANSLATION(schedule_cmd, "enable/disable schedule item", "Aktiviere/Deaktiviere Zeitplanelemente", "activeer/deactiveer tijdschema item", "aktivera/deaktivera schemalagt objekt", "aktywuj/deaktywuj wybrany harmonogram", "aktiver/deaktiver planlagt element", "activer/deactiver élément programmé", "program öğesini etkinleştir/devre dışı bırak", "abilitare/disabilitare l'elemento programmato", "povoliť/deaktivovať položku plánovania", "povolit/deaktivovat položku plánování")
|
||||||
|
MAKE_WORD_TRANSLATION(command_cmd, "execute command", "Befehl ausführen", "opdracht uitvoeren", "kör kommando", "wykonaj polecenie", "kjør kommando", "exécuter commande", "komut çalıştır", "esegui comando", "vykonať príkaz", "provést příkaz")
|
||||||
MAKE_WORD_TRANSLATION(entity_cmd, "set custom value", "Sende eigene Entitäten", "verstuur custom waarde", "sätt ett eget värde", "wyślij własną wartość", "sett egendefinert verdi", "définir valeur personnalisée", "özel değer ayarla", "imposta valori personalizzati", "nastaviť vlastnú hodnotu", "nastavit vlastní hodnotu")
|
MAKE_WORD_TRANSLATION(entity_cmd, "set custom value", "Sende eigene Entitäten", "verstuur custom waarde", "sätt ett eget värde", "wyślij własną wartość", "sett egendefinert verdi", "définir valeur personnalisée", "özel değer ayarla", "imposta valori personalizzati", "nastaviť vlastnú hodnotu", "nastavit vlastní hodnotu")
|
||||||
MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "hämta svar", "uzyskaj odpowiedź", "få svar", "obtenir réponse", "yanıt al", "ottieni risposta", "získať odpoveď", "získat odpověď")
|
MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "hämta svar", "uzyskaj odpowiedź", "få svar", "obtenir réponse", "yanıt al", "ottieni risposta", "získať odpoveď", "získat odpověď")
|
||||||
MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "Zugabe einer Menge kalten Wassers", "stuur koud water", "sckicka en liten mängd kallvatten", "uruchom tryśnięcie zimnej wody", "send kaldtvannspuls", "envoyer de l'eau froide", "soğuk su gönder", "invia acqua fredda", "pošlite studenú dávku vody", "poslat studenou vodu")
|
MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "Zugabe einer Menge kalten Wassers", "stuur koud water", "sckicka en liten mängd kallvatten", "uruchom tryśnięcie zimnej wody", "send kaldtvannspuls", "envoyer de l'eau froide", "soğuk su gönder", "invia acqua fredda", "pošlite studenú dávku vody", "poslat studenou vodu")
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ void Mqtt::start() {
|
|||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
|
|
||||||
// add the 'publish' command ('call system publish' in console or via API)
|
// add the 'publish' command ('call system publish' in console or via API)
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, FL_(publish_cmd));
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), MAKE_CF_CB(System::command_publish), FL_(publish_cmd));
|
||||||
|
|
||||||
#if defined(EMSESP_STANDALONE)
|
#if defined(EMSESP_STANDALONE)
|
||||||
Mqtt::on_connect(); // simulate an MQTT connection
|
Mqtt::on_connect(); // simulate an MQTT connection
|
||||||
@@ -510,6 +510,7 @@ void Mqtt::on_connect() {
|
|||||||
// send initial MQTT messages for some of our services
|
// send initial MQTT messages for some of our services
|
||||||
EMSESP::system_.send_heartbeat(); // send heartbeat
|
EMSESP::system_.send_heartbeat(); // send heartbeat
|
||||||
EMSESP::webCustomEntityService.publish(true);
|
EMSESP::webCustomEntityService.publish(true);
|
||||||
|
EMSESP::webCommandService.publish(true);
|
||||||
EMSESP::webSchedulerService.publish(true);
|
EMSESP::webSchedulerService.publish(true);
|
||||||
EMSESP::analogsensor_.publish_values(true);
|
EMSESP::analogsensor_.publish_values(true);
|
||||||
EMSESP::temperaturesensor_.publish_values(true);
|
EMSESP::temperaturesensor_.publish_values(true);
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ void Network::begin() {
|
|||||||
WiFi.persistent(false);
|
WiFi.persistent(false);
|
||||||
WiFi.setAutoReconnect(false);
|
WiFi.setAutoReconnect(false);
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.disconnect(true, true); // wipe old settings in NVS
|
WiFi.disconnect(true, true); // wipe old settings in NVS. Will give a warning on boot but can be ignored.
|
||||||
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||||
WiFi.setHostname(hostname_.c_str()); // updates shared default_hostname buffer
|
WiFi.setHostname(hostname_.c_str()); // updates shared default_hostname buffer
|
||||||
WiFi.enableSTA(true); // creates the STA netif
|
WiFi.enableSTA(true); // creates the STA netif
|
||||||
@@ -308,7 +308,11 @@ void Network::checkConnection() {
|
|||||||
network_ip_ = 0;
|
network_ip_ = 0;
|
||||||
has_ipv6_ = false;
|
has_ipv6_ = false;
|
||||||
connect_retry_ = 0;
|
connect_retry_ = 0;
|
||||||
if (network_iface_ == NetIface::ETHERNET) {
|
// reset the active interface so findNetworks() treats the next link-up (even on the
|
||||||
|
// same interface) as a new connection and re-runs the setup, including startmDNS()
|
||||||
|
const NetIface lost_iface = network_iface_;
|
||||||
|
network_iface_ = NetIface::NONE;
|
||||||
|
if (lost_iface == NetIface::ETHERNET) {
|
||||||
LOG_WARNING("Ethernet connection lost");
|
LOG_WARNING("Ethernet connection lost");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ void Shower::start() {
|
|||||||
shower_min_duration_ = settings.shower_min_duration; // in seconds
|
shower_min_duration_ = settings.shower_min_duration; // in seconds
|
||||||
});
|
});
|
||||||
|
|
||||||
Command::add(
|
Command::add_json(
|
||||||
EMSdevice::DeviceType::BOILER,
|
EMSdevice::DeviceType::BOILER,
|
||||||
F_(coldshot),
|
F_(coldshot),
|
||||||
[&](const char * value, const int8_t id, JsonObject output) {
|
[&](const char * value, const int8_t id, JsonObject output) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
|
|
||||||
// find tokens - optimized to reduce string allocations
|
// find tokens
|
||||||
std::deque<Token> exprToTokens(const std::string & expr) {
|
std::deque<Token> exprToTokens(const std::string & expr) {
|
||||||
std::deque<Token> tokens;
|
std::deque<Token> tokens;
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
|
|||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort tokens to RPN form - optimized for memory usage
|
// sort tokens to RPN form
|
||||||
std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
|
std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
|
||||||
std::deque<Token> queue;
|
std::deque<Token> queue;
|
||||||
std::vector<Token> stack;
|
std::vector<Token> stack;
|
||||||
@@ -347,7 +347,6 @@ bool isnum(const std::string & s) {
|
|||||||
std::string commands(std::string & expr, bool quotes) {
|
std::string commands(std::string & expr, bool quotes) {
|
||||||
auto expr_new = Helpers::toLower(expr);
|
auto expr_new = Helpers::toLower(expr);
|
||||||
for (uint8_t device = 0; device < EMSdevice::DeviceType::UNKNOWN; device++) {
|
for (uint8_t device = 0; device < EMSdevice::DeviceType::UNKNOWN; device++) {
|
||||||
// Optimized: build string with reserve to avoid temporary allocations
|
|
||||||
std::string d;
|
std::string d;
|
||||||
d.reserve(32); // typical device name length + "/"
|
d.reserve(32); // typical device name length + "/"
|
||||||
d = EMSdevice::device_type_2_device_name(device);
|
d = EMSdevice::device_type_2_device_name(device);
|
||||||
@@ -374,8 +373,7 @@ std::string commands(std::string & expr, bool quotes) {
|
|||||||
JsonDocument doc_in;
|
JsonDocument doc_in;
|
||||||
JsonObject output = doc_out.to<JsonObject>();
|
JsonObject output = doc_out.to<JsonObject>();
|
||||||
JsonObject input = doc_in.to<JsonObject>();
|
JsonObject input = doc_in.to<JsonObject>();
|
||||||
// Optimized: use stack buffer for small strings to avoid heap allocation
|
char cmd_s[COMMAND_MAX_LENGTH + 5]; // "api/" prefix + cmd
|
||||||
char cmd_s[COMMAND_MAX_LENGTH + 5]; // "api/" prefix + cmd
|
|
||||||
snprintf(cmd_s, sizeof(cmd_s), "api/%s", cmd);
|
snprintf(cmd_s, sizeof(cmd_s), "api/%s", cmd);
|
||||||
|
|
||||||
auto return_code = Command::process(cmd_s, true, input, output);
|
auto return_code = Command::process(cmd_s, true, input, output);
|
||||||
|
|||||||
@@ -990,22 +990,24 @@ void System::system_check() {
|
|||||||
// commands - takes static function pointers
|
// commands - takes static function pointers
|
||||||
// can be called via Console using 'call system <cmd>'
|
// can be called via Console using 'call system <cmd>'
|
||||||
void System::commands_init() {
|
void System::commands_init() {
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(read), System::command_read, FL_(read_cmd), CommandFlag::ADMIN_ONLY);
|
// Command::reserve(200);
|
||||||
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);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(read), MAKE_CF_CB(System::command_read), FL_(read_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(sendmail), System::command_sendmail, FL_(sendmail_cmd), CommandFlag::ADMIN_ONLY);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), MAKE_CF_CB(System::command_send), FL_(send_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_cmd), CommandFlag::ADMIN_ONLY);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), MAKE_CF_CB(System::command_fetch), FL_(fetch_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(format), System::command_format, FL_(format_cmd), CommandFlag::ADMIN_ONLY);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(sendmail), MAKE_CF_CB(System::command_sendmail), FL_(sendmail_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(txpause), System::command_txpause, FL_(txpause_cmd), CommandFlag::ADMIN_ONLY);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), MAKE_CF_CB(System::command_restart), FL_(restart_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(led), System::command_led, FL_(led_cmd), CommandFlag::ADMIN_ONLY);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(format), MAKE_CF_CB(System::command_format), FL_(format_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), System::command_watch, FL_(watch_cmd));
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(txpause), MAKE_CF_CB(System::command_txpause), FL_(txpause_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(message), System::command_message, FL_(message_cmd));
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(led), MAKE_CF_CB(System::command_led), FL_(led_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), MAKE_CF_CB(System::command_watch), FL_(watch_cmd));
|
||||||
|
Command::add_json(EMSdevice::DeviceType::SYSTEM, F_(message), System::command_message, FL_(message_cmd));
|
||||||
#if defined(EMSESP_TEST)
|
#if defined(EMSESP_TEST)
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, ("test"), System::command_test, FL_(test_cmd));
|
Command::add(EMSdevice::DeviceType::SYSTEM, ("test"), MAKE_CF_CB(System::command_test), FL_(test_cmd));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// these commands will return data in JSON format
|
// these commands will return data in JSON format
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F("response"), System::command_response, FL_(commands_response));
|
Command::add_json(EMSdevice::DeviceType::SYSTEM, F("response"), System::command_response, FL_(commands_response));
|
||||||
|
|
||||||
// MQTT subscribe "ems-esp/system/#"
|
// MQTT subscribe "ems-esp/system/#"
|
||||||
Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback
|
Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback
|
||||||
@@ -1511,7 +1513,20 @@ bool System::check_upgrade() {
|
|||||||
EMSESP::network_.reconnect();
|
EMSESP::network_.reconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// changes going to v3.9 from an earlier version
|
// capture the raw Scheduler file now, before any upgrade step below rewrites it in the new format.
|
||||||
|
// it's needed further down to migrate the pre-v3.9.0-dev.12 inline command format into the Commands Service
|
||||||
|
#ifndef EMSESP_STANDALONE
|
||||||
|
JsonDocument oldScheduleDoc(PSRAM_DOC);
|
||||||
|
{
|
||||||
|
File schedulerFile = LittleFS.open(EMSESP_SCHEDULER_FILE);
|
||||||
|
if (schedulerFile) {
|
||||||
|
deserializeJson(oldScheduleDoc, schedulerFile);
|
||||||
|
schedulerFile.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// changes going to v3.9 from an 3.8.x or earlier
|
||||||
if (settings_version.major() == 3 && settings_version.minor() < 9) {
|
if (settings_version.major() == 3 && settings_version.minor() < 9) {
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
// AP_MODE_ALWAYS has been removed
|
// AP_MODE_ALWAYS has been removed
|
||||||
@@ -1523,7 +1538,7 @@ bool System::check_upgrade() {
|
|||||||
}
|
}
|
||||||
return StateUpdateResult::UNCHANGED;
|
return StateUpdateResult::UNCHANGED;
|
||||||
});
|
});
|
||||||
// Scheduler name is now mandatory, update FS
|
// Scheduler name is now mandatory, update FS if name is empty
|
||||||
uint8_t i = 0;
|
uint8_t i = 0;
|
||||||
bool schedule_changed = false;
|
bool schedule_changed = false;
|
||||||
EMSESP::webSchedulerService.update([&](WebScheduler & scheduler) {
|
EMSESP::webSchedulerService.update([&](WebScheduler & scheduler) {
|
||||||
@@ -1538,6 +1553,80 @@ bool System::check_upgrade() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Core3 3.9.0-dev.12 implements the new Commands Service.
|
||||||
|
// versions before that stored the command (cmd) and value inline within each Scheduler entry
|
||||||
|
#ifndef EMSESP_STANDALONE
|
||||||
|
{
|
||||||
|
JsonArray oldScheduleItems = oldScheduleDoc["schedule"].as<JsonArray>();
|
||||||
|
|
||||||
|
// only migrate if at least one entry still uses the old inline format (has "cmd" but no "cmd_name")
|
||||||
|
bool old_format = false;
|
||||||
|
for (JsonObject item : oldScheduleItems) {
|
||||||
|
if (!item["cmd"].isNull() && item["cmd_name"].isNull()) {
|
||||||
|
old_format = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_format) {
|
||||||
|
LOG_INFO("Upgrade: Migrating %d Scheduler entries to the new Commands Service", (int)oldScheduleItems.size());
|
||||||
|
|
||||||
|
// create a Command for each Scheduler entry, reusing the entry's name (generating one if empty)
|
||||||
|
EMSESP::webCommandService.update([&](WebCommands & commands) {
|
||||||
|
commands.commandItems.clear();
|
||||||
|
uint8_t idx = 0;
|
||||||
|
for (JsonObject item : oldScheduleItems) {
|
||||||
|
auto ci = CommandItem();
|
||||||
|
ci.cmd = item["cmd"].as<std::string>();
|
||||||
|
ci.value = item["value"].as<std::string>();
|
||||||
|
const char * nm = item["name"];
|
||||||
|
// name could still be empty
|
||||||
|
if (nm != nullptr && nm[0] != '\0') {
|
||||||
|
strlcpy(ci.name, nm, sizeof(ci.name));
|
||||||
|
} else {
|
||||||
|
snprintf(ci.name, sizeof(ci.name), "schedule_%d", idx);
|
||||||
|
}
|
||||||
|
commands.commandItems.push_back(ci);
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
return StateUpdateResult::CHANGED;
|
||||||
|
});
|
||||||
|
|
||||||
|
// point each Scheduler entry at its new Command via cmd_name
|
||||||
|
EMSESP::webSchedulerService.update([&](WebScheduler & scheduler) {
|
||||||
|
uint8_t idx = 0;
|
||||||
|
auto it = scheduler.scheduleItems.begin();
|
||||||
|
for (JsonObject item : oldScheduleItems) {
|
||||||
|
if (it == scheduler.scheduleItems.end()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// flag 132 (0x84) is the old IMMEDIATE format which has no command - erase the entry
|
||||||
|
if (item["flags"].as<uint8_t>() == 0x84) {
|
||||||
|
it = scheduler.scheduleItems.erase(it);
|
||||||
|
idx++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const char * nm = item["name"];
|
||||||
|
char cmd_name[sizeof(it->name)];
|
||||||
|
if (nm != nullptr && nm[0] != '\0') {
|
||||||
|
strlcpy(cmd_name, nm, sizeof(cmd_name));
|
||||||
|
} else {
|
||||||
|
snprintf(cmd_name, sizeof(cmd_name), "schedule_%d", idx);
|
||||||
|
strlcpy(it->name, cmd_name, sizeof(it->name)); // keep entry name consistent with its command
|
||||||
|
}
|
||||||
|
it->cmd_name = cmd_name;
|
||||||
|
++it;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
return StateUpdateResult::CHANGED;
|
||||||
|
});
|
||||||
|
|
||||||
|
// reboot so both services reload cleanly in the new format and re-register their commands
|
||||||
|
reboot_required = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// changes to application settings
|
// changes to application settings
|
||||||
EMSESP::webSettingsService.update([&](WebSettings & settings) {
|
EMSESP::webSettingsService.update([&](WebSettings & settings) {
|
||||||
// force web buffer to 25 for those boards without psram
|
// force web buffer to 25 for those boards without psram
|
||||||
@@ -1592,6 +1681,7 @@ static const std::pair<const char *, const char *> SECTION_MAP[] = {
|
|||||||
{NTP_SETTINGS_FILE, "NTP"},
|
{NTP_SETTINGS_FILE, "NTP"},
|
||||||
{SECURITY_SETTINGS_FILE, "Security"},
|
{SECURITY_SETTINGS_FILE, "Security"},
|
||||||
{EMSESP_SETTINGS_FILE, "Settings"},
|
{EMSESP_SETTINGS_FILE, "Settings"},
|
||||||
|
{EMSESP_COMMANDS_FILE, "Commands"},
|
||||||
{EMSESP_SCHEDULER_FILE, "Schedule"},
|
{EMSESP_SCHEDULER_FILE, "Schedule"},
|
||||||
{EMSESP_CUSTOMIZATION_FILE, "Customizations"},
|
{EMSESP_CUSTOMIZATION_FILE, "Customizations"},
|
||||||
{EMSESP_CUSTOMENTITY_FILE, "Entities"},
|
{EMSESP_CUSTOMENTITY_FILE, "Entities"},
|
||||||
@@ -1667,6 +1757,8 @@ void System::exportSystemBackup(JsonObject output) {
|
|||||||
exportSettings("settings", SECURITY_SETTINGS_FILE, node);
|
exportSettings("settings", SECURITY_SETTINGS_FILE, node);
|
||||||
exportSettings("settings", EMSESP_SETTINGS_FILE, node);
|
exportSettings("settings", EMSESP_SETTINGS_FILE, node);
|
||||||
|
|
||||||
|
node = nodes.add<JsonObject>();
|
||||||
|
exportSettings("commands", EMSESP_COMMANDS_FILE, node);
|
||||||
node = nodes.add<JsonObject>();
|
node = nodes.add<JsonObject>();
|
||||||
exportSettings("schedule", EMSESP_SCHEDULER_FILE, node);
|
exportSettings("schedule", EMSESP_SCHEDULER_FILE, node);
|
||||||
node = nodes.add<JsonObject>();
|
node = nodes.add<JsonObject>();
|
||||||
@@ -2610,6 +2702,12 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
|||||||
obj["name"] = F_(scheduler);
|
obj["name"] = F_(scheduler);
|
||||||
obj["entities"] = EMSESP::webSchedulerService.count_entities();
|
obj["entities"] = EMSESP::webSchedulerService.count_entities();
|
||||||
}
|
}
|
||||||
|
if (EMSESP::webCommandService.count_entities()) {
|
||||||
|
JsonObject obj = devices.add<JsonObject>();
|
||||||
|
obj["type"] = F_(commands);
|
||||||
|
obj["name"] = F_(commands);
|
||||||
|
obj["entities"] = EMSESP::webCommandService.count_entities();
|
||||||
|
}
|
||||||
if (EMSESP::webCustomEntityService.count_entities()) {
|
if (EMSESP::webCustomEntityService.count_entities()) {
|
||||||
JsonObject obj = devices.add<JsonObject>();
|
JsonObject obj = devices.add<JsonObject>();
|
||||||
obj["type"] = F_(custom);
|
obj["type"] = F_(custom);
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ void TemperatureSensor::publish_values(const bool force) {
|
|||||||
publish_sensor(sensor);
|
publish_sensor(sensor);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (!EMSESP::mqtt_.get_publish_onchange(0)) {
|
} else if (!EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
|
||||||
return; // wait for first time period
|
return; // wait for first time period
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
#define EMSESP_APP_VERSION "3.9.0-dev.11"
|
#define EMSESP_APP_VERSION "3.9.0-dev.12"
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
|||||||
EMSESP::webCustomEntityService.load_test_data(); // custom entities
|
EMSESP::webCustomEntityService.load_test_data(); // custom entities
|
||||||
EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS
|
EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS
|
||||||
EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors
|
EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors
|
||||||
|
EMSESP::webCommandService.load_test_data(); // add command items
|
||||||
EMSESP::webSchedulerService.load_test_data(); // add scheduler data
|
EMSESP::webSchedulerService.load_test_data(); // add scheduler data
|
||||||
|
|
||||||
shell.invoke_command("show values");
|
shell.invoke_command("show values");
|
||||||
@@ -406,7 +407,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
|||||||
if (command == "scheduler") {
|
if (command == "scheduler") {
|
||||||
shell.printfln("Adding Scheduler items...");
|
shell.printfln("Adding Scheduler items...");
|
||||||
|
|
||||||
// add some dummy entities
|
EMSESP::webCommandService.load_test_data();
|
||||||
EMSESP::webSchedulerService.load_test_data();
|
EMSESP::webSchedulerService.load_test_data();
|
||||||
|
|
||||||
#ifdef EMSESP_STANDALONE
|
#ifdef EMSESP_STANDALONE
|
||||||
@@ -1116,6 +1117,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
|||||||
EMSESP::webCustomEntityService.load_test_data(); // custom entities
|
EMSESP::webCustomEntityService.load_test_data(); // custom entities
|
||||||
EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS
|
EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS
|
||||||
EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors
|
EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors
|
||||||
|
EMSESP::webCommandService.load_test_data(); // add command items
|
||||||
EMSESP::webSchedulerService.load_test_data(); // run scheduler tests, and conditions
|
EMSESP::webSchedulerService.load_test_data(); // run scheduler tests, and conditions
|
||||||
|
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
@@ -1379,6 +1381,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
|||||||
EMSESP::webCustomEntityService.load_test_data(); // custom entities
|
EMSESP::webCustomEntityService.load_test_data(); // custom entities
|
||||||
EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS
|
EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS
|
||||||
EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors
|
EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors
|
||||||
|
EMSESP::webCommandService.load_test_data(); // add command items
|
||||||
EMSESP::webSchedulerService.load_test_data(); // run scheduler tests, and conditions
|
EMSESP::webSchedulerService.load_test_data(); // run scheduler tests, and conditions
|
||||||
|
|
||||||
request.method(HTTP_GET);
|
request.method(HTTP_GET);
|
||||||
@@ -2110,6 +2113,101 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
|||||||
ok = true;
|
ok = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (command == "version") {
|
||||||
|
shell.printfln("Testing version upgrade and downgrade detection...");
|
||||||
|
|
||||||
|
// mirrors System::check_upgrade(): settings = version stored in settings file, current = running firmware
|
||||||
|
struct VersionTest {
|
||||||
|
const char * settings;
|
||||||
|
const char * current;
|
||||||
|
const char * expected; // "upgrade", "downgrade" or "same"
|
||||||
|
};
|
||||||
|
|
||||||
|
const VersionTest tests[] = {
|
||||||
|
// identical versions
|
||||||
|
{"3.9.0", "3.9.0", "same"},
|
||||||
|
{"3.9.0-dev.12", "3.9.0-dev.12", "same"},
|
||||||
|
|
||||||
|
// numeric upgrades (patch, minor, major)
|
||||||
|
{"3.9.0", "3.9.1", "upgrade"},
|
||||||
|
{"3.8.5", "3.9.0", "upgrade"},
|
||||||
|
{"2.10.9", "3.0.0", "upgrade"},
|
||||||
|
|
||||||
|
// numeric downgrades
|
||||||
|
{"3.9.1", "3.9.0", "downgrade"},
|
||||||
|
{"3.9.0", "3.8.5", "downgrade"},
|
||||||
|
{"3.0.0", "2.10.9", "downgrade"},
|
||||||
|
|
||||||
|
// prerelease (dev) sequences on the same base version
|
||||||
|
{"3.9.0-dev.12", "3.9.0-dev.13", "upgrade"},
|
||||||
|
{"3.9.0-dev.13", "3.9.0-dev.12", "downgrade"},
|
||||||
|
{"3.9.0-dev.9", "3.9.0-dev.12", "upgrade"}, // single vs double digit dev number
|
||||||
|
{"3.9.0-dev.8", "3.9.0-dev.12", "upgrade"}, // regression: was reported as a downgrade
|
||||||
|
|
||||||
|
// prerelease vs release on the same base version (semver: prerelease < release)
|
||||||
|
{"3.9.0-dev.12", "3.9.0", "upgrade"},
|
||||||
|
{"3.9.0", "3.9.0-dev.12", "downgrade"},
|
||||||
|
|
||||||
|
// prerelease vs a different base version
|
||||||
|
{"3.9.0-dev.12", "3.9.1", "upgrade"},
|
||||||
|
{"3.9.1", "3.9.0-dev.12", "downgrade"},
|
||||||
|
{"3.8.5", "3.9.0-dev.12", "upgrade"},
|
||||||
|
|
||||||
|
// mixed prerelease tags
|
||||||
|
{"3.5.0-b13", "3.9.0-dev.12", "upgrade"},
|
||||||
|
|
||||||
|
// partial version strings are shorter than 5 chars, so check_upgrade() treats them as missing (3.5.0)
|
||||||
|
{"3.9", "3.9.0", "upgrade"},
|
||||||
|
{"3.9", "3.9.1", "upgrade"},
|
||||||
|
|
||||||
|
// build metadata after '+' is ignored
|
||||||
|
{"3.9.0+abc123", "3.9.0", "same"},
|
||||||
|
|
||||||
|
// numeric prerelease identifiers compare numerically, so leading zeros are equivalent
|
||||||
|
{"3.9.0-dev.01", "3.9.0-dev.1", "same"},
|
||||||
|
{"3.9.0-dev.012", "3.9.0-dev.12", "same"},
|
||||||
|
|
||||||
|
// missing/short version: check_upgrade() assumes 3.5.0
|
||||||
|
{"", "3.9.0", "upgrade"},
|
||||||
|
{"1.0", "3.9.0", "upgrade"},
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t failed = 0;
|
||||||
|
for (const auto & test : tests) {
|
||||||
|
// replicate check_upgrade()'s handling of a missing version
|
||||||
|
std::string settingsVersion = test.settings;
|
||||||
|
if (settingsVersion.length() < 5) {
|
||||||
|
settingsVersion = "3.5.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
FirmwareVersion settings_version(settingsVersion);
|
||||||
|
FirmwareVersion this_version(test.current);
|
||||||
|
|
||||||
|
const char * actual;
|
||||||
|
if (this_version > settings_version) {
|
||||||
|
actual = "upgrade";
|
||||||
|
} else if (this_version < settings_version) {
|
||||||
|
actual = "downgrade";
|
||||||
|
} else {
|
||||||
|
actual = "same";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pass = (strcmp(actual, test.expected) == 0);
|
||||||
|
if (!pass) {
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
shell.printfln("%s %-14s -> %-14s expected %-9s got %-9s", pass ? "PASS" : "FAIL", test.settings, test.current, test.expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed) {
|
||||||
|
shell.printfln("%d test(s) FAILED", failed);
|
||||||
|
} else {
|
||||||
|
shell.printfln("All version tests passed");
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (command == "mqtt2") {
|
if (command == "mqtt2") {
|
||||||
shell.printfln("Testing MQTT large payloads...");
|
shell.printfln("Testing MQTT large payloads...");
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ namespace emsesp {
|
|||||||
// #define EMSESP_DEBUG_DEFAULT "hpmode"
|
// #define EMSESP_DEBUG_DEFAULT "hpmode"
|
||||||
// #define EMSESP_DEBUG_DEFAULT "shuntingyard"
|
// #define EMSESP_DEBUG_DEFAULT "shuntingyard"
|
||||||
// #define EMSESP_DEBUG_DEFAULT "src"
|
// #define EMSESP_DEBUG_DEFAULT "src"
|
||||||
#define EMSESP_DEBUG_DEFAULT "led"
|
// #define EMSESP_DEBUG_DEFAULT "led"
|
||||||
|
// #define EMSESP_DEBUG_DEFAULT "version"
|
||||||
|
|
||||||
#ifndef EMSESP_DEBUG_DEFAULT
|
#ifndef EMSESP_DEBUG_DEFAULT
|
||||||
#define EMSESP_DEBUG_DEFAULT "general"
|
#define EMSESP_DEBUG_DEFAULT "general"
|
||||||
|
|||||||
458
src/web/WebCommandService.cpp
Normal file
458
src/web/WebCommandService.cpp
Normal file
@@ -0,0 +1,458 @@
|
|||||||
|
/*
|
||||||
|
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||||
|
* Copyright 2020-2025 emsesp.org
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "emsesp.h"
|
||||||
|
#include "WebCommandService.h"
|
||||||
|
|
||||||
|
#include "shuntingYard.h"
|
||||||
|
|
||||||
|
namespace emsesp {
|
||||||
|
|
||||||
|
#ifndef EMSESP_STANDALONE
|
||||||
|
QueueHandle_t WebCommandService::commandQueue_ = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
WebCommandService::WebCommandService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||||
|
: _httpEndpoint(WebCommands::read, WebCommands::update, this, server, EMSESP_COMMANDS_SERVICE_PATH, securityManager, AuthenticationPredicates::IS_AUTHENTICATED)
|
||||||
|
, _fsPersistence(WebCommands::read, WebCommands::update, this, fs, EMSESP_COMMANDS_FILE) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebCommandService::begin() {
|
||||||
|
_fsPersistence.readFromFS();
|
||||||
|
|
||||||
|
EMSESP::webCommandService.read([&](WebCommands & webCommands) { commandItems_ = &webCommands.commandItems; });
|
||||||
|
|
||||||
|
EMSESP::logger().info("Starting Commands service");
|
||||||
|
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||||
|
snprintf(topic, sizeof(topic), "%s/#", F_(commands));
|
||||||
|
Mqtt::subscribe(EMSdevice::DeviceType::COMMAND, topic, nullptr);
|
||||||
|
|
||||||
|
#ifndef EMSESP_STANDALONE
|
||||||
|
// when PSRAM is available, run command execution (potentially blocking, e.g. HTTP) in its own task
|
||||||
|
// so it never stalls the main loop. Without PSRAM we execute inline (see queueCommand()).
|
||||||
|
if (EMSESP::system_.PSram()) {
|
||||||
|
commandQueue_ = xQueueCreate(EMSESP_COMMAND_QUEUE_SIZE, sizeof(CommandJob *));
|
||||||
|
if (commandQueue_ != nullptr) {
|
||||||
|
#if defined(CONFIG_FREERTOS_UNICORE) || (EMSESP_COMMAND_RUNNING_CORE < 0)
|
||||||
|
xTaskCreate((TaskFunction_t)command_task, "command_task", EMSESP_COMMAND_STACKSIZE, NULL, EMSESP_COMMAND_PRIORITY, NULL);
|
||||||
|
#else
|
||||||
|
xTaskCreatePinnedToCore(
|
||||||
|
(TaskFunction_t)command_task, "command_task", EMSESP_COMMAND_STACKSIZE, NULL, EMSESP_COMMAND_PRIORITY, NULL, EMSESP_COMMAND_RUNNING_CORE);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(EMSESP_TEST)
|
||||||
|
load_test_data();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// enqueue a command for the worker task. Fire-and-forget: returns true when the job was queued
|
||||||
|
// (or executed inline when no worker exists). It does NOT report the command's success/failure.
|
||||||
|
bool WebCommandService::queueCommand(const char * name, const char * value) {
|
||||||
|
#ifndef EMSESP_STANDALONE
|
||||||
|
if (commandQueue_ != nullptr) {
|
||||||
|
CommandJob * job = new CommandJob();
|
||||||
|
if (job == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
job->name = name ? name : "";
|
||||||
|
job->has_value = (value != nullptr);
|
||||||
|
job->value = value ? value : "";
|
||||||
|
if (xQueueSend(commandQueue_, &job, 0) != pdPASS) {
|
||||||
|
EMSESP::logger().warning("Command queue full, dropping '%s'", job->name.c_str());
|
||||||
|
delete job;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// no worker task available (no PSRAM or standalone build) - run synchronously
|
||||||
|
return executeCommand(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// true if the command definition is a HTTP/URL command (JSON with a http(s):// url),
|
||||||
|
// i.e. one that will do a blocking TCP/TLS request when executed
|
||||||
|
bool WebCommandService::isUrlCommand(const std::string & command) {
|
||||||
|
JsonDocument doc;
|
||||||
|
if (deserializeJson(doc, command) != DeserializationError::Ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string url = doc["url"] | "";
|
||||||
|
auto lower_url = Helpers::toLower(url.c_str());
|
||||||
|
return lower_url.starts_with("http://") || lower_url.starts_with("https://");
|
||||||
|
}
|
||||||
|
|
||||||
|
// true if a value expression contains an embedded {"url":...} JSON snippet, which compute()
|
||||||
|
// will resolve with a blocking HTTP request. Mirrors the scan compute() does in shuntingYard.cpp
|
||||||
|
bool WebCommandService::valueContainsUrl(const std::string & value) {
|
||||||
|
auto f = value.find_first_of('{');
|
||||||
|
while (f != std::string::npos) {
|
||||||
|
// find the matching closing brace, like compute() does
|
||||||
|
auto e = f + 1;
|
||||||
|
for (uint8_t i = 1; i > 0; e++) {
|
||||||
|
if (e >= value.length()) {
|
||||||
|
return false; // unbalanced braces, compute() will give up too
|
||||||
|
} else if (value[e] == '}') {
|
||||||
|
i--;
|
||||||
|
} else if (value[e] == '{') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JsonDocument doc;
|
||||||
|
if (deserializeJson(doc, value.substr(f, e - f)) == DeserializationError::Ok) {
|
||||||
|
for (JsonPairConst p : doc.as<JsonObjectConst>()) {
|
||||||
|
if (Helpers::toLower(p.key().c_str()) == "url") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f = value.find_first_of('{', e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// smart dispatch: commands that will do a blocking HTTP/TLS request - either a URL command or an
|
||||||
|
// internal command whose value embeds a {url} fetch - are offloaded to the worker task so they
|
||||||
|
// can't stall the caller (main loop for MQTT, async_tcp task for the web API). Everything else
|
||||||
|
// runs synchronously, so the caller still gets the command's real success/failure.
|
||||||
|
// for queued commands the return value only means "dispatched".
|
||||||
|
bool WebCommandService::dispatchCommand(const char * name, const char * value) {
|
||||||
|
#ifndef EMSESP_STANDALONE
|
||||||
|
if (commandQueue_ != nullptr) {
|
||||||
|
const CommandItem * ci = find(name);
|
||||||
|
if (ci != nullptr) {
|
||||||
|
if (isUrlCommand(ci->cmd.c_str())) {
|
||||||
|
return queueCommand(name, value);
|
||||||
|
}
|
||||||
|
// system/message defers evaluation of its value (via the scheduler's raw_value),
|
||||||
|
// so executing it never blocks - keep it synchronous even if the value has a {url}
|
||||||
|
if (Helpers::toLower(ci->cmd.c_str()) != "system/message") {
|
||||||
|
// the effective value is the override if given, else the command's stored default
|
||||||
|
const std::string effective_value = value ? value : std::string(ci->value.c_str());
|
||||||
|
if (valueContainsUrl(effective_value)) {
|
||||||
|
return queueCommand(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return executeCommand(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef EMSESP_STANDALONE
|
||||||
|
// worker task: blocks on the queue and executes each command in turn, off the main loop
|
||||||
|
void WebCommandService::command_task(void * pvParameters) {
|
||||||
|
CommandJob * job = nullptr;
|
||||||
|
while (1) {
|
||||||
|
if (xQueueReceive(commandQueue_, &job, portMAX_DELAY) == pdPASS && job != nullptr) {
|
||||||
|
EMSESP::webCommandService.executeCommand(job->name.c_str(), job->has_value ? job->value.c_str() : nullptr);
|
||||||
|
delete job;
|
||||||
|
job = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void WebCommands::read(WebCommands & webCommands, JsonObject root) {
|
||||||
|
JsonArray items = root["commands"].to<JsonArray>();
|
||||||
|
uint8_t counter = 1;
|
||||||
|
for (const CommandItem & ci : webCommands.commandItems) {
|
||||||
|
JsonObject obj = items.add<JsonObject>();
|
||||||
|
obj["id"] = counter++;
|
||||||
|
obj["cmd"] = ci.cmd;
|
||||||
|
obj["value"] = ci.value;
|
||||||
|
obj["name"] = (const char *)ci.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StateUpdateResult WebCommands::update(JsonObject root, WebCommands & webCommands) {
|
||||||
|
Command::erase_device_commands(EMSdevice::DeviceType::COMMAND);
|
||||||
|
webCommands.commandItems.clear();
|
||||||
|
|
||||||
|
auto items = root["commands"].as<JsonArray>();
|
||||||
|
for (const JsonObject item : items) {
|
||||||
|
auto ci = CommandItem();
|
||||||
|
ci.cmd = item["cmd"].as<std::string>();
|
||||||
|
ci.value = item["value"].as<std::string>();
|
||||||
|
strlcpy(ci.name, item["name"].as<const char *>(), sizeof(ci.name));
|
||||||
|
|
||||||
|
webCommands.commandItems.push_back(ci);
|
||||||
|
Command::add(
|
||||||
|
EMSdevice::DeviceType::COMMAND,
|
||||||
|
webCommands.commandItems.back().name,
|
||||||
|
[name = std::string(webCommands.commandItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||||
|
return EMSESP::webCommandService.dispatchCommand(name.c_str(), value); // value is optional
|
||||||
|
},
|
||||||
|
FL_(command_cmd),
|
||||||
|
CommandFlag::ADMIN_ONLY);
|
||||||
|
}
|
||||||
|
return StateUpdateResult::CHANGED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find a command item by name (case-insensitive)
|
||||||
|
const CommandItem * WebCommandService::find(const char * name) {
|
||||||
|
if (name == nullptr || name[0] == '\0') {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto lower_name = Helpers::toLower(name);
|
||||||
|
for (const CommandItem & ci : *commandItems_) {
|
||||||
|
if (ci.name[0] != '\0' && Helpers::toLower(ci.name) == lower_name) {
|
||||||
|
return &ci;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute a named command — looks up by name and runs it
|
||||||
|
bool WebCommandService::executeCommand(const char * name, const char * value) {
|
||||||
|
const CommandItem * ci = find(name);
|
||||||
|
if (!ci) {
|
||||||
|
EMSESP::logger().warning("Command '%s' not found", name ? name : "");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// if there is a value use it, otherwise use the command's default value
|
||||||
|
std::string cmd_value = value ? value : ci->value.c_str();
|
||||||
|
return executeCommand(ci->name, std::string(ci->cmd.c_str()), cmd_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute a command with explicit cmd and value strings
|
||||||
|
// handles both HTTP URLs (JSON format) and internal API commands
|
||||||
|
bool WebCommandService::executeCommand(const char * name, const std::string & command, const std::string & data) {
|
||||||
|
std::string cmd = Helpers::toLower(command);
|
||||||
|
|
||||||
|
// run the value through the shunting-yard calculator so expressions like "custom/heatcnt + 1"
|
||||||
|
// are resolved (entity references replaced by their values, then computed). Plain values pass
|
||||||
|
// through unchanged. Applies to both URL and internal commands, like the old scheduler code
|
||||||
|
// which computed the value before executing. system/message evaluates its own argument later
|
||||||
|
// (deferred via the scheduler's raw_value), so pre-computing it would run it twice - pass raw.
|
||||||
|
std::string computed_data = data;
|
||||||
|
if (!data.empty() && cmd != "system/message") {
|
||||||
|
computed_data = compute(data);
|
||||||
|
if (computed_data.empty()) {
|
||||||
|
EMSESP::logger().warning("Command '%s': cannot compute value '%s'", name, data.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle HTTP commands (JSON with url/method/value)
|
||||||
|
JsonDocument doc;
|
||||||
|
if (deserializeJson(doc, cmd) == DeserializationError::Ok) {
|
||||||
|
std::string url = doc["url"] | "";
|
||||||
|
auto q = url.find_first_of('?');
|
||||||
|
if (q != std::string::npos) {
|
||||||
|
auto s = url.substr(q + 1);
|
||||||
|
auto l = s.length();
|
||||||
|
commands(s, false);
|
||||||
|
url.replace(q + 1, l, s);
|
||||||
|
}
|
||||||
|
// the cmd's embedded value only gets entity substitution (commands), the passed value is fully computed
|
||||||
|
std::string value = doc["value"] | computed_data;
|
||||||
|
std::string method = doc["method"] | "GET";
|
||||||
|
commands(value, false);
|
||||||
|
auto lower_url = Helpers::toLower(url.c_str());
|
||||||
|
if (lower_url.starts_with("http://") || lower_url.starts_with("https://")) {
|
||||||
|
std::string result;
|
||||||
|
int httpResult = http_request(url, method, value, doc["header"].as<JsonObjectConst>(), result);
|
||||||
|
if (httpResult != 200) {
|
||||||
|
EMSESP::logger().warning("Command '%s': URL command failed with http code %d", name, httpResult);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#if defined(EMSESP_DEBUG)
|
||||||
|
EMSESP::logger().debug("Command '%s': URL '%s' successful with http code %d", name, url.c_str(), httpResult);
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle internal API commands
|
||||||
|
doc.clear();
|
||||||
|
JsonObject input = doc.to<JsonObject>();
|
||||||
|
if (!computed_data.empty()) {
|
||||||
|
input["data"] = computed_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDocument doc_output;
|
||||||
|
JsonObject output = doc_output.to<JsonObject>();
|
||||||
|
|
||||||
|
char command_str[COMMAND_MAX_LENGTH];
|
||||||
|
snprintf(command_str, sizeof(command_str), "/api/%s", cmd.c_str());
|
||||||
|
|
||||||
|
uint8_t return_code = Command::process(command_str, true, input, output);
|
||||||
|
if (return_code == CommandRet::OK) {
|
||||||
|
#if defined(EMSESP_DEBUG)
|
||||||
|
EMSESP::logger().debug("Command '%s' (%s with data '%s') was successful", name, cmd.c_str(), data.c_str());
|
||||||
|
#endif
|
||||||
|
if (data.empty() && output.size()) {
|
||||||
|
Mqtt::queue_publish("response", output);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
char error[100];
|
||||||
|
if (output.size()) {
|
||||||
|
snprintf(error, sizeof(error), "Command '%s': %s", name ? name : "", (const char *)output["message"]);
|
||||||
|
} else {
|
||||||
|
snprintf(error, sizeof(error), "Command '%s': %s failed with error %s", name, cmd.c_str(), Command::return_code_string(return_code));
|
||||||
|
}
|
||||||
|
EMSESP::logger().warning(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebCommandService::get_value_info(JsonObject output, const char * cmd) {
|
||||||
|
if (commandItems_->empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strlen(cmd) || !strcmp(cmd, F_(values)) || !strcmp(cmd, F_(info))) {
|
||||||
|
for (const CommandItem & ci : *commandItems_) {
|
||||||
|
if (ci.name[0] != '\0') {
|
||||||
|
output[(const char *)ci.name] = ci.cmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(cmd, F_(entities))) {
|
||||||
|
for (const CommandItem & ci : *commandItems_) {
|
||||||
|
if (ci.name[0] != '\0') {
|
||||||
|
get_value_json(output[ci.name].to<JsonObject>(), ci);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(cmd, F_(metrics))) {
|
||||||
|
std::string metrics = get_metrics_prometheus();
|
||||||
|
if (!metrics.empty()) {
|
||||||
|
output["api_data"] = metrics;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// look up specific command by name
|
||||||
|
const char * attribute_s = Command::get_attribute(cmd);
|
||||||
|
for (const CommandItem & ci : *commandItems_) {
|
||||||
|
if (Helpers::toLower(ci.name) == cmd) {
|
||||||
|
get_value_json(output, ci);
|
||||||
|
return Command::get_attribute(output, cmd, attribute_s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string WebCommandService::get_metrics_prometheus() {
|
||||||
|
std::string result;
|
||||||
|
result.reserve(commandItems_->size() * 100);
|
||||||
|
for (const CommandItem & ci : *commandItems_) {
|
||||||
|
if (ci.name[0] == '\0') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result += (std::string) "# HELP emsesp_cmd_" + ci.name + " " + ci.name + "\n";
|
||||||
|
result += (std::string) "# TYPE emsesp_cmd_" + ci.name + " gauge\n";
|
||||||
|
result += (std::string) "emsesp_cmd_" + ci.name + " 1\n";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebCommandService::get_value_json(JsonObject output, const CommandItem & ci) {
|
||||||
|
output["name"] = (const char *)ci.name;
|
||||||
|
// output["fullname"] = (const char *)ci.name;
|
||||||
|
// output["type"] = "command";
|
||||||
|
output["command"] = ci.cmd;
|
||||||
|
output["value"] = ci.value;
|
||||||
|
// bool hasName = ci.name[0] != '\0';
|
||||||
|
// output["readable"] = hasName;
|
||||||
|
// output["writeable"] = hasName;
|
||||||
|
// output["visible"] = hasName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebCommandService::publish(const bool force) {
|
||||||
|
if (!Mqtt::enabled() || commandItems_->empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (force && !EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDocument doc(PSRAM_DOC);
|
||||||
|
JsonObject output = doc.to<JsonObject>();
|
||||||
|
for (const CommandItem & ci : *commandItems_) {
|
||||||
|
if (ci.name[0] != '\0' && !output[ci.name].is<JsonVariantConst>()) {
|
||||||
|
output[(const char *)ci.name] = ci.cmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doc.isNull()) {
|
||||||
|
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||||
|
snprintf(topic, sizeof(topic), "%s_data", F_(commands));
|
||||||
|
Mqtt::queue_publish(topic, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t WebCommandService::count_entities() {
|
||||||
|
return static_cast<uint8_t>(commandItems_ ? commandItems_->size() : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(EMSESP_TEST)
|
||||||
|
void WebCommandService::load_test_data() {
|
||||||
|
Command::erase_device_commands(EMSdevice::DeviceType::COMMAND);
|
||||||
|
update([&](WebCommands & webCommands) {
|
||||||
|
webCommands.commandItems.clear();
|
||||||
|
|
||||||
|
auto ci = CommandItem();
|
||||||
|
ci.cmd = "system/fetch";
|
||||||
|
ci.value = "10";
|
||||||
|
strcpy(ci.name, "fetch_values");
|
||||||
|
webCommands.commandItems.push_back(ci);
|
||||||
|
|
||||||
|
ci = CommandItem();
|
||||||
|
ci.cmd = "system/message";
|
||||||
|
ci.value = "hello";
|
||||||
|
strcpy(ci.name, "send_message");
|
||||||
|
webCommands.commandItems.push_back(ci);
|
||||||
|
|
||||||
|
ci = CommandItem();
|
||||||
|
ci.cmd = "system/message";
|
||||||
|
ci.value = "{\"url\":\"http://emsesp.org/versions.json\"}";
|
||||||
|
strcpy(ci.name, "get_versions");
|
||||||
|
webCommands.commandItems.push_back(ci);
|
||||||
|
|
||||||
|
// manually add the commands
|
||||||
|
for (const auto & item : webCommands.commandItems) {
|
||||||
|
if (item.name[0] != '\0') {
|
||||||
|
Command::add(
|
||||||
|
EMSdevice::DeviceType::COMMAND,
|
||||||
|
item.name,
|
||||||
|
[name = std::string(item.name)](const char * value, const int8_t id, JsonObject output) {
|
||||||
|
return EMSESP::webCommandService.dispatchCommand(name.c_str(), value);
|
||||||
|
},
|
||||||
|
FL_(command_cmd),
|
||||||
|
CommandFlag::ADMIN_ONLY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return StateUpdateResult::CHANGED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace emsesp
|
||||||
113
src/web/WebCommandService.h
Normal file
113
src/web/WebCommandService.h
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||||
|
* Copyright 2020-2025 emsesp.org
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <esp32-psram.h>
|
||||||
|
|
||||||
|
#ifndef EMSESP_STANDALONE
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/queue.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WebCommandService_h
|
||||||
|
#define WebCommandService_h
|
||||||
|
|
||||||
|
#define EMSESP_COMMANDS_FILE "/config/emsespCommands.json"
|
||||||
|
#define EMSESP_COMMANDS_SERVICE_PATH "/rest/commands" // GET and POST
|
||||||
|
|
||||||
|
#ifndef EMSESP_COMMAND_RUNNING_CORE
|
||||||
|
#define EMSESP_COMMAND_RUNNING_CORE 1
|
||||||
|
#endif
|
||||||
|
#ifndef EMSESP_COMMAND_STACKSIZE
|
||||||
|
#define EMSESP_COMMAND_STACKSIZE 8192 // needed for TLS
|
||||||
|
#endif
|
||||||
|
#ifndef EMSESP_COMMAND_PRIORITY
|
||||||
|
#define EMSESP_COMMAND_PRIORITY 1
|
||||||
|
#endif
|
||||||
|
#ifndef EMSESP_COMMAND_QUEUE_SIZE
|
||||||
|
#define EMSESP_COMMAND_QUEUE_SIZE 10
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace emsesp {
|
||||||
|
|
||||||
|
class CommandItem {
|
||||||
|
public:
|
||||||
|
stringPSRAM cmd;
|
||||||
|
stringPSRAM value;
|
||||||
|
char name[20];
|
||||||
|
};
|
||||||
|
|
||||||
|
// a single unit of work handed to the command worker task
|
||||||
|
class CommandJob {
|
||||||
|
public:
|
||||||
|
std::string name;
|
||||||
|
std::string value;
|
||||||
|
bool has_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WebCommands {
|
||||||
|
public:
|
||||||
|
std::list<CommandItem, AllocatorPSRAM<CommandItem>> commandItems;
|
||||||
|
|
||||||
|
static void read(WebCommands & webCommands, JsonObject root);
|
||||||
|
static StateUpdateResult update(JsonObject root, WebCommands & webCommands);
|
||||||
|
};
|
||||||
|
|
||||||
|
class WebCommandService : public StatefulService<WebCommands> {
|
||||||
|
public:
|
||||||
|
WebCommandService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||||
|
|
||||||
|
void begin();
|
||||||
|
void publish(const bool force = false);
|
||||||
|
bool get_value_info(JsonObject output, const char * cmd);
|
||||||
|
void get_value_json(JsonObject output, const CommandItem & commandItem);
|
||||||
|
|
||||||
|
bool executeCommand(const char * name, const char * value = nullptr);
|
||||||
|
bool executeCommand(const char * name, const std::string & cmd, const std::string & value);
|
||||||
|
|
||||||
|
bool queueCommand(const char * name, const char * value = nullptr);
|
||||||
|
bool dispatchCommand(const char * name, const char * value = nullptr);
|
||||||
|
|
||||||
|
static bool isUrlCommand(const std::string & command); // true if the command definition is a HTTP/URL command
|
||||||
|
static bool valueContainsUrl(const std::string & value); // true if a value embeds a {"url":...} compute() will fetch
|
||||||
|
|
||||||
|
const CommandItem * find(const char * name);
|
||||||
|
|
||||||
|
uint8_t count_entities();
|
||||||
|
|
||||||
|
std::string get_metrics_prometheus();
|
||||||
|
|
||||||
|
#if defined(EMSESP_TEST)
|
||||||
|
void load_test_data();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifndef EMSESP_STANDALONE
|
||||||
|
static void command_task(void * pvParameters);
|
||||||
|
static QueueHandle_t commandQueue_;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
HttpEndpoint<WebCommands> _httpEndpoint;
|
||||||
|
FSPersistence<WebCommands> _fsPersistence;
|
||||||
|
|
||||||
|
std::list<CommandItem, AllocatorPSRAM<CommandItem>> * commandItems_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace emsesp
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -145,8 +145,8 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
|
|||||||
Command::add(
|
Command::add(
|
||||||
EMSdevice::DeviceType::CUSTOM,
|
EMSdevice::DeviceType::CUSTOM,
|
||||||
webCustomEntity.customEntityItems.back().name,
|
webCustomEntity.customEntityItems.back().name,
|
||||||
[webCustomEntity](const char * value, const int8_t id) {
|
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name);
|
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
|
||||||
},
|
},
|
||||||
FL_(entity_cmd),
|
FL_(entity_cmd),
|
||||||
CommandFlag::ADMIN_ONLY);
|
CommandFlag::ADMIN_ONLY);
|
||||||
@@ -234,7 +234,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
|
|||||||
}
|
}
|
||||||
|
|
||||||
publish_single(entityItem);
|
publish_single(entityItem);
|
||||||
if (EMSESP::mqtt_.get_publish_onchange(0)) {
|
if (EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
|
||||||
publish();
|
publish();
|
||||||
}
|
}
|
||||||
char cmd[COMMAND_MAX_LENGTH];
|
char cmd[COMMAND_MAX_LENGTH];
|
||||||
@@ -415,13 +415,13 @@ std::string WebCustomEntityService::get_metrics_prometheus() {
|
|||||||
|
|
||||||
// build the json for specific entity
|
// build the json for specific entity
|
||||||
void WebCustomEntityService::get_value_json(JsonObject output, CustomEntityItem const & entity) {
|
void WebCustomEntityService::get_value_json(JsonObject output, CustomEntityItem const & entity) {
|
||||||
output["name"] = (const char *)entity.name;
|
output["name"] = (const char *)entity.name;
|
||||||
output["fullname"] = (const char *)entity.name;
|
output["fullname"] = (const char *)entity.name;
|
||||||
output["storage"] = entity.ram == 1 ? "ram" : entity.ram == 2 ? "nvs" : "ems";
|
output["storage"] = entity.ram == 1 ? "ram" : entity.ram == 2 ? "nvs" : "ems";
|
||||||
output["type"] = entity.value_type == DeviceValueType::BOOL ? "boolean" : entity.value_type == DeviceValueType::STRING ? "string" : F_(number);
|
output["type"] = entity.value_type == DeviceValueType::BOOL ? "boolean" : entity.value_type == DeviceValueType::STRING ? "string" : F_(number);
|
||||||
output["readable"] = true;
|
// output["readable"] = true;
|
||||||
output["writeable"] = entity.writeable;
|
output["writeable"] = entity.writeable;
|
||||||
output["visible"] = true;
|
// output["visible"] = true;
|
||||||
|
|
||||||
if (entity.ram == 0) {
|
if (entity.ram == 0) {
|
||||||
output["device_id"] = Helpers::hextoa(entity.device_id);
|
output["device_id"] = Helpers::hextoa(entity.device_id);
|
||||||
@@ -470,7 +470,7 @@ void WebCustomEntityService::publish(const bool force) {
|
|||||||
publish_single(entityItem);
|
publish_single(entityItem);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (!EMSESP::mqtt_.get_publish_onchange(0)) {
|
} else if (!EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
|
||||||
return; // wait for first time period
|
return; // wait for first time period
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -729,7 +729,7 @@ bool WebCustomEntityService::get_value(const std::shared_ptr<const Telegram> & t
|
|||||||
entity.data = data.c_str();
|
entity.data = data.c_str();
|
||||||
if (Mqtt::publish_single()) {
|
if (Mqtt::publish_single()) {
|
||||||
publish_single(entity);
|
publish_single(entity);
|
||||||
} else if (EMSESP::mqtt_.get_publish_onchange(0)) {
|
} else if (EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
|
||||||
has_change = true;
|
has_change = true;
|
||||||
}
|
}
|
||||||
char cmd[COMMAND_MAX_LENGTH];
|
char cmd[COMMAND_MAX_LENGTH];
|
||||||
@@ -751,7 +751,7 @@ bool WebCustomEntityService::get_value(const std::shared_ptr<const Telegram> & t
|
|||||||
entity.value = value;
|
entity.value = value;
|
||||||
if (Mqtt::publish_single()) {
|
if (Mqtt::publish_single()) {
|
||||||
publish_single(entity);
|
publish_single(entity);
|
||||||
} else if (EMSESP::mqtt_.get_publish_onchange(0)) {
|
} else if (EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
|
||||||
has_change = true;
|
has_change = true;
|
||||||
}
|
}
|
||||||
char cmd[COMMAND_MAX_LENGTH];
|
char cmd[COMMAND_MAX_LENGTH];
|
||||||
@@ -796,8 +796,8 @@ void WebCustomEntityService::load_test_data() {
|
|||||||
Command::add(
|
Command::add(
|
||||||
EMSdevice::DeviceType::CUSTOM,
|
EMSdevice::DeviceType::CUSTOM,
|
||||||
webCustomEntity.customEntityItems.back().name,
|
webCustomEntity.customEntityItems.back().name,
|
||||||
[webCustomEntity](const char * value, const int8_t id) {
|
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name);
|
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
|
||||||
},
|
},
|
||||||
FL_(entity_cmd),
|
FL_(entity_cmd),
|
||||||
CommandFlag::ADMIN_ONLY);
|
CommandFlag::ADMIN_ONLY);
|
||||||
@@ -832,8 +832,8 @@ void WebCustomEntityService::load_test_data() {
|
|||||||
Command::add(
|
Command::add(
|
||||||
EMSdevice::DeviceType::CUSTOM,
|
EMSdevice::DeviceType::CUSTOM,
|
||||||
webCustomEntity.customEntityItems.back().name,
|
webCustomEntity.customEntityItems.back().name,
|
||||||
[webCustomEntity](const char * value, const int8_t id) {
|
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name);
|
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
|
||||||
},
|
},
|
||||||
FL_(entity_cmd),
|
FL_(entity_cmd),
|
||||||
CommandFlag::ADMIN_ONLY);
|
CommandFlag::ADMIN_ONLY);
|
||||||
@@ -855,8 +855,8 @@ void WebCustomEntityService::load_test_data() {
|
|||||||
Command::add(
|
Command::add(
|
||||||
EMSdevice::DeviceType::CUSTOM,
|
EMSdevice::DeviceType::CUSTOM,
|
||||||
webCustomEntity.customEntityItems.back().name,
|
webCustomEntity.customEntityItems.back().name,
|
||||||
[webCustomEntity](const char * value, const int8_t id) {
|
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name);
|
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
|
||||||
},
|
},
|
||||||
FL_(entity_cmd),
|
FL_(entity_cmd),
|
||||||
CommandFlag::ADMIN_ONLY);
|
CommandFlag::ADMIN_ONLY);
|
||||||
|
|||||||
@@ -250,6 +250,9 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar
|
|||||||
case EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID:
|
case EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID:
|
||||||
device_type = EMSdevice::DeviceType::SCHEDULER;
|
device_type = EMSdevice::DeviceType::SCHEDULER;
|
||||||
break;
|
break;
|
||||||
|
case EMSdevice::DeviceTypeUniqueID::COMMAND_UID:
|
||||||
|
device_type = EMSdevice::DeviceType::COMMAND;
|
||||||
|
break;
|
||||||
case EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID:
|
case EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID:
|
||||||
device_type = EMSdevice::DeviceType::TEMPERATURESENSOR;
|
device_type = EMSdevice::DeviceType::TEMPERATURESENSOR;
|
||||||
break;
|
break;
|
||||||
@@ -478,11 +481,11 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// show scheduler items
|
// show scheduler items (active state toggles)
|
||||||
if (EMSESP::webSchedulerService.count_entities()) {
|
if (EMSESP::webSchedulerService.count_entities()) {
|
||||||
JsonObject obj = nodes.add<JsonObject>();
|
JsonObject obj = nodes.add<JsonObject>();
|
||||||
obj["id"] = EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID; // it's unique id
|
obj["id"] = EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID;
|
||||||
obj["t"] = EMSdevice::DeviceType::SCHEDULER; // device type number
|
obj["t"] = EMSdevice::DeviceType::SCHEDULER;
|
||||||
JsonArray nodes = obj["nodes"].to<JsonArray>();
|
JsonArray nodes = obj["nodes"].to<JsonArray>();
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
|
|
||||||
@@ -495,14 +498,31 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
|
|||||||
dv["id"] = std::string("00") + scheduleItem.name;
|
dv["id"] = std::string("00") + scheduleItem.name;
|
||||||
dv["c"] = scheduleItem.name;
|
dv["c"] = scheduleItem.name;
|
||||||
|
|
||||||
// for immediate schedules, we don't show the active/inactive state or on/off options
|
char s[12];
|
||||||
if (scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
|
dv["v"] = Helpers::render_boolean(s, scheduleItem.active, true);
|
||||||
char s[12];
|
JsonArray l = dv["l"].to<JsonArray>();
|
||||||
dv["v"] = Helpers::render_boolean(s, scheduleItem.active, true);
|
l.add(Helpers::render_boolean(s, false, true));
|
||||||
JsonArray l = dv["l"].to<JsonArray>();
|
l.add(Helpers::render_boolean(s, true, true));
|
||||||
l.add(Helpers::render_boolean(s, false, true)); // False option
|
}
|
||||||
l.add(Helpers::render_boolean(s, true, true)); // True option
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// show command items (executable from dashboard)
|
||||||
|
if (EMSESP::webCommandService.count_entities()) {
|
||||||
|
JsonObject obj = nodes.add<JsonObject>();
|
||||||
|
obj["id"] = EMSdevice::DeviceTypeUniqueID::COMMAND_UID;
|
||||||
|
obj["t"] = EMSdevice::DeviceType::COMMAND;
|
||||||
|
JsonArray nodes = obj["nodes"].to<JsonArray>();
|
||||||
|
uint8_t count = 0;
|
||||||
|
|
||||||
|
EMSESP::webCommandService.read([&](const WebCommands & webCommands) {
|
||||||
|
for (const CommandItem & ci : webCommands.commandItems) {
|
||||||
|
JsonObject node = nodes.add<JsonObject>();
|
||||||
|
node["id"] = (EMSdevice::DeviceTypeUniqueID::COMMAND_UID * 100) + count++;
|
||||||
|
|
||||||
|
JsonObject dv = node["dv"].to<JsonObject>();
|
||||||
|
dv["id"] = std::string("00") + ci.name;
|
||||||
|
dv["c"] = ci.name;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,15 +39,9 @@ void WebSchedulerService::begin() {
|
|||||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||||
snprintf(topic, sizeof(topic), "%s/#", F_(scheduler));
|
snprintf(topic, sizeof(topic), "%s/#", F_(scheduler));
|
||||||
Mqtt::subscribe(EMSdevice::DeviceType::SCHEDULER, topic, nullptr); // use empty function callback
|
Mqtt::subscribe(EMSdevice::DeviceType::SCHEDULER, topic, nullptr); // use empty function callback
|
||||||
#ifndef EMSESP_STANDALONE
|
|
||||||
if (EMSESP::system_.PSram()) {
|
#if defined(EMSESP_TEST)
|
||||||
#if defined(CONFIG_FREERTOS_UNICORE) || (EMSESP_SCHEDULER_RUNNING_CORE < 0)
|
load_test_data();
|
||||||
xTaskCreate((TaskFunction_t)scheduler_task, "scheduler_task", EMSESP_SCHEDULER_STACKSIZE, NULL, EMSESP_SCHEDULER_PRIORITY, NULL);
|
|
||||||
#else
|
|
||||||
xTaskCreatePinnedToCore(
|
|
||||||
(TaskFunction_t)scheduler_task, "scheduler_task", EMSESP_SCHEDULER_STACKSIZE, NULL, EMSESP_SCHEDULER_PRIORITY, NULL, EMSESP_SCHEDULER_RUNNING_CORE);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,21 +51,18 @@ void WebScheduler::read(WebScheduler & webScheduler, JsonObject root) {
|
|||||||
JsonArray schedule = root["schedule"].to<JsonArray>();
|
JsonArray schedule = root["schedule"].to<JsonArray>();
|
||||||
uint8_t counter = 1;
|
uint8_t counter = 1;
|
||||||
for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
|
for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
|
||||||
JsonObject si = schedule.add<JsonObject>();
|
JsonObject si = schedule.add<JsonObject>();
|
||||||
si["id"] = counter++; // id is only used to render the table and must be unique. 0 is for Dashboard
|
si["id"] = counter++;
|
||||||
si["flags"] = scheduleItem.flags;
|
si["flags"] = scheduleItem.flags;
|
||||||
si["active"] = scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? scheduleItem.active : false;
|
si["active"] = scheduleItem.active;
|
||||||
si["time"] = scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? scheduleItem.time : "";
|
si["time"] = scheduleItem.time;
|
||||||
si["cmd"] = scheduleItem.cmd;
|
si["cmd_name"] = scheduleItem.cmd_name;
|
||||||
si["value"] = scheduleItem.value;
|
si["name"] = (const char *)scheduleItem.name;
|
||||||
si["name"] = (const char *)scheduleItem.name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// call on initialization and also when the Schedule web page is saved
|
// call on initialization and also when the Schedule web page is saved
|
||||||
// this loads the data into the internal class
|
|
||||||
StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webScheduler) {
|
StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webScheduler) {
|
||||||
// reset the list
|
|
||||||
Command::erase_device_commands(EMSdevice::DeviceType::SCHEDULER);
|
Command::erase_device_commands(EMSdevice::DeviceType::SCHEDULER);
|
||||||
for (ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
|
for (ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
|
||||||
char key[sizeof(scheduleItem.name) + 2];
|
char key[sizeof(scheduleItem.name) + 2];
|
||||||
@@ -83,44 +74,41 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
|
|||||||
webScheduler.scheduleItems.clear();
|
webScheduler.scheduleItems.clear();
|
||||||
EMSESP::webSchedulerService.ha_reset();
|
EMSESP::webSchedulerService.ha_reset();
|
||||||
|
|
||||||
// build up the list of schedule items
|
|
||||||
auto scheduleItems = root["schedule"].as<JsonArray>();
|
auto scheduleItems = root["schedule"].as<JsonArray>();
|
||||||
for (const JsonObject schedule : scheduleItems) {
|
for (const JsonObject schedule : scheduleItems) {
|
||||||
// create each schedule item, overwriting any previous settings
|
// create each schedule item, overwriting any previous settings
|
||||||
// ignore the id (as this is only used in the web for table rendering)
|
// ignore the id (as this is only used in the web for table rendering)
|
||||||
auto si = ScheduleItem();
|
auto si = ScheduleItem();
|
||||||
si.active = schedule["active"];
|
si.active = schedule["active"];
|
||||||
si.flags = schedule["flags"];
|
si.flags = schedule["flags"];
|
||||||
si.time = si.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? "" : schedule["time"].as<std::string>();
|
si.time = schedule["time"].as<std::string>();
|
||||||
si.cmd = schedule["cmd"].as<std::string>();
|
si.cmd_name = schedule["cmd_name"].as<std::string>();
|
||||||
si.value = schedule["value"].as<std::string>();
|
|
||||||
strlcpy(si.name, schedule["name"].as<const char *>(), sizeof(si.name));
|
strlcpy(si.name, schedule["name"].as<const char *>(), sizeof(si.name));
|
||||||
|
|
||||||
// calculated elapsed minutes
|
// calculated elapsed minutes
|
||||||
si.elapsed_min = Helpers::string2minutes(si.time.c_str());
|
si.elapsed_min = Helpers::string2minutes(si.time.c_str());
|
||||||
si.retry_cnt = 0xFF; // no startup retries
|
si.retry_cnt = 0xFF;
|
||||||
|
|
||||||
webScheduler.scheduleItems.push_back(si); // add to list
|
webScheduler.scheduleItems.push_back(si);
|
||||||
if (webScheduler.scheduleItems.back().name[0] != '\0') {
|
char key[sizeof(webScheduler.scheduleItems.back().name) + 2];
|
||||||
char key[sizeof(webScheduler.scheduleItems.back().name) + 2];
|
snprintf(key, sizeof(key), "s:%s", webScheduler.scheduleItems.back().name);
|
||||||
snprintf(key, sizeof(key), "s:%s", webScheduler.scheduleItems.back().name);
|
if (EMSESP::nvs_.isKey(key)) {
|
||||||
if (EMSESP::nvs_.isKey(key) && webScheduler.scheduleItems.back().flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
|
webScheduler.scheduleItems.back().active = EMSESP::nvs_.getBool(key);
|
||||||
webScheduler.scheduleItems.back().active = EMSESP::nvs_.getBool(key);
|
|
||||||
}
|
|
||||||
Command::add(
|
|
||||||
EMSdevice::DeviceType::SCHEDULER,
|
|
||||||
webScheduler.scheduleItems.back().name,
|
|
||||||
[webScheduler](const char * value, const int8_t id) {
|
|
||||||
return EMSESP::webSchedulerService.command_setvalue(value, id, webScheduler.scheduleItems.back().name);
|
|
||||||
},
|
|
||||||
FL_(schedule_cmd),
|
|
||||||
CommandFlag::ADMIN_ONLY);
|
|
||||||
}
|
}
|
||||||
|
Command::add(
|
||||||
|
EMSdevice::DeviceType::SCHEDULER,
|
||||||
|
webScheduler.scheduleItems.back().name,
|
||||||
|
[name = std::string(webScheduler.scheduleItems.back().name)](const char * value, const int8_t id, JsonObject output) {
|
||||||
|
return EMSESP::webSchedulerService.command_setvalue(value, id, name.c_str());
|
||||||
|
},
|
||||||
|
FL_(schedule_cmd),
|
||||||
|
CommandFlag::ADMIN_ONLY);
|
||||||
}
|
}
|
||||||
return StateUpdateResult::CHANGED;
|
return StateUpdateResult::CHANGED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set active by api command
|
// set active by api command
|
||||||
|
// value is a boolean to enable/disable the schedule item
|
||||||
bool WebSchedulerService::command_setvalue(const char * value, const int8_t id, const char * name) {
|
bool WebSchedulerService::command_setvalue(const char * value, const int8_t id, const char * name) {
|
||||||
bool v;
|
bool v;
|
||||||
if (!Helpers::value2bool(value, v)) {
|
if (!Helpers::value2bool(value, v)) {
|
||||||
@@ -136,16 +124,13 @@ bool WebSchedulerService::command_setvalue(const char * value, const int8_t id,
|
|||||||
scheduleItem.active = v;
|
scheduleItem.active = v;
|
||||||
publish_single(name, v);
|
publish_single(name, v);
|
||||||
|
|
||||||
if (EMSESP::mqtt_.get_publish_onchange(0)) {
|
if (EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
|
||||||
publish();
|
publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
// save new state to nvs #2946
|
char key[sizeof(scheduleItem.name) + 2];
|
||||||
if (scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
|
snprintf(key, sizeof(key), "s:%s", scheduleItem.name);
|
||||||
char key[sizeof(scheduleItem.name) + 2];
|
EMSESP::nvs_.putBool(key, scheduleItem.active);
|
||||||
snprintf(key, sizeof(key), "s:%s", scheduleItem.name);
|
|
||||||
EMSESP::nvs_.putBool(key, scheduleItem.active);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,22 +203,21 @@ void WebSchedulerService::get_value_json(JsonObject output, const ScheduleItem &
|
|||||||
output["name"] = (const char *)scheduleItem.name;
|
output["name"] = (const char *)scheduleItem.name;
|
||||||
output["fullname"] = (const char *)scheduleItem.name;
|
output["fullname"] = (const char *)scheduleItem.name;
|
||||||
output["type"] = "boolean";
|
output["type"] = "boolean";
|
||||||
Mqtt::add_value_bool(output, "value", scheduleItem.active);
|
Mqtt::add_value_bool(output, "active", scheduleItem.active);
|
||||||
if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
||||||
output["condition"] = scheduleItem.time;
|
output["condition"] = scheduleItem.time;
|
||||||
} else if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE) {
|
} else if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE) {
|
||||||
output["onchange"] = scheduleItem.time;
|
output["onchange"] = scheduleItem.time;
|
||||||
} else if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER) {
|
} else if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER) {
|
||||||
output["timer"] = scheduleItem.time;
|
output["timer"] = scheduleItem.time;
|
||||||
} else if (scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
|
} else {
|
||||||
output["time"] = scheduleItem.time;
|
output["time"] = scheduleItem.time;
|
||||||
}
|
}
|
||||||
output["command"] = scheduleItem.cmd;
|
output["cmd_name"] = scheduleItem.cmd_name;
|
||||||
output["cmd_data"] = scheduleItem.value;
|
// bool hasName = scheduleItem.name[0] != '\0';
|
||||||
bool hasName = scheduleItem.name[0] != '\0';
|
// output["readable"] = hasName;
|
||||||
output["readable"] = hasName;
|
// output["writeable"] = hasName;
|
||||||
output["writeable"] = hasName;
|
// output["visible"] = hasName;
|
||||||
output["visible"] = hasName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// publish single value
|
// publish single value
|
||||||
@@ -264,7 +248,7 @@ void WebSchedulerService::publish(const bool force) {
|
|||||||
publish_single(scheduleItem.name, scheduleItem.active);
|
publish_single(scheduleItem.name, scheduleItem.active);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (!EMSESP::mqtt_.get_publish_onchange(0)) {
|
} else if (!EMSESP::mqtt_.get_publish_onchange(EMSdevice::DeviceType::SYSTEM)) {
|
||||||
return; // wait for first time period
|
return; // wait for first time period
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,7 +274,6 @@ void WebSchedulerService::publish(const bool force) {
|
|||||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", scheduleItem.name);
|
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", scheduleItem.name);
|
||||||
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
||||||
|
|
||||||
// Optimized: use stack buffer instead of string concatenation to avoid heap allocations
|
|
||||||
char val_tpl[150];
|
char val_tpl[150];
|
||||||
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
|
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
|
||||||
snprintf(val_tpl, sizeof(val_tpl), "{{%s if %s}}", val_obj, val_cond);
|
snprintf(val_tpl, sizeof(val_tpl), "{{%s if %s}}", val_obj, val_cond);
|
||||||
@@ -304,7 +287,7 @@ void WebSchedulerService::publish(const bool force) {
|
|||||||
|
|
||||||
config["uniq_id"] = uniq_s;
|
config["uniq_id"] = uniq_s;
|
||||||
config["name"] = (const char *)scheduleItem.name;
|
config["name"] = (const char *)scheduleItem.name;
|
||||||
// Optimized: use stack buffer instead of string concatenation
|
|
||||||
char def_ent_id[80];
|
char def_ent_id[80];
|
||||||
snprintf(def_ent_id, sizeof(def_ent_id), "switch.%s", uniq_s);
|
snprintf(def_ent_id, sizeof(def_ent_id), "switch.%s", uniq_s);
|
||||||
config["def_ent_id"] = def_ent_id;
|
config["def_ent_id"] = def_ent_id;
|
||||||
@@ -339,84 +322,17 @@ uint8_t WebSchedulerService::count_entities() {
|
|||||||
return static_cast<uint8_t>(scheduleItems_ ? scheduleItems_->size() : 0);
|
return static_cast<uint8_t>(scheduleItems_ ? scheduleItems_->size() : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute scheduled command
|
// execute the command associated with a schedule item
|
||||||
// return true if successful, false if not
|
// looks up the named command in WebCommandService and runs it
|
||||||
bool WebSchedulerService::command(const char * name, const std::string & command, const std::string & data) {
|
bool WebSchedulerService::runScheduleCommand(const ScheduleItem & si) {
|
||||||
std::string cmd = Helpers::toLower(command);
|
if (si.cmd_name.empty()) {
|
||||||
|
EMSESP::logger().warning("Schedule '%s': no command assigned", si.name);
|
||||||
// check http commands. e.g.
|
return false;
|
||||||
// tasmota(get): http://<tasmotaIP>/cm?cmnd=power%20ON
|
|
||||||
// shelly(get): http://<shellyIP>/relais/0?turn=on
|
|
||||||
// parse json
|
|
||||||
JsonDocument doc;
|
|
||||||
if (deserializeJson(doc, cmd) == DeserializationError::Ok) {
|
|
||||||
std::string url = doc["url"] | "";
|
|
||||||
// for a GET with parameters replace commands with values
|
|
||||||
// don't search the complete url, it may contain a devicename in path
|
|
||||||
auto q = url.find_first_of('?');
|
|
||||||
if (q != std::string::npos) {
|
|
||||||
auto s = url.substr(q + 1); // copy only parameters
|
|
||||||
auto l = s.length();
|
|
||||||
commands(s, false);
|
|
||||||
url.replace(q + 1, l, s);
|
|
||||||
}
|
|
||||||
std::string value = doc["value"] | data; // extract value if its in the command, or take the data
|
|
||||||
std::string method = doc["method"] | "GET"; // default GET
|
|
||||||
commands(value, false);
|
|
||||||
auto lower_url = Helpers::toLower(url.c_str());
|
|
||||||
if (lower_url.starts_with("http://") || lower_url.starts_with("https://")) {
|
|
||||||
std::string result;
|
|
||||||
int httpResult = http_request(url, method, value, doc["header"].as<JsonObjectConst>(), result);
|
|
||||||
if (httpResult != 200) {
|
|
||||||
EMSESP::logger().warning("Schedule '%s': URL command failed with http code %d", name, httpResult);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#if defined(EMSESP_DEBUG)
|
|
||||||
EMSESP::logger().debug("Schedule %s: URL '%s' command successful with http code %d", name, url.c_str(), httpResult);
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// we can add other json tests here
|
|
||||||
}
|
}
|
||||||
|
return EMSESP::webCommandService.dispatchCommand(si.cmd_name.c_str());
|
||||||
doc.clear();
|
|
||||||
JsonObject input = doc.to<JsonObject>();
|
|
||||||
if (!data.empty()) { // empty data queries a value
|
|
||||||
input["data"] = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonDocument doc_output; // only for commands without output
|
|
||||||
JsonObject output = doc_output.to<JsonObject>();
|
|
||||||
|
|
||||||
// prefix "api/" to command string
|
|
||||||
char command_str[COMMAND_MAX_LENGTH];
|
|
||||||
snprintf(command_str, sizeof(command_str), "/api/%s", cmd.c_str());
|
|
||||||
|
|
||||||
uint8_t return_code = Command::process(command_str, true, input, output); // admin set
|
|
||||||
if (return_code == CommandRet::OK) {
|
|
||||||
#if defined(EMSESP_DEBUG)
|
|
||||||
EMSESP::logger().debug("Schedule command '%s' with data '%s' was successful", cmd.c_str(), data.c_str());
|
|
||||||
#endif
|
|
||||||
if (data.empty() && output.size()) {
|
|
||||||
Mqtt::queue_publish("response", output);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
char error[100];
|
|
||||||
if (output.size()) {
|
|
||||||
// check for empty name
|
|
||||||
snprintf(error, sizeof(error), "Schedule %s: %s", name ? name : "", (const char *)output["message"]); // use error message if we have it
|
|
||||||
} else {
|
|
||||||
snprintf(error, sizeof(error), "Schedule %s: command %s failed with error %s", name, cmd.c_str(), Command::return_code_string(return_code));
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSESP::logger().warning(error);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// called from emsesp.cpp on every entity-change
|
// queue schedules to be handled executed in WebSchedulerService::loop() called from emsesp.cpp
|
||||||
// queue schedules to be handled executed in scheduler-loop
|
|
||||||
bool WebSchedulerService::onChange(const char * cmd) {
|
bool WebSchedulerService::onChange(const char * cmd) {
|
||||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE && Helpers::toLower(scheduleItem.time.c_str()) == Helpers::toLower(cmd)) {
|
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE && Helpers::toLower(scheduleItem.time.c_str()) == Helpers::toLower(cmd)) {
|
||||||
@@ -427,31 +343,16 @@ bool WebSchedulerService::onChange(const char * cmd) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// system/message evaluates its own argument later (deferred via raw_value, computed in loop()),
|
|
||||||
// so pre-computing it here would make any {url} or expression inside it run twice. Pass
|
|
||||||
// system/message its value raw; compute() everything else as before.
|
|
||||||
// templated because ScheduleItem's strings use a PSRAM allocator, not std::string.
|
|
||||||
template <typename C, typename V>
|
|
||||||
static std::string compute_cmd_value(const C & cmd, const V & value) {
|
|
||||||
if (Helpers::toLower(cmd.c_str()) == "system/message") {
|
|
||||||
return std::string(value.c_str());
|
|
||||||
}
|
|
||||||
return compute(value.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle condition schedules, parse string stored in schedule.time field
|
// handle condition schedules, parse string stored in schedule.time field
|
||||||
void WebSchedulerService::condition() {
|
void WebSchedulerService::condition() {
|
||||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
||||||
auto match = compute(scheduleItem.time.c_str());
|
auto match = compute(scheduleItem.time.c_str());
|
||||||
#ifdef EMESESP_DEBUG
|
|
||||||
// EMSESP::logger().debug("condition match: %s", match.c_str());
|
|
||||||
#endif
|
|
||||||
if (match.length() == 1 && match[0] == '1' && scheduleItem.retry_cnt == 0xFF) {
|
if (match.length() == 1 && match[0] == '1' && scheduleItem.retry_cnt == 0xFF) {
|
||||||
scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value)) ? 1 : 0xFF;
|
scheduleItem.retry_cnt = runScheduleCommand(scheduleItem) ? 1 : 0xFF;
|
||||||
} else if (match.length() == 1 && match[0] == '0' && scheduleItem.retry_cnt == 1) {
|
} else if (match.length() == 1 && match[0] == '0' && scheduleItem.retry_cnt == 1) {
|
||||||
scheduleItem.retry_cnt = 0xFF;
|
scheduleItem.retry_cnt = 0xFF;
|
||||||
} else if (match.length() != 1) { // the match is not boolean
|
} else if (match.length() != 1) {
|
||||||
#if defined(EMSESP_DEBUG)
|
#if defined(EMSESP_DEBUG)
|
||||||
EMSESP::logger().debug("condition result: %s", match.c_str());
|
EMSESP::logger().debug("condition result: %s", match.c_str());
|
||||||
#endif
|
#endif
|
||||||
@@ -462,17 +363,15 @@ void WebSchedulerService::condition() {
|
|||||||
|
|
||||||
// process any scheduled jobs
|
// process any scheduled jobs
|
||||||
void WebSchedulerService::loop() {
|
void WebSchedulerService::loop() {
|
||||||
// initialize static value on startup
|
|
||||||
static int8_t last_tm_min = -2; // invalid value also used for startup commands
|
static int8_t last_tm_min = -2; // invalid value also used for startup commands
|
||||||
static uint32_t last_uptime_min = 0;
|
static uint32_t last_uptime_min = 0;
|
||||||
static uint32_t last_uptime_sec = 0;
|
static uint32_t last_uptime_sec = 0;
|
||||||
|
|
||||||
if (!raw_value.empty()) { // process a value from system/message command
|
if (!raw_value.empty()) {
|
||||||
computed_value = compute(raw_value);
|
computed_value = compute(raw_value);
|
||||||
raw_value.clear();
|
raw_value.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// get list of scheduler events and exit if it's empty
|
|
||||||
if (scheduleItems_->empty()) {
|
if (scheduleItems_->empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -480,21 +379,10 @@ void WebSchedulerService::loop() {
|
|||||||
// check if we have onChange events
|
// check if we have onChange events
|
||||||
while (!cmd_changed_.empty()) {
|
while (!cmd_changed_.empty()) {
|
||||||
ScheduleItem si = *cmd_changed_.front();
|
ScheduleItem si = *cmd_changed_.front();
|
||||||
command(si.name, si.cmd.c_str(), compute_cmd_value(si.cmd, si.value));
|
runScheduleCommand(si);
|
||||||
cmd_changed_.pop_front();
|
cmd_changed_.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
|
|
||||||
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value));
|
|
||||||
scheduleItem.active = false;
|
|
||||||
publish_single(scheduleItem.name, false);
|
|
||||||
if (EMSESP::mqtt_.get_publish_onchange(0)) {
|
|
||||||
publish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check conditions every 10 seconds, start after one minute
|
// check conditions every 10 seconds, start after one minute
|
||||||
uint32_t uptime_sec = uuid::get_uptime_sec() / 10;
|
uint32_t uptime_sec = uuid::get_uptime_sec() / 10;
|
||||||
if (last_uptime_sec != uptime_sec && uptime_sec > 5) {
|
if (last_uptime_sec != uptime_sec && uptime_sec > 5) {
|
||||||
@@ -506,106 +394,87 @@ void WebSchedulerService::loop() {
|
|||||||
if (last_tm_min == -2) {
|
if (last_tm_min == -2) {
|
||||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) {
|
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) {
|
||||||
scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value)) ? 0xFF : 0;
|
scheduleItem.retry_cnt = runScheduleCommand(scheduleItem) ? 0xFF : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_tm_min = -1; // startup done, now use for RTC
|
last_tm_min = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check timer every minute, sync to EMS-ESP clock
|
// check timer every minute, sync to EMS-ESP clock
|
||||||
uint32_t uptime_min = uuid::get_uptime_sec() / 60;
|
uint32_t uptime_min = uuid::get_uptime_sec() / 60;
|
||||||
if (last_uptime_min != uptime_min) {
|
if (last_uptime_min != uptime_min) {
|
||||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
// retry startup commands not yet executed
|
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0
|
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0
|
||||||
&& scheduleItem.retry_cnt < MAX_STARTUP_RETRIES) {
|
&& scheduleItem.retry_cnt < MAX_STARTUP_RETRIES) {
|
||||||
scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), scheduleItem.value.c_str()) ? 0xFF : scheduleItem.retry_cnt + 1;
|
scheduleItem.retry_cnt = runScheduleCommand(scheduleItem) ? 0xFF : scheduleItem.retry_cnt + 1;
|
||||||
}
|
}
|
||||||
// scheduled timer commands
|
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0
|
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0
|
||||||
&& (uptime_min % scheduleItem.elapsed_min == 0)) {
|
&& (uptime_min % scheduleItem.elapsed_min == 0)) {
|
||||||
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value));
|
runScheduleCommand(scheduleItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_uptime_min = uptime_min;
|
last_uptime_min = uptime_min;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check calender, sync to RTC, only execute if year is valid
|
// check calendar, sync to RTC, only execute if year is valid
|
||||||
time_t now = time(nullptr);
|
time_t now = time(nullptr);
|
||||||
tm * tm = localtime(&now);
|
tm * tm = localtime(&now);
|
||||||
if (tm->tm_min != last_tm_min && tm->tm_year > 120) {
|
if (tm->tm_min != last_tm_min && tm->tm_year > 120) {
|
||||||
// find the real dow and minute from RTC
|
uint8_t real_dow = 1 << tm->tm_wday;
|
||||||
uint8_t real_dow = 1 << tm->tm_wday; // 1 is Sunday
|
|
||||||
uint16_t real_min = tm->tm_hour * 60 + tm->tm_min;
|
uint16_t real_min = tm->tm_hour * 60 + tm->tm_min;
|
||||||
|
|
||||||
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
uint8_t dow = scheduleItem.flags & SCHEDULEFLAG_SCHEDULE_TIMER ? 0 : scheduleItem.flags;
|
uint8_t dow = scheduleItem.flags & SCHEDULEFLAG_SCHEDULE_TIMER ? 0 : scheduleItem.flags;
|
||||||
if (scheduleItem.active && (real_dow & dow) && real_min == scheduleItem.elapsed_min) {
|
if (scheduleItem.active && (real_dow & dow) && real_min == scheduleItem.elapsed_min) {
|
||||||
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value));
|
runScheduleCommand(scheduleItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_tm_min = tm->tm_min;
|
last_tm_min = tm->tm_min;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute a schedule item immediately
|
|
||||||
bool WebSchedulerService::executeSchedule(const char * name) {
|
|
||||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
|
||||||
if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE && strcmp(scheduleItem.name, name) == 0) {
|
|
||||||
EMSESP::logger().info("Executing schedule '%s'", name);
|
|
||||||
return command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EMSESP::logger().warning("Schedule '%s' not found", name);
|
|
||||||
return false; // not found
|
|
||||||
}
|
|
||||||
|
|
||||||
// process schedules async
|
|
||||||
void WebSchedulerService::scheduler_task(void * pvParameters) {
|
|
||||||
while (1) {
|
|
||||||
delay(10); // no need to hurry
|
|
||||||
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_NORMAL) {
|
|
||||||
EMSESP::webSchedulerService.loop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifndef EMSESP_STANDALONE
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// hard coded tests
|
|
||||||
#if defined(EMSESP_TEST)
|
#if defined(EMSESP_TEST)
|
||||||
void WebSchedulerService::load_test_data() {
|
void WebSchedulerService::load_test_data() {
|
||||||
|
Command::erase_device_commands(EMSdevice::DeviceType::SCHEDULER);
|
||||||
update([&](WebScheduler & webScheduler) {
|
update([&](WebScheduler & webScheduler) {
|
||||||
webScheduler.scheduleItems.clear(); // delete all existing schedules
|
webScheduler.scheduleItems.clear();
|
||||||
|
|
||||||
// test 1
|
auto si = ScheduleItem();
|
||||||
auto si = ScheduleItem();
|
si.active = true;
|
||||||
si.active = true;
|
si.flags = 1; // day schedule
|
||||||
si.flags = 1; // day schedule
|
si.time = "12:00";
|
||||||
si.time = "12:00";
|
si.cmd_name = "fetch_values";
|
||||||
si.cmd = "system/fetch";
|
|
||||||
si.value = "10";
|
|
||||||
strcpy(si.name, "test_scheduler1");
|
strcpy(si.name, "test_scheduler1");
|
||||||
si.elapsed_min = 0;
|
si.elapsed_min = 0;
|
||||||
si.retry_cnt = 0xFF; // no startup retries
|
si.retry_cnt = 0xFF;
|
||||||
|
|
||||||
webScheduler.scheduleItems.push_back(si);
|
webScheduler.scheduleItems.push_back(si);
|
||||||
|
|
||||||
// test 2
|
si = ScheduleItem();
|
||||||
si = ScheduleItem();
|
si.active = true;
|
||||||
si.active = false;
|
si.flags = SCHEDULEFLAG_SCHEDULE_TIMER;
|
||||||
si.flags = SCHEDULEFLAG_SCHEDULE_IMMEDIATE; // immediate
|
si.time = "01:00";
|
||||||
si.time = "13:00";
|
si.cmd_name = "send_message";
|
||||||
si.cmd = "system/message";
|
strcpy(si.name, "test_scheduler2");
|
||||||
si.value = "20";
|
si.elapsed_min = 60;
|
||||||
strcpy(si.name, "test_scheduler2"); // to make sure its excluded from Dashboard
|
si.retry_cnt = 0xFF;
|
||||||
si.elapsed_min = 0;
|
|
||||||
si.retry_cnt = 0xFF; // no startup retries
|
|
||||||
|
|
||||||
webScheduler.scheduleItems.push_back(si);
|
webScheduler.scheduleItems.push_back(si);
|
||||||
|
|
||||||
return StateUpdateResult::CHANGED; // persist the changes
|
for (const auto & item : webScheduler.scheduleItems) {
|
||||||
|
if (item.name[0] != '\0') {
|
||||||
|
Command::add(
|
||||||
|
EMSdevice::DeviceType::SCHEDULER,
|
||||||
|
item.name,
|
||||||
|
[name = std::string(item.name)](const char * value, const int8_t id, JsonObject output) {
|
||||||
|
return EMSESP::webSchedulerService.command_setvalue(value, id, name.c_str());
|
||||||
|
},
|
||||||
|
FL_(schedule_cmd),
|
||||||
|
CommandFlag::ADMIN_ONLY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return StateUpdateResult::CHANGED;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -24,28 +24,14 @@
|
|||||||
#define EMSESP_SCHEDULER_FILE "/config/emsespScheduler.json"
|
#define EMSESP_SCHEDULER_FILE "/config/emsespScheduler.json"
|
||||||
#define EMSESP_SCHEDULER_SERVICE_PATH "/rest/schedule" // GET and POST
|
#define EMSESP_SCHEDULER_SERVICE_PATH "/rest/schedule" // GET and POST
|
||||||
|
|
||||||
#ifndef EMSESP_SCHEDULER_RUNNING_CORE
|
|
||||||
#define EMSESP_SCHEDULER_RUNNING_CORE 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef EMSESP_SCHEDULER_STACKSIZE
|
|
||||||
#define EMSESP_SCHEDULER_STACKSIZE 5120
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef EMSESP_SCHEDULER_PRIORITY
|
|
||||||
#define EMSESP_SCHEDULER_PRIORITY 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// bit flags for the schedule items. Matches those in interface/src/app/main/SchedulerDialog.tsx
|
// bit flags for the schedule items. Matches those in interface/src/app/main/SchedulerDialog.tsx
|
||||||
// 0-127 (0->0x7F) is day schedule
|
// 0-127 (0->0x7F) is day schedule
|
||||||
// 128 (0x80) is timer
|
// 128 (0x80) is timer
|
||||||
// 129 (0x81) is on change
|
// 129 (0x81) is on change
|
||||||
// 130 (0x82) is on condition
|
// 130 (0x82) is on condition
|
||||||
// 132 (0x84) is immediate
|
|
||||||
#define SCHEDULEFLAG_SCHEDULE_TIMER 0x80 // 7th bit for Timer
|
#define SCHEDULEFLAG_SCHEDULE_TIMER 0x80 // 7th bit for Timer
|
||||||
#define SCHEDULEFLAG_SCHEDULE_ONCHANGE 0x81 // 7th+1st bit for OnChange
|
#define SCHEDULEFLAG_SCHEDULE_ONCHANGE 0x81 // 7th+1st bit for OnChange
|
||||||
#define SCHEDULEFLAG_SCHEDULE_CONDITION 0x82 // 7th+2nd bit for Condition
|
#define SCHEDULEFLAG_SCHEDULE_CONDITION 0x82 // 7th+2nd bit for Condition
|
||||||
#define SCHEDULEFLAG_SCHEDULE_IMMEDIATE 0x84 // 7th+3rd bit for Immediate
|
|
||||||
|
|
||||||
#define MAX_STARTUP_RETRIES 3 // retry the start-up commands x times
|
#define MAX_STARTUP_RETRIES 3 // retry the start-up commands x times
|
||||||
|
|
||||||
@@ -53,12 +39,11 @@ namespace emsesp {
|
|||||||
|
|
||||||
class ScheduleItem {
|
class ScheduleItem {
|
||||||
public:
|
public:
|
||||||
boolean active;
|
boolean active; // on or off
|
||||||
uint8_t flags; // bit flags, see SCHEDULEFLAG_* defines
|
uint8_t flags; // bit flags, see SCHEDULEFLAG_* defines
|
||||||
uint16_t elapsed_min; // total mins from 00:00
|
uint16_t elapsed_min; // total mins from 00:00
|
||||||
stringPSRAM time; // HH:MM
|
stringPSRAM time; // HH:MM
|
||||||
stringPSRAM cmd;
|
stringPSRAM cmd_name; // references a named command from WebCommandService
|
||||||
stringPSRAM value;
|
|
||||||
char name[20];
|
char name[20];
|
||||||
uint8_t retry_cnt;
|
uint8_t retry_cnt;
|
||||||
};
|
};
|
||||||
@@ -88,8 +73,6 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
|
|||||||
uint8_t count_entities();
|
uint8_t count_entities();
|
||||||
bool onChange(const char * cmd);
|
bool onChange(const char * cmd);
|
||||||
|
|
||||||
bool executeSchedule(const char * name);
|
|
||||||
|
|
||||||
std::string get_metrics_prometheus();
|
std::string get_metrics_prometheus();
|
||||||
|
|
||||||
std::string raw_value;
|
std::string raw_value;
|
||||||
@@ -103,9 +86,7 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
|
|||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
private:
|
private:
|
||||||
#endif
|
#endif
|
||||||
static void scheduler_task(void * pvParameters);
|
bool runScheduleCommand(const ScheduleItem & si);
|
||||||
|
|
||||||
bool command(const char * name, const std::string & cmd, const std::string & data);
|
|
||||||
void condition();
|
void condition();
|
||||||
|
|
||||||
HttpEndpoint<WebScheduler> _httpEndpoint;
|
HttpEndpoint<WebScheduler> _httpEndpoint;
|
||||||
|
|||||||
@@ -232,8 +232,8 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
|
|||||||
EMSESP::mqtt_.reset_mqtt();
|
EMSESP::mqtt_.reset_mqtt();
|
||||||
} else if (action == "upgradeImportantMessages") {
|
} else if (action == "upgradeImportantMessages") {
|
||||||
root["upgradeImportantMessageType"] = upgradeImportantMessages(param);
|
root["upgradeImportantMessageType"] = upgradeImportantMessages(param);
|
||||||
} else if (action == "executeSchedule") {
|
} else if (action == "executeCommand") {
|
||||||
ok = EMSESP::webSchedulerService.executeSchedule(param.c_str());
|
ok = EMSESP::webCommandService.dispatchCommand(param.c_str()); // command worker task (ok = dispatched); fast internal commands run inline (ok = real result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
|
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"axios": "^1.13.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user