141 Commits

Author SHA1 Message Date
Proddy
994706c9f2 Merge pull request #3023 from MichaelDvP/dev
fetch telegrams with length, dev14
2026-04-16 12:22:06 +02:00
MichaelDvP
8a72ab42cb dev 14, changelog 2026-04-16 08:18:17 +02:00
MichaelDvP
c4db8e3914 set length for more fetch telegrams 2026-04-16 08:10:43 +02:00
MichaelDvP
8d0225e595 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2026-04-16 08:09:58 +02:00
Proddy
966049d0c9 Merge pull request #3006 from proddy/dev
sync with core3 features
2026-04-15 21:29:24 +02:00
proddy
907a65a701 show link to backup page 2026-04-15 21:19:09 +02:00
proddy
f97b8e14e7 package update 2026-04-15 21:18:51 +02:00
proddy
e65f634b21 3.8.2 2026-04-15 21:18:38 +02:00
proddy
fc71ed2b9d 3.8.2 2026-04-15 21:18:27 +02:00
proddy
5a8195d430 auto-formatting 2026-04-15 08:12:22 +02:00
proddy
24a7a607f3 add test data 2026-04-15 08:08:49 +02:00
proddy
061f9ffc52 update prettier 2026-04-15 08:08:45 +02:00
proddy
9e17936bfc fix lint 2026-04-14 21:13:25 +02:00
Proddy
18bb2c4f39 Merge branch 'emsesp:dev' into dev 2026-04-14 21:09:31 +02:00
proddy
7c3782a43f upload warnings 2026-04-14 21:08:45 +02:00
proddy
3ac807bdd5 text change 2026-04-14 21:08:04 +02:00
proddy
1111458863 upgrade message warnings 2026-04-14 09:31:50 +02:00
proddy
99c5e2230c fix link 2026-04-14 09:31:35 +02:00
proddy
3317aa845a package update 2026-04-14 09:31:19 +02:00
proddy
97cd657336 fix links 2026-04-14 09:31:05 +02:00
MichaelDvP
3338f919bd Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev 2026-04-14 08:09:50 +02:00
proddy
7dd13bcab7 mui upgrade 2026-04-13 23:30:36 +02:00
MichaelDvP
f226cb359f Merge branch 'dev' of https://github.com/MichaelDvP/EMS-ESP32 into dev 2026-04-13 21:04:23 +02:00
MichaelDvP
abbba0aa42 telegram length for fetched telegrams 2026-04-13 20:57:20 +02:00
MichaelDvP
39b5a52b01 chore: update generated files for v3.8.2-dev.13 2026-04-13 12:12:42 +00:00
MichaelDvP
b6c3fc5bee read fragmented telegram 0x484, #3017 2026-04-13 14:00:12 +02:00
MichaelDvP
909bea00df update espressif32 6.13.0 2026-04-13 13:59:12 +02:00
MichaelDvP
9522945e06 uart buffer size 2026-04-13 13:58:30 +02:00
MichaelDvP
d6a9f2a731 prepare for translations #3015, update pkg 2026-04-13 13:58:10 +02:00
proddy
0f30c81554 fix compile on linux/osx 2026-04-12 20:32:36 +02:00
MichaelDvP
e514ba4bb5 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2026-04-12 09:46:43 +02:00
Proddy
38e63e3eaa Merge pull request #3016 from misa1515/patch-30
Update index.ts
2026-04-11 22:44:28 +02:00
misa1515
0058324edd Update index.ts 2026-04-10 21:10:36 +02:00
MichaelDvP
ac143d607a http-client to heap 2026-04-10 14:20:17 +02:00
MichaelDvP
e9e3759db3 add solar ts3 2026-04-10 14:19:39 +02:00
proddy
fb09e10f19 sync with core3 features 2026-03-30 23:26:04 +02:00
Proddy
09473f17a0 Merge pull request #2994 from MichaelDvP/dev
dev.12, add dhw4, 5,.. circuits, #2991
2026-03-21 15:46:21 +01:00
MichaelDvP
bbc2de08a5 support dhw5... 2026-03-21 09:52:04 +01:00
MichaelDvP
df808a2bcf dev.12, add dhw4 circuit, #2991 2026-03-20 14:53:07 +01:00
MichaelDvP
d04e7c36f3 reset reason 2026-03-20 14:50:58 +01:00
MichaelDvP
205d826adb asyncWebserver 3.10.3, remove C6 (no core 2 support) 2026-03-20 14:48:34 +01:00
Proddy
4db8e43648 Merge pull request #2990 from MichaelDvP/dev
dev.11, fix #2988, asyncWebserver 3.10.2
2026-03-18 20:18:11 +01:00
MichaelDvP
cc60062678 dev.11, fix #2988, asyncWebserver 3.10.2 2026-03-18 10:53:27 +01:00
Proddy
e3305ab9db Merge pull request #2985 from MichaelDvP/dev
devcie class #2980 and version update #2981, dev10
2026-03-17 20:17:29 +01:00
MichaelDvP
b0157f288e update changelog, dev10 2026-03-17 10:16:01 +01:00
MichaelDvP
a9f50d9371 update version number fixes #2981 2026-03-16 12:55:27 +01:00
MichaelDvP
65a3226404 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2026-03-16 10:05:59 +01:00
Proddy
45690f5418 Merge pull request #2982 from misa1515/patch-29
Update locale_translations.h
2026-03-15 22:16:51 +01:00
misa1515
6836b6197f Update locale_translations.h 2026-03-15 21:27:58 +01:00
MichaelDvP
69d4163b9d fix device_class #2980 2026-03-15 14:12:47 +01:00
proddy
b1e974a82c chore: update generated files for v3.8.2-dev.9 2026-03-13 19:09:14 +00:00
Proddy
34a2b20be8 Merge pull request #2978 from MichaelDvP/dev
add basflowtemp #2969, add pumpkick #2965, add reset HP #2933, fix custom brand
2026-03-13 19:58:03 +01:00
MichaelDvP
f1fc8d9aae update testdata 2026-03-13 16:52:48 +01:00
MichaelDvP
b04355e3e1 update asyncwebserver 2026-03-13 10:32:33 +01:00
MichaelDvP
cd3ae5cdf2 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2026-03-13 10:22:24 +01:00
MichaelDvP
a261ca23af add baseflowtemp #2969 2026-03-13 10:15:51 +01:00
MichaelDvP
cb96904a5c fix custom brand use after free of c_str() in json. 2026-03-13 10:15:00 +01:00
MichaelDvP
4a2d78f8e1 minflowtemp taken from offset 13 or 8 2026-03-11 18:43:25 +01:00
Proddy
f5af4fb52f Merge pull request #2975 from mrkev-gh/esp32s3-no-psram
fix allowed pins for S32S3 without PSRAM
2026-03-11 17:36:38 +01:00
MichaelDvP
2037bc3a10 add reset of HP errors #2933, dev9 2026-03-07 12:02:09 +01:00
MichaelDvP
64d17d7c65 Test for minflowtemp 2026-03-07 11:43:12 +01:00
MichaelDvP
92e2633342 typo 2026-03-07 11:42:27 +01:00
mrkev-gh
96a7ea8a02 fix allowed pins for S32S3 without PSRAM
Some S32S3 do not have PSRAM (e.g. ESP32-S3FN8) and use those GPIO pins
2026-02-28 11:44:53 +01:00
MichaelDvP
5c4aaa4510 add pumpkick #2965, dev.8 2026-02-20 09:56:08 +01:00
Proddy
c05e1cb77b Merge pull request #2966 from MichaelDvP/dev
fixes for #2960 and #2962
2026-02-19 21:58:20 +01:00
MichaelDvP
5879ce4090 fix SRC mode setting from HA #2960 2026-02-18 08:14:47 +01:00
MichaelDvP
ac3e5c793c fix typo for SRC ha-climate creation 2026-02-17 10:09:22 +01:00
MichaelDvP
4326fb931b add prometheus metrics for analog/scheduler/custom #2962 2026-02-16 15:56:23 +01:00
MichaelDvP
ced7051ce7 add prometheus metrics for temperaturesensors 2026-02-16 12:05:45 +01:00
MichaelDvP
421da246ed fix SRC seltemp offset for auto mode #2960 2026-02-16 07:51:10 +01:00
MichaelDvP
148a721e17 read connect seltemp after mode/icon to create HA-climate 2026-02-15 16:49:21 +01:00
MichaelDvP
a811670c5a 3.8.2-dev.6, changelog 2026-02-15 12:03:33 +01:00
MichaelDvP
72f08a86cf fix SRC climate, #2960 2026-02-15 12:03:07 +01:00
MichaelDvP
27c471f45f set model for ems-esp devices, #2958 2026-02-15 12:02:36 +01:00
MichaelDvP
e303972d26 update AsyncWebserver and pkg 2026-02-15 12:01:50 +01:00
MichaelDvP
97bb03d703 add missing check for number mode change 2026-02-15 12:01:10 +01:00
Proddy
e9f77c1bde Merge pull request #2954 from MichaelDvP/dev
fix brand in HA
2026-02-12 17:54:41 +01:00
MichaelDvP
81cba6c0a8 fix brand in HA 2026-02-12 17:41:10 +01:00
Proddy
89029df25e Merge pull request #2953 from MichaelDvP/dev
customize device brand #2784
2026-02-12 13:19:46 +01:00
MichaelDvP
3463b6818d update testdata 2026-02-12 12:14:13 +01:00
MichaelDvP
349843e666 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2026-02-12 12:02:45 +01:00
MichaelDvP
96ae3bbbba customze device brand #2784 2026-02-12 12:01:39 +01:00
Proddy
b153364b60 Merge pull request #2947 from MichaelDvP/dev
save scheduler, custom to nvs, fix #2946
2026-02-10 19:39:59 +01:00
MichaelDvP
ccc40937fb chore: update generated files for v3.8.2-dev.3 2026-02-09 12:02:38 +00:00
MichaelDvP
909edf394d add back 4wayValve as bool, #2844 2026-02-09 09:36:13 +01:00
MichaelDvP
ac8ef646e9 fix standalone test 2026-02-08 14:46:54 +01:00
MichaelDvP
3ef279ea10 3.8.2-dev.3, changelog, update pkg 2026-02-08 14:17:20 +01:00
MichaelDvP
769beeda37 save scheduler active flag to nvs 2026-02-08 13:53:57 +01:00
MichaelDvP
f83404c216 add custom entity type NVS 2026-02-08 13:53:32 +01:00
MichaelDvP
c239658131 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2026-02-02 18:52:29 +01:00
MichaelDvP
be82afd778 add translated heat/eco modes to mode_str_tpl 2026-02-02 18:52:21 +01:00
Proddy
2156c96368 Merge pull request #2942 from misa1515/patch-28
Update locale_translations.h
2026-02-02 17:48:39 +01:00
misa1515
f7f078d82a Update locale_translations.h 2026-02-02 14:15:09 +01:00
Proddy
95168cf514 Add DeepWiki badge to README 2026-02-01 12:57:10 +01:00
Proddy
6dc601c4a2 Merge pull request #2940 from proddy/dev
set range for comfort point temp + offset - #2935
2026-02-01 12:03:22 +01:00
proddy
6b87bbb882 set range for comfort point temp + offset - #2935 2026-02-01 12:02:40 +01:00
proddy
abdf2c5037 package update 2026-02-01 12:02:04 +01:00
Proddy
7beec1b80f Merge pull request #2939 from MichaelDvP/dev
fixes and additions: #2918, #2931, #2933, #2935, #2936
2026-01-30 11:30:34 +01:00
MichaelDvP
3a0e46f064 update expected test data 2026-01-30 11:21:44 +01:00
MichaelDvP
85cc85a923 remove MAX_LOG_ENTRIES check in logger web page 2026-01-30 09:56:38 +01:00
MichaelDvP
ca0079c0df Merge branch 'dev' of https://github.com/MichaelDvP/EMS-ESP32 into dev 2026-01-30 08:58:21 +01:00
MichaelDvP
28b662ad43 update to ESPAsyncWebServer 3.9.6 2026-01-30 08:57:14 +01:00
MichaelDvP
6bac6bbfeb chore: update generated files for v3.8.2-dev.2 2026-01-30 07:08:12 +00:00
MichaelDvP
958ec1002b dev.2 2026-01-30 07:56:39 +01:00
MichaelDvP
438852ecaf Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2026-01-30 07:51:12 +01:00
MichaelDvP
92d82c0a00 remove burnMinPower, #2918 2026-01-30 07:50:54 +01:00
Proddy
7d37267f57 Merge pull request #2934 from proddy/dev
a collection of small changes
2026-01-29 21:03:23 +01:00
MichaelDvP
5641d53cc3 revert syslog buffer (still in heap) 2026-01-29 18:26:03 +01:00
MichaelDvP
8fc6752290 HA climate mode and icon check 2026-01-29 09:53:20 +01:00
proddy
cca6f87500 package update 2026-01-28 21:47:20 +01:00
proddy
758d76051f update discord URL 2026-01-28 21:47:13 +01:00
proddy
95f7e66cff update discord URL and tidy up 2026-01-28 21:46:58 +01:00
MichaelDvP
4e194287c9 remove SRC climate for test 2026-01-28 18:07:47 +01:00
MichaelDvP
3545830552 chore: update generated files for v3.8.2-dev.1 2026-01-28 14:20:04 +00:00
MichaelDvP
074f4c32ed dev.1, changelog 2026-01-28 15:08:28 +01:00
MichaelDvP
b3fec5ed7d fix SRC climate creation, #2936 2026-01-28 15:02:28 +01:00
MichaelDvP
ffb90b8f9a comfortpoint temperature and offset, #2935 2026-01-28 15:01:57 +01:00
MichaelDvP
584618043d weblog buffer max 1000 messages with psram, syslog buffer 250 with psram 2026-01-28 15:01:15 +01:00
MichaelDvP
d702c485b7 validate custom entity writes, #2931 2026-01-25 19:37:34 +01:00
MichaelDvP
d3561da331 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2026-01-25 19:22:07 +01:00
MichaelDvP
0e0aaf37df add climate modes night/eco as off 2026-01-25 19:21:59 +01:00
proddy
5ec068409f package update 2026-01-24 12:10:52 +01:00
proddy
8796b6d340 update dictionary 2026-01-24 12:10:45 +01:00
proddy
bfbb18655d memory optimzations 2026-01-23 19:37:21 +01:00
proddy
9088651e53 asyncwebserver update, improved caching 2026-01-23 19:35:53 +01:00
proddy
3e8f379502 package update 2026-01-23 19:35:37 +01:00
proddy
265c2c4231 3.8.2-dev.1 2026-01-23 12:52:55 +01:00
proddy
f671d79280 package update 2026-01-22 16:40:37 +01:00
proddy
97c89d1d13 add psram 2026-01-22 16:40:15 +01:00
proddy
e0a26a38fa replace unordered_map with map, less heap 2026-01-22 16:40:05 +01:00
proddy
038f06e59f show psram on startup 2026-01-22 16:39:14 +01:00
proddy
f4d2bae04f add psram 2026-01-22 16:38:52 +01:00
proddy
d443e275ea Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev 2026-01-21 14:46:10 +01:00
Proddy
30d2057e01 Merge pull request #2930 from jvhaarst/patch-1
Update Dutch translations for various terms
2026-01-20 08:49:45 +00:00
Jan van Haarst
d952b9aaae Update Dutch translations for various terms
As I don't have the board yet, I saw the error message "Als deze waarschuwing blijft staan na een paar seconden dan loop de instellingen na en in het bijzonder het apparaat type profiel na."
That sentence didn't really flow right for me, so I had a look at the rest of the text, with this result.
2026-01-20 08:27:24 +01:00
proddy
3402215e8d optimizations 2026-01-18 15:49:35 +00:00
proddy
f01031dc26 optimize 2026-01-18 15:00:43 +00:00
proddy
01d4d116b9 cspell updates 2026-01-18 12:36:34 +00:00
proddy
bc7f82eef1 package update 2026-01-18 12:36:20 +00:00
Proddy
efdb355033 Merge pull request #2923 from proddy/dev
v3.8.2
2026-01-12 12:07:02 +01:00
proddy
87c9fd010f v3.8.2 2026-01-11 19:49:01 +01:00
156 changed files with 9952 additions and 6977 deletions

View File

