120 Commits

Author SHA1 Message Date
Proddy
d18e5b1f14 Merge pull request #3041 from proddy/core3
Core3 sync
2026-04-22 16:09:58 +02:00
proddy
20327d817d async-validator fixes 2026-04-22 16:07:59 +02:00
proddy
051c332426 package update 2026-04-22 15:43:46 +02:00
proddy
a09258325e remove YIELD 2026-04-22 15:43:36 +02:00
proddy
61dca0cbda https://github.com/emsesp/EMS-ESP32/issues/2686 2026-04-22 15:43:10 +02:00
Proddy
2bff299193 Merge pull request #3037 from MichaelDvP/core3
Core3 update
2026-04-22 15:26:26 +02:00
MichaelDvP
1329b13db3 Merge branch 'dev' into core3 2026-04-22 15:05:39 +02:00
MichaelDvP
9dd894f0fe Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into core3 2026-04-22 10:14:34 +02:00
MichaelDvP
30d1ae5642 update otadata when littlefs fails 2026-04-22 08:21:29 +02:00
MichaelDvP
79aceef382 Merge branch 'core3' of https://github.com/emsesp/EMS-ESP32 into core3 2026-04-22 07:51:58 +02:00
MichaelDvP
a28e52210a Merge branch 'dev' into core3 2026-04-22 07:50:43 +02:00
MichaelDvP
08eb294213 update readymail 2026-04-22 07:26:17 +02:00
MichaelDvP
888baed81a pkg update 2026-04-21 21:39:10 +02:00
MichaelDvP
4de3955db2 set partition after update 2026-04-21 21:38:52 +02:00
MichaelDvP
25f08c7624 Merge branch 'dev' into core3 2026-04-21 20:44:02 +02:00
proddy
1e8013100c rename build-webUI with build_webUI 2026-04-20 15:46:54 +02:00
proddy
62c8f55568 package update (vite fix) 2026-04-20 15:46:39 +02:00
Proddy
c40d828749 Merge pull request #3031 from proddy/core3
build_webUI -> build-webUI
2026-04-19 19:15:28 +02:00
proddy
84ad08887a build_webUI -> build-webUI 2026-04-19 19:13:15 +02:00
MichaelDvP
ed0a678020 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into core3 2026-04-19 15:49:36 +02:00
Proddy
37107d8500 Merge pull request #3029 from proddy/core3
sync memory optimizations from dev-16 to core3
2026-04-19 15:15:14 +02:00
proddy
dde6a8c5db close dialog after download 2026-04-19 15:10:51 +02:00
proddy
e2750b8572 don't show system backup as its the same page! 2026-04-19 15:10:41 +02:00
proddy
acd23925b5 download text changes 2026-04-19 15:10:20 +02:00
proddy
b0db054e11 fix firmware install date (was using UTC as TZ not initialised) 2026-04-19 14:17:31 +02:00
proddy
51cea8e757 no need to call ntp on begin as its in the loop 2026-04-19 13:15:11 +02:00
proddy
bbb086ea41 add back NTP 2026-04-19 13:14:54 +02:00
proddy
539e6ed080 remove lazy loading 2026-04-19 10:17:10 +02:00
proddy
1d33a26318 fix dns name being set to "tasmota" 2026-04-18 19:42:45 +02:00
proddy
86a20fc97a sync with dev-16 2026-04-18 18:54:33 +02:00
proddy
d6e00c4534 UART_FIFO_LEN is deprecated 2026-04-18 18:50:08 +02:00
Proddy
a813d38108 Merge pull request #3025 from MichaelDvP/core3
Core3
2026-04-17 15:44:51 +02:00
MichaelDvP
685a49c212 Merge branch 'dev' into core3, formatting, add back sendmail settings 2026-04-17 12:28:41 +02:00
Proddy
2c8eb534af Merge pull request #3022 from proddy/core3
fix merge errors
2026-04-16 08:46:06 +02:00
proddy
5210fab4cb switch to C++20 for string find commands 2026-04-16 08:45:07 +02:00
proddy
49787d27f1 add missing code lost in merge 2026-04-16 08:44:55 +02:00
proddy
dfe7b46461 remove unused http 2026-04-16 08:44:44 +02:00
Proddy
f8257de0dd Merge pull request #3009 from MichaelDvP/core3
https client for scheduler/shuntingYard
2026-04-15 21:34:03 +02:00
Proddy
3b3ecc9f1d Merge branch 'core3' into core3 2026-04-15 21:33:27 +02:00
proddy
84105acf5d 3.9.0-dev.0 2026-04-15 20:48:11 +02:00
proddy
def5173692 fix merge issues 2026-04-15 20:37:33 +02:00
proddy
6b31fef1af build on mac osx 2026-04-15 20:37:27 +02:00
Proddy
c9c059ca65 Merge pull request #3020 from proddy/core3
sync with dev
2026-04-15 09:26:32 +02:00
proddy
4d3b31e5a1 sync with dev 2026-04-15 09:25:38 +02:00
MichaelDvP
51d90095aa https client for scheduler/shuntingYard 2026-04-04 11:39:43 +02:00
Proddy
16c0370443 Merge pull request #3005 from proddy/core3
Core3 updates
2026-03-30 23:25:22 +02:00
proddy
67bb38dcf4 updated test data 2026-03-30 23:24:35 +02:00
proddy
049231a36e minor text changes 2026-03-30 23:15:18 +02:00
proddy
349d6b7375 package update 2026-03-30 23:15:07 +02:00
proddy
b72b368d3c ArduinoJson @ 7.4.3 2026-03-30 23:15:00 +02:00
proddy
7f9fd44a02 C14 2026-03-30 21:27:26 +02:00
proddy
a400c5974c comment change 2026-03-30 21:09:59 +02:00
proddy
afca995fe5 remove poll check 2026-03-30 21:06:08 +02:00
proddy
81504fedc5 package update 2026-03-30 21:05:55 +02:00
proddy
3da3345683 fix poll_match_count 2026-03-29 16:30:38 +02:00
Proddy
c6c2889306 Merge branch 'dev' into core3 2026-03-29 15:58:45 +02:00
proddy
b60f0d260a updated tests 2026-03-29 15:55:02 +02:00
proddy
cd750e4777 text change 2026-03-29 15:44:39 +02:00
proddy
4e5d503b35 remove mbedtls, update ArduinoJson 2026-03-29 15:44:27 +02:00
proddy
bd09e17e49 3.8.2-dev.C13 2026-03-29 15:43:44 +02:00
proddy
835eb743bb backup/restore #3002 2026-03-29 15:43:35 +02:00
proddy
69a129d80e move NVS initisalisation higher, add check for poll_id == bus ID 2026-03-29 15:43:03 +02:00
proddy
434bf483fd added comment 2026-03-29 15:41:57 +02:00
proddy
2b8e170b40 text changes 2026-03-29 15:41:46 +02:00
proddy
dc9b95f3e7 updated to prevent warning on WiFI mode (shown in debug mode) 2026-03-29 15:41:23 +02:00
proddy
1616b0da0a package update 2026-03-29 15:41:03 +02:00
proddy
91c457b22b don't show EMS bus error on Dashboard, only devices 2026-03-29 15:40:40 +02:00
proddy
70c60647c7 refresh changed from 3 to 5 seconds 2026-03-29 15:40:19 +02:00
Proddy
c0bea66d27 Merge pull request #2996 from MichaelDvP/core3 2026-03-22 10:12:08 +01:00
MichaelDvP
ed7cc078ed fix testdata 2026-03-22 09:53:21 +01:00
Proddy
60b7d6d795 Merge pull request #2995 from MichaelDvP/core3
Core3 updates, adapt to c6 and c3 chips
2026-03-21 18:02:49 +01:00
MichaelDvP
947f29cca0 Merge branch 'core3' of https://github.com/emsesp/EMS-ESP32 into core3 2026-03-21 16:47:05 +01:00
MichaelDvP
d2a13ec0da core3 adaptions for c3 and c6, compiles for all chips 2026-03-21 16:23:07 +01:00
proddy
cc39ba409e package update 2026-03-21 15:49:14 +01:00
MichaelDvP
ac9db6256e Merge branch 'dev' into core3 2026-03-21 11:19:05 +01:00
MichaelDvP
096f628d97 Merge branch 'core3' of https://github.com/emsesp/EMS-ESP32 into core3 2026-03-21 11:02:41 +01:00
proddy
22312812bb system backup 2026-03-20 17:14:58 +01:00
MichaelDvP
3584975acb env: c6 2026-03-19 16:43:18 +01:00
MichaelDvP
30b9ca4e6c Merge branch 'core3' of https://github.com/emsesp/EMS-ESP32 into core3 2026-03-19 16:41:21 +01:00
MichaelDvP
7c6ff01ebe reset reason, uart adapt for C6 2026-03-19 16:39:27 +01:00
proddy
a54edcaf5b formatting 2026-03-18 20:51:40 +01:00
proddy
e446954844 #2971 2026-03-18 20:45:36 +01:00
proddy
4a2d0d6787 package update 2026-03-18 20:35:55 +01:00
proddy
9725314135 Platform 2026.03.50 Tasmota Arduino Core 3.3.7 based on IDF 5.5.3.260313 and ESP32Async/ESPAsyncWebServer @ 3.10.3 2026-03-18 20:18:59 +01:00
MichaelDvP
e610f0d57f Merge branch 'core3' of https://github.com/emsesp/EMS-ESP32 into core3 2026-03-18 14:21:59 +01:00
MichaelDvP
8244af2940 Merge branch 'dev' into core3, fix #2988 2026-03-18 11:47:16 +01:00
proddy
40f371d23b remove check for downloadOnly 2026-03-17 22:01:38 +01:00
proddy
817b791e59 remove flto 2026-03-17 21:47:12 +01:00
proddy
25a7aac360 lint warning fix 2026-03-17 21:47:01 +01:00
proddy
37115a174d show size of firmware not partition 2026-03-17 21:46:51 +01:00
proddy
1397f81fd0 add C in version so we know its Core 2026-03-17 21:46:27 +01:00
proddy
56365cb403 formatting 2026-03-17 21:46:15 +01:00
proddy
dfd245ee7b rename common.h 2026-03-17 21:45:12 +01:00
proddy
9c81e4b34d optimize for vite v8 2026-03-17 21:44:38 +01:00
proddy
67676df131 package update 2026-03-17 21:44:29 +01:00
proddy
a73b129596 update vite v8 2026-03-17 21:44:17 +01:00
Proddy
4600d886b5 Merge pull request #2986 from MichaelDvP/core3
update Core3, change TLS library
2026-03-17 20:27:45 +01:00
MichaelDvP
0fe45a2405 use ESP_SSLClient for mqtt, add sendmail command (using readymail) 2026-03-17 18:53:37 +01:00
MichaelDvP
db87213242 Merge branch 'dev' into core3 2026-03-17 10:19:26 +01:00
MichaelDvP
5c3c010d5a Merge branch 'dev' into core3 2026-03-16 14:02:02 +01:00
MichaelDvP
c804cedd7a update changelog 2026-03-16 14:01:45 +01:00
MichaelDvP
aa30ca99bf update core 2026-03-15 21:41:18 +01:00
MichaelDvP
c0ca9d1069 update pkg 2026-03-15 16:13:50 +01:00
MichaelDvP
5e79e1d57f more messages on network connect 2026-03-15 16:13:21 +01:00
MichaelDvP
8c732f9f1e allow modbus start/stop without reboot 2026-03-15 16:12:50 +01:00
MichaelDvP
5e94c2f636 Merge branch 'dev' into core3 2026-03-15 16:11:36 +01:00
MichaelDvP
64e5d29996 fix typo in HA-climate creation 2026-02-17 09:23:20 +01:00
Proddy
b320d8ded2 Merge pull request #2963 from MichaelDvP/core3
Core3 network fixes
2026-02-16 18:12:33 +01:00
MichaelDvP
0be1b20996 statemachine for network connection replaces onEvent 2026-02-16 10:37:28 +01:00
MichaelDvP
6c55460622 Merge branch 'dev' into core3 2026-02-16 08:34:23 +01:00
MichaelDvP
d627404dc2 skip onEvent for AP, MQTT, NTP 2026-02-16 07:47:13 +01:00
proddy
f317123c26 fix standalone 2026-02-15 15:27:11 +01:00
proddy
e4df1887b0 remove 2026-02-15 14:16:55 +01:00
proddy
34142c3e85 use Tasmota everywhere 2026-02-15 14:16:50 +01:00
proddy
6e7f8bdf02 add -DNO_TLS_SUPPORT 2026-02-15 14:16:27 +01:00
proddy
3dd9fcfb58 update 2026-02-15 14:16:04 +01:00
proddy
35e2954b8b fix lint errors 2026-02-15 14:15:55 +01:00
proddy
59aa63db0f package update 2026-02-15 14:15:43 +01:00
proddy
7a41a190f8 support s3 2026-02-15 14:15:13 +01:00
proddy
6741232450 WIP: ESP-IDF Core 3 migration - mbedtls SSL, module library, board configs, MQTT and network updates 2026-02-15 13:53:13 +01:00
64 changed files with 1029 additions and 630 deletions

View File

