Compare commits

...
80 Commits
Author SHA1 Message Date
Proddy afb8efec53 Merge pull request #3194 from MichaelDvP/dev
small changes and fixes from #3181
2026-08-20 13:32:06 +02:00
MichaelDvP 84b23f19e5 dev.4 2026-08-20 13:23:05 +02:00
MichaelDvP 668d59a2cd update modbus 2026-08-20 12:44:32 +02:00
MichaelDvP 88f258d7e9 use http_request for version cache 2026-08-20 12:44:21 +02:00
MichaelDvP c45441e3be cleanup sample_value 2026-08-20 12:37:10 +02:00
MichaelDvP 2994535158 add back burnminpower for 0xe6, #3193 2026-08-20 12:36:35 +02:00
MichaelDvP fdfb560cf9 min value for coolstart, #3192 2026-08-20 12:35:55 +02:00
MichaelDvP d56c5897fa fix standalone 2026-08-19 16:40:51 +02:00
MichaelDvP cec0fbc95d revert dev7 mqtt changes 2026-08-19 16:37:10 +02:00
MichaelDvP 8f118e07a3 fix standalone 2026-08-19 16:30:08 +02:00
MichaelDvP b05ae6dbbb fix http timeout setting, dev.8 2026-08-19 15:46:11 +02:00
MichaelDvP 469ca8bf05 revert mqtt dev7 changes 2026-08-19 11:49:29 +02:00
MichaelDvP edd1677526 http timeouts 2026-08-19 11:49:04 +02:00
MichaelDvP c363e0e2c8 do not rebuild all HA configs on a mqtt reconnect, limit by heap 2026-08-17 14:11:42 +02:00
MichaelDvP d397a14b41 prevent unsuccessful mqtt connections, dev7 2026-08-17 11:37:19 +02:00
MichaelDvP 822096b635 detect network disconnect faster 2026-08-17 11:36:18 +02:00
MichaelDvP 0defebfdb6 update pkg 2026-08-17 11:35:40 +02:00
MichaelDvP 574c4e45d5 val_tpl for undefined: 'None' 2026-08-16 10:16:34 +02:00
MichaelDvP 48c118fed7 typo 2026-08-14 15:35:34 +02:00
MichaelDvP ef4335156c remote emulation for UI800 is RT800 2026-08-13 09:49:24 +02:00
MichaelDvP 4d4269cdf7 hc/control setting for UI800 thermostat #3181 2026-08-12 11:44:11 +02:00
MichaelDvP 95a8b78d1d revert commit #9c992d8 2026-08-12 08:54:05 +02:00
MichaelDvP 982b0f304e test with 'None' for undefined HA values 2026-08-11 09:36:30 +02:00
MichaelDvP 1d9a0b6257 set boottime for RTC (if rtc is set) 2026-08-10 10:11:43 +02:00
MichaelDvP 52486ed5b4 HA val_tpl for undefined boottime None 2026-08-10 10:11:01 +02:00
Proddy 73f2634acc Merge pull request #3186 from proddy/dev
more minor fixes, Eth fixes and other improvements
2026-08-09 20:36:58 +02:00
Proddy 2a604008ff Merge branch 'dev' into dev 2026-08-09 20:36:41 +02:00
proddy cd0a51f843 text changes 2026-08-09 20:21:41 +02:00
proddy 34cd93b7be fix bug where changing provision mode inflight would crash 2026-08-09 20:21:32 +02:00
proddy d47915d815 text changes 2026-08-09 20:20:53 +02:00
proddy cb97de7706 no AP if its sets to Never, stop loading ETH.begin() causing memory crash 2026-08-09 20:20:44 +02:00
proddy dbc5a73583 changed path 2026-08-09 20:19:48 +02:00
proddy 4d25c69c84 matching AP provision modes 2026-08-09 20:19:24 +02:00
proddy b430866bbb update tests 2026-08-09 20:19:08 +02:00
proddy 553752a7ca package update 2026-08-09 18:15:10 +02:00
proddy 9c992d8fda show MQTT connection error only once on the first connect failure 2026-08-09 12:28:17 +02:00
proddy e4d127db67 mqtt disconnect on non-psram boards 2026-08-09 09:58:57 +02:00
Proddy 5b41217a67 Merge pull request #3187 from MichaelDvP/dev
workaround for memory leak, #3184
2026-08-09 09:04:39 +02:00
proddy d913824b35 fix compare string length 2026-08-08 11:44:04 +02:00
proddy ae3443c637 various lint optimizations (add const, pass by reference) 2026-08-08 11:43:44 +02:00
proddy 50c34c13f0 replace define with constexpr 2026-08-08 11:42:32 +02:00
proddy d8fab910a8 package update 2026-08-08 11:42:10 +02:00
proddy 4231192991 ui changes to dialog boxes 2026-08-07 13:44:47 +02:00
proddy b47e7d1dd6 fix HA warning on Uptime 2026-08-07 13:44:35 +02:00
proddy fb55327373 fix possible dhcp bounce 2026-08-07 13:44:20 +02:00
proddy 4aab9e095a update 2026-08-07 13:43:09 +02:00
proddy 12ae358434 3.9.0-dev.3 2026-08-07 13:43:00 +02:00
proddy e22b82e913 refactoring based on Michael's comments 2026-08-07 13:42:45 +02:00
proddy 285361c09f package update 2026-08-07 13:42:26 +02:00
MichaelDvP 16caff789d Merge remote-tracking branch 'emsesp/dev' into dev 2026-08-07 12:42:02 +02:00
Proddy 835a8762f6 Merge pull request #3185 from proddy/dev
minor fixes
2026-08-07 12:23:27 +02:00
MichaelDvP 90f0c9c01b workaround for memory leak in setenv, #3184 2026-08-07 11:25:49 +02:00
proddy bde9ab53a1 fix formatting 2026-08-06 16:17:17 +02:00
proddy 901721fb99 pkg update 2026-08-06 12:10:45 +02:00
proddy dac1e1a69c prevent STA warning on boot 2026-08-06 12:10:39 +02:00
proddy 1b2371c67a scan all dns slots, prevent STA warning on boot 2026-08-06 12:10:21 +02:00
proddy 0f396200a2 update with dev.2 changes 2026-08-05 15:43:51 +02:00
proddy 1956ff8386 fix LED flashing 2026-08-05 15:43:40 +02:00
proddy 3a81c70018 add DHCP workaround (disabled for now) 2026-08-05 15:43:30 +02:00
Proddy 7212439f1b Merge pull request #3180 from MichaelDvP/dev
control entity only for master thermostat, #3173
2026-08-03 08:38:30 +02:00
MichaelDvP 6f7f343abd Merge remote-tracking branch 'mdvp/dev' into dev 2026-08-02 16:56:13 +02:00
MichaelDvP 42e6e31476 typo 2026-08-02 16:55:37 +02:00
MichaelDvP 96cf019f0a chore: update generated files for v3.9.0-dev.1 2026-08-02 14:49:34 +00:00
MichaelDvP 59b0417297 Merge branch 'core3' into dev 2026-08-02 16:32:05 +02:00
Proddy 2369277a9e Merge pull request #3179 from proddy/dev
fix lint warnings from SonarQube
2026-08-02 14:42:17 +02:00
proddy e166db8b70 fix lint warnings from SonarQube 2026-08-02 14:41:46 +02:00
proddy 36884867f7 fix for formidable loading, and svg being compressed 2026-08-02 14:40:32 +02:00
proddyandCursor 7fb8eb3592 Merge branch 'core3' into dev
Brings the ESP32 Arduino core 3 work (515 commits) into dev, moving the
version to 3.9.0-dev.0.

