25 Commits

Author SHA1 Message Date
Proddy
44168216fe Merge pull request #3081 from proddy/feature-LED_api
Feature led api #3063
2026-05-16 15:19:31 +02:00
proddy
dee4f3531d remove unneeded include 2026-05-16 15:19:08 +02:00
proddy
bf07f904fc some small optimizations, more colors 2026-05-16 12:27:00 +02:00
proddy
26c2b1b14e package update 2026-05-16 12:26:51 +02:00
proddy
7153bfefba remove unused variables 2026-05-15 20:41:48 +02:00
proddy
807b77d418 logic improvements 2026-05-15 13:06:14 +02:00
proddy
c69a7b03ef remove DEBUG for bus connection testing 2026-05-15 13:06:00 +02:00
proddy
a39626bb0e add comments 2026-05-15 13:05:43 +02:00
proddy
68e14875b3 EMS BUS ID -> EMS Bus ID 2026-05-15 10:49:23 +02:00
proddy
2d6d664109 typo 2026-05-15 10:49:12 +02:00
proddy
dd58973118 comment update 2026-05-15 10:48:40 +02:00
proddy
17d3f182a0 formatting 2026-05-15 10:48:15 +02:00
proddy
1961090c4c show EMS bus ID 2026-05-15 10:48:05 +02:00
proddy
33fda705c0 Feature: Make RGB LED (preset colors) accessible via API/etc #3039
Fixes #3063
2026-05-15 10:07:17 +02:00
proddy
208717a896 rgbLedWrite 2026-05-14 21:25:05 +02:00
proddy
6afc6b1baa replace rgbLedWrite 2026-05-14 21:24:55 +02:00
proddy
a4ddd73fc8 FACTORY_NETWORK_HOSTNAME 2026-05-14 21:24:20 +02:00
proddy
4459b1e333 fix for -DEMSESP_TEST 2026-05-14 21:24:01 +02:00
proddy
b2a66a0789 fix typo in hostname 2026-05-14 15:50:30 +02:00
proddy
33d1315395 package update 2026-05-14 15:50:23 +02:00
proddy
a0cae13f53 bump version 2026-05-14 15:50:16 +02:00
proddy
d1e2634594 rename FACTORY_NETWORK_HOSTNAME 2026-05-14 15:50:08 +02:00
proddy
b2247f5f58 tidy up 2026-05-14 12:10:42 +02:00
proddy
ae46ca2b8a upgrade bus id from x0B to x49 2026-05-14 12:10:33 +02:00
proddy
82ab22518e add cursor settings 2026-05-14 12:10:14 +02:00
40 changed files with 744 additions and 542 deletions

1
.gitignore vendored
View File

@@ -78,3 +78,4 @@ interface/.tsbuildinfo
test/test_api/package-lock.json
.clangd
mklittlefs
.cursor

View File