@@ -12,6 +12,7 @@ For more details go to [emsesp.org](https://emsesp.org/).
- 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)
- e-mail notification using ReadyMail Client
- 2.nd freshwater module (dhw4, dhw5) [#2991](https://github.com/emsesp/EMS-ESP32/issues/2991)
- full system backup and restore
@@ -31,6 +32,8 @@ For more details go to [emsesp.org](https://emsesp.org/).
- 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)
- use tasmota core 2026.03.30
- secure mqtt uses ESP_SSLClient
- fetch telegrams: set length to fetch [#3017](https://github.com/emsesp/EMS-ESP32/issues/3017)
- move http client from stack to heap
- heap optimizations [#3021](https://github.com/emsesp/EMS-ESP32/discussions/3021)

View File

@@ -47,7 +47,7 @@ MAKEFLAGS += -j$(JOBS) -l$(shell echo $$(($(JOBS) * 2)))
#----------------------------------------------------------------------
TARGET := emsesp
BUILD := build
SOURCES := src/core src/devices src/web src/test lib_standalone lib/espMqttClient/src lib/espMqttClient/src/* lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/PButton
SOURCES := src/core src/devices src/web src/test lib_standalone lib/espMqttClient/src lib/espMqttClient/src/* lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/PButton
INCLUDES := src/core src/devices src/web src/test lib_standalone lib/* lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src
LIBRARIES :=

View File

@@ -5,7 +5,7 @@
},
"core": "esp32",
"extra_flags": [
"-DTASMOTA_SDK",
"-DNO_TLS_SUPPORT",
"-DARDUINO_LOLIN_C3_MINI",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1"

View File

@@ -6,7 +6,7 @@
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DTASMOTA_SDK",
"-DNO_TLS_SUPPORT",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0"
],

View File

@@ -21,7 +21,7 @@
"arduino",
"espidf"
],
"name": "Espressif ESP32-S3 32M Flash OPI PSRAM, 4608KB Code/OTA, 2MB FS",
"name": "Tasmota ESP32-S3 32M Flash OPI PSRAM, 4608KB Code/OTA, 2MB FS",
"upload": {
"flash_size": "32MB",
"maximum_ram_size": 327680,

View File

@@ -1,7 +1,7 @@
{
"build": {
"core": "esp32",
"extra_flags": "-DTASMOTA_SDK",
"extra_flags": "-DNO_TLS_SUPPORT",
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",
@@ -19,7 +19,7 @@
"arduino",
"espidf"
],
"name": "Espressif ESP32 16M Flash, 4608KB Code/OTA, 2MB FS",
"name": "Tasmota ESP32 16M Flash, 4608KB Code/OTA, 2MB FS",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,

View File

@@ -19,7 +19,7 @@
"arduino",
"espidf"
],
"name": "Espressif ESP32 16M Flash DIO PSRAM, 4608KB Code/OTA, 2MB FS",
"name": "Tasmota ESP32 16M Flash DIO PSRAM, 4608KB Code/OTA, 2MB FS",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,

View File

@@ -1,7 +1,7 @@
{
"build": {
"core": "esp32",
"extra_flags": "-DTASMOTA_SDK",
"extra_flags": "-DNO_TLS_SUPPORT",
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",

View File

@@ -2,6 +2,7 @@
"build": {
"core": "esp32",
"extra_flags": [
"-DNO_TLS_SUPPORT",
"-DARDUINO_XIAO_ESP32C6",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1"

View File

@@ -30,7 +30,7 @@
"@mui/material": "^9.0.0",
"@preact/compat": "^18.3.2",
"@table-library/react-table-library": "4.1.15",
"alova": "3.5.1",
"alova": "^3.5.1",
"async-validator": "^4.2.5",
"etag": "^1.8.1",
"formidable": "^3.5.4",
@@ -64,8 +64,7 @@
"terser": "^5.46.1",
"typescript-eslint": "^8.59.0",
"vite": "^8.0.9",
"vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^6.1.1"
"vite-plugin-imagemin": "^0.6.1"
},
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
"packageManager": "pnpm@10.33.1+sha512.05ba3c1d5d1c18f68df06470d74055e62d41fc110a0c660db1b2dfb2785327f04cf0f68345d4609bc52089e7fa0343c31593b2f9594e2c5d5da426230acc9820"
}

View File

@@ -30,7 +30,7 @@ importers:
specifier: 4.1.15
version: 4.1.15(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
alova:
specifier: 3.5.1
specifier: ^3.5.1
version: 3.5.1
async-validator:
specifier: ^4.2.5
@@ -126,9 +126,6 @@ importers:
vite-plugin-imagemin:
specifier: ^0.6.1
version: 0.6.1(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1))
vite-tsconfig-paths:
specifier: ^6.1.1
version: 6.1.1(typescript@6.0.3)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1))
packages:
@@ -1287,8 +1284,8 @@ packages:
resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==}
engines: {node: '>=0.10.0'}
caniuse-lite@1.0.30001788:
resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==}
caniuse-lite@1.0.30001790:
resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==}
caw@2.0.1:
resolution: {integrity: sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==}
@@ -1986,9 +1983,6 @@ packages:
resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==}
engines: {node: '>=8'}
globrex@0.1.2:
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
@@ -3176,16 +3170,6 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
tsconfck@3.1.6:
resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==}
engines: {node: ^18 || >=20}
hasBin: true
peerDependencies:
typescript: ^5.0.0
peerDependenciesMeta:
typescript:
optional: true
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@@ -3274,11 +3258,6 @@ packages:
peerDependencies:
vite: 5.x || 6.x || 7.x || 8.x
vite-tsconfig-paths@6.1.1:
resolution: {integrity: sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==}
peerDependencies:
vite: '*'
vite@8.0.9:
resolution: {integrity: sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -4417,7 +4396,7 @@ snapshots:
browserslist@4.28.2:
dependencies:
baseline-browser-mapping: 2.10.20
caniuse-lite: 1.0.30001788
caniuse-lite: 1.0.30001790
electron-to-chromium: 1.5.343
node-releases: 2.0.38
update-browserslist-db: 1.2.3(browserslist@4.28.2)
@@ -4480,7 +4459,7 @@ snapshots:
camelcase@2.1.1: {}
caniuse-lite@1.0.30001788: {}
caniuse-lite@1.0.30001790: {}
caw@2.0.1:
dependencies:
@@ -5265,8 +5244,6 @@ snapshots:
merge2: 1.4.1
slash: 3.0.0
globrex@0.1.2: {}
gopd@1.2.0: {}
got@7.1.0:
@@ -6400,10 +6377,6 @@ snapshots:
dependencies:
typescript: 6.0.3
tsconfck@3.1.6(typescript@6.0.3):
optionalDependencies:
typescript: 6.0.3
tslib@2.8.1: {}
tunnel-agent@0.6.0:
@@ -6516,16 +6489,6 @@ snapshots:
stack-trace: 1.0.0-pre2
vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)
vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)):
dependencies:
debug: 4.4.3
globrex: 0.1.2
tsconfck: 3.1.6(typescript@6.0.3)
vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)
transitivePeerDependencies:
- supports-color
- typescript
vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1):
dependencies:
lightningcss: 1.32.0

View File

@@ -18,7 +18,7 @@ import { PROJECT_NAME } from 'env';
import { useI18nContext } from 'i18n/i18n-react';
import type { SignInRequest } from 'types';
import { onEnterCallback, updateValue } from 'utils';
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
import { SIGN_IN_REQUEST_VALIDATOR, ValidationError, validate } from 'validators';
const SignIn = memo(() => {
const authenticationContext = useContext(AuthenticationContext);
@@ -74,7 +74,7 @@ const SignIn = memo(() => {
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
await signIn();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
setProcessing(false);
}
}, [signInRequest, signIn, LL]);

View File

@@ -28,7 +28,7 @@ import type { ValidateFieldsError } from 'async-validator';
import { BlockFormControlLabel, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { DeviceValueType, DeviceValueTypeNames, DeviceValueUOM_s } from './types';
import type { EntityItem } from './types';
@@ -136,7 +136,7 @@ const CustomEntitiesDialog = ({
}
onSave(processedItem);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [validator, editItem, onSave]);
@@ -215,7 +215,7 @@ const CustomEntitiesDialog = ({
name="value"
label={LL.DEFAULT(0) + ' ' + LL.VALUE(0)}
type="string"
value={editItem.value as string}
value={editItem.value}
variant="outlined"
onChange={updateFormValue}
fullWidth
@@ -260,7 +260,7 @@ const CustomEntitiesDialog = ({
margin="normal"
sx={{ width: '11ch' }}
type="string"
value={editItem.device_id as string}
value={editItem.device_id}
onChange={updateFormValue}
slotProps={{
input: {
@@ -280,7 +280,7 @@ const CustomEntitiesDialog = ({
margin="normal"
sx={{ width: '11ch' }}
type="string"
value={editItem.type_id as string}
value={editItem.type_id}
onChange={updateFormValue}
slotProps={{
input: {
@@ -381,7 +381,7 @@ const CustomEntitiesDialog = ({
fieldErrors={fieldErrors || {}}
name="factor"
label={LL.BITMASK()}
value={editItem.factor as string}
value={editItem.factor}
sx={{ width: '11ch' }}
variant="outlined"
onChange={updateFormValue}

View File

@@ -24,7 +24,7 @@ import type { ValidateFieldsError } from 'async-validator';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
import type { DeviceValue } from './types';
@@ -67,7 +67,7 @@ const DevicesDialog = ({
await validate(validator, editItem);
onSave(editItem);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [validator, editItem, onSave]);

View File

@@ -26,7 +26,7 @@ import type { ValidateFieldsError } from 'async-validator';
import { BlockFormControlLabel, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { ScheduleFlag } from './types';
import type { ScheduleItem } from './types';
@@ -120,7 +120,7 @@ const SchedulerDialog = ({
await validate(validator, itemToSave);
onSave(itemToSave);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
},
[validator, onSave]

View File

@@ -23,7 +23,7 @@ import type { ValidateFieldsError } from 'async-validator';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types';
import type { AnalogSensor } from './types';
@@ -172,7 +172,7 @@ const SensorsAnalogDialog = ({
await validate(validator, editItem);
onSave(editItem);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [validator, editItem, onSave]);

View File

@@ -21,7 +21,7 @@ import type { ValidateFieldsError } from 'async-validator';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import type { TemperatureSensor } from './types';
@@ -84,7 +84,7 @@ const SensorsTemperatureDialog = ({
await validate(validator, editItem);
onSave(editItem);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [validator, editItem, onSave]);

View File

@@ -43,6 +43,16 @@ export interface Settings {
modbus_port: number;
modbus_max_clients: number;
modbus_timeout: number;
email_enabled: boolean;
email_ssl?: boolean;
email_starttls?: boolean;
email_server: string;
email_port: number;
email_login: string;
email_pass: string;
email_sender: string;
email_recp: string;
email_subject: string;
developer_mode: boolean;
}

View File

@@ -21,7 +21,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import type { APSettingsType } from 'types';
import { APProvisionMode } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils';
import { createAPSettingsValidator, validate } from 'validators';
import { ValidationError, createAPSettingsValidator, validate } from 'validators';
export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
provision_mode === APProvisionMode.AP_MODE_ALWAYS ||
@@ -86,7 +86,7 @@ const APSettings = () => {
await validate(createAPSettingsValidator(data), data);
await saveData();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [data, saveData]);

View File

@@ -28,12 +28,13 @@ import {
FormLoader,
MessageBox,
SectionContent,
ValidatedPasswordField,
ValidatedTextField,
useLayoutTitle
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { API, getBoardProfile, readSettings, writeSettings } from '../../api/app';
import { BOARD_PROFILES } from '../main/types';
@@ -153,7 +154,7 @@ const ApplicationSettings = () => {
setFieldErrors(undefined);
await validate(createSettingsValidator(data), data);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
} finally {
await saveData();
}
@@ -351,6 +352,156 @@ const ApplicationSettings = () => {
</Grid>
</Grid>
)}
<Typography color="secondary">eMail</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.email_enabled}
onChange={updateFormValue}
name="email_enabled"
disabled={!hardwareData.psram}
/>
}
label={
<Typography color={!hardwareData.psram ? 'grey' : 'default'}>
Enable eMail notification
{!hardwareData.psram && (
<Typography variant="caption">
&nbsp; &#40;{LL.IS_REQUIRED('PSRAM')}&#41;
</Typography>
)}
</Typography>
}
/>
{data.email_enabled && (
<>
<Grid
container
spacing={2}
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
name="email_server"
label="SMTP Server"
variant="outlined"
value={data.email_server}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
sx={{ width: '12ch' }}
name="email_port"
variant="outlined"
label="Port"
value={numberValue(data.email_port)}
type="number"
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid size={4} mt={!data.email_ssl && !data.email_starttls ? 0 : 3}>
{!data.email_starttls && (
<BlockFormControlLabel
sx={{ width: '12ch' }}
control={
<Checkbox
checked={data.email_ssl}
onChange={updateFormValue}
name="email_ssl"
disabled={
data.email_starttls || data.email_ssl === undefined
}
/>
}
label="SSL/TLS"
/>
)}
{!data.email_ssl && (
<BlockFormControlLabel
sx={{ width: '12ch' }}
control={
<Checkbox
checked={data.email_starttls}
onChange={updateFormValue}
name="email_starttls"
disabled={
data.email_ssl || data.email_starttls === undefined
}
/>
}
label="STARTTLS"
/>
)}
</Grid>
</Grid>
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
name="email_login"
label="Login"
variant="outlined"
value={data.email_login}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid>
<ValidatedPasswordField
fieldErrors={fieldErrors || {}}
name="email_pass"
label="Password"
variant="outlined"
value={data.email_pass}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
</Grid>
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
name="email_sender"
label="From"
variant="outlined"
value={data.email_sender}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
name="email_recp"
label="To"
variant="outlined"
value={data.email_recp}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
name="email_subject"
label="Subject"
variant="outlined"
value={data.email_subject}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
</Grid>
</>
)}
<Typography sx={{ pb: 1, pt: 2 }} variant="h6" color="primary">
{LL.SENSORS()}
</Typography>

View File

@@ -31,7 +31,7 @@ import {
import { useI18nContext } from 'i18n/i18n-react';
import type { MqttSettingsType } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils';
import { createMqttSettingsValidator, validate } from 'validators';
import { ValidationError, createMqttSettingsValidator, validate } from 'validators';
import { callAction } from '../../api/app';
@@ -94,7 +94,7 @@ const MqttSettings = () => {
await validate(createMqttSettingsValidator(data), data);
await saveData();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [data, saveData]);

View File

@@ -36,7 +36,7 @@ import {
import { useI18nContext } from 'i18n/i18n-react';
import type { NTPSettingsType, Time } from 'types';
import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
import { TIME_ZONES, selectedTimeZone, useTimeZoneSelectItems } from './TZ';
@@ -133,7 +133,7 @@ const NTPSettings = () => {
await validate(NTP_SETTINGS_VALIDATOR, data);
await saveData();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [data, saveData]);

View File

@@ -40,7 +40,7 @@ import {
import { useI18nContext } from 'i18n/i18n-react';
import type { NetworkSettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { createNetworkSettingsValidator } from 'validators/network';
import SystemMonitor from '../../status/SystemMonitor';
@@ -116,7 +116,7 @@ const NetworkSettings = () => {
await validate(createNetworkSettingsValidator(data), data);
await saveData();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
deselectNetwork();
}, [data, saveData, deselectNetwork]);

View File

@@ -19,7 +19,7 @@ import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import type { SecuritySettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils';
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
import { SECURITY_SETTINGS_VALIDATOR, ValidationError, validate } from 'validators';
const SecuritySettings = () => {
const { LL } = useI18nContext();
@@ -58,7 +58,7 @@ const SecuritySettings = () => {
await saveData();
await authenticatedContext.refresh();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [data, saveData, authenticatedContext]);

View File

@@ -24,7 +24,7 @@ import {
import { useI18nContext } from 'i18n/i18n-react';
import type { UserType } from 'types';
import { updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
interface UserFormProps {
creating: boolean;
@@ -69,7 +69,7 @@ const User: FC<UserFormProps> = ({
await validate(validator, user);
onDoneEditing();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}
}, [user, validator, onDoneEditing]);

View File

@@ -315,22 +315,13 @@ const InstallDialog = memo(
fetchDevVersion ? latestDevVersion?.name : latestVersion?.name
)}
</Typography>
{upgradeImportantMessageType === 1 && LL.UPGRADE_IMPORTANT_MESSAGES_1()}
{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"
to="https://docs.emsesp.org/FAQ#upgrading-the-firmware"
style={{ color: 'lightblue' }}
>
{LL.ONLINE_HELP()}

View File

@@ -1,6 +1,20 @@
import type { InternalRuleItem, ValidateOption } from 'async-validator';
import type {
InternalRuleItem,
ValidateFieldsError,
ValidateOption
} from 'async-validator';
import type Schema from 'async-validator';
export class ValidationError extends Error {
readonly fieldErrors: ValidateFieldsError;
constructor(fieldErrors: ValidateFieldsError) {
super('Validation failed');
this.name = 'ValidationError';
this.fieldErrors = fieldErrors;
}
}
export const validate = <T extends object>(
validator: Schema,
source: Partial<T>,
@@ -8,7 +22,7 @@ export const validate = <T extends object>(
): Promise<T> =>
new Promise((resolve, reject) => {
void validator.validate(source, options ?? {}, (errors, fieldErrors) => {
errors ? reject(fieldErrors as Error) : resolve(source as T);
errors ? reject(new ValidationError(fieldErrors)) : resolve(source as T);
});
});

View File

@@ -49,6 +49,10 @@ the LICENSE file.
#define EMC_CLIENTID_LENGTH 23 + 1
#endif
#ifdef EMSESP_MQTT_STACKSIZE
#define EMC_TASK_STACK_SIZE EMSESP_MQTT_STACKSIZE
#endif
#ifndef EMC_TASK_STACK_SIZE
#define EMC_TASK_STACK_SIZE 5120
#endif
@@ -66,14 +70,10 @@ the LICENSE file.
#endif
#if EMC_USE_MEMPOOL
#ifndef EMC_NUM_POOL_ELEMENTS
#define EMC_NUM_POOL_ELEMENTS 32
#endif
#ifndef EMC_SIZE_POOL_ELEMENTS
#define EMC_SIZE_POOL_ELEMENTS 128
#endif
#ifndef EMC_NUM_POOL_ELEMENTS
#define EMC_NUM_POOL_ELEMENTS 32
#endif
#ifndef EMC_SIZE_POOL_ELEMENTS
#define EMC_SIZE_POOL_ELEMENTS 128
#endif
#ifndef TASMOTA_SDK
#define EMC_CLIENT_SECURE
#endif

View File

@@ -62,7 +62,11 @@ MqttClient::MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint
_xSemaphore = xSemaphoreCreateMutex();
EMC_SEMAPHORE_GIVE(); // release before first use
if (_useInternalTask == espMqttClientTypes::UseInternalTask::YES) {
xTaskCreatePinnedToCore((TaskFunction_t)_loop, "mqttclient", EMC_TASK_STACK_SIZE, this, priority, &_taskHandle, core);
if (core > 1) {
xTaskCreate((TaskFunction_t)_loop, "mqttclient", EMC_TASK_STACK_SIZE, this, priority, &_taskHandle);
} else {
xTaskCreatePinnedToCore((TaskFunction_t)_loop, "mqttclient", EMC_TASK_STACK_SIZE, this, priority, &_taskHandle, core);
}
}
#else
(void) useInternalTask;
@@ -70,6 +74,7 @@ MqttClient::MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint
(void) core;
#endif
_clientId = _generatedClientId;
_core = core;
}
MqttClient::~MqttClient() {

View File

@@ -69,7 +69,17 @@ class MqttClient {
const char* getClientId() const;
size_t queueSize(); // No const because of mutex
void loop();
uint32_t stack() {
#ifndef EMSESP_STANDALONE
return uxTaskGetStackHighWaterMark(_taskHandle);
#else
return 0;
#endif
}
uint8_t core() {
return _core;
}
protected:
explicit MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority = 1, uint8_t core = 1);
espMqttClientTypes::UseInternalTask _useInternalTask;
@@ -98,6 +108,7 @@ class MqttClient {
uint8_t _willQos;
bool _willRetain;
uint32_t _timeout;
uint8_t _core;
// state is protected to allow state changes by the transport system, defined in child classes
// eg. to allow AsyncTCP

View File

@@ -8,7 +8,7 @@ the LICENSE file.
#include "ClientPosix.h"
#if defined(__linux__)
#if defined(__linux__) || defined(__APPLE__)
namespace espMqttClientInternals {

View File

@@ -8,7 +8,7 @@ the LICENSE file.
#pragma once
#if defined(__linux__)
#if defined(__linux__) || defined(__APPLE__)
#include <stdio.h>
#include <stdlib.h>

View File

@@ -6,66 +6,65 @@ For a copy, see <https://opensource.org/licenses/MIT> or
the LICENSE file.
*/
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#ifndef NO_TLS_SUPPORT
#include "ClientSecureSync.h"
#include <lwip/sockets.h> // socket options
#include <lwip/sockets.h>
#include "../Config.h"
namespace espMqttClientInternals {
ClientSecureSync::ClientSecureSync()
: client() {
// empty
: client() {
client.setClient(&basic_client, true);
client.setBufferSizes(EMC_RX_BUFFER_SIZE, EMC_TX_BUFFER_SIZE);
client.setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds)
}
ClientSecureSync::~ClientSecureSync() {
stop();
}
bool ClientSecureSync::connect(IPAddress ip, uint16_t port) {
bool ret = client.connect(ip, port); // implicit conversion of return code int --> bool
if (ret) {
#if defined(ARDUINO_ARCH_ESP8266)
client.setNoDelay(true);
#elif defined(ARDUINO_ARCH_ESP32)
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure
int val = true;
client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
#endif
}
return ret;
bool ret = client.connect(ip, port); // implicit conversion of return code int --> bool
if (ret) {
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure
int val = true;
basic_client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
}
return ret;
}
bool ClientSecureSync::connect(const char* host, uint16_t port) {
bool ret = client.connect(host, port); // implicit conversion of return code int --> bool
if (ret) {
#if defined(ARDUINO_ARCH_ESP8266)
client.setNoDelay(true);
#elif defined(ARDUINO_ARCH_ESP32)
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure
int val = true;
client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
#endif
}
return ret;
bool ClientSecureSync::connect(const char * host, uint16_t port) {
bool ret = client.connect(host, port); // implicit conversion of return code int --> bool
if (ret) {
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure
int val = true;
basic_client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
}
return ret;
}
size_t ClientSecureSync::write(const uint8_t* buf, size_t size) {
return client.write(buf, size);
size_t ClientSecureSync::write(const uint8_t * buf, size_t size) {
return client.write(buf, size);
}
int ClientSecureSync::read(uint8_t* buf, size_t size) {
return client.read(buf, size);
int ClientSecureSync::read(uint8_t * buf, size_t size) {
return client.read(buf, size);
}
void ClientSecureSync::stop() {
client.stop();
client.stop();
}
bool ClientSecureSync::connected() {
return client.connected();
return client.connected();
}
bool ClientSecureSync::disconnected() {
return !client.connected();
return !client.connected();
}
} // namespace espMqttClientInternals
} // namespace espMqttClientInternals
#endif
#endif

View File

@@ -8,15 +8,11 @@ the LICENSE file.
#pragma once
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#ifndef NO_TLS_SUPPORT
// Added for EMS-ESP
#include "../Config.h"
#if defined(EMC_CLIENT_SECURE)
#include <WiFiClientSecure.h> // includes IPAddress
#else
// #include "esp_tls.h"
#include <WiFiClient.h>
#endif
#include <ESP_SSLClient.h>
#include "Transport.h"
namespace espMqttClientInternals {
@@ -24,6 +20,7 @@ namespace espMqttClientInternals {
class ClientSecureSync : public Transport {
public:
ClientSecureSync();
~ClientSecureSync();
bool connect(IPAddress ip, uint16_t port) override;
bool connect(const char * host, uint16_t port) override;
size_t write(const uint8_t * buf, size_t size) override;
@@ -31,14 +28,11 @@ class ClientSecureSync : public Transport {
void stop() override;
bool connected() override;
bool disconnected() override;
// added for EMS-ESP
#if defined(EMC_CLIENT_SECURE)
WiFiClientSecure client;
#else
WiFiClient client;
#endif
WiFiClient basic_client;
ESP_SSLClient client;
};
} // namespace espMqttClientInternals
#endif
#endif

View File

@@ -8,50 +8,6 @@ the LICENSE file.
#include "espMqttClient.h"
#if defined(ARDUINO_ARCH_ESP8266)
espMqttClient::espMqttClient()
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
, _client() {
_transport = &_client;
}
espMqttClientSecure::espMqttClientSecure()
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
, _client() {
_transport = &_client;
}
espMqttClientSecure & espMqttClientSecure::setInsecure() {
_client.client.setInsecure();
return *this;
}
espMqttClientSecure & espMqttClientSecure::setFingerprint(const uint8_t fingerprint[20]) {
_client.client.setFingerprint(fingerprint);
return *this;
}
espMqttClientSecure & espMqttClientSecure::setTrustAnchors(const X509List * ta) {
_client.client.setTrustAnchors(ta);
return *this;
}
espMqttClientSecure & espMqttClientSecure::setClientRSACert(const X509List * cert, const PrivateKey * sk) {
_client.client.setClientRSACert(cert, sk);
return *this;
}
espMqttClientSecure & espMqttClientSecure::setClientECCert(const X509List * cert, const PrivateKey * sk, unsigned allowed_usages, unsigned cert_issuer_key_type) {
_client.client.setClientECCert(cert, sk, allowed_usages, cert_issuer_key_type);
return *this;
}
espMqttClientSecure & espMqttClientSecure::setCertStore(CertStoreBase * certStore) {
_client.client.setCertStore(certStore);
return *this;
}
#endif
#if defined(ARDUINO_ARCH_ESP32)
espMqttClient::espMqttClient(espMqttClientTypes::UseInternalTask useInternalTask)
: MqttClientSetup(useInternalTask)
@@ -78,51 +34,45 @@ espMqttClientSecure::espMqttClientSecure(uint8_t priority, uint8_t core)
}
espMqttClientSecure & espMqttClientSecure::setInsecure() {
#if defined(EMC_CLIENT_SECURE)
#ifndef NO_TLS_SUPPORT
_client.client.setInsecure();
#endif
return *this;
}
espMqttClientSecure & espMqttClientSecure::setCACert(const char * rootCA) {
#if defined(EMC_CLIENT_SECURE)
#ifndef NO_TLS_SUPPORT
_client.client.setCACert(rootCA);
#endif
return *this;
}
espMqttClientSecure & espMqttClientSecure::setCertificate(const char * clientCa) {
#if defined(EMC_CLIENT_SECURE)
#ifndef NO_TLS_SUPPORT
_client.client.setCertificate(clientCa);
#endif
return *this;
}
espMqttClientSecure & espMqttClientSecure::setPrivateKey(const char * privateKey) {
#if defined(EMC_CLIENT_SECURE)
#ifndef NO_TLS_SUPPORT
_client.client.setPrivateKey(privateKey);
#endif
return *this;
}
espMqttClientSecure & espMqttClientSecure::setPreSharedKey(const char * pskIdent, const char * psKey) {
#if defined(EMC_CLIENT_SECURE)
_client.client.setPreSharedKey(pskIdent, psKey);
#ifndef NO_TLS_SUPPORT
#endif
return *this;
}
#endif
#if defined(__linux__)
#if defined(__linux__) || defined(__APPLE__)
espMqttClient::espMqttClient()
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
, _client() {
_transport = &_client;
}
#elif defined(_WIN32) || defined(__APPLE__)
// Windows
espMqttClient::espMqttClient()
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO) {
}
#endif

View File

@@ -14,7 +14,7 @@ the LICENSE file.
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#include "Transport/ClientSync.h"
#include "Transport/ClientSecureSync.h"
#elif defined(__linux__)
#elif defined(__linux__) || defined(__APPLE__)
#include "Transport/ClientPosix.h"
#endif
@@ -65,10 +65,16 @@ class espMqttClientSecure : public MqttClientSetup<espMqttClientSecure> {
espMqttClientSecure & setPreSharedKey(const char * pskIdent, const char * psKey);
protected:
#ifndef NO_TLS_SUPPORT
espMqttClientInternals::ClientSecureSync _client;
#else
espMqttClientInternals::ClientSync _client;
#endif
};
#elif defined(__linux__)
#endif
#if defined(__linux__) || defined(__APPLE__)
class espMqttClient : public MqttClientSetup<espMqttClient> {
public:
espMqttClient();
@@ -76,10 +82,4 @@ class espMqttClient : public MqttClientSetup<espMqttClient> {
protected:
espMqttClientInternals::ClientPosix _client;
};
#elif defined(_WIN32) || defined(__APPLE__)
class espMqttClient : public MqttClientSetup<espMqttClient> {
public:
espMqttClient();
};
#endif

View File

@@ -15,5 +15,5 @@
"itty-router": "^5.0.23",
"prettier": "^3.8.3"
},
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
"packageManager": "pnpm@10.33.1+sha512.05ba3c1d5d1c18f68df06470d74055e62d41fc110a0c660db1b2dfb2785327f04cf0f68345d4609bc52089e7fa0343c31593b2f9594e2c5d5da426230acc9820"
}

View File

@@ -21,7 +21,7 @@ extra_configs =
pio_local.ini
[common]
core_build_flags = -std=gnu++2a -O3 -flto=auto -Wno-type-limits -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-format -Wno-missing-field-initializers
core_build_flags = -std=gnu++20 -O3 -flto=auto -Wno-type-limits -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-format -Wno-missing-field-initializers
core_unbuild_flags = -std=gnu++11 -std=gnu++14 -std=gnu++17 -fno-lto
my_build_flags =
@@ -53,13 +53,21 @@ build_flags =
unbuild_flags =
${common.core_unbuild_flags}
; 4MB Flash variants
[espressif32_base_4M]
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/tasmota/platform-espressif32/releases/download/2026.04.50/platform-espressif32.zip ; Platform 2026.04.50 Tasmota Arduino Core 3.3.8 based on IDF 5.5.4.260407
; 16MB Flash variants
[espressif32_base_16M]
framework = arduino
board_build.partitions = partitions/esp32_partition_16M.csv
board_upload.flash_size = 16MB
board_build.app_partition_name = app0
platform = espressif32@6.13.0 ; Arduino Core 2.0.17 / IDF 4.4.7
platform = https://github.com/tasmota/platform-espressif32/releases/download/2026.04.50/platform-espressif32.zip ; Platform 2026.04.50 Tasmota Arduino Core 3.3.8 based on IDF 5.5.4.260407
; 32MB Flash variants
[espressif32_base_32M]
@@ -67,29 +75,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.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
[espressif32_base_T_4M]
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/tasmota/platform-espressif32/releases/download/2024.06.00/platform-espressif32.zip ; Arduino Core 2.0.18 with IPv6 support, based on IDF 4.4.8
; Tasmota Arduino Core 3.1.3.250302 based on IDF 5.3.2.250228
; platform = https://github.com/tasmota/platform-espressif32/releases/download/2025.03.30/platform-espressif32.zip
; use Tasmota's library for 16MB Flash variants.
; Removes libs (like mbedtsl, so no WiFi_secure.h) to increase available heap
[espressif32_base_T_16M]
framework = arduino
board_build.partitions = partitions/esp32_partition_16M.csv
board_upload.flash_size = 16MB
board_build.app_partition_name = app0
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.00/platform-espressif32.zip ; Arduino Core 2.0.18 with IPv6 support, based on IDF 4.4.8
; Tasmota Arduino Core 3.1.3.250302 based on IDF 5.3.2.250228
; platform = https://github.com/tasmota/platform-espressif32/releases/download/2025.03.30/platform-espressif32.zip
platform = https://github.com/tasmota/platform-espressif32/releases/download/2026.04.50/platform-espressif32.zip ; Platform 2026.04.50 Tasmota Arduino Core 3.3.8 based on IDF 5.5.4.260407
[env]
build_flags =
@@ -106,6 +92,8 @@ lib_deps =
bblanchon/ArduinoJson @ 7.4.3
ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.10.3
https://github.com/mobizt/ReadyMail.git @ 0.4.0
https://github.com/mobizt/ESP_SSLClient.git @ 3.1.3
; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
; builds the web interface only, not the firmware
@@ -118,18 +106,17 @@ build_src_filter = -<*>
;
; Builds for different board types
; We use Tasmota for boards without PSRAM as this framework has mbedtls removed to save memory.
; If you're building for a single target environment, we recommend creating a pio_local.ini (see example file)
;
[env:s_4M]
; 4MB ESP32 - no SSL, no PSRAM - like a BBQKees older S32 and E32 models - uses Tasmota
extends = espressif32_base_T_4M
; 4MB ESP32 - no SSL, no PSRAM - like a BBQKees older S32 and E32 models
extends = espressif32_base_4M
board = s_4M
[env:s_16M]
; 16MB ESP32 - no PSRAM - like a BBQKees later S32 V2 models - uses Tasmota
extends = espressif32_base_T_16M
; 16MB ESP32 - no PSRAM - like a BBQKees later S32 V2 models
extends = espressif32_base_16M
board = s_16M
[env:s_16M_P]
@@ -148,24 +135,30 @@ extends = espressif32_base_32M
board = s3_32M_P
[env:s2_4M_P]
; based on lolin_s2_mini 4MB with 2MB PSRAM - uses Tasmota
extends = espressif32_base_T_4M
; based on lolin_s2_mini 4MB with 2MB PSRAM
extends = espressif32_base_4M
board = s2_4M_P
[env:c3_mini_4M]
; based on lolin_c3_mini 4MB, no PSRAM - uses Tasmota
extends = espressif32_base_T_4M
; based on lolin_c3_mini 4MB, no PSRAM
extends = espressif32_base_4M
board = c3_mini_4M
; lolin C3 mini v1 needs special wifi initialization
; https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
[env:c3_miniv1_4M]
extends = espressif32_base_T_4M
extends = espressif32_base_4M
board = c3_mini_4M
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_xiao_4M]
extends = espressif32_base_4M
board_build.app_partition_name = app0
board = seeed_xiao_esp32c6
; foundation for building and testing natively, standalone without an ESP32
; use the `standalone` environment instead of `native` for testing
[env:native]
@@ -175,7 +168,8 @@ build_flags =
build_src_flags =
-DEMSESP_STANDALONE -DEMSESP_TEST
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
-std=gnu++2a -Og -ggdb
-DNO_TLS_SUPPORT
-std=gnu++20 -Og -ggdb
-Wall -Wextra
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
-Wno-vla-cxx-extension -Wno-tautological-constant-out-of-range-compare
@@ -213,8 +207,9 @@ build_src_flags =
-DEMSESP_STANDALONE -DEMSESP_TEST
-DEMSESP_UNITY
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
-DNO_TLS_SUPPORT
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
-std=gnu++2a -Og -ggdb
-std=gnu++20 -Og -ggdb
-Wall -Wextra
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
-Wno-vla-cxx-extension -Wno-tautological-constant-out-of-range-compare

View File

@@ -10,7 +10,6 @@ APSettingsService::APSettingsService(AsyncWebServer * server, FS * fs, SecurityM
, _reconfigureAp(false)
, _connected(0) {
addUpdateHandler([this] { reconfigureAP(); }, false);
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
}
void APSettingsService::begin() {
@@ -19,39 +18,27 @@ void APSettingsService::begin() {
// reconfigureAP();
}
// wait 10 sec on STA disconnect before starting AP
void APSettingsService::WiFiEvent(WiFiEvent_t event) {
const uint8_t was_connected = _connected;
switch (event) {
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
_connected &= ~1U;
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
_connected &= ~2U;
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
_connected |= 1U;
break;
case ARDUINO_EVENT_ETH_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP6:
_connected |= 2U;
break;
default:
return;
}
// wait 10 sec before starting AP
if (was_connected && !_connected) {
_lastManaged = uuid::get_uptime();
}
}
void APSettingsService::reconfigureAP() {
_lastManaged = uuid::get_uptime() - MANAGE_NETWORK_DELAY;
_reconfigureAp = true;
}
void APSettingsService::loop() {
const uint8_t was_connected = _connected;
if (WiFi.isConnected()) {
_connected |= 1U;
} else {
_connected &= ~1U;
}
if (ETH.connected()) {
_connected |= 2U;
} else {
_connected &= ~2U;
}
// wait 10 sec before starting AP
if (was_connected && !_connected) {
_lastManaged = uuid::get_uptime();
}
const unsigned long currentMillis = uuid::get_uptime();
if ((currentMillis - _lastManaged) >= MANAGE_NETWORK_DELAY) {
_lastManaged = currentMillis;
@@ -76,11 +63,7 @@ void APSettingsService::manageAP() {
}
void APSettingsService::startAP() {
#if ESP_IDF_VERSION_MAJOR < 5
WiFi.softAPenableIpV6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922
#else
WiFi.softAPenableIPv6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922
#endif
WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_AP), WIFI_BW_HT20);
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients);

View File

@@ -104,7 +104,6 @@ class APSettingsService : public StatefulService<APSettings> {
void startAP();
void stopAP();
void handleDNS();
void WiFiEvent(WiFiEvent_t event);
};
#endif

View File

@@ -6,7 +6,7 @@
static constexpr const char CACHE_CONTROL[] = "public,max-age=60";
// Single static-content handler serving all assets embedded in WWWData.h.
// Single static-content handler serving all assets embedded in WWWData.h
class StaticContentHandler : public AsyncWebHandler {
public:
bool canHandle(AsyncWebServerRequest * request) const override {
@@ -15,8 +15,7 @@ class StaticContentHandler : public AsyncWebHandler {
}
void handleRequest(AsyncWebServerRequest * request) override {
// OPTIONS is handled generically - the server-level CORS headers are
// attached via DefaultHeaders in ESP32React::begin().
// OPTIONS is handled generically - the server-level CORS headers are attached via DefaultHeaders in ESP32React::begin().
if (request->method() == HTTP_OPTIONS) {
request->send(200);
return;
@@ -57,8 +56,7 @@ class StaticContentHandler : public AsyncWebHandler {
}
// Returns the /index.html asset, used as the SPA fallback for any GET
// that didn't match an embedded asset (React Router handles routing on
// the client side).
// that didn't match an embedded asset (React Router handles routing on the client side).
static const WWWAsset * index_asset() {
static const WWWAsset * cached = nullptr;
if (cached == nullptr) {
@@ -106,4 +104,5 @@ void ESP32React::loop() {
_networkSettingsService.loop();
_apSettingsService.loop();
_mqttSettingsService.loop();
_ntpSettingsService.loop();
}

View File

@@ -9,7 +9,6 @@ MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, Secur
, _disconnectedAt(0)
, _disconnectReason(espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED)
, _mqttClient(nullptr) {
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
addUpdateHandler([this] { onConfigUpdated(); }, false);
}
@@ -29,6 +28,7 @@ MqttSettingsService::~MqttSettingsService() {
void MqttSettingsService::begin() {
_fsPersistence.readFromFS();
startClient();
_reconfigureMqtt = true;
}
void MqttSettingsService::startClient() {
@@ -41,7 +41,7 @@ void MqttSettingsService::startClient() {
delete _mqttClient;
_mqttClient = nullptr;
}
#ifndef TASMOTA_SDK
#ifndef NO_TLS_SUPPORT
if (_state.enableTLS) {
isSecure = true;
if (emsesp::EMSESP::system_.PSram() == 0) {
@@ -79,6 +79,10 @@ void MqttSettingsService::startClient() {
}
void MqttSettingsService::loop() {
if (_state.enabled && _mqttClient && _mqttClient->connected() && !emsesp::EMSESP::system_.network_connected()) {
// emsesp::EMSESP::logger().info("Network connection dropped, stopping MQTT client");
_mqttClient->disconnect(true);
}
if (_reconfigureMqtt || (_disconnectedAt && static_cast<uint32_t>(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
// reconfigure MQTT client
_disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime();
@@ -142,28 +146,6 @@ void MqttSettingsService::onConfigUpdated() {
emsesp::EMSESP::mqtt_.start(); // reload EMS-ESP MQTT settings
}
void MqttSettingsService::WiFiEvent(WiFiEvent_t event) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP6:
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
if (_state.enabled && !_mqttClient->connected()) {
onConfigUpdated();
}
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
case ARDUINO_EVENT_ETH_DISCONNECTED:
if (_state.enabled) {
_mqttClient->disconnect(true);
}
break;
default:
break;
}
}
bool MqttSettingsService::configureMqtt() {
// disconnect if already connected
if (_mqttClient->connected()) {
@@ -182,7 +164,7 @@ bool MqttSettingsService::configureMqtt() {
}
_reconfigureMqtt = false;
#ifndef TASMOTA_SDK
#ifndef NO_TLS_SUPPORT
if (_state.enableTLS) {
if (_state.rootCA == "insecure") {
emsesp::EMSESP::logger().debug("Start insecure MQTT");
@@ -219,7 +201,7 @@ bool MqttSettingsService::configureMqtt() {
}
void MqttSettings::read(MqttSettings & settings, JsonObject root) {
#ifndef TASMOTA_SDK
#ifndef NO_TLS_SUPPORT
root["enableTLS"] = settings.enableTLS;
root["rootCA"] = settings.rootCA;
#endif
@@ -258,7 +240,7 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
MqttSettings newSettings;
bool changed = false;
#ifndef TASMOTA_SDK
#ifndef NO_TLS_SUPPORT
newSettings.enableTLS = root["enableTLS"];
newSettings.rootCA = root["rootCA"] | "";
#else
@@ -389,7 +371,7 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
emsesp::EMSESP::mqtt_.set_publish_time_heartbeat(newSettings.publish_time_heartbeat);
}
#ifndef TASMOTA_SDK
#ifndef NO_TLS_SUPPORT
// strip down to certificate only
newSettings.rootCA.replace("\r", "");
newSettings.rootCA.replace("\n", "");

View File

@@ -134,7 +134,6 @@ class MqttSettingsService : public StatefulService<MqttSettings> {
// the MQTT client instance
MqttClient * _mqttClient;
void WiFiEvent(WiFiEvent_t event);
void onMqttConnect(bool sessionPresent);
void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason);
void

View File

@@ -11,36 +11,17 @@ NTPSettingsService::NTPSettingsService(AsyncWebServer * server, FS * fs, Securit
configureTime(request, json);
});
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
addUpdateHandler([this] { configureNTP(); }, false);
}
void NTPSettingsService::begin() {
_fsPersistence.readFromFS();
configureNTP();
}
// handles both WiFI and Ethernet
void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
case ARDUINO_EVENT_ETH_DISCONNECTED:
if (_connected && emsesp::EMSESP::system_.ntp_connected()) {
emsesp::EMSESP::logger().info("WiFi connection dropped, stopping NTP");
_connected = false;
configureNTP();
}
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_ETH_GOT_IP:
// emsesp::EMSESP::logger().info("Got IP address, starting NTP synchronization");
_connected = true;
void NTPSettingsService::loop() {
if (_connected != emsesp::EMSESP::system_.network_connected()) {
_connected = emsesp::EMSESP::system_.network_connected();
configureNTP();
break;
default:
break;
}
}
@@ -55,7 +36,9 @@ void NTPSettingsService::configureNTP() {
} else {
setenv("TZ", _state.tzFormat.c_str(), 1);
tzset();
esp_sntp_stop();
if (esp_sntp_enabled()) {
esp_sntp_stop();
}
}
}
@@ -68,7 +51,12 @@ void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVari
tm.tm_isdst = -1; // not set by strptime, tells mktime to determine daylightsaving
time_t time = mktime(&tm);
struct timeval now = {.tv_sec = time, .tv_usec = {}};
#if CONFIG_IDF_TARGET_ESP32C3
// settimeofday and adjtime() does not work, unknown how to set time
emsesp::EMSESP::logger().warning("manual clock setting not possible");
#else
settimeofday(&now, nullptr);
#endif
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
return;

View File

@@ -44,6 +44,7 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin();
void loop();
static void ntp_received(struct timeval * tv);
private:
@@ -51,7 +52,6 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
FSPersistence<NTPSettings> _fsPersistence;
volatile bool _connected;
void WiFiEvent(WiFiEvent_t event);
void configureNTP();
void configureTime(AsyncWebServerRequest * request, JsonVariant json);
};

View File

@@ -9,7 +9,7 @@ NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs,
, _stopping(false) {
addUpdateHandler([this] { reconfigureWiFiConnection(); }, false);
// Eth is also bound to the WifiGeneric event handler
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event, info); });
// Network.onEvent([this](arduino_event_id_t event, arduino_event_info_t info) { WiFiEvent(event, info); });
}
static bool formatBssid(const String & bssid, uint8_t (&mac)[6]) {
@@ -34,8 +34,8 @@ void NetworkSettingsService::begin() {
WiFi.persistent(false);
WiFi.setAutoReconnect(false);
WiFi.mode(WIFI_MODE_MAX);
WiFi.mode(WIFI_MODE_NULL);
WiFi.mode(WIFI_STA);
WiFi.mode(WIFI_OFF);
// scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN
@@ -62,28 +62,124 @@ void NetworkSettingsService::loop() {
_lastConnectionAttempt = currentMillis;
manageSTA();
}
static uint8_t connect = 0;
enum uint8_t {
CONNECT_IDLE = 0,
CONNECT_WAIT_ETH,
CONNECT_WAIT_IP4,
CONNECT_WAIT_ETH_IP4,
CONNECT_WAIT_IP6,
CONNECT_WAIT_ETH_IP6,
CONNECT_ETH_ACTIVE,
CONNECT_WIFI_ACTIVE
};
switch (connect) {
default:
connect = CONNECT_IDLE;
break;
case CONNECT_IDLE:
if (ETH.started() && _state.ssid.length() == 0) {
emsesp::EMSESP::logger().info("ETH started");
ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str());
ETH.enableIPv6(true);
if (_state.staticIPConfig) {
ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
}
connect = CONNECT_WAIT_ETH;
}
if (WiFi.isConnected()) {
emsesp::EMSESP::logger().info("Wifi connected");
if (_state.tx_power == 0) {
setWiFiPowerOnRSSI();
}
mDNS_start();
emsesp::EMSESP::system_.has_ipv6(true);
connect = CONNECT_WAIT_IP4;
}
break;
case CONNECT_WAIT_ETH:
if (ETH.connected()) {
emsesp::EMSESP::logger().info("ETH connected");
emsesp::EMSESP::system_.ethernet_connected(true);
mDNS_start();
emsesp::EMSESP::system_.has_ipv6(true);
connect = CONNECT_WAIT_ETH_IP4;
}
break;
case CONNECT_WAIT_ETH_IP4:
if (ETH.hasIP()) {
emsesp::EMSESP::logger().info("ETH IPv4: %s", ETH.localIP().toString().c_str());
connect = CONNECT_WAIT_ETH_IP6;
}
if (!ETH.connected()) {
connect = CONNECT_ETH_ACTIVE;
}
break;
case CONNECT_WAIT_ETH_IP6:
if (ETH.hasLinkLocalIPv6() && ETH.hasGlobalIPv6()) {
emsesp::EMSESP::system_.has_ipv6(true);
connect = CONNECT_ETH_ACTIVE;
}
if (!ETH.connected()) {
connect = CONNECT_ETH_ACTIVE;
}
break;
case CONNECT_ETH_ACTIVE:
if (!ETH.connected()) {
emsesp::EMSESP::logger().info("ETH disconnected");
emsesp::EMSESP::system_.ethernet_connected(false);
emsesp::EMSESP::system_.has_ipv6(false);
connect = CONNECT_IDLE;
}
break;
case CONNECT_WAIT_IP4:
if (!WiFi.localIP().toString().isEmpty()) {
emsesp::EMSESP::logger().info("Wifi IPv4: %s", WiFi.localIP().toString().c_str());
connect = CONNECT_WAIT_IP6;
}
if (!WiFi.isConnected()) {
connect = CONNECT_ETH_ACTIVE;
}
break;
case CONNECT_WAIT_IP6:
if (WiFi.linkLocalIPv6().toString() != "::" && WiFi.globalIPv6().toString() != "::") {
emsesp::EMSESP::logger().info("Wifi IPv6: %s, %s", WiFi.linkLocalIPv6().toString().c_str(), WiFi.globalIPv6().toString().c_str());
emsesp::EMSESP::system_.has_ipv6(true);
connect = CONNECT_WIFI_ACTIVE;
}
if (!WiFi.isConnected()) {
connect = CONNECT_WIFI_ACTIVE;
}
break;
case CONNECT_WIFI_ACTIVE:
if (!WiFi.isConnected()) {
emsesp::EMSESP::logger().info("WiFi disconnected");
if (_stopping) {
_lastConnectionAttempt = 0;
_stopping = false;
}
emsesp::EMSESP::system_.has_ipv6(false);
connect = CONNECT_IDLE;
}
break;
}
}
void NetworkSettingsService::manageSTA() {
// Abort if already connected, or if we have no SSID
if (WiFi.isConnected() || _state.ssid.length() == 0) {
#if ESP_IDF_VERSION_MAJOR >= 5
if (_state.ssid.length() == 0) {
ETH.enableIPv6(true);
}
#endif
return;
}
// Connect or reconnect as required
if ((WiFi.getMode() & WIFI_STA) == 0) {
#if ESP_IDF_VERSION_MAJOR >= 5
WiFi.setHostname(_state.hostname.c_str()); // updates shared default_hostname buffer
WiFi.enableSTA(true); // creates the STA netif
WiFi.STA.setHostname(_state.hostname.c_str()); // pushes to esp_netif_set_hostname
WiFi.enableIPv6(true);
#endif
if (_state.staticIPConfig) {
WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); // configure for static IP
}
WiFi.setHostname(_state.hostname.c_str()); // set hostname
// www.esp32.com/viewtopic.php?t=12055
if (_state.bandwidth20) {
@@ -293,7 +389,7 @@ const char * NetworkSettingsService::disconnectReason(uint8_t code) {
}
// handles both WiFI and Ethernet
void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
void NetworkSettingsService::WiFiEvent(arduino_event_id_t event, arduino_event_info_t info) {
#ifndef EMSESP_STANDALONE
switch (event) {
@@ -305,7 +401,7 @@ void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
connectcount_++; // count the number of WiFi reconnects
connectcount_ = connectcount_ + 1; // count the number of WiFi reconnects
emsesp::EMSESP::logger().warning("WiFi disconnected (#%d). Reason: %s (%d)",
connectcount_,
disconnectReason(info.wifi_sta_disconnected.reason),
@@ -325,11 +421,11 @@ void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
break;
case ARDUINO_EVENT_ETH_START:
// configure for static IP
// apply hostname FIRST so DHCP DISCOVER carries the correct name
ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str());
if (_state.staticIPConfig) {
ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
}
ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str());
break;
case ARDUINO_EVENT_ETH_GOT_IP:
@@ -360,25 +456,15 @@ void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
if (_state.tx_power == 0) {
setWiFiPowerOnRSSI();
}
#if ESP_IDF_VERSION_MAJOR < 5
WiFi.enableIpV6(); // force ipv6
#endif
break;
case ARDUINO_EVENT_ETH_CONNECTED:
#if ESP_IDF_VERSION_MAJOR < 5
ETH.enableIpV6(); // force ipv6
#endif
break;
// IPv6 specific - WiFi/Eth
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
case ARDUINO_EVENT_ETH_GOT_IP6: {
#if !TASMOTA_SDK && ESP_IDF_VERSION_MAJOR < 5
auto ip6 = IPv6Address((uint8_t *)info.got_ip6.ip6_info.ip.addr).toString();
#else
auto ip6 = IPAddress(IPv6, (uint8_t *)info.got_ip6.ip6_info.ip.addr, 0).toString();
#endif
auto ip6 = IPAddress(IPv6, (uint8_t *)info.got_ip6.ip6_info.ip.addr, 0).toString();
const char * link = event == ARDUINO_EVENT_ETH_GOT_IP6 ? "Eth" : "WiFi";
if (ip6.startsWith("fe80")) {
emsesp::EMSESP::logger().info("IPv6 (%s) local: %s", link, ip6.c_str());

View File

@@ -107,7 +107,7 @@ class NetworkSettingsService : public StatefulService<NetworkSettings> {
volatile uint16_t connectcount_ = 0; // number of wifi reconnects
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
void WiFiEvent(arduino_event_id_t event, arduino_event_info_t info);
void mDNS_start() const;
const char * disconnectReason(uint8_t code);
void reconfigureWiFiConnection();

View File

@@ -2,7 +2,7 @@
#include <emsesp.h>
#ifdef TASMOTA_SDK
#ifdef NO_TLS_SUPPORT
#include "lwip/dns.h"
#endif
@@ -31,22 +31,13 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
// for both connections show ethernet
if (ethernet_connected) {
// Ethernet
root["local_ip"] = ETH.localIP().toString();
#if ESP_IDF_VERSION_MAJOR < 5
root["local_ipv6"] = ETH.localIPv6().toString();
#else
root["local_ipv6"] = ETH.linkLocalIPv6().toString();
#endif
root["local_ip"] = ETH.localIP().toString();
root["local_ipv6"] = ETH.linkLocalIPv6().toString();
root["mac_address"] = ETH.macAddress();
root["subnet_mask"] = ETH.subnetMask().toString();
root["gateway_ip"] = ETH.gatewayIP().toString();
#ifdef TASMOTA_SDK
IPAddress dnsIP1 = IPAddress(dns_getserver(0));
IPAddress dnsIP2 = IPAddress(dns_getserver(1));
#else
IPAddress dnsIP1 = ETH.dnsIP(0);
IPAddress dnsIP2 = ETH.dnsIP(1);
#endif
IPAddress dnsIP1 = ETH.dnsIP(0);
IPAddress dnsIP2 = ETH.dnsIP(1);
if (IPUtils::isSet(dnsIP1)) {
root["dns_ip_1"] = dnsIP1.toString();
}
@@ -54,12 +45,8 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
root["dns_ip_2"] = dnsIP2.toString();
}
} else if (wifi_status == WL_CONNECTED) {
root["local_ip"] = WiFi.localIP().toString();
#if ESP_IDF_VERSION_MAJOR < 5
root["local_ipv6"] = WiFi.localIPv6().toString();
#else
root["local_ipv6"] = WiFi.linkLocalIPv6().toString();
#endif
root["local_ip"] = WiFi.localIP().toString();
root["local_ipv6"] = WiFi.linkLocalIPv6().toString();
root["mac_address"] = WiFi.macAddress();
root["rssi"] = WiFi.RSSI();
root["ssid"] = WiFi.SSID();
@@ -71,14 +58,8 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
if (WiFi.gatewayIP() != INADDR_NONE) {
root["gateway_ip"] = WiFi.gatewayIP().toString();
}
#ifdef TASMOTA_SDK
IPAddress dnsIP1 = IPAddress(dns_getserver(0));
IPAddress dnsIP2 = IPAddress(dns_getserver(1));
#else
IPAddress dnsIP1 = WiFi.dnsIP(0);
IPAddress dnsIP2 = WiFi.dnsIP(1);
#endif
if (dnsIP1 != INADDR_NONE) {
root["dns_ip_1"] = dnsIP1.toString();
}

View File

@@ -347,23 +347,13 @@ void AnalogSensor::reload(bool get_nvs) {
sensor.polltime_ = sensor.value() != 0 ? uuid::get_uptime() + (sensor.factor() * 1000) : 0;
} else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) {
LOG_DEBUG("PWM output on GPIO %02d", sensor.gpio());
#if ESP_IDF_VERSION_MAJOR >= 5
ledcAttach(sensor.gpio(), sensor.factor(), 13);
#else
uint8_t channel = sensor.type() - AnalogType::PWM_0;
ledcSetup(channel, sensor.factor(), 13);
ledcAttachPin(sensor.gpio(), channel);
#endif
if (sensor.offset() > 100) {
sensor.set_offset(100);
} else if (sensor.offset() < 0) {
sensor.set_offset(0);
}
#if ESP_IDF_VERSION_MAJOR >= 5
ledcWrite(sensor.gpio(), (uint32_t)(sensor.offset() * 8191 / 100));
#else
ledcWrite(channel, (uint32_t)(sensor.offset() * 8191 / 100));
#endif
sensor.set_value(sensor.offset());
sensor.set_uom(DeviceValueUOM::PERCENT);
publish_sensor(sensor);
@@ -1041,12 +1031,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
}
sensor.set_offset(val);
sensor.set_value(val);
#if ESP_IDF_VERSION_MAJOR >= 5
ledcWrite(sensor.gpio(), (uint32_t)(sensor.offset() * 8191 / 100));
#else
uint8_t channel = sensor.type() - AnalogType::PWM_0;
ledcWrite(channel, (uint32_t)(val * 8191 / 100));
#endif
} else {
return false;
}

View File

@@ -285,6 +285,8 @@ enum {
#define EMSESP_PLATFORM "ESP32S3"
#elif CONFIG_IDF_TARGET_ESP32 || EMSESP_STANDALONE
#define EMSESP_PLATFORM "ESP32"
#elif CONFIG_IDF_TARGET_ESP32C6
#define EMSESP_PLATFORM "ESP32C6"
#else
#error Target CONFIG_IDF_TARGET is not supported
#endif
@@ -293,12 +295,9 @@ enum {
#ifndef STRINGIZE
#define STRINGIZE(s) #s
#endif
#ifdef TASMOTA_SDK
#define ARDUINO_VERSION_STR(major, minor, patch) "Tasmota Arduino v" STRINGIZE(major) "." STRINGIZE(minor) "." STRINGIZE(patch)
#else
#define ARDUINO_VERSION_STR(major, minor, patch) "ESP32 Arduino v" STRINGIZE(major) "." STRINGIZE(minor) "." STRINGIZE(patch)
#endif
#define ARDUINO_VERSION ARDUINO_VERSION_STR(ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH)
#endif
#endif
#endif

View File

@@ -1727,6 +1727,8 @@ void EMSESP::start() {
#ifndef EMSESP_STANDALONE
if (factory_settings) {
LOG_WARNING("No settings found on filesystem. Using factory settings.");
// make sure OTAdata is updated with core3 format
esp_ota_set_boot_partition(esp_ota_get_running_partition());
}
#endif

View File

@@ -380,11 +380,15 @@ std::string commands(std::string & expr, bool quotes) {
if (return_code != CommandRet::OK && return_code != CommandRet::NO_VALUE) {
return expr = "";
}
std::string data = output["api_data"] | "";
if (!isnum(data) && quotes) {
data.insert(data.begin(), '"');
data.insert(data.end(), '"');
std::string data;
if (output["api_data"].is<std::string>()) {
data = output["api_data"].as<std::string>();
if (!isnum(data) && quotes) {
data.insert(data.begin(), '"');
data.insert(data.end(), '"');
}
} else {
serializeJson(output, data);
}
expr.replace(f, l, data);
e = f + data.length();
@@ -700,8 +704,7 @@ std::string compute(const std::string & expr) {
std::string cmd = expr_new.substr(f, e - f).c_str();
JsonDocument doc;
if (DeserializationError::Ok == deserializeJson(doc, cmd)) {
HTTPClient * http = new HTTPClient;
std::string url, header_s, value_s, method_s, key_s, keys_s;
std::string url, header_s, value_s, method_s, key_s, keys_s;
// search keys lower case
for (JsonPair p : doc.as<JsonObject>()) {
if (Helpers::toLower(p.key().c_str()) == "url") {
@@ -720,58 +723,150 @@ std::string compute(const std::string & expr) {
keys_s = p.key().c_str();
}
}
if (http->begin(url.c_str())) {
int httpResult = 0;
for (JsonPair p : doc[header_s].as<JsonObject>()) {
http->addHeader(p.key().c_str(), p.value().as<std::string>().c_str());
bool content_set = false;
std::string value = doc[value_s] | "";
std::string method = doc[method_s] | "GET";
if (value.length()) {
method = "POST";
}
std::string result;
int httpResult = 0;
#ifndef NO_TLS_SUPPORT
if (Helpers::toLower(url.c_str()).starts_with("https://")) {
WiFiClient * basic_client = new WiFiClient;
ESP_SSLClient * ssl_client = new ESP_SSLClient;
ssl_client->setInsecure(); // with root CA we should set here: ssl_client->setCACert(rootCACert);
ssl_client->setBufferSizes(1024, 1024);
ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds)
url.replace(0, 8, "");
std::string host = url;
auto index = url.find_first_of('/');
if (index != std::string::npos) {
host = url.substr(0, index);
url.replace(0, index, "");
}
std::string value = doc[value_s] | "";
std::string method = doc[method_s] | "get";
// if there is data, force a POST
if (value.length() || Helpers::toLower(method) == "post") {
if (value.find_first_of('{') != std::string::npos) {
http->addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON
}
httpResult = http->POST(value.c_str());
} else {
httpResult = http->GET(); // normal GET
/*
index = host.find_first_of('@');
std::string auth;
if (index != std::string::npos) {
auth = base64::encode(host.substr(0, index));
host.replace(0, index, "");
}
if (httpResult > 0) {
std::string result = http->getString().c_str();
std::string key = doc[key_s] | "";
JsonDocument keys_doc; // JsonDocument to hold "keys" after doc is parsed with HTTP body
if (doc[keys_s].is<JsonArray>()) {
keys_doc.set(doc[keys_s].as<JsonArray>());
}
JsonArray keys = keys_doc.as<JsonArray>();
if (key.length() || !keys.isNull()) {
doc.clear();
if (DeserializationError::Ok == deserializeJson(doc, result)) {
if (key.length()) {
result = doc[key.c_str()].as<std::string>();
*/
ssl_client->setClient(basic_client);
if (ssl_client->connect(host.c_str(), 443)) {
if (value.length() || Helpers::toLower(method) == "post") {
ssl_client->print("POST ");
ssl_client->print(url.c_str());
ssl_client->println(" HTTP/1.1");
ssl_client->print("Host: ");
ssl_client->println(host.c_str());
for (JsonPair p : doc[header_s].as<JsonObject>()) {
content_set |= (emsesp::Helpers::toLower(p.key().c_str()) == "content-type");
ssl_client->print(p.key().c_str());
ssl_client->print(": ");
ssl_client->println(p.value().as<std::string>().c_str());
}
if (!content_set) {
ssl_client->print("Content-Type: ");
if (value.starts_with('{')) {
ssl_client->println(asyncsrv::T_application_json);
} else {
JsonVariant json = doc.as<JsonVariant>();
for (JsonVariant keys_key : keys) {
if (keys_key.is<std::string>() && json.is<JsonObject>()) {
json = json[keys_key.as<std::string>()].as<JsonVariant>();
} else if (keys_key.is<int>() && json.is<JsonArray>()) {
json = json[keys_key.as<int>()].as<JsonVariant>();
} else {
break; // type mismatch
}
}
result = json.as<std::string>();
ssl_client->println(asyncsrv::T_text_plain);
}
}
ssl_client->print("Content-Length: ");
ssl_client->println(value.length());
ssl_client->println("Connection: close");
ssl_client->print("\r\n");
ssl_client->print(value.c_str());
} else {
ssl_client->print("GET ");
ssl_client->print(url.c_str());
ssl_client->println(" HTTP/1.1");
ssl_client->print("Host: ");
ssl_client->println(host.c_str());
for (JsonPair p : doc[header_s].as<JsonObject>()) {
ssl_client->print(p.key().c_str());
ssl_client->print(": ");
ssl_client->println(p.value().as<std::string>().c_str());
}
ssl_client->println("Connection: close");
}
auto ms = millis();
while (!ssl_client->available() && millis() - ms < 3000) {
delay(0);
}
while (ssl_client->available()) {
result += (char)ssl_client->read();
}
ssl_client->stop();
index = result.find_first_of(' ');
if (index != std::string::npos) {
httpResult = stoi(result.substr(index + 1, 3));
}
index = result.find("\r\n\r\n");
if (index != std::string::npos) {
result.replace(0, index + 4, "");
}
}
delete ssl_client;
delete basic_client;
} else
#endif
if (Helpers::toLower(url.c_str()).starts_with("http://")) {
HTTPClient * http = new HTTPClient;
if (http->begin(url.c_str())) {
for (JsonPair p : doc[header_s].as<JsonObject>()) {
http->addHeader(p.key().c_str(), p.value().as<std::string>().c_str());
content_set |= (emsesp::Helpers::toLower(p.key().c_str()) == "content-type");
}
if (value.length() || Helpers::toLower(method) == "post") {
if (!content_set) {
http->addHeader("Content-Type", value.starts_with('{') ? asyncsrv::T_application_json : asyncsrv::T_text_plain);
}
httpResult = http->POST(value.c_str());
} else {
httpResult = http->GET(); // normal GET
}
if (httpResult > 0) {
result = http->getString().c_str();
}
expr_new.replace(f, e - f, result.c_str());
}
http->end();
delete http;
}
if (httpResult == 200) {
std::string key = doc[key_s] | "";
JsonDocument keys_doc; // JsonDocument to hold "keys" after doc is parsed with HTTP body
if (doc[keys_s].is<JsonArray>()) {
keys_doc.set(doc[keys_s].as<JsonArray>());
}
JsonArray keys = keys_doc.as<JsonArray>();
if (key.length() || !keys.isNull()) {
doc.clear();
if (DeserializationError::Ok == deserializeJson(doc, result)) {
if (key.length()) {
result = doc[key.c_str()].as<std::string>();
} else {
JsonVariant json = doc.as<JsonVariant>();
for (JsonVariant keys_key : keys) {
if (keys_key.is<std::string>() && json.is<JsonObject>()) {
json = json[keys_key.as<std::string>()].as<JsonVariant>();
} else if (keys_key.is<int>() && json.is<JsonArray>()) {
json = json[keys_key.as<int>()].as<JsonVariant>();
} else {
break; // type mismatch
}
}
result = json.as<std::string>();
}
}
}
expr_new.replace(f, e - f, result);
} else if (httpResult != 0) {
EMSESP::logger().warning("URL command failed with https code: %d, response: %s", httpResult, result.c_str());
}
}
f = expr_new.find_first_of('{', e);
}

View File

@@ -37,6 +37,15 @@
#include "../test/test.h"
#endif
#ifndef NO_TLS_SUPPORT
#define ENABLE_SMTP
#define USE_ESP_SSLCLIENT
#define READYCLIENT_SSL_CLIENT ESP_SSLClient
#define READYCLIENT_TYPE_1 // TYPE 1 when using ESP_SSLClient
#include <ESP_SSLClient.h>
#include <ReadyMail.h>
#endif
namespace emsesp {
// Languages supported. Note: the order is important
@@ -105,6 +114,115 @@ bool System::command_send(const char * value, const int8_t id) {
return EMSESP::txservice_.send_raw(value); // ignore id
}
bool System::command_sendmail(const char * value, const int8_t id) {
bool enabled = false;
bool ssl, starttls;
uint16_t port;
String server, login, pass, sender, recp, subject;
EMSESP::webSettingsService.read([&](WebSettings & settings) {
enabled = settings.email_enabled;
ssl = settings.email_ssl;
starttls = settings.email_starttls;
server = settings.email_server;
port = settings.email_port;
login = settings.email_login;
pass = settings.email_pass;
sender = settings.email_sender;
recp = settings.email_recp;
subject = settings.email_subject;
});
if (!enabled) {
return false;
}
LOG_DEBUG("Command sendmail port %d%s called with '%s'", port, ssl ? " (SSL)" : starttls ? " (STARTTLS)" : "", value);
// LOG_DEBUG("Command sendmail port %d called with '%s'", port, value);
bool success = false;
#ifndef NO_TLS_SUPPORT
WiFiClient * basic_client = new WiFiClient;
ESP_SSLClient * ssl_client = new ESP_SSLClient;
ReadyClient * r_client = new ReadyClient(*ssl_client);
SMTPClient * smtp = new SMTPClient(*r_client);
ssl_client->setClient(basic_client);
ssl_client->setInsecure();
ssl_client->setBufferSizes(1024, 1024);
r_client->addPort(port, starttls ? readymail_protocol_tls : ssl ? readymail_protocol_ssl : readymail_protocol_plain_text);
// smtp->connect(server, port, sendmailCallback);
smtp->connect(server, port);
if (!smtp->isConnected()) {
LOG_ERROR("Sendmail connection error");
delete smtp;
delete r_client;
delete ssl_client;
delete basic_client;
return false;
}
// LOG_INFO("authenticate %s:%s", login.c_str(), pass.c_str());
smtp->authenticate(login, pass, readymail_auth_password);
if (!smtp->isAuthenticated()) {
LOG_ERROR("Sendmail authenticate error");
delete smtp;
delete r_client;
delete ssl_client;
delete basic_client;
return false;
}
JsonDocument doc;
String body = value;
if (body.length()) {
auto error = deserializeJson(doc, (const char *)value);
if (!error && doc.as<JsonObject>().size() >= 0) {
subject = doc["subject"] | subject;
recp = doc["to"] | recp;
sender = doc["from"] | sender;
body = doc["body"] | body;
}
}
SMTPMessage & msg = smtp->getMessage();
msg.headers.add(rfc822_subject, subject);
msg.headers.add(rfc822_from, sender);
msg.headers.add(rfc822_to, recp);
// Use addCustom to add custom header e.g. Importance and Priority.
// msg.headers.addCustom("Importance", PRIORITY);
// msg.headers.addCustom("X-MSMail-Priority", PRIORITY);
// msg.headers.addCustom("X-Priority", PRIORITY_NUM);
EMSESP::webSchedulerService.computed_value.clear();
EMSESP::webSchedulerService.raw_value = body.c_str();
for (uint16_t wait = 0; wait < 2000 && !EMSESP::webSchedulerService.raw_value.empty(); wait++) {
delay(1);
}
if (!EMSESP::webSchedulerService.computed_value.empty()) {
body = EMSESP::webSchedulerService.computed_value.c_str();
EMSESP::webSchedulerService.computed_value.clear();
EMSESP::webSchedulerService.computed_value.shrink_to_fit(); // free allocated memory
}
msg.text.body(body);
// bodyText.replace("\r\n", "<br>\r\n");
// msg.html.body("<html><body><div style=\"color:#cc0066;\">" + bodyText + "</div></body></html>");
// msg.html.transferEncoding("base64");
// With embedFile function, the html message will send as attachment.
// if (EMBED_MESSAGE)
// msg.html.embedFile(true, "msg.html", embed_message_type_attachment);
msg.timestamp = time(nullptr);
success = smtp->send(msg);
delete smtp;
delete r_client;
delete ssl_client;
delete basic_client;
#endif
return success;
}
// return string of languages and count
std::string System::languages_string() {
std::string languages_string = std::to_string(NUM_LANGUAGES) + " languages (";
@@ -237,7 +355,8 @@ bool System::command_message(const char * value, const int8_t id, JsonObject out
LOG_INFO("Message: %s", EMSESP::webSchedulerService.computed_value.c_str()); // send to log
Mqtt::queue_publish(F_(message), EMSESP::webSchedulerService.computed_value); // send to MQTT if enabled
output["api_data"] = EMSESP::webSchedulerService.computed_value; // send to API
EMSESP::webSchedulerService.computed_value.clear();
EMSESP::webSchedulerService.computed_value.shrink_to_fit();
return true;
}
@@ -461,7 +580,6 @@ void System::system_restart(const char * partitionname) {
Mqtt::disconnect(); // gracefully disconnect MQTT, needed for QOS1
EMSuart::stop(); // stop UART so there is no interference
#ifndef EMSESP_STANDALONE
delay(1000); // wait 1 second
ESP.restart(); // ka-boom! - this is the only place where the ESP32 restart is called
@@ -563,7 +681,6 @@ void System::store_settings(WebSettings & settings) {
locale_ = settings.locale;
developer_mode_ = settings.developer_mode;
// start services
if (settings.modbus_enabled) {
if (EMSESP::modbus_ == nullptr) {
@@ -605,20 +722,11 @@ void System::start() {
appfree_ = esp_ota_get_running_partition()->size / 1024 - appused_;
refreshHeapMem(); // refresh free heap and max alloc heap
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
#if ESP_IDF_VERSION_MAJOR < 5
temp_sensor_config_t temp_sensor = TSENS_CONFIG_DEFAULT();
temp_sensor_get_config(&temp_sensor);
temp_sensor.dac_offset = TSENS_DAC_DEFAULT; // DEFAULT: range:-10℃ ~ 80℃, error < 1℃.
temp_sensor_set_config(temp_sensor);
temp_sensor_start();
temp_sensor_read_celsius(&temperature_);
#else
temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
temperature_sensor_install(&temp_sensor_config, &temperature_handle_);
temperature_sensor_enable(temperature_handle_);
temperature_sensor_get_celsius(temperature_handle_, &temperature_);
#endif
#endif
#endif
EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
@@ -839,16 +947,9 @@ void System::send_info_mqtt() {
doc["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP());
doc["IPv4 nameserver"] = uuid::printable_to_string(WiFi.dnsIP());
#if ESP_IDF_VERSION_MAJOR < 5
if (WiFi.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.localIPv6().toString() != "::") {
doc["IPv6 address"] = uuid::printable_to_string(WiFi.localIPv6());
}
#else
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
doc["IPv6 address"] = uuid::printable_to_string(WiFi.linkLocalIPv6());
}
#endif
}
#endif
Mqtt::queue_publish_retain(F_(info), doc.as<JsonObject>()); // topic called "info" and it's Retained
@@ -957,12 +1058,11 @@ void System::network_init() {
delay(500);
digitalWrite(eth_power_, HIGH);
}
#if ESP_IDF_VERSION_MAJOR < 5
eth_present_ = ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode);
#else
eth_present_ = ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode);
#endif
if (eth_present_) {
// Push hostname to the ETH netif immediately after it's created
ETH.setHostname(hostname_.c_str());
}
#endif
}
@@ -974,13 +1074,9 @@ void System::system_check() {
#ifndef EMSESP_STANDALONE
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
#if ESP_IDF_VERSION_MAJOR < 5
temp_sensor_read_celsius(&temperature_);
#else
temperature_sensor_get_celsius(temperature_handle_, &temperature_);
#endif
#endif
#endif
#ifdef EMSESP_PINGTEST
static uint64_t ping_count = 0;
@@ -1032,6 +1128,7 @@ void System::commands_init() {
Command::add(EMSdevice::DeviceType::SYSTEM, F_(read), System::command_read, FL_(read_cmd), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, FL_(send_cmd), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, FL_(fetch_cmd), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(sendmail), System::command_sendmail, FL_(sendmail_cmd), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_cmd), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(format), System::command_format, FL_(format_cmd), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(txpause), System::command_txpause, FL_(txpause_cmd), CommandFlag::ADMIN_ONLY);
@@ -1280,16 +1377,9 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(WiFi.gatewayIP()).c_str());
shell.printfln(" IPv4 nameserver: %s", uuid::printable_to_string(WiFi.dnsIP()).c_str());
#if ESP_IDF_VERSION_MAJOR < 5
if (WiFi.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.localIPv6().toString() != "::") {
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(WiFi.localIPv6()).c_str());
}
#else
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(WiFi.linkLocalIPv6()).c_str());
}
#endif
break;
case WL_CONNECT_FAILED:
@@ -1320,15 +1410,9 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str());
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(ETH.gatewayIP()).c_str());
shell.printfln(" IPv4 nameserver: %s", uuid::printable_to_string(ETH.dnsIP()).c_str());
#if ESP_IDF_VERSION_MAJOR < 5
if (ETH.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && ETH.localIPv6().toString() != "::") {
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(ETH.localIPv6()).c_str());
}
#else
if (ETH.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && ETH.linkLocalIPv6().toString() != "::") {
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(ETH.linkLocalIPv6()).c_str());
}
#endif
}
shell.println();
@@ -1350,10 +1434,10 @@ void System::show_system(uuid::console::Shell & shell) {
}
shell.println();
#endif
}
// see if there is a restore of an older settings file that needs to be applied
// note there can be only one file at a time
bool System::check_restore() {
@@ -1653,8 +1737,8 @@ bool System::check_upgrade() {
return false; // no reboot required
}
// map each config filename to its human-readable section key
#ifndef EMSESP_STANDALONE
// map each config filename to its human-readable section key
static const std::pair<const char *, const char *> SECTION_MAP[] = {
{NETWORK_SETTINGS_FILE, "Network"},
{AP_SETTINGS_FILE, "AP"},
@@ -1713,7 +1797,7 @@ void System::exportSystemBackup(JsonObject output) {
output["version"] = EMSESP_APP_VERSION; // add the version to the output
#ifndef EMSESP_STANDALONE
// add date/time if NTP enabled and active
// add date/time if NTP enabled and active
if ((esp_sntp_enabled()) && (EMSESP::system_.ntp_connected())) {
time_t now = time(nullptr);
if (now > 1500000000L) {
@@ -1769,27 +1853,16 @@ void System::exportSystemBackup(JsonObject output) {
const char * nvs_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, "nvs1") ? "nvs1" : "nvs"; // nvs1 is on 16MBs
nvs_iterator_t it = nullptr;
#if ESP_IDF_VERSION_MAJOR < 5
it = nvs_entry_find(nvs_part, "ems-esp", NVS_TYPE_ANY);
if (it == nullptr) {
#else
esp_err_t err = nvs_entry_find(nvs_part, "ems-esp", NVS_TYPE_ANY, &it);
esp_err_t err = nvs_entry_find(nvs_part, "ems-esp", NVS_TYPE_ANY, &it);
if (err != ESP_OK) {
#endif
LOG_ERROR("Failed to find NVS entry for %s", nvs_part);
return;
}
JsonArray entries = node["nvs"].to<JsonArray>();
#if ESP_IDF_VERSION_MAJOR < 5
while (it != nullptr) {
nvs_entry_info_t info;
nvs_entry_info(it, &info);
#else
while (err == ESP_OK) {
nvs_entry_info_t info;
nvs_entry_info(it, &info);
#endif
JsonObject entry = entries.add<JsonObject>();
entry["type"] = info.type;
entry["key"] = info.key;
@@ -1824,14 +1897,8 @@ void System::exportSystemBackup(JsonObject output) {
entry["value"] = EMSESP::nvs_.getString(info.key);
break;
}
#if ESP_IDF_VERSION_MAJOR < 5
it = nvs_entry_next(it);
}
#else
err = nvs_entry_next(&it);
}
#endif
if (it != nullptr) {
nvs_release_iterator(it);
@@ -2633,12 +2700,12 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["maxWebLogBuffer"] = settings.weblog_buffer;
/*
#if defined(EMSESP_UNITY)
node["webLogBuffer"] = 0;
#else
node["webLogBuffer"] = EMSESP::webLogService.num_log_messages();
#endif
*/
#if defined(EMSESP_UNITY)
node["webLogBuffer"] = 0;
#else
node["webLogBuffer"] = EMSESP::webLogService.num_log_messages();
#endif
*/
node["modbusEnabled"] = settings.modbus_enabled;
node["forceHeatingOff"] = settings.boiler_heatingoff;
node["developerMode"] = settings.developer_mode;
@@ -3266,7 +3333,7 @@ void System::set_valid_system_gpios() {
valid_system_gpios_ = string_range_to_vector("0-21", "2, 8, 12-17, 18-19");
#elif CONFIG_IDF_TARGET_ESP32S2
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32s2/api-reference/peripherals/gpio.html
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32s2/api-reference/peripherals/gpio.html
// excluded:
// GPIO26 - GPIO32 = SPI flash and PSRAM
// GPIO45 - GPIO46 = strapping pins
@@ -3279,7 +3346,7 @@ void System::set_valid_system_gpios() {
valid_system_gpios_ = string_range_to_vector("0-46", "19, 20, 26-32, 45-46, 39-42, 22-25");
#elif CONFIG_IDF_TARGET_ESP32S3
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-reference/peripherals/gpio.html
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-reference/peripherals/gpio.html
// excluded:
// GPIO3, GPIO45 - GPIO46 = strapping pins
// GPIO26 - GPIO32 = SPI flash and PSRAM and not recommended
@@ -3298,7 +3365,7 @@ void System::set_valid_system_gpios() {
}
#elif CONFIG_IDF_TARGET_ESP32
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/gpio.html
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/gpio.html
// excluded:
// GPIO6 - GPIO11, GPIO16 - GPIO17 = used for SPI flash and PSRAM (dio mode only GPIO06-GPIO08, GPIO11)
// GPIO20, GPIO24, GPIO28 - GPIO31 = don't exist
@@ -3329,6 +3396,10 @@ void System::set_valid_system_gpios() {
} else {
valid_system_gpios_ = string_range_to_vector("0-39", "6-11, 20, 24, 28-31");
}
#elif CONFIG_IDF_TARGET_ESP32C6
// https://docs.espressif.com/projects/esp-idf/en/v5.5.3/esp32c6/api-reference/peripherals/gpio.html
// 24-30 used for flash, 12-13 USB, 16-17 uart0
valid_system_gpios_ = string_range_to_vector("0-30", "12-13, 16-17, 24-30");
#elif defined(EMSESP_STANDALONE)
valid_system_gpios_ = string_range_to_vector("0-39");
#endif

View File

@@ -37,11 +37,7 @@
#include <uuid/log.h>
#include <PButton.h>
#if ESP_ARDUINO_VERSION_MAJOR < 3
#define EMSESP_RGB_WRITE neopixelWrite
#else
#define EMSESP_RGB_WRITE rgbLedWrite
#endif
#if CONFIG_IDF_TARGET_ESP32
// there is no official API available on the original ESP32
@@ -49,12 +45,8 @@ extern "C" {
uint8_t temprature_sens_read();
}
#elif CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
#if ESP_IDF_VERSION_MAJOR < 5
#include "driver/temp_sensor.h"
#else
#include "driver/temperature_sensor.h"
#endif
#endif
using uuid::console::Shell;
@@ -103,6 +95,7 @@ class System {
static bool command_info(const char * value, const int8_t id, JsonObject output);
static bool command_response(const char * value, const int8_t id, JsonObject output);
static bool command_service(const char * cmd, const char * value);
static bool command_sendmail(const char * value, const int8_t id);
static bool command_txpause(const char * value, const int8_t id);
static bool get_value_info(JsonObject root, const char * cmd);
@@ -502,9 +495,7 @@ class System {
uint32_t appfree_;
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
#if ESP_IDF_VERSION_MAJOR >= 5
temperature_sensor_handle_t temperature_handle_ = NULL;
#endif
#endif
float temperature_ = 0;
};

View File

@@ -159,10 +159,11 @@ class EMSbus {
public:
static uuid::log::Logger logger_;
static constexpr uint8_t EMS_MASK_UNSET = 0xFF; // EMS bus type (budrus/junkers) hasn't been detected yet
static constexpr uint8_t EMS_MASK_HT3 = 0x80; // EMS bus type Junkers/HT3
static constexpr uint8_t EMS_MASK_BUDERUS = 0xFF; // EMS bus type Buderus
static constexpr uint8_t EMS_TX_ERROR_LIMIT = 10; // % limit of failed Tx read/write attempts before showing a warning
static constexpr uint8_t EMS_MASK_UNSET = 0xFF; // EMS bus type (budrus/junkers) hasn't been detected yet
static constexpr uint8_t EMS_MASK_HT3 = 0x80; // EMS bus type Junkers/HT3
static constexpr uint8_t EMS_MASK_BUDERUS = 0xFF; // EMS bus type Buderus
static constexpr uint8_t EMS_TX_ERROR_LIMIT = 10; // % limit of failed Tx read/write attempts before showing a warning
static constexpr uint8_t EMS_POLL_MATCH_LIMIT = 3; // consecutive poll matches needed before declaring bus connected
static bool is_ht3() {
return (ems_mask_ == EMS_MASK_HT3);
@@ -210,7 +211,6 @@ class EMSbus {
if (!last_bus_activity_) {
bus_uptime_start_ = timestamp;
}
last_bus_activity_ = timestamp;
bus_connected_ = true;
}

View File

@@ -21,12 +21,6 @@
#include "temperaturesensor.h"
#include "emsesp.h"
#ifdef ESP32
#define YIELD
#else
#define YIELD yield()
#endif
namespace emsesp {
uuid::log::Logger TemperatureSensor::logger_{F_(temperaturesensor), uuid::log::Facility::DAEMON};
@@ -81,7 +75,6 @@ void TemperatureSensor::loop() {
LOG_DEBUG("Read sensor temperature");
#endif
if (bus_.reset() || parasite_) {
YIELD;
bus_.skip();
bus_.write(CMD_CONVERT_TEMP, parasite_ ? 1 : 0);
state_ = State::READING;
@@ -260,19 +253,16 @@ int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) {
LOG_ERROR("Bus reset failed before reading scratchpad from %s", Sensor(addr).id());
return EMS_VALUE_INT16_NOTSET;
}
YIELD;
uint8_t scratchpad[SCRATCHPAD_LEN] = {0};
bus_.select(addr);
bus_.write(CMD_READ_SCRATCHPAD);
bus_.read_bytes(scratchpad, SCRATCHPAD_LEN);
YIELD;
if (!bus_.reset()) {
LOG_ERROR("Bus reset failed after reading scratchpad from %s", Sensor(addr).id());
return EMS_VALUE_INT16_NOTSET;
}
YIELD;
if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) {
LOG_WARNING("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from sensor %s",

View File

@@ -39,11 +39,11 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
DeviceValueUOM::DEGREES);
// Roomthermostats
for (uint8_t i = 0; i < 16; i++) {
register_telegram_type(0x0BDD + i, "Room", false, MAKE_PF_CB(process_roomThermostat)); // broadcasted
register_telegram_type(0x0B3D + i, "Roomname", false, MAKE_PF_CB(process_roomThermostatName), 100); // fetch for active circuits
register_telegram_type(0x0BDD + i, "Room", false, MAKE_PF_CB(process_roomThermostat)); // broadcasted
register_telegram_type(0x0B3D + i, "Roomname", false, MAKE_PF_CB(process_roomThermostatName), 100); // fetch for active circuits
register_telegram_type(0x0BB5 + i, "Roomsettings", false, MAKE_PF_CB(process_roomThermostatSettings), 8); // fetch for active circuits
register_telegram_type(0x1230 + i, "Roomparams", false, MAKE_PF_CB(process_roomThermostatParam)); // fetch for active circuits
register_telegram_type(0x1244 + i, "Roomdata", false, MAKE_PF_CB(process_roomThermostatData)); // broadcasted
register_telegram_type(0x1230 + i, "Roomparams", false, MAKE_PF_CB(process_roomThermostatParam)); // fetch for active circuits
register_telegram_type(0x1244 + i, "Roomdata", false, MAKE_PF_CB(process_roomThermostatData)); // broadcasted
}
register_telegram_type(0xDB65, "Roomschedule", true, MAKE_PF_CB(process_roomSchedule));
// 0x2040, broadcast 36 bytes:

View File

@@ -166,7 +166,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
set2_typeids = {0x0421, 0x0422, 0x0423, 0x0424};
summer_typeids = {0x02AF, 0x02B0, 0x02B1, 0x02B2, 0x02B3, 0x02B4, 0x02B5, 0x02B6};
curve_typeids = {0x029B, 0x029C, 0x029D, 0x029E, 0x029F, 0x02A0, 0x02A1, 0x02A2};
summer2_typeids = {0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0476, 0x0477, 0x0478};
summer2_typeids = {0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0476, 0x0477, 0x0478};
hp_typeids = {0x0467, 0x0468, 0x0469, 0x046A};
hpmode_typeids = {0x0291, 0x0292, 0x0293, 0x0294};
const size_t monitor_size = monitor_typeids.size();

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.8.2-dev.18"
#define EMSESP_APP_VERSION "3.9.0-dev.0"

View File

@@ -39,7 +39,7 @@ uint32_t inverse_mask = 0;
// receive task, wait for break and call incoming_telegram
void EMSuart::uart_event_task(void * pvParameters) {
uart_event_t event;
uint8_t telegram[UART_FIFO_LEN + 1]; // same size as in driver_install
uint8_t telegram[UART_HW_FIFO_LEN(EMSUART_NUM) + 1]; // same size as in driver_install
uint8_t length = 0;
while (1) {
@@ -66,18 +66,23 @@ void EMSuart::uart_event_task(void * pvParameters) {
// initialize UART driver
void EMSuart::start(const uint8_t tx_mode, const uint8_t rx_gpio, const uint8_t tx_gpio) {
if (tx_mode_ == EMS_TXMODE_INIT) {
#if CONFIG_IDF_TARGET_ESP32C6
uart_config_t uart_config = {.baud_rate = EMSUART_BAUD,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.rx_flow_ctrl_thresh = 0};
#else
uart_config_t uart_config = {.baud_rate = EMSUART_BAUD,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.rx_flow_ctrl_thresh = 0,
.source_clk = UART_SCLK_APB
#if ESP_ARDUINO_VERSION_MAJOR >= 3
,
.flags = {0}
.source_clk = UART_SCLK_APB,
.flags = {0}};
#endif
};
#if defined(EMSUART_RX_INVERT)
inverse_mask |= UART_SIGNAL_RXD_INV;
#endif
@@ -87,7 +92,12 @@ void EMSuart::start(const uint8_t tx_mode, const uint8_t rx_gpio, const uint8_t
uart_param_config(EMSUART_NUM, &uart_config);
uart_set_pin(EMSUART_NUM, tx_gpio, rx_gpio, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
uart_set_line_inverse(EMSUART_NUM, inverse_mask);
uart_driver_install(EMSUART_NUM, UART_FIFO_LEN + 1, 0, UART_FIFO_LEN + 3, &uart_queue, 0); // buffer must be > fifo, queue can hold data+break+overflow message
uart_driver_install(EMSUART_NUM,
UART_HW_FIFO_LEN(EMSUART_NUM) + 1,
0,
UART_HW_FIFO_LEN(EMSUART_NUM) + 3,
&uart_queue,
0); // buffer must be > fifo, queue can hold data+break+overflow message
uart_set_rx_full_threshold(EMSUART_NUM, 1);
uart_set_rx_timeout(EMSUART_NUM, 0); // disable

View File

@@ -54,10 +54,10 @@ void WebCustomization::read(WebCustomization & customizations, JsonObject root)
JsonArray sensorsJson = root["ts"].to<JsonArray>();
for (const SensorCustomization & sensor : customizations.sensorCustomizations) {
JsonObject sensorJson = sensorsJson.add<JsonObject>();
sensorJson["id"] = (const char *)sensor.id; // ID of chip
sensorJson["id"] = (const char *)sensor.id; // ID of dallas temperature sensor chip
sensorJson["name"] = (const char *)sensor.name; // n
sensorJson["offset"] = sensor.offset; // o
sensorJson["is_system"] = sensor.is_system; // s for core_voltage, supply_voltage
sensorJson["is_system"] = sensor.is_system; // s for gateway_temperature
}
// Analog Sensor customization

View File

@@ -352,7 +352,6 @@ bool WebSchedulerService::command(const char * name, const std::string & command
// parse json
JsonDocument doc;
if (deserializeJson(doc, cmd) == DeserializationError::Ok) {
HTTPClient * http = new HTTPClient;
std::string url = doc["url"] | "";
// for a GET with parameters replace commands with values
// don't search the complete url, it may contain a devicename in path
@@ -363,40 +362,139 @@ bool WebSchedulerService::command(const char * name, const std::string & command
commands(s, false);
url.replace(q + 1, l, s);
}
if (http->begin(url.c_str())) {
// add any given headers
for (JsonPair p : doc["header"].as<JsonObject>()) {
http->addHeader(p.key().c_str(), p.value().as<String>().c_str());
std::string value = doc["value"] | data; // extract value if its in the command, or take the data
std::string method = doc["method"] | "GET"; // default GET
commands(value, false);
if (value.length()) {
method = "POST";
}
std::string result;
int httpResult = 0;
#ifndef NO_TLS_SUPPORT
if (Helpers::toLower(url.c_str()).starts_with("https://")) {
WiFiClient * basic_client = new WiFiClient;
ESP_SSLClient * ssl_client = new ESP_SSLClient;
ssl_client->setInsecure(); // with root CA we should set here: ssl_client->setCACert(rootCACert);
ssl_client->setBufferSizes(1024, 1024);
ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds)
url.replace(0, 8, "");
std::string host = url;
auto index = url.find_first_of('/');
if (index != std::string::npos) {
host = url.substr(0, index);
url.replace(0, index, "");
}
std::string value = doc["value"] | data.c_str(); // extract value if its in the command, or take the data
std::string method = doc["method"] | "GET"; // default GET
commands(value, false);
// if there is data, force a POST
int httpResult = 0;
if (value.length() || method == "post") { // we have all lowercase
if (value.find_first_of('{') != std::string::npos) {
http->addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON
// EMSESP::logger().debug("Host: %s, URL: %s", host.c_str(), url.c_str());
ssl_client->setClient(basic_client);
if (ssl_client->connect(host.c_str(), 443)) {
if (value.length() || Helpers::toLower(method) == "post") {
// EMSESP::logger().debug("POST %s HTTP/1.1", url.c_str());
ssl_client->print("POST ");
ssl_client->print(url.c_str());
ssl_client->println(" HTTP/1.1");
ssl_client->print("Host: ");
ssl_client->println(host.c_str());
bool content_set = false;
for (JsonPair p : doc["header"].as<JsonObject>()) {
content_set |= (emsesp::Helpers::toLower(p.key().c_str()) == "content-type");
ssl_client->print(p.key().c_str());
ssl_client->print(": ");
ssl_client->println(p.value().as<std::string>().c_str());
}
if (!content_set) {
ssl_client->print("Content-Type: ");
if (value.starts_with('{')) {
ssl_client->println(asyncsrv::T_application_json);
} else {
ssl_client->println(asyncsrv::T_text_plain);
}
}
ssl_client->print("Content-Length: ");
ssl_client->println(value.length());
ssl_client->println("Connection: close");
ssl_client->print("\r\n");
ssl_client->print(value.c_str());
} else {
// EMSESP::logger().debug("GET %s HTTP/1.1", url.c_str());
ssl_client->print("GET ");
ssl_client->print(url.c_str());
ssl_client->println(" HTTP/1.1");
ssl_client->print("Host: ");
ssl_client->println(host.c_str());
for (JsonPair p : doc["header"].as<JsonObject>()) {
ssl_client->print(p.key().c_str());
ssl_client->print(": ");
ssl_client->println(p.value().as<std::string>().c_str());
}
ssl_client->println("Connection: close");
}
auto ms = millis();
while (ssl_client->connected() && !ssl_client->available() && millis() - ms < 3000) {
delay(0);
}
while (ssl_client->available()) {
result += (char)ssl_client->read();
}
ssl_client->stop();
// EMSESP::logger().debug("HTTPS response: %s", result.c_str());
index = result.find_first_of(' ');
if (index != std::string::npos) {
httpResult = stoi(result.substr(index + 1, 3));
// EMSESP::logger().debug("HTTPS code: %i", httpResult);
}
index = result.find("\r\n\r\n");
if (index != std::string::npos) {
result.replace(0, index + 4, "");
// EMSESP::logger().debug("HTTPS response: %s", result.c_str());
}
httpResult = http->POST(value.c_str());
} else {
httpResult = http->GET(); // normal GET
EMSESP::logger().warning("HTTPS connection failed");
}
http->end();
delete http;
delete ssl_client;
delete basic_client;
// check HTTP return code
if (httpResult != 200) {
char error[100];
snprintf(error, sizeof(error), "Schedule %s: URL command failed with http code %d", name, httpResult);
EMSESP::logger().warning(error);
EMSESP::logger().warning("Schedule '%s': URL command failed with http code %d", name, httpResult);
return false;
}
return true;
} else
#endif
if (Helpers::toLower(url.c_str()).starts_with("http://")) {
HTTPClient * http = new HTTPClient;
if (http->begin(url.c_str())) {
bool content_set = false;
for (JsonPair p : doc["header"].as<JsonObject>()) {
http->addHeader(p.key().c_str(), p.value().as<std::string>().c_str());
content_set |= p.key() == "content-type";
}
// if there is data, force a POST
if (Helpers::toLower(method) == "post") { // we have all lowercase
if (!content_set) {
// http->addHeader("Content-Type", value.find_first_of('{') != std::string::npos ? "application/json" : "text/plain");
if (value.starts_with('{')) {
http->addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON
} else {
http->addHeader(asyncsrv::T_Content_Type, asyncsrv::T_text_plain, false); // auto-set to JSON
}
}
httpResult = http->POST(value.c_str());
} else {
httpResult = http->GET(); // normal GET
if (httpResult > 0) {
result = http->getString().c_str();
}
}
}
http->end();
delete http;
// check HTTP return code
if (httpResult != 200) {
EMSESP::logger().warning("Schedule '%s': URL command failed with http code %d", name, httpResult);
return false;
}
#if defined(EMSESP_DEBUG)
char msg[100];
snprintf(msg, sizeof(msg), "Schedule %s: URL command successful with http code %d", name, httpResult);
EMSESP::logger().debug(msg);
EMSESP::logger().debug("Schedule %s: URL '%s' command successful with http code %d", name, url.c_str(), httpResult);
#endif
return true;
}

View File

@@ -83,6 +83,20 @@ void WebSettings::read(WebSettings & settings, JsonObject root) {
root["modbus_max_clients"] = settings.modbus_max_clients;
root["modbus_timeout"] = settings.modbus_timeout;
root["developer_mode"] = settings.developer_mode;
#ifndef NO_TLS_SUPPORT
root["email_enabled"] = settings.email_enabled;
#else
root["email_enabled"] = false;
#endif
root["email_ssl"] = settings.email_ssl;
root["email_starttls"] = settings.email_starttls;
root["email_server"] = settings.email_server;
root["email_port"] = settings.email_port;
root["email_login"] = settings.email_login;
root["email_pass"] = settings.email_pass;
root["email_sender"] = settings.email_sender;
root["email_recp"] = settings.email_recp;
root["email_subject"] = settings.email_subject;
}
// call on initialization and also when settings are updated/saved via web or console
@@ -242,14 +256,10 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
check_flag(original_settings.low_clock, settings.low_clock, ChangeFlags::RESTART);
// Modbus settings
settings.modbus_enabled = root["modbus_enabled"] | EMSESP_DEFAULT_MODBUS_ENABLED;
check_flag(original_settings.modbus_enabled, settings.modbus_enabled, ChangeFlags::RESTART);
settings.modbus_port = root["modbus_port"] | EMSESP_DEFAULT_MODBUS_PORT;
check_flag(original_settings.modbus_port, settings.modbus_port, ChangeFlags::RESTART);
settings.modbus_enabled = root["modbus_enabled"] | EMSESP_DEFAULT_MODBUS_ENABLED;
settings.modbus_port = root["modbus_port"] | EMSESP_DEFAULT_MODBUS_PORT;
settings.modbus_max_clients = root["modbus_max_clients"] | EMSESP_DEFAULT_MODBUS_MAX_CLIENTS;
check_flag(original_settings.modbus_max_clients, settings.modbus_max_clients, ChangeFlags::RESTART);
settings.modbus_timeout = root["modbus_timeout"] | EMSESP_DEFAULT_MODBUS_TIMEOUT;
check_flag(original_settings.modbus_timeout, settings.modbus_timeout, ChangeFlags::RESTART);
settings.modbus_timeout = root["modbus_timeout"] | EMSESP_DEFAULT_MODBUS_TIMEOUT;
//
// these may need mqtt restart to rebuild HA discovery topics
@@ -300,6 +310,20 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
settings.weblog_level = root["weblog_level"] | EMSESP_DEFAULT_WEBLOG_LEVEL;
settings.weblog_compact = root["weblog_compact"] | EMSESP_DEFAULT_WEBLOG_COMPACT;
settings.email_enabled = root["email_enabled"] | FACTORY_EMAIL_ENABLE;
settings.email_ssl = root["email_ssl"] | FACTORY_EMAIL_SSL;
settings.email_starttls = root["email_starttls"] | FACTORY_EMAIL_STARTTLS;
settings.email_server = root["email_server"] | FACTORY_EMAIL_SERVER;
settings.email_port = root["email_port"] | FACTORY_EMAIL_PORT;
settings.email_login = root["email_login"] | FACTORY_EMAIL_LOGIN;
settings.email_pass = root["email_pass"] | FACTORY_EMAIL_PASSWORD;
settings.email_sender = root["email_sender"] | FACTORY_EMAIL_FROM;
settings.email_recp = root["email_recp"] | FACTORY_EMAIL_TO;
settings.email_subject = root["email_subject"] | FACTORY_EMAIL_SUBJECT;
if (settings.email_ssl && settings.email_starttls) {
settings.email_ssl = false;
}
// if no psram limit weblog buffer to 25 messages
if (EMSESP::system_.PSram() > 0) {
settings.weblog_buffer = root["weblog_buffer"] | EMSESP_DEFAULT_WEBLOG_BUFFER;
@@ -458,23 +482,14 @@ void WebSettings::set_board_profile(WebSettings & settings) {
#if CONFIG_IDF_TARGET_ESP32
// check for no PSRAM, could be a E32 or S32?
if (!ESP.getPsramSize()) {
#if ESP_ARDUINO_VERSION_MAJOR < 3
if (ETH.begin(1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN)) {
#else
if (ETH.begin(ETH_PHY_LAN8720, 1, 23, 18, 16, ETH_CLOCK_GPIO0_IN)) {
#endif
settings.board_profile = "E32"; // Ethernet without PSRAM
} else {
settings.board_profile = "S32"; // ESP32 standard WiFi without PSRAM
}
} else {
// check for boards with PSRAM, could be a E32V2 otherwise default back to the S32
#if ESP_ARDUINO_VERSION_MAJOR < 3
if (ETH.begin(0, 15, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_OUT)) {
#else
// check for boards with PSRAM, could be a E32V2 otherwise default back to the S32
if (ETH.begin(ETH_PHY_LAN8720, 0, 23, 18, 15, ETH_CLOCK_GPIO0_OUT)) {
#endif
if (analogReadMilliVolts(39) > 700) { // core voltage > 2.6V
settings.board_profile = "E32V2_2"; // Ethernet, PSRAM, internal sensors
} else {

View File

@@ -26,6 +26,36 @@
#define EMSESP_SETTINGS_SERVICE_PATH "/rest/settings"
#define EMSESP_BOARD_PROFILE_SERVICE_PATH "/rest/boardProfile"
#ifndef FACTORY_EMAIL_ENABLE
#define FACTORY_EMAIL_ENABLE false
#endif
#ifndef FACTORY_EMAIL_SSL
#define FACTORY_EMAIL_SSL false
#endif
#ifndef FACTORY_EMAIL_STARTTLS
#define FACTORY_EMAIL_STARTTLS true
#endif
#ifndef FACTORY_EMAIL_PORT
#define FACTORY_EMAIL_PORT 587
#endif
#ifndef FACTORY_EMAIL_SERVER
#define FACTORY_EMAIL_SERVER "smtp.example.net"
#endif
#ifndef FACTORY_EMAIL_LOGIN
#define FACTORY_EMAIL_LOGIN ""
#endif
#ifndef FACTORY_EMAIL_PASSWORD
#define FACTORY_EMAIL_PASSWORD ""
#endif
#ifndef FACTORY_EMAIL_FROM
#define FACTORY_EMAIL_FROM "ems-esp@example.net"
#endif
#ifndef FACTORY_EMAIL_TO
#define FACTORY_EMAIL_TO ""
#endif
#ifndef FACTORY_EMAIL_SUBJECT
#define FACTORY_EMAIL_SUBJECT "ems-esp notification"
#endif
namespace emsesp {
class WebSettings {
@@ -77,6 +107,16 @@ class WebSettings {
uint16_t modbus_port;
uint8_t modbus_max_clients;
uint32_t modbus_timeout;
bool email_enabled;
bool email_ssl;
bool email_starttls;
String email_server;
uint16_t email_port;
String email_login;
String email_pass;
String email_sender;
String email_recp;
String email_subject;
uint8_t phy_type;
int8_t eth_power; // -1 means disabled