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 test/test_api/package-lock.json
.clangd .clangd
mklittlefs 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) - updated version check [#3047](https://github.com/emsesp/EMS-ESP32/issues/3047)
- auto-logic to set ht3/ems+ tx-mode - auto-logic to set ht3/ems+ tx-mode
- polarity for digital_in sensors [#3070](https://github.com/emsesp/EMS-ESP32/discussions/3070) - 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 ## Fixed

View File

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

View File

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

View File

@@ -38,7 +38,7 @@
"react": "^19.2.6", "react": "^19.2.6",
"react-dom": "^19.2.6", "react-dom": "^19.2.6",
"react-icons": "^5.6.0", "react-icons": "^5.6.0",
"react-router": "^7.15.0", "react-router": "^7.15.1",
"react-toastify": "^11.1.0", "react-toastify": "^11.1.0",
"typesafe-i18n": "^5.27.1", "typesafe-i18n": "^5.27.1",
"typescript": "^6.0.3" "typescript": "^6.0.3"
@@ -47,17 +47,17 @@
"@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.7.0", "@types/node": "^25.8.0",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"eslint": "^10.3.0", "eslint": "^10.4.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"prettier": "^3.8.3", "prettier": "^3.8.3",
"rollup-plugin-visualizer": "^7.0.1", "rollup-plugin-visualizer": "^7.0.1",
"terser": "^5.47.1", "terser": "^5.47.1",
"typescript-eslint": "^8.59.3", "typescript-eslint": "^8.59.3",
"vite": "^8.0.12", "vite": "^8.0.13",
"vite-plugin-imagemin": "^0.6.1" "vite-plugin-imagemin": "^0.6.1"
}, },
"packageManager": "pnpm@10.33.4+sha512.1c67b3b359b2d408119ba1ed289f34b8fc3c6873412bec6fd264fbdc82489e510fcbecb9ce9d22dae7f3b76269d8441046014bdca53b9979cd7a561ad631b800" "packageManager": "pnpm@10.33.4+sha512.1c67b3b359b2d408119ba1ed289f34b8fc3c6873412bec6fd264fbdc82489e510fcbecb9ce9d22dae7f3b76269d8441046014bdca53b9979cd7a561ad631b800"

306
interface/pnpm-lock.yaml generated
View File

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

View File

@@ -103,7 +103,7 @@ const cz: Translation = {
DISABLED: 'zakázáno', DISABLED: 'zakázáno',
TX_MODE: 'EMS Tx režim', TX_MODE: 'EMS Tx režim',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{Bus|EMS Bus}}',
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',

View File

@@ -103,7 +103,7 @@ const de: Translation = {
DISABLED: 'deaktiviert', DISABLED: 'deaktiviert',
TX_MODE: 'EMS Tx-Modus', TX_MODE: 'EMS Tx-Modus',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{Bus|EMS Bus}}',
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',

View File

@@ -103,7 +103,7 @@ const en: Translation = {
DISABLED: 'disabled', DISABLED: 'disabled',
TX_MODE: 'EMS Tx Mode', TX_MODE: 'EMS Tx Mode',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{Bus|EMS Bus}}',
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',

View File

@@ -103,7 +103,7 @@ const fr: Translation = {
DISABLED: 'désactivé', DISABLED: 'désactivé',
TX_MODE: 'EMS Tx Mode', TX_MODE: 'EMS Tx Mode',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{Bus|EMS Bus}}',
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',

View File

@@ -103,7 +103,7 @@ const it: Translation = {
DISABLED: 'disattivato', DISABLED: 'disattivato',
TX_MODE: 'EMS Modo Tx ', TX_MODE: 'EMS Modo Tx ',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{Bus|EMS Bus}}',
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',

View File

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

View File

@@ -103,7 +103,7 @@ const no: Translation = {
DISABLED: 'avslått', DISABLED: 'avslått',
TX_MODE: 'EMS Tx Mode', TX_MODE: 'EMS Tx Mode',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{Bus|EMS Bus}}',
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',

View File