The 10 commits dev had since the merge base were auto-formatting, docs
regeneration and a version bump, all of which core3 had already applied
or superseded, so every conflict resolved to the core3 side and the
resulting tree matches core3 exactly.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 13:04:07 +02:00
Proddy e0f59a4c31 Merge pull request #3176 from proddy/dev
pkg update and getting ready for stable
2026-08-01 17:05:18 +02:00
proddy 7eba8acbb4 3.8.3-dev.11 2026-08-01 17:04:13 +02:00
proddy d0e6a603bb auto-formatting 2026-08-01 16:57:41 +02:00
proddy 36b4dd55a7 pkg update 2026-08-01 16:44:16 +02:00
proddy 341a4d4e1d update version 2026-08-01 16:44:11 +02:00
proddy a61745b710 formatting 2026-08-01 16:44:03 +02:00
proddy b1ebd81ca2 update 2026-08-01 16:43:55 +02:00
MichaelDvP 6487a3be70 remove device without entities and not registered by busmaster 2026-07-31 18:40:18 +02:00
MichaelDvP 2eb804de0b show control only for master thermostat #3173 2026-07-31 18:33:25 +02:00
Proddy 1ff53e6ac7 Merge pull request #3171 from MichaelDvP/dev
fix water SM100
2026-07-28 19:02:52 +02:00
MichaelDvP 9cab3b480e chore: update generated files for v3.8.3-dev.10 2026-07-22 15:37:09 +00:00
Proddy bacd3faac2 Merge pull request #3159 from MichaelDvP/dev
dev9
2026-07-15 08:44:31 +02:00
69 changed files with 7087 additions and 7024 deletions
+10
View File
@@ -16,6 +16,12 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
## Fixed ## Fixed
- shunting yard show json - shunting yard show json
- memory leak when using different timezones for ems-esp and thermostat[#3184](https://github.com/emsesp/EMS-ESP32/issues/3184)
- LED stayed off on a healthy system when "Disable LED" was unchecked
- Ethernet MAC address changed with the new SDK, breaking DHCP reservations (currently disabled)
- "IPv4 nameserver" showed an IPv6 address when IPv6 was in use
- HA Discovery warning on Uptime after EMS-ESP boot due to NTP not ready
- hc/control setting for UI800 thermostats [#3181](https://github.com/emsesp/EMS-ESP32/discussions/3181)
## Changed ## Changed
@@ -24,4 +30,8 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- network fallback to AP only after start [#3090](https://github.com/emsesp/EMS-ESP32/issues/3090) - network fallback to AP only after start [#3090](https://github.com/emsesp/EMS-ESP32/issues/3090)
- replaced Web async-validator with custom validator and toast with native snackbar to reduce bundle size - replaced Web async-validator with custom validator and toast with native snackbar to reduce bundle size
- Gateway and Connect devices are shown in the Devices page, but disabled [3126](https://github.com/emsesp/EMS-ESP32/discussions/3126) - Gateway and Connect devices are shown in the Devices page, but disabled [3126](https://github.com/emsesp/EMS-ESP32/discussions/3126)
- show control setting only for master thermostats (0x10) [#3173](https://github.com/emsesp/EMS-ESP32/issues/3173)
- remove devices without entities not listed in 0x07 telegram
- changes to the Dialogs in Dashboard page, removed the run icon in the Commands page. Renamed Execute to Run.
- set `None` for undefined values in HA `val_tpl`
-14
View File
@@ -5966,7 +5966,6 @@ uint8
| hc1.roomtempdiff | room temp difference | uint8 (&gt;=0&lt;=0) | K | true | HC | 42 | 1 | 1 | | hc1.roomtempdiff | room temp difference | uint8 (&gt;=0&lt;=0) | K | true | HC | 42 | 1 | 1 |
uint8 uint8
| hc1.hpminflowtemp | HP min. flow temp. | uint8 (&gt;=0&lt;=0) | C | true | HC | 43 | 1 | 1 | | hc1.hpminflowtemp | HP min. flow temp. | uint8 (&gt;=0&lt;=0) | C | true | HC | 43 | 1 | 1 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
uint8 uint8
| hc1.heatondelay | heat-on delay | uint8 (&gt;=1&lt;=48) | hours | true | HC | 47 | 1 | 1 | | hc1.heatondelay | heat-on delay | uint8 (&gt;=1&lt;=48) | hours | true | HC | 47 | 1 | 1 |
uint8 uint8
@@ -6116,7 +6115,6 @@ uint8
| hc1.roomtempdiff | room temp difference | uint8 (&gt;=0&lt;=0) | K | true | HC | 42 | 1 | 1 | | hc1.roomtempdiff | room temp difference | uint8 (&gt;=0&lt;=0) | K | true | HC | 42 | 1 | 1 |
uint8 uint8
| hc1.hpminflowtemp | HP min. flow temp. | uint8 (&gt;=0&lt;=0) | C | true | HC | 43 | 1 | 1 | | hc1.hpminflowtemp | HP min. flow temp. | uint8 (&gt;=0&lt;=0) | C | true | HC | 43 | 1 | 1 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.remotehum | room humidity from remote | cmd | % | true | HC | 46 | 1 | 1 | | hc1.remotehum | room humidity from remote | cmd | % | true | HC | 46 | 1 | 1 |
uint8 uint8
@@ -6283,7 +6281,6 @@ uint8
| hc1.roomtempdiff | room temp difference | uint8 (&gt;=0&lt;=0) | K | true | HC | 42 | 1 | 1 | | hc1.roomtempdiff | room temp difference | uint8 (&gt;=0&lt;=0) | K | true | HC | 42 | 1 | 1 |
uint8 uint8
| hc1.hpminflowtemp | HP min. flow temp. | uint8 (&gt;=0&lt;=0) | C | true | HC | 43 | 1 | 1 | | hc1.hpminflowtemp | HP min. flow temp. | uint8 (&gt;=0&lt;=0) | C | true | HC | 43 | 1 | 1 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.remotehum | room humidity from remote | cmd | % | true | HC | 46 | 1 | 1 | | hc1.remotehum | room humidity from remote | cmd | % | true | HC | 46 | 1 | 1 |
uint8 uint8
@@ -6806,7 +6803,6 @@ uint8
| hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 | | hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 |
int8 int8
| hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 | | hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.program | program | enum | | true | HC | 27 | 1 | 1 | | hc1.program | program | enum | | true | HC | 27 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 | | hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 |
@@ -6867,7 +6863,6 @@ uint8
| hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 | | hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 |
int8 int8
| hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 | | hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.program | program | enum | | true | HC | 27 | 1 | 1 | | hc1.program | program | enum | | true | HC | 27 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 | | hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 |
@@ -6926,7 +6921,6 @@ uint8
| hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 | | hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 |
int8 int8
| hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 | | hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.program | program | enum | | true | HC | 27 | 1 | 1 | | hc1.program | program | enum | | true | HC | 27 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 | | hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 |
@@ -6985,7 +6979,6 @@ uint8
| hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 | | hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 |
int8 int8
| hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 | | hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.program | program | enum | | true | HC | 27 | 1 | 1 | | hc1.program | program | enum | | true | HC | 27 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 | | hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 |
@@ -7046,7 +7039,6 @@ uint8
| hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 | | hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 |
int8 int8
| hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 | | hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.program | program | enum | | true | HC | 27 | 1 | 1 | | hc1.program | program | enum | | true | HC | 27 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 | | hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 |
@@ -7107,7 +7099,6 @@ uint8
| hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 | | hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 |
int8 int8
| hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 | | hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.program | program | enum | | true | HC | 27 | 1 | 1 | | hc1.program | program | enum | | true | HC | 27 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 | | hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 |
@@ -7166,7 +7157,6 @@ uint8
| hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 | | hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 |
int8 int8
| hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 | | hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.program | program | enum | | true | HC | 27 | 1 | 1 | | hc1.program | program | enum | | true | HC | 27 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 | | hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 |
@@ -7227,7 +7217,6 @@ uint8
| hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 | | hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 |
int8 int8
| hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 | | hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.program | program | enum | | true | HC | 27 | 1 | 1 | | hc1.program | program | enum | | true | HC | 27 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 | | hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 |
@@ -7286,7 +7275,6 @@ uint8
| hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 | | hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 |
int8 int8
| hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 | | hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.program | program | enum | | true | HC | 27 | 1 | 1 | | hc1.program | program | enum | | true | HC | 27 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 | | hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 |
@@ -7345,7 +7333,6 @@ uint8
| hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 | | hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 |
int8 int8
| hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 | | hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.program | program | enum | | true | HC | 27 | 1 | 1 | | hc1.program | program | enum | | true | HC | 27 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 | | hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 |
@@ -7406,7 +7393,6 @@ uint8
| hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 | | hc1.ecotemp | eco temperature | uint8 (&gt;=0&lt;=0) | C | true | HC | 5 | 1 | 1/2 |
int8 int8
| hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 | | hc1.nofrosttemp | nofrost temperature | int8 (&gt;=0&lt;=0) | C | true | HC | 18 | 1 | 1/2 |
| hc1.control | control device | enum | | true | HC | 44 | 1 | 1 |
| hc1.program | program | enum | | true | HC | 27 | 1 | 1 | | hc1.program | program | enum | | true | HC | 27 | 1 | 1 |
| hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 | | hc1.remotetemp | room temperature from remote | cmd | C | true | HC | 45 | 1 | 1/10 |
| hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 | | hc1.targetflowtemp | target flow temperature | uint8 | C | false | HC | 19 | 1 | 1 |
+6008 -5995
View File
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -23,13 +23,13 @@
"standalone-devcontainer": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite --host\"" "standalone-devcontainer": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite --host\""
}, },
"dependencies": { "dependencies": {
"@alova/adapter-xhr": "2.3.1", "@alova/adapter-xhr": "2.3.3",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@mui/icons-material": "^9.2.0", "@mui/icons-material": "^9.3.1",
"@mui/material": "^9.2.0", "@mui/material": "^9.3.1",
"@table-library/react-table-library": "4.1.15", "@table-library/react-table-library": "4.1.15",
"alova": "^3.5.3", "alova": "^3.5.4",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"preact": "^10.29.8", "preact": "^10.29.8",
"react": "^19.2.8", "react": "^19.2.8",
@@ -42,21 +42,21 @@
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@preact/preset-vite": "^2.10.6", "@preact/preset-vite": "^2.10.6",
"@trivago/prettier-plugin-sort-imports": "^6.0.2", "@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/node": "^26.1.2", "@types/node": "^26.2.0",
"@types/react": "^19.2.18", "@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4", "@types/react-dom": "^19.2.4",
"concurrently": "^10.0.4", "concurrently": "^10.0.5",
"eslint": "^10.8.0", "eslint": "^10.8.1",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"etag": "^1.8.1", "etag": "^1.8.1",
"mime-types": "^3.0.2", "mime-types": "^3.0.2",
"prettier": "^3.9.6", "prettier": "^3.9.6",
"rollup-plugin-visualizer": "^7.0.1", "rollup-plugin-visualizer": "^7.1.1",
"terser": "^5.49.0", "terser": "^5.50.0",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"typescript-eslint": "^8.65.0", "typescript-eslint": "^8.67.0",
"vite": "^8.2.0", "vite": "^8.2.1",
"vite-plugin-imagemin": "^0.6.1" "vite-plugin-imagemin": "^0.6.1"
}, },
"packageManager": "pnpm@11.18.0+sha512.33d83c77da82f49fba836925c6f1b841181ec3132b670639bd012f7075f5c7cf634c5f870147c19aae7478fac01df09d8892e880454896edd23ee9b33757563c" "packageManager": "pnpm@11.22.0+sha512.1ff870c4c6133dfd88fb2afc46dd13d47f09c9794b438c6fdb47ca98caf3bc16381ee0be93a091b8e3824cf01f889f46d7d9e20910fb0be1ab0fb5baa80dd621"
} }
+295 -393
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -30
View File
@@ -3,11 +3,9 @@ import { useBlocker } from 'react-router';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import { Box, Button, IconButton, Typography } from '@mui/material'; import { Box, Button, Typography } from '@mui/material';
import { callAction } from '@/api/app';
import { import {
Body, Body,
Cell, Cell,
@@ -48,7 +46,7 @@ const DEFAULT_COMMAND_ITEM: Omit<CommandItem, 'id'> = {
const commandsTheme = { const commandsTheme = {
Table: ` Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(100px, 1fr)) repeat(1, minmax(100px, 1fr)) 160px 32px; --data-table-library_grid-template-columns: repeat(1, minmax(100px, 1fr)) repeat(1, minmax(100px, 1fr)) 160px;
`, `,
BaseRow: ` BaseRow: `
font-size: 14px; font-size: 14px;
@@ -143,21 +141,6 @@ const Commands = () => {
} }
}; };
const { send: executeCommand } = useRequest(
(id: string) => callAction({ action: 'executeCommand', param: id }),
{ immediate: false }
)
.onSuccess(() => {
toast.success(LL.EXECUTE_COMMAND_SENT());
})
.onError((error) => {
toast.error(String(error.error?.message || 'An error occurred'));
});
const execute = async (name: string) => {
await executeCommand(name);
};
const editItem = (ci: CommandItem) => { const editItem = (ci: CommandItem) => {
setCreating(false); setCreating(false);
setSelectedItem(ci); setSelectedItem(ci);
@@ -222,7 +205,6 @@ const Commands = () => {
<HeaderCell stiff>{LL.COMMAND(0)}</HeaderCell> <HeaderCell stiff>{LL.COMMAND(0)}</HeaderCell>
<HeaderCell stiff>{LL.VALUE(0)}</HeaderCell> <HeaderCell stiff>{LL.VALUE(0)}</HeaderCell>
<HeaderCell stiff>{LL.NAME(0)}</HeaderCell> <HeaderCell stiff>{LL.NAME(0)}</HeaderCell>
<HeaderCell></HeaderCell>
</HeaderRow> </HeaderRow>
</Header> </Header>
<Body> <Body>
@@ -231,16 +213,6 @@ const Commands = () => {
<Cell>{ci.cmd}</Cell> <Cell>{ci.cmd}</Cell>
<Cell>{ci.value}</Cell> <Cell>{ci.value}</Cell>
<Cell>{ci.name}</Cell> <Cell>{ci.name}</Cell>
<Cell>
{numChanges === 0 && (
<IconButton
onClick={() => execute(ci.name)}
style={{ backgroundColor: 'transparent' }}
>
<PlayArrowIcon color="primary" sx={{ fontSize: 16 }} />
</IconButton>
)}
</Cell>
</Row> </Row>
))} ))}
</Body> </Body>
+4 -4
View File
@@ -95,7 +95,7 @@ const CommandsDialog = ({
{ immediate: false } { immediate: false }
) )
.onSuccess(() => { .onSuccess(() => {
toast.success(LL.EXECUTE_COMMAND_SENT()); toast.success(LL.COMMAND_SENT());
}) })
.onError((error) => { .onError((error) => {
toast.error(String(error.error?.message || 'An error occurred')); toast.error(String(error.error?.message || 'An error occurred'));
@@ -121,8 +121,8 @@ const CommandsDialog = ({
return ( return (
<Dialog sx={dialogStyle} open={open} onClose={handleClose}> <Dialog sx={dialogStyle} open={open} onClose={handleClose}>
<DialogTitle> <DialogTitle>
{creating ? `${LL.ADD(1)} ${LL.NEW(0)}` : LL.EDIT()}&nbsp; {creating ? `${LL.ADD(1)} ` : LL.UPDATE() + '/' + LL.RUN()}&nbsp;
{LL.COMMAND(1)} {LL.COMMAND(0)}
</DialogTitle> </DialogTitle>
<DialogContent dividers> <DialogContent dividers>
<ValidatedTextField <ValidatedTextField
@@ -195,7 +195,7 @@ const CommandsDialog = ({
onClick={execute} onClick={execute}
color="success" color="success"
> >
{LL.EXECUTE()} {LL.RUN()}
</Button> </Button>
)} )}
</DialogActions> </DialogActions>
+5 -2
View File
@@ -334,7 +334,7 @@ const Dashboard = memo(() => {
(di.dv.v === '' || di.dv.v === undefined ? ( (di.dv.v === '' || di.dv.v === undefined ? (
<IconButton <IconButton
size="small" size="small"
aria-label={LL.RUN_COMMAND()} aria-label={LL.RUN()}
onClick={() => editDashboardValue(di)} onClick={() => editDashboardValue(di)}
style={{ backgroundColor: 'transparent' }} style={{ backgroundColor: 'transparent' }}
> >
@@ -402,7 +402,10 @@ const Dashboard = memo(() => {
{selectedDashboardItem && selectedDashboardItem.dv && ( {selectedDashboardItem && selectedDashboardItem.dv && (
<DevicesDialog <DevicesDialog
open={deviceValueDialogOpen} open={deviceValueDialogOpen}
onClose={() => setDeviceValueDialogOpen(false)} onClose={() => {
setDeviceValueDialogOpen(false);
setSelectedDashboardItem(undefined);
}}
onSave={deviceValueDialogSave} onSave={deviceValueDialogSave}
selectedItem={selectedDashboardItem.dv} selectedItem={selectedDashboardItem.dv}
writeable={true} writeable={true}
+1
View File
@@ -592,6 +592,7 @@ const Devices = memo(() => {
const deviceValueDialogClose = () => { const deviceValueDialogClose = () => {
setDeviceValueDialogOpen(false); setDeviceValueDialogOpen(false);
setSelectedDeviceValue(undefined);
if (selectedDevice !== undefined) { if (selectedDevice !== undefined) {
void sendDeviceData(selectedDevice); void sendDeviceData(selectedDevice);
} }
+23 -17
View File
@@ -30,7 +30,7 @@ import { ValidationError, validate } from 'validators';
import type Schema from 'validators/schema'; import type Schema from 'validators/schema';
import type { ValidateFieldsError } from 'validators/schema'; import type { ValidateFieldsError } from 'validators/schema';
import { DeviceValueUOM, DeviceValueUOM_s } from './types'; import { DeviceValueUOM, DeviceValueUOM_s, getDeviceValueKind } from './types';
import type { DeviceValue } from './types'; import type { DeviceValue } from './types';
interface DevicesDialogProps { interface DevicesDialogProps {
@@ -70,7 +70,7 @@ const DevicesDialog = ({
{ immediate: false } { immediate: false }
) )
.onSuccess(() => { .onSuccess(() => {
toast.success(LL.EXECUTE_COMMAND_SENT()); toast.success(LL.COMMAND_SENT());
}) })
.onError((error) => { .onError((error) => {
toast.error(String(error.error?.message || 'An error occurred')); toast.error(String(error.error?.message || 'An error occurred'));
@@ -119,18 +119,20 @@ const DevicesDialog = ({
return undefined; return undefined;
}; };
const isCommand = const kind = getDeviceValueKind(selectedItem);
(selectedItem.v === '' || selectedItem.v === undefined) &&
Boolean(selectedItem.c); const dialogTitle =
const isSchedulerImmediate = selectedItem.v === undefined; kind === 'command' || kind === 'deviceCommand'
const dialogTitle = isCommand ? LL.RUN() + ' ' + LL.COMMAND(0)
? isSchedulerImmediate : kind === 'scheduler'
? LL.EXECUTE() + ' ' + LL.SCHEDULE(0) ? LL.UPDATE() + ' ' + LL.SCHEDULE(0)
: LL.RUN_COMMAND() : writeable
: writeable ? LL.CHANGE_VALUE()
? LL.CHANGE_VALUE() : LL.VALUE(0);
: LL.VALUE(0);
const buttonLabel = isCommand ? LL.EXECUTE() : LL.UPDATE(); const buttonLabel =
kind === 'command' || kind === 'deviceCommand' ? LL.RUN() : LL.UPDATE();
const helperText = showHelperText(editItem); const helperText = showHelperText(editItem);
const valueLabel = LL.VALUE(0); const valueLabel = LL.VALUE(0);
@@ -142,7 +144,7 @@ const DevicesDialog = ({
<Typography sx={{ mb: 2 }} color="warning" variant="body2"> <Typography sx={{ mb: 2 }} color="warning" variant="body2">
{editItem.id.slice(2)} {editItem.id.slice(2)}
</Typography> </Typography>
{!isSchedulerImmediate && ( {kind !== 'command' && (
<Grid container> <Grid container>
<Grid size={12}> <Grid size={12}>
{editItem.l ? ( {editItem.l ? (
@@ -227,11 +229,15 @@ const DevicesDialog = ({
</Button> </Button>
<Button <Button
startIcon={ startIcon={
isCommand ? <PlayArrowIcon /> : <WarningIcon color="warning" /> kind === 'command' ? (
<PlayArrowIcon />
) : kind === 'deviceEntity' || kind === 'deviceCommand' ? (
<WarningIcon color="warning" />
) : undefined
} }
variant="outlined" variant="outlined"
onClick={doAction} onClick={doAction}
color={isCommand ? 'success' : 'primary'} color={kind !== 'value' ? 'success' : 'primary'}
> >
{buttonLabel} {buttonLabel}
</Button> </Button>
+12
View File
@@ -210,6 +210,18 @@ export enum DeviceValueUOM {
HERTZ HERTZ
} }
export type DeviceValueKind =
'value' | 'scheduler' | 'command' | 'deviceCommand' | 'deviceEntity';
export function getDeviceValueKind(item: DeviceValue): DeviceValueKind {
if (!item.c) return 'value';
if (item.v !== undefined && item.l !== undefined && item.u === undefined)
return 'scheduler';
if (item.v === undefined && item.l === undefined) return 'command';
if (item.u === DeviceValueUOM.NONE) return 'deviceCommand';
return 'deviceEntity';
}
export const DeviceValueUOM_s = [ export const DeviceValueUOM_s = [
'', '',
'°C', '°C',
+2 -3
View File
@@ -37,13 +37,11 @@ const cs: Translation = {
VALUE: '{{value|Value}}', VALUE: '{{value|Value}}',
DEVICES: 'Zařízení', DEVICES: 'Zařízení',
SENSORS: 'Senzory', SENSORS: 'Senzory',
RUN_COMMAND: 'Zavolat příkaz',
CHANGE_VALUE: 'Změnit hodnotu', CHANGE_VALUE: 'Změnit hodnotu',
CANCEL: 'Zrušit', CANCEL: 'Zrušit',
REMOVE_ALL: 'Odebrat vše', REMOVE_ALL: 'Odebrat vše',
APPLY_CHANGES: 'Použít změny ({0})', APPLY_CHANGES: 'Použít změny ({0})',
UPDATE: 'Aktualizovat', UPDATE: 'Aktualizovat',
EXECUTE: 'Provést',
REMOVE: 'Odebrat', REMOVE: 'Odebrat',
PROBLEM_UPDATING: 'Problém s aktualizací', PROBLEM_UPDATING: 'Problém s aktualizací',
PROBLEM_LOADING: 'Problém s načítáním', PROBLEM_LOADING: 'Problém s načítáním',
@@ -368,7 +366,8 @@ const cs: Translation = {
COMMANDS: 'Příkazy', COMMANDS: 'Příkazy',
COMMANDS_UPDATED: 'Příkazy byly aktualizovány', COMMANDS_UPDATED: 'Příkazy byly aktualizovány',
COMMANDS_HELP_1: 'Definujte vlastní příkazy pro magistrali EMS', COMMANDS_HELP_1: 'Definujte vlastní příkazy pro magistrali EMS',
EXECUTE_COMMAND_SENT: 'Příkaz odeslán', COMMAND_SENT: 'Příkaz odeslán',
RUN: 'Spustit příkaz'
}; };
export default cs; export default cs;
+2 -3
View File
@@ -37,13 +37,11 @@ const de: Translation = {
VALUE: '{{wert|Wert}}', VALUE: '{{wert|Wert}}',
DEVICES: 'Geräte', DEVICES: 'Geräte',
SENSORS: 'Sensoren', SENSORS: 'Sensoren',
RUN_COMMAND: 'Befehl ausführen',
CHANGE_VALUE: 'Wert ändern', CHANGE_VALUE: 'Wert ändern',
CANCEL: 'Abbrechen', CANCEL: 'Abbrechen',
REMOVE_ALL: 'Alles entfernen', REMOVE_ALL: 'Alles entfernen',
APPLY_CHANGES: 'Änderungen anwenden ({0})', APPLY_CHANGES: 'Änderungen anwenden ({0})',
UPDATE: 'Aktualisieren', UPDATE: 'Aktualisieren',
EXECUTE: 'Ausführen',
REMOVE: 'Entfernen', REMOVE: 'Entfernen',
PROBLEM_UPDATING: 'Problem beim Aktualisieren', PROBLEM_UPDATING: 'Problem beim Aktualisieren',
PROBLEM_LOADING: 'Problem beim Laden', PROBLEM_LOADING: 'Problem beim Laden',
@@ -368,7 +366,8 @@ const de: Translation = {
COMMANDS: 'Befehle', COMMANDS: 'Befehle',
COMMANDS_UPDATED: 'Befehle wurden aktualisiert', COMMANDS_UPDATED: 'Befehle wurden aktualisiert',
COMMANDS_HELP_1: 'Definieren Sie eigene Befehle für die EMS-Magistral', COMMANDS_HELP_1: 'Definieren Sie eigene Befehle für die EMS-Magistral',
EXECUTE_COMMAND_SENT: 'Befehl gesendet', COMMAND_SENT: 'Befehl gesendet',
RUN: 'Befehl ausführen'
}; };
export default de; export default de;
+2 -3
View File
@@ -37,13 +37,11 @@ const en: Translation = {
VALUE: '{{value|Value}}', VALUE: '{{value|Value}}',
DEVICES: 'Devices', DEVICES: 'Devices',
SENSORS: 'Sensors', SENSORS: 'Sensors',
RUN_COMMAND: 'Call Command',
CHANGE_VALUE: 'Change Value', CHANGE_VALUE: 'Change Value',
CANCEL: 'Cancel', CANCEL: 'Cancel',
REMOVE_ALL: 'Remove All', REMOVE_ALL: 'Remove All',
APPLY_CHANGES: 'Apply Changes ({0})', APPLY_CHANGES: 'Apply Changes ({0})',
UPDATE: 'Update', UPDATE: 'Update',
EXECUTE: 'Execute',
REMOVE: 'Remove', REMOVE: 'Remove',
PROBLEM_UPDATING: 'Problem updating', PROBLEM_UPDATING: 'Problem updating',
PROBLEM_LOADING: 'Problem loading', PROBLEM_LOADING: 'Problem loading',
@@ -368,7 +366,8 @@ const en: Translation = {
COMMANDS: 'Commands', COMMANDS: 'Commands',
COMMANDS_UPDATED: 'Commands updated', COMMANDS_UPDATED: 'Commands updated',
COMMANDS_HELP_1: 'Define reusable named commands below. These can be executed from the console, API/MQTT, or referenced by the Scheduler', COMMANDS_HELP_1: 'Define reusable named commands below. These can be executed from the console, API/MQTT, or referenced by the Scheduler',
EXECUTE_COMMAND_SENT: 'Command sent', COMMAND_SENT: 'Command sent',
RUN: 'Run'
}; };
export default en; export default en;
+2 -3
View File
@@ -37,13 +37,11 @@ const fr: Translation = {
VALUE: 'Valeur', VALUE: 'Valeur',
DEVICES: 'Appareils', DEVICES: 'Appareils',
SENSORS: 'Capteurs', SENSORS: 'Capteurs',
RUN_COMMAND: 'Lancer une commande',
CHANGE_VALUE: 'Changer la valeur', CHANGE_VALUE: 'Changer la valeur',
CANCEL: 'Annuler', CANCEL: 'Annuler',
REMOVE_ALL: 'Supprimer tout', REMOVE_ALL: 'Supprimer tout',
APPLY_CHANGES: 'Appliquer les changements ({0})', APPLY_CHANGES: 'Appliquer les changements ({0})',
UPDATE: 'Update', UPDATE: 'Update',
EXECUTE: 'Execute',
REMOVE: 'Enlever', REMOVE: 'Enlever',
PROBLEM_UPDATING: 'Problème lors de la mise à jour', PROBLEM_UPDATING: 'Problème lors de la mise à jour',
PROBLEM_LOADING: 'Problème lors du chargement', PROBLEM_LOADING: 'Problème lors du chargement',
@@ -368,7 +366,8 @@ const fr: Translation = {
COMMANDS: 'Commandes', COMMANDS: 'Commandes',
COMMANDS_UPDATED: 'Commandes mises à jour', COMMANDS_UPDATED: 'Commandes mises à jour',
COMMANDS_HELP_1: 'Définir des commandes personnalisées pour la magistral EMS', COMMANDS_HELP_1: 'Définir des commandes personnalisées pour la magistral EMS',
EXECUTE_COMMAND_SENT: 'Commande envoyée', COMMAND_SENT: 'Commande envoyée',
RUN: 'Exécuter une commande'
}; };
export default fr; export default fr;
+2 -3
View File
@@ -37,13 +37,11 @@ const it: Translation = {
VALUE: '{{valore|Valore}}', VALUE: '{{valore|Valore}}',
DEVICES: 'Dispositivi', DEVICES: 'Dispositivi',
SENSORS: 'Sensori', SENSORS: 'Sensori',
RUN_COMMAND: 'Esegui',
CHANGE_VALUE: 'Cambia Valore', CHANGE_VALUE: 'Cambia Valore',
CANCEL: 'Annulla', CANCEL: 'Annulla',
REMOVE_ALL: 'Rimuovi tutto', REMOVE_ALL: 'Rimuovi tutto',
APPLY_CHANGES: 'Applica Cambiamenti ({0})', APPLY_CHANGES: 'Applica Cambiamenti ({0})',
UPDATE: 'Update', UPDATE: 'Update',
EXECUTE: 'Execute',
REMOVE: 'Elimina', REMOVE: 'Elimina',
PROBLEM_UPDATING: 'Problema aggiornamento', PROBLEM_UPDATING: 'Problema aggiornamento',
PROBLEM_LOADING: 'Problema caricamento', PROBLEM_LOADING: 'Problema caricamento',
@@ -368,7 +366,8 @@ const it: Translation = {
COMMANDS: 'Comandi', COMMANDS: 'Comandi',
COMMANDS_UPDATED: 'Comandi aggiornati', COMMANDS_UPDATED: 'Comandi aggiornati',
COMMANDS_HELP_1: 'Definisci comandi personalizzati per la magistrali EMS', COMMANDS_HELP_1: 'Definisci comandi personalizzati per la magistrali EMS',
EXECUTE_COMMAND_SENT: 'Comando inviato', COMMAND_SENT: 'Comando inviato',
RUN: 'Esegui comando'
}; };
export default it; export default it;
+2 -3
View File
@@ -37,13 +37,11 @@ const nl: Translation = {
VALUE: '{{waarde|Waarde}}', VALUE: '{{waarde|Waarde}}',
DEVICES: 'Apparaten', DEVICES: 'Apparaten',
SENSORS: 'Sensoren', SENSORS: 'Sensoren',
RUN_COMMAND: 'Call commando',
CHANGE_VALUE: 'Wijzig waarde', CHANGE_VALUE: 'Wijzig waarde',
CANCEL: 'Annuleren', CANCEL: 'Annuleren',
REMOVE_ALL: 'Verwijderen alles', REMOVE_ALL: 'Verwijderen alles',
APPLY_CHANGES: 'Aanpassen ({0})', APPLY_CHANGES: 'Aanpassen ({0})',
UPDATE: 'Update', UPDATE: 'Update',
EXECUTE: 'Uitvoeren',
REMOVE: 'Verwijderen', REMOVE: 'Verwijderen',
PROBLEM_UPDATING: 'Probleem met updaten', PROBLEM_UPDATING: 'Probleem met updaten',
PROBLEM_LOADING: 'Probleem met laden', PROBLEM_LOADING: 'Probleem met laden',
@@ -368,7 +366,8 @@ const nl: Translation = {
COMMANDS: 'Commando\'s', COMMANDS: 'Commando\'s',
COMMANDS_UPDATED: 'Commando\'s bijgewerkt', COMMANDS_UPDATED: 'Commando\'s bijgewerkt',
COMMANDS_HELP_1: 'Definieer hergebruikbare benoemde commando\'s hieronder. Deze kunnen worden uitgevoerd vanuit de console, API/MQTT, of worden aangeroepen door de Scheduler', COMMANDS_HELP_1: 'Definieer hergebruikbare benoemde commando\'s hieronder. Deze kunnen worden uitgevoerd vanuit de console, API/MQTT, of worden aangeroepen door de Scheduler',
EXECUTE_COMMAND_SENT: 'Commando verzonden', COMMAND_SENT: 'Commando verzonden',
RUN: 'Commando uitvoeren'
}; };
export default nl; export default nl;
+2 -3
View File
@@ -37,13 +37,11 @@ const no: Translation = {
VALUE: '{{verdi|Verdi}}', VALUE: '{{verdi|Verdi}}',
DEVICES: 'Enheter', DEVICES: 'Enheter',
SENSORS: 'Sensorer', SENSORS: 'Sensorer',
RUN_COMMAND: 'Kjør kommando',
CHANGE_VALUE: 'Endre Verdi', CHANGE_VALUE: 'Endre Verdi',
CANCEL: 'Avbryt', CANCEL: 'Avbryt',
REMOVE_ALL: 'Fjern alles', REMOVE_ALL: 'Fjern alles',
APPLY_CHANGES: 'Utfør endringer({0})', APPLY_CHANGES: 'Utfør endringer({0})',
UPDATE: 'Oppdater', UPDATE: 'Oppdater',
EXECUTE: 'Utfør',
REMOVE: 'Fjern', REMOVE: 'Fjern',
PROBLEM_UPDATING: 'Problem med oppdatering', PROBLEM_UPDATING: 'Problem med oppdatering',
PROBLEM_LOADING: 'Problem med opplasting', PROBLEM_LOADING: 'Problem med opplasting',
@@ -368,7 +366,8 @@ const no: Translation = {
COMMANDS: 'Kommandoer', COMMANDS: 'Kommandoer',
COMMANDS_UPDATED: 'Kommandoer oppdatert', COMMANDS_UPDATED: 'Kommandoer oppdatert',
COMMANDS_HELP_1: 'Definer egne kommandoer for EMS-Magistral', COMMANDS_HELP_1: 'Definer egne kommandoer for EMS-Magistral',
EXECUTE_COMMAND_SENT: 'Kommando sendt', COMMAND_SENT: 'Kommando sendt',
RUN: 'Kjør'
}; };
export default no; export default no;
+2 -3
View File
@@ -37,13 +37,11 @@ const pl: BaseTranslation = {
VALUE: '{{W|w|w}}artość', VALUE: '{{W|w|w}}artość',
DEVICES: 'Urządzenia', DEVICES: 'Urządzenia',
SENSORS: 'Czujniki', SENSORS: 'Czujniki',
RUN_COMMAND: 'Wykonaj komendę',
CHANGE_VALUE: 'Zmiana wartości', CHANGE_VALUE: 'Zmiana wartości',
CANCEL: 'Anuluj', CANCEL: 'Anuluj',
REMOVE_ALL: 'Usuń wszystko', REMOVE_ALL: 'Usuń wszystko',
APPLY_CHANGES: 'Zapisz zmiany ({0})', APPLY_CHANGES: 'Zapisz zmiany ({0})',
UPDATE: 'Zmień', UPDATE: 'Zmień',
EXECUTE: 'Wykonaj',
REMOVE: 'Usuń', REMOVE: 'Usuń',
PROBLEM_UPDATING: 'Problem z uaktualnieniem!', PROBLEM_UPDATING: 'Problem z uaktualnieniem!',
PROBLEM_LOADING: 'Problem z załadowaniem!', PROBLEM_LOADING: 'Problem z załadowaniem!',
@@ -368,7 +366,8 @@ const pl: BaseTranslation = {
COMMANDS: 'Komendy', COMMANDS: 'Komendy',
COMMANDS_UPDATED: 'Komendy zostały zaktualizowane', COMMANDS_UPDATED: 'Komendy zostały zaktualizowane',
COMMANDS_HELP_1: 'Zdefiniuj niestandardowe komendy dla magistrali EMS', COMMANDS_HELP_1: 'Zdefiniuj niestandardowe komendy dla magistrali EMS',
EXECUTE_COMMAND_SENT: 'Komenda wysłana', COMMAND_SENT: 'Komenda wysłana',
RUN: 'Wykonaj'
}; };
export default pl; export default pl;
+2 -3
View File
@@ -37,13 +37,11 @@ const sk: Translation = {
VALUE: '{{hodnota|Hodnota}}', VALUE: '{{hodnota|Hodnota}}',
DEVICES: 'Zariadenia', DEVICES: 'Zariadenia',
SENSORS: 'Snímače', SENSORS: 'Snímače',
RUN_COMMAND: 'Volať príkaz',
CHANGE_VALUE: 'Zmena hodnoty', CHANGE_VALUE: 'Zmena hodnoty',
CANCEL: 'Zrušiť', CANCEL: 'Zrušiť',
REMOVE_ALL: 'Odstrániť všetko', REMOVE_ALL: 'Odstrániť všetko',
APPLY_CHANGES: 'Aplikovať zmeny ({0})', APPLY_CHANGES: 'Aplikovať zmeny ({0})',
UPDATE: 'Aktualizovať', UPDATE: 'Aktualizovať',
EXECUTE: 'Spustiť',
REMOVE: 'Odstrániť', REMOVE: 'Odstrániť',
PROBLEM_UPDATING: 'Problém s aktualizáciou', PROBLEM_UPDATING: 'Problém s aktualizáciou',
PROBLEM_LOADING: 'Problém s načítaním', PROBLEM_LOADING: 'Problém s načítaním',
@@ -368,7 +366,8 @@ const sk: Translation = {
COMMANDS: 'Príkazy', COMMANDS: 'Príkazy',
COMMANDS_UPDATED: 'Príkazy aktualizované', COMMANDS_UPDATED: 'Príkazy aktualizované',
COMMANDS_HELP_1: 'Definujte vlastné príkazy pre magistrali EMS', COMMANDS_HELP_1: 'Definujte vlastné príkazy pre magistrali EMS',
EXECUTE_COMMAND_SENT: 'Príkaz odoslaný', COMMAND_SENT: 'Príkaz odoslaný',
RUN: 'Volať príkaz'
}; };
export default sk; export default sk;
+2 -3
View File
@@ -37,13 +37,11 @@ const sv: Translation = {
VALUE: '{{värde|Värde}}', VALUE: '{{värde|Värde}}',
DEVICES: 'Enheter', DEVICES: 'Enheter',
SENSORS: 'Sensorer', SENSORS: 'Sensorer',
RUN_COMMAND: 'Kör kommando',
CHANGE_VALUE: 'Ändra värde', CHANGE_VALUE: 'Ändra värde',
CANCEL: 'Avbryt', CANCEL: 'Avbryt',
REMOVE_ALL: 'Ta bort allt', REMOVE_ALL: 'Ta bort allt',
APPLY_CHANGES: 'Utför ändringar ({0})', APPLY_CHANGES: 'Utför ändringar ({0})',
UPDATE: 'Uppdatera', UPDATE: 'Uppdatera',
EXECUTE: 'Utför',
REMOVE: 'Ta bort', REMOVE: 'Ta bort',
PROBLEM_UPDATING: 'Problem vid uppdatering', PROBLEM_UPDATING: 'Problem vid uppdatering',
PROBLEM_LOADING: 'Problem vid hämtning', PROBLEM_LOADING: 'Problem vid hämtning',
@@ -368,7 +366,8 @@ const sv: Translation = {
COMMANDS: 'Kommandon', COMMANDS: 'Kommandon',
COMMANDS_UPDATED: 'Kommandon uppdaterade', COMMANDS_UPDATED: 'Kommandon uppdaterade',
COMMANDS_HELP_1: 'Definiera egna kommandon för EMS-Magistral', COMMANDS_HELP_1: 'Definiera egna kommandon för EMS-Magistral',
EXECUTE_COMMAND_SENT: 'Kommando skickat', COMMAND_SENT: 'Kommando skickat',
RUN: 'Kör kommando'
}; };
export default sv; export default sv;
+2 -3
View File
@@ -37,13 +37,11 @@ const tr: Translation = {
VALUE: '{{değer|Değer}}', VALUE: '{{değer|Değer}}',
DEVICES: 'Cihazlar', DEVICES: 'Cihazlar',
SENSORS: 'Sensörler', SENSORS: 'Sensörler',
RUN_COMMAND: 'Çalıştırma Komutu',
CHANGE_VALUE: 'Değeri Değiştir', CHANGE_VALUE: 'Değeri Değiştir',
CANCEL: 'İptal', CANCEL: 'İptal',
REMOVE_ALL: 'Tümünü Kaldır', REMOVE_ALL: 'Tümünü Kaldır',
APPLY_CHANGES: 'Değişiklikleri Uygula ({0})', APPLY_CHANGES: 'Değişiklikleri Uygula ({0})',
UPDATE: 'Update', UPDATE: 'Update',
EXECUTE: 'Uygulamak',
REMOVE: 'Kaldır', REMOVE: 'Kaldır',
PROBLEM_UPDATING: 'Güncelleme Sorunu', PROBLEM_UPDATING: 'Güncelleme Sorunu',
PROBLEM_LOADING: 'Yükleme Sorunu', PROBLEM_LOADING: 'Yükleme Sorunu',
@@ -368,7 +366,8 @@ const tr: Translation = {
COMMANDS: 'Komutlar', COMMANDS: 'Komutlar',
COMMANDS_UPDATED: 'Komutlar güncellendi', COMMANDS_UPDATED: 'Komutlar güncellendi',
COMMANDS_HELP_1: 'Özel komutları EMS hattına tanımlayın', COMMANDS_HELP_1: 'Özel komutları EMS hattına tanımlayın',
EXECUTE_COMMAND_SENT: 'Komut gönderildi', COMMAND_SENT: 'Komut gönderildi',
RUN: 'Komutu çalıştır'
}; };
export default tr; export default tr;
+21 -17
View File
@@ -2,6 +2,7 @@ import preact from '@preact/preset-vite';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { visualizer } from 'rollup-plugin-visualizer'; import { visualizer } from 'rollup-plugin-visualizer';
import { pathToFileURL } from 'url';
import { Plugin, PluginOption, defineConfig } from 'vite'; import { Plugin, PluginOption, defineConfig } from 'vite';
import viteImagemin from 'vite-plugin-imagemin'; import viteImagemin from 'vite-plugin-imagemin';
import zlib from 'zlib'; import zlib from 'zlib';
@@ -178,24 +179,20 @@ const createProductionTerserOptions = () => ({
toplevel: true toplevel: true
}); });
// Image optimization plugin // Image optimization plugin.
// Only svgo runs here: the raster optimizers (optipng, pngquant, gifsicle,
// jpegtran, mozjpeg) are native binaries whose install scripts are blocked by
// `allowBuilds` in pnpm-workspace.yaml, so they would fail on every build.
// Raster assets are committed pre-optimized instead.
const imageOptimizationPlugin = { const imageOptimizationPlugin = {
...viteImagemin({ ...viteImagemin({
verbose: false, verbose: false,
gifsicle: { filter: /\.svg$/i,
optimizationLevel: 7, gifsicle: false,
interlaced: false jpegTran: false,
}, mozjpeg: false,
optipng: { optipng: false,
optimizationLevel: 7 pngquant: false,
},
mozjpeg: {
quality: 20
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: { svgo: {
plugins: [ plugins: [
{ name: 'removeViewBox' }, { name: 'removeViewBox' },
@@ -210,8 +207,15 @@ export default defineConfig(
async ({ command, mode }: { command: string; mode: string }) => { async ({ command, mode }: { command: string; mode: string }) => {
if (command === 'serve') { if (command === 'serve') {
console.log(`Preparing for standalone build with server, mode=${mode}`); console.log(`Preparing for standalone build with server, mode=${mode}`);
// @ts-expect-error - mock server doesn't have type declarations // Imported through a computed URL so the config bundler leaves it as a real
const { default: mockServer } = await import('../mock-api/mockServer.js'); // runtime import: mock-api is a separate package with its own node_modules,
// and inlining it here would drag its dependencies into every build.
const mockServerUrl = pathToFileURL(
path.resolve(import.meta.dirname, '../mock-api/mockServer.js')
).href;
const { default: mockServer } = (await import(
/* @vite-ignore */ mockServerUrl
)) as { default: () => PluginOption };
return { return {
plugins: [...createBasePlugins(true, true), mockServer()], plugins: [...createBasePlugins(true, true), mockServer()],
resolve: { resolve: {
+5 -1
View File
@@ -22,6 +22,10 @@
#define NTP_SETTINGS_FILE "/config/ntpSettings.json" #define NTP_SETTINGS_FILE "/config/ntpSettings.json"
#define EMSESP_SETTINGS_FILE "/config/emsespSettings.json" #define EMSESP_SETTINGS_FILE "/config/emsespSettings.json"
// AP provision modes, mirrors src/ESP32React/APSettingsService.h
#define AP_MODE_DISCONNECTED 1
#define AP_MODE_NEVER 2
class DummySettings { class DummySettings {
public: public:
// SYSTEM // SYSTEM
@@ -81,7 +85,7 @@ class DummySettings {
String password = "ems-esp"; String password = "ems-esp";
// AP // AP
uint8_t provisionMode = 0; uint8_t provisionMode = AP_MODE_DISCONNECTED; // always
// NTP // NTP
String server = "pool.ntp.org"; String server = "pool.ntp.org";
+3 -1
View File
@@ -64,9 +64,11 @@ class ESP_SSLClient : public Stream {
} }
void flush() override { void flush() override {
} }
void stop() { void stop() {
} }
void setTimeout(uint32_t) {
}
}; };
#endif // ESP_SSLClient_h #endif // ESP_SSLClient_h
+3 -1
View File
@@ -39,7 +39,9 @@ class WiFiClient {
} }
void stop() { void stop() {
} }
void setTimeout(uint32_t){
}
// ESP32 socket option passthrough (e.g. TCP_NODELAY) // ESP32 socket option passthrough (e.g. TCP_NODELAY)
int setSocketOption(int, int, const void *, size_t) { int setSocketOption(int, int, const void *, size_t) {
return 0; return 0;
+1 -1
View File
@@ -18,5 +18,5 @@
"@trivago/prettier-plugin-sort-imports": "^6.0.2", "@trivago/prettier-plugin-sort-imports": "^6.0.2",
"prettier": "^3.9.6" "prettier": "^3.9.6"
}, },
"packageManager": "pnpm@11.18.0+sha512.33d83c77da82f49fba836925c6f1b841181ec3132b670639bd012f7075f5c7cf634c5f870147c19aae7478fac01df09d8892e880454896edd23ee9b33757563c" "packageManager": "pnpm@11.21.0+sha512.521705bce689924eac72f5a3587122f362689ef6571e55ba80076fd637c11132ecffada26fad4ea79c485bfddbfd3d5a2a5b05805a77e893de71ec8a6cca3bb1"
} }
+2 -1
View File
@@ -1343,4 +1343,5 @@ optimisations
IILE IILE
Sumr Sumr
eraseap eraseap
roomtempoffset roomtempoffset
dhcpc
+1 -1
View File
@@ -7,7 +7,7 @@ Workflow:
3. Write that block to test/test_api/test_api.h. 3. Write that block to test/test_api/test_api.h.
4. Run `pio run -e native-test -t exec`. 4. Run `pio run -e native-test -t exec`.
run with `python3 scripts/generate_test_api.py` run with `python3 ./scripts/generate_test_api.py`
""" """
import subprocess import subprocess
+12 -5
View File
@@ -31,7 +31,8 @@ StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) {
APSettings newSettings{}; APSettings newSettings{};
newSettings.provisionMode = static_cast<uint8_t>(root["provision_mode"] | FACTORY_AP_PROVISION_MODE); newSettings.provisionMode = static_cast<uint8_t>(root["provision_mode"] | FACTORY_AP_PROVISION_MODE);
switch (settings.provisionMode) { // coerce removed/invalid modes (e.g. legacy AP_MODE_ALWAYS = 0) to the current default
switch (newSettings.provisionMode) {
case AP_MODE_DISCONNECTED: case AP_MODE_DISCONNECTED:
case AP_MODE_NEVER: case AP_MODE_NEVER:
break; break;
@@ -53,11 +54,17 @@ StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) {
return StateUpdateResult::UNCHANGED; return StateUpdateResult::UNCHANGED;
} }
settings = newSettings; const uint8_t previousProvisionMode = settings.provisionMode;
settings = newSettings;
// if the AP mode has changed, force a disconnect and reconnect // FS load at boot uses this updater before Network::begin(); skip reconnect on that first apply.
if (settings.provisionMode != newSettings.provisionMode) { // Subsequent provision-mode changes (UI/API) must reload Network's cached AP settings.
emsesp::EMSESP::network_.reconnect(); static bool appliedOnce = false;
const bool modeChanged = previousProvisionMode != settings.provisionMode;
if (appliedOnce && modeChanged) {
emsesp::EMSESP::network_.schedule_reconnect();
} }
appliedOnce = true;
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
} }
+1 -1
View File
@@ -51,7 +51,7 @@ enum APNetworkStatus { ACTIVE = 0, INACTIVE };
class APSettings { class APSettings {
public: public:
uint8_t provisionMode = FACTORY_AP_PROVISION_MODE; // 0 = on, 2 = off uint8_t provisionMode = FACTORY_AP_PROVISION_MODE; // 1 = on disconnect, 2 = off
String ssid = FACTORY_AP_SSID; String ssid = FACTORY_AP_SSID;
String password = FACTORY_AP_PASSWORD; String password = FACTORY_AP_PASSWORD;
uint8_t channel = FACTORY_AP_CHANNEL; uint8_t channel = FACTORY_AP_CHANNEL;
+11 -2
View File
@@ -184,7 +184,12 @@ bool MqttSettingsService::configureMqtt() {
static_cast<espMqttClientSecure *>(_mqttClient)->setKeepAlive(_state.keepAlive); static_cast<espMqttClientSecure *>(_mqttClient)->setKeepAlive(_state.keepAlive);
static_cast<espMqttClientSecure *>(_mqttClient)->setCleanSession(_state.cleanSession); static_cast<espMqttClientSecure *>(_mqttClient)->setCleanSession(_state.cleanSession);
static_cast<espMqttClientSecure *>(_mqttClient)->setWill(will_topic, 1, true, "offline"); // QOS 1, retain static_cast<espMqttClientSecure *>(_mqttClient)->setWill(will_topic, 1, true, "offline"); // QOS 1, retain
return _mqttClient->connect(); // return _mqttClient->connect();
if (_mqttClient->connect()) {
emsesp::EMSESP::logger().info("Starting MQTT service");
return true;
}
return false;
} }
static_cast<espMqttClient *>(_mqttClient)->setServer(_state.host.c_str(), _state.port); static_cast<espMqttClient *>(_mqttClient)->setServer(_state.host.c_str(), _state.port);
if (_state.username.length() > 0) { if (_state.username.length() > 0) {
@@ -194,7 +199,11 @@ bool MqttSettingsService::configureMqtt() {
static_cast<espMqttClient *>(_mqttClient)->setKeepAlive(_state.keepAlive); static_cast<espMqttClient *>(_mqttClient)->setKeepAlive(_state.keepAlive);
static_cast<espMqttClient *>(_mqttClient)->setCleanSession(_state.cleanSession); static_cast<espMqttClient *>(_mqttClient)->setCleanSession(_state.cleanSession);
static_cast<espMqttClient *>(_mqttClient)->setWill(will_topic, 1, true, "offline"); // QOS 1, retain static_cast<espMqttClient *>(_mqttClient)->setWill(will_topic, 1, true, "offline"); // QOS 1, retain
return _mqttClient->connect(); // return _mqttClient->connect();
if (_mqttClient->connect()) {
emsesp::EMSESP::logger().info("Starting MQTT service");
return true;
}
} }
return false; return false;
+11 -2
View File
@@ -77,13 +77,15 @@ void NTPSettings::read(NTPSettings & settings, JsonObject root) {
root["enabled"] = settings.enabled; root["enabled"] = settings.enabled;
root["server"] = settings.server; root["server"] = settings.server;
root["tz_label"] = settings.tzLabel; root["tz_label"] = settings.tzLabel;
root["tz_format"] = settings.tzFormat; root["tz_format"] = settings.tzFormat.substring(0, settings.tzFormat.indexOf(" "));
root["thermostat_sync"] = settings.thermostat_sync; root["thermostat_sync"] = settings.thermostat_sync;
root["tz_label_t"] = settings.tzLabelT; root["tz_label_t"] = settings.tzLabelT;
root["tz_format_t"] = settings.tzFormatT; root["tz_format_t"] = settings.tzFormatT.substring(0, settings.tzFormatT.indexOf(" "));
} }
StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) { StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
settings.tzFormat.reserve(40);
settings.tzFormatT.reserve(40);
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED; settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
settings.server = root["server"] | FACTORY_NTP_SERVER; settings.server = root["server"] | FACTORY_NTP_SERVER;
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL; settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
@@ -91,5 +93,12 @@ StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
settings.thermostat_sync = root["thermostat_sync"] | FACTORY_NTP_THERMOSTAT_SYNC; settings.thermostat_sync = root["thermostat_sync"] | FACTORY_NTP_THERMOSTAT_SYNC;
settings.tzLabelT = root["tz_label_t"] | FACTORY_NTP_TIME_ZONE_LABEL; settings.tzLabelT = root["tz_label_t"] | FACTORY_NTP_TIME_ZONE_LABEL;
settings.tzFormatT = root["tz_format_t"] | FACTORY_NTP_TIME_ZONE_FORMAT; settings.tzFormatT = root["tz_format_t"] | FACTORY_NTP_TIME_ZONE_FORMAT;
// The format string must have same length to avoid memory leak, #3184
while (settings.tzFormat.length() < 40) {
settings.tzFormat += " ";
}
while (settings.tzFormatT.length() < 40) {
settings.tzFormatT += " ";
}
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
} }
+2 -2
View File
@@ -94,7 +94,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
} }
#endif #endif
// it's firmware - initialize the ArduinoOTA updater // it's firmware - initialize the ArduinoOTA updater
emsesp::EMSESP::logger().info("Uploading firmware file %s (size: %d KB). Please wait...", filename.c_str(), filesize / 1024); emsesp::EMSESP::logger().info("Uploading firmware file %s (size: %dKB). Please wait...", filename.c_str(), filesize / 1024);
// turn off UART to prevent interference with the upload // turn off UART to prevent interference with the upload
emsesp::EMSuart::stop(); emsesp::EMSuart::stop();
@@ -111,7 +111,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
} }
} else if (_is_filesystem) { } else if (_is_filesystem) {
// LittleFS filesystem image - flash directly to the spiffs/littlefs partition // LittleFS filesystem image - flash directly to the spiffs/littlefs partition
emsesp::EMSESP::logger().info("Uploading filesystem image %s (size: %u KB). Please wait...", filename.c_str(), static_cast<unsigned>(filesize / 1024)); emsesp::EMSESP::logger().info("Uploading filesystem image %s (size: %uKB). Please wait...", filename.c_str(), static_cast<unsigned>(filesize / 1024));
emsesp::EMSuart::stop(); emsesp::EMSuart::stop();
LittleFS.end(); // unmount LittleFS before we overwrite the partition under it LittleFS.end(); // unmount LittleFS before we overwrite the partition under it
+9 -3
View File
@@ -102,7 +102,7 @@ void AnalogSensor::start(const bool factory_settings) {
Command::add( Command::add(
EMSdevice::DeviceType::ANALOGSENSOR, EMSdevice::DeviceType::ANALOGSENSOR,
F_(setvalue), F_(setvalue),
[&](const char * value, const int8_t id, JsonObject output) { return command_setvalue(value, id); }, [this](const char * value, const int8_t id, JsonObject) { return command_setvalue(value, id); },
FL_(setiovalue_cmd), FL_(setiovalue_cmd),
CommandFlag::ADMIN_ONLY); CommandFlag::ADMIN_ONLY);
@@ -111,6 +111,12 @@ void AnalogSensor::start(const bool factory_settings) {
Mqtt::subscribe(EMSdevice::DeviceType::ANALOGSENSOR, topic, nullptr); // use empty function callback Mqtt::subscribe(EMSdevice::DeviceType::ANALOGSENSOR, topic, nullptr); // use empty function callback
} }
void AnalogSensor::ha_reset() {
for (const auto & sensor : sensors_) {
remove_ha_topic(sensor.type(), sensor.gpio());
}
}
// load settings from the customization file, sorts them and initializes the GPIOs // load settings from the customization file, sorts them and initializes the GPIOs
void AnalogSensor::reload(bool get_nvs) { void AnalogSensor::reload(bool get_nvs) {
exclude_types_.clear(); exclude_types_.clear();
@@ -195,7 +201,7 @@ void AnalogSensor::reload(bool get_nvs) {
Command::add( Command::add(
EMSdevice::DeviceType::ANALOGSENSOR, EMSdevice::DeviceType::ANALOGSENSOR,
sensor.name, sensor.name,
[&](const char * value, const int8_t id, JsonObject output) { return command_setvalue(value, sensor.gpio); }, [this, gpio = sensor.gpio](const char * value, const int8_t, JsonObject) { return command_setvalue(value, gpio); },
sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2) ? FL_(counter) sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2) ? FL_(counter)
: sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out) : sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out)
: sensor.type == AnalogType::RGB ? FL_(RGB) : sensor.type == AnalogType::RGB ? FL_(RGB)
@@ -729,7 +735,7 @@ void AnalogSensor::publish_values(const bool force) {
} }
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery // don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) { if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}"; config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else 'None'}}";
} else { } else {
config["val_tpl"] = (std::string) "{{" + val_obj + "}}"; config["val_tpl"] = (std::string) "{{" + val_obj + "}}";
} }
+1
View File
@@ -144,6 +144,7 @@ class AnalogSensor {
void publish_values(const bool force); void publish_values(const bool force);
void reload(bool get_nvs = false); void reload(bool get_nvs = false);
bool updated_values(); bool updated_values();
void ha_reset();
// return back reference to the sensor list, used by other classes // return back reference to the sensor list, used by other classes
const std::vector<Sensor, AllocatorPSRAM<Sensor>> & sensors() const { const std::vector<Sensor, AllocatorPSRAM<Sensor>> & sensors() const {
+4 -4
View File
@@ -492,7 +492,7 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha
} }
// add a command to the list, which does not return json // add a command to the list, which does not return json
void Command::add(const uint8_t device_type, const uint8_t device_id, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags) { void Command::add(const uint8_t device_type, const uint8_t device_id, const char * cmd, const cmd_function_p & cb, const char * const * description, uint8_t flags) {
// if the command already exists for that device type don't add it // if the command already exists for that device type don't add it
if (find_command(device_type, device_id, cmd, flags) != nullptr) { if (find_command(device_type, device_id, cmd, flags) != nullptr) {
return; return;
@@ -508,13 +508,13 @@ void Command::add(const uint8_t device_type, const uint8_t device_id, const char
// add a command with no json output // add a command with no json output
// system/temperature/analog devices uses device_id 0 // system/temperature/analog devices uses device_id 0
void Command::add(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags) { void Command::add(const uint8_t device_type, const char * cmd, const cmd_function_p & cb, const char * const * description, uint8_t flags) {
add(device_type, 0, cmd, cb, description, flags); add(device_type, 0, cmd, cb, description, flags);
} }
// add a command to the list, which does return a json object as output // add a command to the list, which does return a json object as output
// these commands bypass the readonly check (they are actions, not entity setters) // these commands bypass the readonly check (they are actions, not entity setters)
void Command::add_json(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags) { void Command::add_json(const uint8_t device_type, const char * cmd, const cmd_function_p & cb, const char * const * description, uint8_t flags) {
// if the command already exists for that device type don't add it // if the command already exists for that device type don't add it
if (find_command(device_type, 0, cmd, flags) != nullptr) { if (find_command(device_type, 0, cmd, flags) != nullptr) {
return; return;
@@ -828,7 +828,7 @@ uint8_t Command::json_message(uint8_t error_code, const char * message, const Js
// e.g. //one/two////three/// becomes /one/two/three // e.g. //one/two////three/// becomes /one/two/three
std::string SUrlParser::path() { std::string SUrlParser::path() {
std::string s = "/"; // set up the beginning slash std::string s = "/"; // set up the beginning slash
for (const std::string & f : m_folders) { for (const auto & f : m_folders) {
s += f; s += f;
s += "/"; s += "/";
} }
+19 -19
View File
@@ -67,13 +67,13 @@ class Command {
cmd_function_p cmdfunction_; cmd_function_p cmdfunction_;
const char * const * description_; const char * const * description_;
CmdFunction(const uint8_t device_type, CmdFunction(const uint8_t device_type,
const uint8_t device_id, const uint8_t device_id,
const uint8_t flags, const uint8_t flags,
const bool has_json_output, const bool has_json_output,
const char * cmd, const char * cmd,
const cmd_function_p cmdfunction, const cmd_function_p & cmdfunction,
const char * const * description) const char * const * description)
: device_type_(device_type) : device_type_(device_type)
, device_id_(device_id) , device_id_(device_id)
, flags_(flags) , flags_(flags)
@@ -105,22 +105,22 @@ class Command {
static uint8_t call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id = -1); static uint8_t call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id = -1);
// with normal call back function taking a value and id // with normal call back function taking a value and id
static void add(const uint8_t device_type, static void add(const uint8_t device_type,
const uint8_t device_id, const uint8_t device_id,
const char * cmd, const char * cmd,
const cmd_function_p cb, const cmd_function_p & cb,
const char * const * description, const char * const * description,
uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT); uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
// same for system/temperature/analog devices // same for system/temperature/analog devices
static void static void
add(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT); add(const uint8_t device_type, const char * cmd, const cmd_function_p & cb, const char * const * description, uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
// command that writes a JSON object as its output; bypasses the readonly check // command that writes a JSON object as its output; bypasses the readonly check
static void add_json(const uint8_t device_type, static void add_json(const uint8_t device_type,
const char * cmd, const char * cmd,
const cmd_function_p cb, const cmd_function_p & cb,
const char * const * description, const char * const * description,
uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT); uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT);
static void reserve(size_t num) { static void reserve(size_t num) {
cmdfunctions_.reserve(num); cmdfunctions_.reserve(num);
+7 -10
View File
@@ -104,7 +104,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
shell.printfln("Unknown show command"); shell.printfln("Unknown show command");
} }
}, },
[](Shell const & shell, const std::vector<std::string> & current_arguments, const std::string & next_argument) -> std::vector<std::string> { [](Shell const &, const std::vector<std::string> &, const std::string &) -> std::vector<std::string> {
return std::vector<std::string>{"system", "users", "devices", "log", "ems", "values", "mqtt", "commands"}; return std::vector<std::string>{"system", "users", "devices", "log", "ems", "values", "mqtt", "commands"};
}); });
@@ -191,7 +191,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
CommandFlags::ADMIN, CommandFlags::ADMIN,
{F_(restart)}, {F_(restart)},
{F_(partitionname_optional)}, {F_(partitionname_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell &, const std::vector<std::string> & arguments) {
if (arguments.size()) { if (arguments.size()) {
EMSESP::system_.system_restart(arguments.front().c_str()); EMSESP::system_.system_restart(arguments.front().c_str());
} else { } else {
@@ -199,10 +199,9 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
} }
}); });
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, string_vector{F_(wifi), F_(reconnect)}, [](Shell &, const std::vector<std::string> &) {
CommandFlags::ADMIN, EMSESP::network_.reconnect();
string_vector{F_(wifi), F_(reconnect)}, });
[](Shell & shell, const std::vector<std::string> & arguments) { EMSESP::network_.reconnect(); });
// //
// SET commands // SET commands
@@ -244,7 +243,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN, CommandFlags::ADMIN,
string_vector{F_(set), F_(wifi), F_(password)}, string_vector{F_(set), F_(wifi), F_(password)},
[](Shell & shell, const std::vector<std::string> & arguments) { [](Shell & shell, const std::vector<std::string> &) {
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) { shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
if (completed) { if (completed) {
shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) { shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) {
@@ -367,9 +366,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
// EMS device commands // EMS device commands
// //
commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, {F_(scan)}, [](Shell & shell, const std::vector<std::string> & arguments) { commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, {F_(scan)}, [](Shell &, const std::vector<std::string> &) { EMSESP::scan_devices(); });
EMSESP::scan_devices();
});
/* removed scan deep /* removed scan deep
commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, {F_(scan)}, {F_(deep_optional)}, [](Shell & shell, const std::vector<std::string> & arguments) { commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, {F_(scan)}, {F_(deep_optional)}, [](Shell & shell, const std::vector<std::string> & arguments) {
+77 -73
View File
@@ -52,7 +52,7 @@ bool EMSdevice::has_entities() const {
} }
bool found = false; bool found = false;
EMSESP::webCustomizationService.read([&](WebCustomization & settings) { EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
for (EntityCustomization entityCustomization : settings.entityCustomizations) { for (const auto & entityCustomization : settings.entityCustomizations) {
if (entityCustomization.device_id == device_id() && entityCustomization.entity_ids.size()) { if (entityCustomization.device_id == device_id() && entityCustomization.entity_ids.size()) {
found = true; found = true;
break; break;
@@ -564,17 +564,17 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const ch
// add to device value library, also know now as a "device entity" // add to device value library, also know now as a "device entity"
// this function will also apply any customizations to the entity // this function will also apply any customizations to the entity
void EMSdevice::add_device_value(int8_t tag, // to be used to group mqtt together, either as separate topics as a nested object void EMSdevice::add_device_value(int8_t tag, // to be used to group mqtt together, either as separate topics as a nested object
void * value_p, // pointer to the value from the .h file void * value_p, // pointer to the value from the .h file
uint8_t type, // one of DeviceValueType uint8_t type, // one of DeviceValueType
const char * const ** options, // options for enum, which are translated as a list of lists const char * const ** options, // options for enum, which are translated as a list of lists
const char * const * options_single, // list of names const char * const * options_single, // list of names
int8_t numeric_operator, // to divide or multiply, see DeviceValueNumOps:: int8_t numeric_operator, // to divide or multiply, see DeviceValueNumOps::
const char * const * name, // list of names, including shortname and translations const char * const * name, // list of names, including shortname and translations
uint8_t uom, // unit of measure from DeviceValueUOM uint8_t uom, // unit of measure from DeviceValueUOM
const cmd_function_p f, // command function pointer const cmd_function_p & f, // command function pointer
int16_t min, // min allowed value int16_t min, // min allowed value
uint32_t max // max allowed value uint32_t max // max allowed value
) { ) {
uint8_t state = DeviceValueState::DV_DEFAULT; // determine state uint8_t state = DeviceValueState::DV_DEFAULT; // determine state
auto custom_fullname = std::string(""); // custom fullname auto custom_fullname = std::string(""); // custom fullname
@@ -636,7 +636,7 @@ void EMSdevice::add_device_value(int8_t tag, // to b
// scan through customizations to see if it's on the exclusion list by matching the productID and deviceID // scan through customizations to see if it's on the exclusion list by matching the productID and deviceID
EMSESP::webCustomizationService.read([&](WebCustomization & settings) { EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
for (EntityCustomization entityCustomization : settings.entityCustomizations) { for (const auto & entityCustomization : settings.entityCustomizations) {
if ((entityCustomization.product_id == product_id()) && (entityCustomization.device_id == device_id())) { if ((entityCustomization.product_id == product_id()) && (entityCustomization.device_id == device_id())) {
char entity[70]; char entity[70];
if (tag < DeviceValueTAG::TAG_HC1) { if (tag < DeviceValueTAG::TAG_HC1) {
@@ -645,7 +645,7 @@ void EMSdevice::add_device_value(int8_t tag, // to b
snprintf(entity, sizeof(entity), "%s/%s", tag_to_mqtt(tag), short_name); snprintf(entity, sizeof(entity), "%s/%s", tag_to_mqtt(tag), short_name);
} }
for (const std::string & entity_id : entityCustomization.entity_ids) { for (const auto & entity_id : entityCustomization.entity_ids) {
// if there is an appended custom name, strip it to get the true entity name // if there is an appended custom name, strip it to get the true entity name
// and extract the new custom name // and extract the new custom name
auto custom_name_pos = entity_id.find('|'); auto custom_name_pos = entity_id.find('|');
@@ -693,96 +693,100 @@ void EMSdevice::erase_device_values() {
} }
} }
devicevalues_.clear(); devicevalues_.clear();
telegram_functions_.clear();
handlers_ignored_.clear();
handlers_config_.clear();
handlers_broadcasted_.clear();
} }
// single list of options // single list of options
void EMSdevice::register_device_value(int8_t tag, void EMSdevice::register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const char * const * options_single, const char * const * options_single,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f) { const cmd_function_p & f) {
// create a multi-list from the options // create a multi-list from the options
add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, 0, 0); add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, 0, 0);
}; };
// single list of options, with no translations, with min and max // single list of options, with no translations, with min and max
void EMSdevice::register_device_value(int8_t tag, void EMSdevice::register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const char * const * options_single, const char * const * options_single,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p & f,
int16_t min, int16_t min,
uint32_t max) { uint32_t max) {
// create a multi-list from the options // create a multi-list from the options
add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, min, max); add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, min, max);
}; };
void EMSdevice::register_device_value(int8_t tag, void EMSdevice::register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
int8_t numeric_operator, int8_t numeric_operator,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f) { const cmd_function_p & f) {
add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, 0, 0); add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, 0, 0);
} }
void EMSdevice::register_device_value(int8_t tag, void EMSdevice::register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
int8_t numeric_operator, int8_t numeric_operator,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p & f,
int16_t min, int16_t min,
uint32_t max) { uint32_t max) {
add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, min, max); add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, min, max);
} }
// no options, no function // no options, no function
void EMSdevice::register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f) { void EMSdevice::register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p & f) {
add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, 0, 0); add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, 0, 0);
}; };
// no options, with min/max // no options, with min/max
void EMSdevice::register_device_value(int8_t tag, void EMSdevice::register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p & f,
int16_t min, int16_t min,
uint32_t max) { uint32_t max) {
add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, min, max); add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, min, max);
}; };
// function with min and max values // function with min and max values
// adds a new command to the command list // adds a new command to the command list
// in this function we separate out the short and long names and take any translations // in this function we separate out the short and long names and take any translations
void EMSdevice::register_device_value(int8_t tag, void EMSdevice::register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const char * const ** options, const char * const ** options,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p & f,
int16_t min, int16_t min,
uint32_t max) { uint32_t max) {
add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, min, max); add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, min, max);
} }
// function with no min and max values (set to 0) // function with no min and max values (set to 0)
void EMSdevice::register_device_value(int8_t tag, void EMSdevice::register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const char * const ** options, const char * const ** options,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f) { const cmd_function_p & f) {
add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, 0, 0); add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, 0, 0);
} }
@@ -1206,10 +1210,10 @@ void EMSdevice::generate_values_web_customization(JsonArray output) {
// this is when the mask has it's high bit (0x80) set // this is when the mask has it's high bit (0x80) set
// https://github.com/emsesp/EMS-ESP32/issues/891 // https://github.com/emsesp/EMS-ESP32/issues/891
EMSESP::webCustomizationService.read([&](WebCustomization & settings) { EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
for (EntityCustomization entityCustomization : settings.entityCustomizations) { for (const auto & entityCustomization : settings.entityCustomizations) {
if (entityCustomization.device_id == device_id()) { if (entityCustomization.device_id == device_id()) {
// entity_ids is a list of all entities with the mask prefixed in the string // entity_ids is a list of all entities with the mask prefixed in the string
for (const std::string & entity_id : entityCustomization.entity_ids) { for (const auto & entity_id : entityCustomization.entity_ids) {
uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str()); uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str());
if (mask & 0x80) { if (mask & 0x80) {
JsonObject obj = output.add<JsonObject>(); JsonObject obj = output.add<JsonObject>();
+56 -55
View File
@@ -290,78 +290,79 @@ class EMSdevice {
void generate_values_web(JsonObject output, const bool is_dashboard = false); void generate_values_web(JsonObject output, const bool is_dashboard = false);
void generate_values_web_customization(JsonArray output); void generate_values_web_customization(JsonArray output);
void add_device_value(int8_t tag, void add_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const char * const ** options, const char * const ** options,
const char * const * options_single, const char * const * options_single,
int8_t numeric_operator, int8_t numeric_operator,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p & f,
int16_t min, int16_t min,
uint32_t max); uint32_t max);
void register_device_value(int8_t tag, void register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const char * const ** options, const char * const ** options,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p & f,
int16_t min, int16_t min,
uint32_t max); uint32_t max);
void erase_device_values(); void erase_device_values();
void void
register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom, const cmd_function_p f); register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom, const cmd_function_p & f);
void register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom); void register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom);
void register_device_value(int8_t tag, void register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
int8_t numeric_operator, int8_t numeric_operator,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f = nullptr); const cmd_function_p & f = nullptr);
void register_device_value(int8_t tag, void register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
int8_t numeric_operator, int8_t numeric_operator,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p & f,
int16_t min, int16_t min,
uint32_t max); uint32_t max);
// single list of options // single list of options
void register_device_value(int8_t tag, void register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const char * const * options_single, const char * const * options_single,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f = nullptr); const cmd_function_p & f = nullptr);
// single list of options, with no translations, with min and max // single list of options, with no translations, with min and max
void register_device_value(int8_t tag, void register_device_value(int8_t tag,
void * value_p, void * value_p,
uint8_t type, uint8_t type,
const char * const * options_single, const char * const * options_single,
const char * const * name, const char * const * name,
uint8_t uom, uint8_t uom,
const cmd_function_p f, const cmd_function_p & f,
int16_t min, int16_t min,
uint32_t max); uint32_t max);
// no options, optional function f // no options, optional function f
void register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f = nullptr); void register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p & f = nullptr);
// no options, with min/max // no options, with min/max
void register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f, int16_t min, uint32_t max); void
register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p & f, int16_t min, uint32_t max);
void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) const; void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) const;
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) const; void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) const;
+5 -5
View File
@@ -192,6 +192,11 @@ class DeviceValue {
int16_t min; // min range int16_t min; // min range
uint32_t max; // max range uint32_t max; // max range
// optional custom name from customization. Allocated on heap only when actually set,
// so unnamed entities (the vast majority) don't pay for an inline std::string.
// Prefer custom_fullname() / set_custom_fullname() / has_custom_fullname() over direct access.
std::unique_ptr<std::string> custom_fullname_;
DeviceValue(uint8_t device_type, // EMSdevice::DeviceType DeviceValue(uint8_t device_type, // EMSdevice::DeviceType
int8_t tag, // DeviceValueTAG::* int8_t tag, // DeviceValueTAG::*
void * value_p, // pointer to variable of any type void * value_p, // pointer to variable of any type
@@ -245,11 +250,6 @@ class DeviceValue {
static const char * const * DeviceValueTAG_s[]; static const char * const * DeviceValueTAG_s[];
static const char * const DeviceValueTAG_mqtt[]; static const char * const DeviceValueTAG_mqtt[];
static uint8_t NUM_TAGS; // # tags static uint8_t NUM_TAGS; // # tags
private:
// optional custom name from customization. Allocated on heap only when actually set,
// so unnamed entities (the vast majority) don't pay for an inline std::string.
std::unique_ptr<std::string> custom_fullname_;
}; };
}; // namespace emsesp }; // namespace emsesp
+42 -17
View File
@@ -54,19 +54,19 @@ bool EMSESP::entity_compaction_pending_ = false;
AsyncWebServer webServer(80); AsyncWebServer webServer(80);
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
FS dummyFS; FS dummyFS;
auto & fsRef = dummyFS; FS * const fsRef = &dummyFS;
#else #else
auto & fsRef = LittleFS; FS * const fsRef = &LittleFS;
#endif #endif
ESP32React EMSESP::esp32React(&webServer, &fsRef); ESP32React EMSESP::esp32React(&webServer, fsRef);
WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager()); WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager()); WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager()); WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
WebCommandService EMSESP::webCommandService = WebCommandService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager()); WebCommandService EMSESP::webCommandService = WebCommandService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager()); WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, &fsRef, EMSESP::esp32React.getSecurityManager()); WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, fsRef, EMSESP::esp32React.getSecurityManager());
WebActivityService EMSESP::webActivityService = WebActivityService(&webServer, EMSESP::esp32React.getSecurityManager()); WebActivityService EMSESP::webActivityService = WebActivityService(&webServer, EMSESP::esp32React.getSecurityManager());
WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp32React.getSecurityManager()); WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp32React.getSecurityManager());
@@ -173,10 +173,23 @@ uint8_t EMSESP::device_id_from_cmd(const uint8_t device_type, const char * cmd,
return 0; return 0;
} }
// clears list of recognized devices // remove a single device with all it's entities, commands, and telegrams
void EMSESP::erase_device(const uint8_t type_id) {
for (auto it = emsdevices.begin(); it < emsdevices.end(); it++) {
if ((*it)->device_id() == type_id) {
(*it)->erase_device_values();
emsdevices.erase(it);
break;
}
}
}
// clears list of recognized devices, entities and telegrams
void EMSESP::clear_all_devices() { void EMSESP::clear_all_devices() {
// temporarily removed: clearing the list causes a crash, the associated commands and mqtt should also be removed. for (const auto & emsdevice : emsdevices) {
// emsdevices.clear(); // remove entries, but doesn't delete actual devices emsdevice->erase_device_values();
}
emsdevices.clear();
} }
// called from EMSdevice/Command whenever an entity or telegram handler is registered. // called from EMSdevice/Command whenever an entity or telegram handler is registered.
@@ -644,8 +657,8 @@ void EMSESP::reset_mqtt_ha() {
} }
// force the re-creating of the temperature and analog sensor topics (for HA) // force the re-creating of the temperature and analog sensor topics (for HA)
temperaturesensor_.reload(); temperaturesensor_.ha_reset();
analogsensor_.reload(); analogsensor_.ha_reset();
// rebuild MQTT HA config topics for shower, custom entities and scheduler // rebuild MQTT HA config topics for shower, custom entities and scheduler
shower_.ha_reset(); shower_.ha_reset();
@@ -1027,10 +1040,13 @@ void EMSESP::process_UBADevices(const std::shared_ptr<const Telegram> & telegram
if (next_byte & 0x01) { if (next_byte & 0x01) {
// if we haven't already detected this device, request it's version details, unless its us (EMS-ESP) // if we haven't already detected this device, request it's version details, unless its us (EMS-ESP)
// when the version info is received, it will automagically add the device // when the version info is received, it will automagically add the device
if ((device_id != EMSbus::ems_bus_id()) && !(EMSESP::device_exists(device_id))) { if ((device_id != EMSbus::ems_bus_id()) && !(device_exists(device_id))) {
LOG_DEBUG("New EMS device detected with ID 0x%02X. Requesting version information.", device_id); LOG_DEBUG("New EMS device detected with ID 0x%02X. Requesting version information.", device_id);
send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id); send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
} }
} else if (device_exists(device_id) && !device_hasEntities(device_id)) {
LOG_DEBUG("Remove EMS device with ID 0x%02X", device_id);
erase_device(device_id); //
} }
next_byte = next_byte >> 1; // advance 1 bit next_byte = next_byte >> 1; // advance 1 bit
} }
@@ -1237,6 +1253,15 @@ bool EMSESP::process_telegram(const std::shared_ptr<const Telegram> & telegram)
return telegram_found; return telegram_found;
} }
bool EMSESP::device_hasEntities(const uint8_t device_id) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice->is_device_id(device_id) && (emsdevice->count_entities() > 0)) {
return true;
}
}
return false; // not found
}
// return true if we have this device already registered // return true if we have this device already registered
bool EMSESP::device_exists(const uint8_t device_id) { bool EMSESP::device_exists(const uint8_t device_id) {
if (emsdevices.empty()) { if (emsdevices.empty()) {
@@ -1459,7 +1484,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
// see if we have a custom device name in our Customizations list, and if so set it // see if we have a custom device name in our Customizations list, and if so set it
webCustomizationService.read([&](WebCustomization const & settings) { webCustomizationService.read([&](WebCustomization const & settings) {
for (EntityCustomization e : settings.entityCustomizations) { for (const auto & e : settings.entityCustomizations) {
if ((e.device_id == device_id) && (e.product_id == product_id)) { if ((e.device_id == device_id) && (e.product_id == product_id)) {
LOG_DEBUG("Have customizations for %s with deviceID 0x%02X productID %d", e.custom_name.c_str(), device_id, product_id); LOG_DEBUG("Have customizations for %s with deviceID 0x%02X productID %d", e.custom_name.c_str(), device_id, product_id);
emsdevices.back()->custom_name(e.custom_name); emsdevices.back()->custom_name(e.custom_name);
+3 -1
View File
@@ -101,7 +101,7 @@ class Module {}; // forward declaration
// for Command Function callbacks (Command::cmd_function_p). The unified callback takes a JsonObject // for Command Function callbacks (Command::cmd_function_p). The unified callback takes a JsonObject
// output which entity/setter commands ignore. // output which entity/setter commands ignore.
#define MAKE_CF_CB(__f) [&](const char * value, const int8_t id, JsonObject output) { return __f(value, id); } #define MAKE_CF_CB(__f) [this](const char * value, const int8_t id, JsonObject output) { return __f(value, id); }
namespace emsesp { namespace emsesp {
@@ -152,6 +152,7 @@ class EMSESP {
static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value); static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value);
static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid); static void send_write_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid);
static bool device_hasEntities(const uint8_t device_id);
static bool device_exists(const uint8_t device_id); static bool device_exists(const uint8_t device_id);
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);
@@ -238,6 +239,7 @@ class EMSESP {
static bool add_device(const uint8_t device_id, const uint8_t product_id, const char * version, const uint8_t brand); static bool add_device(const uint8_t device_id, const uint8_t product_id, const char * version, const uint8_t brand);
static void scan_devices(); static void scan_devices();
static void erase_device(const uint8_t type_id);
static void clear_all_devices(); static void clear_all_devices();
// called whenever a device entity or telegram handler is registered, so we can // called whenever a device entity or telegram handler is registered, so we can
+49 -30
View File
@@ -59,50 +59,60 @@ bool LED::loop(uint8_t healthcheck, bool button_busy) {
// the user-requested LED blink always has preference // the user-requested LED blink always has preference
if (!is_user_led_blink_) { if (!is_user_led_blink_) {
// check for button press. // check for button press.
// if button is pressed, show LED (yellow on RGB LED, on/off on standard LED) // while the button is held show the LED (yellow on RGB LED, on/off on standard LED),
// it will turn off on the next loop cycle // and on release go back to the default state and re-check the health on the next cycle
if (last_button_busy_ != button_busy) { if (last_button_busy_ != button_busy) {
last_button_busy_ = button_busy; last_button_busy_ = button_busy;
set_led(button_busy ? Color::OFF : Color::YELLOW); // Yellow if (button_busy) {
set_led(Color::YELLOW); // Yellow
} else {
reset_led();
}
return false; return false;
} }
// check the system health. // check the system health.
// Set the sequence accordingly, only if the healthcheck is not 0 and has changed // Set the sequence accordingly, only if the healthcheck is not 0 and has changed
if (healthcheck != previous_healthcheck_) { if (healthcheck != previous_healthcheck_) {
previous_healthcheck_ = healthcheck; color_steps_[0] = Color::OFF;
color_steps_[0] = Color::OFF; color_steps_[1] = Color::OFF;
color_steps_[1] = Color::OFF; color_steps_[2] = Color::OFF;
color_steps_[2] = Color::OFF;
// if the healthcheck is 0, i.e. system is healthy, reset the LED // if the healthcheck is 0, i.e. system is healthy, reset the LED
if (healthcheck == 0) { if (healthcheck == 0) {
reset_led(); reset_led();
return false; } else {
// 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
}
} }
// 1 flash (blue) is the EMS bus is not connected previous_healthcheck_ = healthcheck; // must be set after reset_led(), which invalidates it
// 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 // there's nothing to flash, so leave the LED in the state reset_led() gave it.
if (no_network) { // Running the sequence here would keep writing Color::OFF and a healthy system would never show green.
color_steps_[0] = Color::RED; // red, no network if (healthcheck == 0 || button_busy) {
} else if (no_bus) { return false;
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
}
} }
} }
@@ -119,6 +129,15 @@ void LED::reset_led() {
color_steps_[0] = Color::OFF; color_steps_[0] = Color::OFF;
color_steps_[1] = Color::OFF; color_steps_[1] = Color::OFF;
color_steps_[2] = Color::OFF; 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
previous_healthcheck_ = System::HEALTHCHECK_RESET;
// rewind the sequence so it starts from the top if the health degrades again
led_long_timer_ = 1; // 1 will kick it off immediately
led_short_timer_ = 0;
led_flash_step_ = 0;
} }
// LED flash every few ms and then perform a factory reset // LED flash every few ms and then perform a factory reset
@@ -313,4 +332,4 @@ void LED::start_led_fast_flash(uint8_t duration) {
led_fast_flash_timer_ = true; // it's active led_fast_flash_timer_ = true; // it's active
} }
} // namespace emsesp } // namespace emsesp
+23 -22
View File
@@ -179,28 +179,29 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(fanWork), 257, 1), // fanwork REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(fanWork), 257, 1), // fanwork
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(ignWork), 258, 1), // ignwork REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(ignWork), 258, 1), // ignwork
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(oilPreHeat), 259, 1), // oilpreheat REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(oilPreHeat), 259, 1), // oilpreheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMaxPower), 260, 1), // burnmaxpower REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMinPower), 260, 1), // burnminpower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMinPeriod), 261, 1), // burnminperiod REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMaxPower), 261, 1), // burnmaxpower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(absBurnPow), 262, 1), // absburnpow REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(burnMinPeriod), 262, 1), // burnminperiod
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatblock), 263, 1), // heatblock REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(absBurnPow), 263, 1), // absburnpow
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boilHystOn), 264, 1), // boilhyston REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatblock), 264, 1), // heatblock
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boilHystOff), 265, 1), // boilhystoff REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boilHystOn), 265, 1), // boilhyston
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boil2HystOn), 266, 1), // boil2hyston REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boilHystOff), 266, 1), // boilhystoff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boil2HystOff), 267, 1), // boil2hystoff REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boil2HystOn), 267, 1), // boil2hyston
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveOn), 268, 1), // curveon REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(boil2HystOff), 268, 1), // boil2hystoff
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveBase), 269, 1), // curvebase REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveOn), 269, 1), // curveon
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveEnd), 270, 1), // curveend REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveBase), 270, 1), // curvebase
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(summertemp), 271, 1), // summertemp REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(curveEnd), 271, 1), // curveend
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nofrostmode), 272, 1), // nofrostmode REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(summertemp), 272, 1), // summertemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nofrosttemp), 273, 1), // nofrosttemp REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nofrostmode), 273, 1), // nofrostmode
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(gasMeterHeat), 274, 2), // gasmeterheat REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nofrosttemp), 274, 1), // nofrosttemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgHeat2), 276, 2), // nrgheat2 REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(gasMeterHeat), 275, 2), // gasmeterheat
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nomPower), 278, 1), // nompower REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nrgHeat2), 277, 2), // nrgheat2
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(netFlowTemp), 279, 1), // netflowtemp REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(nomPower), 279, 1), // nompower
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatValve), 280, 1), // heatvalve REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(netFlowTemp), 280, 1), // netflowtemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(keepWarmTemp), 281, 1), // keepwarmtemp REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatValve), 281, 1), // heatvalve
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(setReturnTemp), 282, 1), // setreturntemp REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(keepWarmTemp), 282, 1), // keepwarmtemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingOn), 283, 1), // heating REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(setReturnTemp), 283, 1), // setreturntemp
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DEVICE_DATA, FL_(heatingOn), 284, 1), // heating
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(nrgWw), 0, 2), // nrg REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(nrgWw), 0, 2), // nrg
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(meterWw), 2, 2), // meter REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(meterWw), 2, 2), // meter
REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(upTimeCompWw), 4, 2), // uptimecomp REGISTER_MAPPING(dt::BOILER, TAG_TYPE_DHW, FL_(upTimeCompWw), 4, 2), // uptimecomp
+44 -30
View File
@@ -313,6 +313,31 @@ void Mqtt::on_publish(uint16_t packetId) const {
LOG_DEBUG("Packet %d sent successful", packetId); LOG_DEBUG("Packet %d sent successful", packetId);
} }
// gracefully disconnect from the broker by sending a proper MQTT DISCONNECT packet.
// this is called just before a restart so the broker ends our session cleanly
// (avoids a lingering session that shows up as "session taken over" on reconnect).
//
// the disconnect is deferred inside espMqttClient - the DISCONNECT packet is only
// queued and actually sent when loop() runs the state machine. On PSRAM boards the
// client has its own task that keeps pumping loop(), so it flushes on its own. On
// non-PSRAM boards (UseInternalTask::NO) nothing drives loop() once we're mid-restart,
// so we pump it here until the client is fully disconnected, bounded by a timeout.
void Mqtt::disconnect() {
if (!mqttClient_) {
return;
}
mqttClient_->disconnect(); // graceful - queues the DISCONNECT packet
if (EMSESP::system_.PSram() == 0) {
uint32_t start = uuid::get_uptime();
while (!mqttClient_->disconnected() && (uuid::get_uptime() - start) < MQTT_DISCONNECT_TIMEOUT) {
mqttClient_->loop();
delay(2);
}
}
}
// called when MQTT settings have changed via the MQTT Settings or Application Settings Web pages // called when MQTT settings have changed via the MQTT Settings or Application Settings Web pages
void Mqtt::reset_mqtt() { void Mqtt::reset_mqtt() {
if (!enabled()) { if (!enabled()) {
@@ -326,6 +351,7 @@ void Mqtt::reset_mqtt() {
if (mqttClient_->connected()) { if (mqttClient_->connected()) {
mqttClient_->disconnect(true); // force a disconnect mqttClient_->disconnect(true); // force a disconnect
} }
load_settings(); // reload MQTT settings
} }
// load the settings from service // load the settings from service
@@ -447,7 +473,7 @@ bool Mqtt::get_publish_onchange(uint8_t device_type) {
return false; return false;
} }
void Mqtt::on_disconnect(espMqttClientTypes::DisconnectReason reason) { void Mqtt::on_disconnect(espMqttClientTypes::DisconnectReason reason) {
// only show the error once, not every 2 seconds // only show the error once on the first connect failure
if (!connecting_) { if (!connecting_) {
return; return;
} }
@@ -486,8 +512,6 @@ void Mqtt::on_connect() {
connecting_ = true; connecting_ = true;
queuecount_ = mqttClient_->queueSize(); queuecount_ = mqttClient_->queueSize();
load_settings(); // reload MQTT settings - in case they have changes
if (ha_enabled_) { if (ha_enabled_) {
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
@@ -587,9 +611,11 @@ void Mqtt::ha_status() {
// These come from the info MQTT topic - and handled in the publish_ha_sensor_config function // These come from the info MQTT topic - and handled in the publish_ha_sensor_config function
publish_system_ha_sensor_config(DeviceValueType::STRING, "Version", "version", DeviceValueUOM::NONE); publish_system_ha_sensor_config(DeviceValueType::STRING, "Version", "version", DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::STRING, "Boot time", "bootTime", DeviceValueUOM::UPTIME); publish_system_ha_sensor_config(DeviceValueType::STRING,
"Uptime",
"bootTime",
DeviceValueUOM::UPTIME); // called Uptime in HA and derives value from bootTime element in info topic
} }
// add sub or pub task to the queue. // add sub or pub task to the queue.
// the base is not included in the topic // the base is not included in the topic
bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain) { bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain) {
@@ -988,10 +1014,9 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// set the entity_id. This is breaking change in HA 2025.10.0 - see https://github.com/home-assistant/core/pull/151775 // set the entity_id. This is breaking change in HA 2025.10.0 - see https://github.com/home-assistant/core/pull/151775
// extract the string from topic up to the / using std::string // extract the string from topic up to the / using std::string
// it must be lowercase (although HA will slugify it and not reject it)
std::string topic_str(topic); std::string topic_str(topic);
doc["def_ent_id"] = topic_str.substr(0, topic_str.find("/")) + "." + uniq_id; doc["def_ent_id"] = Helpers::toLower(topic_str.substr(0, topic_str.find("/"))) + "." + Helpers::toLower(uniq_id);
char sample_val[30] = "0"; // sample, correct(!) entity value, used only to prevent warning/error in HA if real value is not published yet
// we add the command topic parameter for commands // we add the command topic parameter for commands
if (has_cmd) { if (has_cmd) {
@@ -1015,13 +1040,11 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
for (uint8_t i = 0; i < options_size; i++) { for (uint8_t i = 0; i < options_size; i++) {
option_list.add(Helpers::itoa(i)); // as a string option_list.add(Helpers::itoa(i)); // as a string
} }
snprintf(sample_val, sizeof(sample_val), "'0'");
} else { } else {
// use strings // use strings
for (uint8_t i = 0; i < options_size; i++) { for (uint8_t i = 0; i < options_size; i++) {
option_list.add(Helpers::translated_word(options[i])); option_list.add(Helpers::translated_word(options[i]));
} }
snprintf(sample_val, sizeof(sample_val), "'%s'", Helpers::translated_word(options[0]));
} }
} else if (type != DeviceValueType::STRING && type != DeviceValueType::BOOL) { } else if (type != DeviceValueType::STRING && type != DeviceValueType::BOOL) {
// For numeric's add the range and mode // For numeric's add the range and mode
@@ -1043,7 +1066,6 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
if (dv_set_min != 0 || dv_set_max != 0) { if (dv_set_min != 0 || dv_set_max != 0) {
doc["min"] = dv_set_min; doc["min"] = dv_set_min;
doc["max"] = dv_set_max; doc["max"] = dv_set_max;
snprintf(sample_val, sizeof(sample_val), "%i", dv_set_min);
} }
} }
@@ -1067,7 +1089,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
snprintf(stat_t, sizeof(stat_t), "~/%s", tag_to_topic(device_type, tag).c_str()); snprintf(stat_t, sizeof(stat_t), "~/%s", tag_to_topic(device_type, tag).c_str());
// Override - there are exceptions for DeviceType::SYSTEM, which uses the heartbeat topic // Override - there are exceptions for DeviceType::SYSTEM, which uses the heartbeat topic
// and when fetching the version and bootTime we want to take this from the info topic instead // and when fetching the version and uptime we want to take this from the info topic instead
if (device_type == EMSdevice::DeviceType::SYSTEM) { if (device_type == EMSdevice::DeviceType::SYSTEM) {
// handle the exceptions // handle the exceptions
if (strncmp(entity, "version", 7) == 0) { if (strncmp(entity, "version", 7) == 0) {
@@ -1095,18 +1117,19 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// has no unit of measure or icon, and must be true/false (not on/off or 1/0) // has no unit of measure or icon, and must be true/false (not on/off or 1/0)
if (type == DeviceValueType::BOOL) { if (type == DeviceValueType::BOOL) {
add_ha_bool(doc.as<JsonObject>()); add_ha_bool(doc.as<JsonObject>());
strlcpy(sample_val, "false", sizeof(sample_val)); // default is "false"
} }
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery // don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
if (discovery_type() == discoveryType::HOMEASSISTANT) { if (discovery_type() == discoveryType::HOMEASSISTANT) {
// handle special cases for value_template depending on the uom
if (uom == DeviceValueUOM::TIMESTAMP) { if (uom == DeviceValueUOM::TIMESTAMP) {
// special case for timestamp, using "value_template": "{{ (value_json.bootTime | as_datetime).isoformat() }}", // uptime
char val_tpl[100]; char val_tpl[100];
snprintf(val_tpl, sizeof(val_tpl), "{{ (value_json.%s | as_datetime).isoformat() }}", entity); snprintf(val_tpl, sizeof(val_tpl), "{{ (value_json.%s | as_datetime).isoformat() }}", entity);
doc["val_tpl"] = val_tpl; doc["val_tpl"] = val_tpl;
} else { } else {
doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else " + sample_val + "}}"; // default
doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else 'None'}}";
} }
add_ha_avty_section(doc.as<JsonObject>(), stat_t, val_cond); // adds availability section add_ha_avty_section(doc.as<JsonObject>(), stat_t, val_cond); // adds availability section
} else { } else {
@@ -1141,7 +1164,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// https://developers.home-assistant.io/docs/core/entity/sensor/ // https://developers.home-assistant.io/docs/core/entity/sensor/
// Home Assistant tracks the min, max and mean value during the statistics period // Home Assistant tracks the min, max and mean value during the statistics period
// For statistics, the sensor must have state_class set to either MEASUREMENT, TOTAL or TOTAL_INCREASING // For statistics, the sensor must have state_class set to either MEASUREMENT, TOTAL or TOTAL_INCREASING
// When set to MEASUREMENT the device_class must cannot be DATE, ENUM, ENERGY, GAS, MONETARY, TIMESTAMP, VOLUME or WATER // When set to MEASUREMENT the device_class must cannot be DATE, ENUM, ENERGY, GAS, MONETARY, TIMESTAMP, UPTIME, VOLUME or WATER
// //
void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8_t type, const uint8_t uom, const char * entity, bool display_only) { void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8_t type, const uint8_t uom, const char * entity, bool display_only) {
if (device_type == EMSdevice::DeviceType::SYSTEM) { if (device_type == EMSdevice::DeviceType::SYSTEM) {
@@ -1280,7 +1303,7 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
doc[sc_ha] = sc_ha_measurement; doc[sc_ha] = sc_ha_measurement;
doc[dc_ha] = "timestamp"; doc[dc_ha] = "timestamp";
break; break;
case DeviceValueUOM::UPTIME: case DeviceValueUOM::UPTIME: // no state class for uptime
doc[dc_ha] = "uptime"; doc[dc_ha] = "uptime";
break; break;
case DeviceValueUOM::MV: case DeviceValueUOM::MV:
@@ -1403,7 +1426,7 @@ bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_room
snprintf(mode_str_tpl, snprintf(mode_str_tpl,
sizeof(mode_str_tpl), sizeof(mode_str_tpl),
"{%%if %s%%}off{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}off{%%elif %s=='%s'%%}off{%%elif " "{%%if %s%%}None{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}off{%%elif %s=='%s'%%}off{%%elif "
"%s=='%s'%%}off{%%else%%}auto{%%endif%%}", "%s=='%s'%%}off{%%else%%}auto{%%endif%%}",
hc_mode_cond, hc_mode_cond,
hc_mode_s, hc_mode_s,
@@ -1440,15 +1463,15 @@ bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_room
doc["mode_stat_tpl"] = mode_str_tpl; doc["mode_stat_tpl"] = mode_str_tpl;
doc["temp_cmd_t"] = temp_cmd_s; doc["temp_cmd_t"] = temp_cmd_s;
doc["temp_stat_t"] = topic_t; doc["temp_stat_t"] = topic_t;
doc["temp_stat_tpl"] = (std::string) "{{" + seltemp_s + " if " + seltemp_cond + " else 0}}"; doc["temp_stat_tpl"] = (std::string) "{{" + seltemp_s + " if " + seltemp_cond + " else 'None'}}";
if (has_roomtemp) { if (has_roomtemp) {
doc["curr_temp_t"] = topic_t; doc["curr_temp_t"] = topic_t;
doc["curr_temp_tpl"] = (std::string) "{{" + currtemp_s + " if " + currtemp_cond + " else 0}}"; doc["curr_temp_tpl"] = (std::string) "{{" + currtemp_s + " if " + currtemp_cond + " else 'None'}}";
} }
if (tag >= DeviceValueTAG::TAG_SRC1) { if (tag >= DeviceValueTAG::TAG_SRC1) {
doc["curr_hum_t"] = topic_t; doc["curr_hum_t"] = topic_t;
doc["curr_hum_tpl"] = (std::string) "{{" + currhum_s + " if " + currhum_cond + " else 0}}"; doc["curr_hum_tpl"] = (std::string) "{{" + currhum_s + " if " + currhum_cond + " else 'None'}}";
} }
doc["min_temp"] = Helpers::render_value(min_s, min, 0, EMSESP::system_.fahrenheit() ? 2 : 0); doc["min_temp"] = Helpers::render_value(min_s, min, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
@@ -1602,9 +1625,6 @@ void Mqtt::add_ha_avty_section(JsonObject doc, const char * state_t, const char
avty_json["t"] = state; avty_json["t"] = state;
snprintf(tpl, sizeof(tpl), "{{'online' if %s else 'offline'}}", cond1); snprintf(tpl, sizeof(tpl), "{{'online' if %s else 'offline'}}", cond1);
avty_json["val_tpl"] = tpl; avty_json["val_tpl"] = tpl;
if (!avty.add(avty_json)) {
LOG_WARNING("Failed to add availability condition 1 (low memory)");
}
} }
// condition 2 // condition 2
@@ -1613,9 +1633,6 @@ void Mqtt::add_ha_avty_section(JsonObject doc, const char * state_t, const char
avty_json["t"] = state; avty_json["t"] = state;
snprintf(tpl, sizeof(tpl), "{{'online' if %s else 'offline'}}", cond2); snprintf(tpl, sizeof(tpl), "{{'online' if %s else 'offline'}}", cond2);
avty_json["val_tpl"] = tpl; avty_json["val_tpl"] = tpl;
if (!avty.add(avty_json)) {
LOG_WARNING("Failed to add availability condition 2 (low memory)");
}
} }
// negative condition // negative condition
@@ -1624,9 +1641,6 @@ void Mqtt::add_ha_avty_section(JsonObject doc, const char * state_t, const char
avty_json["t"] = state; avty_json["t"] = state;
snprintf(tpl, sizeof(tpl), "{{'offline' if %s else 'online'}}", negcond); snprintf(tpl, sizeof(tpl), "{{'offline' if %s else 'online'}}", negcond);
avty_json["val_tpl"] = tpl; avty_json["val_tpl"] = tpl;
if (!avty.add(avty_json)) {
LOG_WARNING("Failed to add negative availability condition (low memory)");
}
} }
} }
+4 -7
View File
@@ -61,8 +61,9 @@ class Mqtt {
enum Operation : uint8_t { PUBLISH, SUBSCRIBE, UNSUBSCRIBE }; enum Operation : uint8_t { PUBLISH, SUBSCRIBE, UNSUBSCRIBE };
enum NestedFormat : uint8_t { NESTED = 1, SINGLE }; 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 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 void on_connect(); static void on_connect();
static void on_disconnect(espMqttClientTypes::DisconnectReason reason); static void on_disconnect(espMqttClientTypes::DisconnectReason reason);
@@ -132,11 +133,7 @@ class Mqtt {
return mqttClient_ ? mqttClient_->connected() : false; return mqttClient_ ? mqttClient_->connected() : false;
} }
static void disconnect() { static void disconnect();
if (mqttClient_) {
mqttClient_->disconnect();
};
}
static bool enabled() { static bool enabled() {
return mqtt_enabled_; return mqtt_enabled_;
+105 -19
View File
@@ -82,7 +82,13 @@ void Network::begin() {
WiFi.persistent(false); WiFi.persistent(false);
WiFi.setAutoReconnect(false); WiFi.setAutoReconnect(false);
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
WiFi.disconnect(true, true); // wipe old settings in NVS. Will give a warning on boot but can be ignored.
if (WiFi.STA.started()) {
WiFi.disconnect(true, true); // wipe old settings; only when STA is up
} else {
WiFi.disconnect(true, false); // disconnect/off without erase, so no warning is shown
}
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
WiFi.setHostname(hostname_.c_str()); // updates shared default_hostname buffer WiFi.setHostname(hostname_.c_str()); // updates shared default_hostname buffer
WiFi.enableSTA(true); // creates the STA netif WiFi.enableSTA(true); // creates the STA netif
@@ -152,7 +158,7 @@ void Network::begin() {
} }
// format the BSSID (MAC address) of the network interface // format the BSSID (MAC address) of the network interface
bool Network::formatBSSID(const String & bssid, uint8_t (&mac)[6]) { bool Network::formatBSSID([[maybe_unused]] const String & bssid, [[maybe_unused]] uint8_t (&mac)[6]) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
uint tmp[6]; uint tmp[6];
if (bssid.isEmpty() || sscanf(bssid.c_str(), "%X:%X:%X:%X:%X:%X", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != 6) { if (bssid.isEmpty() || sscanf(bssid.c_str(), "%X:%X:%X:%X:%X:%X", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != 6) {
@@ -243,11 +249,12 @@ void Network::reconnect() {
// pick the first phase that has the hardware/config to even be attempted on this device. // pick the first phase that has the hardware/config to even be attempted on this device.
// boards without an Ethernet PHY skip straight to WIFI; without a configured SSID, straight to AP // boards without an Ethernet PHY skip straight to WIFI; without a configured SSID, straight to AP
// (unless AP provision mode is Never — then stay on WIFI and keep waiting)
NetPhase Network::initialPhase() const { NetPhase Network::initialPhase() const {
if (phy_type_ != PHY_type::PHY_TYPE_NONE) { if (phy_type_ != PHY_type::PHY_TYPE_NONE) {
return NetPhase::ETHERNET; return NetPhase::ETHERNET;
} }
if (!ssid_.isEmpty()) { if (!ssid_.isEmpty() || ap_provisionMode_ == AP_MODE_NEVER) {
return NetPhase::WIFI; return NetPhase::WIFI;
} }
return NetPhase::AP; return NetPhase::AP;
@@ -256,6 +263,16 @@ NetPhase Network::initialPhase() const {
// network loop, looking for new and disconnecting networks // network loop, looking for new and disconnecting networks
void Network::loop() { void Network::loop() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
// a reconnect was requested from another task, run it here where nothing is mid-request
if (reconnect_pending_) {
reconnect_pending_ = false;
reconnect();
}
// already have a connection: verify it's still alive
if (network_ip_ != 0 || last_disconnect_reason_ != 0) {
checkConnection();
}
// 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 = uuid::get_uptime_ms(); const unsigned long currentMillis = uuid::get_uptime_ms();
const uint32_t reconnectDelay = const uint32_t reconnectDelay =
@@ -268,11 +285,6 @@ void Network::loop() {
startWIFI(); // WiFi startWIFI(); // WiFi
startAP(); // Captive Portal (AP) startAP(); // Captive Portal (AP)
// already have a connection: verify it's still alive
if (network_ip_ != 0 || last_disconnect_reason_ != 0) {
checkConnection();
}
findNetworks(); // detect any new network connections findNetworks(); // detect any new network connections
} }
@@ -331,7 +343,7 @@ void Network::checkConnection() {
// 802.11ac - wifi5 // 802.11ac - wifi5
// 802.11ax - wifi6 // 802.11ax - wifi6
// tx_power is the Tx power to set, 0 for auto // tx_power is the Tx power to set, 0 for auto
void Network::setWiFiPower(uint8_t tx_power) { void Network::setWiFiPower([[maybe_unused]] uint8_t tx_power) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (tx_power != 0) { if (tx_power != 0) {
if (!WiFi.setTxPower(static_cast<wifi_power_t>(tx_power))) { if (!WiFi.setTxPower(static_cast<wifi_power_t>(tx_power))) {
@@ -413,7 +425,7 @@ void Network::startmDNS() const {
} }
// WiFi disconnect reason code to string // WiFi disconnect reason code to string
const char * Network::disconnectReason(uint8_t code) { const char * Network::disconnectReason([[maybe_unused]] uint8_t code) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
switch (code) { switch (code) {
case WIFI_REASON_UNSPECIFIED: // = 1, case WIFI_REASON_UNSPECIFIED: // = 1,
@@ -516,6 +528,22 @@ void Network::startWIFI() {
#endif #endif
} }
#if CONFIG_IDF_TARGET_ESP32 && !defined(EMSESP_STANDALONE)
// Unlike WiFi, which stamps the hostname onto the STA netif as it is created, the Arduino ETH class
// leaves it unset, so lwIP falls back to CONFIG_LWIP_LOCAL_HOSTNAME ("tasmota" in the SDK we build
// against). esp_eth_start() polls the PHY synchronously, so on a warm reboot where the link never
// dropped the DHCP DISCOVER can go out before ETH.begin() has even returned. This handler is
// registered ahead of the esp_netif glue and therefore runs first, applying the hostname before the
// netif is handed to lwIP.
static char eth_hostname[33] = {0};
static void ethApplyHostname(void * /*arg*/, esp_event_base_t /*base*/, int32_t /*event_id*/, void * /*event_data*/) {
if (eth_hostname[0] != '\0' && ETH.netif() != nullptr) {
esp_netif_set_hostname(ETH.netif(), eth_hostname);
}
}
#endif
// Ethernet management // Ethernet management
// Brings up the ETH driver / netif exactly once. After ETH.begin() returns true the driver // Brings up the ETH driver / netif exactly once. After ETH.begin() returns true the driver
// continues to run autonomously (link negotiation, DHCP, etc) // continues to run autonomously (link negotiation, DHCP, etc)
@@ -527,8 +555,12 @@ void Network::startEthernet() {
return; return;
} }
// already up and running, nothing to do // The driver is installed once and then left alone. Re-running the bring-up on a live interface
if (ethernet_connect_pending_ || ethernet_connected()) { // tears it down behind our back: pinMode() on the PHY power pin hands the pin from the Ethernet
// bus back to GPIO, which fires the peripheral manager's deinit callback and calls ETH.end(),
// destroying the netif, its event group and the event handlers from the main loop task while the
// arduino_events task may be dispatching a link-up on the other core.
if (ethernet_started_ || ethernet_connect_pending_ || ethernet_connected()) {
return; return;
} }
@@ -539,6 +571,17 @@ void Network::startEthernet() {
return; return;
} }
/*
// The Tasmota SDK is built with CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=2, so the Ethernet MAC is derived as
// a locally-administered address from base+1 instead of the universal base+3 that Arduino Core 2 used.
// Seed the MAC table with the old value before the EMAC driver reads it, so DHCP reservations survive an upgrade.
uint8_t eth_mac[6];
if (esp_read_mac(eth_mac, ESP_MAC_BASE) == ESP_OK) {
eth_mac[5] += 3;
esp_iface_mac_addr_set(eth_mac, ESP_MAC_ETH);
}
*/
// reset power and add a delay as ETH doesn't not always start up correctly after a warm boot // reset power and add a delay as ETH doesn't not always start up correctly after a warm boot
if (eth_power_ != -1) { if (eth_power_ != -1) {
pinMode(eth_power_, OUTPUT); pinMode(eth_power_, OUTPUT);
@@ -560,16 +603,45 @@ void Network::startEthernet() {
eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720 eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720
: (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_TLK110 : (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_TLK110
: ETH_PHY_RTL8201; // Type of the Ethernet PHY (LAN8720 or TLK110) : ETH_PHY_RTL8201; // Type of the Ethernet PHY (LAN8720 or TLK110)
// must be in place before ETH.begin() so it beats the esp_netif glue to the START event
strlcpy(eth_hostname, hostname_.c_str(), sizeof(eth_hostname));
if (!eth_hostname_handler_registered_ && esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_START, ethApplyHostname, nullptr) == ESP_OK) {
eth_hostname_handler_registered_ = true;
}
if (ETH.begin(type, eth_phy_addr_, 23, 18, eth_power_, (eth_clock_mode_t)eth_clock_mode_)) { if (ETH.begin(type, eth_phy_addr_, 23, 18, eth_power_, (eth_clock_mode_t)eth_clock_mode_)) {
LOG_DEBUG("Ethernet module found - starting"); // LOG_DEBUG("Ethernet module found - initialising");
ethernet_started_ = true;
ethernet_connect_pending_ = true; ethernet_connect_pending_ = true;
ETH.setHostname(hostname_.c_str()); // Push hostname to the ETH netif immediately after it's created
// check whether DHCP already announced us under lwIP's default hostname, before we correct it
esp_netif_t * eth_netif = ETH.netif();
const char * old_hostname = nullptr;
bool stale_dhcp = false;
if (eth_netif != nullptr && !staticIPConfig_) {
esp_netif_dhcp_status_t dhcp_status = ESP_NETIF_DHCP_INIT;
stale_dhcp = esp_netif_get_hostname(eth_netif, &old_hostname) == ESP_OK && old_hostname != nullptr && hostname_ != old_hostname
&& esp_netif_dhcpc_get_status(eth_netif, &dhcp_status) == ESP_OK && dhcp_status == ESP_NETIF_DHCP_STARTED;
}
if (!ETH.setHostname(hostname_.c_str())) {
LOG_WARNING("Failed to set Ethernet hostname to %s", hostname_.c_str());
}
// the DHCP server has already been given the wrong name, so re-run the client to re-announce
if (stale_dhcp) {
LOG_DEBUG("Restarting Ethernet DHCP client to announce hostname %s", hostname_.c_str());
esp_netif_dhcpc_stop(eth_netif);
esp_netif_dhcpc_start(eth_netif);
}
ETH.enableIPv6(true); ETH.enableIPv6(true);
if (staticIPConfig_) { if (staticIPConfig_) {
ETH.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_); ETH.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_);
} }
} else { } else {
LOG_ERROR("Failed to start Ethernet module"); LOG_ERROR("Failed to start Ethernet");
} }
#endif #endif
#endif #endif
@@ -614,6 +686,11 @@ void Network::findNetworks() {
} }
const NetIface candidate = iface_from_desc(desc); const NetIface candidate = iface_from_desc(desc);
// AP is disabled in settings — don't treat SoftAP as a usable network
if (candidate == NetIface::AP && ap_provisionMode_ == AP_MODE_NEVER) {
continue;
}
if (iface_priority(candidate) <= iface_priority(best_iface)) { if (iface_priority(candidate) <= iface_priority(best_iface)) {
continue; // already have something at least as good continue; // already have something at least as good
} }
@@ -692,12 +769,17 @@ void Network::findNetworks() {
: "AP"); : "AP");
// give up on this interface and try the next one. ETHERNET -> WIFI (or straight to AP if no SSID configured); WIFI -> AP. // give up on this interface and try the next one. ETHERNET -> WIFI (or straight to AP if no SSID configured); WIFI -> AP.
// AP_MODE_NEVER: never promote to the AP phase — keep waiting on the current interface.
if (connect_retry_ >= MAX_NETWORK_RECONNECTION_ATTEMPTS && phase_ != NetPhase::AP) { if (connect_retry_ >= MAX_NETWORK_RECONNECTION_ATTEMPTS && phase_ != NetPhase::AP) {
if (phase_ == NetPhase::ETHERNET) { if (phase_ == NetPhase::ETHERNET) {
if (ssid_.isEmpty()) { if (ssid_.isEmpty()) {
if (!ethernet_ever_connected_) { if (!ethernet_ever_connected_) {
LOG_WARNING("Ethernet failed to connect after %u attempts, falling back to AP", connect_retry_); if (ap_provisionMode_ == AP_MODE_NEVER) {
phase_ = NetPhase::AP; LOG_WARNING("Ethernet failed to connect after %u attempts (AP provision mode is Never)", connect_retry_);
} else {
LOG_WARNING("Ethernet failed to connect after %u attempts, falling back to AP", connect_retry_);
phase_ = NetPhase::AP;
}
} }
} else { } else {
LOG_WARNING("Ethernet failed to connect after %u attempts, switching to WiFi", connect_retry_); LOG_WARNING("Ethernet failed to connect after %u attempts, switching to WiFi", connect_retry_);
@@ -706,8 +788,12 @@ void Network::findNetworks() {
ethernet_connect_pending_ = false; ethernet_connect_pending_ = false;
} else if (phase_ == NetPhase::WIFI) { } else if (phase_ == NetPhase::WIFI) {
if (!wifi_ever_connected_) { if (!wifi_ever_connected_) {
LOG_WARNING("WiFi failed to connect after %u attempts, falling back to AP", connect_retry_); if (ap_provisionMode_ == AP_MODE_NEVER) {
phase_ = NetPhase::AP; LOG_WARNING("WiFi failed to connect after %u attempts (AP provision mode is Never)", connect_retry_);
} else {
LOG_WARNING("WiFi failed to connect after %u attempts, falling back to AP", connect_retry_);
phase_ = NetPhase::AP;
}
} }
wifi_connect_pending_ = false; wifi_connect_pending_ = false;
} }
+12 -3
View File
@@ -20,6 +20,7 @@
#define EMSESP_NETWORK_H_ #define EMSESP_NETWORK_H_
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
#include <esp_mac.h>
#include <esp_wifi.h> #include <esp_wifi.h>
#include <ETH.h> #include <ETH.h>
#include <WiFi.h> #include <WiFi.h>
@@ -137,6 +138,11 @@ class Network {
void reconnect(); void reconnect();
// ask for a reconnect() to run from the next Network::loop()
void schedule_reconnect() {
reconnect_pending_ = true;
}
// map a netif description string (from esp_netif_get_desc) to a NetIface // map a netif description string (from esp_netif_get_desc) to a NetIface
static NetIface iface_from_desc(const char * desc) { static NetIface iface_from_desc(const char * desc) {
if (!desc) { if (!desc) {
@@ -201,12 +207,15 @@ class Network {
volatile bool wifi_connect_pending_ = false; volatile bool wifi_connect_pending_ = false;
volatile bool ethernet_connect_pending_ = false; volatile bool ethernet_connect_pending_ = false;
volatile bool reconnect_pending_ = false; // set by schedule_reconnect(), actioned in loop()
NetPhase phase_ = NetPhase::ETHERNET; NetPhase phase_ = NetPhase::ETHERNET;
bool wifi_events_registered_ = false; // ensure WiFi.onEvent() handlers are registered only once across begin()/reconnect() cycles bool wifi_events_registered_ = false; // ensure WiFi.onEvent() handlers are registered only once across begin()/reconnect() cycles
bool wifi_ever_connected_ = false; // set true once we've successfully obtained an IP bool eth_hostname_handler_registered_ = false; // for the ETHERNET_EVENT_START hostname handler
bool ethernet_ever_connected_ = false; // set true once we've successfully obtained an IP bool ethernet_started_ = false; // ETH.begin() succeeded; the driver runs for the lifetime of the boot
bool wifi_ever_connected_ = false; // set true once we've successfully obtained an IP
bool ethernet_ever_connected_ = false; // set true once we've successfully obtained an IP
// Network and AP settings // Network and AP settings
bool enableMDNS_; bool enableMDNS_;
+2 -2
View File
@@ -36,7 +36,7 @@ void Shower::start() {
Command::add_json( Command::add_json(
EMSdevice::DeviceType::BOILER, EMSdevice::DeviceType::BOILER,
F_(coldshot), F_(coldshot),
[&](const char * value, const int8_t id, JsonObject output) { [this](const char * value, const int8_t id, JsonObject output) {
LOG_INFO("Forcing coldshot..."); LOG_INFO("Forcing coldshot...");
if (shower_state_) { if (shower_state_) {
output["message"] = "OK"; output["message"] = "OK";
@@ -229,7 +229,7 @@ void Shower::create_ha_discovery() {
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery // don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) { if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
doc["val_tpl"] = "{{value_json.duration if value_json.duration is defined else 0}}"; doc["val_tpl"] = "{{value_json.duration if value_json.duration is defined else 'None'}}";
} else { } else {
doc["val_tpl"] = "{{value_json.duration}}"; doc["val_tpl"] = "{{value_json.duration}}";
} }
+3 -1
View File
@@ -712,9 +712,11 @@ int http_request(std::string url, const std::string & method, const std::string
// assets / large CDN responses. Such servers do not negotiate max_fragment_length, so the body // assets / large CDN responses. Such servers do not negotiate max_fragment_length, so the body
// can't be decoded and reads return 0. If this path is ever used to fetch large or CDN-hosted // can't be decoded and reads return 0. If this path is ever used to fetch large or CDN-hosted
// payloads, bump the RX buffer to 16384 (see uploadFirmwareURL in core/system.cpp for reference). // payloads, bump the RX buffer to 16384 (see uploadFirmwareURL in core/system.cpp for reference).
ssl_client->setBufferSizes(1024, 1024); ssl_client->setBufferSizes(16384, 1024);
ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds) ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds)
} }
basic_client->setTimeout(5000); // socket-level read timeout
ssl_client->setTimeout(5); // Stream::readBytes timeout used by Update
ssl_client->setClient(basic_client, is_https); // enableSSL = false for plain HTTP ssl_client->setClient(basic_client, is_https); // enableSSL = false for plain HTTP
url.replace(0, is_https ? 8 : 7, ""); url.replace(0, is_https ? 8 : 7, "");
+41 -26
View File
@@ -93,6 +93,20 @@ uint32_t System::min_free_mem_;
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_;
#ifndef EMSESP_STANDALONE
// The DNS slots are shared between IPv4 and IPv6, so with IPv6 enabled the first slot can hold an
// IPv6 server. Pick the first IPv4 one so it matches the label we print it under.
static IPAddress dns_ipv4(const NetworkInterface & netif) {
for (uint8_t i = 0; i < ESP_NETIF_DNS_MAX; i++) {
IPAddress ip = netif.dnsIP(i);
if (ip.type() == IPv4 && static_cast<uint32_t>(ip) != 0) {
return ip;
}
}
return IPAddress();
}
#endif
// find the index of the language // find the index of the language
// 0 = EN, 1 = DE, etc... // 0 = EN, 1 = DE, etc...
uint8_t System::language_index() { uint8_t System::language_index() {
@@ -110,7 +124,7 @@ bool System::command_send(const char * value, const int8_t id) {
} }
// send email via SMTP // send email via SMTP
bool System::command_sendmail(const char * value, const int8_t id) { bool System::command_sendmail(const char * value, const int8_t) {
bool enabled = false; bool enabled = false;
uint8_t security; uint8_t security;
uint16_t port; uint16_t port;
@@ -132,21 +146,23 @@ bool System::command_sendmail(const char * value, const int8_t id) {
LOG_DEBUG("Command sendmail port %d%s called with '%s'", LOG_DEBUG("Command sendmail port %d%s called with '%s'",
port, port,
security == EMAIL_SECURITY::SSL ? " (SSL)" security == EMAIL_SECURITY::SSL ? " (SSL)"
: security == EMAIL_SECURITY::STARTTLS ? "" : security == EMAIL_SECURITY::STARTTLS ? " (STARTTLS)"
: " (STARTTLS)", : "",
value); value);
bool success = false; bool success = false;
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
WiFiClient * basic_client = new WiFiClient; auto * basic_client = new WiFiClient;
ESP_SSLClient * ssl_client = new ESP_SSLClient; auto * ssl_client = new ESP_SSLClient;
ReadyClient * r_client = new ReadyClient(*ssl_client); auto * r_client = new ReadyClient(*ssl_client);
SMTPClient * smtp = new SMTPClient(*r_client); auto * smtp = new SMTPClient(*r_client);
ssl_client->setClient(basic_client); ssl_client->setClient(basic_client);
ssl_client->setInsecure(); ssl_client->setInsecure();
ssl_client->setBufferSizes(1024, 1024); ssl_client->setBufferSizes(16384, 1024);
basic_client->setTimeout(5000); // socket-level read timeout
ssl_client->setTimeout(5); // Stream::readBytes timeout used by Update
r_client->addPort(port, r_client->addPort(port,
security == EMAIL_SECURITY::NONE ? readymail_protocol_plain_text security == EMAIL_SECURITY::NONE ? readymail_protocol_plain_text
: security == EMAIL_SECURITY::SSL ? readymail_protocol_ssl : security == EMAIL_SECURITY::SSL ? readymail_protocol_ssl
@@ -487,7 +503,7 @@ void System::set_partition_install_date() {
} }
// sets the partition to use on the next restart // sets the partition to use on the next restart
bool System::set_partition(const char * partitionname) { bool System::set_partition([[maybe_unused]] const char * partitionname) {
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
return true; return true;
#else #else
@@ -572,7 +588,7 @@ void System::system_restart(const char * partitionname) {
} }
Serial.flush(); // wait for hardware TX buffer to drain Serial.flush(); // wait for hardware TX buffer to drain
Mqtt::disconnect(); // gracefully disconnect MQTT, needed for QOS1 Mqtt::disconnect(); // gracefully disconnect MQTT (flushes the DISCONNECT before reboot, needed for QOS1)
EMSuart::stop(); // stop UART so there is no interference EMSuart::stop(); // stop UART so there is no interference
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
delay(1000); // wait 1 second delay(1000); // wait 1 second
@@ -826,7 +842,7 @@ bool System::loop() {
void System::send_info_mqtt() { void System::send_info_mqtt() {
static uint8_t _connection = 0; static uint8_t _connection = 0;
uint8_t connection = (EMSESP::network_.ethernet_connected() ? 1 : 0) + (EMSESP::network_.wifi_connected() ? 2 : 0) + (ntp_connected_ ? 4 : 0) uint8_t connection = (EMSESP::network_.ethernet_connected() ? 1 : 0) + (EMSESP::network_.wifi_connected() ? 2 : 0) + (ntp_connected_ ? 4 : 0)
+ (EMSESP::network_.has_ipv6() ? 8 : 0); + (EMSESP::network_.has_ipv6() ? 8 : 0);
// check if connection status has changed // check if connection status has changed
if (!Mqtt::connected() || connection == _connection) { if (!Mqtt::connected() || connection == _connection) {
return; return;
@@ -839,7 +855,7 @@ void System::send_info_mqtt() {
// if NTP is enabled send the boot_time in local time in ISO 8601 format (eg: 2022-11-15 20:46:38) // if NTP is enabled send the boot_time in local time in ISO 8601 format (eg: 2022-11-15 20:46:38)
// https://github.com/emsesp/EMS-ESP32/issues/751 // https://github.com/emsesp/EMS-ESP32/issues/751
if (ntp_connected_) { if (time(nullptr) > 1500000000L) {
char time_string[25]; char time_string[25];
time_t now = time(nullptr) - uuid::get_uptime_sec(); time_t now = time(nullptr) - uuid::get_uptime_sec();
strftime(time_string, 25, "%FT%T%z", localtime(&now)); strftime(time_string, 25, "%FT%T%z", localtime(&now));
@@ -858,8 +874,7 @@ void System::send_info_mqtt() {
doc["MAC"] = WiFi.macAddress(); doc["MAC"] = WiFi.macAddress();
doc["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask()); doc["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask());
doc["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP()); doc["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP());
doc["IPv4 nameserver"] = uuid::printable_to_string(WiFi.dnsIP()); doc["IPv4 DNS Server"] = uuid::printable_to_string(dns_ipv4(WiFi.STA));
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") { if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
doc["IPv6 address"] = uuid::printable_to_string(WiFi.linkLocalIPv6()); doc["IPv6 address"] = uuid::printable_to_string(WiFi.linkLocalIPv6());
} }
@@ -1168,7 +1183,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" Hostname: %s", WiFi.getHostname()); shell.printfln(" Hostname: %s", WiFi.getHostname());
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str()); shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(WiFi.gatewayIP()).c_str()); shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(WiFi.gatewayIP()).c_str());
shell.printfln(" IPv4 nameserver: %s", uuid::printable_to_string(WiFi.dnsIP()).c_str()); shell.printfln(" IPv4 DNS Server: %s", uuid::printable_to_string(dns_ipv4(WiFi.STA)).c_str());
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") { if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(WiFi.linkLocalIPv6()).c_str()); shell.printfln(" IPv6 address: %s", uuid::printable_to_string(WiFi.linkLocalIPv6()).c_str());
} }
@@ -1201,7 +1216,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" Hostname: %s", ETH.getHostname()); shell.printfln(" Hostname: %s", ETH.getHostname());
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str()); shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str());
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(ETH.gatewayIP()).c_str()); shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(ETH.gatewayIP()).c_str());
shell.printfln(" IPv4 nameserver: %s", uuid::printable_to_string(ETH.dnsIP()).c_str()); shell.printfln(" IPv4 DNS Server: %s", uuid::printable_to_string(dns_ipv4(ETH)).c_str());
if (ETH.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && ETH.linkLocalIPv6().toString() != "::") { if (ETH.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && ETH.linkLocalIPv6().toString() != "::") {
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(ETH.linkLocalIPv6()).c_str()); shell.printfln(" IPv6 address: %s", uuid::printable_to_string(ETH.linkLocalIPv6()).c_str());
} }
@@ -1532,7 +1547,7 @@ bool System::check_upgrade() {
EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) { EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) {
if (apSettings.provisionMode == 0) { if (apSettings.provisionMode == 0) {
apSettings.provisionMode = AP_MODE_DISCONNECTED; // AP_MODE_DISCONNECTED is the new default apSettings.provisionMode = AP_MODE_DISCONNECTED; // AP_MODE_DISCONNECTED is the new default
LOG_INFO("Upgrade: Setting AP provision mode to auto"); LOG_INFO("Upgrade: Setting AP provision mode to on disconnect");
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
} }
return StateUpdateResult::UNCHANGED; return StateUpdateResult::UNCHANGED;
@@ -1694,7 +1709,7 @@ static const std::pair<const char *, const char *> SECTION_MAP[] = {
#endif #endif
// convert a single config file into a section of the output json object // convert a single config file into a section of the output json object
void System::exportSettings(const std::string & type, const char * filename, JsonObject output) { void System::exportSettings(const std::string & type, [[maybe_unused]] const char * filename, JsonObject output) {
if (type != "settings") { if (type != "settings") {
output["type"] = type; // add the type to the output, not for settings as it's already added because its grouped output["type"] = type; // add the type to the output, not for settings as it's already added because its grouped
} }
@@ -1849,7 +1864,7 @@ void System::exportSystemBackup(JsonObject output) {
} }
// write a settings file using input from a json object, called from upload/restore // write a settings file using input from a json object, called from upload/restore
bool System::saveSettings(const char * filename, JsonObject input) { bool System::saveSettings([[maybe_unused]] const char * filename, [[maybe_unused]] JsonObject input) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
const char * section = nullptr; const char * section = nullptr;
for (const auto & [f, label] : SECTION_MAP) { for (const auto & [f, label] : SECTION_MAP) {
@@ -2819,7 +2834,7 @@ bool System::load_board_profile(std::vector<int8_t> & data, const std::string &
// rgb for RGB // rgb for RGB
// For example: /api/system/led?data=red:blink1 // For example: /api/system/led?data=red:blink1
// For older non-RGB models, the colour would default to just being on. // For older non-RGB models, the colour would default to just being on.
bool System::command_led(const char * value, const int8_t id) { bool System::command_led(const char * value, const int8_t) {
if (!value) { if (!value) {
return false; // no argument return false; // no argument
} }
@@ -2842,7 +2857,7 @@ bool System::command_led(const char * value, const int8_t id) {
} }
// 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) {
bool arg; bool arg;
if (!Helpers::value2bool(value, arg)) { if (!Helpers::value2bool(value, arg)) {
return false; // argument not recognized return false; // argument not recognized
@@ -2985,7 +3000,7 @@ bool System::ntp_connected() {
} }
// see if its a BBQKees Gateway by checking the eFuse values // see if its a BBQKees Gateway by checking the eFuse values
String System::getBBQKeesGatewayDetails(uint8_t detail) { String System::getBBQKeesGatewayDetails([[maybe_unused]] uint8_t detail) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
union { union {
struct { struct {
@@ -3095,7 +3110,7 @@ bool System::uploadFirmwareURL(const char * url) {
ssl_client.setSessionTimeout(120); ssl_client.setSessionTimeout(120);
} }
basic_client.setTimeout(15000); // socket-level read timeout basic_client.setTimeout(15000); // socket-level read timeout
ssl_client.setTimeout(15000); // Stream::readBytes timeout used by Update ssl_client.setTimeout(15); // Stream::readBytes timeout used by Update
ssl_client.setClient(&basic_client, is_https); // enableSSL = false for plain HTTP ssl_client.setClient(&basic_client, is_https); // enableSSL = false for plain HTTP
const uint16_t port = is_https ? 443 : 80; const uint16_t port = is_https ? 443 : 80;
@@ -3235,7 +3250,7 @@ bool System::uploadFirmwareURL(const char * url) {
return false; // error return false; // error
} }
LOG_INFO("Firmware uploading (size: %d KB) over %s. Please wait...", firmware_size / 1024, is_https ? "HTTPS" : "HTTP"); LOG_INFO("Firmware uploading (size: %dKB) over %s. Please wait...", firmware_size / 1024, is_https ? "HTTPS" : "HTTP");
Shell::loop_all(); // flush log buffers so latest messages are shown in console Shell::loop_all(); // flush log buffers so latest messages are shown in console
@@ -3401,7 +3416,7 @@ bool System::readCommand(const char * data) {
} }
// system read command // system read command
bool System::command_read(const char * value, const int8_t id) { bool System::command_read(const char * value, const int8_t) {
return readCommand(value); return readCommand(value);
} }
@@ -3696,7 +3711,7 @@ void System::restore_snapshot_gpios(std::vector<int8_t> & u_gpios, std::vector<i
} }
// show the contents of a directory in the LittleFS filesystem // show the contents of a directory in the LittleFS filesystem
void System::listDir(const char * dirname, uint8_t levels) { void System::listDir([[maybe_unused]] const char * dirname, [[maybe_unused]] uint8_t levels) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
+9 -6
View File
@@ -45,17 +45,20 @@ void TemperatureSensor::start(const bool factory_settings) {
Mqtt::subscribe(EMSdevice::DeviceType::TEMPERATURESENSOR, topic, nullptr); // use empty function callback Mqtt::subscribe(EMSdevice::DeviceType::TEMPERATURESENSOR, topic, nullptr); // use empty function callback
} }
void TemperatureSensor::ha_reset() {
for (auto & sensor : sensors_) {
remove_ha_topic(sensor.id());
sensor.ha_registered = false; // force HA configs to be re-created
}
}
// load settings // load settings
void TemperatureSensor::reload() { void TemperatureSensor::reload() {
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;
}); });
ha_reset();
for (auto & sensor : sensors_) {
remove_ha_topic(sensor.id());
sensor.ha_registered = false; // force HA configs to be re-created
}
} }
void TemperatureSensor::loop() { void TemperatureSensor::loop() {
@@ -543,7 +546,7 @@ void TemperatureSensor::publish_values(const bool force) {
// for the value template, there's a problem still with Domoticz probably due to the special characters. // for the value template, there's a problem still with Domoticz probably due to the special characters.
// See https://github.com/emsesp/EMS-ESP32/issues/1360 // See https://github.com/emsesp/EMS-ESP32/issues/1360
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) { if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else -55}}"; config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else 'None'}}";
} else { } else {
config["val_tpl"] = (std::string) "{{" + val_obj + "}}"; // omit value conditional Jinja2 template code config["val_tpl"] = (std::string) "{{" + val_obj + "}}"; // omit value conditional Jinja2 template code
} }
+1
View File
@@ -93,6 +93,7 @@ class TemperatureSensor {
void publish_sensor(const Sensor & sensor); void publish_sensor(const Sensor & sensor);
void publish_values(const bool force); void publish_values(const bool force);
void reload(); void reload();
void ha_reset();
bool updated_values(); bool updated_values();
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1); bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
+21 -21
View File
@@ -220,13 +220,13 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &fanWork_, DeviceValueType::BOOL, FL_(fanWork), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &fanWork_, DeviceValueType::BOOL, FL_(fanWork), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &ignWork_, DeviceValueType::BOOL, FL_(ignWork), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &ignWork_, DeviceValueType::BOOL, FL_(ignWork), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &oilPreHeat_, DeviceValueType::BOOL, FL_(oilPreHeat), DeviceValueUOM::NONE); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &oilPreHeat_, DeviceValueType::BOOL, FL_(oilPreHeat), DeviceValueUOM::NONE);
// remove burnMinPower, #2918 // remove burnMinPower, #2918, add back for 0xE6, #3193
// register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
// &burnMinPower_, &burnMinPower_,
// DeviceValueType::UINT8, DeviceValueType::UINT8,
// FL_(burnMinPower), FL_(burnMinPower),
// DeviceValueUOM::PERCENT, DeviceValueUOM::PERCENT,
// MAKE_CF_CB(set_min_power)); MAKE_CF_CB(set_min_power));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&burnMaxPower_, &burnMaxPower_,
DeviceValueType::UINT8, DeviceValueType::UINT8,
@@ -1622,7 +1622,7 @@ void Boiler::process_UBAParametersPlus(const std::shared_ptr<const Telegram> & t
has_update(telegram, heatingActivated_, 0); has_update(telegram, heatingActivated_, 0);
has_update(telegram, heatingTemp_, 1); has_update(telegram, heatingTemp_, 1);
has_update(telegram, burnMaxPower_, 4); has_update(telegram, burnMaxPower_, 4);
// has_update(telegram, burnMinPower_, 5); // removed #2918 has_update(telegram, burnMinPower_, 5); // removed #2918
has_update(telegram, boilHystOff_, 8); has_update(telegram, boilHystOff_, 8);
has_update(telegram, boilHystOn_, 9); has_update(telegram, boilHystOn_, 9);
has_update(telegram, burnMinPeriod_, 10); has_update(telegram, burnMinPeriod_, 10);
@@ -1822,7 +1822,7 @@ void Boiler::process_HpPool(const std::shared_ptr<const Telegram> & telegram) {
// Boiler(0x08) -> All(0x00), ?(0x04A2), data: 02 01 01 00 01 00 // Boiler(0x08) -> All(0x00), ?(0x04A2), data: 02 01 01 00 01 00
// Boiler(0x08) -W-> Me(0x0B), HpInput(0x04A2), data: 20 07 06 01 00 (from #802) // Boiler(0x08) -W-> Me(0x0B), HpInput(0x04A2), data: 20 07 06 01 00 (from #802)
// see https://github.com/emsesp/EMS-ESP32/issues/2844#issuecomment-3689049155 // see https://github.com/emsesp/EMS-ESP32/issues/2844#issuecomment-3689049155
void Boiler::process_HpInput(const std::shared_ptr<const Telegram> & telegram) { void Boiler::process_HpInput(const std::shared_ptr<const Telegram> &) {
} }
// Heatpump inputs settings- type 0x486 (https://github.com/emsesp/EMS-ESP32/issues/600) // Heatpump inputs settings- type 0x486 (https://github.com/emsesp/EMS-ESP32/issues/600)
@@ -1923,7 +1923,7 @@ void Boiler::process_UBASetPoints2(const std::shared_ptr<const Telegram> & teleg
} }
// 0x35 - not yet implemented, not readable, only for settings // 0x35 - not yet implemented, not readable, only for settings
void Boiler::process_UBAFlags(const std::shared_ptr<const Telegram> & telegram) { void Boiler::process_UBAFlags(const std::shared_ptr<const Telegram> &) {
} }
// 0x1C // 0x1C
@@ -1942,7 +1942,7 @@ void Boiler::process_UBAMaintenanceStatus(const std::shared_ptr<const Telegram>
} }
// 0xBF // 0xBF
void Boiler::process_ErrorMessage(const std::shared_ptr<const Telegram> & telegram) { void Boiler::process_ErrorMessage(const std::shared_ptr<const Telegram> &) {
EMSESP::send_read_request(0xC2, device_id(), 0, 20); // read last errorcode EMSESP::send_read_request(0xC2, device_id(), 0, 20); // read last errorcode
EMSESP::send_read_request(0xC6, device_id(), 0, 21); // read last errorcode EMSESP::send_read_request(0xC6, device_id(), 0, 21); // read last errorcode
} }
@@ -2494,7 +2494,7 @@ bool Boiler::set_ww_disinfect_temp(const char * value, const int8_t id) {
} }
// Set the dhw priority // Set the dhw priority
bool Boiler::set_ww_prio(const char * value, const int8_t id) { bool Boiler::set_ww_prio(const char * value, const int8_t) {
bool b; bool b;
if (!Helpers::value2bool(value, b)) { if (!Helpers::value2bool(value, b)) {
return false; return false;
@@ -2814,7 +2814,7 @@ bool Boiler::set_pump_delay(const char * value, const int8_t id) {
} }
// set pump logic temperature // set pump logic temperature
bool Boiler::set_pumpOnTemp(const char * value, const int8_t id) { bool Boiler::set_pumpOnTemp(const char * value, const int8_t) {
int v; int v;
if (!Helpers::value2temperature(value, v)) { if (!Helpers::value2temperature(value, v)) {
return false; return false;
@@ -3037,7 +3037,7 @@ bool Boiler::set_reset(const char * value, const int8_t id) {
return false; return false;
} }
bool Boiler::set_manDefrost(const char * value, const int8_t id) { bool Boiler::set_manDefrost(const char * value, const int8_t) {
bool b; bool b;
if (!Helpers::value2bool(value, b)) { if (!Helpers::value2bool(value, b)) {
return false; return false;
@@ -3046,7 +3046,7 @@ bool Boiler::set_manDefrost(const char * value, const int8_t id) {
return true; return true;
} }
bool Boiler::set_chimneysweeper(const char * value, const int8_t id) { bool Boiler::set_chimneysweeper(const char * value, const int8_t) {
bool b; bool b;
if (!Helpers::value2bool(value, b)) { if (!Helpers::value2bool(value, b)) {
return false; return false;
@@ -3325,7 +3325,7 @@ bool Boiler::set_auxLimit(const char * value, const int8_t id) {
return false; return false;
} }
bool Boiler::set_auxHeaterSource(const char * value, const int8_t id) { bool Boiler::set_auxHeaterSource(const char * value, const int8_t) {
uint8_t v; uint8_t v;
if (Helpers::value2enum(value, v, FL_(enum_auxHeaterSource))) { if (Helpers::value2enum(value, v, FL_(enum_auxHeaterSource))) {
write_command(0x491, 0, v, 0x491); write_command(0x491, 0, v, 0x491);
@@ -3409,7 +3409,7 @@ bool Boiler::set_hpMaxPower(const char * value, const int8_t id) {
return false; return false;
} }
bool Boiler::set_pvMaxComp(const char * value, const int8_t id) { bool Boiler::set_pvMaxComp(const char * value, const int8_t) {
float v; float v;
if (Helpers::value2float(value, v)) { if (Helpers::value2float(value, v)) {
write_command(0x484, 54, (uint8_t)(v * 10), 0x484); write_command(0x484, 54, (uint8_t)(v * 10), 0x484);
@@ -3437,7 +3437,7 @@ bool Boiler::set_hpPowerLimit(const char * value, const int8_t id) {
return false; return false;
} }
bool Boiler::set_powerReduction(const char * value, const int8_t id) { bool Boiler::set_powerReduction(const char * value, const int8_t) {
int v; int v;
if (Helpers::value2number(value, v)) { if (Helpers::value2number(value, v)) {
write_command(0x484, 64, v / 10, 0x484); write_command(0x484, 64, v / 10, 0x484);
@@ -3661,7 +3661,7 @@ bool Boiler::set_shutdown(const char * value, const int8_t id) {
return false; return false;
} }
bool Boiler::set_pumpKickHour(const char * value, const int8_t id) { bool Boiler::set_pumpKickHour(const char * value, const int8_t) {
int v; int v;
if (Helpers::value2number(value, v, 0, 23)) { if (Helpers::value2number(value, v, 0, 23)) {
write_command(0xEB, 0, v, 0xEB); write_command(0xEB, 0, v, 0xEB);
@@ -3670,7 +3670,7 @@ bool Boiler::set_pumpKickHour(const char * value, const int8_t id) {
return false; return false;
} }
bool Boiler::set_pumpKickDay(const char * value, const int8_t id) { bool Boiler::set_pumpKickDay(const char * value, const int8_t) {
uint8_t v; uint8_t v;
if (Helpers::value2enum(value, v, FL_(enum_dayOfWeek))) { if (Helpers::value2enum(value, v, FL_(enum_dayOfWeek))) {
write_command(0xEB, 1, v + 1, 0xEB); write_command(0xEB, 1, v + 1, 0xEB);
@@ -3679,7 +3679,7 @@ bool Boiler::set_pumpKickDay(const char * value, const int8_t id) {
return false; return false;
} }
bool Boiler::set_pumpKickDelay(const char * value, const int8_t id) { bool Boiler::set_pumpKickDelay(const char * value, const int8_t) {
int v; int v;
if (Helpers::value2number(value, v, 0, 32767)) { if (Helpers::value2number(value, v, 0, 32767)) {
uint8_t data[2] = {(uint8_t)(v >> 8), (uint8_t)v}; uint8_t data[2] = {(uint8_t)(v >> 8), (uint8_t)v};
+2 -2
View File
@@ -278,7 +278,7 @@ bool Mixer::set_flowTempOffset(const char * value, const int8_t id) {
return false; return false;
} }
bool Mixer::set_pressure(const char * value, const int8_t id) { bool Mixer::set_pressure(const char * value, const int8_t) {
int v; int v;
if (!Helpers::value2number(value, v)) { if (!Helpers::value2number(value, v)) {
return false; return false;
@@ -288,7 +288,7 @@ bool Mixer::set_pressure(const char * value, const int8_t id) {
return true; return true;
} }
bool Mixer::set_wwprio(const char * value, const int8_t id) { bool Mixer::set_wwprio(const char * value, const int8_t) {
bool b; bool b;
if (!Helpers::value2bool(value, b)) { if (!Helpers::value2bool(value, b)) {
return false; return false;
+2 -2
View File
@@ -1122,7 +1122,7 @@ bool Solar::set_diffControl(const char * value, const int8_t id) {
return true; return true;
} }
bool Solar::set_solarHeatAssistOn(const char * value, const int8_t id) { bool Solar::set_solarHeatAssistOn(const char * value, const int8_t) {
float t; float t;
if (!Helpers::value2float(value, t)) { if (!Helpers::value2float(value, t)) {
return false; return false;
@@ -1131,7 +1131,7 @@ bool Solar::set_solarHeatAssistOn(const char * value, const int8_t id) {
return true; return true;
} }
bool Solar::set_solarHeatAssistOff(const char * value, const int8_t id) { bool Solar::set_solarHeatAssistOff(const char * value, const int8_t) {
float t; float t;
if (!Helpers::value2float(value, t)) { if (!Helpers::value2float(value, t)) {
return false; return false;
+55 -31
View File
@@ -1663,32 +1663,40 @@ void Thermostat::process_RC35Timer(const std::shared_ptr<const Telegram> & teleg
has_update(telegram, hc->pause, 85); // time in hours has_update(telegram, hc->pause, 85); // time in hours
has_update(telegram, hc->party, 86); // time in hours has_update(telegram, hc->party, 86); // time in hours
if (telegram->message_length + telegram->offset >= 92 && telegram->offset <= 87) { // vacation dates are in positions 87 to 92
char data[sizeof(hc->vacation) + 4]; // avoid compiler warning if (telegram->offset <= 87 && (telegram->offset + telegram->message_length) > 92) {
snprintf(data, const uint8_t pos = 87 - telegram->offset;
sizeof(data), if ((pos + 5) < EMS_MAX_TELEGRAM_MESSAGE_LENGTH) {
"%02d.%02d.%04d-%02d.%02d.%04d", char data[sizeof(hc->vacation) + 4]; // avoid compiler warning
telegram->message_data[87 - telegram->offset], snprintf(data,
telegram->message_data[88 - telegram->offset], sizeof(data),
telegram->message_data[89 - telegram->offset] + 2000, "%02d.%02d.%04d-%02d.%02d.%04d",
telegram->message_data[90 - telegram->offset], telegram->message_data[pos],
telegram->message_data[91 - telegram->offset], telegram->message_data[pos + 1],
telegram->message_data[92 - telegram->offset] + 2000); telegram->message_data[pos + 2] + 2000,
has_update(hc->vacation, data, sizeof(hc->vacation)); telegram->message_data[pos + 3],
telegram->message_data[pos + 4],
telegram->message_data[pos + 5] + 2000);
has_update(hc->vacation, data, sizeof(hc->vacation));
}
} }
if (telegram->message_length + telegram->offset >= 98 && telegram->offset <= 93) { // holiday dates are in positions 93 to 98
char data[sizeof(hc->holiday) + 4]; // avoid compiler warning if (telegram->offset <= 93 && (telegram->offset + telegram->message_length) > 98) {
snprintf(data, const uint8_t pos = 93 - telegram->offset;
sizeof(data), if ((pos + 5) < EMS_MAX_TELEGRAM_MESSAGE_LENGTH) {
"%02d.%02d.%04d-%02d.%02d.%04d", char data[sizeof(hc->holiday) + 4]; // avoid compiler warning
telegram->message_data[93 - telegram->offset], snprintf(data,
telegram->message_data[94 - telegram->offset], sizeof(data),
telegram->message_data[95 - telegram->offset] + 2000, "%02d.%02d.%04d-%02d.%02d.%04d",
telegram->message_data[96 - telegram->offset], telegram->message_data[pos],
telegram->message_data[97 - telegram->offset], telegram->message_data[pos + 1],
telegram->message_data[98 - telegram->offset] + 2000); telegram->message_data[pos + 2] + 2000,
has_update(hc->holiday, data, sizeof(hc->holiday)); telegram->message_data[pos + 3],
telegram->message_data[pos + 4],
telegram->message_data[pos + 5] + 2000);
has_update(hc->holiday, data, sizeof(hc->holiday));
}
} }
} }
@@ -1862,7 +1870,7 @@ void Thermostat::process_RCErrorMessage(const std::shared_ptr<const Telegram> &
} }
// 0xBF // 0xBF
void Thermostat::process_ErrorMessageBF(const std::shared_ptr<const Telegram> & telegram) { void Thermostat::process_ErrorMessageBF(const std::shared_ptr<const Telegram> &) {
EMSESP::send_read_request(0xC0, device_id(), 0, 20); // read last errorcode EMSESP::send_read_request(0xC0, device_id(), 0, 20); // read last errorcode
} }
@@ -2338,7 +2346,7 @@ bool Thermostat::set_damping(const char * value, const int8_t id) {
} }
// 0x0241- Set solar // 0x0241- Set solar
bool Thermostat::set_solar(const char * value, const int8_t id) { bool Thermostat::set_solar(const char * value, const int8_t) {
bool b; bool b;
if (Helpers::value2bool(value, b)) { if (Helpers::value2bool(value, b)) {
if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) { if (model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
@@ -2393,7 +2401,7 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
} }
// BC400 // BC400
// 1-RC100, 2-RC100H, 3-RC200 // 1-RC100, 2-RC100H, 3-RC200
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400 || model() == EMSdevice::EMS_DEVICE_FLAG_UI800) { } else if (model() == EMSdevice::EMS_DEVICE_FLAG_BC400) {
if (Helpers::value2enum(value, ctrl, FL_(enum_control2))) { if (Helpers::value2enum(value, ctrl, FL_(enum_control2))) {
write_command(hpmode_typeids[hc->hc()], 3, ctrl); write_command(hpmode_typeids[hc->hc()], 3, ctrl);
hc->control = ctrl; // set in advance, dont wait for verify hc->control = ctrl; // set in advance, dont wait for verify
@@ -2413,6 +2421,20 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
} }
return true; return true;
} }
} else if (model() == EMSdevice::EMS_DEVICE_FLAG_UI800) {
if (Helpers::value2enum(value, ctrl, FL_(enum_control3))) {
write_command(hpmode_typeids[hc->hc()], 3, ctrl);
hc->control = ctrl; // set in advance, dont wait for verify
if (hc->remotetemp != EMS_VALUE_INT16_NOTSET && ctrl > 0) {
if (ctrl == 6) {
Roomctrl::set_remotetemp(Roomctrl::RT800, hc->hc(), hc->remotetemp);
} else {
hc->remotetemp = EMS_VALUE_INT16_NOTSET;
Roomctrl::set_remotetemp(0, hc->hc(), hc->remotetemp);
}
}
return true;
}
// RC100 or RC300 // RC100 or RC300
// 1-RC200, 2-RC100, 3-RC100H // 1-RC200, 2-RC100, 3-RC100H
} else if (isRC300() || model() == EMSdevice::EMS_DEVICE_FLAG_RC100) { } else if (isRC300() || model() == EMSdevice::EMS_DEVICE_FLAG_RC100) {
@@ -3034,7 +3056,7 @@ bool Thermostat::set_party(const char * value, const int8_t id) {
return true; return true;
} }
bool Thermostat::set_absent(const char * value, const int8_t id) { bool Thermostat::set_absent(const char * value, const int8_t) {
bool b; bool b;
if (Helpers::value2bool(value, b)) { if (Helpers::value2bool(value, b)) {
write_command(0x16E, 0, b ? 0xFF : 0, 0x16E); write_command(0x16E, 0, b ? 0xFF : 0, 0x16E);
@@ -4693,7 +4715,7 @@ void Thermostat::register_device_values() {
1, 1,
10); 10);
register_device_value( register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &coolstart, DeviceValueType::UINT8, FL_(coolstart), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_coolstart), 20, 35); DeviceValueTAG::TAG_DEVICE_DATA, &coolstart, DeviceValueType::UINT8, FL_(coolstart), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_coolstart), 24, 35);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&heatondelay, &heatondelay,
DeviceValueType::UINT8, DeviceValueType::UINT8,
@@ -5172,7 +5194,7 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control2), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control)); register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control2), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
} else if (model == EMSdevice::EMS_DEVICE_FLAG_UI800) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_UI800) {
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control3), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control)); register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control3), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
} else { } else if (device_id() == 0x10) { // only master thermostats have control by remote
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control1), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control)); register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_control1), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
} }
if (model != EMSdevice::EMS_DEVICE_FLAG_RC100 && model != EMSdevice::EMS_DEVICE_FLAG_CR120) { if (model != EMSdevice::EMS_DEVICE_FLAG_RC100 && model != EMSdevice::EMS_DEVICE_FLAG_CR120) {
@@ -5476,7 +5498,9 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
FL_(nofrosttemp), FL_(nofrosttemp),
DeviceValueUOM::DEGREES, DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_nofrosttemp)); MAKE_CF_CB(set_nofrosttemp));
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_j_control), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control)); if (device_id() == 0x10) { // only master thermostats have control by remote
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_j_control), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
}
register_device_value(tag, &hc->program, DeviceValueType::ENUM, FL_(enum_progMode4), FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program)); register_device_value(tag, &hc->program, DeviceValueType::ENUM, FL_(enum_progMode4), FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program));
register_device_value(tag, register_device_value(tag,
&hc->remotetemp, &hc->remotetemp,
+1 -1
View File
@@ -87,7 +87,7 @@ void Ventilation::process_ModeMessage(const std::shared_ptr<const Telegram> & te
} }
// message 0x0587, data: 00 00 64 00 64 0A 00 01 54 01 00 01 00 00 00 46 00 00 00 02 00 A3 00 A3 // message 0x0587, data: 00 00 64 00 64 0A 00 01 54 01 00 01 00 00 00 46 00 00 00 02 00 A3 00 A3
void Ventilation::process_BypassMessage(const std::shared_ptr<const Telegram> & telegram) { void Ventilation::process_BypassMessage(const std::shared_ptr<const Telegram> &) {
// has_update(telegram, bypass_closing, 0); // has_update(telegram, bypass_closing, 0);
// has_update(telegram, bypass_opening, 1); // has_update(telegram, bypass_opening, 1);
} }
+1 -1
View File
@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.9.0-dev.0" #define EMSESP_APP_VERSION "3.9.0-dev.4"
+2 -2
View File
@@ -195,7 +195,7 @@ StateUpdateResult WebCommands::update(JsonObject root, WebCommands & webCommands
Command::add( Command::add(
EMSdevice::DeviceType::COMMAND, EMSdevice::DeviceType::COMMAND,
webCommands.commandItems.back().name, webCommands.commandItems.back().name,
[name = std::string(webCommands.commandItems.back().name)](const char * value, const int8_t id, JsonObject output) { [name = std::string(webCommands.commandItems.back().name)](const char * value, const int8_t, JsonObject) {
return EMSESP::webCommandService.dispatchCommand(name.c_str(), value); // value is optional return EMSESP::webCommandService.dispatchCommand(name.c_str(), value); // value is optional
}, },
FL_(command_cmd), FL_(command_cmd),
@@ -448,7 +448,7 @@ void WebCommandService::load_test_data() {
Command::add( Command::add(
EMSdevice::DeviceType::COMMAND, EMSdevice::DeviceType::COMMAND,
item.name, item.name,
[name = std::string(item.name)](const char * value, const int8_t id, JsonObject output) { [name = std::string(item.name)](const char * value, const int8_t, JsonObject) {
return EMSESP::webCommandService.dispatchCommand(name.c_str(), value); return EMSESP::webCommandService.dispatchCommand(name.c_str(), value);
}, },
FL_(command_cmd), FL_(command_cmd),
+5 -5
View File
@@ -147,7 +147,7 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
Command::add( Command::add(
EMSdevice::DeviceType::CUSTOM, EMSdevice::DeviceType::CUSTOM,
webCustomEntity.customEntityItems.back().name, webCustomEntity.customEntityItems.back().name,
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) { [name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject) {
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str()); return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
}, },
FL_(entity_cmd), FL_(entity_cmd),
@@ -501,7 +501,7 @@ void WebCustomEntityService::publish(const bool force) {
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj); snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery // don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) { if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}"; config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else 'None'}}";
} else { } else {
config["val_tpl"] = (std::string) "{{" + val_obj + "}}"; config["val_tpl"] = (std::string) "{{" + val_obj + "}}";
} }
@@ -798,7 +798,7 @@ void WebCustomEntityService::load_test_data() {
Command::add( Command::add(
EMSdevice::DeviceType::CUSTOM, EMSdevice::DeviceType::CUSTOM,
webCustomEntity.customEntityItems.back().name, webCustomEntity.customEntityItems.back().name,
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) { [name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject) {
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str()); return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
}, },
FL_(entity_cmd), FL_(entity_cmd),
@@ -834,7 +834,7 @@ void WebCustomEntityService::load_test_data() {
Command::add( Command::add(
EMSdevice::DeviceType::CUSTOM, EMSdevice::DeviceType::CUSTOM,
webCustomEntity.customEntityItems.back().name, webCustomEntity.customEntityItems.back().name,
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) { [name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject) {
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str()); return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
}, },
FL_(entity_cmd), FL_(entity_cmd),
@@ -857,7 +857,7 @@ void WebCustomEntityService::load_test_data() {
Command::add( Command::add(
EMSdevice::DeviceType::CUSTOM, EMSdevice::DeviceType::CUSTOM,
webCustomEntity.customEntityItems.back().name, webCustomEntity.customEntityItems.back().name,
[name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) { [name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject) {
return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str()); return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str());
}, },
FL_(entity_cmd), FL_(entity_cmd),
+1 -1
View File
@@ -317,7 +317,7 @@ void WebCustomizationService::customization_entities(AsyncWebServerRequest * req
// add deleted entities from file // add deleted entities from file
read([&](WebCustomization & settings) { read([&](WebCustomization & settings) {
for (EntityCustomization entityCustomization : settings.entityCustomizations) { for (const auto & entityCustomization : settings.entityCustomizations) {
if (entityCustomization.device_id == device_id) { if (entityCustomization.device_id == device_id) {
for (const auto & entity_id : entityCustomization.entity_ids) { for (const auto & entity_id : entityCustomization.entity_ids) {
uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str()); uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str());
+3 -3
View File
@@ -98,7 +98,7 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
Command::add( Command::add(
EMSdevice::DeviceType::SCHEDULER, EMSdevice::DeviceType::SCHEDULER,
webScheduler.scheduleItems.back().name, webScheduler.scheduleItems.back().name,
[name = std::string(webScheduler.scheduleItems.back().name)](const char * value, const int8_t id, JsonObject output) { [name = std::string(webScheduler.scheduleItems.back().name)](const char * value, const int8_t id, JsonObject) {
return EMSESP::webSchedulerService.command_setvalue(value, id, name.c_str()); return EMSESP::webSchedulerService.command_setvalue(value, id, name.c_str());
}, },
FL_(schedule_cmd), FL_(schedule_cmd),
@@ -275,7 +275,7 @@ void WebSchedulerService::publish(const bool force) {
char val_tpl[150]; char val_tpl[150];
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) { if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
snprintf(val_tpl, sizeof(val_tpl), "{{%s if %s}}", val_obj, val_cond); snprintf(val_tpl, sizeof(val_tpl), "{{%s if %s else 'None'}}", val_obj, val_cond);
} else { } else {
snprintf(val_tpl, sizeof(val_tpl), "{{%s}}", val_obj); // omit value conditional Jinja2 template code snprintf(val_tpl, sizeof(val_tpl), "{{%s}}", val_obj); // omit value conditional Jinja2 template code
} }
@@ -464,7 +464,7 @@ void WebSchedulerService::load_test_data() {
Command::add( Command::add(
EMSdevice::DeviceType::SCHEDULER, EMSdevice::DeviceType::SCHEDULER,
item.name, item.name,
[name = std::string(item.name)](const char * value, const int8_t id, JsonObject output) { [name = std::string(item.name)](const char * value, const int8_t id, JsonObject) {
return EMSESP::webSchedulerService.command_setvalue(value, id, name.c_str()); return EMSESP::webSchedulerService.command_setvalue(value, id, name.c_str());
}, },
FL_(schedule_cmd), FL_(schedule_cmd),
+6 -82
View File
@@ -23,6 +23,7 @@
#include <WiFiClient.h> #include <WiFiClient.h>
#include <ESP_SSLClient.h> #include <ESP_SSLClient.h>
#endif #endif
#include "shuntingYard.h"
namespace emsesp { namespace emsesp {
@@ -421,91 +422,14 @@ bool WebStatusService::refresh_versions_cache() {
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
return false; return false;
#else #else
// detect scheme from EMSESP_VERSIONS_URL (case-insensitive). One code path for HTTP and HTTPS, std::string result;
// using ESP_SSLClient as a plain TCP passthrough when SSL is disabled. JsonDocument doc;
String url = EMSESP_VERSIONS_URL; auto http_code = http_request(VERSIONS_URL, "GET", "", doc.as<JsonObjectConst>(), result);
String lower = url;
lower.toLowerCase();
const bool is_https = lower.startsWith("https://");
if (!is_https && !lower.startsWith("http://")) {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("versions.json: unsupported scheme");
#endif
return false;
}
const int scheme_len = is_https ? 8 : 7;
WiFiClient basic_client;
ESP_SSLClient ssl_client;
if (is_https) {
ssl_client.setInsecure();
ssl_client.setBufferSizes(16384, 1024); // versions.json fits easily but TLS records may be full-size
ssl_client.setSessionTimeout(120);
}
basic_client.setTimeout(5000);
ssl_client.setTimeout(5000);
ssl_client.setClient(&basic_client, is_https);
// split into host and path
String rest = url.substring(scheme_len);
String host;
String path;
int s = rest.indexOf('/');
if (s < 0) {
host = rest;
path = "/";
} else {
host = rest.substring(0, s);
path = rest.substring(s);
}
if (!ssl_client.connect(host.c_str(), is_https ? 443 : 80)) {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("versions.json: connection failed");
#endif
return false;
}
// minimal HTTP/1.0 GET so we don't have to handle chunked encoding
ssl_client.print("GET ");
ssl_client.print(path);
ssl_client.println(" HTTP/1.0");
ssl_client.print("Host: ");
ssl_client.println(host);
ssl_client.println("User-Agent: EMS-ESP");
ssl_client.println("Connection: close");
ssl_client.print("\r\n");
// wait for the first byte. The 5 seconds is GitHub's SLO
uint32_t ms = millis();
while (ssl_client.connected() && !ssl_client.available() && millis() - ms < 5000) {
delay(1);
}
// parse status line
String status_line = ssl_client.readStringUntil('\n');
int sp = status_line.indexOf(' ');
int http_code = (sp >= 0) ? status_line.substring(sp + 1, sp + 4).toInt() : 0;
if (http_code != 200) { if (http_code != 200) {
ssl_client.stop(); EMSESP::logger().warning("versions.json: HTTP error code %d", http_code);
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("versions.json: HTTP error code %d", http_code);
#endif
return false; return false;
} }
DeserializationError err = deserializeJson(doc, result);
// skip headers
while (ssl_client.connected() || ssl_client.available()) {
String line = ssl_client.readStringUntil('\n');
line.trim();
if (line.isEmpty()) {
break;
}
}
JsonDocument doc(PSRAM_DOC);
DeserializationError err = deserializeJson(doc, ssl_client);
ssl_client.stop();
if (err) { if (err) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
EMSESP::logger().debug("versions.json: parse error"); EMSESP::logger().debug("versions.json: parse error");
+2 -2
View File
@@ -4,8 +4,6 @@
#define EMSESP_SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus" #define EMSESP_SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus"
#define EMSESP_ACTION_SERVICE_PATH "/rest/action" #define EMSESP_ACTION_SERVICE_PATH "/rest/action"
#define EMSESP_VERSIONS_URL "http://emsesp.org/versions.json"
#include "../core/firmwareVersion.h" #include "../core/firmwareVersion.h"
#include "../emsesp_version.h" #include "../emsesp_version.h"
@@ -72,6 +70,8 @@ class WebStatusService {
bool refresh_versions_cache(); // does the actual HTTPS fetch + parse, returns true on success bool refresh_versions_cache(); // does the actual HTTPS fetch + parse, returns true on success
static constexpr const char * VERSIONS_URL = "http://emsesp.org/versions.json";
static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour on success static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour on success
static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after failure static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after failure
static constexpr uint32_t VERSIONS_INITIAL_FETCH_DELAY_MS = 5UL * 1000UL; // 5 s after a link comes up static constexpr uint32_t VERSIONS_INITIAL_FETCH_DELAY_MS = 5UL * 1000UL; // 5 s after a link comes up
File diff suppressed because one or more lines are too long