@@ -22,7 +22,7 @@ _Make sure your have performed every step and checked the applicable boxes befor
- [ ] Searched the issue in [issues](https://github.com/emsesp/EMS-ESP32/issues)
- [ ] Searched the issue in [discussions](https://github.com/emsesp/EMS-ESP32/discussions)
- [ ] Searched the issue in the [docs](https://emsesp.org/Troubleshooting/)
- [ ] Searched the issue in the [chat](https://discord.gg/3J3GgnzpyT)
- [ ] Searched the issue in the [chat](https://discord.gg/GP9DPSgeJq)
- [ ] Provide the System information in the area below, taken from `http://<IP>/api/system`
```json

View File

@@ -7,5 +7,5 @@ contact_links:
url: https://github.com/emsesp/EMS-ESP32/discussions
about: EMS-ESP usage Questions, Feature Requests and Projects.
- name: EMS-ESP Users Chat
url: https://discord.gg/3J3GgnzpyT
url: https://discord.gg/GP9DPSgeJq
about: Chat for feedback, questions and troubleshooting.

View File

@@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.8.1] 11 January 2026
## Added
- update time saved in nvs
- heatpump entities [#2883](https://github.com/emsesp/EMS-ESP32/issues/2883)
- HA input number format (mode) selectable box/slider (slider for max range 100) [#2900](https://github.com/emsesp/EMS-ESP32/discussions/2900)
## Fixed
- fix EMS bus disconnected errors on some systems [#2881](https://github.com/emsesp/EMS-ESP32/issues/2881)
- selflowtemp fix [#2876](https://github.com/emsesp/EMS-ESP32/issues/2876)
- updated valid GPIOs for ESP32S2, ESP32S3 and ESP32 that caused custom systems to block gpios [#2887](https://github.com/emsesp/EMS-ESP32/issues/2887)
- Junkers wwcharge offset [#2860](https://github.com/emsesp/EMS-ESP32/issues/2860)
- fixed minflowtemp [#2890](https://github.com/emsesp/EMS-ESP32/issues/2890)
- don't add HA uom/classes for bool values [#2885](https://github.com/emsesp/EMS-ESP32/issues/2885)
- fixed missing progress bar on web firmware uploads
## Changed
- snapshot gpios stored in temporary ram
- GPIOs stored along with the name and reported in log if conflicting
- free GPIOs depend on board profile [#2901](https://github.com/emsesp/EMS-ESP32/issues/2901)
- prefer PSram for mqtt queue [#2889](https://github.com/emsesp/EMS-ESP32/issues/2889)
- day schedule defult to all days, no day selected is not allowed
- board profile `CUSTOM` can only be selected in developer mode
- mqtt sends round values without decimals (`28` instead of `28.0`)
## [3.8.0] 31 December 2025
## Added

View File

@@ -2,30 +2,32 @@
For more details go to [emsesp.org](https://emsesp.org/).
## [3.8.1]
## [3.8.2]
## Added
- update time saved in nvs
- heatpump entities [#2883](https://github.com/emsesp/EMS-ESP32/issues/2883)
- HA input number format (mode) selectable box/slider (slider for max range 100) [#2900](https://github.com/emsesp/EMS-ESP32/discussions/2900)
- comfortpoint for BC400 [#2935](https://github.com/emsesp/EMS-ESP32/issues/2935)
- customize device brand [#2784](https://github.com/emsesp/EMS-ESP32/issues/2784)
- set model for ems-esp devices temperature, analog, etc. [#2958](https://github.com/emsesp/EMS-ESP32/discussions/2958)
- prometheus metrics for temperature/analog/scheduler/custom [#2962](https://github.com/emsesp/EMS-ESP32/issues/2962)
- boiler pumpkick [#2965](https://github.com/emsesp/EMS-ESP32/discussions/2965)
- heatpump reset [#2933](https://github.com/emsesp/EMS-ESP32/issues/2933)
- 2.nd freshwater module (dhw4, dhw5) [#2991](https://github.com/emsesp/EMS-ESP32/issues/2991)
## Fixed
- fix EMS bus disconnected errors on some systems [#2881](https://github.com/emsesp/EMS-ESP32/issues/2881)
- selflowtemp fix [#2876](https://github.com/emsesp/EMS-ESP32/issues/2876)
- updated valid GPIOs for ESP32S2, ESP32S3 and ESP32 that caused custom systems to block gpios [#2887](https://github.com/emsesp/EMS-ESP32/issues/2887)
- Junkers wwcharge offset [#2860](https://github.com/emsesp/EMS-ESP32/issues/2860)
- fixed minflowtemp [#2890](https://github.com/emsesp/EMS-ESP32/issues/2890)
- don't add HA uom/classes for bool values [#2885](https://github.com/emsesp/EMS-ESP32/issues/2885)
- fixed missing progress bar on web firmware uploads
- SRC climate creation [#2936](https://github.com/emsesp/EMS-ESP32/issues/2936) and [#2960](https://github.com/emsesp/EMS-ESP32/issues/2960)
- missing translations [#3015](https://github.com/emsesp/EMS-ESP32/issues/3015)
## Changed
- snapshot gpios stored in temporary ram
- GPIOs stored along with the name and reported in log if conflicting
- free GPIOs depend on board profile [#2901](https://github.com/emsesp/EMS-ESP32/issues/2901)
- prefer PSram for mqtt queue [#2889](https://github.com/emsesp/EMS-ESP32/issues/2889)
- day schedule defult to all days, no day selected is not allowed
- board profile `CUSTOM` can only be selected in developer mode
- mqtt sends round values without decimals (`28` instead of `28.0`)
- weblogbuffer up to 1000 messages with PSRAM, mentioned in [#2933](https://github.com/emsesp/EMS-ESP32/issues/2933)
- validate custom entity writes, [#2931](https://github.com/emsesp/EMS-ESP32/issues/2931)
- remove wrong burnMinPower [#2918](https://github.com/emsesp/EMS-ESP32/issues/2918)
- store scheduler active state to nvs [#2946](https://github.com/emsesp/EMS-ESP32/discussions/2946)
- translated modes `heat` and `eco` for HA-climate mode-str-tpl
- support `minflowtemp` and `baseflowtemp` [#2969](https://github.com/emsesp/EMS-ESP32/discussions/2969)
- update version if it is 00.00 in first read [#2981](https://github.com/emsesp/EMS-ESP32/issues/2981)
- device class for % values [#2980](https://github.com/emsesp/EMS-ESP32/issues/2980)
- fetch telegrams: set length to fetch [#3017](https://github.com/emsesp/EMS-ESP32/issues/3017)
- move http client from stack to heap

View File

@@ -63,8 +63,9 @@ CXX_STANDARD := -std=gnu++17
#----------------------------------------------------------------------
# Defined Symbols
#----------------------------------------------------------------------
DEFINES += -DARDUINOJSON_ENABLE -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0
DEFINES += -DARDUINOJSON_ENABLE -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0
DEFINES += -DEMSESP_STANDALONE -DEMSESP_TEST -DEMSESP_DEBUG -DEMC_RX_BUFFER_SIZE=1500
DEFINES += -DNO_TLS_SUPPORT
DEFINES += $(ARGS)
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32S3\"
@@ -79,6 +80,10 @@ SYMBOLS := $(CURDIR)/$(BUILD)/$(TARGET).out
CSOURCES := $(shell find $(SOURCES) -name "*.c" 2>/dev/null)
CXXSOURCES := $(shell find $(SOURCES) -name "*.cpp" 2>/dev/null)
# Exclude files not needed for standalone build, if they exist
CSOURCES := $(filter-out src/core/ModuleLibrary.c,$(CSOURCES))
CXXSOURCES := $(filter-out src/core/ModuleLibrary.cpp,$(CXXSOURCES))
OBJS := $(patsubst %,$(BUILD)/%.o,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)))
DEPS := $(patsubst %,$(BUILD)/%.d,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)))

View File

@@ -18,7 +18,7 @@
<a href="https://emsesp.org">
<img src="https://img.shields.io/badge/Documentation-0077b5?style=for-the-badge&logo=googledocs&logoColor=white" alt="Guides" />
</a>
<a href="https://discord.gg/3J3GgnzpyT">
<a href="https://discord.gg/GP9DPSgeJq">
<img src="https://img.shields.io/badge/Discord-7289da?style=for-the-badge&logo=discord&logoColor=white" alt="Discord" />
</a>
<a href="https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md">
@@ -32,7 +32,8 @@
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=emsesp_EMS-ESP32&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=emsesp_EMS-ESP32)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9441142f49424ef891e8f5251866ee6b)](https://app.codacy.com/gh/emsesp/EMS-ESP32/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
[![downloads](https://img.shields.io/github/downloads/emsesp/EMS-ESP32/total.svg)](https://github.com/emsesp/EMS-ESP32/releases)
[![chat](https://img.shields.io/discord/816637840644505620.svg?style=flat-square&color=blueviolet)](https://discord.gg/3J3GgnzpyT)
[![chat](https://img.shields.io/discord/816637840644505620.svg?style=flat-square&color=blueviolet)](https://discord.gg/GP9DPSgeJq)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/emsesp/EMS-ESP32)
[![GitHub stars](https://img.shields.io/github/stars/emsesp/EMS-ESP32.svg?style=social&label=Star)](https://github.com/emsesp/EMS-ESP32/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/emsesp/EMS-ESP32.svg?style=social&label=Fork)](https://github.com/emsesp/EMS-ESP32/network)
@@ -40,7 +41,8 @@
**EMS-ESP** is an open-source firmware for the Espressif ESP32 microcontroller to communicate with **EMS** (Energy Management System) compatible equipment from manufacturers such as Bosch, Buderus, Nefit, Junkers, Worcester, Sieger, elm.leblanc and iVT.
It requires a small circuit to interface with the EMS bus which can be purchased from <https://bbqkees-electronics.nl> or custom built.
It requires a small circuit to interface with the EMS bus which can be purchased from <https://bbqkees-electronics.nl>. These gateways are tested thoroughly and certified to work with EMS-ESP.
## 📦&nbsp; **Key Features**
@@ -64,17 +66,17 @@ Head over to the [Installation Guide](https://emsesp.org/Installing) section of
## 📋&nbsp; **Documentation**
Visit [emsesp.org](https://emsesp.org) for more details on how to install and configure EMS-ESP. There is also a collection of Frequently Asked Questions and Troubleshooting tips with example customizations from the community.
Visit [emsesp.org](https://emsesp.org) for more details on how to setup and configure EMS-ESP. You'll also find more a collection of example configuarations, Frequently Asked Questions and Troubleshooting tips.
## 💬&nbsp; **Getting Support**
To chat with the community reach out on our [Discord Server](https://discord.gg/3J3GgnzpyT).
To chat with the community reach out on our [Discord Server](https://discord.gg/GP9DPSgeJq).
If you find an issue or have a request, see [how to request support](https://emsesp.org/Support/) on how to submit a bug report or feature request.
If you find an issue or have a request, see the [Getting Support](https://emsesp.org/Support/) section of the documentation. Note if you are using a non-BBQKees EMS gateway, you may need to contact the manufacturer for support.
## 🎥&nbsp; **Live Demo**
For a live demo go to [demo.emsesp.org](https://demo.emsesp.org). Pick a language from the sign on page and log in with any username or password. Note not all features are operational as it's based on static data.
To see a live demo go to [demo.emsesp.org](https://demo.emsesp.org). Pick a language and use any username and password to log in. Note whast you're seeing is static example data so not all features are operational.
## 💖&nbsp; **Contributors**
@@ -84,7 +86,7 @@ If you like **EMS-ESP**, please give it a ✨ on GitHub, or even better fork it
## 📦&nbsp; **Building**
See the [Building Guide](https://emsesp.org/Building) section of the documentation for instructions on how to build EMS-ESP.
See the [Building the firmware](https://emsesp.org/Building) guide in the documentation for instructions on how to build EMS-ESP from this source code.
## 📢&nbsp; **Libraries used**

View File

@@ -9,6 +9,7 @@
}
],
"dictionaries": ["project-words"],
"caseSensitive": false,
"ignorePaths": [
"node_modules",
"compile_commands.json",
@@ -36,6 +37,7 @@
"pnpm-*.yaml",
"vite.config.ts",
"lib/esp32-psram/**",
"test/test_api/test_api.h"
"test/test_api/test_api.h",
"lib_standalone/**"
]
}

View File

@@ -1,85 +1,228 @@
{
"type": "settings",
"Network": {
"ssid": "my_wifi_ssid",
"bssid": "",
"password": "my_wifi_password",
"hostname": "ems-esp"
"type": "systembackup",
"version": "3.8.2",
"date": "2026-03-29T13:28:15",
"systembackup": [
{
"type": "settings",
"Network": {
"ssid": "",
"bssid": "",
"password": "",
"hostname": "ems-esp",
"static_ip_config": false,
"bandwidth20": false,
"nosleep": true,
"enableMDNS": true,
"enableCORS": false,
"CORSOrigin": "*",
"tx_power": 0
},
"AP": {
"provision_mode": 2,
"ssid": "ems-esp",
"password": "ems-esp-neo",
"channel": 1,
"ssid_hidden": false,
"max_clients": 4,
"local_ip": "192.168.4.1",
"gateway_ip": "192.168.4.1",
"subnet_mask": "255.255.255.0"
},
"MQTT": {
"enableTLS": false,
"rootCA": "",
"enabled": false,
"host": "",
"port": 1883,
"base": "ems-esp",
"username": "",
"password": "",
"client_id": "esp32-b8ffc9ec",
"keep_alive": 60,
"clean_session": false,
"entity_format": 1,
"publish_time_boiler": 10,
"publish_time_thermostat": 10,
"publish_time_solar": 10,
"publish_time_mixer": 10,
"publish_time_water": 10,
"publish_time_other": 60,
"publish_time_sensor": 10,
"publish_time_heartbeat": 60,
"mqtt_qos": 0,
"mqtt_retain": false,
"ha_enabled": false,
"nested_format": 1,
"discovery_prefix": "homeassistant",
"discovery_type": 0,
"ha_number_mode": 0,
"publish_single": false,
"publish_single2cmd": false,
"send_response": false
},
"NTP": {
"enabled": true,
"server": "time.google.com",
"tz_label": "Europe/Amsterdam",
"tz_format": "CET-1CEST,M3.5.0,M10.5.0/3"
},
"Security": {
"jwt_secret": "ems-esp-neo",
"users": [
{
"username": "admin",
"password": "admin",
"admin": true
},
{
"username": "guest",
"password": "guest",
"admin": false
}
]
},
"Settings": {
"version": "3.8.2",
"board_profile": "E32V2_2",
"platform": "ESP32",
"locale": "en",
"tx_mode": 1,
"ems_bus_id": 11,
"syslog_enabled": false,
"syslog_level": 3,
"trace_raw": false,
"syslog_mark_interval": 0,
"syslog_host": "",
"syslog_port": 514,
"boiler_heatingoff": false,
"remote_timeout": 24,
"remote_timeout_en": false,
"shower_timer": false,
"shower_alert": false,
"shower_alert_coldshot": 10,
"shower_alert_trigger": 7,
"shower_min_duration": 180,
"rx_gpio": 4,
"tx_gpio": 5,
"dallas_gpio": 14,
"dallas_parasite": false,
"led_gpio": 32,
"hide_led": false,
"led_type": 1,
"low_clock": false,
"telnet_enabled": true,
"notoken_api": false,
"readonly_mode": false,
"analog_enabled": true,
"pbutton_gpio": 34,
"solar_maxflow": 30,
"fahrenheit": false,
"bool_format": 1,
"bool_dashboard": 1,
"enum_format": 1,
"weblog_level": 6,
"weblog_buffer": 50,
"weblog_compact": true,
"phy_type": 1,
"eth_power": 15,
"eth_phy_addr": 0,
"eth_clock_mode": 1,
"modbus_enabled": false,
"modbus_port": 502,
"modbus_max_clients": 10,
"modbus_timeout": 300,
"developer_mode": true,
"email_enabled": false,
"email_ssl": false,
"email_starttls": true,
"email_server": "smtp.example.net",
"email_port": 587,
"email_login": "",
"email_pass": "",
"email_sender": "ems-esp@example.net",
"email_recp": "",
"email_subject": "ems-esp notification"
}
},
"AP": {
"provision_mode": 2,
"ssid": "ems-esp",
"password": "ems-esp-neo",
"channel": 1,
"ssid_hidden": false,
"max_clients": 4,
"local_ip": "192.168.4.1",
"gateway_ip": "192.168.4.1",
"subnet_mask": "255.255.255.0"
{
"type": "schedule",
"Schedule": {
"schedule": []
}
},
"MQTT": {
"enableTLS": false,
"rootCA": "",
"enabled": false,
"host": "127.0.0.1",
"port": 1883,
"base": "ems-esp",
"username": "username",
"password": "password",
"client_id": "ems-esp",
"entity_format": 1,
"publish_time_boiler": 10,
"publish_time_thermostat": 10,
"publish_time_solar": 10,
"publish_time_mixer": 10,
"publish_time_water": 10,
"publish_time_other": 60,
"publish_time_sensor": 10,
"publish_time_heartbeat": 60,
"mqtt_qos": 0,
"mqtt_retain": false,
"ha_enabled": false,
"nested_format": 1,
"discovery_prefix": "homeassistant",
"discovery_type": 0,
"publish_single": false,
"publish_single2cmd": false,
"send_response": false
{
"type": "customizations",
"Customizations": {
"ts": [
{
"id": "28_1767_7B13_2502",
"name": "gateway_temperature",
"offset": 0,
"is_system": true
}
],
"as": [
{
"gpio": 39,
"name": "core_voltage",
"offset": 0,
"factor": 0.003771,
"uom": 23,
"type": 3,
"is_system": true
},
{
"gpio": 36,
"name": "supply_voltage",
"offset": 0,
"factor": 0.017,
"uom": 23,
"type": 3,
"is_system": true
},
{
"gpio": 2,
"name": "led",
"offset": 0,
"factor": 1,
"uom": 0,
"type": 6,
"is_system": true
}
],
"masked_entities": []
}
},
"NTP": {
"enabled": true,
"server": "time.google.com",
"tz_label": "Europe/Amsterdam",
"tz_format": "CET-1CEST,M3.5.0,M10.5.0/3"
{
"type": "entities",
"Entities": {
"entities": []
}
},
"Security": {
"jwt_secret": "ems-esp-neo",
"users": [
{
"username": "admin",
"password": "admin",
"admin": true
},
{
"username": "guest",
"password": "guest",
"admin": false
}
]
{
"type": "modules",
"Modules": {
"modules": []
}
},
"Settings": {
"board_profile": "S3",
"locale": "en",
"tx_mode": 1,
"ems_bus_id": 11,
"boiler_heatingoff": false,
"hide_led": true,
"telnet_enabled": true,
"notoken_api": false,
"analog_enabled": true,
"fahrenheit": false,
"bool_format": 1,
"bool_dashboard": 1,
"enum_format": 1
{
"type": "customSupport",
"Support": {
"html": [
"This product is installed and managed by:",
"",
"<b>Bosch Installer Example</b>",
"",
"Nefit Road 12",
"1234 AB Amsterdam",
"Phone: +31 123 456 789",
"email: support@boschinstaller.nl",
"",
"For help and questions please <a target='_blank' href='https://emsesp.org'>contact</a> your installer."
],
"img_url": "https://emsesp.org/media/images/designer.png"
}
}
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ telegram_type_id,name,is_fetched
0x19,UBAMonitorSlow,
0x1A,UBASetPoints,
0x1C,UBAMaintenanceStatus,
0x1E,WM10TempMessage,
0x1E,HydrTemp,
0x23,JunkersSetMixer,fetched
0x27,UBASettingsWW,fetched
0x28,WeatherComp,fetched
@@ -72,11 +72,12 @@ telegram_type_id,name,is_fetched
0xE6,UBAParametersPlus,fetched
0xE9,UBAMonitorWWPlus,
0xEA,UBAParameterWWPlus,fetched
0xEB,PumpKick,fetched
0x0101,ISM1Set,fetched
0x0103,ISM1StatusMessage,fetched
0x0104,ISM2StatusMessage,
0x010C,IPMStatusMessage,
0x011E,JunkersDisp,fetched
0x011E,IPMTempMessage,
0x012E,HPEnergy1,
0x013B,HPEnergy2,
0x0165,JunkersSet,
@@ -111,8 +112,8 @@ telegram_type_id,name,is_fetched
0x02A0,RC300Curves,
0x02A1,RC300Curves,
0x02A2,RC300Curves,
0x02A5,RC300Monitor,
0x02A6,RC300Monitor,
0x02A5,RC300Monitor,fetched
0x02A6,CRFMonitor,
0x02A7,RC300Monitor,
0x02A8,CRFMonitor,
0x02A9,RC300Monitor,
@@ -197,7 +198,7 @@ telegram_type_id,name,is_fetched
0x04A2,HpInput,fetched
0x04A5,HPFan,fetched
0x04A7,HPPowerLimit,fetched
0x04AA,HPPower2,fetched
0x04AA,HPPower,
0x04AE,HPEnergy,fetched
0x04AF,HPMeters,fetched
0x055C,VentilationSet,fetched
1 telegram_type_id name is_fetched
13 0x19 UBAMonitorSlow
14 0x1A UBASetPoints
15 0x1C UBAMaintenanceStatus
16 0x1E WM10TempMessage HydrTemp
17 0x23 JunkersSetMixer fetched
18 0x27 UBASettingsWW fetched
19 0x28 WeatherComp fetched
72 0xE6 UBAParametersPlus fetched
73 0xE9 UBAMonitorWWPlus
74 0xEA UBAParameterWWPlus fetched
75 0xEB PumpKick fetched
76 0x0101 ISM1Set fetched
77 0x0103 ISM1StatusMessage fetched
78 0x0104 ISM2StatusMessage
79 0x010C IPMStatusMessage
80 0x011E JunkersDisp IPMTempMessage fetched
81 0x012E HPEnergy1
82 0x013B HPEnergy2
83 0x0165 JunkersSet
112 0x02A0 RC300Curves
113 0x02A1 RC300Curves
114 0x02A2 RC300Curves
115 0x02A5 RC300Monitor fetched
116 0x02A6 RC300Monitor CRFMonitor
117 0x02A7 RC300Monitor
118 0x02A8 CRFMonitor
119 0x02A9 RC300Monitor
198 0x04A2 HpInput fetched
199 0x04A5 HPFan fetched
200 0x04A7 HPPowerLimit fetched
201 0x04AA HPPower2 HPPower fetched
202 0x04AE HPEnergy fetched
203 0x04AF HPMeters fetched
204 0x055C VentilationSet fetched

View File

@@ -1,5 +1,5 @@
{
"adapter": "react",
"baseLocale": "pl",
"$schema": "https://unpkg.com/typesafe-i18n@5.26.2/schema/typesafe-i18n.json"
"$schema": "https://unpkg.com/typesafe-i18n@5.27.1/schema/typesafe-i18n.json"
}

View File

@@ -1,9 +1,10 @@
// @ts-check
import eslint from '@eslint/js';
import prettierConfig from 'eslint-config-prettier';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
export default tseslint.config(
export default defineConfig(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
prettierConfig,

View File

@@ -1,6 +1,6 @@
{
"name": "EMS-ESP",
"version": "3.8.0",
"version": "3.8.2",
"description": "EMS-ESP WebUI",
"homepage": "https://emsesp.org",
"author": "proddy, emsesp.org",
@@ -26,46 +26,46 @@
"@alova/adapter-xhr": "2.3.1",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.7",
"@mui/material": "^7.3.7",
"@preact/compat": "^18.3.1",
"@mui/icons-material": "^9.0.0",
"@mui/material": "^9.0.0",
"@preact/compat": "^18.3.2",
"@table-library/react-table-library": "4.1.15",
"alova": "3.4.1",
"alova": "3.5.1",
"async-validator": "^4.2.5",
"etag": "^1.8.1",
"formidable": "^3.5.4",
"jwt-decode": "^4.0.0",
"magic-string": "^0.30.21",
"mime-types": "^3.0.2",
"preact": "^10.28.2",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-icons": "^5.5.0",
"react-router": "^7.12.0",
"preact": "^10.29.1",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-icons": "^5.6.0",
"react-router": "^7.14.1",
"react-toastify": "^11.0.5",
"typesafe-i18n": "^5.26.2",
"typescript": "^5.9.3"
"typesafe-i18n": "^5.27.1",
"typescript": "^6.0.2"
},
"devDependencies": {
"@babel/core": "^7.28.5",
"@eslint/js": "^9.39.2",
"@preact/compat": "^18.3.1",
"@preact/preset-vite": "^2.10.2",
"@babel/core": "^7.29.0",
"@eslint/js": "^10.0.1",
"@preact/compat": "^18.3.2",
"@preact/preset-vite": "^2.10.5",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/node": "^25.0.6",
"@types/react": "^19.2.8",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"axe-core": "^4.11.1",
"axe-core": "^4.11.3",
"concurrently": "^9.2.1",
"eslint": "^9.39.2",
"eslint": "^10.2.0",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.7.4",
"rollup-plugin-visualizer": "^6.0.5",
"terser": "^5.44.1",
"typescript-eslint": "^8.52.0",
"vite": "^7.3.1",
"prettier": "^3.8.3",
"rollup-plugin-visualizer": "^7.0.1",
"terser": "^5.46.1",
"typescript-eslint": "^8.58.2",
"vite": "^8.0.8",
"vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^6.0.4"
"vite-tsconfig-paths": "^6.1.1"
},
"packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48"
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
}

2447
interface/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ import { toast } from 'react-toastify';
import ForwardIcon from '@mui/icons-material/Forward';
import { Box, Button, Paper, Typography } from '@mui/material';
import type { Theme } from '@mui/material/styles';
import * as AuthenticationApi from 'components/routing/authentication';
import { useRequest } from 'alova/client';
@@ -36,7 +37,7 @@ const SignIn = memo(() => {
{
immediate: false
}
).onSuccess((response) => {
).onSuccess((response: { data: { access_token: string } }) => {
if (response.data) {
authenticationContext.signIn(response.data.access_token);
}
@@ -78,7 +79,6 @@ const SignIn = memo(() => {
}
}, [signInRequest, signIn, LL]);
// Memoize callback to prevent recreation on every render
const submitOnEnter = useMemo(() => onEnterCallback(signIn), [signIn]);
// get rid of scrollbar
@@ -92,13 +92,15 @@ const SignIn = memo(() => {
return (
<Box
display="flex"
height="100vh"
margin="auto"
padding={2}
justifyContent="center"
flexDirection="column"
maxWidth={(theme) => theme.breakpoints.values.sm}
sx={(theme: Theme) => ({
display: 'flex',
height: '100vh',
margin: 'auto',
padding: 2,
justifyContent: 'center',
flexDirection: 'column',
maxWidth: theme.breakpoints.values.sm
})}
>
<Paper
sx={(theme) => ({
@@ -111,16 +113,18 @@ const SignIn = memo(() => {
width: '100%'
})}
>
<Typography mb={1} variant="h4">
<Typography sx={{ mb: 1 }} variant="h4">
{PROJECT_NAME}
</Typography>
<LanguageSelector />
<Box
mt={1}
display="flex"
flexDirection="column"
gap={1}
alignItems="center"
sx={{
mt: 1,
display: 'flex',
flexDirection: 'column',
gap: 1,
alignItems: 'center'
}}
>
<ValidatedTextField
fieldErrors={fieldErrors || {}}

View File

@@ -310,13 +310,15 @@ const CustomEntities = () => {
/>
)}
</Cell>
<Cell>{ei.ram > 0 ? '' : showHex(ei.device_id as number, 2)}</Cell>
<Cell>{ei.ram > 0 ? '' : showHex(ei.type_id as number, 3)}</Cell>
<Cell>{ei.ram > 0 ? '' : ei.offset}</Cell>
<Cell>
{ei.ram === 1 ? '' : showHex(ei.device_id as number, 2)}
</Cell>
<Cell>{ei.ram === 1 ? '' : showHex(ei.type_id as number, 3)}</Cell>
<Cell>{ei.ram === 1 ? '' : ei.offset}</Cell>
<Cell>
{ei.ram === 1 ? 'RAM' : DeviceValueTypeNames[ei.value_type]}
{ei.ram === 1
? 'RAM'
: ei.ram === 2
? 'NVS'
: DeviceValueTypeNames[ei.value_type]}
</Cell>
<Cell>{formatValue(ei.value, ei.uom)}</Cell>
</Row>
@@ -341,9 +343,9 @@ const CustomEntities = () => {
return (
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
<Box mb={2} color="warning.main">
<Typography variant="body1">{LL.ENTITIES_HELP_1()}.</Typography>
</Box>
<Typography sx={{ mb: 2 }} color="warning" variant="body1">
{LL.ENTITIES_HELP_1()}.
</Typography>
{renderEntity()}
@@ -359,8 +361,8 @@ const CustomEntities = () => {
/>
)}
<Box mt={2} display="flex" flexWrap="wrap">
<Box flexGrow={1}>
<Box sx={{ mt: 2, display: 'flex', flexWrap: 'wrap' }}>
<Box sx={{ flexGrow: 1 }}>
{numChanges > 0 && (
<ButtonRow>
<Button
@@ -382,7 +384,7 @@ const CustomEntities = () => {
</ButtonRow>
)}
</Box>
<Box flexWrap="nowrap" whiteSpace="nowrap">
<Box sx={{ flexWrap: 'nowrap', whiteSpace: 'nowrap' }}>
<Button
startIcon={<AddIcon />}
variant="outlined"

View File

@@ -7,7 +7,7 @@ import DoneIcon from '@mui/icons-material/Done';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutlined';
import {
Box,
Button,
@@ -178,7 +178,7 @@ const CustomEntitiesDialog = ({
onChange={updateFormValue}
/>
</Grid>
<Grid mt={3}>
<Grid sx={{ mt: 3 }}>
<BlockFormControlLabel
control={
<Checkbox
@@ -205,9 +205,10 @@ const CustomEntitiesDialog = ({
>
<MenuItem value={0}>EMS-{LL.VALUE(1)}</MenuItem>
<MenuItem value={1}>RAM-{LL.VALUE(1)}</MenuItem>
<MenuItem value={2}>NVS-{LL.VALUE(1)}</MenuItem>
</TextField>
</Grid>
{editItem.ram === 1 && (
{editItem.ram > 0 && (
<>
<Grid>
<TextField
@@ -237,7 +238,7 @@ const CustomEntitiesDialog = ({
)}
{editItem.ram === 0 && (
<>
<Grid mt={3}>
<Grid sx={{ mt: 3 }}>
<BlockFormControlLabel
control={
<Checkbox
@@ -403,7 +404,7 @@ const CustomEntitiesDialog = ({
</DialogContent>
<DialogActions>
{!creating && (
<Box flexGrow={1}>
<Box sx={{ flexGrow: 1 }}>
<Button
startIcon={<RemoveIcon />}
variant="outlined"

View File

@@ -111,13 +111,14 @@ const Customizations = () => {
const [selectedDeviceTypeNameURL, setSelectedDeviceTypeNameURL] =
useState<string>(''); // needed for API URL
const [selectedDeviceName, setSelectedDeviceName] = useState<string>('');
const [selectedDeviceBrand, setSelectedDeviceBrand] = useState<string>('');
const { send: sendResetCustomizations } = useRequest(resetCustomizations(), {
immediate: false
});
const { send: sendDeviceName } = useRequest(
(data: { id: number; name: string }) => writeDeviceName(data),
(data: { id: number; name: string; brand: string }) => writeDeviceName(data),
{
immediate: false
}
@@ -267,6 +268,7 @@ const Customizations = () => {
if (device) {
setSelectedDeviceTypeNameURL(device.url || '');
setSelectedDeviceName(device.n);
setSelectedDeviceBrand(device.b);
}
setNumChanges(0);
setRestartNeeded(false);
@@ -442,7 +444,11 @@ const Customizations = () => {
}, [devices, deviceEntities, selectedDevice, sendCustomizationEntities, LL]);
const renameDevice = useCallback(async () => {
await sendDeviceName({ id: selectedDevice, name: selectedDeviceName })
await sendDeviceName({
id: selectedDevice,
name: selectedDeviceName,
brand: selectedDeviceBrand
})
.then(() => {
toast.success(LL.UPDATED_OF(LL.NAME(1)));
})
@@ -453,24 +459,42 @@ const Customizations = () => {
setRename(false);
await fetchCoreData();
});
}, [selectedDevice, selectedDeviceName, sendDeviceName, LL, fetchCoreData]);
}, [
selectedDevice,
selectedDeviceName,
selectedDeviceBrand,
sendDeviceName,
LL,
fetchCoreData
]);
const renderDeviceList = () => (
<>
<Box mb={1} color="warning.main">
<Typography variant="body1">{LL.CUSTOMIZATIONS_HELP_1()}.</Typography>
</Box>
<Box display="flex" flexWrap="wrap" alignItems="center" gap={2}>
<Typography sx={{ mb: 1 }} color="warning" variant="body1">
{LL.CUSTOMIZATIONS_HELP_1()}.
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 2 }}>
{rename ? (
<TextField
name="device"
label={LL.EMS_DEVICE()}
fullWidth
variant="outlined"
value={selectedDeviceName}
onChange={(e) => setSelectedDeviceName(e.target.value)}
margin="normal"
/>
<>
<TextField
name="device"
label={LL.EMS_DEVICE()}
style={{ minWidth: '48%' }}
variant="outlined"
value={selectedDeviceName}
onChange={(e) => setSelectedDeviceName(e.target.value)}
margin="normal"
/>
<TextField
name="brand"
label={LL.BRAND()}
style={{ minWidth: '48%' }}
variant="outlined"
value={selectedDeviceBrand}
onChange={(e) => setSelectedDeviceBrand(e.target.value)}
margin="normal"
/>
</>
) : (
<TextField
name="device"
@@ -546,27 +570,22 @@ const Customizations = () => {
const renderDeviceData = () => {
return (
<>
<Box color="warning.main">
<Typography variant="body2" mt={1} mb={1}>
<OptionIcon type="favorite" isSet={true} />={LL.CUSTOMIZATIONS_HELP_2()}
&nbsp;&nbsp;
<OptionIcon type="readonly" isSet={true} />={LL.CUSTOMIZATIONS_HELP_3()}
&nbsp;&nbsp;
<OptionIcon type="api_mqtt_exclude" isSet={true} />=
{LL.CUSTOMIZATIONS_HELP_4()}&nbsp;&nbsp;
<OptionIcon type="web_exclude" isSet={true} />=
{LL.CUSTOMIZATIONS_HELP_5()}&nbsp;&nbsp;
<OptionIcon type="deleted" isSet={true} />={LL.CUSTOMIZATIONS_HELP_6()}
</Typography>
</Box>
<Typography sx={{ mt: 1, mb: 1 }} color="warning" variant="body2">
<OptionIcon type="favorite" isSet={true} />={LL.CUSTOMIZATIONS_HELP_2()}
&nbsp;&nbsp;
<OptionIcon type="readonly" isSet={true} />={LL.CUSTOMIZATIONS_HELP_3()}
&nbsp;&nbsp;
<OptionIcon type="api_mqtt_exclude" isSet={true} />=
{LL.CUSTOMIZATIONS_HELP_4()}&nbsp;&nbsp;
<OptionIcon type="web_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_5()}
&nbsp;&nbsp;
<OptionIcon type="deleted" isSet={true} />={LL.CUSTOMIZATIONS_HELP_6()}
</Typography>
<Grid
container
mb={1}
mt={0}
spacing={2}
direction="row"
justifyContent="flex-start"
alignItems="center"
sx={{ mb: 1, mt: 0, justifyContent: 'flex-start', alignItems: 'center' }}
>
<Grid>
<TextField
@@ -755,8 +774,8 @@ const Customizations = () => {
</Button>
</MessageBox>
) : (
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1}>
<Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
<Box sx={{ flexGrow: 1 }}>
{numChanges !== 0 && (
<ButtonRow>
<Button

View File

@@ -37,7 +37,7 @@ interface LabelValueProps {
const LabelValue = memo(({ label, value }: LabelValueProps) => (
<Grid container direction="row">
<Typography variant="body2" color="warning.main">
<Typography variant="body2" color="warning">
{label}:&nbsp;
</Typography>
<Typography variant="body2">{value}</Typography>
@@ -131,7 +131,7 @@ const CustomizationsDialog = ({
/>
<LabelValue label={LL.WRITEABLE()} value={writeableIcon} />
<Box mt={1} mb={2}>
<Box sx={{ mt: 1, mb: 2 }}>
<EntityMaskToggle onUpdate={updateDeviceEntity} de={editItem} />
</Box>
@@ -172,7 +172,7 @@ const CustomizationsDialog = ({
</Grid>
{error && (
<Typography variant="body2" color="error" mt={2}>
<Typography sx={{ mt: 2 }} variant="body2" color="error">
Error: Check min and max values
</Typography>
)}

View File

@@ -6,7 +6,7 @@ import { toast } from 'react-toastify';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import EditIcon from '@mui/icons-material/Edit';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
import HelpOutlineIcon from '@mui/icons-material/HelpOutlined';
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
import {
@@ -262,12 +262,8 @@ const Dashboard = memo(() => {
return (
<>
{!data.connected && (
<MessageBox level="error" message={LL.EMS_BUS_WARNING()} />
)}
{data.connected && data.nodes.length > 0 && !hasFavEntities && (
<MessageBox mb={2} level="warning">
<MessageBox sx={{ mb: 2 }} level="warning">
<Typography>
{LL.NO_DATA_1()}&nbsp;
<Link to="/customizations" style={{ color: 'white' }}>
@@ -284,10 +280,12 @@ const Dashboard = memo(() => {
)}
<Box
display="flex"
justifyContent="flex-end"
flexWrap="nowrap"
whiteSpace="nowrap"
sx={{
display: 'flex',
justifyContent: 'flex-end',
flexWrap: 'nowrap',
whiteSpace: 'nowrap'
}}
>
<ToggleButtonGroup
size="small"
@@ -310,7 +308,7 @@ const Dashboard = memo(() => {
</Box>
{data.nodes.length > 0 ? (
<Box mt={1} justifyContent="center" flexDirection="column">
<Box sx={{ mt: 1, justifyContent: 'center', flexDirection: 'column' }}>
<IconContext.Provider
value={{
color: 'lightblue',
@@ -377,13 +375,8 @@ const Dashboard = memo(() => {
</IconContext.Provider>
</Box>
) : (
<Box
display="flex"
// justifyContent="flex-end"
// flexWrap="nowrap"
// whiteSpace="nowrap"
>
<Typography mt={1} color="warning.main" variant="body1">
<Box sx={{ display: 'flex' }}>
<Typography sx={{ mt: 1 }} color="warning" variant="body1">
no data
</Typography>
<Tooltip title={LL.DASHBOARD_1()}>

View File

@@ -1,4 +1,5 @@
import { memo } from 'react';
import type { IconType } from 'react-icons';
import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ai';
import { CgSmartHomeBoiler } from 'react-icons/cg';
import { FaSolarPanel } from 'react-icons/fa';
@@ -15,14 +16,9 @@ import { PiFan, PiGauge } from 'react-icons/pi';
import { TiFlowSwitch, TiThermometer } from 'react-icons/ti';
import { VscVmConnect } from 'react-icons/vsc';
import type { SvgIconProps } from '@mui/material';
import { DeviceType } from './types';
const deviceIconLookup: Record<
DeviceType,
React.ComponentType<SvgIconProps> | null
> = {
const deviceIconLookup: Record<DeviceType, IconType | null> = {
[DeviceType.TEMPERATURESENSOR]: TiThermometer,
[DeviceType.ANALOGSENSOR]: PiGauge,
[DeviceType.BOILER]: CgSmartHomeBoiler,

View File

@@ -8,7 +8,7 @@ import {
useState
} from 'react';
import { IconContext } from 'react-icons';
import { useNavigate } from 'react-router';
import { Link, useNavigate } from 'react-router';
import { toast } from 'react-toastify';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
@@ -534,9 +534,19 @@ const Devices = memo(() => {
const renderCoreData = () => (
<>
{!coreData.connected ? (
<MessageBox level="error" message={LL.EMS_BUS_WARNING()} />
<MessageBox level="error" message={LL.EMS_BUS_WARNING() + '.'}>
&nbsp;(
<Link
target="_blank"
to="https://docs.emsesp.org/Troubleshooting#ems-bus-is-not-connecting"
style={{ color: 'white' }}
>
{LL.ONLINE_HELP()}
</Link>
)
</MessageBox>
) : (
<Box justifyContent="center" flexDirection="column">
<Box sx={{ justifyContent: 'center', flexDirection: 'column' }}>
<IconContext.Provider
value={{
color: 'lightblue',
@@ -660,12 +670,12 @@ const Devices = memo(() => {
}}
>
<Box sx={{ p: 1 }}>
<Grid container justifyContent="space-between">
<Typography noWrap variant="subtitle1" color="warning.main">
<Grid container sx={{ justifyContent: 'space-between' }}>
<Typography noWrap variant="subtitle1" color="warning">
{deviceInfo.n}&nbsp;(
{deviceInfo.tn})
</Typography>
<Grid justifyContent="flex-end">
<Grid sx={{ justifyContent: 'flex-end' }}>
<ButtonTooltip title={LL.CLOSE()}>
<IconButton onClick={resetDeviceSelect} aria-label={LL.CLOSE()}>
<HighlightOffIcon color="primary" sx={{ fontSize: 18 }} />

View File

@@ -128,9 +128,9 @@ const DevicesDialog = ({
<Dialog sx={dialogStyle} open={open} onClose={onClose}>
<DialogTitle>{dialogTitle}</DialogTitle>
<DialogContent dividers>
<Box color="warning.main" mb={2}>
<Typography variant="body2">{editItem.id.slice(2)}</Typography>
</Box>
<Typography sx={{ mb: 2 }} color="warning" variant="body2">
{editItem.id.slice(2)}
</Typography>
<Grid container>
<Grid size={12}>
{editItem.l ? (

View File

@@ -11,6 +11,7 @@ import {
Box,
Button,
Divider,
Grid,
Link,
List,
ListItem,
@@ -42,7 +43,7 @@ interface CustomSupport {
html: string | null;
}
const DEFAULT_IMAGE_URL = 'https://emsesp.org/_media/images/installer.jpeg';
const DEFAULT_IMAGE_URL = 'https://emsesp.org/media/images/installer.jpeg';
const SUPPORT_BOX_STYLES: SxProps<Theme> = {
borderRadius: 3,
@@ -71,7 +72,6 @@ const HelpComponent = () => {
});
const [imgError, setImgError] = useState<boolean>(false);
// Memoize the request method to prevent re-creation on every render
const getCustomSupportMethod = useMemo(
() => callAction({ action: 'getCustomSupport' }),
[]
@@ -120,7 +120,7 @@ const HelpComponent = () => {
label: () => LL.HELP_INFORMATION_1()
},
{
href: 'https://discord.gg/3J3GgnzpyT',
href: 'https://discord.gg/GP9DPSgeJq',
icon: <CommentIcon />,
label: () => LL.HELP_INFORMATION_2()
},
@@ -146,11 +146,9 @@ const HelpComponent = () => {
<SectionContent>
{customSupport.html && (
<Stack
padding={1}
mb={2}
direction="row"
divider={<Divider orientation="vertical" flexItem />}
sx={SUPPORT_BOX_STYLES}
sx={{ padding: 1, mb: 2, ...SUPPORT_BOX_STYLES }}
>
<Typography variant="subtitle1">
<div dangerouslySetInnerHTML={{ __html: customSupport.html }} />
@@ -185,9 +183,9 @@ const HelpComponent = () => {
</List>
)}
<Box p={2} color="warning.main">
<Typography mb={1} variant="body1">
{LL.HELP_INFORMATION_4()}.
<Grid container spacing={2} sx={{ mt: 2, alignItems: 'center' }}>
<Typography sx={{ mb: 1 }} color="warning" variant="body1">
{LL.HELP_INFORMATION_4()}:
</Typography>
<Button
startIcon={<DownloadIcon />}
@@ -197,11 +195,11 @@ const HelpComponent = () => {
>
{LL.SUPPORT_INFORMATION(0)}
</Button>
</Box>
</Grid>
<Divider sx={{ mt: 4 }} />
<Typography color="white" variant="subtitle1" align="center" mt={1}>
<Typography color="white" variant="subtitle1" align="center" sx={{ mt: 1 }}>
&copy;&nbsp;
<Link
target="_blank"

View File

@@ -186,9 +186,9 @@ const Modules = () => {
return (
<>
<Box mb={2} color="warning.main">
<Typography variant="body1">{LL.MODULES_DESCRIPTION()}.</Typography>
</Box>
<Typography sx={{ mb: 2 }} color="warning" variant="body1">
{LL.MODULES_DESCRIPTION()}.
</Typography>
<Table
data={{ nodes: modules }}
theme={modules_theme}
@@ -236,8 +236,8 @@ const Modules = () => {
)}
</Table>
<Box mt={1} display="flex" flexWrap="wrap">
<Box flexGrow={1}>
<Box sx={{ mt: 1, display: 'flex', flexWrap: 'wrap' }}>
<Box sx={{ flexGrow: 1 }}>
{numChanges !== 0 && (
<ButtonRow>
<Button

View File

@@ -79,7 +79,7 @@ const ModulesDialog = ({
label="Enabled"
/>
</Grid>
<Box mt={2} mb={1}>
<Box sx={{ mt: 2, mb: 1 }}>
<TextField
name="license"
label="License Key"

View File

@@ -2,12 +2,12 @@ import { memo } from 'react';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutlined';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
import StarIcon from '@mui/icons-material/Star';
import StarOutlineIcon from '@mui/icons-material/StarOutline';
import StarOutlineIcon from '@mui/icons-material/StarOutlined';
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
import type { SvgIconProps } from '@mui/material';

View File

@@ -358,9 +358,9 @@ const Scheduler = () => {
return (
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
<Box mb={2} color="warning.main">
<Typography variant="body1">{LL.SCHEDULER_HELP_1()}.</Typography>
</Box>
<Typography sx={{ mb: 2 }} color="warning" variant="body1">
{LL.SCHEDULER_HELP_1()}.
</Typography>
{renderSchedule()}
{selectedScheduleItem && (
@@ -375,8 +375,8 @@ const Scheduler = () => {
/>
)}
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1}>
<Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
<Box sx={{ flexGrow: 1 }}>
{numChanges !== 0 && (
<ButtonRow>
<Button
@@ -398,7 +398,7 @@ const Scheduler = () => {
</ButtonRow>
)}
</Box>
<Box flexWrap="nowrap" whiteSpace="nowrap">
<Box sx={{ flexWrap: 'nowrap', whiteSpace: 'nowrap' }}>
<ButtonRow>
<Button
startIcon={<AddIcon />}

View File

@@ -4,7 +4,7 @@ import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutlined';
import {
Box,
Button,
@@ -338,11 +338,13 @@ const SchedulerDialog = ({
onChange={updateFormValue}
/>
{isTimerSchedule && (
<Box color="warning.main" ml={2} mt={4}>
<Typography variant="body2">
{LL.SCHEDULER_HELP_2()}
</Typography>
</Box>
<Typography
sx={{ ml: 2, mt: 4 }}
color="warning"
variant="body2"
>
{LL.SCHEDULER_HELP_2()}
</Typography>
)}
</>
) : (
@@ -391,7 +393,7 @@ const SchedulerDialog = ({
<DialogActions>
{!creating && (
<Box flexGrow={1}>
<Box sx={{ flexGrow: 1 }}>
<Button
startIcon={<RemoveIcon />}
variant="outlined"

View File

@@ -348,7 +348,7 @@ const Sensors = () => {
const addAnalogSensor = useCallback(() => {
if (firstAvailableGPIO.current === undefined) {
toast.error('No available GPIO found');
toast.error(LL.NO_GPIO());
return;
}
setCreating(true);
@@ -591,7 +591,14 @@ const Sensors = () => {
/>
)}
{sensorData?.analog_enabled === true && me.admin && (
<Box mt={2} display="flex" flexWrap="wrap" justifyContent="flex-end">
<Box
sx={{
mt: 2,
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'flex-end'
}}
>
<Button
variant="outlined"
color="primary"

View File

@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutlined';
import WarningIcon from '@mui/icons-material/Warning';
import {
Box,
@@ -479,7 +479,7 @@ const SensorsAnalogDialog = ({
)}
</Grid>
{fieldErrors && Object.keys(fieldErrors).length > 0 && (
<Box mt={1}>
<Box sx={{ mt: 1 }}>
{Object.values(fieldErrors).map((errArr, idx) =>
Array.isArray(errArr)
? errArr.map((err, j) => (
@@ -487,7 +487,7 @@ const SensorsAnalogDialog = ({
key={`${idx}-${j}`}
color="error"
variant="caption"
display="block"
sx={{ display: 'block' }}
>
{err.message}
</Typography>
@@ -498,7 +498,7 @@ const SensorsAnalogDialog = ({
)}
{editItem.s && (
<Grid>
<Typography mt={1} color="warning.main" variant="body2">
<Typography sx={{ mt: 1 }} color="warning" variant="body2">
<WarningIcon
fontSize="small"
sx={{ mr: 1, verticalAlign: 'middle' }}
@@ -511,7 +511,7 @@ const SensorsAnalogDialog = ({
</DialogContent>
<DialogActions>
{!creating && (
<Box flexGrow={1} sx={{ '& button': { mt: 0 } }}>
<Box sx={{ flexGrow: 1, '& button': { mt: 0 } }}>
<Button
startIcon={<RemoveIcon />}
disabled={editItem.s}

View File

@@ -4,7 +4,6 @@ import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done';
import WarningIcon from '@mui/icons-material/Warning';
import {
Box,
Button,
Dialog,
DialogActions,
@@ -111,11 +110,9 @@ const SensorsTemperatureDialog = ({
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
<DialogTitle>{dialogTitle}</DialogTitle>
<DialogContent dividers>
<Box color="warning.main" mb={2}>
<Typography variant="body2">
{LL.ID_OF(LL.SENSOR(0))}: {editItem.id}
</Typography>
</Box>
<Typography sx={{ mb: 2 }} color="warning" variant="body2">
{LL.ID_OF(LL.SENSOR(0))}: {editItem.id}
</Typography>
<Grid container spacing={2}>
<Grid>
<ValidatedTextField
@@ -142,7 +139,7 @@ const SensorsTemperatureDialog = ({
</Grid>
{editItem.s && (
<Grid>
<Typography mt={1} color="warning.main" variant="body2">
<Typography sx={{ mt: 1 }} color="warning" variant="body2">
<WarningIcon
fontSize="small"
sx={{ mr: 1, verticalAlign: 'middle' }}

View File

@@ -41,8 +41,12 @@ const UserProfileComponent = () => {
/>
</ListItem>
</List>
<Box mt={2} mb={2} display="flex" alignItems="center">
<Typography mr={2} variant="body1" align="center">
<Box sx={{ mt: 2, mb: 2, display: 'flex', alignItems: 'center' }}>
<Typography
sx={{ mr: 2, textAlign: 'center' }}
color="warning"
variant="body1"
>
{LL.LANGUAGE()}:
</Typography>
<LanguageSelector />

View File

@@ -771,7 +771,7 @@ const ApplicationSettings = () => {
label={LL.REMOTE_TIMEOUT_EN()}
/>
{data.remote_timeout_en && (
<Box mt={2}>
<Box sx={{ mt: 2 }}>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
name="remote_timeout"

View File

@@ -1,12 +1,23 @@
import { useCallback, useMemo, useState } from 'react';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
import DownloadIcon from '@mui/icons-material/GetApp';
import { Box, Button, Grid, Typography } from '@mui/material';
import WarningIcon from '@mui/icons-material/Warning';
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
Typography
} from '@mui/material';
import * as SystemApi from 'api/system';
import { API, callAction } from 'api/app';
import { dialogStyle } from '@/CustomTheme';
import { useRequest } from 'alova/client';
import type { APIcall } from 'app/main/types';
import SystemMonitor from 'app/status/SystemMonitor';
@@ -19,16 +30,11 @@ import {
import { useI18nContext } from 'i18n/i18n-react';
import { saveFile } from 'utils';
interface DownloadButton {
key: string;
type: string;
label: string | number;
isGridButton: boolean;
}
const DownloadUpload = () => {
const { LL } = useI18nContext();
const [confirmBackup, setConfirmBackup] = useState<boolean>(false);
const [restarting, setRestarting] = useState<boolean>(false);
const { send: sendExportData } = useRequest(
@@ -63,40 +69,44 @@ const DownloadUpload = () => {
useLayoutTitle(LL.DOWNLOAD_UPLOAD());
const downloadButtons: DownloadButton[] = useMemo(
() => [
{
key: 'settings',
type: 'settings',
label: LL.SETTINGS_OF(LL.APPLICATION()),
isGridButton: true
},
{
key: 'customizations',
type: 'customizations',
label: LL.CUSTOMIZATIONS(),
isGridButton: true
},
{
key: 'entities',
type: 'entities',
label: LL.CUSTOM_ENTITIES(0),
isGridButton: true
},
{
key: 'schedule',
type: 'schedule',
label: LL.SCHEDULE(0),
isGridButton: true
},
{
key: 'allvalues',
type: 'allvalues',
label: LL.ALLVALUES(),
isGridButton: false
}
],
[LL]
const handleCloseBackupDialog = useCallback(() => {
setConfirmBackup(false);
}, []);
const renderBackupDialog = useMemo(
() => (
<Dialog
sx={dialogStyle}
open={confirmBackup}
onClose={handleCloseBackupDialog}
>
<DialogTitle>{LL.DOWNLOAD_SYSTEM_BACKUP()}</DialogTitle>
<DialogContent dividers>
<WarningIcon color="warning" sx={{ fontSize: 18 }} />
&nbsp;
{LL.WARNING_SYSTEM_BACKUP()}
</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={handleCloseBackupDialog}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
onClick={() => handleDownload('systembackup')()}
color="primary"
>
{LL.DOWNLOAD(0)}
</Button>
</DialogActions>
</Dialog>
),
[confirmBackup, handleCloseBackupDialog, LL]
);
const handleDownload = useCallback(
@@ -118,58 +128,57 @@ const DownloadUpload = () => {
);
}
const gridButtons = downloadButtons.filter((btn) => btn.isGridButton);
const standaloneButton = downloadButtons.find((btn) => !btn.isGridButton);
return (
<SectionContent>
{renderBackupDialog}
<Typography sx={{ pb: 2 }} variant="h6" color="primary">
{LL.DOWNLOAD(0)}
</Typography>
<Typography mb={1} variant="body1" color="warning">
{LL.DOWNLOAD_SETTINGS_TEXT()}.
</Typography>
<Grid container spacing={2}>
{gridButtons.map((button) => (
<Grid key={button.key}>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={handleDownload(button.type)}
>
{button.label}
</Button>
</Grid>
))}
</Grid>
<Typography mt={2} mb={1} variant="body1" color="warning">
{LL.DOWNLOAD_SETTINGS_TEXT2()}.
</Typography>
{standaloneButton && (
<Grid
container
spacing={2}
sx={{
alignItems: 'center'
}}
>
<Typography variant="body1" color="warning">
{LL.DOWNLOAD_SETTINGS_TEXT()}:
</Typography>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={handleDownload(standaloneButton.type)}
onClick={() => setConfirmBackup(true)}
>
{standaloneButton.label}
{LL.DOWNLOAD_SYSTEM_BACKUP()}
</Button>
)}
</Grid>
<Grid container spacing={2} sx={{ mt: 2, alignItems: 'center' }}>
<Typography variant="body1" color="warning">
{LL.DOWNLOAD_SETTINGS_TEXT2()}:
</Typography>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={handleDownload('allvalues')}
>
{LL.ALLVALUES()}
</Button>
</Grid>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.UPLOAD()}
</Typography>
<Box color="warning.main" sx={{ pb: 2 }}>
<Typography variant="body1">{LL.UPLOAD_TEXT()}.</Typography>
</Box>
<Typography sx={{ pb: 2 }} color="warning" variant="body1">
{LL.UPLOAD_TEXT()}:
</Typography>
<SingleUpload text={LL.UPLOAD_DRAG()} doRestart={doRestart} />
<SingleUpload doRestart={doRestart} />
</SectionContent>
);
};

View File

@@ -129,7 +129,7 @@ const MqttSettings = () => {
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
<>
<Box display="flex" gap={2} mb={1}>
<Box sx={{ display: 'flex', gap: 2, mb: 1 }}>
<BlockFormControlLabel
control={
<Checkbox

View File

@@ -193,9 +193,9 @@ const NTPSettings = () => {
{timeZoneItems}
</ValidatedTextField>
<Box display="flex" flexWrap="wrap">
<Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
{!data.enabled && !dirtyFlags.length && (
<Box flexWrap="nowrap" whiteSpace="nowrap">
<Box sx={{ flexWrap: 'nowrap', whiteSpace: 'nowrap' }}>
<ButtonRow>
<Button
onClick={openSetTime}
@@ -259,9 +259,9 @@ const NTPSettings = () => {
<Dialog sx={dialogStyle} open={settingTime} onClose={handleCloseSetTime}>
<DialogTitle>{LL.SET_TIME(1)}</DialogTitle>
<DialogContent dividers>
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
<Typography variant="body2">{LL.SET_TIME_TEXT()}</Typography>
</Box>
<Typography color="warning" variant="body2">
{LL.SET_TIME_TEXT()}
</Typography>
<TextField
label={LL.LOCAL_TIME(0)}
type="datetime-local"

View File

@@ -156,11 +156,13 @@ const Settings = () => {
<Divider />
<Box
mt={2}
display="flex"
justifyContent="flex-end"
flexWrap="nowrap"
whiteSpace="nowrap"
sx={{
mt: 2,
display: 'flex',
justifyContent: 'flex-end',
flexWrap: 'nowrap',
whiteSpace: 'nowrap'
}}
>
<Button
startIcon={<SettingsBackupRestoreIcon />}

View File

@@ -54,19 +54,27 @@ const GenerateToken = ({ username, onClose }: GenerateTokenProps) => {
<DialogContent dividers>
{token ? (
<>
<MessageBox message={LL.ACCESS_TOKEN_TEXT()} level="info" my={2} />
<Box mt={2} mb={2}>
<MessageBox
message={LL.ACCESS_TOKEN_TEXT()}
level="info"
sx={{ mt: 2, mb: 2 }}
/>
<Box sx={{ mt: 2, mb: 2 }}>
<TextField
label="Token"
multiline
value={token.token}
fullWidth
contentEditable={false}
slotProps={{
input: {
readOnly: true
}
}}
/>
</Box>
</>
) : (
<Box m={4} textAlign="center">
<Box sx={{ m: 4, textAlign: 'center' }}>
<LinearProgress />
<Typography variant="h6">{LL.GENERATING_TOKEN()}&hellip;</Typography>
</Box>

View File

@@ -240,12 +240,16 @@ const ManageUsers = () => {
</Table>
{noAdminConfigured() && (
<MessageBox level="warning" message={LL.USER_WARNING()} my={2} />
<MessageBox
level="warning"
message={LL.USER_WARNING()}
sx={{ mt: 2, mb: 2 }}
/>
)}
<Box display="flex" flexWrap="wrap">
<Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
{changed !== 0 && (
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<Box sx={{ flexGrow: 1, '& button': { mt: 2 } }}>
<ButtonRow>
<Button
startIcon={<CancelIcon />}
@@ -270,7 +274,7 @@ const ManageUsers = () => {
</ButtonRow>
</Box>
)}
<Box flexWrap="nowrap" whiteSpace="nowrap">
<Box sx={{ flexWrap: 'nowrap', whiteSpace: 'nowrap' }}>
<ButtonRow>
<Button
startIcon={<PersonAddIcon />}

View File

@@ -38,8 +38,6 @@ import type { LogEntry, LogSettings } from 'types';
import { LogLevel } from 'types';
import { updateValueDirty, useRest } from 'utils';
const MAX_LOG_ENTRIES = 1000; // Limit log entries to prevent memory issues
const TextColors: Record<LogLevel, string> = {
[LogLevel.ERROR]: '#ff0000', // red
[LogLevel.WARNING]: '#ff0000', // red
@@ -200,10 +198,6 @@ const SystemLog = () => {
}
}
const newLog = [...log, logentry];
// Limit log entries to prevent memory issues - only slice when necessary
if (newLog.length > MAX_LOG_ENTRIES) {
return newLog.slice(-MAX_LOG_ENTRIES);
}
return newLog;
});
}, []);
@@ -272,7 +266,7 @@ const SystemLog = () => {
return (
<>
<Grid container spacing={2} alignItems="center">
<Grid container spacing={2} sx={{ alignItems: 'center' }}>
<Grid>
<TextField
name="level"
@@ -308,6 +302,8 @@ const SystemLog = () => {
<MenuItem value={50}>50</MenuItem>
<MenuItem value={75}>75</MenuItem>
<MenuItem value={100}>100</MenuItem>
<MenuItem value={500}>500</MenuItem>
<MenuItem value={1000}>1000</MenuItem>
</TextField>
</Grid>
)}

View File

@@ -60,18 +60,16 @@ const SystemMonitor = () => {
const { statusMessage, isUploading, progressValue } = useMemo(() => {
const status = data?.status;
let message = '';
if (status && status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING) {
message = LL.WAIT_FIRMWARE();
} else if (status === SystemStatusCodes.SYSTEM_STATUS_PENDING_RESTART) {
message = LL.APPLICATION_RESTARTING();
} else if (status === SystemStatusCodes.SYSTEM_STATUS_NORMAL) {
message = LL.RESTARTING_PRE();
} else if (status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD) {
message = 'Upload Failed';
} else {
message = LL.RESTARTING_POST();
}
const message =
status && status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING
? LL.WAIT_FIRMWARE()
: status === SystemStatusCodes.SYSTEM_STATUS_PENDING_RESTART
? LL.APPLICATION_RESTARTING()
: status === SystemStatusCodes.SYSTEM_STATUS_NORMAL
? LL.RESTARTING_PRE()
: status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD
? 'Upload Failed'
: LL.RESTARTING_POST();
const uploading =
status !== undefined && status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING;
@@ -120,17 +118,15 @@ const SystemMonitor = () => {
p: 3
}}
>
<Box display="flex" alignItems="center" flexDirection="column">
<Box sx={{ display: 'flex', alignItems: 'center', flexDirection: 'column' }}>
<img
src="/app/icon.png"
alt="EMS-ESP"
style={{ width: '40px', height: '40px', marginBottom: '16px' }}
/>
<Typography
color="secondary"
sx={{ color: 'secondary', fontWeight: 400, textAlign: 'center' }}
variant="h6"
fontWeight={400}
textAlign="center"
>
{statusMessage}
</Typography>
@@ -150,11 +146,14 @@ const SystemMonitor = () => {
</MessageBox>
) : (
<>
<Typography mt={2} variant="h6" fontWeight={400} textAlign="center">
<Typography
sx={{ mt: 2, fontWeight: 400, textAlign: 'center' }}
variant="h6"
>
{LL.PLEASE_WAIT()}&hellip;
</Typography>
{isUploading && (
<Box width="100%" pl={2} pr={2} py={2}>
<Box sx={{ width: '100%', pl: 2, pr: 2, py: 2 }}>
<LinearProgressWithLabel value={progressValue} />
</Box>
)}

View File

@@ -7,6 +7,7 @@ import {
useRef,
useState
} from 'react';
import { Link } from 'react-router';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
@@ -24,7 +25,6 @@ import {
DialogTitle,
Grid,
IconButton,
Link,
Table,
TableBody,
TableCell,
@@ -274,6 +274,7 @@ const InstallDialog = memo(
fetchDevVersion,
latestVersion,
latestDevVersion,
upgradeImportantMessageType,
downloadOnly,
platform,
LL,
@@ -284,6 +285,7 @@ const InstallDialog = memo(
fetchDevVersion: boolean;
latestVersion?: VersionInfo;
latestDevVersion?: VersionInfo;
upgradeImportantMessageType: number;
downloadOnly: boolean;
platform: string;
LL: TranslationFunctions;
@@ -307,12 +309,33 @@ const InstallDialog = memo(
{`${LL.INSTALL()} ${fetchDevVersion ? LL.DEVELOPMENT() : LL.STABLE()} Firmware`}
</DialogTitle>
<DialogContent dividers>
<Typography mb={2}>
<Typography sx={{ mb: 2 }}>
{LL.INSTALL_VERSION(
downloadOnly ? LL.DOWNLOAD(1) : LL.INSTALL(),
fetchDevVersion ? latestDevVersion?.name : latestVersion?.name
)}
</Typography>
{upgradeImportantMessageType === 2 && LL.UPGRADE_IMPORTANT_MESSAGES_2()}
{upgradeImportantMessageType === 1 && (
<>
{LL.UPGRADE_IMPORTANT_MESSAGES_1()}
<Typography sx={{ mt: 2 }}>
<Link to="/settings/downloadUpload" style={{ color: 'lightblue' }}>
{LL.DOWNLOAD_SYSTEM_BACKUP()}
</Link>
</Typography>
</>
)}
<Typography sx={{ mt: 2 }}>
<Link
to="https://docs.emsesp.org/FAQ#upgrading-the-firmware"
target="_blank"
rel="noreferrer"
style={{ color: 'lightblue' }}
>
{LL.ONLINE_HELP()}
</Link>
</Typography>
</DialogContent>
<DialogActions>
<Button
@@ -329,7 +352,12 @@ const InstallDialog = memo(
onClick={onClose}
color="primary"
>
<Link underline="none" target="_blank" href={binURL} color="primary">
<Link
to={binURL}
target="_blank"
rel="noreferrer"
style={{ color: 'lightblue', textDecoration: 'none' }}
>
{LL.DOWNLOAD(0)}
</Link>
</Button>
@@ -371,7 +399,9 @@ const InstallPartitionDialog = memo(
{LL.INSTALL()} {LL.STORED_VERSIONS()}
</DialogTitle>
<DialogContent dividers>
<Typography mb={2}>{LL.INSTALL_VERSION(LL.INSTALL(), version)}</Typography>
<Typography sx={{ mb: 2 }}>
{LL.INSTALL_VERSION(LL.INSTALL(), version)}
</Typography>
</DialogContent>
<DialogActions>
<Button
@@ -467,6 +497,26 @@ const Version = () => {
immediate: false
});
const [upgradeImportantMessageType, setUpgradeImportantMessageType] =
useState<number>(0);
const { send: checkUpgradeImportantMessages } = useRequest(
(version: string) =>
callAction({ action: 'upgradeImportantMessages', param: version }),
{
immediate: false
}
)
.onSuccess((event) => {
const upgradeImportantMessageType_n = (
event.data as { upgradeImportantMessageType: number }
).upgradeImportantMessageType;
setUpgradeImportantMessageType(upgradeImportantMessageType_n);
})
.onError((error) => {
toast.error(String(error.error?.message || 'An error occurred'));
});
// Memoized values
const platform = useMemo(() => (data ? getPlatform(data) : ''), [data]);
@@ -532,10 +582,16 @@ const Version = () => {
[]
);
const showFirmwareDialog = useCallback((useDevVersion: boolean) => {
setFetchDevVersion(useDevVersion);
setOpenInstallDialog(true);
}, []);
const showFirmwareDialog = useCallback(
(useDevVersion: boolean) => {
setFetchDevVersion(useDevVersion);
void checkUpgradeImportantMessages(
useDevVersion ? latestDevVersion?.name : latestVersion?.name
);
setOpenInstallDialog(true);
},
[latestDevVersion, latestVersion, fetchDevVersion]
);
const closeInstallDialog = useCallback(() => {
setOpenInstallDialog(false);
@@ -637,8 +693,8 @@ const Version = () => {
return (
<>
<Box p={2} border="1px solid grey" borderRadius={2}>
<Typography mb={1} variant="h6" color="primary">
<Box sx={{ p: 2, border: '1px solid #565656', borderRadius: 2 }}>
<Typography sx={{ mb: 1 }} variant="h6" color="primary">
{LL.THIS_VERSION()}
</Typography>
@@ -703,7 +759,7 @@ const Version = () => {
{internetLive ? (
<>
<Typography mt={4} mb={1} variant="h6" color="primary">
<Typography sx={{ mt: 4, mb: 1 }} variant="h6" color="primary">
{LL.AVAILABLE_VERSION()}
</Typography>
@@ -725,7 +781,7 @@ const Version = () => {
</Grid>
<Grid size={{ xs: 8, md: 10 }}>
{otherPartitions.map((partition) => (
<Typography key={partition.partition} mb={1}>
<Typography key={partition.partition} sx={{ mb: 1 }}>
{partition.version}
<IconButton
onClick={() =>
@@ -791,7 +847,7 @@ const Version = () => {
</Grid>
</>
) : (
<Typography mt={2} color="warning">
<Typography sx={{ mt: 2 }} color="warning">
<WarningIcon color="warning" sx={{ verticalAlign: 'middle', mr: 2 }} />
{LL.INTERNET_CONNECTION_REQUIRED()}
</Typography>
@@ -815,6 +871,7 @@ const Version = () => {
fetchDevVersion={fetchDevVersion}
latestVersion={latestVersion}
latestDevVersion={latestDevVersion}
upgradeImportantMessageType={upgradeImportantMessageType}
downloadOnly={downloadOnly}
platform={platform}
LL={LL}
@@ -832,7 +889,7 @@ const Version = () => {
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
{LL.UPLOAD()}
</Typography>
<SingleUpload text={LL.UPLOAD_DROP_TEXT()} doRestart={doRestart} />
<SingleUpload doRestart={doRestart} />
</>
)}
</Box>

View File

@@ -53,12 +53,16 @@ const MessageBox: FC<PropsWithChildren<MessageBoxProps>> = ({
return (
<Box
p={2}
display="flex"
alignItems="center"
borderRadius={1}
sx={{ backgroundColor, color: 'white', ...sx }}
{...rest}
sx={{
display: 'flex',
alignItems: 'center',
borderRadius: 1,
backgroundColor,
color: 'white',
p: 2,
...sx
}}
>
<Icon />
{(message || children) && (

View File

@@ -29,7 +29,7 @@ const LayoutDrawerComponent = ({ mobileOpen, onClose }: LayoutDrawerProps) => {
() => (
<>
<Toolbar disableGutters>
<Box display="flex" alignItems="center" px={2}>
<Box sx={{ display: 'flex', alignItems: 'center', p: 2 }}>
<LayoutDrawerLogo src="/app/icon.png" alt={PROJECT_NAME} />
<Typography variant="h6">{PROJECT_NAME}</Typography>
</Box>

View File

@@ -51,9 +51,7 @@ const LayoutMenuComponent = () => {
sx={{ my: 0 }}
slotProps={{
primary: {
fontWeight: '600',
mb: '2px',
color: 'lightblue'
sx: { fontWeight: 600, mb: '2px', color: 'lightblue' }
}
}}
/>

View File

@@ -32,8 +32,16 @@ const FormLoaderComponent = ({ errorMessage, onRetry }: FormLoaderProps) => {
);
}
return (
<Box m={2} py={2} display="flex" alignItems="center" flexDirection="column">
<Box py={2}>
<Box
sx={{
m: 2,
py: 2,
display: 'flex',
alignItems: 'center',
flexDirection: 'column'
}}
>
<Box sx={{ p: 2 }}>
<CircularProgress size={100} />
</Box>
</Box>

View File

@@ -15,12 +15,14 @@ const circularProgressStyles: SxProps<Theme> = (theme: Theme) => ({
const LoadingSpinner = ({ height = '100%' }: LoadingSpinnerProps) => {
return (
<Box
display="flex"
alignItems="center"
justifyContent="center"
flexDirection="column"
padding={2}
height={height}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
padding: 2,
height
}}
>
<CircularProgress sx={circularProgressStyles} size={100} />
</Box>

View File

@@ -1,4 +1,4 @@
// Code inspired by Prince Azubuike from https://medium.com/@dprincecoder/creating-a-drag-and-drop-file-upload-component-in-react-a-step-by-step-guide-4d93b6cc21e0
// drag/drop code inspired by Prince Azubuike from https://medium.com/@dprincecoder/creating-a-drag-and-drop-file-upload-component-in-react-a-step-by-step-guide-4d93b6cc21e0
import {
type ChangeEvent,
type DragEvent,
@@ -6,12 +6,28 @@ import {
useRef,
useState
} from 'react';
import { Link } from 'react-router';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import UploadIcon from '@mui/icons-material/Upload';
import { Box, Button, Typography, styled } from '@mui/material';
import WarningIcon from '@mui/icons-material/Warning';
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Typography,
styled
} from '@mui/material';
import { callAction } from 'api/app';
import { dialogStyle } from '@/CustomTheme';
import { useRequest } from 'alova/client';
import { useI18nContext } from 'i18n/i18n-react';
const DocumentUploader = styled(Box)<{ active?: boolean }>(({ theme, active }) => ({
@@ -58,6 +74,29 @@ const DragNdrop = ({ text, onFileSelected }: DragNdropProps) => {
const [dragged, setDragged] = useState(false);
const inputRef = useRef<HTMLInputElement | null>(null);
const { LL } = useI18nContext();
const [showUpgradeDialog, setShowUpgradeDialog] = useState(false);
const [upgradeImportantMessageType, setUpgradeImportantMessageType] =
useState<number>(0);
const { send: checkUpgradeImportantMessages } = useRequest(
(version: string) =>
callAction({ action: 'upgradeImportantMessages', param: version }),
{
immediate: false
}
)
.onSuccess((event) => {
const upgradeImportantMessageType_n = (
event.data as { upgradeImportantMessageType: number }
).upgradeImportantMessageType;
setUpgradeImportantMessageType(upgradeImportantMessageType_n);
if (upgradeImportantMessageType_n === 0) {
onFileSelected(file);
}
})
.onError((error) => {
toast.error(String(error.error?.message || 'An error occurred'));
});
const checkFileExtension = (file: File) => {
const validExtensions = ['.json', '.bin', '.md5'];
@@ -97,9 +136,8 @@ const DragNdrop = ({ text, onFileSelected }: DragNdropProps) => {
const handleUploadClick = (event: MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
if (file) {
onFileSelected(file);
}
void checkUpgradeImportantMessages(file?.name || '');
setShowUpgradeDialog(true);
};
const handleBrowseClick = () => {
@@ -158,6 +196,67 @@ const DragNdrop = ({ text, onFileSelected }: DragNdropProps) => {
{LL.UPLOAD()}
</Button>
</Box>
{showUpgradeDialog && upgradeImportantMessageType > 0 && (
<Dialog
sx={dialogStyle}
open={showUpgradeDialog}
onClose={() => setShowUpgradeDialog(false)}
>
<DialogTitle>
<WarningIcon
color="warning"
sx={{ fontSize: 18, verticalAlign: 'middle' }}
/>
&nbsp;&nbsp;
{LL.UPGRADE_IMPORTANT_MESSAGES()}
</DialogTitle>
<DialogContent dividers>
{upgradeImportantMessageType === 2 &&
LL.UPGRADE_IMPORTANT_MESSAGES_2()}
{upgradeImportantMessageType === 1 && (
<>
{LL.UPGRADE_IMPORTANT_MESSAGES_1()}
<Typography sx={{ mt: 2 }}>
<Link
to="/settings/downloadUpload"
style={{ color: 'lightblue' }}
>
{LL.DOWNLOAD_SYSTEM_BACKUP()}
</Link>
</Typography>
</>
)}{' '}
<Typography sx={{ mt: 2 }}>
<Link
to="https://docs.emsesp.org/FAQ#upgrading-the-firmware"
target="_blank"
rel="noreferrer"
style={{ color: 'lightblue' }}
>
{LL.ONLINE_HELP()}
</Link>
</Typography>
</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => setShowUpgradeDialog(false)}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<UploadIcon />}
variant="outlined"
onClick={() => onFileSelected(file)}
color="primary"
>
{LL.UPLOAD()}
</Button>
</DialogActions>
</Dialog>
)}
</>
)}
</DocumentUploader>

View File

@@ -13,11 +13,10 @@ import DragNdrop from './DragNdrop';
import { LinearProgressWithLabel } from './LinearProgressWithLabel';
interface SingleUploadProps {
text: string;
doRestart: () => void;
}
const SingleUpload = ({ text, doRestart }: SingleUploadProps) => {
const SingleUpload = ({ doRestart }: SingleUploadProps) => {
const [md5, setMd5] = useState<string>();
const [file, setFile] = useState<File>();
const { LL } = useI18nContext();
@@ -58,7 +57,7 @@ const SingleUpload = ({ text, doRestart }: SingleUploadProps) => {
<>
{isUploading ? (
<>
<Box width="100%" pl={2} pr={2}>
<Box sx={{ width: '100%', pl: 2, pr: 2 }}>
<LinearProgressWithLabel
value={
progress.total === 0 || progress.loaded === 0
@@ -81,11 +80,11 @@ const SingleUpload = ({ text, doRestart }: SingleUploadProps) => {
</Button>
</>
) : (
<DragNdrop text={text} onFileSelected={setFile} />
<DragNdrop text={LL.UPLOAD_DROP_TEXT()} onFileSelected={setFile} />
)}
{md5 && (
<Box mt={2}>
<Box sx={{ mt: 2 }}>
<Typography variant="body2">{'MD5: ' + md5}</Typography>
</Box>
)}

View File

@@ -186,7 +186,8 @@ const cz: Translation = {
BUFFER_SIZE: 'Maximální velikost vyrovnávací paměti',
COMPACT: 'Kompaktní',
DOWNLOAD_SETTINGS_TEXT: 'Vytvořte zálohu svého nastavení a konfigurace',
DOWNLOAD_SETTINGS_TEXT2: 'Exportovat všechna data',
DOWNLOAD_SETTINGS_TEXT2: 'Exportovat všechny hodnoty',
DOWNLOAD_SYSTEM_BACKUP: 'Systémová záloha',
UPLOAD_TEXT: 'Nahrajte nový soubor firmwaru (.bin) nebo záložní soubor (.json)',
UPLOAD_DROP_TEXT: 'Přetáhněte soubor sem nebo klikněte pro výběr',
ERROR: 'Neočekávaná chyba, zkuste to prosím znovu',
@@ -349,15 +350,22 @@ const cz: Translation = {
NO_DATA_1: 'Nebyly nalezeny žádné oblíbené entity. Použijte modul',
NO_DATA_2: 'pro jejich výběr.',
NO_DATA_3: 'Pro zobrazení všech dostupných entit navštivte stránku',
NO_GPIO:'Nebylo nalezeno žádné volné GPIO',
THIS_VERSION: 'Tato verze',
PLATFORM: 'Platforma',
RELEASE_TYPE: 'Typ sestavení',
INTERNET_CONNECTION_REQUIRED: 'Pro automatickou kontrolu a instalaci aktualizací je třeba internetové připojení',
SWITCH_RELEASE_TYPE: 'Přepnout na {0} verzi',
FIRMWARE_VERSION_INFO: 'Informace o verzi firmwaru',
NO_DATA: 'Žádná data',
NO_DATA: 'žádné údaje',
USER_PROFILE: 'Uživatelský profil',
STORED_VERSIONS: 'Uložené verze'
STORED_VERSIONS: 'Uložené verze',
ONLINE_HELP: 'online nápověda',
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizovat důležité zprávy',
UPGRADE_IMPORTANT_MESSAGES_1: 'Tato aktualizace vyžaduje obnovení továrního nastavení. Ujistěte se, že jste vytvořili zálohu své konfigurace a nastavení před pokračováním a nahrajte ji po instalaci nové verze.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujete se na novou hlavní verzi. Ujistěte se, že jste přečetli ChangeLog pro jakékoliv závažné změny.',
WARNING_SYSTEM_BACKUP: 'Toto vytvoří zálohu vašich celých systémových konfigurací a nastavení. Všechna hesla budou v zálohovém souboru čitelná. Buďte opatrní při sdílení! Opravdu chcete pokračovat?'
};
export default cz;

View File

@@ -186,7 +186,8 @@ const de: Translation = {
BUFFER_SIZE: 'Max. Puffergröße',
COMPACT: 'Kompakte Darstellung',
DOWNLOAD_SETTINGS_TEXT: 'Erstellen Sie eine Sicherung Ihrer Konfigurationen und Einstellungen',
DOWNLOAD_SETTINGS_TEXT2: 'Exportiere alle Daten',
DOWNLOAD_SETTINGS_TEXT2: 'Exportiere alle Werte',
DOWNLOAD_SYSTEM_BACKUP: 'System Sicherung',
UPLOAD_TEXT: 'Laden Sie eine neue Firmware-Datei (.bin) oder eine Sicherungsdatei (.json) hoch',
UPLOAD_DROP_TEXT: 'Legen Sie eine Firmware-Datei (.bin) ab oder klicken Sie hier',
ERROR: 'Unerwarteter Fehler, bitte versuchen Sie es erneut.',
@@ -349,6 +350,7 @@ const de: Translation = {
NO_DATA_1: 'Keine favorisierten EMS-Entitäten gefunden! Verwenden Sie das Modul',
NO_DATA_2: ', um sie zu markieren.',
NO_DATA_3: 'Um alle verfügbaren Entitäten anzuzeigen, gehen Sie zu',
NO_GPIO:'Keine freien GPIO gefunden',
THIS_VERSION: 'Diese Version',
PLATFORM: 'Plattform',
RELEASE_TYPE: 'Release Typ',
@@ -357,7 +359,13 @@ const de: Translation = {
FIRMWARE_VERSION_INFO: 'Firmware-Versionsinformation',
NO_DATA: 'Keine Daten',
USER_PROFILE: 'Benutzerprofil',
STORED_VERSIONS: 'Gespeicherte Versionen'
STORED_VERSIONS: 'Gespeicherte Versionen',
ONLINE_HELP: 'Online-Hilfe',
UPGRADE_IMPORTANT_MESSAGES: 'Wichtige Nachrichten aktualisieren',
UPGRADE_IMPORTANT_MESSAGES_1: 'Diese Aktualisierung erfordert eine Werkseinstellung. Stellen Sie sicher, dass Sie eine Sicherung Ihrer Konfiguration und Einstellungen vor dem Fortfahren erstellt haben und diese nach der Installation der neuen Version hochladen.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Sie aktualisieren auf eine neue Hauptversion. Stellen Sie sicher, dass Sie den ChangeLog für alle wichtigen Änderungen gelesen haben.',
WARNING_SYSTEM_BACKUP: 'Dies wird eine Sicherung Ihrer vollständigen Systemkonfiguration und -einstellungen erstellen. Alle Passwörter werden im Sicherungsdatei lesbar sein. Seien Sie vorsichtig beim Teilen! Möchten Sie fortfahren?'
};
export default de;

View File

@@ -186,7 +186,8 @@ const en: Translation = {
BUFFER_SIZE: 'Max Buffer Size',
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
DOWNLOAD_SETTINGS_TEXT2: 'Export all data',
DOWNLOAD_SETTINGS_TEXT2: 'Export all values',
DOWNLOAD_SYSTEM_BACKUP: 'System Backup',
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
ERROR: 'Unexpected Error, please try again',
@@ -349,6 +350,7 @@ const en: Translation = {
NO_DATA_1: 'No favorite EMS entities found yet. Use the',
NO_DATA_2: 'module to mark them.',
NO_DATA_3: 'To see all available entities go to',
NO_GPIO:'No available GPIO found',
THIS_VERSION: 'This Version',
PLATFORM: 'Platform',
RELEASE_TYPE: 'Release Type',
@@ -357,7 +359,13 @@ const en: Translation = {
FIRMWARE_VERSION_INFO: 'Firmware Version Information',
NO_DATA: 'No data',
USER_PROFILE: 'User Profile',
STORED_VERSIONS: 'Stored Versions'
STORED_VERSIONS: 'Stored Versions',
ONLINE_HELP: 'online help',
UPGRADE_IMPORTANT_MESSAGES: 'Upgrade Important Messages',
UPGRADE_IMPORTANT_MESSAGES_1: 'This upgrade requires a factory reset. Make sure you have made a backup of your configuration and settings before continuing, and upload this after the new version is installed.',
UPGRADE_IMPORTANT_MESSAGES_2: 'You are upgrading to a new major version. Make sure you have read the ChangeLog for any breaking changes.',
WARNING_SYSTEM_BACKUP: 'This will create a backup of your full system configuration and settings. All passwords will be readable in the backup file. Be careful with sharing! Do you want to continue?'
};
export default en;

View File

@@ -186,7 +186,8 @@ const fr: Translation = {
BUFFER_SIZE: 'Max taille du buffer',
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Créer une sauvegarde de vos paramètres et configurations',
DOWNLOAD_SETTINGS_TEXT2: 'Exporter toutes les données',
DOWNLOAD_SETTINGS_TEXT2: 'Exporter toutes les valeurs',
DOWNLOAD_SYSTEM_BACKUP: 'Sauvegarde système',
UPLOAD_TEXT: 'Télécharger un nouveau fichier firmware (.bin) ou une sauvegarde (.json)',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
ERROR: 'Erreur inattendue, veuillez réessayer',
@@ -349,6 +350,7 @@ const fr: Translation = {
NO_DATA_1: 'Aucune entité EMS favorite trouvée. Utilisez le',
NO_DATA_2: 'module pour les marquer.',
NO_DATA_3: 'Pour voir toutes les entités disponibles, aller à',
NO_GPIO:"Aucun GPIO disponible n'a été détecté",
THIS_VERSION: 'Cette version',
PLATFORM: 'Plateforme',
RELEASE_TYPE: 'Type de version',
@@ -357,7 +359,13 @@ const fr: Translation = {
FIRMWARE_VERSION_INFO: 'Informations sur la version du firmware',
NO_DATA: 'Aucune donnée',
USER_PROFILE: 'Profil utilisateur',
STORED_VERSIONS: 'Versions stockées'
STORED_VERSIONS: 'Versions stockées',
ONLINE_HELP: 'aide en ligne',
UPGRADE_IMPORTANT_MESSAGES: 'Mettre à jour les messages importants',
UPGRADE_IMPORTANT_MESSAGES_1: 'Cette mise à jour nécessite une réinitialisation de fabrique. Assurez-vous d\'avoir créé une sauvegarde de vos configurations et paramètres avant de continuer et de la charger après l\'installation de la nouvelle version.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Vous mettez à jour vers une nouvelle version majeure. Assurez-vous de lire le ChangeLog pour tout changement important.',
WARNING_SYSTEM_BACKUP: 'Cela créera une sauvegarde de votre configuration et paramètres complets. Tous les mots de passe seront lisibles dans le fichier de sauvegarde. Soyez prudent avec le partage ! Voulez-vous continuer ?'
};
export default fr;

View File

@@ -186,7 +186,8 @@ const it: Translation = {
BUFFER_SIZE: 'Max Buffer Size',
COMPACT: 'Compatto',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
DOWNLOAD_SETTINGS_TEXT2: 'Esporta tutti i dati',
DOWNLOAD_SETTINGS_TEXT2: 'Esporta tutti i valori',
DOWNLOAD_SYSTEM_BACKUP: 'Backup sistema',
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
ERROR: 'Errore Inaspettato, prego tenta ancora',
@@ -349,6 +350,7 @@ const it: Translation = {
NO_DATA_1: 'Nessuna entità EMS preferita trovata. Usa il',
NO_DATA_2: 'modulo per marcarle.',
NO_DATA_3: 'Per vedere tutte le entità disponibili vai a',
NO_GPIO:'Non è stato trovato alcun GPIO disponibile',
THIS_VERSION: 'Questa versione',
PLATFORM: 'Piattaforma',
RELEASE_TYPE: 'Tipo di rilascio',
@@ -357,7 +359,13 @@ const it: Translation = {
FIRMWARE_VERSION_INFO: 'Informazioni sulla versione del firmware',
NO_DATA: 'Nessun dato',
USER_PROFILE: 'Profilo utente',
STORED_VERSIONS: 'Versioni memorizzate'
STORED_VERSIONS: 'Versioni memorizzate',
ONLINE_HELP: 'aiuto online',
UPGRADE_IMPORTANT_MESSAGES: 'Aggiorna Messaggi Importanti',
UPGRADE_IMPORTANT_MESSAGES_1: 'Questa aggiornamento richiede un ripristino di fabbrica. Assicurati di aver creato un backup delle tue configurazioni e impostazioni prima di continuare e di caricarlo dopo l\'installazione della nuova versione.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Stai aggiornando a una nuova versione principale. Assicurati di aver letto il ChangeLog per qualsiasi cambiamento importante.',
WARNING_SYSTEM_BACKUP: 'Questo creerà un backup delle tue configurazioni e impostazioni complete. Tutte le password saranno leggibili nel file di backup. Sei sicuro di voler continuare?'
};
export default it;

View File

@@ -27,7 +27,7 @@ const nl: Translation = {
REFRESH: 'Ververs',
EXPORT: 'Export',
FAVORITES: "Favorieten",
DEVICE_DETAILS: 'Device Gegevens',
DEVICE_DETAILS: 'Apparaat Gegevens',
ID_OF: '{0} ID',
DEVICE: 'Apparaat',
PRODUCT: 'Product',
@@ -65,7 +65,7 @@ const nl: Translation = {
TEMP_SENSOR: 'Temperatuur sensor',
TEMP_SENSORS: 'Temperatuur Sensoren',
WRITE_CMD_SENT: 'Schrijf commando gestuurd',
EMS_BUS_WARNING: 'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden dan loop de instellingen na en in het bijzonder het apparaat type profiel na.',
EMS_BUS_WARNING: 'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden loop dan de instellingen na en in het bijzonder het apparaat type profiel.',
EMS_BUS_SCANNING: 'Scannen naar EMS apparaten...',
CONNECTED: 'Verbonden',
TX_ISSUES: 'Tx bus probleem. Probeer een andere Tx verzendmodus',
@@ -75,7 +75,7 @@ const nl: Translation = {
EMS_DEVICE: 'EMS Apparaat',
SUCCESS: 'SUCCESS',
FAIL: 'MISLUKT',
QUALITY: 'QUALITEIT',
QUALITY: 'KWALITEIT',
SCAN: 'Scan',
STATUS_NAMES: [
'EMS Telegrammen ontvangen (Rx)',
@@ -120,7 +120,7 @@ const nl: Translation = {
ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)',
ENABLE_SHOWER_ALERT: 'Activeer Douchemelding',
TRIGGER_TIME: 'Trigger tijd',
COLD_SHOT_DURATION: 'Tijd Shot koud water',
COLD_SHOT_DURATION: 'Lengte koud water puls',
FORMATTING_OPTIONS: 'Formatteringsopties',
BOOLEAN_FORMAT_DASHBOARD: 'Boolean formaat web',
BOOLEAN_FORMAT_API: 'Boolean formaat API/MQTT',
@@ -143,7 +143,7 @@ const nl: Translation = {
CUSTOMIZATIONS_FULL: 'Te veel entiteiten geselecteerd. Sla op in delen aub',
CUSTOMIZATIONS_SAVED: 'Custom aanpassingen opgeslagen',
CUSTOMIZATIONS_HELP_1: 'Selecteer een apparaat en pas de entiteiten aan door middel van de opties',
CUSTOMIZATIONS_HELP_2: 'Markeer as favoriet',
CUSTOMIZATIONS_HELP_2: 'Markeer als favoriet',
CUSTOMIZATIONS_HELP_3: 'Zet schrijfacties uit',
CUSTOMIZATIONS_HELP_4: 'Uitsluiten van MQTT en API',
CUSTOMIZATIONS_HELP_5: 'verbergen voor apparaten',
@@ -186,12 +186,13 @@ const nl: Translation = {
BUFFER_SIZE: 'Max buffer grootte',
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Maak een back-up van uw configuratie en instellingen',
DOWNLOAD_SETTINGS_TEXT2: 'Exporteer alle data',
DOWNLOAD_SETTINGS_TEXT2: 'Exporteer alle waarden',
DOWNLOAD_SYSTEM_BACKUP: 'Systeem Backup',
UPLOAD_TEXT: 'Upload een nieuw firmwarebestand (.bin) of een back-upbestand (.json)',
UPLOAD_DROP_TEXT: 'Sleep en firmware .bin bestand hierheen of klik hier',
ERROR: 'Onverwachte fout, probeer opnieuw',
TIME_SET: 'Tijd ingesteld',
MANAGE_USERS: 'Beheer Gebruikers',
MANAGE_USERS: 'Gebruikersbeheer',
IS_ADMIN: 'is Admin',
USER_WARNING: 'U dient tenminste 1 admin gebruiker te configureren',
ADD: 'Toevoegen',
@@ -200,7 +201,7 @@ const nl: Translation = {
GENERATING_TOKEN: 'Token aan het genereren',
USER: 'Gebruiker',
MODIFY: 'Aanpassen',
SU_TEXT: 'Het su (super user) wachtwoord wordt gebruikt om authorisatie tokens te signeren en ook om admin privileges te activeren in de console.',
SU_TEXT: 'Het su (super user) wachtwoord wordt gebruikt om authorisatie tokens te ondertekenen en ook om admin privileges te activeren in de console.',
NOT_ENABLED: 'Niet geactiveerd',
ERRORS_OF: '{0} Foutmeldingen',
DISCONNECT_REASON: 'Verbinding verbroken vanwege',
@@ -349,6 +350,7 @@ const nl: Translation = {
NO_DATA_1: 'Er zijn nog geen favoriete EMS-entiteiten gevonden. Gebruik de',
NO_DATA_2: 'module om ze te markeren.',
NO_DATA_3: 'Om alle beschikbare entiteiten te zien, ga naar',
NO_GPIO:'Er is geen beschikbare GPIO gevonden',
THIS_VERSION: 'Deze Versie',
PLATFORM: 'Platform',
RELEASE_TYPE: 'Release Typ',
@@ -357,7 +359,13 @@ const nl: Translation = {
FIRMWARE_VERSION_INFO: 'Informatie over firmwareversie',
NO_DATA: 'Geen data',
USER_PROFILE: 'Gebruikersprofiel',
STORED_VERSIONS: 'Opgeslagen versies'
STORED_VERSIONS: 'Opgeslagen versies',
ONLINE_HELP: 'online help',
UPGRADE_IMPORTANT_MESSAGES: 'Upgrade Belangrijke Berichten',
UPGRADE_IMPORTANT_MESSAGES_1: 'Deze upgrade vereist een fabrieksinstelling. Zorg ervoor dat u een back-up van uw configuratie en instellingen hebt gemaakt voordat u doorgaat en upload deze na de installatie van de nieuwe versie.',
UPGRADE_IMPORTANT_MESSAGES_2: 'U updatet naar een nieuwe grote versie. Zorg ervoor dat u de ChangeLog hebt gelezen voor alle brekende wijzigingen.',
WARNING_SYSTEM_BACKUP: 'Dit zal een back-up van uw volledige systeemconfiguratie en instellingen maken. Alle wachtwoorden zijn leesbaar in het back-upbestand. Wees voorzichtig bij delen! Wilt u doorgaan?'
};
export default nl;
export default nl;

View File

@@ -186,7 +186,8 @@ const no: Translation = {
BUFFER_SIZE: 'Max Buffer Størrelse',
COMPACT: 'Komprimere',
DOWNLOAD_SETTINGS_TEXT: 'Lag en sikkerhetskopi av dine konfigurasjon og innstillinger',
DOWNLOAD_SETTINGS_TEXT2: 'Eksporter alle data',
DOWNLOAD_SETTINGS_TEXT2: 'Eksporter alle verdier',
DOWNLOAD_SYSTEM_BACKUP: 'System Sikkerhetskopi',
UPLOAD_TEXT: 'Last opp en ny firmware fil (.bin) eller en sikkerhetskopi fil (.json)',
UPLOAD_DROP_TEXT: 'Dropp en firmware fil (.bin) eller klikk her',
ERROR: 'Ukjent feil, prøv igjen',
@@ -349,6 +350,7 @@ const no: Translation = {
NO_DATA_1: 'Ingen favoritte EMS enheter funnet enda. Bruk',
NO_DATA_2: 'modul for å markere dem.',
NO_DATA_3: 'For å se alle tilgjengelige enheter, gå til',
NO_GPIO:'Det ble ikke funnet noen tilgjengelige GPIO-porter',
THIS_VERSION: 'Denne versjonen',
PLATFORM: 'Plattform',
RELEASE_TYPE: 'Utgivelses type',
@@ -357,7 +359,13 @@ const no: Translation = {
FIRMWARE_VERSION_INFO: 'Informasjon om firmwareversjon',
NO_DATA: 'Ingen data',
USER_PROFILE: 'Brukerprofil',
STORED_VERSIONS: 'Lagret versjoner'
STORED_VERSIONS: 'Lagret versjoner',
ONLINE_HELP: 'online hjelp',
UPGRADE_IMPORTANT_MESSAGES: 'Oppdater viktige meldinger',
UPGRADE_IMPORTANT_MESSAGES_1: 'Denne oppdateringen krever en fabriksinstilling. Sørg for at du har laget en sikkerhetskopi av din konfigurasjon og innstillinger før du fortsetter, og last denne opp etter at den nye versjonen er installert.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Du oppdaterer til en ny hovedversjon. Sørg for at du har lest ChangeLog for eventuelle bruddende endringer.',
WARNING_SYSTEM_BACKUP: 'Dette vil lage en sikkerhetskopi av din fullstendige systemkonfigurasjon og innstillinger. Alle passord vil være lesbare i sikkerhetskopien. Vær forsiktig med deling! Vil du fortsette?'
};
export default no;

View File

@@ -186,7 +186,8 @@ const pl: BaseTranslation = {
BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)',
COMPACT: 'Kompaktowy',
DOWNLOAD_SETTINGS_TEXT: 'Utwórz kopię swoich ustawień i konfiguracji',
DOWNLOAD_SETTINGS_TEXT2: 'Eksportuj wszystkie dane',
DOWNLOAD_SETTINGS_TEXT2: 'Eksportuj wszystkie wartości',
DOWNLOAD_SYSTEM_BACKUP: 'Kopia zapasowa systemu',
UPLOAD_TEXT: 'Wgraj nowy plik firmware (.bin) lub kopię ustawień (.json)',
UPLOAD_DROP_TEXT: 'Upuść plik firmware .bin lub kliknij tutaj',
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
@@ -349,6 +350,7 @@ const pl: BaseTranslation = {
NO_DATA_1: 'Brak ulubionych encji EMS. Użyj',
NO_DATA_2: 'moduł do ich oznaczenia.',
NO_DATA_3: 'Aby zobaczyć wszystkie dostępne encje przejdź do',
NO_GPIO:'Nie znaleziono dostępnych pinów GPIO',
THIS_VERSION: 'Ta wersja',
PLATFORM: 'Platforma',
RELEASE_TYPE: 'Typ wydania',
@@ -357,7 +359,13 @@ const pl: BaseTranslation = {
FIRMWARE_VERSION_INFO: 'Informacje o wersji firmware',
NO_DATA: 'Brak danych',
USER_PROFILE: 'Profil użytkownika',
STORED_VERSIONS: 'Zapisane wersje'
STORED_VERSIONS: 'Zapisane wersje',
ONLINE_HELP: 'pomoc online',
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizuj ważne wiadomości',
UPGRADE_IMPORTANT_MESSAGES_1: 'Ta aktualizacja wymaga resetu fabrycznego. Upewnij się, że masz utworzoną kopię swoich ustawień i konfiguracji przed kontynuowaniem i przesuń ją po zainstalowaniu nowej wersji.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujesz się do nowej głównej wersji. Upewnij się, że przeczytałeś ChangeLog dla wszelkich istotnych zmian.',
WARNING_SYSTEM_BACKUP: 'To spowoduje utworzenie kopii zapasowej całej konfiguracji i ustawień systemu. Wszystkie hasła będą widoczne w pliku kopii zapasowej. Bądź ostrożny przy udostępnianiu! Chcesz kontynuować?'
};
export default pl;

View File

@@ -4,7 +4,7 @@ const sk: Translation = {
LANGUAGE: 'Jazyk',
RETRY: 'Opakovať',
LOADING: 'Načítanie',
IS_REQUIRED: '{0} je požadovaných',
IS_REQUIRED: '{0} je požadovaná',
SIGN_IN: 'Prihlásiť sa',
SIGN_OUT: 'Odhlásiť sa',
USERNAME: 'Užívateľské meno',
@@ -186,7 +186,8 @@ const sk: Translation = {
BUFFER_SIZE: 'Buffer-max. veľkosť',
COMPACT: 'Kompaktné',
DOWNLOAD_SETTINGS_TEXT: 'Vytvorte zálohu svojej konfigurácie a nastavení',
DOWNLOAD_SETTINGS_TEXT2: 'Exportovať všetky dáta',
DOWNLOAD_SETTINGS_TEXT2: 'Exportovať všetky hodnoty',
DOWNLOAD_SYSTEM_BACKUP: 'Systémová záloha',
UPLOAD_TEXT: 'Nahrajte nový súbor firmvéru (.bin) alebo súbor zálohy (.json)',
UPLOAD_DROP_TEXT: 'Presuňte súbor .bin firmvéru alebo kliknite sem',
ERROR: 'Neočakávaná chyba, prosím skúste to znova',
@@ -275,11 +276,11 @@ const sk: Translation = {
NETWORK_SUBNET: 'Maska podsiete',
NETWORK_DNS: 'DNS servery',
ADDRESS_OF: '{0} adresa',
ADMINISTRATOR: 'Administrator',
ADMINISTRATOR: 'Administrátor',
GUEST: 'Hosť',
NEW: 'Nová',
NEW: 'Novú',
NEW_NAME_OF: 'Nový názov {0}',
ENTITY: 'entita',
ENTITY: 'entitu',
MIN: 'min',
MAX: 'max',
BLOCK_NAVIGATE_1: 'Máte neuložené zmeny',
@@ -349,6 +350,7 @@ const sk: Translation = {
NO_DATA_1: 'Nenašli sa žiadne obľúbené entity EMS. Použite',
NO_DATA_2: 'modul na ich označenie.',
NO_DATA_3: 'Ak chcete zobraziť všetky dostupné entity, prejdite na',
NO_GPIO:'Nebol nájdený žiadny dostupný GPIO',
THIS_VERSION: 'Táto verzia',
PLATFORM: 'Platforma',
RELEASE_TYPE: 'Typ vydania',
@@ -357,7 +359,13 @@ const sk: Translation = {
FIRMWARE_VERSION_INFO: 'Informácie o verzii firmware',
NO_DATA: 'Žiadne dáta',
USER_PROFILE: 'Profil používateľa',
STORED_VERSIONS: 'Uložené verzie'
STORED_VERSIONS: 'Uložené verzie',
ONLINE_HELP: 'online pomoc',
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizovať dôležité správy',
UPGRADE_IMPORTANT_MESSAGES_1: 'Táto aktualizácia vyžaduje reštart základných nastavení. Uistite sa, že ste vytvorili zálohu svojich konfigurácií a nastavení pred pokračovaním a nahrajte ju po instalácii novej verzie.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujete sa na novú hlavnú verziu. Uistite sa, že ste prečítali ChangeLog pre akékoľvek dôležité zmeny.',
WARNING_SYSTEM_BACKUP: 'Toto vytvorí zálohu všetkých vašich celých systémových konfigurácií a nastavení. Všetky hesla budú čitateľné v zálohovom súbore. Buďte opatrní pri zdieľaní! Chcete pokračovať?'
};
export default sk;

View File

@@ -186,7 +186,8 @@ const sv: Translation = {
BUFFER_SIZE: 'Max bufferstorlek',
COMPACT: 'Komprimerad',
DOWNLOAD_SETTINGS_TEXT: 'Skapa en säkerhetskopia av din konfiguration och inställningar',
DOWNLOAD_SETTINGS_TEXT2: 'Exportera alla data',
DOWNLOAD_SETTINGS_TEXT2: 'Exportera alla värden',
DOWNLOAD_SYSTEM_BACKUP: 'System säkerhetskopia',
UPLOAD_TEXT: 'Ladda upp en ny firmwarefil (.bin) eller en säkerhetskopiafil (.json)',
UPLOAD_DROP_TEXT: 'Droppa en firmware .bin fil eller klicka här',
ERROR: 'Okänt fel, var god försök igen',
@@ -349,6 +350,7 @@ const sv: Translation = {
NO_DATA_1: 'Inga favorit EMS enheter hittade än. Använd',
NO_DATA_2: 'modul för att markera dem.',
NO_DATA_3: 'För att se alla tillgängliga enheter, gå till',
NO_GPIO:'Inga tillgängliga GPIO-portar hittades',
THIS_VERSION: 'Denna version',
PLATFORM: 'Plattform',
RELEASE_TYPE: 'Utgivelsestyp',
@@ -357,7 +359,13 @@ const sv: Translation = {
FIRMWARE_VERSION_INFO: 'Information om firmwareversion',
NO_DATA: 'Ingen data',
USER_PROFILE: 'Användarprofil',
STORED_VERSIONS: 'Lagrad versioner'
STORED_VERSIONS: 'Lagrad versioner',
ONLINE_HELP: 'online hjälp',
UPGRADE_IMPORTANT_MESSAGES: 'Uppdatera viktiga meddelanden',
UPGRADE_IMPORTANT_MESSAGES_1: 'Denna uppdatering kräver en fabriksåterställning. Se till att du har gjort en säkerhetskopia av din konfiguration och inställningar innan du fortsätter och ladda upp denna efter att den nya versionen är installerad.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Du uppdaterar till en ny huvudversion. Se till att du har läst ChangeLog för eventuella brkande ändringar.',
WARNING_SYSTEM_BACKUP: 'Detta kommer att skapa en säkerhetskopia av din fullständiga systemkonfiguration och inställningar. Alla lösenord kommer att vara läsbara i säkerhetskopien. Var försiktig med att dela! Vill du fortsätta?'
};
export default sv;

View File

@@ -186,7 +186,8 @@ const tr: Translation = {
BUFFER_SIZE: 'En fazla bellek boyutu',
COMPACT: 'Sıkışık',
DOWNLOAD_SETTINGS_TEXT: 'Yapılandırma ve ayarlarınızın yedekleme yapın',
DOWNLOAD_SETTINGS_TEXT2: 'Tüm verileri dışarı al',
DOWNLOAD_SETTINGS_TEXT2: 'Tüm değerleri dışarı al',
DOWNLOAD_SYSTEM_BACKUP: 'Sistem yedekleme',
UPLOAD_TEXT: 'Yeni bir firmware dosyası (.bin) veya yedek dosyası (.json) yükle',
UPLOAD_DROP_TEXT: 'Bir firmware .bin dosyası veya buraya tıklayın',
ERROR: 'Beklenemedik hata, lütfen tekrar deneyin.',
@@ -349,6 +350,7 @@ const tr: Translation = {
NO_DATA_1: 'Henüz bir favori EMS varlığı bulunamadı. Kullanın',
NO_DATA_2: 'modülünü kullanın.',
NO_DATA_3: 'Tüm kullanılabilir varlıkları görmek için git',
NO_GPIO:'Kullanılabilir GPIO bulunamadı',
THIS_VERSION: 'Bu Sürüm',
PLATFORM: 'Platforma',
RELEASE_TYPE: 'Sürüm Tipi',
@@ -357,7 +359,13 @@ const tr: Translation = {
FIRMWARE_VERSION_INFO: 'Firmware Sürüm Bilgisi',
NO_DATA: 'Hiçbir veri yok',
USER_PROFILE: 'Kullanıcı Profili',
STORED_VERSIONS: 'Kaydedilmiş Sürümler'
STORED_VERSIONS: 'Kaydedilmiş Sürümler',
ONLINE_HELP: 'online yardım',
UPGRADE_IMPORTANT_MESSAGES: 'Önemli Mesajları Güncelle',
UPGRADE_IMPORTANT_MESSAGES_1: 'Bu güncelleme továrnı ayarlarını gerektirir. Yapılandırmanızı ve ayarlarınızı önce yedekleyin ve ardından yeni sürüm yüklendikten sonra yükleyin.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Yeni bir büyük sürüme yükselteceksiniz. Değişiklikleri ChangeLogı okuduğunuzdan emin olun.',
WARNING_SYSTEM_BACKUP: 'Bu, sistem yapılandırmanızı ve ayarlarınızın bir yedeklemesi oluşturacaktır. Tüm şifreler yedekleme dosyasında okunabilir olacaktır. Paylaşırken dikkatli olun! Devam etmek istediğinize emin misiniz?'
};
export default tr;

View File

@@ -23,6 +23,8 @@ export const saveFile = (
}, 100);
} catch (error) {
console.error('Failed to save file:', error);
throw new Error(`Unable to save file: ${filename}${extension}`);
throw new Error(`Unable to save file: ${filename}${extension}`, {
cause: error
});
}
};

View File

@@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react';
const DEFAULT_DELAY = 3000;
const DEFAULT_DELAY = 5000;
/**
* Custom hook for setting up an interval with proper cleanup

View File

@@ -2,9 +2,8 @@ import preact from '@preact/preset-vite';
import fs from 'fs';
import path from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
import { Plugin, defineConfig } from 'vite';
import { Plugin, PluginOption, defineConfig } from 'vite';
import viteImagemin from 'vite-plugin-imagemin';
import viteTsconfigPaths from 'vite-tsconfig-paths';
import zlib from 'zlib';
// @ts-expect-error - mock server doesn't have type declarations
@@ -99,16 +98,31 @@ const createPreactPlugin = (devToolsEnabled: boolean) =>
prefreshEnabled: false
});
// Patch preact/compat to export stub React 19 APIs (use, useOptimistic) so that
// react-router v7 doesn't trigger IMPORT_IS_UNDEFINED warnings from Rolldown.
const preactCompatPatchPlugin = (): Plugin => ({
name: 'preact-compat-react19-patch',
transform(code, id) {
if (id.includes('preact') && id.includes('compat.module.js')) {
return {
code:
code +
'\nexport var use = undefined;\nexport var useOptimistic = undefined;\n',
map: null
};
}
return undefined;
}
});
// Common base plugins
const createBasePlugins = (
devToolsEnabled: boolean,
includeBundleReporter = true
) => {
const plugins = [
): PluginOption[] => {
const plugins: PluginOption[] = [
createPreactPlugin(devToolsEnabled),
viteTsconfigPaths({
projects: ['./tsconfig.json']
})
preactCompatPatchPlugin()
];
if (includeBundleReporter) {
plugins.push(bundleSizeReporter());
@@ -234,7 +248,8 @@ export default defineConfig(
plugins: [...createBasePlugins(true, true), mockServer()],
resolve: {
alias: RESOLVE_ALIASES,
extensions: RESOLVE_EXTENSIONS
extensions: RESOLVE_EXTENSIONS,
tsconfigPaths: true
},
server: {
open: true,
@@ -263,7 +278,8 @@ export default defineConfig(
plugins: createBasePlugins(false, true),
resolve: {
alias: RESOLVE_ALIASES,
extensions: RESOLVE_EXTENSIONS
extensions: RESOLVE_EXTENSIONS,
tsconfigPaths: true
},
build: {
...createBaseBuildConfig(),
@@ -297,7 +313,8 @@ export default defineConfig(
],
resolve: {
alias: RESOLVE_ALIASES,
extensions: RESOLVE_EXTENSIONS
extensions: RESOLVE_EXTENSIONS,
tsconfigPaths: true
},
build: {
...createBaseBuildConfig(),
@@ -306,8 +323,7 @@ export default defineConfig(
rollupOptions: {
treeshake: {
moduleSideEffects: false,
propertyReadSideEffects: false,
tryCatchDeoptimization: false,
propertyReadSideEffects: false as const,
unknownGlobalSideEffects: false
},
output: {

View File

@@ -148,9 +148,7 @@
#elif defined(ARDUINO_ARCH_ESP32)
#include <driver/rtc_io.h>
#include <soc/gpio_struct.h>
#if ESP_IDF_VERSION_MAJOR >= 5
#include "soc/gpio_periph.h"
#endif // ESP_IDF_VERSION_MAJOR >= 5
#define PIN_TO_BASEREG(pin) (0)
#define PIN_TO_BITMASK(pin) (pin)
#define IO_REG_TYPE uint32_t

View File

@@ -12,7 +12,9 @@
// Include all library components
#include "esp32-psram/AllocatorPSRAM.h" // PSRAM-backed vector
#include "esp32-psram/VectorPSRAM.h" // PSRAM-backed vector
#include "esp32-psram/VectorHIMEM.h" // HIMEM-backed vector
#if CONFIG_IDF_TARGET_ESP32
#include "esp32-psram/VectorHIMEM.h" // HIMEM-backed vector (ESP32 only)
#endif
// #include "esp32-psram/InMemoryFile.h" // File interface using vectors
// #include "esp32-psram/PSRAM.h" // PSRAM file system
// #include "esp32-psram/HIMEM.h" // HIMEM file system

View File

@@ -1,5 +1,8 @@
#pragma once
// HIMEM is only available on original ESP32
#if CONFIG_IDF_TARGET_ESP32
#include <algorithm>
#include <limits>
#include <memory>
@@ -360,3 +363,5 @@ class HimemBlock {
};
} // namespace esp32_psram
#endif // CONFIG_IDF_TARGET_ESP32

View File

@@ -238,7 +238,9 @@ using RingBufferStreamPSRAM = RingBufferStream<VectorPSRAM<uint8_t>>;
/**
* @brief Type alias for a RingBufferStream that uses HIMEM-backed vector storage
*/
#if CONFIG_IDF_TARGET_ESP32
using RingBufferStreamHIMEM = RingBufferStream<VectorHIMEM<uint8_t>>;
#endif
/**
* @brief Type alias for a RingBufferStream that uses std::vector storage

View File

@@ -209,8 +209,10 @@ using TypedRingBufferRAM = TypedRingBuffer<T, std::vector<T>>;
/**
* @brief Type alias for a typed ring buffer that uses HIMEM-backed vector storage
*/
#if CONFIG_IDF_TARGET_ESP32
template<typename T>
using TypedRingBufferHIMEM = TypedRingBuffer<T, VectorHIMEM<T>>;
#endif
/**
* @brief Type alias for a typed ring buffer that uses PSRAM-backed vector storage

View File

@@ -1,5 +1,8 @@
#pragma once
// HIMEM is only available on original ESP32
#if CONFIG_IDF_TARGET_ESP32
#include "HimemBlock.h"
namespace esp32_psram {
@@ -526,4 +529,6 @@ void swap(VectorHIMEM<T>& lhs, VectorHIMEM<T>& rhs) noexcept {
lhs.swap(rhs);
}
} // namespace esp32_psram
} // namespace esp32_psram
#endif // CONFIG_IDF_TARGET_ESP32

View File

@@ -34,6 +34,7 @@
#include <string>
#include <vector>
#include <esp32-psram.h>
#include <uuid/common.h>
#include <uuid/log.h>
@@ -1645,22 +1646,22 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
uint8_t cursor_ = 0; /*!< cursor position from end of line */
uint8_t esc_ = 0; /*!< esc sequence running */
Stream & stream_; /*!< Stream used for the input/output of this shell. @since 3.0.0 */
std::shared_ptr<Commands> commands_; /*!< Commands available for execution in this shell. @since 0.1.0 */
std::deque<unsigned int> context_; /*!< Context stack for this shell. Affects which commands are available. Should never be empty. @since 0.1.0 */
unsigned int flags_ = 0; /*!< Current flags for this shell. Affects which commands are available. @since 0.1.0 */
Stream & stream_; /*!< Stream used for the input/output of this shell. @since 3.0.0 */
std::shared_ptr<Commands> commands_; /*!< Commands available for execution in this shell. @since 0.1.0 */
std::deque<unsigned int, AllocatorPSRAM<unsigned int>> context_; /*!< Context stack for this shell. Affects which commands are available. Should never be empty. @since 0.1.0 */
unsigned int flags_ = 0; /*!< Current flags for this shell. Affects which commands are available. @since 0.1.0 */
#if UUID_CONSOLE_THREAD_SAFE
mutable std::mutex mutex_; /*!< Mutex for queued log messages. @since 1.0.0 */
#endif
unsigned long log_message_id_ = 0; /*!< The next identifier to use for queued log messages. @since 0.1.0 */
std::list<QueuedLogMessage> log_messages_; /*!< Queued log messages, in the order they were received. @since 0.1.0 */
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum command line length in bytes. @since 0.6.0 */
std::string line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */
size_t maximum_command_line_length_ = MAX_COMMAND_LINE_LENGTH; /*!< Maximum command line length in bytes. @since 0.6.0 */
unsigned char previous_ = 0; /*!< Previous character that was entered on the command line. Used to detect CRLF line endings. @since 0.1.0 */
Mode mode_ = Mode::NORMAL; /*!< Current execution mode. @since 0.1.0 */
std::unique_ptr<ModeData> mode_data_ = nullptr; /*!< Data associated with the current execution mode. @since 0.1.0 */
bool stopped_ = false; /*!< Indicates that the shell has been stopped. @since 0.1.0 */
unsigned long log_message_id_ = 0; /*!< The next identifier to use for queued log messages. @since 0.1.0 */
std::list<QueuedLogMessage, AllocatorPSRAM<QueuedLogMessage>> log_messages_; /*!< Queued log messages, in the order they were received. @since 0.1.0 */
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum command line length in bytes. @since 0.6.0 */
std::string line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */
size_t maximum_command_line_length_ = MAX_COMMAND_LINE_LENGTH; /*!< Maximum command line length in bytes. @since 0.6.0 */
unsigned char previous_ = 0; /*!< Previous character that was entered on the command line. Used to detect CRLF line endings. @since 0.1.0 */
Mode mode_ = Mode::NORMAL; /*!< Current execution mode. @since 0.1.0 */
std::unique_ptr<ModeData> mode_data_ = nullptr; /*!< Data associated with the current execution mode. @since 0.1.0 */
bool stopped_ = false; /*!< Indicates that the shell has been stopped. @since 0.1.0 */
bool prompt_displayed_ = false; /*!< Indicates that a command prompt has been displayed, so that the output of invoke_command() is correct. @since 0.1.0 */
uint64_t idle_time_ = 0; /*!< Time the shell became idle. @since 0.7.0 */
uint64_t idle_timeout_ = 0; /*!< Idle timeout (in milliseconds). @since 0.7.0 */
@@ -1786,7 +1787,7 @@ class CommandLine {
* @return A reference to the parameters.
* @since 0.6.0
*/
inline std::vector<std::string> & operator*() {
inline std::vector<std::string, AllocatorPSRAM<std::string>> & operator*() {
return parameters_;
}
/**
@@ -1795,7 +1796,7 @@ class CommandLine {
* @return A reference to the parameters.
* @since 0.6.0
*/
inline const std::vector<std::string> & operator*() const {
inline const std::vector<std::string, AllocatorPSRAM<std::string>> & operator*() const {
return parameters_;
}
/**
@@ -1804,7 +1805,7 @@ class CommandLine {
* @return A pointer to the parameters.
* @since 0.4.0
*/
inline std::vector<std::string> * operator->() {
inline std::vector<std::string, AllocatorPSRAM<std::string>> * operator->() {
return &parameters_;
}
/**
@@ -1813,7 +1814,7 @@ class CommandLine {
* @return A pointer to the parameters.
* @since 0.4.0
*/
inline const std::vector<std::string> * operator->() const {
inline const std::vector<std::string, AllocatorPSRAM<std::string>> * operator->() const {
return &parameters_;
}
@@ -1843,8 +1844,8 @@ class CommandLine {
bool trailing_space = false; /*!< Command line has a trailing space. @since 0.4.0 */
private:
std::vector<std::string> parameters_; /*!< Separate command line parameters. @since 0.4.0 */
size_t escape_parameters_ = std::numeric_limits<size_t>::max(); /*!< Number of initial arguments to escape in output. @since 0.5.0 */
std::vector<std::string, AllocatorPSRAM<std::string>> parameters_; /*!< Separate command line parameters. @since 0.4.0 */
size_t escape_parameters_ = std::numeric_limits<size_t>::max(); /*!< Number of initial arguments to escape in output. @since 0.5.0 */
};
/**

View File

@@ -232,7 +232,7 @@ void Logger::vlog(Level level, const char * format, va_list ap) const {
}
void Logger::vlog(Level level, Facility facility, const char * format, va_list ap) const {
std::vector<char> text(MAX_LOG_LENGTH + 1);
std::vector<char, AllocatorPSRAM<char>> text(MAX_LOG_LENGTH + 1);
if (vsnprintf(text.data(), text.size(), format, ap) <= 0) {
return;
@@ -241,7 +241,7 @@ void Logger::vlog(Level level, Facility facility, const char * format, va_list a
dispatch(level, facility, text);
}
void Logger::dispatch(Level level, Facility facility, std::vector<char> & text) const {
void Logger::dispatch(Level level, Facility facility, std::vector<char, AllocatorPSRAM<char>> & text) const {
std::shared_ptr<Message> message = std::make_shared<Message>(get_uptime_ms(), level, facility, name_, text.data());
text.resize(0);

View File

@@ -31,6 +31,7 @@
#include <string>
#include <vector>
#include <esp32-psram.h>
#include <uuid/common.h>
#ifndef UUID_COMMON_THREAD_SAFE
@@ -645,7 +646,7 @@ class Logger {
* @param[in] text Log message text.
* @since 1.0.0
*/
void dispatch(Level level, Facility facility, std::vector<char> & text) const;
void dispatch(Level level, Facility facility, std::vector<char, AllocatorPSRAM<char>> & text) const;
static std::atomic<Level> global_level_; /*!< Minimum global log level across all handlers. @since 3.0.0 */
#if UUID_LOG_THREAD_SAFE
@@ -723,7 +724,7 @@ class PrintHandler : public uuid::log::Handler {
mutable std::mutex mutex_; /*!< Mutex for configuration, state and queued log messages. @since 2.3.0 */
#endif
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum number of log messages to buffer before they are output. @since 2.2.0 */
std::list<std::shared_ptr<Message>> log_messages_; /*!< Queued log messages, in the order they were received. @since 2.2.0 */
std::list<std::shared_ptr<Message>, AllocatorPSRAM<std::shared_ptr<Message>>> log_messages_; /*!< Queued log messages, in the order they were received. @since 2.2.0 */
};
} // namespace log

View File

@@ -20,7 +20,7 @@
#include "../../src/core/emsesp.h"
#ifndef UUID_SYSLOG_HAVE_GETTIMEOFDAY
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#if defined(ARDUINO_ARCH_ESP8266)
// time() does not return UTC on the ESP8266: https://github.com/esp8266/Arduino/issues/4637
#define UUID_SYSLOG_HAVE_GETTIMEOFDAY 1
#endif

View File

@@ -28,6 +28,7 @@
#include <memory>
#include <string>
#include <esp32-psram.h>
#include <uuid/log.h>
#ifndef UUID_LOG_THREAD_SAFE
@@ -321,7 +322,7 @@ class SyslogService : public uuid::log::Handler {
#endif
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum number of log messages to buffer before they are output. @since 1.0.0 */
unsigned long log_message_id_ = 0; /*!< The next identifier to use for queued log messages. @since 1.0.0 */
std::list<QueuedLogMessage> log_messages_; /*!< Queued log messages, in the order they were received. @since 1.0.0 */
std::list<QueuedLogMessage, AllocatorPSRAM<QueuedLogMessage>> log_messages_; /*!< Queued log messages, in the order they were received. @since 1.0.0 */
uint64_t mark_interval_ = 0; /*!< Mark interval in milliseconds. @since 2.0.0 */
uint64_t last_message_ = 0; /*!< Last message/mark time. @since 2.0.0 */

View File

@@ -249,7 +249,7 @@ size_t TelnetStream::write(uint8_t data) {
}
size_t TelnetStream::write(const uint8_t * buffer, size_t size) {
std::vector<unsigned char> data;
std::vector<unsigned char, AllocatorPSRAM<unsigned char>> data;
data.reserve(size);
while (size-- > 0) {
@@ -310,7 +310,7 @@ size_t TelnetStream::raw_write(unsigned char data) {
return 1;
}
size_t TelnetStream::raw_write(const std::vector<unsigned char> & data) {
size_t TelnetStream::raw_write(const std::vector<unsigned char, AllocatorPSRAM<unsigned char>> & data) {
return raw_write(reinterpret_cast<const unsigned char *>(data.data()), data.size());
}

View File

@@ -33,6 +33,7 @@
#include <string>
#include <vector>
#include <esp32-psram.h>
#include <uuid/console.h>
namespace uuid {
@@ -203,7 +204,7 @@ class TelnetStream : public ::Stream {
* @return The number of bytes that were output.
* @since 0.1.0
*/
size_t raw_write(const std::vector<unsigned char> & data);
size_t raw_write(const std::vector<unsigned char, AllocatorPSRAM<unsigned char>> & data);
/**
* Write an array of bytes directly to the output stream.
*
@@ -222,7 +223,7 @@ class TelnetStream : public ::Stream {
unsigned char previous_in_ = 0; /*!< Previous character that was received. Used to detect CR NUL. @since 0.1.0 */
unsigned char previous_out_ = 0; /*!< Previous character that was sent. Used to insert NUL after CR without LF. @since 0.1.0 */
int peek_ = -1; /*!< Previously read data cached by peek(). @since 0.1.0 */
std::vector<char> output_buffer_; /*!< Buffer data to be output until a read function is called. @since 0.1.0 */
std::vector<char, AllocatorPSRAM<char>> output_buffer_; /*!< Buffer data to be output until a read function is called. @since 0.1.0 */
};
/**
@@ -425,7 +426,7 @@ class TelnetService {
WiFiServer server_; /*!< TCP server. @since 0.1.0 */
size_t maximum_connections_ = MAX_CONNECTIONS; /*!< Maximum number of concurrent open connections. @since 0.1.0 */
std::list<Connection> connections_; /*!< Open connections. @since 0.1.0 */
std::list<Connection, AllocatorPSRAM<Connection>> connections_; /*!< Open connections. @since 0.1.0 */
shell_factory_function shell_factory_; /*!< Function to create a shell. @since 0.1.0 */
unsigned long initial_idle_timeout_ = DEFAULT_IDLE_TIMEOUT; /*!< Initial idle timeout (in seconds). @since 0.1.0 */
unsigned long write_timeout_ = DEFAULT_WRITE_TIMEOUT; /*!< Write timeout (in milliseconds). @since 0.1.0 */

View File

@@ -145,6 +145,7 @@ double ledcSetup(uint8_t chan, double freq, uint8_t bit_num) {
return 0;
};
void ledcAttachPin(uint8_t pin, uint8_t chan) {};
void ledcAttach(uint8_t pin, uint8_t chan, uint8_t bit_num) {};
void ledcWrite(uint8_t chan, uint32_t duty) {};
void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val) {};
void rgbLedWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val) {};

View File

@@ -72,6 +72,7 @@ void analogSetAttenuation(adc_attenuation_t attenuation);
void dacWrite(uint8_t pin, uint8_t value);
double ledcSetup(uint8_t chan, double freq, uint8_t bit_num);
void ledcAttachPin(uint8_t pin, uint8_t chan);
void ledcAttach(uint8_t pin, uint8_t chan, uint8_t bit_num);
void ledcWrite(uint8_t chan, uint32_t duty);
void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val);
void rgbLedWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val);

View File

@@ -1,5 +1,5 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// Copyright © 2014-2026, Benoit BLANCHON
// MIT License
#pragma once
@@ -239,11 +239,11 @@
#define ARDUINOJSON_BIN2ALPHA_1111() P
#define ARDUINOJSON_BIN2ALPHA_(A, B, C, D) ARDUINOJSON_BIN2ALPHA_##A##B##C##D()
#define ARDUINOJSON_BIN2ALPHA(A, B, C, D) ARDUINOJSON_BIN2ALPHA_(A, B, C, D)
#define ARDUINOJSON_VERSION "7.4.2"
#define ARDUINOJSON_VERSION "7.4.3"
#define ARDUINOJSON_VERSION_MAJOR 7
#define ARDUINOJSON_VERSION_MINOR 4
#define ARDUINOJSON_VERSION_REVISION 2
#define ARDUINOJSON_VERSION_MACRO V742
#define ARDUINOJSON_VERSION_REVISION 3
#define ARDUINOJSON_VERSION_MACRO V743
#ifndef ARDUINOJSON_VERSION_NAMESPACE
#define ARDUINOJSON_VERSION_NAMESPACE \
ARDUINOJSON_CONCAT5(ARDUINOJSON_VERSION_MACRO, \
@@ -1536,11 +1536,12 @@ template <typename T, size_t = sizeof(T)>
struct FloatTraits {};
template <typename T>
struct FloatTraits<T, 8 /*64bits*/> {
using mantissa_type = uint64_t;
static const short mantissa_bits = 52;
static const mantissa_type mantissa_max = (mantissa_type(1) << mantissa_bits) - 1;
using exponent_type = int16_t;
static const exponent_type exponent_max = 308;
using mantissa_type = uint64_t;
static const short mantissa_bits = 52;
static const mantissa_type mantissa_max = (mantissa_type(1) << mantissa_bits) - 1;
using exponent_type = int16_t;
static const exponent_type exponent_max = 308;
static const size_t binaryPowersOfTen = 9;
static pgm_ptr<T> positiveBinaryPowersOfTen() {
ARDUINOJSON_DEFINE_PROGMEM_ARRAY( //
uint64_t,
@@ -1601,11 +1602,12 @@ struct FloatTraits<T, 8 /*64bits*/> {
};
template <typename T>
struct FloatTraits<T, 4 /*32bits*/> {
using mantissa_type = uint32_t;
static const short mantissa_bits = 23;
static const mantissa_type mantissa_max = (mantissa_type(1) << mantissa_bits) - 1;
using exponent_type = int8_t;
static const exponent_type exponent_max = 38;
using mantissa_type = uint32_t;
static const short mantissa_bits = 23;
static const mantissa_type mantissa_max = (mantissa_type(1) << mantissa_bits) - 1;
using exponent_type = int8_t;
static const exponent_type exponent_max = 38;
static const size_t binaryPowersOfTen = 6;
static pgm_ptr<T> positiveBinaryPowersOfTen() {
ARDUINOJSON_DEFINE_PROGMEM_ARRAY(uint32_t,
factors,
@@ -1668,9 +1670,12 @@ template <typename TFloat, typename TExponent>
inline TFloat make_float(TFloat m, TExponent e) {
using traits = FloatTraits<TFloat>;
auto powersOfTen = e > 0 ? traits::positiveBinaryPowersOfTen() : traits::negativeBinaryPowersOfTen();
auto count = traits::binaryPowersOfTen;
if (e <= 0)
e = TExponent(-e);
for (uint8_t index = 0; e != 0; index++) {
if (index >= count)
return traits::nan();
if (e & 1)
m *= powersOfTen[index];
e >>= 1;
@@ -2422,7 +2427,7 @@ class VariantData {
return;
var->removeMember(key, resources);
}
void reset() {
void reset() { // TODO: remove
type_ = VariantType::Null;
}
void setBoolean(bool value) {
@@ -4214,7 +4219,7 @@ template <typename T, typename Enable = void>
struct Comparer;
template <typename T>
struct Comparer<T, enable_if_t<IsString<T>::value>> : ComparerBase {
T rhs;
T rhs; // TODO: store adapted string?
explicit Comparer(T value)
: rhs(value) {
}
@@ -5298,7 +5303,7 @@ class StringBuilder {
append(*s++);
}
void append(const char * s, size_t n) {
while (n-- > 0)
while (n-- > 0) // TODO: memcpy
append(*s++);
}
void append(char c) {

View File

@@ -19,12 +19,164 @@ class AsyncClient {
class AsyncServer {
public:
AsyncServer(uint16_t port)
: _port(port){};
~AsyncServer(){};
: _port(port) {};
~AsyncServer() {};
protected:
uint16_t _port;
};
namespace asyncsrv {
static constexpr const char empty[] = "";
static constexpr const char T__opaque[] = "\", opaque=\"";
static constexpr const char T_100_CONTINUE[] = "100-continue";
static constexpr const char T_13[] = "13";
static constexpr const char T_ACCEPT[] = "Accept";
static constexpr const char T_Accept_Ranges[] = "Accept-Ranges";
static constexpr const char T_attachment[] = "attachment; filename=\"";
static constexpr const char T_AUTH[] = "Authorization";
static constexpr const char T_auth_nonce[] = "\", qop=\"auth\", nonce=\"";
static constexpr const char T_BASIC[] = "Basic";
static constexpr const char T_BASIC_REALM[] = "Basic realm=\"";
static constexpr const char T_BEARER[] = "Bearer";
static constexpr const char T_BODY[] = "body";
static constexpr const char T_Cache_Control[] = "Cache-Control";
static constexpr const char T_chunked[] = "chunked";
static constexpr const char T_close[] = "close";
static constexpr const char T_cnonce[] = "cnonce";
static constexpr const char T_Connection[] = "Connection";
static constexpr const char T_Content_Disposition[] = "Content-Disposition";
static constexpr const char T_Content_Encoding[] = "Content-Encoding";
static constexpr const char T_Content_Length[] = "Content-Length";
static constexpr const char T_Content_Type[] = "Content-Type";
static constexpr const char T_Content_Location[] = "Content-Location";
static constexpr const char T_Cookie[] = "Cookie";
static constexpr const char T_CORS_ACAC[] = "Access-Control-Allow-Credentials";
static constexpr const char T_CORS_ACAH[] = "Access-Control-Allow-Headers";
static constexpr const char T_CORS_ACAM[] = "Access-Control-Allow-Methods";
static constexpr const char T_CORS_ACAO[] = "Access-Control-Allow-Origin";
static constexpr const char T_CORS_ACMA[] = "Access-Control-Max-Age";
static constexpr const char T_CORS_O[] = "Origin";
static constexpr const char T_data_[] = "data: ";
static constexpr const char T_Date[] = "Date";
static constexpr const char T_DIGEST[] = "Digest";
static constexpr const char T_DIGEST_[] = "Digest ";
static constexpr const char T_ETag[] = "ETag";
static constexpr const char T_event_[] = "event: ";
static constexpr const char T_EXPECT[] = "Expect";
static constexpr const char T_FALSE[] = "false";
static constexpr const char T_filename[] = "filename";
static constexpr const char T_gzip[] = "gzip";
static constexpr const char T_Host[] = "host";
static constexpr const char T_HTTP_1_0[] = "HTTP/1.0";
static constexpr const char T_HTTP_100_CONT[] = "HTTP/1.1 100 Continue\r\n\r\n";
static constexpr const char T_id__[] = "id: ";
static constexpr const char T_IMS[] = "If-Modified-Since";
static constexpr const char T_INM[] = "If-None-Match";
static constexpr const char T_inline[] = "inline";
static constexpr const char T_keep_alive[] = "keep-alive";
static constexpr const char T_Last_Event_ID[] = "Last-Event-ID";
static constexpr const char T_Last_Modified[] = "Last-Modified";
static constexpr const char T_LOCATION[] = "Location";
static constexpr const char T_LOGIN_REQ[] = "Login Required";
static constexpr const char T_MULTIPART_[] = "multipart/";
static constexpr const char T_name[] = "name";
static constexpr const char T_nc[] = "nc";
static constexpr const char T_no_cache[] = "no-cache";
static constexpr const char T_nonce[] = "nonce";
static constexpr const char T_none[] = "none";
static constexpr const char T_opaque[] = "opaque";
static constexpr const char T_qop[] = "qop";
static constexpr const char T_realm[] = "realm";
static constexpr const char T_realm__[] = "realm=\"";
static constexpr const char T_response[] = "response";
static constexpr const char T_retry_[] = "retry: ";
static constexpr const char T_retry_after[] = "Retry-After";
static constexpr const char T_nn[] = "\n\n";
static constexpr const char T_rn[] = "\r\n";
static constexpr const char T_rnrn[] = "\r\n\r\n";
static constexpr const char T_Server[] = "Server";
static constexpr const char T_Transfer_Encoding[] = "Transfer-Encoding";
static constexpr const char T_TRUE[] = "true";
static constexpr const char T_UPGRADE[] = "Upgrade";
static constexpr const char T_uri[] = "uri";
static constexpr const char T_username[] = "username";
static constexpr const char T_WS[] = "websocket";
static constexpr const char T_WWW_AUTH[] = "WWW-Authenticate";
// HTTP Methods
static constexpr const char T_ANY[] = "ANY";
static constexpr const char T_GET[] = "GET";
static constexpr const char T_POST[] = "POST";
static constexpr const char T_PUT[] = "PUT";
static constexpr const char T_DELETE[] = "DELETE";
static constexpr const char T_PATCH[] = "PATCH";
static constexpr const char T_HEAD[] = "HEAD";
static constexpr const char T_OPTIONS[] = "OPTIONS";
static constexpr const char T_UNKNOWN[] = "UNKNOWN";
// Req content types
static constexpr const char T_RCT_NOT_USED[] = "RCT_NOT_USED";
static constexpr const char T_RCT_DEFAULT[] = "RCT_DEFAULT";
static constexpr const char T_RCT_HTTP[] = "RCT_HTTP";
static constexpr const char T_RCT_WS[] = "RCT_WS";
static constexpr const char T_RCT_EVENT[] = "RCT_EVENT";
static constexpr const char T_ERROR[] = "ERROR";
// extensions & MIME-Types
static constexpr const char T__avif[] = ".avif"; // AVIF: Highly compressed images. Compatible with all modern browsers.
static constexpr const char T__csv[] = ".csv"; // CSV: Data logging and configuration
static constexpr const char T__css[] = ".css"; // CSS: Styling for web interfaces
static constexpr const char T__gif[] = ".gif"; // GIF: Simple animations. Legacy support
static constexpr const char T__gz[] = ".gz"; // GZ: compressed files
static constexpr const char T__htm[] = ".htm"; // HTM: Web interface files
static constexpr const char T__html[] = ".html"; // HTML: Web interface files
static constexpr const char T__ico[] = ".ico"; // ICO: Favicons, system icons. Legacy support
static constexpr const char T__jpg[] = ".jpg"; // JPEG/JPG: Photos. Legacy support
static constexpr const char T__js[] = ".js"; // JavaScript: Interactive functionality
static constexpr const char T__json[] = ".json"; // JSON: Data exchange format
static constexpr const char T__mp4[] = ".mp4"; // MP4: Proprietary format. Worse compression than WEBM.
static constexpr const char T__mjs[] = ".mjs"; // MJS: JavaScript module format
static constexpr const char T__opus[] = ".opus"; // OPUS: High compression audio format
static constexpr const char T__pdf[] = ".pdf"; // PDF: Universal document format
static constexpr const char T__png[] = ".png"; // PNG: Icons, logos, transparency. Legacy support
static constexpr const char T__svg[] = ".svg"; // SVG: Vector graphics, icons (scalable, tiny file sizes)
static constexpr const char T__ttf[] = ".ttf"; // TTF: Font file. Legacy support
static constexpr const char T__txt[] = ".txt"; // TXT: Plain text files
static constexpr const char T__webm[] = ".webm"; // WebM: Video. Open source, optimized for web. Compatible with all modern browsers.
static constexpr const char T__webp[] = ".webp"; // WebP: Highly compressed images. Compatible with all modern browsers.
static constexpr const char T__woff[] = ".woff"; // WOFF: Font file. Legacy support
static constexpr const char T__woff2[] = ".woff2"; // WOFF2: Better compression. Compatible with all modern browsers.
static constexpr const char T__xml[] = ".xml"; // XML: Configuration and data files
static constexpr const char T_application_javascript[] = "application/javascript"; // Obsolete type for JavaScript
static constexpr const char T_application_json[] = "application/json";
static constexpr const char T_application_msgpack[] = "application/msgpack";
static constexpr const char T_application_octet_stream[] = "application/octet-stream";
static constexpr const char T_application_pdf[] = "application/pdf";
static constexpr const char T_app_xform_urlencoded[] = "application/x-www-form-urlencoded";
static constexpr const char T_audio_opus[] = "audio/opus";
static constexpr const char T_font_ttf[] = "font/ttf";
static constexpr const char T_font_woff[] = "font/woff";
static constexpr const char T_font_woff2[] = "font/woff2";
static constexpr const char T_image_avif[] = "image/avif";
static constexpr const char T_image_gif[] = "image/gif";
static constexpr const char T_image_jpeg[] = "image/jpeg";
static constexpr const char T_image_png[] = "image/png";
static constexpr const char T_image_svg_xml[] = "image/svg+xml";
static constexpr const char T_image_webp[] = "image/webp";
static constexpr const char T_image_x_icon[] = "image/x-icon";
static constexpr const char T_text_css[] = "text/css";
static constexpr const char T_text_csv[] = "text/csv";
static constexpr const char T_text_event_stream[] = "text/event-stream";
static constexpr const char T_text_html[] = "text/html";
static constexpr const char T_text_javascript[] = "text/javascript";
static constexpr const char T_text_plain[] = "text/plain";
static constexpr const char T_text_xml[] = "text/xml";
static constexpr const char T_video_mp4[] = "video/mp4";
static constexpr const char T_video_webm[] = "video/webm";
} // namespace asyncsrv
#endif

View File

@@ -67,6 +67,9 @@ class Preferences {
size_t putString(const char * key, String value) {
return 0;
}
bool isKey(const char * key) {
return true;
}
// unused....
@@ -84,7 +87,6 @@ class Preferences {
// size_t putBool(const char * key, bool value);
// size_t putBytes(const char * key, const void * value, size_t len);
// bool isKey(const char * key);
// PreferenceType getType(const char * key);
// int8_t getChar(const char * key, int8_t defaultValue = 0);
// int16_t getShort(const char * key, int16_t defaultValue = 0);

View File

@@ -1,6 +1,6 @@
{
"name": "mock-api",
"version": "3.8.0",
"version": "3.8.2",
"description": "mock api for EMS-ESP",
"author": "proddy, emsesp.org",
"license": "MIT",
@@ -12,8 +12,8 @@
"@msgpack/msgpack": "^3.1.3",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"formidable": "^3.5.4",
"itty-router": "^5.0.22",
"prettier": "^3.7.4"
"itty-router": "^5.0.23",
"prettier": "^3.8.3"
},
"packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48"
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
}

116
mock-api/pnpm-lock.yaml generated
View File

@@ -13,25 +13,25 @@ importers:
version: 3.1.3
'@trivago/prettier-plugin-sort-imports':
specifier: ^6.0.2
version: 6.0.2(prettier@3.7.4)
version: 6.0.2(prettier@3.8.3)
formidable:
specifier: ^3.5.4
version: 3.5.4
itty-router:
specifier: ^5.0.22
version: 5.0.22
specifier: ^5.0.23
version: 5.0.23
prettier:
specifier: ^3.7.4
version: 3.7.4
specifier: ^3.8.3
version: 3.8.3
packages:
'@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
'@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
'@babel/generator@7.28.5':
resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
'@babel/generator@7.29.1':
resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
engines: {node: '>=6.9.0'}
'@babel/helper-globals@7.28.0':
@@ -46,21 +46,21 @@ packages:
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
'@babel/parser@7.28.5':
resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
'@babel/parser@7.29.2':
resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
'@babel/template@7.28.6':
resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
engines: {node: '>=6.9.0'}
'@babel/traverse@7.28.5':
resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
'@babel/traverse@7.29.0':
resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
engines: {node: '>=6.9.0'}
'@babel/types@7.28.5':
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
'@babel/types@7.29.0':
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
'@jridgewell/gen-mapping@0.3.13':
@@ -112,8 +112,8 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
brace-expansion@2.1.0:
resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
@@ -131,8 +131,8 @@ packages:
resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==}
engines: {node: '>=14.0.0'}
itty-router@5.0.22:
resolution: {integrity: sha512-9hmdGErWdYDOurGYxSbqLhy4EFReIwk71hMZTJ5b+zfa2zjMNV1ftFno2b8VjAQvX615gNB8Qxbl9JMRqHnIVA==}
itty-router@5.0.23:
resolution: {integrity: sha512-i49WU+SNPrwOZA4Z61En1RYd5h2Lcqa+5IvCpMrNi4dxymzJK15ozUUnRrWIUAv95Zamd4eJPAot2UvHRrQg7w==}
javascript-natural-sort@0.7.1:
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
@@ -145,11 +145,11 @@ packages:
engines: {node: '>=6'}
hasBin: true
lodash-es@4.17.22:
resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==}
lodash-es@4.18.1:
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
minimatch@9.0.9:
resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
engines: {node: '>=16 || 14 >=14.17'}
ms@2.1.3:
@@ -167,8 +167,8 @@ packages:
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
prettier@3.7.4:
resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==}
prettier@3.8.3:
resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==}
engines: {node: '>=14'}
hasBin: true
@@ -177,16 +177,16 @@ packages:
snapshots:
'@babel/code-frame@7.27.1':
'@babel/code-frame@7.29.0':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
js-tokens: 4.0.0
picocolors: 1.1.1
'@babel/generator@7.28.5':
'@babel/generator@7.29.1':
dependencies:
'@babel/parser': 7.28.5
'@babel/types': 7.28.5
'@babel/parser': 7.29.2
'@babel/types': 7.29.0
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
@@ -197,29 +197,29 @@ snapshots:
'@babel/helper-validator-identifier@7.28.5': {}
'@babel/parser@7.28.5':
'@babel/parser@7.29.2':
dependencies:
'@babel/types': 7.28.5
'@babel/types': 7.29.0
'@babel/template@7.27.2':
'@babel/template@7.28.6':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/parser': 7.28.5
'@babel/types': 7.28.5
'@babel/code-frame': 7.29.0
'@babel/parser': 7.29.2
'@babel/types': 7.29.0
'@babel/traverse@7.28.5':
'@babel/traverse@7.29.0':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/generator': 7.28.5
'@babel/code-frame': 7.29.0
'@babel/generator': 7.29.1
'@babel/helper-globals': 7.28.0
'@babel/parser': 7.28.5
'@babel/template': 7.27.2
'@babel/types': 7.28.5
'@babel/parser': 7.29.2
'@babel/template': 7.28.6
'@babel/types': 7.29.0
debug: 4.4.3
transitivePeerDependencies:
- supports-color
'@babel/types@7.28.5':
'@babel/types@7.29.0':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
@@ -246,17 +246,17 @@ snapshots:
dependencies:
'@noble/hashes': 1.8.0
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.7.4)':
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.8.3)':
dependencies:
'@babel/generator': 7.28.5
'@babel/parser': 7.28.5
'@babel/traverse': 7.28.5
'@babel/types': 7.28.5
'@babel/generator': 7.29.1
'@babel/parser': 7.29.2
'@babel/traverse': 7.29.0
'@babel/types': 7.29.0
javascript-natural-sort: 0.7.1
lodash-es: 4.17.22
minimatch: 9.0.5
lodash-es: 4.18.1
minimatch: 9.0.9
parse-imports-exports: 0.2.4
prettier: 3.7.4
prettier: 3.8.3
transitivePeerDependencies:
- supports-color
@@ -264,7 +264,7 @@ snapshots:
balanced-match@1.0.2: {}
brace-expansion@2.0.2:
brace-expansion@2.1.0:
dependencies:
balanced-match: 1.0.2
@@ -283,7 +283,7 @@ snapshots:
dezalgo: 1.0.4
once: 1.4.0
itty-router@5.0.22: {}
itty-router@5.0.23: {}
javascript-natural-sort@0.7.1: {}
@@ -291,11 +291,11 @@ snapshots:
jsesc@3.1.0: {}
lodash-es@4.17.22: {}
lodash-es@4.18.1: {}
minimatch@9.0.5:
minimatch@9.0.9:
dependencies:
brace-expansion: 2.0.2
brace-expansion: 2.1.0
ms@2.1.3: {}
@@ -311,6 +311,6 @@ snapshots:
picocolors@1.1.1: {}
prettier@3.7.4: {}
prettier@3.8.3: {}
wrappy@1.0.2: {}

View File

@@ -141,8 +141,8 @@ let system_status = {
let DEV_VERSION_IS_UPGRADEABLE: boolean;
let STABLE_VERSION_IS_UPGRADEABLE: boolean;
let THIS_VERSION: string;
let LATEST_STABLE_VERSION = '3.8.0';
let LATEST_DEV_VERSION = '3.8.1-dev.2';
let LATEST_STABLE_VERSION = '3.8.2';
let LATEST_DEV_VERSION = '3.8.3-dev.2';
// scenarios for testing versioning
let version_test = 0; // on latest stable, or switch to dev
@@ -363,6 +363,8 @@ function export_data(type: string) {
return emsesp_modules;
case 'allvalues':
return emsesp_allvalues;
case 'systembackup':
return emsesp_systembackup;
default:
return status(404);
}
@@ -386,13 +388,34 @@ function custom_support() {
'',
"For help and questions please <a target='_blank' href='https://emsesp.org'>contact</a> your installer."
],
img_url: 'https://emsesp.org/_media/images/designer.png'
img_url: 'https://emsesp.org/media/images/designer.png'
// img_url: 'https://picsum.photos/200/300'
}
};
}
// called by Action endpoint
// called by Action endpoint upgradeImportantMessages
function upgradeImportantMessages(version: string) {
// 0 is do nothing
// 1 means 3.9 and factory reset required
// 2 means a major version upgrade
let upgradeImportantMessageType_n = 0;
// see if its a filename with a .bin extension
if (version.endsWith('.bin')) {
upgradeImportantMessageType_n = 1; // 1 means 3.9 and factory reset required
} else if (version.endsWith('.md')) {
upgradeImportantMessageType_n = 0;
} else {
// this is a version string like "3.9.0"
upgradeImportantMessageType_n = 2;
}
console.log('upgradeImportantMessageType: ' + upgradeImportantMessageType_n);
return { upgradeImportantMessageType: upgradeImportantMessageType_n };
}
// called by Action endpoint checkUpgrade
function check_upgrade(version: string) {
let data = {};
if (version) {
@@ -732,6 +755,8 @@ const emsesp_info = {
]
};
const emsesp_systembackup = {};
const emsesp_allvalues = {
'Boiler Nefit Trendline HRC30 (DeviceID:0x08, ProductID:123, Version:06.01)': {
'force heating off': 'off',
@@ -4564,7 +4589,7 @@ router
let sorted_devices = [...emsesp_coredata.devices].sort((a, b) => a.t - b.t);
// append emsesp_coredata to sorted_devices so Custom is always at the end of the list
sorted_devices.push(emsesp_coredata_custom);
// sorted_devices = []; // uncomment if simulating no devices...
// return { connected: false, devices: [] }; // uncomment if simulating no devices...
return { connected: true, devices: sorted_devices };
})
.get(EMSESP_SENSOR_DATA_ENDPOINT, () => {
@@ -5166,6 +5191,9 @@ router
// set partition
console.log('setting partition to', content.param);
return status(200);
} else if (action === 'upgradeImportantMessages') {
// check upgrade important messages
return upgradeImportantMessages(content.param);
}
}
return status(404); // cmd not found

View File

@@ -15,15 +15,14 @@ description = EMS-ESP Firmware for the ESP32
src_dir = src
lib_dir = lib
boards_dir = boards
; build_cache_dir = .pio/build_cache
extra_configs =
factory_settings.ini
pio_local.ini
[common]
core_build_flags = -std=c++17 -std=gnu++17 -O3 -flto=auto -Wno-type-limits -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-format
core_unbuild_flags = -std=gnu++11 -fno-lto
core_build_flags = -std=c++17 -std=gnu++17 -O3 -Wno-type-limits -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-format -Wno-missing-field-initializers
core_unbuild_flags = -std=gnu++11
my_build_flags =
@@ -60,7 +59,7 @@ framework = arduino
board_build.partitions = partitions/esp32_partition_16M.csv
board_upload.flash_size = 16MB
board_build.app_partition_name = app0
platform = espressif32@6.12.0 ; Arduino Core 2.0.17 / IDF 4.4.7
platform = espressif32@6.13.0 ; Arduino Core 2.0.17 / IDF 4.4.7
; 32MB Flash variants
[espressif32_base_32M]
@@ -68,7 +67,7 @@ framework = arduino
board_build.partitions = partitions/esp32_partition_32M.csv
board_upload.flash_size = 32MB
board_build.app_partition_name = app0
platform = espressif32@6.12.0 ; Arduino Core 2.0.17 / IDF 4.4.7
platform = espressif32@6.13.0 ; Arduino Core 2.0.17 / IDF 4.4.7
; use Tasmota's library for 4MB Flash variants.
; Removes libs (like mbedtsl, so no WiFi_secure.h) to increase available heap
@@ -104,11 +103,10 @@ monitor_filters = direct
build_type = release
board_build.filesystem = littlefs
lib_deps =
bblanchon/ArduinoJson @ 7.4.2
bblanchon/ArduinoJson @ 7.4.3
ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.9.4
https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
ESP32Async/ESPAsyncWebServer @ 3.10.3
; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
; builds the web interface only, not the firmware
[env:build_webUI]
@@ -168,19 +166,6 @@ build_flags =
${common.build_flags}
-DBOARD_C3_MINI_V1
; XIAO ESP32C - 512KB SRAM & 4MB Flash - https://wiki.seeedstudio.com/xiao_esp32c6_getting_started/
[env:c6]
framework = arduino
board_build.partitions = partitions/esp32_partition_4M.csv
board_upload.flash_size = 4MB
board_build.app_partition_name = app0
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21-2/platform-espressif32.zip ; Arduino Release v3.2.1 based on ESP-IDF v5.4.2
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.30-2/platform-espressif32.zip ; Arduino Release v3.3.0 based on ESP-IDF v5.5.0
board = seeed_xiao_esp32c6
build_flags =
${common.build_flags}
-DBOARD_C6
; foundation for building and testing natively, standalone without an ESP32
; use the `standalone` environment instead of `native` for testing
[env:native]
@@ -212,6 +197,7 @@ build_src_filter =
-<../lib/uuid-syslog>
-<../lib/eModbus>
-<../lib/OneWire>
-<../src/core/ModuleLibrary.cpp>
lib_ldf_mode = off
lib_deps =
@@ -254,6 +240,7 @@ build_src_filter =
-<../lib/uuid-syslog>
-<../lib/eModbus>
-<../lib/OneWire>
-<../src/core/ModuleLibrary.cpp>
lib_ldf_mode = off
lib_deps = Unity
test_testing_command =

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
sonar.organization=emsesp
sonar.projectKey=emsesp_EMS-ESP32
sonar.projectName=EMS-ESP32
sonar.projectVersion=3.8.0
sonar.projectVersion=3.8.2
sonar.sources=./src
sonar.cfamily.compile-commands=bw-output/compile_commands.json
sonar.sourceEncoding=UTF-8

View File

@@ -21,24 +21,24 @@ void APSettingsService::begin() {
// wait 10 sec on STA disconnect before starting AP
void APSettingsService::WiFiEvent(WiFiEvent_t event) {
uint8_t was_connected = _connected;
const uint8_t was_connected = _connected;
switch (event) {
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
_connected &= ~1;
_connected &= ~1U;
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
_connected &= ~2;
_connected &= ~2U;
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
_connected |= 1;
_connected |= 1U;
break;
case ARDUINO_EVENT_ETH_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP6:
_connected |= 2;
_connected |= 2U;
break;
default:
break;
return;
}
// wait 10 sec before starting AP
if (was_connected && !_connected) {
@@ -52,18 +52,19 @@ void APSettingsService::reconfigureAP() {
}
void APSettingsService::loop() {
unsigned long currentMillis = uuid::get_uptime();
unsigned long manageElapsed = static_cast<uint32_t>(currentMillis - _lastManaged);
if (manageElapsed >= MANAGE_NETWORK_DELAY) {
const unsigned long currentMillis = uuid::get_uptime();
if ((currentMillis - _lastManaged) >= MANAGE_NETWORK_DELAY) {
_lastManaged = currentMillis;
manageAP();
}
handleDNS();
if (_dnsServer) {
handleDNS();
}
}
void APSettingsService::manageAP() {
WiFiMode_t currentWiFiMode = WiFi.getMode();
const WiFiMode_t currentWiFiMode = WiFi.getMode();
if (_state.provisionMode == AP_MODE_ALWAYS || (_state.provisionMode == AP_MODE_DISCONNECTED && !_connected)) {
if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) {
startAP();
@@ -87,8 +88,10 @@ void APSettingsService::startAP() {
WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
#endif
if (!_dnsServer) {
IPAddress apIp = WiFi.softAPIP();
emsesp::EMSESP::logger().info("Starting Access Point with captive portal on %s", apIp.toString().c_str());
const IPAddress apIp = WiFi.softAPIP();
char ipStr[16];
snprintf(ipStr, sizeof(ipStr), "%u.%u.%u.%u", apIp[0], apIp[1], apIp[2], apIp[3]);
emsesp::EMSESP::logger().info("Starting Access Point with captive portal on %s", ipStr);
_dnsServer = new DNSServer;
_dnsServer->start(DNS_PORT, "*", apIp);
}
@@ -111,8 +114,8 @@ void APSettingsService::handleDNS() {
}
APNetworkStatus APSettingsService::getAPNetworkStatus() {
WiFiMode_t currentWiFiMode = WiFi.getMode();
bool apActive = currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA;
const WiFiMode_t currentWiFiMode = WiFi.getMode();
const bool apActive = (currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA);
if (apActive && _state.provisionMode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
return APNetworkStatus::LINGERING;
@@ -135,7 +138,7 @@ void APSettings::read(const APSettings & settings, JsonObject root) {
}
StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) {
APSettings newSettings = {};
APSettings newSettings{};
newSettings.provisionMode = static_cast<uint8_t>(root["provision_mode"] | FACTORY_AP_PROVISION_MODE);
switch (settings.provisionMode) {

View File

@@ -1,5 +1,5 @@
#ifndef APSettingsConfig_h
#define APSettingsConfig_h
#ifndef APSettingsService_h
#define APSettingsService_h
#include "HttpEndpoint.h"
#include "FSPersistence.h"
@@ -70,9 +70,9 @@ class APSettings {
IPAddress subnetMask;
bool operator==(const APSettings & settings) const {
return provisionMode == settings.provisionMode && ssid == settings.ssid && password == settings.password && channel == settings.channel
&& ssidHidden == settings.ssidHidden && maxClients == settings.maxClients && localIP == settings.localIP && gatewayIP == settings.gatewayIP
&& subnetMask == settings.subnetMask;
return provisionMode == settings.provisionMode && channel == settings.channel && ssidHidden == settings.ssidHidden && maxClients == settings.maxClients
&& localIP == settings.localIP && gatewayIP == settings.gatewayIP && subnetMask == settings.subnetMask && ssid == settings.ssid
&& password == settings.password;
}
static void read(const APSettings & settings, JsonObject root);
@@ -96,8 +96,8 @@ class APSettingsService : public StatefulService<APSettings> {
// for the management delay loop
volatile unsigned long _lastManaged;
volatile boolean _reconfigureAp;
uint8_t _connected;
volatile bool _reconfigureAp;
volatile uint8_t _connected;
void reconfigureAP();
void manageAP();

View File

@@ -66,21 +66,16 @@ void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument & jsonDocument) {
}
/*
* ESP32 uses mbedtls, with decent HMAC implementations supporting sha256, as well as others.
* No need to pull in additional crypto libraries - lets use what we already have.
* HMAC-SHA256 using mbedtls
*/
String ArduinoJsonJWT::sign(String & payload) {
std::array<unsigned char, 32> hmacResult{};
{
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
mbedtls_md_hmac_starts(&ctx, reinterpret_cast<const unsigned char *>(_secret.c_str()), _secret.length());
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char *>(payload.c_str()), payload.length());
mbedtls_md_hmac_finish(&ctx, hmacResult.data());
mbedtls_md_free(&ctx);
}
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
reinterpret_cast<const unsigned char *>(_secret.c_str()),
_secret.length(),
reinterpret_cast<const unsigned char *>(payload.c_str()),
payload.length(),
hmacResult.data());
return encode(reinterpret_cast<const char *>(hmacResult.data()), hmacResult.size());
}

View File

@@ -2,6 +2,8 @@
#include "WWWData.h" // include auto-generated static web resources
static constexpr const char CACHE_CONTROL[] = "public,max-age=60";
ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
: _securitySettingsService(server, fs)
, _networkSettingsService(server, fs, &_securitySettingsService)
@@ -22,21 +24,18 @@ ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
ArRequestHandlerFunction indexHtmlHandler = nullptr;
WWWData::registerRoutes([server, &indexHtmlHandler](const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) {
ArRequestHandlerFunction requestHandler = [contentType, content, len, hash](AsyncWebServerRequest * request) {
AsyncWebServerResponse * response;
String etag = "\"" + hash + "\""; // RFC9110: ETag must be enclosed in double quotes
// Check if the client already has the same version and respond with a 304 (Not modified)
if (request->header("If-None-Match").equals(hash)) {
response = request->beginResponse(304);
} else {
response = request->beginResponse(200, contentType, content, len);
response->addHeader("Content-Encoding", "gzip"); // not br for brotlin only works over HTTPS
ArRequestHandlerFunction requestHandler = [contentType, content, len, etag](AsyncWebServerRequest * request) {
if (request->header(asyncsrv::T_INM) == etag) {
request->send(304);
return;
}
// always send these headers - see https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
response->addHeader("ETag", hash);
response->addHeader("Cache-Control", "no-cache"); // Requires revalidation before using cached content (ETags enable 304 responses)
AsyncWebServerResponse * response = request->beginResponse(200, contentType, content, len);
response->addHeader(asyncsrv::T_Content_Encoding, asyncsrv::T_gzip, false);
response->addHeader(asyncsrv::T_ETag, etag, false);
response->addHeader(asyncsrv::T_Cache_Control, CACHE_CONTROL, false);
request->send(response);
};
@@ -69,11 +68,11 @@ void ESP32React::begin() {
_networkSettingsService.read([&](NetworkSettings & networkSettings) {
DefaultHeaders & defaultHeaders = DefaultHeaders::Instance();
if (networkSettings.enableCORS) {
defaultHeaders.addHeader("Access-Control-Allow-Origin", networkSettings.CORSOrigin);
defaultHeaders.addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
defaultHeaders.addHeader("Access-Control-Allow-Credentials", "true");
defaultHeaders.addHeader(asyncsrv::T_CORS_ACAO, networkSettings.CORSOrigin);
defaultHeaders.addHeader(asyncsrv::T_CORS_ACAH, "Accept, Content-Type, Authorization");
defaultHeaders.addHeader(asyncsrv::T_CORS_ACAC, "true");
}
defaultHeaders.addHeader("Server", networkSettings.hostname);
defaultHeaders.addHeader(asyncsrv::T_Server, networkSettings.hostname);
});
_apSettingsService.begin();
_ntpSettingsService.begin();

View File

@@ -10,20 +10,23 @@
class JsonUtils {
public:
static void readIP(JsonObject root, const String & key, IPAddress & ip, const String & def) {
IPAddress defaultIp = {};
IPAddress defaultIp{};
if (!defaultIp.fromString(def)) {
defaultIp = INADDR_NONE;
}
readIP(root, key, ip, defaultIp);
}
static void readIP(JsonObject root, const String & key, IPAddress & ip, const IPAddress & defaultIp = INADDR_NONE) {
if (!root[key].is<String>() || !ip.fromString(root[key].as<String>())) {
const JsonVariant value = root[key];
if (!value.is<String>() || !ip.fromString(value.as<const char *>())) {
ip = defaultIp;
}
}
static void writeIP(JsonObject root, const String & key, const IPAddress & ip) {
if (IPUtils::isSet(ip)) {
root[key] = ip.toString();
char ipStr[16];
snprintf(ipStr, sizeof(ipStr), "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
root[key] = ipStr;
}
}
};

View File

@@ -255,8 +255,8 @@ void MqttSettings::read(MqttSettings & settings, JsonObject root) {
}
StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings) {
MqttSettings newSettings = {};
bool changed = false;
MqttSettings newSettings;
bool changed = false;
#ifndef TASMOTA_SDK
newSettings.enableTLS = root["enableTLS"];
@@ -316,6 +316,10 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
changed = true;
}
if (newSettings.ha_number_mode != settings.ha_number_mode) {
changed = true;
}
if (newSettings.entity_format != settings.entity_format) {
changed = true;
}

View File

@@ -125,11 +125,11 @@ class MqttSettingsService : public StatefulService<MqttSettings> {
FSPersistence<MqttSettings> _fsPersistence;
// variable to help manage connection
bool _reconfigureMqtt;
unsigned long _disconnectedAt;
volatile bool _reconfigureMqtt;
volatile unsigned long _disconnectedAt;
// connection status
espMqttClientTypes::DisconnectReason _disconnectReason;
volatile espMqttClientTypes::DisconnectReason _disconnectReason;
// the MQTT client instance
MqttClient * _mqttClient;

View File

@@ -49,7 +49,7 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
private:
HttpEndpoint<NTPSettings> _httpEndpoint;
FSPersistence<NTPSettings> _fsPersistence;
bool _connected;
volatile bool _connected;
void WiFiEvent(WiFiEvent_t event);
void configureNTP();

View File

@@ -102,10 +102,10 @@ class NetworkSettingsService : public StatefulService<NetworkSettings> {
HttpEndpoint<NetworkSettings> _httpEndpoint;
FSPersistence<NetworkSettings> _fsPersistence;
unsigned long _lastConnectionAttempt;
bool _stopping;
volatile unsigned long _lastConnectionAttempt;
volatile bool _stopping;
uint16_t connectcount_ = 0; // number of wifi reconnects
volatile uint16_t connectcount_ = 0; // number of wifi reconnects
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
void mDNS_start() const;

View File

@@ -61,22 +61,27 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
if (_is_firmware) {
// Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip offset 12: esp32:0, S2:2, C3:5
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) {
if (len > 12 && (data[0] != ESP_IMAGE_HEADER_MAGIC || data[12] != ESP_CHIP_ID_ESP32)) {
handleError(request, 503); // service unavailable
return;
}
#elif CONFIG_IDF_TARGET_ESP32S2
if (len > 12 && (data[0] != 0xE9 || data[12] != 2)) {
if (len > 12 && (data[0] != ESP_IMAGE_HEADER_MAGIC || data[12] != ESP_CHIP_ID_ESP32S2)) {
handleError(request, 503); // service unavailable
return;
}
#elif CONFIG_IDF_TARGET_ESP32C3
if (len > 12 && (data[0] != 0xE9 || data[12] != 5)) {
if (len > 12 && (data[0] != ESP_IMAGE_HEADER_MAGIC || data[12] != ESP_CHIP_ID_ESP32C3)) {
handleError(request, 503); // service unavailable
return;
}
#elif CONFIG_IDF_TARGET_ESP32S3
if (len > 12 && (data[0] != 0xE9 || data[12] != 9)) {
if (len > 12 && (data[0] != ESP_IMAGE_HEADER_MAGIC || data[12] != ESP_CHIP_ID_ESP32S3)) {
handleError(request, 503); // service unavailable
return;
}
#elif CONFIG_IDF_TARGET_ESP32C6
if (len > 12 && (data[0] != ESP_IMAGE_HEADER_MAGIC || data[12] != ESP_CHIP_ID_ESP32C6)) {
handleError(request, 503); // service unavailable
return;
}
@@ -179,7 +184,7 @@ void UploadFileService::handleError(AsyncWebServerRequest * request, int code) {
}
void UploadFileService::handleEarlyDisconnect() {
emsesp::EMSESP::logger().info("Upload ended");
emsesp::EMSESP::logger().info("Upload finished");
emsesp::EMSESP::system_.uart_init(); // re-enable UART
_is_firmware = false;

View File

@@ -0,0 +1,31 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include <emsesp.h>
void ModuleLibrary::list(JsonObject output) {};
void ModuleLibrary::loop() {};
void ModuleLibrary::start(emsesp::EMSESP * emsesp_main, bool test_mode) {};
bool ModuleLibrary::enable(const char * key, const char * license, bool enable) {
return true;
};

Some files were not shown because too many files have changed in this diff Show More