@@ -103,7 +103,7 @@ const sk: Translation = {
DISABLED: 'zakázané', DISABLED: 'zakázané',
TX_MODE: 'EMS Tx režim', TX_MODE: 'EMS Tx režim',
HARDWARE: 'Hardware', HARDWARE: 'Hardware',
EMS_BUS: '{{BUS|EMS BUS}}', EMS_BUS: '{{Bus|EMS Bus}}',
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',

View File

@@ -103,7 +103,7 @@ const sv: Translation = {
DISABLED: 'inaktiverad', DISABLED: 'inaktiverad',
TX_MODE: 'EMS Tx-läge', TX_MODE: 'EMS Tx-läge',
HARDWARE: 'Hårdvara', HARDWARE: 'Hårdvara',
EMS_BUS: '{{BUSS|EMS-BUSS}}', EMS_BUS: '{{Buss|EMS-Buss}}',
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',

View File

@@ -103,7 +103,7 @@ const tr: Translation = {
DISABLED: 'devre dışı', DISABLED: 'devre dışı',
TX_MODE: 'EMS Tx Modu', TX_MODE: 'EMS Tx Modu',
HARDWARE: 'Donanım', HARDWARE: 'Donanım',
EMS_BUS: '{{HAT|EMS HATTI}}', EMS_BUS: '{{Hat|EMS Hatti}}',
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',

View File

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

View File

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

View File

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

View File

@@ -299,7 +299,7 @@ void AnalogSensor::reload(bool get_nvs) {
uint8_t r = v / 10000; uint8_t r = v / 10000;
uint8_t g = (v - r * 10000) / 100; uint8_t g = (v - r * 10000) / 100;
uint8_t b = v % 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); LOG_DEBUG("RGB set to %d, %d, %d", r, g, b);
} else if (sensor.type() == AnalogType::DIGITAL_OUT) { } else if (sensor.type() == AnalogType::DIGITAL_OUT) {
LOG_DEBUG("Digital Write on GPIO %02d", sensor.gpio()); 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 r = v / 10000;
uint8_t g = (v - r * 10000) / 100; uint8_t g = (v - r * 10000) / 100;
uint8_t b = v % 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); LOG_DEBUG("RGB set to %d, %d, %d", r, g, b);
} else if (sensor.type() == AnalogType::PULSE) { } else if (sensor.type() == AnalogType::PULSE) {
uint8_t v = val; uint8_t v = val;

View File

@@ -2312,7 +2312,6 @@ std::string EMSdevice::name() {
// returns true on success. // returns true on success.
int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result) { int EMSdevice::get_modbus_value(uint8_t tag, const std::string & shortname, std::vector<uint16_t> & result) {
// find device value by shortname // 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; }); 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)) { if (it == devicevalues_.end() && (it->short_name != shortname || it->tag != tag)) {
return -1; return -1;

View File

@@ -90,6 +90,7 @@ Network EMSESP::network_; // network services
TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors
AnalogSensor EMSESP::analogsensor_; // Analog sensors AnalogSensor EMSESP::analogsensor_; // Analog sensors
Shower EMSESP::shower_; // Shower logic Shower EMSESP::shower_; // Shower logic
LED EMSESP::led_; // LED handler
Preferences EMSESP::nvs_; // NV Storage Preferences EMSESP::nvs_; // NV Storage
// for a specific EMS device go and request data values // 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 // 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 // return total number of devices excluding the Controller
uint8_t EMSESP::count_devices() { uint8_t EMSESP::count_devices() {
if (emsdevices.empty()) { if (emsdevices.empty()) {
@@ -207,27 +193,6 @@ uint8_t EMSESP::count_devices() {
return count; 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 // scans for new devices
void EMSESP::scan_devices() { void EMSESP::scan_devices() {
EMSESP::clear_all_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 // show the EMS bus status plus both Rx and Tx queues
void EMSESP::show_ems(uuid::console::Shell & shell) { void EMSESP::show_ems(uuid::console::Shell & shell) {
// EMS bus information // EMS bus information
shell.printfln("EMS Bus ID: %02X", EMSbus::ems_bus_id());
switch (bus_status()) { switch (bus_status()) {
case BUS_STATUS_OFFLINE: case BUS_STATUS_OFFLINE:
shell.printfln("EMS Bus is disconnected."); shell.printfln("EMS Bus is disconnected.");
@@ -585,7 +551,7 @@ void EMSESP::publish_all(bool force) {
publish_other_values(); // switch and heat pump, ... publish_other_values(); // switch and heat pump, ...
publish_sensor_values(true); // includes temperature and analog sensors 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()) { if (Mqtt::ha_enabled()) {
Mqtt::ha_status(); Mqtt::ha_status();
} }
system_.send_heartbeat(); system_.send_heartbeat(); // send MQTT heartbeat topic
break; break;
default: default:
// all finished // all finished
@@ -1532,7 +1498,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
Roomctrl::check(data[1], data, length); Roomctrl::check(data[1], data, length);
#ifdef EMSESP_UART_DEBUG #ifdef EMSESP_UART_DEBUG
// get_uptime is only updated once per loop, does not give the right time // 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 #endif
// add to RxQueue for log/watch // add to RxQueue for log/watch
rxservice_.add(data, length); rxservice_.add(data, length);
@@ -1616,11 +1582,11 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
#ifdef EMSESP_UART_DEBUG #ifdef EMSESP_UART_DEBUG
char s[4]; char s[4];
if (first_value & 0x80) { 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 // 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 { } 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 #endif
// check for poll to us, if so send top message from Tx queue immediately and quit // 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; return;
} else { } else {
#ifdef EMSESP_UART_DEBUG #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 #endif
Roomctrl::check(data[1], data, length); // check if there is a message for the roomcontroller 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; bool factory_settings = false;
#endif #endif
#if defined(EMSESP_DEBUG) // #if defined(EMSESP_DEBUG)
// LOG_DEBUG("Listing root directory before:"); // LOG_DEBUG("Listing root directory before:");
// system_.listDir("/", 3); // show the contents of the root directory // system_.listDir("/", 3); // show the contents of the root directory
#endif // #endif
// start NVS storage // start NVS storage
if (!nvs_.begin("ems-esp", false, "nvs1")) { // try bigger nvs partition on 16M flash first 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) // loads core system services settings (mqtt, ap, ntp etc)
esp32React.begin(); esp32React.begin();
#if defined(EMSESP_DEBUG) // #if defined(EMSESP_DEBUG)
// LOG_DEBUG("Listing root directory after:"); // LOG_DEBUG("Listing root directory after:");
// system_.listDir("/", 3); // show the contents of the root directory // system_.listDir("/", 3); // show the contents of the root directory
#endif // #endif
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (factory_settings) { if (factory_settings) {
@@ -1866,7 +1832,7 @@ void EMSESP::loop() {
// handles LED and checks system health, and syslog service // handles LED and checks system health, and syslog service
if (system_.loop()) { 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 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 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 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 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 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 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); static void show_device_values(uuid::console::Shell & shell);
@@ -233,6 +231,7 @@ class EMSESP {
static TemperatureSensor temperaturesensor_; static TemperatureSensor temperaturesensor_;
static AnalogSensor analogsensor_; static AnalogSensor analogsensor_;
static Shower shower_; static Shower shower_;
static LED led_;
static RxService rxservice_; static RxService rxservice_;
static TxService txservice_; static TxService txservice_;
static Preferences nvs_; 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(restart)
MAKE_WORD(format) MAKE_WORD(format)
MAKE_WORD(txpause) MAKE_WORD(txpause)
MAKE_WORD(led)
MAKE_WORD(raw) MAKE_WORD(raw)
MAKE_WORD(watch) MAKE_WORD(watch)
MAKE_WORD(syslog) MAKE_WORD(syslog)
@@ -171,7 +172,6 @@ MAKE_WORD_CUSTOM(password_prompt, "Password: ")
MAKE_WORD_CUSTOM(unset, "<unset>") MAKE_WORD_CUSTOM(unset, "<unset>")
MAKE_WORD_CUSTOM(enable_mandatory, "<enable | disable>") MAKE_WORD_CUSTOM(enable_mandatory, "<enable | disable>")
MAKE_WORD_CUSTOM(service_mandatory, "<ap | mqtt | ntp>") MAKE_WORD_CUSTOM(service_mandatory, "<ap | mqtt | ntp>")
MAKE_WORD_CUSTOM(txpause_cmd, "enable/disable TX")
// more common names that don't need translations // more common names that don't need translations
MAKE_NOTRANSLATION(1x3min, "1x3min") 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(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(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(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 // tags
MAKE_WORD_TRANSLATION(tag_hc1, "hc1", "HK1", "hc1", "VK1", "OG1", "hc1", "hc1", "ID1", "hc1", "hc1", "hc1") 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; uint32_t Mqtt::mqtt_publish_fails_ = 0;
bool Mqtt::connecting_ = false; bool Mqtt::connecting_ = false;
bool Mqtt::initialized_ = false; bool Mqtt::initialized_ = false;
bool Mqtt::ha_climate_reset_ = false;
uint16_t Mqtt::queuecount_ = 0; uint16_t Mqtt::queuecount_ = 0;
uint8_t Mqtt::connectcount_ = 0; uint8_t Mqtt::connectcount_ = 0;
uint32_t Mqtt::mqtt_message_id_ = 0; uint32_t Mqtt::mqtt_message_id_ = 0;
@@ -131,10 +130,10 @@ void Mqtt::loop() {
uint32_t currentMillis = uuid::get_uptime(); 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_) { if (currentMillis - last_publish_heartbeat_ > publish_time_heartbeat_) {
last_publish_heartbeat_ = currentMillis; 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 // temperature and analog sensor publish on change
@@ -493,7 +492,6 @@ void Mqtt::on_connect() {
queue_unsubscribe_message(discovery_prefix_ + "/+/" + Mqtt::basename() + "/#"); queue_unsubscribe_message(discovery_prefix_ + "/+/" + Mqtt::basename() + "/#");
EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any 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_status(); // create the EMS-ESP device in HA, which is MQTT retained
ha_climate_reset(true);
} else { } else {
// with disabled HA we subscribe and the broker sends all stored HA-emsesp-configs. // 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") // 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 // send initial MQTT messages for some of our services
EMSESP::system_.send_heartbeat(); // send heartbeat EMSESP::system_.send_heartbeat(); // send heartbeat
// for publish on change publish the initial complete list
EMSESP::webCustomEntityService.publish(true); EMSESP::webCustomEntityService.publish(true);
EMSESP::webSchedulerService.publish(true); EMSESP::webSchedulerService.publish(true);
EMSESP::analogsensor_.publish_values(true); EMSESP::analogsensor_.publish_values(true);

View File

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

View File

@@ -257,7 +257,7 @@ NetPhase Network::initialPhase() const {
void Network::loop() { void Network::loop() {
#ifndef EMSESP_STANDALONE #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 // 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 = const uint32_t reconnectDelay =
(network_iface_ == NetIface::WIFI || network_iface_ == NetIface::ETHERNET) ? NETWORK_RECONNECTION_DELAY_LONG : NETWORK_RECONNECTION_DELAY_SHORT; (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) { 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::max_alloc_mem_;
uint32_t System::heap_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 // GPIOs
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::valid_system_gpios_; std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::valid_system_gpios_;
std::vector<System::GpioUsage, AllocatorPSRAM<System::GpioUsage>> System::used_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 // read specific major system settings to store locally for faster access
void System::store_settings(WebSettings & settings) { void System::store_settings(WebSettings & settings) {
version_ = settings.version;
rx_gpio_ = settings.rx_gpio; rx_gpio_ = settings.rx_gpio;
tx_gpio_ = settings.tx_gpio; tx_gpio_ = settings.tx_gpio;
pbutton_gpio_ = settings.pbutton_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; low_clock_ = settings.low_clock;
hide_led_ = settings.hide_led;
led_type_ = settings.led_type;
board_profile_ = settings.board_profile; board_profile_ = settings.board_profile;
telnet_enabled_ = settings.telnet_enabled; telnet_enabled_ = settings.telnet_enabled;
@@ -734,22 +720,24 @@ void System::start() {
}); });
commands_init(); // console & api commands commands_init(); // console & api commands
led_init(); // init LED EMSESP::led_.init(); // init LED
button_init(); // button button_init(); // button
last_system_check_ = 0; // force the LED to go from fast flash to pulse
uart_init(); // start UART uart_init(); // start UART
syslog_init(); // start syslog syslog_init(); // start syslog
modbus_init(); // start modbus 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) { void System::button_OnClick(PButton & b) {
LOG_NOTICE("Button pressed - single click"); LOG_NOTICE("Button pressed - single click");
#ifdef EMSESP_DEBUG
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
// show filesystem listDir("/", 3); // show filesystem
listDir("/", 3); #endif
// used to test LED monitoring and sequence_led. See system_check() for more details.
EMSESP::system_.healthcheck(99); // 99 = special trigger
#endif #endif
} }
@@ -757,7 +745,7 @@ void System::button_OnClick(PButton & b) {
// reconnect to AP by removing the SSID from the network settings // 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 // 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) { 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 #ifndef EMSESP_STANDALONE
// set AP mode to always so will join AP if wifi ssid fails to connect // set AP mode to always so will join AP if wifi ssid fails to connect
EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) { EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) {
@@ -773,55 +761,6 @@ void System::button_OnDblClick(PButton & b) {
#endif #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 // button long press
void System::button_OnLongPress(PButton & b) { void System::button_OnLongPress(PButton & b) {
LOG_NOTICE("Button pressed - long press - restart EMS-ESP"); LOG_NOTICE("Button pressed - long press - restart EMS-ESP");
@@ -831,7 +770,7 @@ void System::button_OnLongPress(PButton & b) {
// button indefinite press // button indefinite press
void System::button_OnVLongPress(PButton & b) { void System::button_OnVLongPress(PButton & b) {
LOG_NOTICE("Button pressed - very long press - perform factory reset"); 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 // push button
@@ -850,21 +789,7 @@ void System::button_init() {
#endif #endif
} }
// set the LED to on or off when in normal operating mode // init UART
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
}
}
void System::uart_init() { void System::uart_init() {
EMSuart::stop(); EMSuart::stop();
EMSuart::start(tx_mode_, rx_gpio_, tx_gpio_); // start UART, GPIOs have already been checked EMSuart::start(tx_mode_, rx_gpio_, tx_gpio_); // start UART, GPIOs have already been checked
@@ -879,18 +804,16 @@ bool System::loop() {
system_restart(); system_restart();
} }
// if LED flashing is active, run the LED flash myPButton_.check(); // check button press
if (led_flash_timer_) { system_check(); // System health check
led_flash();
return true; // is active // 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 #ifndef EMSESP_STANDALONE
// syslog service
if (syslog_enabled_) { if (syslog_enabled_) {
syslog_.loop(); syslog_.loop();
} }
@@ -898,7 +821,7 @@ bool System::loop() {
send_info_mqtt(); 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 // 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++); LOG_NOTICE("Ping test, #%d", ping_count++);
#endif #endif
if (healthcheck_ != 99) { // skip if we're testing
// check if we have a valid network connection // check if we have a valid network connection
if (!EMSESP::network_.network_connected()) { healthcheck_ = (healthcheck_ & ~HEALTHCHECK_NO_NETWORK) | (EMSESP::network_.network_connected() ? 0 : HEALTHCHECK_NO_NETWORK);
healthcheck_ |= HEALTHCHECK_NO_NETWORK;
} else {
healthcheck_ &= ~HEALTHCHECK_NO_NETWORK;
}
// check if we have a bus connection // check if we have a bus connection
if (!EMSbus::bus_connected()) { healthcheck_ = (healthcheck_ & ~HEALTHCHECK_NO_BUS) | (EMSbus::bus_connected() ? 0 : HEALTHCHECK_NO_BUS);
healthcheck_ |= HEALTHCHECK_NO_BUS;
} else { } else {
healthcheck_ &= ~HEALTHCHECK_NO_BUS; LOG_DEBUG("Healthcheck: testing mode");
healthcheck_ = 0; // make it all look healthy - this is temporary for one cycle
} }
// 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; static uint8_t last_healthcheck_ = 0;
if (healthcheck_ != last_healthcheck_) { if (healthcheck_ != last_healthcheck_) {
last_healthcheck_ = healthcheck_; last_healthcheck_ = healthcheck_;
EMSESP::system_.send_heartbeat();
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);
}
}
} }
} }
} }
@@ -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_(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_(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_(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_(watch), System::command_watch, FL_(watch_cmd));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(message), System::command_message, FL_(message_cmd)); Command::add(EMSdevice::DeviceType::SYSTEM, F_(message), System::command_message, FL_(message_cmd));
#if defined(EMSESP_TEST) #if defined(EMSESP_TEST)
@@ -1109,98 +1014,6 @@ void System::commands_init() {
Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback 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 % // Return the quality (Received Signal Strength Indicator) of the WiFi network as a %
// High quality: 90% ~= -55dBm // High quality: 90% ~= -55dBm
// Medium quality: 50% ~= -75dBm // Medium quality: 50% ~= -75dBm
@@ -1406,7 +1219,6 @@ void System::show_system(uuid::console::Shell & shell) {
#endif #endif
} }
// see if there is a restore of an older settings file that needs to be applied // 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 // note there can be only one file at a time
bool System::check_restore() { bool System::check_restore() {
@@ -1436,6 +1248,22 @@ bool System::check_restore() {
saveSettings(MQTT_SETTINGS_FILE, section); saveSettings(MQTT_SETTINGS_FILE, section);
saveSettings(NTP_SETTINGS_FILE, section); saveSettings(NTP_SETTINGS_FILE, section);
saveSettings(SECURITY_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); saveSettings(EMSESP_SETTINGS_FILE, section);
} }
if (section_type == "schedule") { if (section_type == "schedule") {
@@ -1685,8 +1513,12 @@ bool System::check_upgrade() {
// force web buffer to 25 for those boards without psram // force web buffer to 25 for those boards without psram
if ((EMSESP::system_.PSram() == 0) && (settings.weblog_buffer != 25)) { if ((EMSESP::system_.PSram() == 0) && (settings.weblog_buffer != 25)) {
settings.weblog_buffer = 25; settings.weblog_buffer = 25;
// 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::CHANGED;
} }
}
return StateUpdateResult::UNCHANGED; return StateUpdateResult::UNCHANGED;
}); });
} else if (this_version < settings_version) { } else if (this_version < settings_version) {
@@ -1944,14 +1776,12 @@ bool System::command_service(const char * cmd, const char * value) {
settings.hide_led = b; settings.hide_led = b;
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}); });
EMSESP::system_.hide_led(b);
ok = true; ok = true;
} else if (!strcmp(cmd, "settings/analogenabled")) { } else if (!strcmp(cmd, "settings/analogenabled")) {
EMSESP::webSettingsService.update([&](WebSettings & settings) { EMSESP::webSettingsService.update([&](WebSettings & settings) {
settings.analog_enabled = b; settings.analog_enabled = b;
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
}); });
EMSESP::system_.analog_enabled(b);
ok = true; ok = true;
} else if (!strcmp(cmd, "mqtt/enabled")) { } else if (!strcmp(cmd, "mqtt/enabled")) {
EMSESP::esp32React.getMqttSettingsService()->update([&](MqttSettings & Settings) { 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["ethPhyAddr"] = settings.eth_phy_addr;
node["ethClockMmode"] = settings.eth_clock_mode; node["ethClockMmode"] = settings.eth_clock_mode;
} }
node["rxGPIO"] = EMSESP::system_.rx_gpio_; node["rxGPIO"] = settings.rx_gpio;
node["txGPIO"] = EMSESP::system_.tx_gpio_; node["txGPIO"] = settings.tx_gpio;
node["dallasGPIO"] = EMSESP::system_.dallas_gpio_; node["dallasGPIO"] = settings.dallas_gpio;
node["pbuttonGPIO"] = EMSESP::system_.pbutton_gpio_; node["pbuttonGPIO"] = settings.pbutton_gpio;
node["ledGPIO"] = EMSESP::system_.led_gpio_; node["ledGPIO"] = settings.led_gpio;
node["ledType"] = settings.led_type; node["ledType"] = settings.led_type;
} }
node["hideLed"] = settings.hide_led; node["hideLed"] = settings.hide_led;
@@ -2842,6 +2672,39 @@ bool System::load_board_profile(std::vector<int8_t> & data, const std::string &
return true; 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) // txpause command - temporarily pause the TX, by setting Txmode to 0 (disabled)
bool System::command_txpause(const char * value, const int8_t id) { bool System::command_txpause(const char * value, const int8_t id) {
bool arg; bool arg;

View File

@@ -26,6 +26,7 @@
#include "console.h" #include "console.h"
#include "mqtt.h" #include "mqtt.h"
#include "telegram.h" #include "telegram.h"
#include "led.h"
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
#include <esp_wifi.h> #include <esp_wifi.h>
@@ -37,8 +38,6 @@
#include <uuid/log.h> #include <uuid/log.h>
#include <PButton.h> #include <PButton.h>
#define EMSESP_RGB_WRITE rgbLedWrite
#if CONFIG_IDF_TARGET_ESP32 #if CONFIG_IDF_TARGET_ESP32
// there is no official API available on the original ESP32 // there is no official API available on the original ESP32
extern "C" { extern "C" {
@@ -54,8 +53,6 @@ using uuid::console::Shell;
#define EMSESP_CUSTOMSUPPORT_FILE "/config/customSupport.json" #define EMSESP_CUSTOMSUPPORT_FILE "/config/customSupport.json"
#define RGB_LED_BRIGHTNESS 20 // 255 is max brightness
namespace emsesp { namespace emsesp {
enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110, PHY_TYPE_RTL8201 }; 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_service(const char * cmd, const char * value);
static bool command_sendmail(const char * value, const int8_t id); 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_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 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); 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); static bool uploadFirmwareURL(const char * url = nullptr);
void led_init();
void button_init(); void button_init();
void commands_init(); void commands_init();
void uart_init(); void uart_init();
@@ -166,10 +163,6 @@ class System {
static String get_ip_or_hostname(); static String get_ip_or_hostname();
void dallas_gpio(uint8_t gpio) {
dallas_gpio_ = gpio;
}
bool telnet_enabled() { bool telnet_enabled() {
return telnet_enabled_; return telnet_enabled_;
} }
@@ -190,18 +183,6 @@ class System {
return modbus_timeout_; 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() { bool readonly_mode() {
return readonly_mode_; return readonly_mode_;
} }
@@ -354,6 +335,11 @@ class System {
static bool set_partition(const char * partitionname); 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: private:
static uuid::log::Logger logger_; 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_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) 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 // 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_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 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 #else
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // do a system check every 5 seconds static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // do a system check every 5 seconds
#endif #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 #ifndef EMSESP_STANDALONE
static uuid::syslog::SyslogService syslog_; static uuid::syslog::SyslogService syslog_;
#endif #endif
void led_monitor();
void system_check(); void system_check();
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> string_range_to_vector(const std::string & range, const std::string & exclude = ""); 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 // EMS-ESP settings
std::string hostname_; std::string hostname_;
String locale_; String locale_;
bool hide_led_;
uint8_t led_type_;
uint8_t led_gpio_;
bool analog_enabled_;
bool low_clock_; bool low_clock_;
String board_profile_; String board_profile_;
uint8_t pbutton_gpio_; uint8_t pbutton_gpio_;
uint8_t rx_gpio_; uint8_t rx_gpio_;
uint8_t tx_gpio_; uint8_t tx_gpio_;
uint8_t tx_mode_; uint8_t tx_mode_;
uint8_t dallas_gpio_;
bool telnet_enabled_; bool telnet_enabled_;
bool syslog_enabled_; bool syslog_enabled_;
int8_t syslog_level_; int8_t syslog_level_;
@@ -451,7 +417,6 @@ class System {
uint8_t bool_format_; uint8_t bool_format_;
uint8_t enum_format_; uint8_t enum_format_;
bool readonly_mode_; bool readonly_mode_;
String version_;
bool modbus_enabled_; bool modbus_enabled_;
uint16_t modbus_port_; uint16_t modbus_port_;
uint8_t modbus_max_clients_; 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 // start and initialize Tx
// send out request to EMS bus for all devices // send out request to EMS bus for all devices
void TxService::start() { void TxService::start() {
// grab the bus ID and tx_mode // grab the EMS Bus ID and tx_mode
EMSESP::webSettingsService.read([&](WebSettings const & settings) { EMSESP::webSettingsService.read([&](WebSettings const & settings) {
ems_bus_id(settings.ems_bus_id); ems_bus_id(settings.ems_bus_id);
tx_mode(settings.tx_mode); tx_mode(settings.tx_mode);

View File

@@ -174,7 +174,7 @@ class EMSbus {
} }
static void set_ems2() { static void set_ems2() {
isEMS2_ = true;; isEMS2_ = true;
} }
static uint8_t ems_mask() { static uint8_t ems_mask() {
@@ -203,7 +203,7 @@ class EMSbus {
// checks every 30 seconds if the EMS bus is still alive // checks every 30 seconds if the EMS bus is still alive
static bool bus_connected() { static bool bus_connected() {
#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST) #if defined(EMSESP_STANDALONE)
return true; return true;
#else #else
if ((uuid::get_uptime() - last_bus_activity_) > EMS_BUS_TIMEOUT) { 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 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 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_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_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 uint8_t tx_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
static bool isEMS2_; static bool isEMS2_;

View File

@@ -47,13 +47,10 @@ void TemperatureSensor::start(const bool factory_settings) {
// load settings // load settings
void TemperatureSensor::reload() { 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) { EMSESP::webSettingsService.read([&](WebSettings const & settings) {
dallas_gpio_ = settings.dallas_gpio; dallas_gpio_ = settings.dallas_gpio;
parasite_ = settings.dallas_parasite; parasite_ = settings.dallas_parasite;
}); });
EMSESP::system_.dallas_gpio(dallas_gpio_); // set to system for checks
for (auto & sensor : sensors_) { for (auto & sensor : sensors_) {
remove_ha_topic(sensor.id()); 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 // THESE ONLY WORK WITH AN ESP32, not in standalone/native mode
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (command == "ls") { if (command == "ls") {
listDir(LittleFS, "/", 3); EMSESP::system_.listDir("/", 3);
ok = true; 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"); 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") { if (command == "shuntingyard") {
shell.printfln("Testing shunting yard..."); shell.printfln("Testing shunting yard...");

View File

@@ -64,6 +64,7 @@ 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"
#ifndef EMSESP_DEBUG_DEFAULT #ifndef EMSESP_DEBUG_DEFAULT
#define EMSESP_DEBUG_DEFAULT "general" #define EMSESP_DEBUG_DEFAULT "general"

View File

@@ -387,7 +387,7 @@ void WebSettingsService::onUpdate() {
} }
if (WebSettings::has_flags(WebSettings::ChangeFlags::LED)) { if (WebSettings::has_flags(WebSettings::ChangeFlags::LED)) {
EMSESP::system_.led_init(); EMSESP::led_.init();
} }
if (WebSettings::has_flags(WebSettings::ChangeFlags::MQTT)) { 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 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)) { if (!(latest_version > current_version)) {
return 1; // if moving from below 3.8.x to 3.9.x return 1 return 0; // no upgrade (same version or downgrade)
} }
if (latest_version > current_version && current_version.major() < latest_version.major()) { if (current_version < FirmwareVersion("3.9.0") && latest_version.major() == 3 && latest_version.minor() == 9) {
return 2; // if it's a major version upgrade return 2 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()) { if (current_version.major() < latest_version.major()) {
return 0; // if it's just a minor version upgrade return 0 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 // action = getVersions