@@ -18,6 +18,7 @@ For more details go to [emsesp.org](https://emsesp.org/).
- updated version check [#3047](https://github.com/emsesp/EMS-ESP32/issues/3047)
- auto-logic to set ht3/ems+ tx-mode
- polarity for digital_in sensors [#3070](https://github.com/emsesp/EMS-ESP32/discussions/3070)
- user-requested LED blink [#3063](https://github.com/emsesp/EMS-ESP32/issues/3063)
## Fixed

View File

@@ -8,7 +8,7 @@
"ssid": "",
"bssid": "",
"password": "",
"hostname": "ems-esp2",
"hostname": "ems-esp",
"static_ip_config": false,
"bandwidth20": false,
"nosleep": true,

View File

@@ -3,7 +3,7 @@ build_flags =
; WiFi settings
-D FACTORY_WIFI_SSID=\"\"
-D FACTORY_WIFI_PASSWORD=\"\"
-D FACTORY_WIFI_HOSTNAME=\"ems-esp\"
-D FACTORY_NETWORK_HOSTNAME=\"ems-esp\"
; Access point settings
-D FACTORY_AP_PROVISION_MODE=AP_MODE_DISCONNECTED

View File

@@ -38,7 +38,7 @@
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-icons": "^5.6.0",
"react-router": "^7.15.0",
"react-router": "^7.15.1",
"react-toastify": "^11.1.0",
"typesafe-i18n": "^5.27.1",
"typescript": "^6.0.3"
@@ -47,17 +47,17 @@
"@eslint/js": "^10.0.1",
"@preact/preset-vite": "^2.10.5",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/node": "^25.7.0",
"@types/node": "^25.8.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"concurrently": "^9.2.1",
"eslint": "^10.3.0",
"eslint": "^10.4.0",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.8.3",
"rollup-plugin-visualizer": "^7.0.1",
"terser": "^5.47.1",
"typescript-eslint": "^8.59.3",
"vite": "^8.0.12",
"vite": "^8.0.13",
"vite-plugin-imagemin": "^0.6.1"
},
"packageManager": "pnpm@10.33.4+sha512.1c67b3b359b2d408119ba1ed289f34b8fc3c6873412bec6fd264fbdc82489e510fcbecb9ce9d22dae7f3b76269d8441046014bdca53b9979cd7a561ad631b800"

306
interface/pnpm-lock.yaml generated
View File

@@ -54,8 +54,8 @@ importers:
specifier: ^5.6.0
version: 5.6.0(react@19.2.6)
react-router:
specifier: ^7.15.0
version: 7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
specifier: ^7.15.1
version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
react-toastify:
specifier: ^11.1.0
version: 11.1.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
@@ -68,16 +68,16 @@ importers:
devDependencies:
'@eslint/js':
specifier: ^10.0.1
version: 10.0.1(eslint@10.3.0)
version: 10.0.1(eslint@10.4.0)
'@preact/preset-vite':
specifier: ^2.10.5
version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.12(@types/node@25.7.0)(terser@5.47.1))
version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.13(@types/node@25.8.0)(terser@5.47.1))
'@trivago/prettier-plugin-sort-imports':
specifier: ^6.0.2
version: 6.0.2(prettier@3.8.3)
'@types/node':
specifier: ^25.7.0
version: 25.7.0
specifier: ^25.8.0
version: 25.8.0
'@types/react':
specifier: ^19.2.14
version: 19.2.14
@@ -88,29 +88,29 @@ importers:
specifier: ^9.2.1
version: 9.2.1
eslint:
specifier: ^10.3.0
version: 10.3.0
specifier: ^10.4.0
version: 10.4.0
eslint-config-prettier:
specifier: ^10.1.8
version: 10.1.8(eslint@10.3.0)
version: 10.1.8(eslint@10.4.0)
prettier:
specifier: ^3.8.3
version: 3.8.3
rollup-plugin-visualizer:
specifier: ^7.0.1
version: 7.0.1(rolldown@1.0.0)(rollup@4.59.0)
version: 7.0.1(rolldown@1.0.1)(rollup@4.59.0)
terser:
specifier: ^5.47.1
version: 5.47.1
typescript-eslint:
specifier: ^8.59.3
version: 8.59.3(eslint@10.3.0)(typescript@6.0.3)
version: 8.59.3(eslint@10.4.0)(typescript@6.0.3)
vite:
specifier: ^8.0.12
version: 8.0.12(@types/node@25.7.0)(terser@5.47.1)
specifier: ^8.0.13
version: 8.0.13(@types/node@25.8.0)(terser@5.47.1)
vite-plugin-imagemin:
specifier: ^0.6.1
version: 0.6.1(vite@8.0.12(@types/node@25.7.0)(terser@5.47.1))
version: 0.6.1(vite@8.0.13(@types/node@25.8.0)(terser@5.47.1))
packages:
@@ -302,8 +302,8 @@ packages:
resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/config-helpers@0.5.5':
resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==}
'@eslint/config-helpers@0.6.0':
resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/core@1.2.1':
@@ -475,8 +475,8 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@oxc-project/types@0.129.0':
resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==}
'@oxc-project/types@0.130.0':
resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==}
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
@@ -504,103 +504,103 @@ packages:
preact: ^10.4.0 || ^11.0.0-0
vite: '>=2.0.0'
'@rolldown/binding-android-arm64@1.0.0':
resolution: {integrity: sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==}
'@rolldown/binding-android-arm64@1.0.1':
resolution: {integrity: sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@rolldown/binding-darwin-arm64@1.0.0':
resolution: {integrity: sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==}
'@rolldown/binding-darwin-arm64@1.0.1':
resolution: {integrity: sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.0.0':
resolution: {integrity: sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==}
'@rolldown/binding-darwin-x64@1.0.1':
resolution: {integrity: sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@rolldown/binding-freebsd-x64@1.0.0':
resolution: {integrity: sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==}
'@rolldown/binding-freebsd-x64@1.0.1':
resolution: {integrity: sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.0.0':
resolution: {integrity: sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==}
'@rolldown/binding-linux-arm-gnueabihf@1.0.1':
resolution: {integrity: sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.0.0':
resolution: {integrity: sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==}
'@rolldown/binding-linux-arm64-gnu@1.0.1':
resolution: {integrity: sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.0.0':
resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==}
'@rolldown/binding-linux-arm64-musl@1.0.1':
resolution: {integrity: sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.0.0':
resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==}
'@rolldown/binding-linux-ppc64-gnu@1.0.1':
resolution: {integrity: sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.0.0':
resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==}
'@rolldown/binding-linux-s390x-gnu@1.0.1':
resolution: {integrity: sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.0.0':
resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==}
'@rolldown/binding-linux-x64-gnu@1.0.1':
resolution: {integrity: sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.0.0':
resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==}
'@rolldown/binding-linux-x64-musl@1.0.1':
resolution: {integrity: sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.0.0':
resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==}
'@rolldown/binding-openharmony-arm64@1.0.1':
resolution: {integrity: sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@rolldown/binding-wasm32-wasi@1.0.0':
resolution: {integrity: sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==}
'@rolldown/binding-wasm32-wasi@1.0.1':
resolution: {integrity: sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [wasm32]
'@rolldown/binding-win32-arm64-msvc@1.0.0':
resolution: {integrity: sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==}
'@rolldown/binding-win32-arm64-msvc@1.0.1':
resolution: {integrity: sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.0.0':
resolution: {integrity: sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==}
'@rolldown/binding-win32-x64-msvc@1.0.1':
resolution: {integrity: sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
'@rolldown/pluginutils@1.0.0':
resolution: {integrity: sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==}
'@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
'@rollup/pluginutils@4.2.1':
resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
@@ -829,8 +829,8 @@ packages:
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.
'@types/node@25.7.0':
resolution: {integrity: sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==}
'@types/node@25.8.0':
resolution: {integrity: sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -1330,8 +1330,8 @@ packages:
duplexer3@0.1.5:
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
electron-to-chromium@1.5.353:
resolution: {integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==}
electron-to-chromium@1.5.357:
resolution: {integrity: sha512-NHlTIQDK8fmVwHwuIzmXYEJ1Ewq3D9wDNc0cWXxDGysP6Pb21giwGNkxiTifyKy/4SoPuN5l6GLP1W9Sv7zB2g==}
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -1519,8 +1519,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
eslint@10.3.0:
resolution: {integrity: sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==}
eslint@10.4.0:
resolution: {integrity: sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
@@ -2589,8 +2589,8 @@ packages:
react-is@19.2.6:
resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==}
react-router@7.15.0:
resolution: {integrity: sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==}
react-router@7.15.1:
resolution: {integrity: sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
@@ -2676,8 +2676,8 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rolldown@1.0.0:
resolution: {integrity: sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==}
rolldown@1.0.1:
resolution: {integrity: sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
@@ -3010,8 +3010,8 @@ packages:
unbzip2-stream@1.4.3:
resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==}
undici-types@7.21.0:
resolution: {integrity: sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==}
undici-types@7.24.6:
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
@@ -3059,8 +3059,8 @@ packages:
peerDependencies:
vite: 5.x || 6.x || 7.x || 8.x
vite@8.0.12:
resolution: {integrity: sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==}
vite@8.0.13:
resolution: {integrity: sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
@@ -3420,9 +3420,9 @@ snapshots:
'@esbuild/linux-loong64@0.14.54':
optional: true
'@eslint-community/eslint-utils@4.9.1(eslint@10.3.0)':
'@eslint-community/eslint-utils@4.9.1(eslint@10.4.0)':
dependencies:
eslint: 10.3.0
eslint: 10.4.0
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
@@ -3435,7 +3435,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@eslint/config-helpers@0.5.5':
'@eslint/config-helpers@0.6.0':
dependencies:
'@eslint/core': 1.2.1
@@ -3443,9 +3443,9 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
'@eslint/js@10.0.1(eslint@10.3.0)':
'@eslint/js@10.0.1(eslint@10.4.0)':
optionalDependencies:
eslint: 10.3.0
eslint: 10.4.0
'@eslint/object-schema@3.0.5': {}
@@ -3600,23 +3600,23 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.20.1
'@oxc-project/types@0.129.0': {}
'@oxc-project/types@0.130.0': {}
'@popperjs/core@2.11.8': {}
'@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.12(@types/node@25.7.0)(terser@5.47.1))':
'@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.13(@types/node@25.8.0)(terser@5.47.1))':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0)
'@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0)
'@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.12(@types/node@25.7.0)(terser@5.47.1))
'@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.13(@types/node@25.8.0)(terser@5.47.1))
'@rollup/pluginutils': 5.3.0(rollup@4.59.0)
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0)
debug: 4.4.3
magic-string: 0.30.21
picocolors: 1.1.1
vite: 8.0.12(@types/node@25.7.0)(terser@5.47.1)
vite-prerender-plugin: 0.5.13(vite@8.0.12(@types/node@25.7.0)(terser@5.47.1))
vite: 8.0.13(@types/node@25.8.0)(terser@5.47.1)
vite-prerender-plugin: 0.5.13(vite@8.0.13(@types/node@25.8.0)(terser@5.47.1))
zimmerframe: 1.1.4
transitivePeerDependencies:
- preact
@@ -3631,7 +3631,7 @@ snapshots:
'@prefresh/utils@1.2.1': {}
'@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.12(@types/node@25.7.0)(terser@5.47.1))':
'@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.13(@types/node@25.8.0)(terser@5.47.1))':
dependencies:
'@babel/core': 7.29.0
'@prefresh/babel-plugin': 0.5.3
@@ -3639,60 +3639,60 @@ snapshots:
'@prefresh/utils': 1.2.1
'@rollup/pluginutils': 4.2.1
preact: 10.29.1
vite: 8.0.12(@types/node@25.7.0)(terser@5.47.1)
vite: 8.0.13(@types/node@25.8.0)(terser@5.47.1)
transitivePeerDependencies:
- supports-color
'@rolldown/binding-android-arm64@1.0.0':
'@rolldown/binding-android-arm64@1.0.1':
optional: true
'@rolldown/binding-darwin-arm64@1.0.0':
'@rolldown/binding-darwin-arm64@1.0.1':
optional: true
'@rolldown/binding-darwin-x64@1.0.0':
'@rolldown/binding-darwin-x64@1.0.1':
optional: true
'@rolldown/binding-freebsd-x64@1.0.0':
'@rolldown/binding-freebsd-x64@1.0.1':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.0.0':
'@rolldown/binding-linux-arm-gnueabihf@1.0.1':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.0.0':
'@rolldown/binding-linux-arm64-gnu@1.0.1':
optional: true
'@rolldown/binding-linux-arm64-musl@1.0.0':
'@rolldown/binding-linux-arm64-musl@1.0.1':
optional: true
'@rolldown/binding-linux-ppc64-gnu@1.0.0':
'@rolldown/binding-linux-ppc64-gnu@1.0.1':
optional: true
'@rolldown/binding-linux-s390x-gnu@1.0.0':
'@rolldown/binding-linux-s390x-gnu@1.0.1':
optional: true
'@rolldown/binding-linux-x64-gnu@1.0.0':
'@rolldown/binding-linux-x64-gnu@1.0.1':
optional: true
'@rolldown/binding-linux-x64-musl@1.0.0':
'@rolldown/binding-linux-x64-musl@1.0.1':
optional: true
'@rolldown/binding-openharmony-arm64@1.0.0':
'@rolldown/binding-openharmony-arm64@1.0.1':
optional: true
'@rolldown/binding-wasm32-wasi@1.0.0':
'@rolldown/binding-wasm32-wasi@1.0.1':
dependencies:
'@emnapi/core': 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)
optional: true
'@rolldown/binding-win32-arm64-msvc@1.0.0':
'@rolldown/binding-win32-arm64-msvc@1.0.1':
optional: true
'@rolldown/binding-win32-x64-msvc@1.0.0':
'@rolldown/binding-win32-x64-msvc@1.0.1':
optional: true
'@rolldown/pluginutils@1.0.0': {}
'@rolldown/pluginutils@1.0.1': {}
'@rollup/pluginutils@4.2.1':
dependencies:
@@ -3822,7 +3822,7 @@ snapshots:
'@types/glob@7.2.0':
dependencies:
'@types/minimatch': 6.0.0
'@types/node': 25.7.0
'@types/node': 25.8.0
'@types/imagemin-gifsicle@7.0.4':
dependencies:
@@ -3851,21 +3851,21 @@ snapshots:
'@types/imagemin@7.0.1':
dependencies:
'@types/node': 25.7.0
'@types/node': 25.8.0
'@types/json-schema@7.0.15': {}
'@types/keyv@3.1.4':
dependencies:
'@types/node': 25.7.0
'@types/node': 25.8.0
'@types/minimatch@6.0.0':
dependencies:
minimatch: 10.2.5
'@types/node@25.7.0':
'@types/node@25.8.0':
dependencies:
undici-types: 7.21.0
undici-types: 7.24.6
'@types/parse-json@4.0.2': {}
@@ -3885,21 +3885,21 @@ snapshots:
'@types/responselike@1.0.3':
dependencies:
'@types/node': 25.7.0
'@types/node': 25.8.0
'@types/svgo@2.6.4':
dependencies:
'@types/node': 25.7.0
'@types/node': 25.8.0
'@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.3.0)(typescript@6.0.3))(eslint@10.3.0)(typescript@6.0.3)':
'@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.4.0)(typescript@6.0.3))(eslint@10.4.0)(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.59.3(eslint@10.3.0)(typescript@6.0.3)
'@typescript-eslint/parser': 8.59.3(eslint@10.4.0)(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.59.3
'@typescript-eslint/type-utils': 8.59.3(eslint@10.3.0)(typescript@6.0.3)
'@typescript-eslint/utils': 8.59.3(eslint@10.3.0)(typescript@6.0.3)
'@typescript-eslint/type-utils': 8.59.3(eslint@10.4.0)(typescript@6.0.3)
'@typescript-eslint/utils': 8.59.3(eslint@10.4.0)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.59.3
eslint: 10.3.0
eslint: 10.4.0
ignore: 7.0.5
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@6.0.3)
@@ -3907,14 +3907,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.59.3(eslint@10.3.0)(typescript@6.0.3)':
'@typescript-eslint/parser@8.59.3(eslint@10.4.0)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.59.3
'@typescript-eslint/types': 8.59.3
'@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.59.3
debug: 4.4.3
eslint: 10.3.0
eslint: 10.4.0
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -3937,13 +3937,13 @@ snapshots:
dependencies:
typescript: 6.0.3
'@typescript-eslint/type-utils@8.59.3(eslint@10.3.0)(typescript@6.0.3)':
'@typescript-eslint/type-utils@8.59.3(eslint@10.4.0)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/types': 8.59.3
'@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3)
'@typescript-eslint/utils': 8.59.3(eslint@10.3.0)(typescript@6.0.3)
'@typescript-eslint/utils': 8.59.3(eslint@10.4.0)(typescript@6.0.3)
debug: 4.4.3
eslint: 10.3.0
eslint: 10.4.0
ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
@@ -3966,13 +3966,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.59.3(eslint@10.3.0)(typescript@6.0.3)':
'@typescript-eslint/utils@8.59.3(eslint@10.4.0)(typescript@6.0.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0)
'@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0)
'@typescript-eslint/scope-manager': 8.59.3
'@typescript-eslint/types': 8.59.3
'@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3)
eslint: 10.3.0
eslint: 10.4.0
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -4109,7 +4109,7 @@ snapshots:
dependencies:
baseline-browser-mapping: 2.10.29
caniuse-lite: 1.0.30001792
electron-to-chromium: 1.5.353
electron-to-chromium: 1.5.357
node-releases: 2.0.44
update-browserslist-db: 1.2.3(browserslist@4.28.2)
@@ -4468,7 +4468,7 @@ snapshots:
duplexer3@0.1.5: {}
electron-to-chromium@1.5.353: {}
electron-to-chromium@1.5.357: {}
emoji-regex@10.6.0: {}
@@ -4584,9 +4584,9 @@ snapshots:
escape-string-regexp@4.0.0: {}
eslint-config-prettier@10.1.8(eslint@10.3.0):
eslint-config-prettier@10.1.8(eslint@10.4.0):
dependencies:
eslint: 10.3.0
eslint: 10.4.0
eslint-scope@9.1.2:
dependencies:
@@ -4599,12 +4599,12 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
eslint@10.3.0:
eslint@10.4.0:
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0)
'@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0)
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.5
'@eslint/config-helpers': 0.5.5
'@eslint/config-helpers': 0.6.0
'@eslint/core': 1.2.1
'@eslint/plugin-kit': 0.7.1
'@humanfs/node': 0.16.8
@@ -5648,7 +5648,7 @@ snapshots:
react-is@19.2.6: {}
react-router@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
react-router@7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
dependencies:
cookie: 1.1.1
react: 19.2.6
@@ -5738,35 +5738,35 @@ snapshots:
dependencies:
glob: 7.2.3
rolldown@1.0.0:
rolldown@1.0.1:
dependencies:
'@oxc-project/types': 0.129.0
'@rolldown/pluginutils': 1.0.0
'@oxc-project/types': 0.130.0
'@rolldown/pluginutils': 1.0.1
optionalDependencies:
'@rolldown/binding-android-arm64': 1.0.0
'@rolldown/binding-darwin-arm64': 1.0.0
'@rolldown/binding-darwin-x64': 1.0.0
'@rolldown/binding-freebsd-x64': 1.0.0
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0
'@rolldown/binding-linux-arm64-gnu': 1.0.0
'@rolldown/binding-linux-arm64-musl': 1.0.0
'@rolldown/binding-linux-ppc64-gnu': 1.0.0
'@rolldown/binding-linux-s390x-gnu': 1.0.0
'@rolldown/binding-linux-x64-gnu': 1.0.0
'@rolldown/binding-linux-x64-musl': 1.0.0
'@rolldown/binding-openharmony-arm64': 1.0.0
'@rolldown/binding-wasm32-wasi': 1.0.0
'@rolldown/binding-win32-arm64-msvc': 1.0.0
'@rolldown/binding-win32-x64-msvc': 1.0.0
'@rolldown/binding-android-arm64': 1.0.1
'@rolldown/binding-darwin-arm64': 1.0.1
'@rolldown/binding-darwin-x64': 1.0.1
'@rolldown/binding-freebsd-x64': 1.0.1
'@rolldown/binding-linux-arm-gnueabihf': 1.0.1
'@rolldown/binding-linux-arm64-gnu': 1.0.1
'@rolldown/binding-linux-arm64-musl': 1.0.1
'@rolldown/binding-linux-ppc64-gnu': 1.0.1
'@rolldown/binding-linux-s390x-gnu': 1.0.1
'@rolldown/binding-linux-x64-gnu': 1.0.1
'@rolldown/binding-linux-x64-musl': 1.0.1
'@rolldown/binding-openharmony-arm64': 1.0.1
'@rolldown/binding-wasm32-wasi': 1.0.1
'@rolldown/binding-win32-arm64-msvc': 1.0.1
'@rolldown/binding-win32-x64-msvc': 1.0.1
rollup-plugin-visualizer@7.0.1(rolldown@1.0.0)(rollup@4.59.0):
rollup-plugin-visualizer@7.0.1(rolldown@1.0.1)(rollup@4.59.0):
dependencies:
open: 11.0.0
picomatch: 4.0.4
source-map: 0.7.6
yargs: 18.0.0
optionalDependencies:
rolldown: 1.0.0
rolldown: 1.0.1
rollup: 4.59.0
rollup@4.59.0:
@@ -6070,13 +6070,13 @@ snapshots:
dependencies:
typescript: 6.0.3
typescript-eslint@8.59.3(eslint@10.3.0)(typescript@6.0.3):
typescript-eslint@8.59.3(eslint@10.4.0)(typescript@6.0.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.3.0)(typescript@6.0.3))(eslint@10.3.0)(typescript@6.0.3)
'@typescript-eslint/parser': 8.59.3(eslint@10.3.0)(typescript@6.0.3)
'@typescript-eslint/eslint-plugin': 8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.4.0)(typescript@6.0.3))(eslint@10.4.0)(typescript@6.0.3)
'@typescript-eslint/parser': 8.59.3(eslint@10.4.0)(typescript@6.0.3)
'@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3)
'@typescript-eslint/utils': 8.59.3(eslint@10.3.0)(typescript@6.0.3)
eslint: 10.3.0
'@typescript-eslint/utils': 8.59.3(eslint@10.4.0)(typescript@6.0.3)
eslint: 10.4.0
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -6088,7 +6088,7 @@ snapshots:
buffer: 5.7.1
through: 2.3.8
undici-types@7.21.0: {}
undici-types@7.24.6: {}
universalify@2.0.1: {}
@@ -6121,7 +6121,7 @@ snapshots:
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
vite-plugin-imagemin@0.6.1(vite@8.0.12(@types/node@25.7.0)(terser@5.47.1)):
vite-plugin-imagemin@0.6.1(vite@8.0.13(@types/node@25.8.0)(terser@5.47.1)):
dependencies:
'@types/imagemin': 7.0.1
'@types/imagemin-gifsicle': 7.0.4
@@ -6146,11 +6146,11 @@ snapshots:
imagemin-webp: 6.1.0
jpegtran-bin: 6.0.1
pathe: 0.2.0
vite: 8.0.12(@types/node@25.7.0)(terser@5.47.1)
vite: 8.0.13(@types/node@25.8.0)(terser@5.47.1)
transitivePeerDependencies:
- supports-color
vite-prerender-plugin@0.5.13(vite@8.0.12(@types/node@25.7.0)(terser@5.47.1)):
vite-prerender-plugin@0.5.13(vite@8.0.13(@types/node@25.8.0)(terser@5.47.1)):
dependencies:
kolorist: 1.8.0
magic-string: 0.30.21
@@ -6158,17 +6158,17 @@ snapshots:
simple-code-frame: 1.3.0
source-map: 0.7.6
stack-trace: 1.0.0
vite: 8.0.12(@types/node@25.7.0)(terser@5.47.1)
vite: 8.0.13(@types/node@25.8.0)(terser@5.47.1)
vite@8.0.12(@types/node@25.7.0)(terser@5.47.1):
vite@8.0.13(@types/node@25.8.0)(terser@5.47.1):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
postcss: 8.5.14
rolldown: 1.0.0
rolldown: 1.0.1
tinyglobby: 0.2.16
optionalDependencies:
'@types/node': 25.7.0
'@types/node': 25.8.0
fsevents: 2.3.3
terser: 5.47.1

View File

@@ -103,7 +103,7 @@ const cz: Translation = {
DISABLED: 'zakázáno',
TX_MODE: 'EMS Tx režim',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
EMS_BUS: '{{Bus|EMS Bus}}',
GENERAL_OPTIONS: 'Obecné možnosti',
LANGUAGE_ENTITIES: 'Jazyk (pro entity zařízení)',
HIDE_LED: 'Skrýt LED',

View File

@@ -103,7 +103,7 @@ const de: Translation = {
DISABLED: 'deaktiviert',
TX_MODE: 'EMS Tx-Modus',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
EMS_BUS: '{{Bus|EMS Bus}}',
GENERAL_OPTIONS: 'Allgemeine Optionen',
LANGUAGE_ENTITIES: 'Sprache (für Geräteentitäten)',
HIDE_LED: 'LED ausblenden',

View File

@@ -103,7 +103,7 @@ const en: Translation = {
DISABLED: 'disabled',
TX_MODE: 'EMS Tx Mode',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
EMS_BUS: '{{Bus|EMS Bus}}',
GENERAL_OPTIONS: 'General Options',
LANGUAGE_ENTITIES: 'Language (for device entities)',
HIDE_LED: 'Hide LED',

View File

@@ -103,7 +103,7 @@ const fr: Translation = {
DISABLED: 'désactivé',
TX_MODE: 'EMS Tx Mode',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
EMS_BUS: '{{Bus|EMS Bus}}',
GENERAL_OPTIONS: 'Options générales',
LANGUAGE_ENTITIES: 'Langue (pour les entités du matériel)',
HIDE_LED: 'Cacher la LED',

View File

@@ -103,7 +103,7 @@ const it: Translation = {
DISABLED: 'disattivato',
TX_MODE: 'EMS Modo Tx ',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
EMS_BUS: '{{Bus|EMS Bus}}',
GENERAL_OPTIONS: 'Opzioni Generali',
LANGUAGE_ENTITIES: 'Lingua (per entità dispositivi)',
HIDE_LED: 'Nascondi LED',

View File

@@ -102,7 +102,7 @@ const nl: Translation = {
PHY_TYPE: 'Eth PHY Type',
TX_MODE: 'EMS Tx Mode',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
EMS_BUS: '{{Bus|EMS Bus}}',
DISABLED: 'Uitgeschakeld',
GENERAL_OPTIONS: 'Algemene Opties',
LANGUAGE_ENTITIES: 'Taal (voor apparaat entiteiten)',

View File

@@ -103,7 +103,7 @@ const no: Translation = {
DISABLED: 'avslått',
TX_MODE: 'EMS Tx Mode',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
EMS_BUS: '{{Bus|EMS Bus}}',
GENERAL_OPTIONS: 'Generelle Innstillinger',
LANGUAGE_ENTITIES: 'Språk (for objekter)',
HIDE_LED: 'Skjul LED',

View File

@@ -103,7 +103,7 @@ const sk: Translation = {
DISABLED: 'zakázané',
TX_MODE: 'EMS Tx režim',
HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}',
EMS_BUS: '{{Bus|EMS Bus}}',
GENERAL_OPTIONS: 'Všeobecné možnosti',
LANGUAGE_ENTITIES: 'Jazyk (pre entity zariadenia)',
HIDE_LED: 'Skryť LED',

View File

@@ -103,7 +103,7 @@ const sv: Translation = {
DISABLED: 'inaktiverad',
TX_MODE: 'EMS Tx-läge',
HARDWARE: 'Hårdvara',
EMS_BUS: '{{BUSS|EMS-BUSS}}',
EMS_BUS: '{{Buss|EMS-Buss}}',
GENERAL_OPTIONS: 'Allmänna inställningar',
LANGUAGE_ENTITIES: 'Språk (för entiteter)',
HIDE_LED: 'Inaktivera LED',

View File

@@ -103,7 +103,7 @@ const tr: Translation = {
DISABLED: 'devre dışı',
TX_MODE: 'EMS Tx Modu',
HARDWARE: 'Donanım',
EMS_BUS: '{{HAT|EMS HATTI}}',
EMS_BUS: '{{Hat|EMS Hatti}}',
GENERAL_OPTIONS: 'Genel Seçenekler',
LANGUAGE_ENTITIES: 'Dil (cihaz varlıkları için)',
HIDE_LED: 'LEDi kapa',

View File

@@ -43,7 +43,7 @@ StateUpdateResult NetworkSettings::update(JsonObject root, NetworkSettings & set
settings.ssid = root["ssid"] | FACTORY_WIFI_SSID;
settings.bssid = root["bssid"] | "";
settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
settings.hostname = root["hostname"] | FACTORY_NETWORK_HOSTNAME;
settings.staticIPConfig = root["static_ip_config"];
settings.bandwidth20 = root["bandwidth20"];
settings.tx_power = static_cast<uint8_t>(root["tx_power"] | 0);

View File

@@ -21,8 +21,8 @@
#define FACTORY_WIFI_PASSWORD ""
#endif
#ifndef FACTORY_WIFI_HOSTNAME
#define FACTORY_WIFI_HOSTNAME ""
#ifndef FACTORY_NETWORK_HOSTNAME
#define FACTORY_NETWORK_HOSTNAME ""
#endif
class NetworkSettings {
@@ -31,7 +31,7 @@ class NetworkSettings {
String ssid = FACTORY_WIFI_SSID;
String bssid = "";
String password = FACTORY_WIFI_PASSWORD;
String hostname = FACTORY_WIFI_HOSTNAME;
String hostname = FACTORY_NETWORK_HOSTNAME;
bool staticIPConfig = false;
bool bandwidth20 = false;
uint8_t tx_power = 0;

View File

@@ -4,7 +4,6 @@
#include <esp_app_format.h>
#include <esp_ota_ops.h>
// #include <esp_partition.h>
static String getFilenameExtension(const String & filename) {
const auto pos = filename.lastIndexOf('.');
@@ -49,7 +48,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
// LittleFS filesystem image
_is_filesystem = true;
_md5[0] = '\0'; // clear any stale md5 so Update.end() doesn't compare against it
} else if ((extension == "bin") && (filesize > 2000000)) {
} else if ((extension == "bin") && (filesize > 1900000)) {
_is_firmware = true;
} else if (extension == "json") {
_md5[0] = '\0'; // clear md5
@@ -61,6 +60,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
return;
} else {
_md5.front() = '\0';
emsesp::EMSESP::logger().err("Unsupported file type: %s, size: %u", filename.c_str(), filesize);
handleError(request, 406); // Not Acceptable - unsupported file type
return;
}

View File

@@ -299,7 +299,7 @@ void AnalogSensor::reload(bool get_nvs) {
uint8_t r = v / 10000;
uint8_t g = (v - r * 10000) / 100;
uint8_t b = v % 100;
EMSESP_RGB_WRITE(sensor.gpio(), 2 * r, 2 * g, 2 * b);
rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b);
LOG_DEBUG("RGB set to %d, %d, %d", r, g, b);
} else if (sensor.type() == AnalogType::DIGITAL_OUT) {
LOG_DEBUG("Digital Write on GPIO %02d", sensor.gpio());
@@ -989,7 +989,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
uint8_t r = v / 10000;
uint8_t g = (v - r * 10000) / 100;
uint8_t b = v % 100;
EMSESP_RGB_WRITE(sensor.gpio(), 2 * r, 2 * g, 2 * b);
rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b);
LOG_DEBUG("RGB set to %d, %d, %d", r, g, b);
} else if (sensor.type() == AnalogType::PULSE) {
uint8_t v = val;

View File

@@ -2312,7 +2312,6 @@ std::string EMSdevice::name() {
// returns true on success.
int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result) {
// find device value by shortname
// TODO replace linear search which is inefficient
const auto & it = std::find_if(devicevalues_.begin(), devicevalues_.end(), [&](const DeviceValue & x) { return x.tag == tag && x.short_name == shortname; });
if (it == devicevalues_.end() && (it->short_name != shortname || it->tag != tag)) {
return -1;

View File

@@ -90,6 +90,7 @@ Network EMSESP::network_; // network services
TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors
AnalogSensor EMSESP::analogsensor_; // Analog sensors
Shower EMSESP::shower_; // Shower logic
LED EMSESP::led_; // LED handler
Preferences EMSESP::nvs_; // NV Storage
// for a specific EMS device go and request data values
@@ -177,21 +178,6 @@ void EMSESP::clear_all_devices() {
// emsdevices.clear(); // remove entries, but doesn't delete actual devices
}
// return number of devices of a known type
uint8_t EMSESP::count_devices(const uint8_t device_type) {
if (emsdevices.empty()) {
return 0;
}
uint8_t count = 0;
for (const auto & emsdevice : emsdevices) {
if (emsdevice && emsdevice->device_type() == device_type) {
count++;
}
}
return count;
}
// return total number of devices excluding the Controller
uint8_t EMSESP::count_devices() {
if (emsdevices.empty()) {
@@ -207,27 +193,6 @@ uint8_t EMSESP::count_devices() {
return count;
}
// returns the index of a device if there are more of the same type
// or 0 if there is only one or none
uint8_t EMSESP::device_index(const uint8_t device_type, const uint8_t unique_id) {
uint8_t count = 0;
uint8_t index = 0;
uint8_t current_index = 1;
for (const auto & emsdevice : emsdevices) {
if (emsdevice->device_type() == device_type) {
count++;
if (emsdevice->unique_id() == unique_id) {
index = current_index;
}
current_index++;
}
}
// Return 0 if only one device exists or not found
return (count <= 1) ? 0 : index;
}
// scans for new devices
void EMSESP::scan_devices() {
EMSESP::clear_all_devices();
@@ -265,6 +230,7 @@ uint8_t EMSESP::bus_status() {
// show the EMS bus status plus both Rx and Tx queues
void EMSESP::show_ems(uuid::console::Shell & shell) {
// EMS bus information
shell.printfln("EMS Bus ID: %02X", EMSbus::ems_bus_id());
switch (bus_status()) {
case BUS_STATUS_OFFLINE:
shell.printfln("EMS Bus is disconnected.");
@@ -585,7 +551,7 @@ void EMSESP::publish_all(bool force) {
publish_other_values(); // switch and heat pump, ...
publish_sensor_values(true); // includes temperature and analog sensors
system_.send_heartbeat();
system_.send_heartbeat(); // send MQTT heartbeat topic
}
}
@@ -626,7 +592,7 @@ void EMSESP::publish_all_loop() {
if (Mqtt::ha_enabled()) {
Mqtt::ha_status();
}
system_.send_heartbeat();
system_.send_heartbeat(); // send MQTT heartbeat topic
break;
default:
// all finished
@@ -1532,7 +1498,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
Roomctrl::check(data[1], data, length);
#ifdef EMSESP_UART_DEBUG
// get_uptime is only updated once per loop, does not give the right time
LOG_TRACE("[UART_DEBUG] Echo after %d ms: %s", ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
LOG_TRACE("[UART_DEBUG] Echo after %d ms: %s", uuid::get_uptime_ms() - rx_time_, Helpers::data_to_hex(data, length).c_str());
#endif
// add to RxQueue for log/watch
rxservice_.add(data, length);
@@ -1616,11 +1582,11 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
#ifdef EMSESP_UART_DEBUG
char s[4];
if (first_value & 0x80) {
LOG_TRACE("[UART_DEBUG] next Poll %s after %d ms", Helpers::hextoa(s, first_value), ::millis() - rx_time_);
LOG_TRACE("[UART_DEBUG] next Poll %s after %d ms", Helpers::hextoa(s, first_value), uuid::get_uptime_ms() - rx_time_);
// time measurement starts here, use millis because get_uptime is only updated once per loop
rx_time_ = ::millis();
rx_time_ = uuid::get_uptime_ms();
} else {
LOG_TRACE("[UART_DEBUG] Poll ack %s after %d ms", Helpers::hextoa(s, first_value), ::millis() - rx_time_);
LOG_TRACE("[UART_DEBUG] Poll ack %s after %d ms", Helpers::hextoa(s, first_value), uuid::get_uptime_ms() - rx_time_);
}
#endif
// check for poll to us, if so send top message from Tx queue immediately and quit
@@ -1634,7 +1600,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
return;
} else {
#ifdef EMSESP_UART_DEBUG
LOG_TRACE("[UART_DEBUG] Reply after %d ms: %s", ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
LOG_TRACE("[UART_DEBUG] Reply after %d ms: %s", uuid::get_uptime_ms() - rx_time_, Helpers::data_to_hex(data, length).c_str());
#endif
Roomctrl::check(data[1], data, length); // check if there is a message for the roomcontroller
@@ -1711,10 +1677,10 @@ void EMSESP::start() {
bool factory_settings = false;
#endif
#if defined(EMSESP_DEBUG)
// LOG_DEBUG("Listing root directory before:");
// system_.listDir("/", 3); // show the contents of the root directory
#endif
// #if defined(EMSESP_DEBUG)
// LOG_DEBUG("Listing root directory before:");
// system_.listDir("/", 3); // show the contents of the root directory
// #endif
// start NVS storage
if (!nvs_.begin("ems-esp", false, "nvs1")) { // try bigger nvs partition on 16M flash first
@@ -1730,10 +1696,10 @@ void EMSESP::start() {
// loads core system services settings (mqtt, ap, ntp etc)
esp32React.begin();
#if defined(EMSESP_DEBUG)
// LOG_DEBUG("Listing root directory after:");
// system_.listDir("/", 3); // show the contents of the root directory
#endif
// #if defined(EMSESP_DEBUG)
// LOG_DEBUG("Listing root directory after:");
// system_.listDir("/", 3); // show the contents of the root directory
// #endif
#ifndef EMSESP_STANDALONE
if (factory_settings) {
@@ -1866,7 +1832,7 @@ void EMSESP::loop() {
// handles LED and checks system health, and syslog service
if (system_.loop()) {
return; // LED flashing is active, skip the rest of the loop
return; // LED flashing is active meaning its about to reboot, skip the rest of the loop
}
esp32React.loop(); // core services: Network, AP, MQTT and NTP

View File

@@ -138,9 +138,7 @@ class EMSESP {
static void device_active(const uint8_t device_id, const bool active);
static bool cmd_is_readonly(const uint8_t device_type, const uint8_t device_id, const char * cmd, const int8_t id);
static uint8_t device_id_from_cmd(const uint8_t device_type, const char * cmd, const int8_t id);
static uint8_t count_devices(const uint8_t device_type);
static uint8_t count_devices();
static uint8_t device_index(const uint8_t device_type, const uint8_t unique_id);
static bool get_device_value_info(JsonObject root, const char * cmd, const int8_t id, const uint8_t devicetype);
static void show_device_values(uuid::console::Shell & shell);
@@ -233,6 +231,7 @@ class EMSESP {
static TemperatureSensor temperaturesensor_;
static AnalogSensor analogsensor_;
static Shower shower_;
static LED led_;
static RxService rxservice_;
static TxService txservice_;
static Preferences nvs_;

316
src/core/led.cpp Normal file
View File

@@ -0,0 +1,316 @@
/*
* 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 "led.h"
#include "emsesp.h"
namespace emsesp {
uuid::log::Logger LED::logger_{F_(led), uuid::log::Facility::KERN};
// initialise the LED, fetching the settings from the WebSettingsService
// set the LED to on or off when in normal operating mode
void LED::init() {
// copy the application settings
EMSESP::webSettingsService.read([&](WebSettings & settings) {
led_gpio_ = settings.led_gpio;
led_type_ = settings.led_type;
hide_led_ = settings.hide_led;
});
if (!led_gpio_) { // 0 means disabled
LOG_INFO("LED disabled");
return;
}
// for safety
if (!led_type_) {
pinMode(led_gpio_, OUTPUT);
}
reset_led(); // start with LED in default state, depending on if it's hidden or not
}
// handle LED routine
// called from the System::loop()
// returns true if the LED flash is active, i.e its a lock down state
bool LED::loop(uint8_t healthcheck, bool button_busy) {
// if LED flashing is active it means its about to perform a factory reset, so don't do anything else and keep it flashing
if (led_fast_flash_timer_) {
led_fast_flash();
return true;
}
// the user-requested LED blink always has preference
if (!is_user_led_blink_) {
// check for button press.
// if button is pressed, show LED (yellow on RGB LED, on/off on standard LED)
// it will turn off on the next loop cycle
if (last_button_busy_ != button_busy) {
last_button_busy_ = button_busy;
set_led(button_busy ? Color::OFF : Color::YELLOW); // Yellow
return false;
}
// check the system health.
// Set the sequence accordingly, only if the healthcheck is not 0 and has changed
if (healthcheck != previous_healthcheck_) {
previous_healthcheck_ = healthcheck;
color_steps_[0] = Color::OFF;
color_steps_[1] = Color::OFF;
color_steps_[2] = Color::OFF;
// if the healthcheck is 0, i.e. system is healthy, reset the LED
if (healthcheck == 0) {
reset_led();
return false;
}
// 1 flash (blue) is the EMS bus is not connected
// 2 flashes (red, red) if the network (wifi or ethernet) is not connected
// 3 flashes (red, red, blue) is both the bus and the network are not connected
bool no_network = (healthcheck & System::HEALTHCHECK_NO_NETWORK) == System::HEALTHCHECK_NO_NETWORK;
bool no_bus = (healthcheck & System::HEALTHCHECK_NO_BUS) == System::HEALTHCHECK_NO_BUS;
// set step 1
if (no_network) {
color_steps_[0] = Color::RED; // red, no network
} else if (no_bus) {
color_steps_[0] = Color::BLUE; // blue, no bus
}
// set step 2
if (no_network) {
color_steps_[1] = Color::RED; // red, no network
}
// set step 3
if (no_network && no_bus) {
color_steps_[2] = Color::BLUE; // blue, no network and no bus
}
}
}
// show the LED status based on the healthcheck and button busy status
sequence_led();
return false;
}
// turn the LED back it's default state depending on if it's hidden or not
void LED::reset_led() {
is_user_led_blink_ = false;
set_led(hide_led_ ? Color::OFF : Color::GREEN); // Green
color_steps_[0] = Color::OFF;
color_steps_[1] = Color::OFF;
color_steps_[2] = Color::OFF;
}
// LED flash every few ms and then perform a factory reset
void LED::led_fast_flash() {
uint32_t current_time = uuid::get_uptime();
if (current_time - last_toggle_time_ >= LED_FLASH_INTERVAL_MS) {
led_flash_state_ = !led_flash_state_;
last_toggle_time_ = current_time;
set_led(led_flash_state_ ? Color::YELLOW : Color::OFF); // Yellow
}
// after duration, turn off the LED
if (current_time - led_flash_start_time_ >= led_flash_duration_) {
set_led(Color::OFF);
led_fast_flash_timer_ = false;
#ifndef EMSESP_DEBUG
System::command_format(nullptr, 0); // Execute format operation, unless in debug mode
#endif
}
}
// set LED on/off or RGB color
// ignores whether the LED is hidden or not (if hide_led_ is set)
void LED::set_led(Color color) {
if (!led_gpio_) {
return;
}
// RGB lookup table indexed by Color enum (must match enum order in led.h)
static constexpr uint8_t B = RGB_LED_BRIGHTNESS;
static constexpr uint8_t H = RGB_LED_BRIGHTNESS / 2;
static constexpr uint8_t rgb_table[][3] = {
{0, 0, 0}, // OFF
{B, B, B}, // ON (white)
{B, 0, 0}, // RED
{0, B, 0}, // GREEN
{0, 0, B}, // BLUE
{B, B, 0}, // YELLOW
{B, H, 0}, // ORANGE
{0, B, B}, // CYAN
{H, 0, H} // PINK
};
const uint8_t color_idx = static_cast<uint8_t>(color);
const uint8_t idx = (color_idx < sizeof(rgb_table) / sizeof(rgb_table[0])) ? color_idx : static_cast<uint8_t>(Color::OFF);
const uint8_t red = rgb_table[idx][0];
const uint8_t green = rgb_table[idx][1];
const uint8_t blue = rgb_table[idx][2];
if (led_type_) {
rgbLedWrite(led_gpio_, red, green, blue);
} else {
digitalWrite(led_gpio_, (red == 0 && green == 0 && blue == 0) || color == Color::OFF ? !LED_ON : LED_ON);
}
}
// set LED custom routine
// For example: /api/system/led?data=red:blink1
// For older non-RGB models, the colour would default to just being on.
bool LED::set_custom_led_routine(std::string color, std::string pattern) {
static constexpr struct {
const char * name;
Color value;
} color_map[] = {
{"off", Color::OFF},
{"on", Color::ON},
{"white", Color::ON},
{"red", Color::RED},
{"green", Color::GREEN},
{"blue", Color::BLUE},
{"yellow", Color::YELLOW},
{"orange", Color::ORANGE},
{"cyan", Color::CYAN},
{"pink", Color::PINK},
};
Color color_type = Color::OFF;
bool color_matched = false;
for (const auto & entry : color_map) {
if (color == entry.name) {
color_type = entry.value;
color_matched = true;
break;
}
}
if (!color_matched) {
return false;
}
// reset the color steps
color_steps_[0] = Color::OFF;
color_steps_[1] = Color::OFF;
color_steps_[2] = Color::OFF;
// blink patterns
if (pattern == "blink1") {
color_steps_[0] = color_type;
} else if (pattern == "blink2") {
color_steps_[0] = color_type;
color_steps_[1] = color_type;
} else if (pattern == "blink3") {
color_steps_[0] = color_type;
color_steps_[1] = color_type;
color_steps_[2] = color_type;
// special patterns, ignores the user color
} else if (pattern == "rgb") {
color_steps_[0] = Color::RED;
color_steps_[1] = Color::GREEN;
color_steps_[2] = Color::BLUE;
} else if (pattern == "cpc") {
color_steps_[0] = Color::CYAN;
color_steps_[1] = Color::PINK;
color_steps_[2] = Color::CYAN;
} else {
return false; // pattern not recognized
}
is_user_led_blink_ = true; // user routine is active
// when this is called we want the sequence_led to restart immediately and skip the long pause
led_long_timer_ = uuid::get_uptime() + HEALTHCHECK_LED_FLASH_FAST_DURATION + 200UL;
return true;
}
// uses LED to show system health and user-requested LED blinks
// it works in a batch of 3 configured flashes, then a long pause
// the timing is different for user-requested LED blink and for system healthcheck
void LED::sequence_led() {
// first long pause before we start flashing
auto current_time = uuid::get_uptime();
if (led_long_timer_
&& (uint32_t)(current_time - led_long_timer_) >= (is_user_led_blink_ ? HEALTHCHECK_LED_LONG_FAST_DURATION : HEALTHCHECK_LED_LONG_DURATION)) {
led_short_timer_ = current_time; // start the short timer
led_long_timer_ = 0; // stop long timer
led_flash_step_ = 1; // enable the short flash timer
}
// the flash timer which starts after the long pause
if (led_flash_step_
&& (uint32_t)(current_time - led_short_timer_) >= (is_user_led_blink_ ? HEALTHCHECK_LED_FLASH_FAST_DURATION : HEALTHCHECK_LED_FLASH_DURATION)) {
led_long_timer_ = 0; // stop the long timer
led_short_timer_ = current_time;
if (++led_flash_step_ == 8) {
// finished first iteration, reset the whole sequence, turn off LED
led_long_timer_ = uuid::get_uptime();
led_flash_step_ = 0;
set_led(Color::OFF); // turn off the LED
// if we're running a user-requested LED blink, turn it off and go back to the healthcheck sequence
if (is_user_led_blink_) {
is_user_led_blink_ = false;
previous_healthcheck_ = System::HEALTHCHECK_RESET; // this will force the healthcheck to be checked again
}
return;
}
if (led_flash_step_ % 2) {
// handle the three step events (on odd numbers 3,5,7 etc). see if we need to set a LED color
switch (led_flash_step_) {
case 3: // first flash
set_led(color_steps_[0]);
break;
case 5: // second flash
set_led(color_steps_[1]);
break;
case 7: // third flash
set_led(color_steps_[2]);
break;
default:
break;
}
} else {
set_led(Color::OFF); // turn off on even number count, to make it flash
}
}
}
// Start the LED flash timer - duration in seconds
void LED::start_led_fast_flash(uint8_t duration) {
// Don't start if already running
if (led_fast_flash_timer_) {
return;
}
// Reset counter and state
led_flash_start_time_ = uuid::get_uptime(); // current time
led_flash_duration_ = (uint32_t)duration * 1000; // duration in milliseconds
led_fast_flash_timer_ = true; // it's active
}
} // namespace emsesp

93
src/core/led.h Normal file
View File

@@ -0,0 +1,93 @@
/*
* 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/>.
*/
#ifndef EMSESP_LED_H_
#define EMSESP_LED_H_
#include <Arduino.h>
#include <uuid/log.h>
namespace emsesp {
class LED {
public:
enum Color : uint8_t {
OFF = 0, // 0
ON = 1, // 1 - white
RED = 2, // 2
GREEN = 3, // 3
BLUE = 4, // 4
YELLOW = 5, // 5
ORANGE = 6, // 6
CYAN = 7, // 7
PINK = 8 // 8
};
void init();
bool loop(uint8_t healthcheck, bool button_busy);
void start_led_fast_flash(uint8_t duration); // duration in seconds
bool set_custom_led_routine(std::string color, std::string pattern);
private:
static uuid::log::Logger logger_;
void sequence_led();
void led_fast_flash();
void reset_led(); // turn the LED back it's default state depending on if it's hidden or not
void set_led(Color color);
static constexpr uint32_t HEALTHCHECK_LED_LONG_DURATION = 1000; // 1 second between flash sequences
static constexpr uint32_t HEALTHCHECK_LED_LONG_FAST_DURATION = 500; // 1/2 second between flash sequences
static constexpr uint32_t HEALTHCHECK_LED_FLASH_DURATION = 150; // 150ms
static constexpr uint32_t HEALTHCHECK_LED_FLASH_FAST_DURATION = 150;
static constexpr uint32_t LED_FLASH_INTERVAL_MS = 100; // LED toggle period during factory-reset flash
static constexpr uint8_t RGB_LED_BRIGHTNESS = 20; // 255 is max brightness
static constexpr uint8_t LED_ON = HIGH; // LED on
// local copies of the application settings
uint8_t led_gpio_ = 0;
uint8_t led_type_ = 0;
bool hide_led_ = false;
bool led_fast_flash_timer_ = false;
uint32_t led_flash_start_time_ = 0;
uint32_t led_flash_duration_ = 0;
// led_flash() state
bool led_flash_state_ = false;
uint32_t last_toggle_time_ = 0;
// sequence_led() state
bool last_button_busy_ = false;
uint32_t led_long_timer_ = 1; // 1 will kick it off immediately
uint32_t led_short_timer_ = 0;
uint8_t led_flash_step_ = 0; // 0 means we're not in the short flash timer
// set_led_routine() state
Color color_steps_[3] = {Color::OFF, Color::OFF, Color::OFF};
// if true, the user has requested a custom LED blink, this always has preference over the button or showing the system health
bool is_user_led_blink_ = false;
uint8_t previous_healthcheck_ = 0;
};
} // namespace emsesp
#endif

View File

@@ -43,6 +43,7 @@ MAKE_WORD(fetch)
MAKE_WORD(restart)
MAKE_WORD(format)
MAKE_WORD(txpause)
MAKE_WORD(led)
MAKE_WORD(raw)
MAKE_WORD(watch)
MAKE_WORD(syslog)
@@ -171,7 +172,6 @@ MAKE_WORD_CUSTOM(password_prompt, "Password: ")
MAKE_WORD_CUSTOM(unset, "<unset>")
MAKE_WORD_CUSTOM(enable_mandatory, "<enable | disable>")
MAKE_WORD_CUSTOM(service_mandatory, "<ap | mqtt | ntp>")
MAKE_WORD_CUSTOM(txpause_cmd, "enable/disable TX")
// more common names that don't need translations
MAKE_NOTRANSLATION(1x3min, "1x3min")

View File

@@ -85,6 +85,7 @@ MAKE_WORD_TRANSLATION(system_cmd, "system setting", "System Einstellung", "syste
MAKE_WORD_TRANSLATION(showertimer_cmd, "enable shower timer", "aktiviere Duschzeitmessung", "activeer douche timer", "aktivera duschtimer", "aktywuj czasomierz prysznica", "aktiver dusjtimer", "activer minuteur de douche", "duş zamanlayıcısını etkinleştir", "abilita timer doccia", "povoliť časovač sprchovania", "povolit časovač sprchy")
MAKE_WORD_TRANSLATION(showeralert_cmd, "enable shower alert", "aktiviere Duschzeitwarnung", "activeer douche alarm", "aktivera duschvarning", "aktywuj alarm prysznica", "aktiver dusjvarsel", "activer alerte de douche", "duş uyarısını etkinleştir", "abilita allarme doccia", "povoliť upozornenie na sprchu", "povolit alarm sprchy")
MAKE_WORD_TRANSLATION(txpause_cmd, "pause EMS Tx", "EMS Tx pausieren", "pauzeer EMS Tx", "pausa EMS Tx", "wstrzymaj EMS Tx", "pause EMS Tx", "pause EMS Tx", "EMS Tx'i duraklat", "pausa EMS Tx", "pozastaviť EMS Tx", "pauzovat EMS Tx")
MAKE_WORD_TRANSLATION(led_cmd, "flash the LED", "LED blinken", "LED knipperen", "LED blinka", "LED błyska", "LED blink", "LED clignote", "LED yanıp söner", "LED lampeggia", "LED bliká", "LED bliká")
// tags
MAKE_WORD_TRANSLATION(tag_hc1, "hc1", "HK1", "hc1", "VK1", "OG1", "hc1", "hc1", "ID1", "hc1", "hc1", "hc1")

View File

@@ -53,7 +53,6 @@ std::vector<Mqtt::MQTTSubFunction, AllocatorPSRAM<Mqtt::MQTTSubFunction>> Mqtt::
uint32_t Mqtt::mqtt_publish_fails_ = 0;
bool Mqtt::connecting_ = false;
bool Mqtt::initialized_ = false;
bool Mqtt::ha_climate_reset_ = false;
uint16_t Mqtt::queuecount_ = 0;
uint8_t Mqtt::connectcount_ = 0;
uint32_t Mqtt::mqtt_message_id_ = 0;
@@ -131,10 +130,10 @@ void Mqtt::loop() {
uint32_t currentMillis = uuid::get_uptime();
// send heartbeat
// send heartbeat per the frequency in the MQTT settings
if (currentMillis - last_publish_heartbeat_ > publish_time_heartbeat_) {
last_publish_heartbeat_ = currentMillis;
EMSESP::system_.send_heartbeat(); // send heartbeat
EMSESP::system_.send_heartbeat(); // send MQTT heartbeat topic
}
// temperature and analog sensor publish on change
@@ -493,7 +492,6 @@ void Mqtt::on_connect() {
queue_unsubscribe_message(discovery_prefix_ + "/+/" + Mqtt::basename() + "/#");
EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any
ha_status(); // create the EMS-ESP device in HA, which is MQTT retained
ha_climate_reset(true);
} else {
// with disabled HA we subscribe and the broker sends all stored HA-emsesp-configs.
// Around line 272 they are removed (search for "// remove HA topics if we don't use discover")
@@ -511,7 +509,6 @@ void Mqtt::on_connect() {
// send initial MQTT messages for some of our services
EMSESP::system_.send_heartbeat(); // send heartbeat
// for publish on change publish the initial complete list
EMSESP::webCustomEntityService.publish(true);
EMSESP::webSchedulerService.publish(true);
EMSESP::analogsensor_.publish_values(true);

View File

@@ -138,10 +138,6 @@ class Mqtt {
};
}
static MqttClient * client() {
return mqttClient_;
}
static bool enabled() {
return mqtt_enabled_;
}
@@ -231,14 +227,6 @@ class Mqtt {
ha_enabled_ = ha_enabled;
}
static bool ha_climate_reset() {
return ha_climate_reset_;
}
static void ha_climate_reset(bool reset) {
ha_climate_reset_ = reset;
}
static std::string get_response() {
return lastresponse_;
}
@@ -317,7 +305,6 @@ class Mqtt {
static uint32_t mqtt_publish_fails_;
static uint16_t queuecount_;
static uint8_t connectcount_;
static bool ha_climate_reset_;
static std::string lastresponse_;

View File

@@ -257,7 +257,7 @@ NetPhase Network::initialPhase() const {
void Network::loop() {
#ifndef EMSESP_STANDALONE
// if we already have a Wifi or Ethernet connection then re-check every NETWORK_RECONNECTION_DELAY_LONG, otherwise NETWORK_RECONNECTION_DELAY_SHORT
const unsigned long currentMillis = millis();
const unsigned long currentMillis = uuid::get_uptime_ms();
const uint32_t reconnectDelay =
(network_iface_ == NetIface::WIFI || network_iface_ == NetIface::ETHERNET) ? NETWORK_RECONNECTION_DELAY_LONG : NETWORK_RECONNECTION_DELAY_SHORT;
if (!lastConnectionAttempt_ || static_cast<uint32_t>(currentMillis - lastConnectionAttempt_) >= reconnectDelay) {

View File

@@ -88,13 +88,6 @@ bool System::test_set_all_active_ = false;
uint32_t System::max_alloc_mem_;
uint32_t System::heap_mem_;
// LED flash timer
uint8_t System::led_flash_gpio_ = 0;
uint8_t System::led_flash_type_ = 0;
uint32_t System::led_flash_start_time_ = 0;
uint32_t System::led_flash_duration_ = 0;
bool System::led_flash_timer_ = false;
// GPIOs
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::valid_system_gpios_;
std::vector<System::GpioUsage, AllocatorPSRAM<System::GpioUsage>> System::used_gpios_;
@@ -670,18 +663,11 @@ void System::modbus_init() {
// read specific major system settings to store locally for faster access
void System::store_settings(WebSettings & settings) {
version_ = settings.version;
rx_gpio_ = settings.rx_gpio;
tx_gpio_ = settings.tx_gpio;
pbutton_gpio_ = settings.pbutton_gpio;
dallas_gpio_ = settings.dallas_gpio;
led_gpio_ = settings.led_gpio;
analog_enabled_ = settings.analog_enabled;
low_clock_ = settings.low_clock;
hide_led_ = settings.hide_led;
led_type_ = settings.led_type;
board_profile_ = settings.board_profile;
telnet_enabled_ = settings.telnet_enabled;
@@ -733,23 +719,25 @@ void System::start() {
hostname(networkSettings.hostname.c_str()); // sets the hostname
});
commands_init(); // console & api commands
led_init(); // init LED
button_init(); // button
last_system_check_ = 0; // force the LED to go from fast flash to pulse
uart_init(); // start UART
syslog_init(); // start syslog
modbus_init(); // start modbus
commands_init(); // console & api commands
EMSESP::led_.init(); // init LED
button_init(); // button
uart_init(); // start UART
syslog_init(); // start syslog
modbus_init(); // start modbus
}
// button single click
// button single click - does nothing in normal operation
// in debug mode, it will trigger a special healthcheck to test the LED monitoring and sequence_led
void System::button_OnClick(PButton & b) {
LOG_NOTICE("Button pressed - single click");
#ifdef EMSESP_DEBUG
#ifndef EMSESP_STANDALONE
// show filesystem
listDir("/", 3);
listDir("/", 3); // show filesystem
#endif
// used to test LED monitoring and sequence_led. See system_check() for more details.
EMSESP::system_.healthcheck(99); // 99 = special trigger
#endif
}
@@ -757,7 +745,7 @@ void System::button_OnClick(PButton & b) {
// reconnect to AP by removing the SSID from the network settings
// note: in v3.9 this is normal behaviour to fallback to AP if the Wifi or Ethernet connection fails
void System::button_OnDblClick(PButton & b) {
LOG_NOTICE("Button pressed - double click - wifi reconnect to AP");
LOG_NOTICE("Button pressed - double click - reset network");
#ifndef EMSESP_STANDALONE
// set AP mode to always so will join AP if wifi ssid fails to connect
EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) {
@@ -773,55 +761,6 @@ void System::button_OnDblClick(PButton & b) {
#endif
}
// LED flash every 100ms
void System::led_flash() {
static bool led_flash_state_ = false;
static uint32_t last_toggle_time_ = 0;
uint32_t current_time = uuid::get_uptime();
if (current_time - last_toggle_time_ >= 100) { // every 100ms
led_flash_state_ = !led_flash_state_;
last_toggle_time_ = current_time;
if (led_flash_type_) {
uint8_t intensity = led_flash_state_ ? RGB_LED_BRIGHTNESS : 0;
EMSESP_RGB_WRITE(led_flash_gpio_, intensity, intensity, 0); // RGB LED - Yellow
} else {
digitalWrite(led_flash_gpio_, led_flash_state_ ? LED_ON : !LED_ON); // Standard LED
}
}
// after duration, turn off the LED
if (current_time - led_flash_start_time_ >= led_flash_duration_) {
if (led_flash_type_) {
EMSESP_RGB_WRITE(led_flash_gpio_, 0, 0, 0);
} else {
digitalWrite(led_flash_gpio_, !LED_ON);
}
led_flash_timer_ = false;
command_format(nullptr, 0); // Execute format operation
}
}
// Start the LED flash timer - duration in seconds
void System::start_led_flash(uint8_t duration) {
// Don't start if already running
if (led_flash_timer_) {
return;
}
// Get LED settings
EMSESP::webSettingsService.read([&](WebSettings & settings) {
led_flash_type_ = settings.led_type;
led_flash_gpio_ = settings.led_gpio;
});
// Reset counter and state
led_flash_start_time_ = uuid::get_uptime(); // current time
led_flash_duration_ = duration * 1000; // duration in milliseconds
led_flash_timer_ = true; // it's active
}
// button long press
void System::button_OnLongPress(PButton & b) {
LOG_NOTICE("Button pressed - long press - restart EMS-ESP");
@@ -831,7 +770,7 @@ void System::button_OnLongPress(PButton & b) {
// button indefinite press
void System::button_OnVLongPress(PButton & b) {
LOG_NOTICE("Button pressed - very long press - perform factory reset");
start_led_flash(5); // Start LED flash timer for 5 seconds
EMSESP::led_.start_led_fast_flash(5); // Start LED flash timer for 5 seconds
}
// push button
@@ -850,21 +789,7 @@ void System::button_init() {
#endif
}
// set the LED to on or off when in normal operating mode
void System::led_init() {
if (!led_gpio_) { // 0 means disabled
LOG_INFO("LED disabled");
return;
}
if (led_type_) {
EMSESP_RGB_WRITE(led_gpio_, 0, 0, 0);
} else {
pinMode(led_gpio_, OUTPUT);
digitalWrite(led_gpio_, !LED_ON); // start with LED off
}
}
// init UART
void System::uart_init() {
EMSuart::stop();
EMSuart::start(tx_mode_, rx_gpio_, tx_gpio_); // start UART, GPIOs have already been checked
@@ -879,18 +804,16 @@ bool System::loop() {
system_restart();
}
// if LED flashing is active, run the LED flash
if (led_flash_timer_) {
led_flash();
return true; // is active
myPButton_.check(); // check button press
system_check(); // System health check
// handle the LED
if (EMSESP::led_.loop(healthcheck_, myPButton_.button_busy())) {
return true; // restart is pending, skip the rest of the loop
}
led_monitor(); // check status and report back using the LED
myPButton_.check(); // check button press
system_check(); // check system health
// syslog
#ifndef EMSESP_STANDALONE
// syslog service
if (syslog_enabled_) {
syslog_.loop();
}
@@ -898,7 +821,7 @@ bool System::loop() {
send_info_mqtt();
return false; // LED flashing is not active
return false;
}
// send MQTT info topic appended with the version information as JSON, as a retained flag
@@ -1047,41 +970,22 @@ void System::system_check() {
LOG_NOTICE("Ping test, #%d", ping_count++);
#endif
// check if we have a valid network connection
if (!EMSESP::network_.network_connected()) {
healthcheck_ |= HEALTHCHECK_NO_NETWORK;
if (healthcheck_ != 99) { // skip if we're testing
// check if we have a valid network connection
healthcheck_ = (healthcheck_ & ~HEALTHCHECK_NO_NETWORK) | (EMSESP::network_.network_connected() ? 0 : HEALTHCHECK_NO_NETWORK);
// check if we have a bus connection
healthcheck_ = (healthcheck_ & ~HEALTHCHECK_NO_BUS) | (EMSbus::bus_connected() ? 0 : HEALTHCHECK_NO_BUS);
} else {
healthcheck_ &= ~HEALTHCHECK_NO_NETWORK;
LOG_DEBUG("Healthcheck: testing mode");
healthcheck_ = 0; // make it all look healthy - this is temporary for one cycle
}
// check if we have a bus connection
if (!EMSbus::bus_connected()) {
healthcheck_ |= HEALTHCHECK_NO_BUS;
} else {
healthcheck_ &= ~HEALTHCHECK_NO_BUS;
}
// see if the healthcheck state has changed
// see if the healthcheck state has changed, if so send out the new heartbeat
static uint8_t last_healthcheck_ = 0;
if (healthcheck_ != last_healthcheck_) {
last_healthcheck_ = healthcheck_;
EMSESP::system_.send_heartbeat(); // send MQTT heartbeat immediately when connected
// see if we're better now
if (healthcheck_ == 0) {
// everything is healthy, show LED permanently on or off depending on setting
// Green on RGB LED, on/off on standard LED
if (led_gpio_) {
led_type_ ? EMSESP_RGB_WRITE(led_gpio_, 0, hide_led_ ? 0 : RGB_LED_BRIGHTNESS, 0)
: digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON); // Green
}
} else {
// turn off LED so we're ready for the warning flashes
if (led_gpio_) {
led_type_ ? EMSESP_RGB_WRITE(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON);
}
}
EMSESP::system_.send_heartbeat();
}
}
}
@@ -1096,6 +1000,7 @@ void System::commands_init() {
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_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_(txpause), System::command_txpause, FL_(txpause_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_(watch), System::command_watch, FL_(watch_cmd));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(message), System::command_message, FL_(message_cmd));
#if defined(EMSESP_TEST)
@@ -1109,98 +1014,6 @@ void System::commands_init() {
Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback
}
// uses LED to show system health
void System::led_monitor() {
// if button is pressed, show LED (yellow on RGB LED, on/off on standard LED)
static bool button_busy_ = false;
if (button_busy_ != myPButton_.button_busy()) {
button_busy_ = myPButton_.button_busy();
if (led_type_) {
EMSESP_RGB_WRITE(led_gpio_, button_busy_ ? RGB_LED_BRIGHTNESS : 0, button_busy_ ? RGB_LED_BRIGHTNESS : 0, 0); // Yellow
} else {
digitalWrite(led_gpio_, button_busy_ ? LED_ON : !LED_ON);
}
}
// we only need to run the LED healthcheck if there are errors
// skip if we're in the led_flash_timer or if a button has been pressed
if (!healthcheck_ || !led_gpio_ || button_busy_ || led_flash_timer_) {
return; // all good
}
static uint32_t led_long_timer_ = 1; // 1 will kick it off immediately
static uint32_t led_short_timer_ = 0;
static uint8_t led_flash_step_ = 0; // 0 means we're not in the short flash timer
auto current_time = uuid::get_uptime();
// first long pause before we start flashing
if (led_long_timer_ && (uint32_t)(current_time - led_long_timer_) >= HEALTHCHECK_LED_LONG_DUARATION) {
led_short_timer_ = current_time; // start the short timer
led_long_timer_ = 0; // stop long timer
led_flash_step_ = 1; // enable the short flash timer
}
// the flash timer which starts after the long pause
if (led_flash_step_ && (uint32_t)(current_time - led_short_timer_) >= HEALTHCHECK_LED_FLASH_DUARATION) {
led_long_timer_ = 0; // stop the long timer
led_short_timer_ = current_time;
static bool led_on_ = false;
if (++led_flash_step_ == 8) {
// reset the whole sequence
led_long_timer_ = uuid::get_uptime();
led_flash_step_ = 0;
led_type_ ? EMSESP_RGB_WRITE(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON); // LED off
} else if (led_flash_step_ % 2) {
// handle the step events (on odd numbers 3,5,7,etc). see if we need to turn on a LED
// 1 flash (blue) is the EMS bus is not connected
// 2 flashes (red, red) if the network (wifi or ethernet) is not connected
// 3 flashes (red, red, blue) is both the bus and the network are not connected
bool no_network = (healthcheck_ & HEALTHCHECK_NO_NETWORK) == HEALTHCHECK_NO_NETWORK;
bool no_bus = (healthcheck_ & HEALTHCHECK_NO_BUS) == HEALTHCHECK_NO_BUS;
if (led_type_) {
if (led_flash_step_ == 3) {
if (no_network) {
EMSESP_RGB_WRITE(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red
} else if (no_bus) {
EMSESP_RGB_WRITE(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue
}
}
if (led_flash_step_ == 5 && no_network) {
EMSESP_RGB_WRITE(led_gpio_, RGB_LED_BRIGHTNESS, 0, 0); // red
}
if ((led_flash_step_ == 7) && no_network && no_bus) {
EMSESP_RGB_WRITE(led_gpio_, 0, 0, RGB_LED_BRIGHTNESS); // blue
}
} else {
if ((led_flash_step_ == 3) && (no_network || no_bus)) {
led_on_ = true;
}
if ((led_flash_step_ == 5) && no_network) {
led_on_ = true;
}
if ((led_flash_step_ == 7) && no_network && no_bus) {
led_on_ = true;
}
if (led_on_) {
digitalWrite(led_gpio_, LED_ON); // LED on
}
}
} else {
// turn the led off after the flash, on even number count
if (led_on_) {
led_type_ ? EMSESP_RGB_WRITE(led_gpio_, 0, 0, 0) : digitalWrite(led_gpio_, !LED_ON);
led_on_ = false;
}
}
}
}
// Return the quality (Received Signal Strength Indicator) of the WiFi network as a %
// High quality: 90% ~= -55dBm
// Medium quality: 50% ~= -75dBm
@@ -1406,7 +1219,6 @@ void System::show_system(uuid::console::Shell & shell) {
#endif
}
// see if there is a restore of an older settings file that needs to be applied
// note there can be only one file at a time
bool System::check_restore() {
@@ -1436,6 +1248,22 @@ bool System::check_restore() {
saveSettings(MQTT_SETTINGS_FILE, section);
saveSettings(NTP_SETTINGS_FILE, section);
saveSettings(SECURITY_SETTINGS_FILE, section);
// next is application settings
// we need to set the EMS Bus ID to 0x49 if it's 0x0B and coming from a version which is < v3.9.0
std::string settingsVersion = section["Settings"]["version"];
FirmwareVersion settings_version(settingsVersion);
if (settings_version < FirmwareVersion("3.9.0")) {
if (section["Settings"]["ems_bus_id"].is<int>()) {
int ems_bus_id = section["Settings"]["ems_bus_id"];
if (ems_bus_id == 0x0B) {
// set to EMSESP_DEFAULT_EMS_BUS_ID
section["Settings"]["ems_bus_id"] = EMSESP_DEFAULT_EMS_BUS_ID;
LOG_INFO("Overriding EMS Bus ID to %02X (was %02X)", EMSESP_DEFAULT_EMS_BUS_ID, ems_bus_id);
}
}
}
// continue processing the rest of the sections
saveSettings(EMSESP_SETTINGS_FILE, section);
}
if (section_type == "schedule") {
@@ -1685,7 +1513,11 @@ bool System::check_upgrade() {
// force web buffer to 25 for those boards without psram
if ((EMSESP::system_.PSram() == 0) && (settings.weblog_buffer != 25)) {
settings.weblog_buffer = 25;
return StateUpdateResult::CHANGED;
// if we're coming from < v3.9.0 and the Bus ID is the service key (0x0B), set it to the new default
if (settings.ems_bus_id == 0x0B && settings_version.major() <= 3 && settings_version.minor() < 9) {
settings.ems_bus_id = EMSESP_DEFAULT_EMS_BUS_ID;
return StateUpdateResult::CHANGED;
}
}
return StateUpdateResult::UNCHANGED;
});
@@ -1944,14 +1776,12 @@ bool System::command_service(const char * cmd, const char * value) {
settings.hide_led = b;
return StateUpdateResult::CHANGED;
});
EMSESP::system_.hide_led(b);
ok = true;
} else if (!strcmp(cmd, "settings/analogenabled")) {
EMSESP::webSettingsService.update([&](WebSettings & settings) {
settings.analog_enabled = b;
return StateUpdateResult::CHANGED;
});
EMSESP::system_.analog_enabled(b);
ok = true;
} else if (!strcmp(cmd, "mqtt/enabled")) {
EMSESP::esp32React.getMqttSettingsService()->update([&](MqttSettings & Settings) {
@@ -2663,11 +2493,11 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["ethPhyAddr"] = settings.eth_phy_addr;
node["ethClockMmode"] = settings.eth_clock_mode;
}
node["rxGPIO"] = EMSESP::system_.rx_gpio_;
node["txGPIO"] = EMSESP::system_.tx_gpio_;
node["dallasGPIO"] = EMSESP::system_.dallas_gpio_;
node["pbuttonGPIO"] = EMSESP::system_.pbutton_gpio_;
node["ledGPIO"] = EMSESP::system_.led_gpio_;
node["rxGPIO"] = settings.rx_gpio;
node["txGPIO"] = settings.tx_gpio;
node["dallasGPIO"] = settings.dallas_gpio;
node["pbuttonGPIO"] = settings.pbutton_gpio;
node["ledGPIO"] = settings.led_gpio;
node["ledType"] = settings.led_type;
}
node["hideLed"] = settings.hide_led;
@@ -2842,6 +2672,39 @@ bool System::load_board_profile(std::vector<int8_t> & data, const std::string &
return true;
}
// led command
// https://github.com/emsesp/EMS-ESP32/issues/3063
// /api//system/led command that takes an argument in the form [color]:[pattern]
// color is red, green, blue, yellow, white
// pattern is
// blink1 for 1 time
// blink2 for 2 times
// blink3 for 3 times
// rgb for RGB
// For example: /api/system/led?data=red:blink1
// For older non-RGB models, the colour would default to just being on.
bool System::command_led(const char * value, const int8_t id) {
if (!value) {
return false; // no argument
}
std::string arg = value;
if (arg.find(':') == std::string::npos) {
LOG_ERROR("LED command must be in the form [color]:[pattern]");
return false; // not in the form [color]:[pattern]
}
std::string color = arg.substr(0, arg.find(':'));
std::string pattern = arg.substr(arg.find(':') + 1);
// set and validate the color and pattern
if (!EMSESP::led_.set_custom_led_routine(color, pattern)) {
LOG_ERROR("Invalid color or pattern.");
return false;
}
return true;
}
// txpause command - temporarily pause the TX, by setting Txmode to 0 (disabled)
bool System::command_txpause(const char * value, const int8_t id) {
bool arg;

View File

@@ -26,6 +26,7 @@
#include "console.h"
#include "mqtt.h"
#include "telegram.h"
#include "led.h"
#ifndef EMSESP_STANDALONE
#include <esp_wifi.h>
@@ -37,8 +38,6 @@
#include <uuid/log.h>
#include <PButton.h>
#define EMSESP_RGB_WRITE rgbLedWrite
#if CONFIG_IDF_TARGET_ESP32
// there is no official API available on the original ESP32
extern "C" {
@@ -54,8 +53,6 @@ using uuid::console::Shell;
#define EMSESP_CUSTOMSUPPORT_FILE "/config/customSupport.json"
#define RGB_LED_BRIGHTNESS 20 // 255 is max brightness
namespace emsesp {
enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110, PHY_TYPE_RTL8201 };
@@ -98,6 +95,7 @@ class System {
static bool command_service(const char * cmd, const char * value);
static bool command_sendmail(const char * value, const int8_t id);
static bool command_txpause(const char * value, const int8_t id);
static bool command_led(const char * value, const int8_t id);
static bool get_value_info(JsonObject root, const char * cmd);
static void get_value_json(JsonObject output, const std::string & circuit, const std::string & name, JsonVariant val);
@@ -144,7 +142,6 @@ class System {
static bool uploadFirmwareURL(const char * url = nullptr);
void led_init();
void button_init();
void commands_init();
void uart_init();
@@ -166,10 +163,6 @@ class System {
static String get_ip_or_hostname();
void dallas_gpio(uint8_t gpio) {
dallas_gpio_ = gpio;
}
bool telnet_enabled() {
return telnet_enabled_;
}
@@ -190,18 +183,6 @@ class System {
return modbus_timeout_;
}
bool analog_enabled() {
return analog_enabled_;
}
void analog_enabled(bool b) {
analog_enabled_ = b;
}
void hide_led(bool b) {
hide_led_ = b;
}
bool readonly_mode() {
return readonly_mode_;
}
@@ -354,6 +335,11 @@ class System {
static bool set_partition(const char * partitionname);
// healthcheck flags - shared with LED for status visualization
static constexpr uint8_t HEALTHCHECK_NO_BUS = (1 << 0); // 1
static constexpr uint8_t HEALTHCHECK_NO_NETWORK = (1 << 1); // 2
static constexpr uint8_t HEALTHCHECK_RESET = (1 << 7); // 128
private:
static uuid::log::Logger logger_;
@@ -374,15 +360,6 @@ class System {
static constexpr uint32_t BUTTON_Debounce = 40; // Debounce period to prevent flickering when pressing or releasing the button (in ms)
static constexpr uint32_t BUTTON_DblClickDelay = 250; // Max period between clicks for a double click event (in ms)
// LED flash timer
static bool led_flash_timer_;
static uint8_t led_flash_gpio_;
static uint8_t led_flash_type_;
static uint32_t led_flash_start_time_;
static uint32_t led_flash_duration_;
static void start_led_flash(uint8_t duration);
static void led_flash();
// button press delays
static constexpr uint32_t BUTTON_LongPressDelay = 3000; // Hold period for a long press event (in ms) - ~3 seconds
static constexpr uint32_t BUTTON_VLongPressDelay = 9500; // Hold period for a very long press event (in ms) - !10 seconds
@@ -393,17 +370,11 @@ class System {
#else
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // do a system check every 5 seconds
#endif
static constexpr uint32_t HEALTHCHECK_LED_LONG_DUARATION = 1500; // 1.5 seconds
static constexpr uint32_t HEALTHCHECK_LED_FLASH_DUARATION = 150; // 150ms
static constexpr uint8_t HEALTHCHECK_NO_BUS = (1 << 0); // 1
static constexpr uint8_t HEALTHCHECK_NO_NETWORK = (1 << 1); // 2
static constexpr uint8_t LED_ON = HIGH; // LED on
#ifndef EMSESP_STANDALONE
static uuid::syslog::SyslogService syslog_;
#endif
void led_monitor();
void system_check();
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> string_range_to_vector(const std::string & range, const std::string & exclude = "");
@@ -429,17 +400,12 @@ class System {
// EMS-ESP settings
std::string hostname_;
String locale_;
bool hide_led_;
uint8_t led_type_;
uint8_t led_gpio_;
bool analog_enabled_;
bool low_clock_;
String board_profile_;
uint8_t pbutton_gpio_;
uint8_t rx_gpio_;
uint8_t tx_gpio_;
uint8_t tx_mode_;
uint8_t dallas_gpio_;
bool telnet_enabled_;
bool syslog_enabled_;
int8_t syslog_level_;
@@ -451,7 +417,6 @@ class System {
uint8_t bool_format_;
uint8_t enum_format_;
bool readonly_mode_;
String version_;
bool modbus_enabled_;
uint16_t modbus_port_;
uint8_t modbus_max_clients_;

View File

@@ -257,7 +257,7 @@ void RxService::add_empty(const uint8_t src, const uint8_t dest, const uint16_t
// start and initialize Tx
// send out request to EMS bus for all devices
void TxService::start() {
// grab the bus ID and tx_mode
// grab the EMS Bus ID and tx_mode
EMSESP::webSettingsService.read([&](WebSettings const & settings) {
ems_bus_id(settings.ems_bus_id);
tx_mode(settings.tx_mode);

View File

@@ -174,7 +174,7 @@ class EMSbus {
}
static void set_ems2() {
isEMS2_ = true;;
isEMS2_ = true;
}
static uint8_t ems_mask() {
@@ -203,7 +203,7 @@ class EMSbus {
// checks every 30 seconds if the EMS bus is still alive
static bool bus_connected() {
#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST)
#if defined(EMSESP_STANDALONE)
return true;
#else
if ((uuid::get_uptime() - last_bus_activity_) > EMS_BUS_TIMEOUT) {
@@ -247,7 +247,7 @@ class EMSbus {
static uint32_t bus_uptime_start_; // timestamp of first time we connected to the bus
static bool bus_connected_; // start assuming the bus hasn't been connected
static uint8_t ems_mask_; // unset=0xFF, buderus=0x00, junkers/ht3=0x80
static uint8_t ems_bus_id_; // the bus id, which configurable and stored in settings
static uint8_t ems_bus_id_; // the EMS Bus id, which configurable and stored in settings
static uint8_t tx_mode_; // local copy of the tx mode
static uint8_t tx_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
static bool isEMS2_;

View File

@@ -47,13 +47,10 @@ void TemperatureSensor::start(const bool factory_settings) {
// load settings
void TemperatureSensor::reload() {
// load the service settings
EMSESP::system_.dallas_gpio(0); // reset in system to check valid sensor
EMSESP::webSettingsService.read([&](WebSettings const & settings) {
dallas_gpio_ = settings.dallas_gpio;
parasite_ = settings.dallas_parasite;
});
EMSESP::system_.dallas_gpio(dallas_gpio_); // set to system for checks
for (auto & sensor : sensors_) {
remove_ha_topic(sensor.id());

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.9.0-dev.7"
#define EMSESP_APP_VERSION "3.9.0-dev.8"

View File

@@ -450,7 +450,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// THESE ONLY WORK WITH AN ESP32, not in standalone/native mode
#ifndef EMSESP_STANDALONE
if (command == "ls") {
listDir(LittleFS, "/", 3);
EMSESP::system_.listDir("/", 3);
ok = true;
}
@@ -1091,6 +1091,22 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call boiler circpump/value");
}
if (command == "led") {
shell.printfln("Testing LED...");
JsonDocument doc;
AsyncWebServerRequest request;
request.method(HTTP_POST);
char data1[] = "{\"data\":\"red:blink1\"}";
deserializeJson(doc, data1);
JsonVariant json = doc.as<JsonVariant>();
request.url("/api/system/led");
EMSESP::webAPIService.webAPIService(&request, json);
ok = true;
}
if (command == "shuntingyard") {
shell.printfln("Testing shunting yard...");

View File

@@ -64,6 +64,7 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "hpmode"
// #define EMSESP_DEBUG_DEFAULT "shuntingyard"
// #define EMSESP_DEBUG_DEFAULT "src"
#define EMSESP_DEBUG_DEFAULT "led"
#ifndef EMSESP_DEBUG_DEFAULT
#define EMSESP_DEBUG_DEFAULT "general"

View File

@@ -387,7 +387,7 @@ void WebSettingsService::onUpdate() {
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::LED)) {
EMSESP::system_.led_init();
EMSESP::led_.init();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::MQTT)) {

View File

@@ -295,19 +295,19 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
FirmwareVersion current_version(current_version_s); // get current version
if ((current_version.major() <= 3 && current_version.minor() <= 8) && (latest_version.major() == 3 && latest_version.minor() == 9)) {
return 1; // if moving from below 3.8.x to 3.9.x return 1
if (!(latest_version > current_version)) {
return 0; // no upgrade (same version or downgrade)
}
if (latest_version > current_version && current_version.major() < latest_version.major()) {
return 2; // if it's a major version upgrade return 2
if (current_version < FirmwareVersion("3.9.0") && latest_version.major() == 3 && latest_version.minor() == 9) {
return 1; // upgrading to 3.9.x from anything older - new partition layout warning
}
if (latest_version > current_version && current_version.minor() < latest_version.minor()) {
return 0; // if it's just a minor version upgrade return 0
if (current_version.major() < latest_version.major()) {
return 2; // major version upgrade
}
return 0; // if it's not a valid version upgrade return 0
return 0; // minor or patch upgrade, no special message
}
// action = getVersions