Compare commits

...
7 Commits
Author SHA1 Message Date
Proddy 491bb01088 Merge pull request #3253 from proddy/dev
replacement for rgbLedWrite() to support new v6 of the WS2812B driver
2026-09-24 18:44:32 +02:00
Proddy 37d571b6e9 Merge branch 'emsesp:dev' into dev 2026-09-24 18:43:33 +02:00
proddy f05105861e update doc 2026-09-24 18:43:02 +02:00
proddy 09e8ef5403 pkg update 2026-09-24 17:29:06 +02:00
proddy 100edcefe2 optimize LED and fix some edge cases 2026-09-24 17:21:41 +02:00
proddy eddc443d72 replacement for rgbLedWrite() to support new v6 of the WS2812B driver 2026-09-24 11:29:14 +02:00
proddy 40c4fdd2d1 move hardcoded consts to header files 2026-09-24 11:28:14 +02:00
30 changed files with 467 additions and 273 deletions
+18
View File
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.8.6] 24 September 2026
## Added
- own rgb_led library supporting WS2812B V5 and the newer V6 RGB LEDs
## [3.8.5] 22 September 2026
## Added
- Change webUI message when upgrading to a major version of the firmware, in preparation for the upcoming 3.9.0 release.
## [3.8.4] 4 August 2026
## Fixed
- Allow auto-download and install of firmware versions which are smaller to allow update to the latest 3.9.0-development version.
## [3.8.3] 1 August 2026
## Added
+2 -1
View File
@@ -18,7 +18,7 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- remove HA config for excluded entities [#3229](https://github.com/emsesp/EMS-ESP32/issues/3229)
- `show system` reports the Ethernet status on boards with a PHY, so a failed initialisation or a link that never came up is visible instead of Ethernet being omitted completely. Also shows the loop task's lowest free stack, to help diagnose reboots
- new Application Setting "Automatically check for firmware updates", off by default. When enabled EMS-ESP asks emsesp.org for the latest version once a day on its own, otherwise it only asks when the WebUI wants to show it, so an unattended system never contacts emsesp.org
- Automatically check and convert older 3.8.x filesystem to the new 3.9.0 format
- automatically check and convert older 3.8.x filesystem to the new 3.9.0 format
## Fixed
@@ -56,4 +56,5 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- multiple optimization in network, power down WiFi when Ethernet and WiFi nosleep is instant when selected [#3213](https://github.com/emsesp/EMS-ESP32/pull/3213)
- TLS buffers (~17KB per HTTPS request) are taken from PSRAM on boards that have it, instead of fragmenting the internal heap
- failed versions.json fetches back off from 5 up to 160 minutes instead of retrying every 5 minutes, as each attempt blocks the main loop for as long as the DNS, connect and read timeouts allow
- optimized the LED library to support WS2812B V5 and the newer V6 RGB LEDs
+1 -1
View File
@@ -50,7 +50,7 @@
"eslint-config-prettier": "^10.1.8",
"etag": "^1.8.1",
"mime-types": "^3.0.2",
"prettier": "^3.9.8",
"prettier": "^3.9.9",
"rollup-plugin-visualizer": "^7.1.1",
"terser": "^5.51.2",
"typescript": "^6.0.3",
+87 -87
View File
@@ -217,7 +217,7 @@ importers:
version: 2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))
'@trivago/prettier-plugin-sort-imports':
specifier: ^6.0.2
version: 6.0.2(prettier@3.9.8)(supports-color@10.2.2)
version: 6.0.2(prettier@3.9.9)(supports-color@10.2.2)
'@types/node':
specifier: ^26.6.2
version: 26.6.2
@@ -243,11 +243,11 @@ importers:
specifier: ^3.0.2
version: 3.0.2
prettier:
specifier: ^3.9.8
version: 3.9.8
specifier: ^3.9.9
version: 3.9.9
rollup-plugin-visualizer:
specifier: ^7.1.1
version: 7.1.1(rolldown@1.2.9)
version: 7.1.1(rolldown@1.2.10)
terser:
specifier: ^5.51.2
version: 5.51.2
@@ -627,8 +627,8 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@oxc-project/types@0.150.0':
resolution: {integrity: sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==}
'@oxc-project/types@0.151.0':
resolution: {integrity: sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==}
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
@@ -661,98 +661,98 @@ packages:
'@remix-run/route-pattern@0.22.1':
resolution: {integrity: sha512-czdGJWh09Lapvcqt7Vb09KlrU2PhRJ8xQaI+AItTuD9aDJ5MAgxnS38lnys6Ll+6RJEqPiaUqTwIQhVLwFvv+w==}
'@rolldown/binding-android-arm-eabi@1.2.9':
resolution: {integrity: sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==}
'@rolldown/binding-android-arm-eabi@1.2.10':
resolution: {integrity: sha512-bp9svZb+QurZeh+8H4BhrZkifEB0YBNvTVzNSJnJQkj4NrRwmQoDUCGP0vSN7PbvLeM7l1tK6GXL8mrTiH2myg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
'@rolldown/binding-android-arm64@1.2.9':
resolution: {integrity: sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==}
'@rolldown/binding-android-arm64@1.2.10':
resolution: {integrity: sha512-wm6Dld3RXUAZ/gRWKyUy+4W1B5CB5UeFaOzsSWJWEdxZXHH8rCYiZ5dGe6oJmhsunAPWzL7FZV+VtvmN5Ye2eA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@rolldown/binding-darwin-arm64@1.2.9':
resolution: {integrity: sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==}
'@rolldown/binding-darwin-arm64@1.2.10':
resolution: {integrity: sha512-UbEfXq/AqGNgRTV3ik+X/iR6mUxu2QdYAadwRxJWquUGnW6gDqdP1FtLtFXRow7RJx0ssRwi80XAPr4r+4DtsA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.2.9':
resolution: {integrity: sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==}
'@rolldown/binding-darwin-x64@1.2.10':
resolution: {integrity: sha512-7f5h17q5KZVx/ji1vb8OTq31ch1O2I7K8NPIr44GkyWTApXMIsmhWqZfgpOH10xeauqghDAvGlZktasCkcF6Eg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@rolldown/binding-freebsd-x64@1.2.9':
resolution: {integrity: sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==}
'@rolldown/binding-freebsd-x64@1.2.10':
resolution: {integrity: sha512-ynOk/eEYhC6ZB2xCGvKrEOwE58oBy9LnrAqtkrDF9Fz1VTaNdGZTsV0VarJdhPwb+sOJTGjCLwcuyRJZ1dnMcQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.2.9':
resolution: {integrity: sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==}
'@rolldown/binding-linux-arm-gnueabihf@1.2.10':
resolution: {integrity: sha512-ERrAs185meZZhGan7a4l3RiiJK1ArSDlHdST++uvSxe+FDbR4TwUPahT/cbZJvaG6fIpDpF78surN+tX708Y4Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.2.9':
resolution: {integrity: sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==}
'@rolldown/binding-linux-arm64-gnu@1.2.10':
resolution: {integrity: sha512-KN7OHKD0J3jy1UzBwZWPxpwhODf9IARUIJcrH+yLYKOcmegZ8luEUM38lDP1bDVj40yP6PsSzCqOJF76vljFnQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.2.9':
resolution: {integrity: sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==}
'@rolldown/binding-linux-arm64-musl@1.2.10':
resolution: {integrity: sha512-8l9wP8O+wa8zD6iw6egSfzVtu7oZVfH3hlUsMM4MwbLMhxleqeoXbZzjddyK3YyNlwLhqznq3tF7PkNJ8T/V2w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.2.9':
resolution: {integrity: sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==}
'@rolldown/binding-linux-ppc64-gnu@1.2.10':
resolution: {integrity: sha512-SeXNKeQzA5kLhz/J0CH6ZP0/HJ3v1xm/0YbiYpE0kK7emfRC2OIGGIaE14xzkISEGv2aYuUSpiLiU5Gbq+OI0A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.2.9':
resolution: {integrity: sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==}
'@rolldown/binding-linux-s390x-gnu@1.2.10':
resolution: {integrity: sha512-mtht0nR+y8/hart4175Ll15w7lY8dg7CtQ+j2FDNTsDRspOWTK/2V3l0aj9sIj7XmvqxT8Yli/wq22e7feTTWg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.2.9':
resolution: {integrity: sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==}
'@rolldown/binding-linux-x64-gnu@1.2.10':
resolution: {integrity: sha512-FSM94nGd55NYo48usCyM/nHfUKRnqc9+b0vJNuKV0oCCpIp/OGims7rO1Nv/DkFkt0S/s2rxsJ2kkS8J3HcpeA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.2.9':
resolution: {integrity: sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==}
'@rolldown/binding-linux-x64-musl@1.2.10':
resolution: {integrity: sha512-C3YxNB16myRLs7o+B+6PnQ6jBsdIS4+AE4Ah8glVGhDpEv9AOvxhZ/1duAb4B0UGczEK/lBbccksd8VI+p6zfw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.2.9':
resolution: {integrity: sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==}
'@rolldown/binding-openharmony-arm64@1.2.10':
resolution: {integrity: sha512-571TlE/F1eeTjjdjYAMMMPs1Mfv3MtX6s3+ZKVU6HiUjZ5Njc6c/qzNy/8K3zALTZnaw3JQVYrHxvNfjm43KAg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@rolldown/binding-win32-arm64-msvc@1.2.9':
resolution: {integrity: sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==}
'@rolldown/binding-win32-arm64-msvc@1.2.10':
resolution: {integrity: sha512-QXW+ZWaiqs2c7Fi++D/SsW07LTPcUrncxcskJGfGNBoaLik1IU6fJymz4HsqwEO0u5Iq11yTO0B/mc4cPk7jrQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.2.9':
resolution: {integrity: sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==}
'@rolldown/binding-win32-x64-msvc@1.2.10':
resolution: {integrity: sha512-5FQFGgah17YeMtG1Yd5a+rMxQpTksyNXxRtKz06FVTaQw3RKYUJQbUoKk0/5jrXBpDo+7makNP7UHA2LQyH64A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
@@ -1338,8 +1338,8 @@ packages:
duplexer3@0.1.5:
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
electron-to-chromium@1.5.434:
resolution: {integrity: sha512-7EeFW9OLf1NN9NKQP9xdOE/XKqjxv7JDPP6AEAkHli11+Fae1OaLoNuAf13hQv1PPNtuES0pLQSPk2+fS3s8ww==}
electron-to-chromium@1.5.438:
resolution: {integrity: sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==}
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -2318,8 +2318,8 @@ packages:
node-html-parser@6.1.13:
resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==}
node-releases@2.0.56:
resolution: {integrity: sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==}
node-releases@2.0.57:
resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==}
engines: {node: '>=18'}
normalize-package-data@2.5.0:
@@ -2555,8 +2555,8 @@ packages:
resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==}
engines: {node: '>=4'}
prettier@3.9.8:
resolution: {integrity: sha512-WRFq3Wn3WId7LLROfMLdH7xaFr2jR62wU8nLO6rQUOLOxNZUviyJQs1M0iIhLexSFy+L+w0ch66wtoO2jRjG0A==}
prettier@3.9.9:
resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==}
engines: {node: '>=14'}
hasBin: true
@@ -2686,8 +2686,8 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rolldown@1.2.9:
resolution: {integrity: sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==}
rolldown@1.2.10:
resolution: {integrity: sha512-OxkA08pSryMK7B3XiFA09B4OJ1xJMPgIYCBMY2xchzpqgBGsV1o0DetPAE+Sl3N3L4oCPiEzmHVSOj7iR04Zog==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
@@ -3591,7 +3591,7 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.20.3
'@oxc-project/types@0.150.0': {}
'@oxc-project/types@0.151.0': {}
'@popperjs/core@2.11.8': {}
@@ -3638,49 +3638,49 @@ snapshots:
'@remix-run/route-pattern@0.22.1': {}
'@rolldown/binding-android-arm-eabi@1.2.9':
'@rolldown/binding-android-arm-eabi@1.2.10':
optional: true
'@rolldown/binding-android-arm64@1.2.9':
'@rolldown/binding-android-arm64@1.2.10':
optional: true
'@rolldown/binding-darwin-arm64@1.2.9':
'@rolldown/binding-darwin-arm64@1.2.10':
optional: true
'@rolldown/binding-darwin-x64@1.2.9':
'@rolldown/binding-darwin-x64@1.2.10':
optional: true
'@rolldown/binding-freebsd-x64@1.2.9':
'@rolldown/binding-freebsd-x64@1.2.10':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.2.9':
'@rolldown/binding-linux-arm-gnueabihf@1.2.10':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.2.9':
'@rolldown/binding-linux-arm64-gnu@1.2.10':
optional: true
'@rolldown/binding-linux-arm64-musl@1.2.9':
'@rolldown/binding-linux-arm64-musl@1.2.10':
optional: true
'@rolldown/binding-linux-ppc64-gnu@1.2.9':
'@rolldown/binding-linux-ppc64-gnu@1.2.10':
optional: true
'@rolldown/binding-linux-s390x-gnu@1.2.9':
'@rolldown/binding-linux-s390x-gnu@1.2.10':
optional: true
'@rolldown/binding-linux-x64-gnu@1.2.9':
'@rolldown/binding-linux-x64-gnu@1.2.10':
optional: true
'@rolldown/binding-linux-x64-musl@1.2.9':
'@rolldown/binding-linux-x64-musl@1.2.10':
optional: true
'@rolldown/binding-openharmony-arm64@1.2.9':
'@rolldown/binding-openharmony-arm64@1.2.10':
optional: true
'@rolldown/binding-win32-arm64-msvc@1.2.9':
'@rolldown/binding-win32-arm64-msvc@1.2.10':
optional: true
'@rolldown/binding-win32-x64-msvc@1.2.9':
'@rolldown/binding-win32-x64-msvc@1.2.10':
optional: true
'@rolldown/pluginutils@1.0.1': {}
@@ -3707,7 +3707,7 @@ snapshots:
react-virtualized-auto-sizer: 1.0.26(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
react-window: 1.8.11(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.8)(supports-color@10.2.2)':
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.9)(supports-color@10.2.2)':
dependencies:
'@babel/generator': 7.29.8
'@babel/parser': 7.29.9
@@ -3717,7 +3717,7 @@ snapshots:
lodash-es: 4.18.1
minimatch: 9.0.9
parse-imports-exports: 0.2.4
prettier: 3.9.8
prettier: 3.9.9
transitivePeerDependencies:
- supports-color
@@ -4009,8 +4009,8 @@ snapshots:
dependencies:
baseline-browser-mapping: 2.11.25
caniuse-lite: 1.0.30001810
electron-to-chromium: 1.5.434
node-releases: 2.0.56
electron-to-chromium: 1.5.438
node-releases: 2.0.57
update-browserslist-db: 1.3.3(browserslist@4.29.0)
buffer-alloc-unsafe@1.1.0: {}
@@ -4374,7 +4374,7 @@ snapshots:
duplexer3@0.1.5: {}
electron-to-chromium@1.5.434: {}
electron-to-chromium@1.5.438: {}
emoji-regex@10.6.0: {}
@@ -5311,7 +5311,7 @@ snapshots:
css-select: 5.2.2
he: 1.2.0
node-releases@2.0.56: {}
node-releases@2.0.57: {}
normalize-package-data@2.5.0:
dependencies:
@@ -5515,7 +5515,7 @@ snapshots:
prepend-http@2.0.0: {}
prettier@3.9.8: {}
prettier@3.9.9: {}
process-nextick-args@2.0.1: {}
@@ -5645,35 +5645,35 @@ snapshots:
dependencies:
glob: 7.2.3
rolldown@1.2.9:
rolldown@1.2.10:
dependencies:
'@oxc-project/types': 0.150.0
'@oxc-project/types': 0.151.0
'@rolldown/pluginutils': 1.0.1
optionalDependencies:
'@rolldown/binding-android-arm-eabi': 1.2.9
'@rolldown/binding-android-arm64': 1.2.9
'@rolldown/binding-darwin-arm64': 1.2.9
'@rolldown/binding-darwin-x64': 1.2.9
'@rolldown/binding-freebsd-x64': 1.2.9
'@rolldown/binding-linux-arm-gnueabihf': 1.2.9
'@rolldown/binding-linux-arm64-gnu': 1.2.9
'@rolldown/binding-linux-arm64-musl': 1.2.9
'@rolldown/binding-linux-ppc64-gnu': 1.2.9
'@rolldown/binding-linux-s390x-gnu': 1.2.9
'@rolldown/binding-linux-x64-gnu': 1.2.9
'@rolldown/binding-linux-x64-musl': 1.2.9
'@rolldown/binding-openharmony-arm64': 1.2.9
'@rolldown/binding-win32-arm64-msvc': 1.2.9
'@rolldown/binding-win32-x64-msvc': 1.2.9
'@rolldown/binding-android-arm-eabi': 1.2.10
'@rolldown/binding-android-arm64': 1.2.10
'@rolldown/binding-darwin-arm64': 1.2.10
'@rolldown/binding-darwin-x64': 1.2.10
'@rolldown/binding-freebsd-x64': 1.2.10
'@rolldown/binding-linux-arm-gnueabihf': 1.2.10
'@rolldown/binding-linux-arm64-gnu': 1.2.10
'@rolldown/binding-linux-arm64-musl': 1.2.10
'@rolldown/binding-linux-ppc64-gnu': 1.2.10
'@rolldown/binding-linux-s390x-gnu': 1.2.10
'@rolldown/binding-linux-x64-gnu': 1.2.10
'@rolldown/binding-linux-x64-musl': 1.2.10
'@rolldown/binding-openharmony-arm64': 1.2.10
'@rolldown/binding-win32-arm64-msvc': 1.2.10
'@rolldown/binding-win32-x64-msvc': 1.2.10
rollup-plugin-visualizer@7.1.1(rolldown@1.2.9):
rollup-plugin-visualizer@7.1.1(rolldown@1.2.10):
dependencies:
open: 11.0.4
picomatch: 4.0.7
source-map: 0.8.0
yargs: 18.2.0
optionalDependencies:
rolldown: 1.2.9
rolldown: 1.2.10
run-applescript@7.1.0: {}
@@ -6032,7 +6032,7 @@ snapshots:
lightningcss: 1.33.0
picomatch: 4.0.7
postcss: 8.5.28
rolldown: 1.2.9
rolldown: 1.2.10
tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 26.6.2
-2
View File
@@ -148,7 +148,5 @@ double ledcSetup(uint8_t chan, double freq, uint8_t bit_num) {
void ledcAttachPin(uint8_t pin, uint8_t chan) {};
void ledcAttach(uint8_t pin, uint8_t chan, uint8_t bit_num) {};
void ledcWrite(uint8_t chan, uint32_t duty) {};
void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val) {};
void rgbLedWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val) {};
#endif
-2
View File
@@ -83,8 +83,6 @@ double ledcSetup(uint8_t chan, double freq, uint8_t bit_num);
void ledcAttachPin(uint8_t pin, uint8_t chan);
void ledcAttach(uint8_t pin, uint8_t chan, uint8_t bit_num);
void ledcWrite(uint8_t chan, uint32_t duty);
void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val);
void rgbLedWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val);
inline int rtc_get_reset_reason(int cpu_no) {
return 0;
+1 -1
View File
@@ -15,7 +15,7 @@
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"prettier": "^3.9.8"
"prettier": "^3.9.9"
},
"packageManager": "pnpm@12.6.0"
}
+8 -8
View File
@@ -175,10 +175,10 @@ importers:
devDependencies:
'@trivago/prettier-plugin-sort-imports':
specifier: ^6.0.2
version: 6.0.2(prettier@3.9.8)
version: 6.0.2(prettier@3.9.9)
prettier:
specifier: ^3.9.8
version: 3.9.8
specifier: ^3.9.9
version: 3.9.9
packages:
@@ -303,8 +303,8 @@ packages:
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
prettier@3.9.8:
resolution: {integrity: sha512-WRFq3Wn3WId7LLROfMLdH7xaFr2jR62wU8nLO6rQUOLOxNZUviyJQs1M0iIhLexSFy+L+w0ch66wtoO2jRjG0A==}
prettier@3.9.9:
resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==}
engines: {node: '>=14'}
hasBin: true
@@ -373,7 +373,7 @@ snapshots:
'@msgpack/msgpack@3.1.3': {}
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.8)':
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.9)':
dependencies:
'@babel/generator': 7.29.8
'@babel/parser': 7.29.9
@@ -383,7 +383,7 @@ snapshots:
lodash-es: 4.18.1
minimatch: 9.0.9
parse-imports-exports: 0.2.4
prettier: 3.9.8
prettier: 3.9.9
transitivePeerDependencies:
- supports-color
@@ -421,4 +421,4 @@ snapshots:
picocolors@1.1.1: {}
prettier@3.9.8: {}
prettier@3.9.9: {}
+1 -1
View File
@@ -786,7 +786,6 @@ negcond
negheat
nein
nennvolumentstrom
neopixel
netcmp
netflowtemp
netif
@@ -1352,3 +1351,4 @@ zyxwvutsrqponmlkjihgfedcba
öffnen
česky
živanović
neopixel
+1 -1
View File
@@ -71,7 +71,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
_is_filesystem = true;
_md5[0] = '\0'; // clear any stale md5 so Update.end() doesn't compare against it
_md5_applied = false;
} else if ((extension == "bin") && (filesize > 1000000)) {
} else if ((extension == "bin") && (filesize >= emsesp::System::MIN_FIRMWARE_SIZE)) {
_is_firmware = true;
} else if (extension == "json") {
_md5[0] = '\0'; // clear md5
+5 -5
View File
@@ -307,7 +307,7 @@ void AnalogSensor::reload(bool get_nvs) {
uint8_t r = v / 10000;
uint8_t g = (v - r * 10000) / 100;
uint8_t b = v % 100;
rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b);
rgb_led::write(sensor.gpio(), 2 * r, 2 * g, 2 * b);
LOG_DEBUG("RGB set to %d, %d, %d", r, g, b);
} else if (sensor.type() == AnalogType::DIGITAL_OUT) {
LOG_DEBUG("Digital Write on GPIO %02d", sensor.gpio());
@@ -405,8 +405,8 @@ void AnalogSensor::measure() {
sensor.sum_ = (sensor.sum_ * 15 + a * 16) / 16;
sensor.analog_ = sensor.sum_ / 16;
}
if (sensor.analog_ > 0 && sensor.analog_ < 3300 && (sensor.last_reading_ + 1 < sensor.analog_ || sensor.last_reading_ > sensor.analog_ + 1)) {
sensor.set_value(sensor.offset() + 1 / (1 / T25 + log((double)sensor.analog_ / (3300 - sensor.analog_) * (Rt / R0)) / Beta)
if (sensor.analog_ > 0 && sensor.analog_ < NTC_VREF_MV && (sensor.last_reading_ + 1 < sensor.analog_ || sensor.last_reading_ > sensor.analog_ + 1)) {
sensor.set_value(sensor.offset() + 1 / (1 / T25 + log((double)sensor.analog_ / (NTC_VREF_MV - sensor.analog_) * (Rt / R0)) / Beta)
- T0); // Temperature in Celsius
sensor.last_reading_ = sensor.analog_;
sensorreads_++;
@@ -424,7 +424,7 @@ void AnalogSensor::measure() {
edgecnt[index] = 0;
portEXIT_CRITICAL_ISR(&mux);
sensor.set_value(sensor.factor() * 1000000.0 / t);
} else if (micros() - edge[index] > 10000000ul && sensor.value() > 0) {
} else if (micros() - edge[index] > FREQ_ZERO_TIMEOUT_US && sensor.value() > 0) {
sensor.set_value(0);
}
if (sensor.value() != oldval) {
@@ -999,7 +999,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
uint8_t r = v / 10000;
uint8_t g = (v - r * 10000) / 100;
uint8_t b = v % 100;
rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b);
rgb_led::write(sensor.gpio(), 2 * r, 2 * g, 2 * b);
LOG_DEBUG("RGB set to %d, %d, %d", r, g, b);
} else if (sensor.type() == AnalogType::PULSE) {
uint8_t v = val;
+2
View File
@@ -189,8 +189,10 @@ class AnalogSensor {
static constexpr double T25 = 298.15;
static constexpr double R0 = 100000;
static constexpr double Rt = 60000;
static constexpr uint32_t NTC_VREF_MV = 3300; // NTC voltage divider supply (in mV) - 3.3 V
static constexpr uint8_t MAX_SENSORS = 20;
static constexpr uint32_t MEASURE_ANALOG_INTERVAL = 500;
static constexpr uint32_t FREQ_ZERO_TIMEOUT_US = 10000000; // report 0 Hz if no edge arrives for this long (in us) - 10 seconds
static uuid::log::Logger logger_;
void remove_ha_topic(const int8_t type, const uint8_t id) const;
+2 -2
View File
@@ -2281,8 +2281,8 @@ bool EMSdevice::handle_telegram(const std::shared_ptr<const Telegram> & telegram
EMSESP::logger().debug("This telegram (%s) is not recognized by the EMS bus", tf.telegram_type_name_);
#endif
// removing fetch after start causes issue: https://github.com/emsesp/EMS-ESP32/issues/1420
// continue retry the first 5 minutes, then disable (added 15.3.2024)
if (uuid::get_uptime_sec() > 600) {
// continue retrying for a while after boot, then disable (added 15.3.2024)
if (uuid::get_uptime_sec() > FETCH_RETRY_DURATION_SEC) {
tf.fetch_ = false;
}
return false;
+3
View File
@@ -441,6 +441,9 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20;
// keep fetching telegrams the bus master doesn't recognise for this long after boot, then give up (in seconds) - 10 minutes
static constexpr uint32_t FETCH_RETRY_DURATION_SEC = 600;
// static device IDs
static constexpr uint8_t EMS_DEVICE_ID_BOILER = 0x08; // fixed device_id for Master Boiler/UBA
static constexpr uint8_t EMS_DEVICE_ID_HS1 = 0x70; // fixed device_id for 1st. Cascade Boiler/UBA
+2 -2
View File
@@ -1978,12 +1978,12 @@ void EMSESP::loop() {
}
}
// reset status after 5 Minutes
// reset status after a timeout
if (EMSESP::system_.systemStatus() != SYSTEM_STATUS::SYSTEM_STATUS_NORMAL) {
static uint32_t starttime = 0;
if (starttime == 0) {
starttime = uuid::get_uptime_ms();
} else if (uuid::get_uptime_ms() - starttime > 300000) {
} else if (uuid::get_uptime_ms() - starttime > SYSTEM_STATUS_RESET_TIMEOUT) {
starttime = 0;
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL);
}
+2
View File
@@ -291,6 +291,8 @@ class EMSESP {
static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute
static constexpr uint8_t EMS_WAIT_KM_TIMEOUT = 60; // wait one minute
static constexpr uint32_t SYSTEM_STATUS_RESET_TIMEOUT = 300000; // return a non-normal system status to normal after this (in ms) - 5 minutes
struct Device_record {
uint8_t product_id;
EMSdevice::DeviceType device_type;
+3 -3
View File
@@ -66,13 +66,13 @@ int HttpClient::request(std::string url, const std::string & method, const std::
ssl_client->setInsecure();
// Notes: with root CA we should set here: ssl_client->setCACert(rootCACert);
// 1 KB RX buffer is fine for small JSON-style endpoints used by the scheduler/shunting-yard but it is NOT enough for servers that send full-size TLS records (>1 KB)
ssl_client->setBufferSizes(16384, 1024);
ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds)
ssl_client->setBufferSizes(TLS_RX_BUFFER_SIZE, TLS_TX_BUFFER_SIZE);
ssl_client->setSessionTimeout(TLS_SESSION_TIMEOUT_S);
}
// WiFiClient is NetworkClient, which declares no setTimeout() of its own - calling it binds to
// Stream::setTimeout() and only affects readBytes(), leaving the socket on the core's 3s default
basic_client->setConnectionTimeout(CONNECT_TIMEOUT_MS);
ssl_client->setTimeout(5); // seconds, drives BearSSL only - unused on the plain HTTP path
ssl_client->setTimeout(TLS_READ_TIMEOUT_S); // drives BearSSL only - unused on the plain HTTP path
ssl_client->setClient(basic_client, is_https); // enableSSL = false for plain HTTP
const uint16_t port = is_https ? 443 : 80;
+8
View File
@@ -29,6 +29,14 @@ class HttpClient {
public:
static int request(std::string url, const std::string & method, const std::string & value, JsonObjectConst headers, std::string & result);
// ESP_SSLClient (BearSSL) settings, shared by every TLS client in the project.
// The RX buffer must hold one full TLS record: servers such as GitHub's CDN send up-to-16 KB
// records and don't negotiate max_fragment_length, so a smaller buffer can't decode the body
static constexpr size_t TLS_RX_BUFFER_SIZE = 16384;
static constexpr size_t TLS_TX_BUFFER_SIZE = 1024;
static constexpr uint32_t TLS_SESSION_TIMEOUT_S = 120; // in seconds, the library minimum
static constexpr uint32_t TLS_READ_TIMEOUT_S = 5; // in seconds, not ms like the socket timeouts
private:
static constexpr uint32_t CONNECT_TIMEOUT_MS = 5000; // TCP connect, the core defaults to 3s
static constexpr uint32_t FIRST_BYTE_TIMEOUT_MS = 8000; // how long the server may take to start replying
+151 -69
View File
@@ -19,19 +19,139 @@
#include "led.h"
#include "emsesp.h"
#ifndef EMSESP_STANDALONE
#include <esp32-hal-rmt.h>
#endif
namespace emsesp {
namespace rgb_led {
#ifndef EMSESP_STANDALONE
namespace {
// durations in 100 ns ticks
constexpr uint16_t T0H = 3; // 300 ns
constexpr uint16_t T0L = 9; // 900 ns
constexpr uint16_t T1H = 8; // 800 ns
constexpr uint16_t T1L = 6; // 600 ns
// rmt_data_t.val layout: duration0:15, level0:1, duration1:15, level1:1 (high then low)
constexpr uint32_t SYMBOL_0 = T0H | (1UL << 15) | (static_cast<uint32_t>(T0L) << 16);
constexpr uint32_t SYMBOL_1 = T1H | (1UL << 15) | (static_cast<uint32_t>(T1L) << 16);
constexpr uint32_t RMT_FREQ_HZ = 10000000; // 10 MHz
constexpr uint32_t RESET_US = 300;
constexpr uint8_t NO_PIN = 0xFF;
constexpr uint8_t MAX_PINS = 2; // status LED + an RGB analog sensor; the ESP32-C3 only has 2 RMT TX channels
struct Channel {
uint8_t pin = NO_PIN;
uint32_t last_tx_us = 0;
};
Channel channels[MAX_PINS];
Channel * init(uint8_t pin) {
Channel * free_slot = nullptr;
for (auto & ch : channels) {
if (ch.pin == pin) {
return &ch;
}
if (!free_slot && ch.pin == NO_PIN) {
free_slot = &ch;
}
}
if (!free_slot) {
// all slots in use: release the least recently written one
const uint32_t now = micros();
free_slot = &channels[0];
for (auto & ch : channels) {
if (now - ch.last_tx_us > now - free_slot->last_tx_us) {
free_slot = &ch;
}
}
end(free_slot->pin);
}
if (!rmtInit(pin, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, RMT_FREQ_HZ)) {
return nullptr;
}
free_slot->pin = pin;
free_slot->last_tx_us = micros() - RESET_US; // no wait on first write
return free_slot;
}
} // namespace
bool write(uint8_t pin, uint8_t red, uint8_t green, uint8_t blue, Order order) {
Channel * ch = init(pin);
if (!ch) {
return false;
}
// enforce the latch/reset gap since the previous frame (V6 needs > 280 us)
const uint32_t elapsed = micros() - ch->last_tx_us;
if (elapsed < RESET_US) {
delayMicroseconds(RESET_US - elapsed);
}
const uint8_t bytes[3] = {order == Order::GRB ? green : red, order == Order::GRB ? red : green, blue};
rmt_data_t frame[24];
for (uint8_t i = 0; i < 24; i++) {
frame[i].val = (bytes[i >> 3] & (0x80 >> (i & 7))) ? SYMBOL_1 : SYMBOL_0;
}
const bool ok = rmtWrite(pin, frame, 24, RMT_WAIT_FOR_EVER);
ch->last_tx_us = micros();
return ok;
}
void end(uint8_t pin) {
for (auto & ch : channels) {
if (ch.pin == pin && pin != NO_PIN) {
rmtDeinit(pin);
ch.pin = NO_PIN;
}
}
}
#else
bool write(uint8_t, uint8_t, uint8_t, uint8_t, Order) {
return true;
}
void end(uint8_t) {
}
#endif
} // namespace rgb_led
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() {
// settings may have changed: turn off and release the previous pin
if (led_gpio_) {
set_led(Color::OFF);
if (led_type_) {
rgb_led::end(led_gpio_);
}
}
// copy the application settings
EMSESP::webSettingsService.read([&](WebSettings & settings) {
led_gpio_ = settings.led_gpio;
led_type_ = settings.led_type;
hide_led_ = settings.hide_led;
});
last_color_ = COLOR_UNSET;
if (!led_gpio_) { // 0 means disabled
LOG_INFO("LED disabled");
@@ -74,10 +194,6 @@ bool LED::loop(uint8_t healthcheck, bool button_busy) {
// check the system health.
// Set the sequence accordingly, only if the healthcheck is not 0 and has changed
if (healthcheck != previous_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();
@@ -85,25 +201,12 @@ bool LED::loop(uint8_t healthcheck, bool button_busy) {
// 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;
const bool no_network = (healthcheck & System::HEALTHCHECK_NO_NETWORK) == System::HEALTHCHECK_NO_NETWORK;
const 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
}
color_steps_[0] = no_network ? Color::RED : (no_bus ? Color::BLUE : Color::OFF);
color_steps_[1] = no_network ? Color::RED : Color::OFF;
color_steps_[2] = (no_network && no_bus) ? Color::BLUE : Color::OFF;
}
previous_healthcheck_ = healthcheck; // must be set after reset_led(), which invalidates it
@@ -126,9 +229,7 @@ bool LED::loop(uint8_t healthcheck, bool button_busy) {
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;
color_steps_[0] = color_steps_[1] = color_steps_[2] = Color::OFF;
// invalidate the last health state so the sequence is rebuilt on the next check,
// needed because the color steps above have been cleared
@@ -163,9 +264,10 @@ void LED::led_fast_flash() {
// 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_) {
if (!led_gpio_ || color == last_color_) {
return;
}
last_color_ = color;
// RGB lookup table indexed by Color enum (must match enum order in led.h)
static constexpr uint8_t B = RGB_LED_BRIGHTNESS;
@@ -181,28 +283,26 @@ void LED::set_led(Color color) {
{0, B, B}, // CYAN
{H, 0, H} // PINK
};
static_assert(sizeof(rgb_table) / sizeof(rgb_table[0]) == Color::PINK + 1, "rgb_table must match the Color enum");
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];
const uint8_t * rgb = rgb_table[color];
if (led_type_) {
rgbLedWrite(led_gpio_, red, green, blue);
rgb_led::write(led_gpio_, rgb[0], rgb[1], rgb[2]);
} else {
digitalWrite(led_gpio_, (red == 0 && green == 0 && blue == 0) || color == Color::OFF ? !LED_ON : LED_ON);
digitalWrite(led_gpio_, 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) {
bool LED::set_custom_led_routine(const std::string & color, const std::string & pattern) {
static constexpr struct {
const char * name;
Color value;
} color_map[] = {
{"", Color::OFF},
{"off", Color::OFF},
{"on", Color::ON},
{"white", Color::ON},
@@ -228,35 +328,33 @@ bool LED::set_custom_led_routine(std::string color, std::string pattern) {
return false;
}
// reset the color steps
color_steps_[0] = Color::OFF;
color_steps_[1] = Color::OFF;
color_steps_[2] = Color::OFF;
// build the steps locally so an unrecognized pattern leaves the current sequence untouched
Color steps[3] = {Color::OFF, Color::OFF, Color::OFF};
// blink patterns
if (pattern == "blink1") {
color_steps_[0] = color_type;
steps[0] = color_type;
} else if (pattern == "blink2") {
color_steps_[0] = color_type;
color_steps_[1] = color_type;
steps[0] = steps[1] = color_type;
} else if (pattern == "blink3") {
color_steps_[0] = color_type;
color_steps_[1] = color_type;
color_steps_[2] = color_type;
steps[0] = steps[1] = 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;
steps[0] = Color::RED;
steps[1] = Color::GREEN;
steps[2] = Color::BLUE;
} else if (pattern == "cpc") {
color_steps_[0] = Color::CYAN;
color_steps_[1] = Color::PINK;
color_steps_[2] = Color::CYAN;
steps[0] = Color::CYAN;
steps[1] = Color::PINK;
steps[2] = Color::CYAN;
} else {
return false; // pattern not recognized
}
color_steps_[0] = steps[0];
color_steps_[1] = steps[1];
color_steps_[2] = steps[2];
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
@@ -286,7 +384,7 @@ void LED::sequence_led() {
if (++led_flash_step_ == 8) {
// finished first iteration, reset the whole sequence, turn off LED
led_long_timer_ = uuid::get_uptime();
led_long_timer_ = current_time;
led_flash_step_ = 0;
set_led(Color::OFF); // turn off the LED
@@ -298,24 +396,8 @@ void LED::sequence_led() {
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
}
// odd steps 3, 5 and 7 show the three configured colors, even steps turn the LED off to make it flash
set_led(led_flash_step_ % 2 ? color_steps_[(led_flash_step_ - 3) / 2] : Color::OFF);
}
}
+40 -14
View File
@@ -24,6 +24,29 @@
namespace emsesp {
// WS2812B driver replacing arduino-esp32's rgbLedWrite()/neopixelWrite(), which hard-code
// T0H = 400 ns, T1L = 400 ns and have no reset gap. The WS2812B-2020-V6 needs T0H 220-380 ns,
// T1L 580-1000 ns and a reset > 280 us, otherwise it misreads 0-bits as 1-bits (washed-out white).
// Timings used (10 MHz RMT clock, 1 tick = 100 ns) fit both the original WS2812B and the V6:
//
// sent old WS2812B WS2812B-2020-V6
// T0H 300 ns 250-550 ns 220-380 ns
// T0L 900 ns 700-1000 ns 580-1000 ns
// T1H 800 ns 650-950 ns 580-1000 ns
// T1L 600 ns 300-600 ns 580-1000 ns
// Reset >300 us >50 us >280 us
//
// Do not mix with rgbLedWrite()/neopixelWrite() on the same pin, as both claim the pin's RMT channel.
namespace rgb_led {
enum class Order : uint8_t { GRB, RGB }; // WS2812B is GRB
// Blocks ~30 us for the transmit, plus up to the reset gap if called again right after the previous update
bool write(uint8_t pin, uint8_t red, uint8_t green, uint8_t blue, Order order = Order::GRB);
void end(uint8_t pin); // release the pin's RMT channel, e.g. before reusing the pin for something else
} // namespace rgb_led
class LED {
public:
enum Color : uint8_t {
@@ -42,7 +65,7 @@ class LED {
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);
bool set_custom_led_routine(const std::string & color, const std::string & pattern);
private:
static uuid::log::Logger logger_;
@@ -59,28 +82,31 @@ class LED {
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
static constexpr Color COLOR_UNSET = static_cast<Color>(0xFF); // forces the next set_led() to write
// led_fast_flash() state
uint32_t led_flash_start_time_ = 0;
uint32_t led_flash_duration_ = 0;
uint32_t last_toggle_time_ = 0;
// sequence_led() state
uint32_t led_long_timer_ = 1; // 1 will kick it off immediately
uint32_t led_short_timer_ = 0;
// 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;
bool led_fast_flash_timer_ = false;
bool led_flash_state_ = false;
// led_flash() state
bool led_flash_state_ = false;
uint32_t last_toggle_time_ = 0;
bool last_button_busy_ = false;
uint8_t led_flash_step_ = 0; // 0 means we're not in the short flash timer
// 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
// set_custom_led_routine() and healthcheck state
Color color_steps_[3] = {Color::OFF, Color::OFF, Color::OFF};
Color last_color_ = COLOR_UNSET; // last color written, to skip redundant writes
// 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;
+1 -1
View File
@@ -643,7 +643,7 @@ bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, con
// check free mem
#ifndef EMSESP_STANDALONE
// if (ESP.getFreeHeap() < 60 * 1024 || ESP.getMaxAllocHeap() < 40 * 1024) {
if (heap_caps_get_free_size(MALLOC_CAP_8BIT) < 60 * 1024) { // checks free Heap+PSRAM
if (heap_caps_get_free_size(MALLOC_CAP_8BIT) < MQTT_MIN_FREE_HEAP) { // checks free Heap+PSRAM
if (operation == Operation::PUBLISH) {
mqtt_message_id_++;
mqtt_publish_fails_++;
+3 -2
View File
@@ -61,9 +61,10 @@ class Mqtt {
enum Operation : uint8_t { PUBLISH, SUBSCRIBE, UNSUBSCRIBE };
enum NestedFormat : uint8_t { NESTED = 1, SINGLE };
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // fixed, not a user setting anymore
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // fixed, not a user setting anymore
static constexpr uint16_t MQTT_QUEUE_MAX_SIZE = 300;
static constexpr uint32_t MQTT_DISCONNECT_TIMEOUT = 1000; // max ms to flush a graceful disconnect on non-PSRAM boards
static constexpr uint32_t MQTT_DISCONNECT_TIMEOUT = 1000; // max ms to flush a graceful disconnect on non-PSRAM boards
static constexpr uint32_t MQTT_MIN_FREE_HEAP = 60 * 1024; // don't queue any MQTT message below this much free heap+PSRAM (in bytes) - 60 KB
static void on_connect();
static void on_disconnect(espMqttClientTypes::DisconnectReason reason);
+34 -37
View File
@@ -33,6 +33,7 @@
#include "firmwareVersion.h"
#include "shuntingYard.h" // for compute() used by the message and sendmail commands
#include "httpClient.h" // for the shared TLS client settings
#if defined(EMSESP_TEST)
#include "../test/test.h"
@@ -218,7 +219,7 @@ bool System::command_sendmail(const char * value, const int8_t) {
ESP_SSLClient * ssl_client = nullptr;
SMTPClient * smtp = nullptr;
basic_client->setTimeout(5000);
basic_client->setTimeout(SMTP_TIMEOUT_MS);
const bool implicit_ssl = (security == EMAIL_SECURITY::SSL);
const bool start_tls = (security == EMAIL_SECURITY::STARTTLS);
@@ -229,8 +230,8 @@ bool System::command_sendmail(const char * value, const int8_t) {
} else {
ssl_client = new ESP_SSLClient;
ssl_client->setInsecure();
ssl_client->setBufferSizes(16384, 1024);
ssl_client->setTimeout(5);
ssl_client->setBufferSizes(HttpClient::TLS_RX_BUFFER_SIZE, HttpClient::TLS_TX_BUFFER_SIZE);
ssl_client->setTimeout(HttpClient::TLS_READ_TIMEOUT_S);
// enableSSL is baked in at setClient() — true only for implicit SSL (port 465)
ssl_client->setClient(basic_client, implicit_ssl);
if (start_tls) {
@@ -2923,11 +2924,13 @@ bool System::load_board_profile(std::vector<int8_t> & data, const std::string &
// 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
// color is optional
// pattern is
// blink1 for 1 time
// blink2 for 2 times
// blink3 for 3 times
// rgb for RGB
// cpc for CPC
// 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) {
@@ -2936,16 +2939,19 @@ bool System::command_led(const char * value, const int8_t) {
}
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;
std::string pattern;
auto sep = arg.find(':');
if (sep == std::string::npos) {
pattern = arg; // no color given, only a pattern
} else {
color = arg.substr(0, sep);
pattern = arg.substr(sep + 1);
}
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.");
LOG_ERROR("Invalid color or pattern");
return false;
}
@@ -3093,8 +3099,7 @@ void System::ntp_connected(bool b) {
// get NTP status
bool System::ntp_connected() {
// timeout 2 hours, ntp sync is normally every hour.
if ((uuid::get_uptime_sec() - ntp_last_check_ > 7201) && ntp_connected_) {
if ((uuid::get_uptime_sec() - ntp_last_check_ > NTP_TIMEOUT_SEC) && ntp_connected_) {
ntp_connected(false);
}
@@ -3200,20 +3205,14 @@ bool System::uploadFirmwareURL(const char * url) {
if (is_https) {
ssl_client.setInsecure(); // no CA validation, matches the rest of the project
// BearSSL needs a receive buffer large enough to hold one full TLS record.
// GitHub's release-assets CDN sends standard up-to-16 KB records and does NOT
// negotiate max_fragment_length, so a small (e.g. 1 KB) RX buffer makes the
// body unreadable (headers still fit one small record, hence Content-Length
// looks fine, but the first body record cannot be decoded). 16384 + overhead
// is the safe value the library itself uses by default; we go a bit smaller
// to be friendlier to 4 MB / no-PSRAM boards while still big enough for any
// record the CDN actually sends in practice.
ssl_client.setBufferSizes(16384, 1024);
ssl_client.setSessionTimeout(120);
// a small (e.g. 1 KB) RX buffer makes the body unreadable: the headers still fit one small
// record, hence Content-Length looks fine, but the first body record cannot be decoded
ssl_client.setBufferSizes(HttpClient::TLS_RX_BUFFER_SIZE, HttpClient::TLS_TX_BUFFER_SIZE);
ssl_client.setSessionTimeout(HttpClient::TLS_SESSION_TIMEOUT_S);
}
basic_client.setTimeout(15000); // socket-level read timeout
ssl_client.setTimeout(15); // Stream::readBytes timeout used by Update
ssl_client.setClient(&basic_client, is_https); // enableSSL = false for plain HTTP
basic_client.setTimeout(FIRMWARE_UPLOAD_READ_TIMEOUT_S * 1000); // socket-level read timeout, in ms
ssl_client.setTimeout(FIRMWARE_UPLOAD_READ_TIMEOUT_S); // Stream::readBytes timeout used by Update
ssl_client.setClient(&basic_client, is_https); // enableSSL = false for plain HTTP
const uint16_t port = is_https ? 443 : 80;
String url_remain = saved_url.substring(scheme_len);
@@ -3248,9 +3247,9 @@ bool System::uploadFirmwareURL(const char * url) {
ssl_client.println("Connection: close");
ssl_client.print("\r\n");
// wait for the first byte (up to 8s, matching the previous HTTP timeout)
// wait for the first byte (matching the previous HTTP timeout)
uint32_t ms = millis();
while (ssl_client.connected() && !ssl_client.available() && millis() - ms < 8000) {
while (ssl_client.connected() && !ssl_client.available() && millis() - ms < FIRMWARE_UPLOAD_RESPONSE_TIMEOUT) {
delay(1);
}
@@ -3326,7 +3325,7 @@ bool System::uploadFirmwareURL(const char * url) {
// wait for the first byte of the body so the read loop sees real data
// (headers and body may arrive in separate TLS records)
uint32_t body_wait = millis();
while (ssl_client.connected() && !ssl_client.available() && millis() - body_wait < 8000) {
while (ssl_client.connected() && !ssl_client.available() && millis() - body_wait < FIRMWARE_UPLOAD_RESPONSE_TIMEOUT) {
delay(1);
}
if (!ssl_client.available()) {
@@ -3341,7 +3340,7 @@ bool System::uploadFirmwareURL(const char * url) {
}
// check we have a valid size
if (firmware_size < 1677721) { // 1.6MB or greater is required
if (firmware_size < (int)MIN_FIRMWARE_SIZE) {
LOG_ERROR("Firmware upload failed - invalid size");
return false; // error
}
@@ -3360,12 +3359,10 @@ bool System::uploadFirmwareURL(const char * url) {
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING);
// explicit chunked read loop instead of Update.writeStream():
constexpr size_t CHUNK_SIZE = 1024;
constexpr uint32_t READ_TIMEOUT_MS = 30000; // overall stall timeout per chunk
uint8_t buf[CHUNK_SIZE];
size_t total_read = 0;
bool magic_ok = false;
int last_pct = -1;
uint8_t buf[FIRMWARE_UPLOAD_CHUNK_SIZE];
size_t total_read = 0;
bool magic_ok = false;
int last_pct = -1;
while (total_read < (size_t)firmware_size) {
// a cancel is signalled by the WebUI dropping the status below UPLOADING (back to NORMAL)
@@ -3386,7 +3383,7 @@ bool System::uploadFirmwareURL(const char * url) {
if (!ssl_client.connected()) {
break;
}
if (millis() - wait_start > READ_TIMEOUT_MS) {
if (millis() - wait_start > FIRMWARE_UPLOAD_STALL_TIMEOUT) {
break;
}
// also bail out promptly if a cancel arrives mid-stall
@@ -3407,8 +3404,8 @@ bool System::uploadFirmwareURL(const char * url) {
}
size_t want = (size_t)firmware_size - total_read;
if (want > CHUNK_SIZE) {
want = CHUNK_SIZE;
if (want > FIRMWARE_UPLOAD_CHUNK_SIZE) {
want = FIRMWARE_UPLOAD_CHUNK_SIZE;
}
size_t n = stream->readBytes(buf, want);
+15
View File
@@ -370,6 +370,9 @@ class System {
static constexpr uint8_t HEALTHCHECK_NO_NETWORK = (1 << 1); // 2
static constexpr uint8_t HEALTHCHECK_RESET = (1 << 7); // 128
// smallest .bin accepted as a firmware image, for both URL and web uploads - 1.6 MB
static constexpr size_t MIN_FIRMWARE_SIZE = 1677721;
private:
static uuid::log::Logger logger_;
@@ -402,6 +405,18 @@ class System {
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // do a system check every 5 seconds
#endif
// NTP syncs about every hour, so allow one missed sync before treating it as lost (in seconds) - 2 hours
static constexpr uint32_t NTP_TIMEOUT_SEC = 7201;
// email
static constexpr uint32_t SMTP_TIMEOUT_MS = 5000; // socket read timeout (in ms) - 5 seconds
// firmware upload from URL
static constexpr uint32_t FIRMWARE_UPLOAD_RESPONSE_TIMEOUT = 8000; // max wait for the first byte of the response headers/body (in ms) - 8 seconds
static constexpr uint32_t FIRMWARE_UPLOAD_READ_TIMEOUT_S = 15; // socket and Stream::readBytes timeout used by Update (in seconds)
static constexpr uint32_t FIRMWARE_UPLOAD_STALL_TIMEOUT = 30000; // abort if no data arrives for a chunk (in ms) - 30 seconds
static constexpr size_t FIRMWARE_UPLOAD_CHUNK_SIZE = 1024; // read buffer, on the stack
#ifndef EMSESP_STANDALONE
static uuid::syslog::SyslogService syslog_;
#endif
+1 -1
View File
@@ -286,7 +286,7 @@ void TxService::send_poll() const {
uint8_t TxService::get_send_id() {
static uint32_t count = 0;
if (!tx_telegrams_.empty() && tx_telegrams_.front().telegram_->src != ems_bus_id()) {
if (++count > 500) { // after 500 polls (~3-10 sec) there will be no master poll for this id
if (++count > MAX_POLLS_WITHOUT_MASTER) {
tx_telegrams_.pop_front();
count = 0;
return tx_telegrams_.empty() ? ems_bus_id() : tx_telegrams_.front().telegram_->src;
+3
View File
@@ -440,6 +440,9 @@ class TxService : public EMSbus {
static constexpr uint8_t MAXIMUM_TX_RETRIES = 3;
static constexpr uint32_t POST_SEND_DELAY = 2000;
// drop a queued Tx telegram sent from another id if the master hasn't polled that id after this many polls (~3-10 seconds)
static constexpr uint32_t MAX_POLLS_WITHOUT_MASTER = 500;
private:
std::deque<QueuedTxTelegram, AllocatorPSRAM<QueuedTxTelegram>> tx_telegrams_; // the Tx queue
+1 -1
View File
@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.9.0-dev.13"
#define EMSESP_APP_VERSION "3.9.0-dev.14"
+2 -2
View File
@@ -76,9 +76,9 @@ void WebLogService::operator<<(std::shared_ptr<uuid::log::Message> message) {
limit_log_messages_ = maximum_log_messages_;
} else {
uint32_t maxAlloc = ESP.getMaxAllocHeap();
if (limit_log_messages_ > 5 && maxAlloc < (50 * 1024)) { // 50k
if (limit_log_messages_ > 5 && maxAlloc < LOG_HEAP_LOW) {
--limit_log_messages_;
} else if (limit_log_messages_ < maximum_log_messages_ && maxAlloc > (60 * 1024)) { // 60k
} else if (limit_log_messages_ < maximum_log_messages_ && maxAlloc > LOG_HEAP_HIGH) {
++limit_log_messages_;
}
}
+5
View File
@@ -43,6 +43,11 @@ class WebLogService : public uuid::log::Handler {
virtual void operator<<(std::shared_ptr<uuid::log::Message> message);
private:
// on boards without PSRAM, shrink the log buffer below LOW and grow it again above HIGH (max alloc heap, in bytes).
// The gap between them stops the limit flapping around a single threshold
static constexpr uint32_t LOG_HEAP_LOW = 50 * 1024; // 50 KB
static constexpr uint32_t LOG_HEAP_HIGH = 60 * 1024; // 60 KB
AsyncEventSource events_;
class QueuedLogMessage {
+65 -30
View File
@@ -1,53 +1,88 @@
#!/bin/sh
#!/usr/bin/env bash
#
# Command line test for the API
#
emsesp_url="http://ems-esp.local"
emsesp_url="http://ems-espT.local"
# get the token from the Security page. This is the token for the admin user, unless changed it'll always be the same
emsesp_token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWV9.2bHpWya2C7Q12WjNUBD6_7N3RCD7CMl-EGhyQVzFdDg"
# This example will get the system info, via a GET request
curl -X GET ${emsesp_url}/api/system/info
echo "\n"
test0() {
curl -X GET ${emsesp_url}/api/system/info
}
# This example will execute a read command on product ID 8 and type ID 1
curl -X POST \
-H "Authorization: Bearer ${emsesp_token}" \
-H "Content-Type: application/json" \
-d '{"data":"8 1"}' \
${emsesp_url}/api/system/read
echo "\n"
test1() {
curl -X POST \
-H "Authorization: Bearer ${emsesp_token}" \
-H "Content-Type: application/json" \
-d '{"data":"8 1"}' \
${emsesp_url}/api/system/read
}
# This example will use the message command to get the locale (en)
curl -X POST \
-H "Authorization: Bearer ${emsesp_token}" \
-H "Content-Type: application/json" \
-d '{"value":"system/settings/locale"}' \
${emsesp_url}/api/system/message
echo "\n"
test2() {
curl -X POST \
-H "Authorization: Bearer ${emsesp_token}" \
-H "Content-Type: application/json" \
-d '{"value":"system/settings/locale"}' \
${emsesp_url}/api/system/message
}
# This example will export all values to a json file, including custom entities, sensors and schedules
curl -X POST \
-H "Authorization: Bearer ${emsesp_token}" \
-H "Content-Type: application/json" \
-d '{"action":"export", "param":"allvalues"}' \
${emsesp_url}/rest/action
echo "\n"
test3() {
curl -X POST \
-H "Authorization: Bearer ${emsesp_token}" \
-H "Content-Type: application/json" \
-d '{"action":"export", "param":"allvalues"}' \
${emsesp_url}/rest/action
}
# Get all versions
curl -X POST \
test4() {
curl -X POST \
-H "Authorization: Bearer ${emsesp_token}" \
-H "Content-Type: application/json" \
-d '{"action":"getVersions"}' \
${emsesp_url}/rest/action
-d '{"action":"getVersions"}' \
${emsesp_url}/rest/action
}
# led tests
test5() {
curl -X POST \
-H "Authorization: Bearer ${emsesp_token}" \
-H "Content-Type: application/json" \
-d '{"data":"red:blink1"}' \
${emsesp_url}/api/system/led
}
test6() {
curl -X POST \
-H "Authorization: Bearer ${emsesp_token}" \
-H "Content-Type: application/json" \
-d '{"data":"rgb"}' \
${emsesp_url}/api/system/led
}
# Create a list of test function names to run
tests_to_run=(
# test0
# test1
# test2
# test3
# test4
test5
test6
)
for test_func in "${tests_to_run[@]}"; do
echo "Running $test_func..."
$test_func
echo ""
done
echo "\n"
# This example is how to call a service in Home Assistant via the API
# Which can be added to an EMS-EPS schedule