69 Commits

Author SHA1 Message Date
proddy
e8f57b6343 v3.8.1 2026-01-11 19:28:38 +01:00
proddy
a1ab81de66 Merge branch 'dev' 2026-01-11 19:14:11 +01:00
proddy
28135c225b Merge branch 'dev' 2025-12-31 21:26:15 +01:00
proddy
eaa277fef0 v3.7.2 2025-03-22 10:32:03 +01:00
proddy
e418b7d8e7 fix routing in tabs - #2264 2024-11-30 21:31:42 +01:00
proddy
a4e3be6a69 3.7.1 changelog 2024-11-29 10:30:14 +01:00
proddy
a1bc5bb055 3.7.1 2024-11-29 10:30:05 +01:00
proddy
f444ca31e0 Merge remote-tracking branch 'origin/dev' 2024-10-27 11:41:47 +01:00
proddy
1b70b55989 update demo url 2024-08-10 14:58:48 +02:00
proddy
1487f30c43 Merge remote-tracking branch 'origin/dev' for 3.6.5 2024-03-23 17:56:05 +01:00
Proddy
e00eb8e64f 3.6.4 2023-11-26 20:11:36 +01:00
Proddy
f41bb3671c 3.6.4 2023-11-24 07:36:36 +01:00
Proddy
22c75e6df3 3.6.4 2023-11-24 07:36:29 +01:00
proddy
5ab10b7aa6 fixes #1450 2023-11-22 09:48:09 +01:00
Proddy
ee5fd4d0eb 3.6.3 2023-11-21 14:40:47 +01:00
Proddy
46f35bc67c another patch on 3.6.3 2023-11-21 14:40:32 +01:00
Proddy
ec85a7ec24 3.6.3 (refershed) 2023-11-19 21:24:01 +01:00
Proddy
02f2389587 add workflow_dispatch: 2023-11-18 18:51:12 +01:00
Proddy
7f140021aa quick fix - https://github.com/emsesp/EMS-ESP32/pull/1438 2023-11-18 18:47:28 +01:00
Proddy
6796962c1e 3.6.3 2023-11-18 13:35:20 +01:00
Proddy
df6de21cf4 Merge remote-tracking branch 'origin/dev' 2023-11-18 13:35:04 +01:00
Proddy
df9f75a5c9 updated yarn for 3.6.2 2023-10-01 17:42:54 +02:00
Proddy
7bd8710eb6 3.6.2 2023-10-01 17:40:09 +02:00
Proddy
32f2c6d341 Merge remote-tracking branch 'origin/dev' 2023-10-01 17:40:02 +02:00
Proddy
86919c1684 Merge branch 'origin/dev' 2023-09-09 14:12:07 +02:00
Proddy
86e29515e7 build s3 2023-08-15 18:44:24 +02:00
Proddy
46eb4185d7 update with 3.6.0 2023-08-13 14:37:13 +02:00
Proddy
8da6761a48 Merge branch 'dev' 2023-08-13 14:32:41 +02:00
Proddy
9233f0dfcc v3.5.1 - merge with patch 2023-03-11 16:06:05 +01:00
Proddy
292f743b14 Update bug_report.md 2023-02-19 11:18:08 +01:00
Proddy
dd6dfffd57 Delete questions---troubleshooting.md 2023-02-19 10:49:52 +01:00
Proddy
ec705a5307 Delete feature_request.md 2023-02-19 10:49:45 +01:00
Proddy
f45f071710 Create config.yml 2023-02-19 10:49:32 +01:00
Proddy
f3858546de Merge branch 'origin/dev' 2023-02-06 21:58:27 +01:00
Proddy
d0ac0b7804 update with version on dev 2022-10-30 16:58:37 +01:00
Proddy
d8284ec09f Merge pull request #705 from MichaelDvP/main
v3.4.4 Fix for new installations with filesystem not initializing
2022-10-29 10:46:41 +02:00
MichaelDvP
6e982acde8 v3.4.4 Fix for new installations with filesystem not initializing 2022-10-28 10:50:51 +02:00
Proddy
8c94ce99b2 quick fix for filesystem initialization 2022-10-08 09:23:00 +02:00
proddy
fc057d18c9 Merge remote-tracking branch 'origin/dev' 2022-09-18 14:33:23 +02:00
Proddy
18e9b99413 Merge remote-tracking branch 'origin/dev' into main 2022-05-29 16:16:38 +02:00
Proddy
a47e0e8266 update for 3.4.0 2022-05-23 21:20:45 +02:00
Proddy
f412ddc716 Merge remote-tracking branch 'origin/dev' into main 2022-05-23 21:20:36 +02:00
proddy
29110e96e5 Merge remote-tracking branch 'origin/dev' 2022-01-20 10:51:40 +01:00
proddy
b65866217a 3.4.0 2021-11-28 23:03:28 +01:00
proddy
611e3b1243 Merge remote-tracking branch 'origin/dev' 2021-11-28 23:03:15 +01:00
proddy
2ca0a0c634 v3.2.1 merged from dev 2021-08-08 14:46:14 +02:00
proddy
7eb1f061b7 Merge remote-tracking branch 'origin/dev' for 3.2.0 release 2021-08-06 12:06:08 +02:00
proddy
50459a23fe force v16 of nodejs 2021-06-26 11:13:07 +02:00
proddy
5bf53c3389 3.1.1 2021-06-26 11:03:03 +02:00
proddy
4b7aa95be3 Merge remote-tracking branch 'origin/dev' 2021-06-26 11:02:55 +02:00
Proddy
70943f5758 Update pre_release.yml 2021-05-16 15:52:09 +02:00
Proddy
3bc280b817 Delete check_code.yml 2021-05-16 15:51:56 +02:00
Proddy
62b15a5319 Update pre_release.yml 2021-05-16 15:35:06 +02:00
Proddy
8dd18802d6 Update tagged_release.yml 2021-05-16 15:34:45 +02:00
proddy
57a516a83a updated README and images 2021-05-09 15:13:16 +02:00
proddy
a57fdaa4b3 Merge remote-tracking branch 'origin/dev' into main 2021-05-04 12:21:51 +02:00
proddy
4841e42286 Merge remote-tracking branch 'origin/dev' into main 2021-03-30 16:35:18 +02:00
proddy
8c2d2b06ed cleaned up old changelog 2021-03-18 20:59:09 +01:00
proddy
38c8b1b7f0 3.0.0 2021-03-18 20:58:21 +01:00
proddy
6fb5933a02 Merge remote-tracking branch 'origin/dev' into main 2021-03-18 20:58:12 +01:00
proddy
c0944433be remove workspace.code-workspace 2021-03-16 17:41:42 +01:00
Proddy
478e6362c9 Merge pull request #27 from FauthD:main
Add global names to Dallas sensors to avoid ugly <unknown> and other …
2021-03-16 17:39:00 +01:00
fauthd
4d6354db78 Add stuff to gitignore, add vscode workspace 2021-03-16 16:47:09 +01:00
fauthd
beab0f0c77 Add global names to Dallas sensors to avoid ugly <unknown> and other issues in HA 2021-03-16 16:00:23 +01:00
Proddy
c17749bd22 Update README.md 2021-03-14 23:43:33 +01:00
proddy
2bad769c5c build: include assets 2021-03-14 21:20:51 +01:00
proddy
8ad89ca64b move repo 2021-03-14 21:05:15 +01:00
proddy
9244d8daec Semantic Commit Messages 2021-03-14 18:10:57 +01:00
proddy
02d01334b2 update new build 2021-03-14 17:37:18 +01:00
134 changed files with 6567 additions and 8731 deletions

View File

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

View File

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

View File

@@ -33,7 +33,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- board profile `CUSTOM` can only be selected in developer mode
- mqtt sends round values without decimals (`28` instead of `28.0`)
## [3.8.0] 31 December 2025
## Added

View File

@@ -6,28 +6,6 @@ For more details go to [emsesp.org](https://emsesp.org/).
## Added
- comfortpoint for BC400 [#2935](https://github.com/emsesp/EMS-ESP32/issues/2935)
- customize device brand [#2784](https://github.com/emsesp/EMS-ESP32/issues/2784)
- set model for ems-esp devices temperature, analog, etc. [#2958](https://github.com/emsesp/EMS-ESP32/discussions/2958)
- prometheus metrics for temperature/analog/scheduler/custom [#2962](https://github.com/emsesp/EMS-ESP32/issues/2962)
- boiler pumpkick [#2965](https://github.com/emsesp/EMS-ESP32/discussions/2965)
- heatpump reset [#2933](https://github.com/emsesp/EMS-ESP32/issues/2933)
- e-mail notification using ReadyMail Client
- 2.nd freshwater module (dhw4) [#2991](https://github.com/emsesp/EMS-ESP32/issues/2991)
## Fixed
- SRC climate creation [#2936](https://github.com/emsesp/EMS-ESP32/issues/2936) and [#2960](https://github.com/emsesp/EMS-ESP32/issues/2960)
## Changed
- weblogbuffer up to 1000 messages with PSRAM, mentioned in [#2933](https://github.com/emsesp/EMS-ESP32/issues/2933)
- validate custom entity writes, [#2931](https://github.com/emsesp/EMS-ESP32/issues/2931)
- remove wrong burnMinPower [#2918](https://github.com/emsesp/EMS-ESP32/issues/2918)
- store scheduler active state to nvs [#2946](https://github.com/emsesp/EMS-ESP32/discussions/2946)
- translated modes `heat` and `eco` for HA-climate mode-str-tpl
- support `minflowtemp` and `baseflowtemp` [#2969](https://github.com/emsesp/EMS-ESP32/discussions/2969)
- update version if it is 00.00 in first read [#2981](https://github.com/emsesp/EMS-ESP32/issues/2981)
- device class for % values [#2980](https://github.com/emsesp/EMS-ESP32/issues/2980)
- use tasmota core 2026.03.30
- secure mqtt uses ESP_SSLClient

View File

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

View File

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

View File

@@ -5,7 +5,7 @@
},
"core": "esp32",
"extra_flags": [
"-DNO_TLS_SUPPORT",
"-DTASMOTA_SDK",
"-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",
"-DNO_TLS_SUPPORT",
"-DTASMOTA_SDK",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0"
],

View File

@@ -21,7 +21,7 @@
"arduino",
"espidf"
],
"name": "Tasmota ESP32-S3 32M Flash OPI PSRAM, 4608KB Code/OTA, 2MB FS",
"name": "Espressif 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": "-DNO_TLS_SUPPORT",
"extra_flags": "-DTASMOTA_SDK",
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",
@@ -19,7 +19,7 @@
"arduino",
"espidf"
],
"name": "Tasmota ESP32 16M Flash, 4608KB Code/OTA, 2MB FS",
"name": "Espressif 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": "Tasmota ESP32 16M Flash DIO PSRAM, 4608KB Code/OTA, 2MB FS",
"name": "Espressif 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": "-DNO_TLS_SUPPORT",
"extra_flags": "-DTASMOTA_SDK",
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -26,45 +26,46 @@
"@alova/adapter-xhr": "2.3.1",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.9",
"@mui/material": "^7.3.9",
"@preact/compat": "^18.3.2",
"@mui/icons-material": "^7.3.7",
"@mui/material": "^7.3.7",
"@preact/compat": "^18.3.1",
"@table-library/react-table-library": "4.1.15",
"alova": "^3.5.1",
"alova": "3.4.1",
"async-validator": "^4.2.5",
"etag": "^1.8.1",
"formidable": "^3.5.4",
"jwt-decode": "^4.0.0",
"magic-string": "^0.30.21",
"mime-types": "^3.0.2",
"preact": "^10.29.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-icons": "^5.6.0",
"react-router": "^7.13.2",
"preact": "^10.28.2",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-icons": "^5.5.0",
"react-router": "^7.12.0",
"react-toastify": "^11.0.5",
"typesafe-i18n": "^5.27.1",
"typescript": "^6.0.2"
"typesafe-i18n": "^5.26.2",
"typescript": "^5.9.3"
},
"devDependencies": {
"@babel/core": "^7.29.0",
"@eslint/js": "^10.0.1",
"@preact/compat": "^18.3.2",
"@preact/preset-vite": "^2.10.5",
"@babel/core": "^7.28.5",
"@eslint/js": "^9.39.2",
"@preact/compat": "^18.3.1",
"@preact/preset-vite": "^2.10.2",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/node": "^25.0.6",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"axe-core": "^4.11.1",
"concurrently": "^9.2.1",
"eslint": "^10.1.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.8.1",
"rollup-plugin-visualizer": "^7.0.1",
"terser": "^5.46.1",
"typescript-eslint": "^8.57.2",
"vite": "^8.0.3",
"vite-plugin-imagemin": "^0.6.1"
"prettier": "^3.7.4",
"rollup-plugin-visualizer": "^6.0.5",
"terser": "^5.44.1",
"typescript-eslint": "^8.52.0",
"vite": "^7.3.1",
"vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^6.0.4"
},
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
"packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48"
}

2402
interface/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -310,15 +310,13 @@ const CustomEntities = () => {
/>
)}
</Cell>
<Cell>{ei.ram > 0 ? '' : showHex(ei.device_id as number, 2)}</Cell>
<Cell>{ei.ram > 0 ? '' : showHex(ei.type_id as number, 3)}</Cell>
<Cell>{ei.ram > 0 ? '' : ei.offset}</Cell>
<Cell>
{ei.ram === 1
? 'RAM'
: ei.ram === 2
? 'NVS'
: DeviceValueTypeNames[ei.value_type]}
{ei.ram === 1 ? '' : showHex(ei.device_id as number, 2)}
</Cell>
<Cell>{ei.ram === 1 ? '' : showHex(ei.type_id as number, 3)}</Cell>
<Cell>{ei.ram === 1 ? '' : ei.offset}</Cell>
<Cell>
{ei.ram === 1 ? 'RAM' : DeviceValueTypeNames[ei.value_type]}
</Cell>
<Cell>{formatValue(ei.value, ei.uom)}</Cell>
</Row>

View File

@@ -205,10 +205,9 @@ const CustomEntitiesDialog = ({
>
<MenuItem value={0}>EMS-{LL.VALUE(1)}</MenuItem>
<MenuItem value={1}>RAM-{LL.VALUE(1)}</MenuItem>
<MenuItem value={2}>NVS-{LL.VALUE(1)}</MenuItem>
</TextField>
</Grid>
{editItem.ram > 0 && (
{editItem.ram === 1 && (
<>
<Grid>
<TextField

View File

@@ -111,14 +111,13 @@ const Customizations = () => {
const [selectedDeviceTypeNameURL, setSelectedDeviceTypeNameURL] =
useState<string>(''); // needed for API URL
const [selectedDeviceName, setSelectedDeviceName] = useState<string>('');
const [selectedDeviceBrand, setSelectedDeviceBrand] = useState<string>('');
const { send: sendResetCustomizations } = useRequest(resetCustomizations(), {
immediate: false
});
const { send: sendDeviceName } = useRequest(
(data: { id: number; name: string; brand: string }) => writeDeviceName(data),
(data: { id: number; name: string }) => writeDeviceName(data),
{
immediate: false
}
@@ -268,7 +267,6 @@ const Customizations = () => {
if (device) {
setSelectedDeviceTypeNameURL(device.url || '');
setSelectedDeviceName(device.n);
setSelectedDeviceBrand(device.b);
}
setNumChanges(0);
setRestartNeeded(false);
@@ -444,11 +442,7 @@ const Customizations = () => {
}, [devices, deviceEntities, selectedDevice, sendCustomizationEntities, LL]);
const renameDevice = useCallback(async () => {
await sendDeviceName({
id: selectedDevice,
name: selectedDeviceName,
brand: selectedDeviceBrand
})
await sendDeviceName({ id: selectedDevice, name: selectedDeviceName })
.then(() => {
toast.success(LL.UPDATED_OF(LL.NAME(1)));
})
@@ -459,14 +453,7 @@ const Customizations = () => {
setRename(false);
await fetchCoreData();
});
}, [
selectedDevice,
selectedDeviceName,
selectedDeviceBrand,
sendDeviceName,
LL,
fetchCoreData
]);
}, [selectedDevice, selectedDeviceName, sendDeviceName, LL, fetchCoreData]);
const renderDeviceList = () => (
<>
@@ -475,26 +462,15 @@ const Customizations = () => {
</Box>
<Box display="flex" flexWrap="wrap" alignItems="center" gap={2}>
{rename ? (
<>
<TextField
name="device"
label={LL.EMS_DEVICE()}
style={{ minWidth: '48%' }}
variant="outlined"
value={selectedDeviceName}
onChange={(e) => setSelectedDeviceName(e.target.value)}
margin="normal"
/>
<TextField
name="brand"
label={LL.BRAND()}
style={{ minWidth: '48%' }}
variant="outlined"
value={selectedDeviceBrand}
onChange={(e) => setSelectedDeviceBrand(e.target.value)}
margin="normal"
/>
</>
<TextField
name="device"
label={LL.EMS_DEVICE()}
fullWidth
variant="outlined"
value={selectedDeviceName}
onChange={(e) => setSelectedDeviceName(e.target.value)}
margin="normal"
/>
) : (
<TextField
name="device"

View File

@@ -262,6 +262,10 @@ const Dashboard = memo(() => {
return (
<>
{!data.connected && (
<MessageBox level="error" message={LL.EMS_BUS_WARNING()} />
)}
{data.connected && data.nodes.length > 0 && !hasFavEntities && (
<MessageBox mb={2} level="warning">
<Typography>

View File

@@ -8,7 +8,7 @@ import {
useState
} from 'react';
import { IconContext } from 'react-icons';
import { Link, useNavigate } from 'react-router';
import { useNavigate } from 'react-router';
import { toast } from 'react-toastify';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
@@ -534,17 +534,7 @@ const Devices = memo(() => {
const renderCoreData = () => (
<>
{!coreData.connected ? (
<MessageBox level="error" message={LL.EMS_BUS_WARNING() + '.'}>
&nbsp;(
<Link
target="_blank"
to="https://docs.emsesp.org/Troubleshooting#ems-bus-is-not-connecting"
style={{ color: 'white' }}
>
{LL.ONLINE_HELP()}
</Link>
)
</MessageBox>
<MessageBox level="error" message={LL.EMS_BUS_WARNING()} />
) : (
<Box justifyContent="center" flexDirection="column">
<IconContext.Provider

View File

@@ -120,7 +120,7 @@ const HelpComponent = () => {
label: () => LL.HELP_INFORMATION_1()
},
{
href: 'https://discord.gg/GP9DPSgeJq',
href: 'https://discord.gg/3J3GgnzpyT',
icon: <CommentIcon />,
label: () => LL.HELP_INFORMATION_2()
},

View File

@@ -43,16 +43,6 @@ 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

@@ -28,7 +28,6 @@ import {
FormLoader,
MessageBox,
SectionContent,
ValidatedPasswordField,
ValidatedTextField,
useLayoutTitle
} from 'components';
@@ -352,156 +351,6 @@ 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

@@ -20,6 +20,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import { saveFile } from 'utils';
interface DownloadButton {
key: string;
type: string;
label: string | number;
isGridButton: boolean;
@@ -65,31 +66,31 @@ const DownloadUpload = () => {
const downloadButtons: DownloadButton[] = useMemo(
() => [
{
key: 'settings',
type: 'settings',
label: LL.SETTINGS_OF(LL.APPLICATION()),
isGridButton: true
},
{
key: 'customizations',
type: 'customizations',
label: LL.CUSTOMIZATIONS(),
isGridButton: true
},
{
key: 'entities',
type: 'entities',
label: LL.CUSTOM_ENTITIES(0),
isGridButton: true
},
{
key: 'schedule',
type: 'schedule',
label: LL.SCHEDULE(0),
isGridButton: true
},
{
type: 'systembackup',
label: LL.DOWNLOAD_SYSTEM_BACKUP(),
isGridButton: true
},
{
key: 'allvalues',
type: 'allvalues',
label: LL.ALLVALUES(),
isGridButton: false
@@ -132,7 +133,7 @@ const DownloadUpload = () => {
<Grid container spacing={2}>
{gridButtons.map((button) => (
<Grid key={button.type}>
<Grid key={button.key}>
<Button
startIcon={<DownloadIcon />}
variant="outlined"

View File

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

View File

@@ -60,16 +60,18 @@ const SystemMonitor = () => {
const { statusMessage, isUploading, progressValue } = useMemo(() => {
const status = data?.status;
const message =
status && status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING
? LL.WAIT_FIRMWARE()
: status === SystemStatusCodes.SYSTEM_STATUS_PENDING_RESTART
? LL.APPLICATION_RESTARTING()
: status === SystemStatusCodes.SYSTEM_STATUS_NORMAL
? LL.RESTARTING_PRE()
: status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD
? 'Upload Failed'
: LL.RESTARTING_POST();
let message = '';
if (status && status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING) {
message = LL.WAIT_FIRMWARE();
} else if (status === SystemStatusCodes.SYSTEM_STATUS_PENDING_RESTART) {
message = LL.APPLICATION_RESTARTING();
} else if (status === SystemStatusCodes.SYSTEM_STATUS_NORMAL) {
message = LL.RESTARTING_PRE();
} else if (status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD) {
message = 'Upload Failed';
} else {
message = LL.RESTARTING_POST();
}
const uploading =
status !== undefined && status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING;

View File

@@ -274,6 +274,7 @@ const InstallDialog = memo(
fetchDevVersion,
latestVersion,
latestDevVersion,
downloadOnly,
platform,
LL,
onClose,
@@ -283,6 +284,7 @@ const InstallDialog = memo(
fetchDevVersion: boolean;
latestVersion?: VersionInfo;
latestDevVersion?: VersionInfo;
downloadOnly: boolean;
platform: string;
LL: TranslationFunctions;
onClose: () => void;
@@ -307,7 +309,7 @@ const InstallDialog = memo(
<DialogContent dividers>
<Typography mb={2}>
{LL.INSTALL_VERSION(
LL.INSTALL(),
downloadOnly ? LL.DOWNLOAD(1) : LL.INSTALL(),
fetchDevVersion ? latestDevVersion?.name : latestVersion?.name
)}
</Typography>
@@ -331,14 +333,16 @@ const InstallDialog = memo(
{LL.DOWNLOAD(0)}
</Link>
</Button>
<Button
startIcon={<WarningIcon color="warning" />}
variant="outlined"
onClick={() => onInstall(binURL)}
color="primary"
>
{LL.INSTALL()}
</Button>
{!downloadOnly && (
<Button
startIcon={<WarningIcon color="warning" />}
variant="outlined"
onClick={() => onInstall(binURL)}
color="primary"
>
{LL.INSTALL()}
</Button>
)}
</DialogActions>
</Dialog>
);
@@ -419,6 +423,7 @@ const Version = () => {
const [stableUpgradeAvailable, setStableUpgradeAvailable] =
useState<boolean>(false);
const [internetLive, setInternetLive] = useState<boolean>(false);
const [downloadOnly, setDownloadOnly] = useState<boolean>(false);
const [showVersionInfo, setShowVersionInfo] = useState<number>(0); // 1 = stable, 2 = dev, 3 = partition
const [firmwareSize, setFirmwareSize] = useState<number>(0);
@@ -444,6 +449,9 @@ const Version = () => {
error
} = useRequest(SystemApi.readSystemStatus).onSuccess((event) => {
const systemData = event.data as VersionData;
if (systemData.arduino_version.startsWith('Tasmota')) {
setDownloadOnly(true);
}
setUsingDevVersion(systemData.emsesp_version.includes('dev'));
});
@@ -807,6 +815,7 @@ const Version = () => {
fetchDevVersion={fetchDevVersion}
latestVersion={latestVersion}
latestDevVersion={latestDevVersion}
downloadOnly={downloadOnly}
platform={platform}
LL={LL}
onClose={closeInstallDialog}
@@ -842,6 +851,7 @@ const Version = () => {
locale,
openInstallDialog,
fetchDevVersion,
downloadOnly,
me.admin,
showButtons,
handleVersionInfoClose,

View File

@@ -186,8 +186,7 @@ const cz: Translation = {
BUFFER_SIZE: 'Maximální velikost vyrovnávací paměti',
COMPACT: 'Kompaktní',
DOWNLOAD_SETTINGS_TEXT: 'Vytvořte zálohu svého nastavení a konfigurace',
DOWNLOAD_SETTINGS_TEXT2: 'Exportovat všechny hodnoty',
DOWNLOAD_SYSTEM_BACKUP: 'Systémová záloha',
DOWNLOAD_SETTINGS_TEXT2: 'Exportovat všechna data',
UPLOAD_TEXT: 'Nahrajte nový soubor firmwaru (.bin) nebo záložní soubor (.json)',
UPLOAD_DROP_TEXT: 'Přetáhněte soubor sem nebo klikněte pro výběr',
ERROR: 'Neočekávaná chyba, zkuste to prosím znovu',
@@ -358,8 +357,7 @@ const cz: Translation = {
FIRMWARE_VERSION_INFO: 'Informace o verzi firmwaru',
NO_DATA: 'Žádná data',
USER_PROFILE: 'Uživatelský profil',
STORED_VERSIONS: 'Uložené verze',
ONLINE_HELP: 'online nápověda'
STORED_VERSIONS: 'Uložené verze'
};
export default cz;

View File

@@ -186,8 +186,7 @@ const de: Translation = {
BUFFER_SIZE: 'Max. Puffergröße',
COMPACT: 'Kompakte Darstellung',
DOWNLOAD_SETTINGS_TEXT: 'Erstellen Sie eine Sicherung Ihrer Konfigurationen und Einstellungen',
DOWNLOAD_SETTINGS_TEXT2: 'Exportiere alle Werte',
DOWNLOAD_SYSTEM_BACKUP: 'System Sicherung',
DOWNLOAD_SETTINGS_TEXT2: 'Exportiere alle Daten',
UPLOAD_TEXT: 'Laden Sie eine neue Firmware-Datei (.bin) oder eine Sicherungsdatei (.json) hoch',
UPLOAD_DROP_TEXT: 'Legen Sie eine Firmware-Datei (.bin) ab oder klicken Sie hier',
ERROR: 'Unerwarteter Fehler, bitte versuchen Sie es erneut.',
@@ -358,8 +357,7 @@ const de: Translation = {
FIRMWARE_VERSION_INFO: 'Firmware-Versionsinformation',
NO_DATA: 'Keine Daten',
USER_PROFILE: 'Benutzerprofil',
STORED_VERSIONS: 'Gespeicherte Versionen',
ONLINE_HELP: 'Online-Hilfe'
STORED_VERSIONS: 'Gespeicherte Versionen'
};
export default de;

View File

@@ -186,8 +186,7 @@ const en: Translation = {
BUFFER_SIZE: 'Max Buffer Size',
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
DOWNLOAD_SETTINGS_TEXT2: 'Export all values',
DOWNLOAD_SYSTEM_BACKUP: 'System Backup',
DOWNLOAD_SETTINGS_TEXT2: 'Export all data',
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
ERROR: 'Unexpected Error, please try again',
@@ -358,8 +357,7 @@ const en: Translation = {
FIRMWARE_VERSION_INFO: 'Firmware Version Information',
NO_DATA: 'No data',
USER_PROFILE: 'User Profile',
STORED_VERSIONS: 'Stored Versions',
ONLINE_HELP: 'online help'
STORED_VERSIONS: 'Stored Versions'
};
export default en;

View File

@@ -186,8 +186,7 @@ const fr: Translation = {
BUFFER_SIZE: 'Max taille du buffer',
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Créer une sauvegarde de vos paramètres et configurations',
DOWNLOAD_SETTINGS_TEXT2: 'Exporter toutes les valeurs',
DOWNLOAD_SYSTEM_BACKUP: 'Sauvegarde système',
DOWNLOAD_SETTINGS_TEXT2: 'Exporter toutes les données',
UPLOAD_TEXT: 'Télécharger un nouveau fichier firmware (.bin) ou une sauvegarde (.json)',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
ERROR: 'Erreur inattendue, veuillez réessayer',
@@ -358,8 +357,7 @@ const fr: Translation = {
FIRMWARE_VERSION_INFO: 'Informations sur la version du firmware',
NO_DATA: 'Aucune donnée',
USER_PROFILE: 'Profil utilisateur',
STORED_VERSIONS: 'Versions stockées',
ONLINE_HELP: 'aide en ligne'
STORED_VERSIONS: 'Versions stockées'
};
export default fr;

View File

@@ -186,8 +186,7 @@ const it: Translation = {
BUFFER_SIZE: 'Max Buffer Size',
COMPACT: 'Compatto',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
DOWNLOAD_SETTINGS_TEXT2: 'Esporta tutti i valori',
DOWNLOAD_SYSTEM_BACKUP: 'Backup sistema',
DOWNLOAD_SETTINGS_TEXT2: 'Esporta tutti i dati',
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
ERROR: 'Errore Inaspettato, prego tenta ancora',
@@ -358,8 +357,7 @@ const it: Translation = {
FIRMWARE_VERSION_INFO: 'Informazioni sulla versione del firmware',
NO_DATA: 'Nessun dato',
USER_PROFILE: 'Profilo utente',
STORED_VERSIONS: 'Versioni memorizzate',
ONLINE_HELP: 'aiuto online'
STORED_VERSIONS: 'Versioni memorizzate'
};
export default it;

View File

@@ -27,7 +27,7 @@ const nl: Translation = {
REFRESH: 'Ververs',
EXPORT: 'Export',
FAVORITES: "Favorieten",
DEVICE_DETAILS: 'Apparaat Gegevens',
DEVICE_DETAILS: 'Device Gegevens',
ID_OF: '{0} ID',
DEVICE: 'Apparaat',
PRODUCT: 'Product',
@@ -65,7 +65,7 @@ const nl: Translation = {
TEMP_SENSOR: 'Temperatuur sensor',
TEMP_SENSORS: 'Temperatuur Sensoren',
WRITE_CMD_SENT: 'Schrijf commando gestuurd',
EMS_BUS_WARNING: 'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden loop dan de instellingen na en in het bijzonder het apparaat type profiel.',
EMS_BUS_WARNING: 'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden dan loop de instellingen na en in het bijzonder het apparaat type profiel na.',
EMS_BUS_SCANNING: 'Scannen naar EMS apparaten...',
CONNECTED: 'Verbonden',
TX_ISSUES: 'Tx bus probleem. Probeer een andere Tx verzendmodus',
@@ -75,7 +75,7 @@ const nl: Translation = {
EMS_DEVICE: 'EMS Apparaat',
SUCCESS: 'SUCCESS',
FAIL: 'MISLUKT',
QUALITY: 'KWALITEIT',
QUALITY: 'QUALITEIT',
SCAN: 'Scan',
STATUS_NAMES: [
'EMS Telegrammen ontvangen (Rx)',
@@ -120,7 +120,7 @@ const nl: Translation = {
ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)',
ENABLE_SHOWER_ALERT: 'Activeer Douchemelding',
TRIGGER_TIME: 'Trigger tijd',
COLD_SHOT_DURATION: 'Lengte koud water puls',
COLD_SHOT_DURATION: 'Tijd Shot koud water',
FORMATTING_OPTIONS: 'Formatteringsopties',
BOOLEAN_FORMAT_DASHBOARD: 'Boolean formaat web',
BOOLEAN_FORMAT_API: 'Boolean formaat API/MQTT',
@@ -143,7 +143,7 @@ const nl: Translation = {
CUSTOMIZATIONS_FULL: 'Te veel entiteiten geselecteerd. Sla op in delen aub',
CUSTOMIZATIONS_SAVED: 'Custom aanpassingen opgeslagen',
CUSTOMIZATIONS_HELP_1: 'Selecteer een apparaat en pas de entiteiten aan door middel van de opties',
CUSTOMIZATIONS_HELP_2: 'Markeer als favoriet',
CUSTOMIZATIONS_HELP_2: 'Markeer as favoriet',
CUSTOMIZATIONS_HELP_3: 'Zet schrijfacties uit',
CUSTOMIZATIONS_HELP_4: 'Uitsluiten van MQTT en API',
CUSTOMIZATIONS_HELP_5: 'verbergen voor apparaten',
@@ -186,13 +186,12 @@ const nl: Translation = {
BUFFER_SIZE: 'Max buffer grootte',
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Maak een back-up van uw configuratie en instellingen',
DOWNLOAD_SETTINGS_TEXT2: 'Exporteer alle waarden',
DOWNLOAD_SYSTEM_BACKUP: 'Systeem Backup',
DOWNLOAD_SETTINGS_TEXT2: 'Exporteer alle data',
UPLOAD_TEXT: 'Upload een nieuw firmwarebestand (.bin) of een back-upbestand (.json)',
UPLOAD_DROP_TEXT: 'Sleep en firmware .bin bestand hierheen of klik hier',
ERROR: 'Onverwachte fout, probeer opnieuw',
TIME_SET: 'Tijd ingesteld',
MANAGE_USERS: 'Gebruikersbeheer',
MANAGE_USERS: 'Beheer Gebruikers',
IS_ADMIN: 'is Admin',
USER_WARNING: 'U dient tenminste 1 admin gebruiker te configureren',
ADD: 'Toevoegen',
@@ -201,7 +200,7 @@ const nl: Translation = {
GENERATING_TOKEN: 'Token aan het genereren',
USER: 'Gebruiker',
MODIFY: 'Aanpassen',
SU_TEXT: 'Het su (super user) wachtwoord wordt gebruikt om authorisatie tokens te ondertekenen en ook om admin privileges te activeren in de console.',
SU_TEXT: 'Het su (super user) wachtwoord wordt gebruikt om authorisatie tokens te signeren en ook om admin privileges te activeren in de console.',
NOT_ENABLED: 'Niet geactiveerd',
ERRORS_OF: '{0} Foutmeldingen',
DISCONNECT_REASON: 'Verbinding verbroken vanwege',
@@ -358,8 +357,7 @@ const nl: Translation = {
FIRMWARE_VERSION_INFO: 'Informatie over firmwareversie',
NO_DATA: 'Geen data',
USER_PROFILE: 'Gebruikersprofiel',
STORED_VERSIONS: 'Opgeslagen versies',
ONLINE_HELP: 'online help'
STORED_VERSIONS: 'Opgeslagen versies'
};
export default nl;
export default nl;

View File

@@ -186,8 +186,7 @@ const no: Translation = {
BUFFER_SIZE: 'Max Buffer Størrelse',
COMPACT: 'Komprimere',
DOWNLOAD_SETTINGS_TEXT: 'Lag en sikkerhetskopi av dine konfigurasjon og innstillinger',
DOWNLOAD_SETTINGS_TEXT2: 'Eksporter alle verdier',
DOWNLOAD_SYSTEM_BACKUP: 'System Sikkerhetskopi',
DOWNLOAD_SETTINGS_TEXT2: 'Eksporter alle data',
UPLOAD_TEXT: 'Last opp en ny firmware fil (.bin) eller en sikkerhetskopi fil (.json)',
UPLOAD_DROP_TEXT: 'Dropp en firmware fil (.bin) eller klikk her',
ERROR: 'Ukjent feil, prøv igjen',
@@ -358,8 +357,7 @@ const no: Translation = {
FIRMWARE_VERSION_INFO: 'Informasjon om firmwareversjon',
NO_DATA: 'Ingen data',
USER_PROFILE: 'Brukerprofil',
STORED_VERSIONS: 'Lagret versjoner',
ONLINE_HELP: 'online hjelp'
STORED_VERSIONS: 'Lagret versjoner'
};
export default no;

View File

@@ -186,8 +186,7 @@ const pl: BaseTranslation = {
BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)',
COMPACT: 'Kompaktowy',
DOWNLOAD_SETTINGS_TEXT: 'Utwórz kopię swoich ustawień i konfiguracji',
DOWNLOAD_SETTINGS_TEXT2: 'Eksportuj wszystkie wartości',
DOWNLOAD_SYSTEM_BACKUP: 'Kopia zapasowa systemu',
DOWNLOAD_SETTINGS_TEXT2: 'Eksportuj wszystkie dane',
UPLOAD_TEXT: 'Wgraj nowy plik firmware (.bin) lub kopię ustawień (.json)',
UPLOAD_DROP_TEXT: 'Upuść plik firmware .bin lub kliknij tutaj',
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
@@ -358,8 +357,7 @@ const pl: BaseTranslation = {
FIRMWARE_VERSION_INFO: 'Informacje o wersji firmware',
NO_DATA: 'Brak danych',
USER_PROFILE: 'Profil użytkownika',
STORED_VERSIONS: 'Zapisane wersje',
ONLINE_HELP: 'pomoc online'
STORED_VERSIONS: 'Zapisane wersje'
};
export default pl;

View File

@@ -186,8 +186,7 @@ const sk: Translation = {
BUFFER_SIZE: 'Buffer-max. veľkosť',
COMPACT: 'Kompaktné',
DOWNLOAD_SETTINGS_TEXT: 'Vytvorte zálohu svojej konfigurácie a nastavení',
DOWNLOAD_SETTINGS_TEXT2: 'Exportovať všetky hodnoty',
DOWNLOAD_SYSTEM_BACKUP: 'Systémová záloha',
DOWNLOAD_SETTINGS_TEXT2: 'Exportovať všetky dáta',
UPLOAD_TEXT: 'Nahrajte nový súbor firmvéru (.bin) alebo súbor zálohy (.json)',
UPLOAD_DROP_TEXT: 'Presuňte súbor .bin firmvéru alebo kliknite sem',
ERROR: 'Neočakávaná chyba, prosím skúste to znova',
@@ -358,8 +357,7 @@ const sk: Translation = {
FIRMWARE_VERSION_INFO: 'Informácie o verzii firmware',
NO_DATA: 'Žiadne dáta',
USER_PROFILE: 'Profil používateľa',
STORED_VERSIONS: 'Uložené verzie',
ONLINE_HELP: 'online pomoc'
STORED_VERSIONS: 'Uložené verzie'
};
export default sk;

View File

@@ -186,8 +186,7 @@ const sv: Translation = {
BUFFER_SIZE: 'Max bufferstorlek',
COMPACT: 'Komprimerad',
DOWNLOAD_SETTINGS_TEXT: 'Skapa en säkerhetskopia av din konfiguration och inställningar',
DOWNLOAD_SETTINGS_TEXT2: 'Exportera alla värden',
DOWNLOAD_SYSTEM_BACKUP: 'System säkerhetskopia',
DOWNLOAD_SETTINGS_TEXT2: 'Exportera alla data',
UPLOAD_TEXT: 'Ladda upp en ny firmwarefil (.bin) eller en säkerhetskopiafil (.json)',
UPLOAD_DROP_TEXT: 'Droppa en firmware .bin fil eller klicka här',
ERROR: 'Okänt fel, var god försök igen',
@@ -358,8 +357,7 @@ const sv: Translation = {
FIRMWARE_VERSION_INFO: 'Information om firmwareversion',
NO_DATA: 'Ingen data',
USER_PROFILE: 'Användarprofil',
STORED_VERSIONS: 'Lagrad versioner',
ONLINE_HELP: 'online hjälp'
STORED_VERSIONS: 'Lagrad versioner'
};
export default sv;

View File

@@ -186,8 +186,7 @@ const tr: Translation = {
BUFFER_SIZE: 'En fazla bellek boyutu',
COMPACT: 'Sıkışık',
DOWNLOAD_SETTINGS_TEXT: 'Yapılandırma ve ayarlarınızın yedekleme yapın',
DOWNLOAD_SETTINGS_TEXT2: 'Tüm değerleri dışarı al',
DOWNLOAD_SYSTEM_BACKUP: 'Sistem yedekleme',
DOWNLOAD_SETTINGS_TEXT2: 'Tüm verileri dışarı al',
UPLOAD_TEXT: 'Yeni bir firmware dosyası (.bin) veya yedek dosyası (.json) yükle',
UPLOAD_DROP_TEXT: 'Bir firmware .bin dosyası veya buraya tıklayın',
ERROR: 'Beklenemedik hata, lütfen tekrar deneyin.',
@@ -358,8 +357,7 @@ const tr: Translation = {
FIRMWARE_VERSION_INFO: 'Firmware Sürüm Bilgisi',
NO_DATA: 'Hiçbir veri yok',
USER_PROFILE: 'Kullanıcı Profili',
STORED_VERSIONS: 'Kaydedilmiş Sürümler',
ONLINE_HELP: 'online yardım'
STORED_VERSIONS: 'Kaydedilmiş Sürümler'
};
export default tr;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -49,10 +49,6 @@ 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
@@ -70,10 +66,14 @@ 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
#ifndef EMC_NUM_POOL_ELEMENTS
#define EMC_NUM_POOL_ELEMENTS 32
#endif
#ifndef EMC_SIZE_POOL_ELEMENTS
#define EMC_SIZE_POOL_ELEMENTS 128
#endif
#endif
#ifndef TASMOTA_SDK
#define EMC_CLIENT_SECURE
#endif

View File

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

View File

@@ -69,17 +69,7 @@ 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;
@@ -108,7 +98,6 @@ 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

@@ -6,65 +6,66 @@ For a copy, see <https://opensource.org/licenses/MIT> or
the LICENSE file.
*/
#ifndef NO_TLS_SUPPORT
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#include "ClientSecureSync.h"
#include <lwip/sockets.h>
#include "../Config.h"
#include <lwip/sockets.h> // socket options
namespace espMqttClientInternals {
ClientSecureSync::ClientSecureSync()
: 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();
: client() {
// empty
}
bool ClientSecureSync::connect(IPAddress ip, uint16_t port) {
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 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 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;
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;
}
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,11 +8,15 @@ the LICENSE file.
#pragma once
#ifndef NO_TLS_SUPPORT
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
// #include "esp_tls.h"
// Added for EMS-ESP
#include "../Config.h"
#if defined(EMC_CLIENT_SECURE)
#include <WiFiClientSecure.h> // includes IPAddress
#else
#include <WiFiClient.h>
#include <ESP_SSLClient.h>
#endif
#include "Transport.h"
namespace espMqttClientInternals {
@@ -20,7 +24,6 @@ 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;
@@ -28,11 +31,14 @@ class ClientSecureSync : public Transport {
void stop() override;
bool connected() override;
bool disconnected() override;
WiFiClient basic_client;
ESP_SSLClient client;
// added for EMS-ESP
#if defined(EMC_CLIENT_SECURE)
WiFiClientSecure client;
#else
WiFiClient client;
#endif
};
} // namespace espMqttClientInternals
#endif
#endif

View File

@@ -8,6 +8,50 @@ 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)
@@ -34,35 +78,36 @@ espMqttClientSecure::espMqttClientSecure(uint8_t priority, uint8_t core)
}
espMqttClientSecure & espMqttClientSecure::setInsecure() {
#ifndef NO_TLS_SUPPORT
#if defined(EMC_CLIENT_SECURE)
_client.client.setInsecure();
#endif
return *this;
}
espMqttClientSecure & espMqttClientSecure::setCACert(const char * rootCA) {
#ifndef NO_TLS_SUPPORT
#if defined(EMC_CLIENT_SECURE)
_client.client.setCACert(rootCA);
#endif
return *this;
}
espMqttClientSecure & espMqttClientSecure::setCertificate(const char * clientCa) {
#ifndef NO_TLS_SUPPORT
#if defined(EMC_CLIENT_SECURE)
_client.client.setCertificate(clientCa);
#endif
return *this;
}
espMqttClientSecure & espMqttClientSecure::setPrivateKey(const char * privateKey) {
#ifndef NO_TLS_SUPPORT
#if defined(EMC_CLIENT_SECURE)
_client.client.setPrivateKey(privateKey);
#endif
return *this;
}
espMqttClientSecure & espMqttClientSecure::setPreSharedKey(const char * pskIdent, const char * psKey) {
#ifndef NO_TLS_SUPPORT
#if defined(EMC_CLIENT_SECURE)
_client.client.setPreSharedKey(pskIdent, psKey);
#endif
return *this;
}
@@ -75,4 +120,9 @@ espMqttClient::espMqttClient()
, _client() {
_transport = &_client;
}
#elif defined(_WIN32) || defined(__APPLE__)
// Windows
espMqttClient::espMqttClient()
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO) {
}
#endif

View File

@@ -65,16 +65,10 @@ 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
};
#endif
#if defined(__linux__)
#elif defined(__linux__)
class espMqttClient : public MqttClientSetup<espMqttClient> {
public:
espMqttClient();
@@ -82,4 +76,10 @@ class espMqttClient : public MqttClientSetup<espMqttClient> {
protected:
espMqttClientInternals::ClientPosix _client;
};
#elif defined(_WIN32) || defined(__APPLE__)
class espMqttClient : public MqttClientSetup<espMqttClient> {
public:
espMqttClient();
};
#endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,8 +12,8 @@
"@msgpack/msgpack": "^3.1.3",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"formidable": "^3.5.4",
"itty-router": "^5.0.23",
"prettier": "^3.8.1"
"itty-router": "^5.0.22",
"prettier": "^3.7.4"
},
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
"packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48"
}

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

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

View File

@@ -363,8 +363,6 @@ function export_data(type: string) {
return emsesp_modules;
case 'allvalues':
return emsesp_allvalues;
case 'systembackup':
return emsesp_systembackup;
default:
return status(404);
}
@@ -734,8 +732,6 @@ const emsesp_info = {
]
};
const emsesp_systembackup = {};
const emsesp_allvalues = {
'Boiler Nefit Trendline HRC30 (DeviceID:0x08, ProductID:123, Version:06.01)': {
'force heating off': 'off',
@@ -4568,7 +4564,7 @@ router
let sorted_devices = [...emsesp_coredata.devices].sort((a, b) => a.t - b.t);
// append emsesp_coredata to sorted_devices so Custom is always at the end of the list
sorted_devices.push(emsesp_coredata_custom);
// return { connected: false, devices: [] }; // uncomment if simulating no devices...
// sorted_devices = []; // uncomment if simulating no devices...
return { connected: true, devices: sorted_devices };
})
.get(EMSESP_SENSOR_DATA_ENDPOINT, () => {

View File

@@ -15,14 +15,15 @@ description = EMS-ESP Firmware for the ESP32
src_dir = src
lib_dir = lib
boards_dir = boards
; build_cache_dir = .pio/build_cache
extra_configs =
factory_settings.ini
pio_local.ini
[common]
core_build_flags = -std=c++17 -std=gnu++17 -O3 -Wno-type-limits -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-format -Wno-missing-field-initializers
core_unbuild_flags = -std=gnu++11
core_build_flags = -std=c++17 -std=gnu++17 -O3 -flto=auto -Wno-type-limits -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-format
core_unbuild_flags = -std=gnu++11 -fno-lto
my_build_flags =
@@ -53,21 +54,13 @@ 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.03.50/platform-espressif32.zip ; Tasmota Arduino Core 3.3.7 based on IDF 5.5.3.260313
; 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 = https://github.com/tasmota/platform-espressif32/releases/download/2026.03.50/platform-espressif32.zip ; Tasmota Arduino Core 3.3.7 based on IDF 5.5.3.260313
platform = espressif32@6.12.0 ; Arduino Core 2.0.17 / IDF 4.4.7
; 32MB Flash variants
[espressif32_base_32M]
@@ -75,7 +68,29 @@ framework = arduino
board_build.partitions = partitions/esp32_partition_32M.csv
board_upload.flash_size = 32MB
board_build.app_partition_name = app0
platform = https://github.com/tasmota/platform-espressif32/releases/download/2026.03.50/platform-espressif32.zip ; Tasmota Arduino Core 3.3.7 based on IDF 5.5.3.260313
platform = espressif32@6.12.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
[env]
build_flags =
@@ -89,12 +104,11 @@ monitor_filters = direct
build_type = release
board_build.filesystem = littlefs
lib_deps =
bblanchon/ArduinoJson @ 7.4.3
bblanchon/ArduinoJson @ 7.4.2
ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.10.3
https://github.com/mobizt/ReadyMail.git @ 0.3.8
https://github.com/mobizt/ESP_SSLClient.git @ 3.1.3
; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
ESP32Async/ESPAsyncWebServer @ 3.9.4
https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
; builds the web interface only, not the firmware
[env:build_webUI]
@@ -106,17 +120,18 @@ 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
extends = espressif32_base_4M
; 4MB ESP32 - no SSL, no PSRAM - like a BBQKees older S32 and E32 models - uses Tasmota
extends = espressif32_base_T_4M
board = s_4M
[env:s_16M]
; 16MB ESP32 - no PSRAM - like a BBQKees later S32 V2 models
extends = espressif32_base_16M
; 16MB ESP32 - no PSRAM - like a BBQKees later S32 V2 models - uses Tasmota
extends = espressif32_base_T_16M
board = s_16M
[env:s_16M_P]
@@ -135,29 +150,36 @@ extends = espressif32_base_32M
board = s3_32M_P
[env:s2_4M_P]
; based on lolin_s2_mini 4MB with 2MB PSRAM
extends = espressif32_base_4M
; based on lolin_s2_mini 4MB with 2MB PSRAM - uses Tasmota
extends = espressif32_base_T_4M
board = s2_4M_P
[env:c3_mini_4M]
; based on lolin_c3_mini 4MB, no PSRAM
extends = espressif32_base_4M
; based on lolin_c3_mini 4MB, no PSRAM - uses Tasmota
extends = espressif32_base_T_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_4M
extends = espressif32_base_T_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
[env:c6]
framework = arduino
board_build.partitions = partitions/esp32_partition_4M.csv
board_upload.flash_size = 4MB
board_build.app_partition_name = app0
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21-2/platform-espressif32.zip ; Arduino Release v3.2.1 based on ESP-IDF v5.4.2
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.30-2/platform-espressif32.zip ; Arduino Release v3.3.0 based on ESP-IDF v5.5.0
board = seeed_xiao_esp32c6
build_flags =
${common.build_flags}
-DBOARD_C6
; foundation for building and testing natively, standalone without an ESP32
; use the `standalone` environment instead of `native` for testing
@@ -168,7 +190,6 @@ build_flags =
build_src_flags =
-DEMSESP_STANDALONE -DEMSESP_TEST
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
-DNO_TLS_SUPPORT
-std=gnu++17 -Og -ggdb
-Wall -Wextra
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
@@ -191,7 +212,6 @@ build_src_filter =
-<../lib/uuid-syslog>
-<../lib/eModbus>
-<../lib/OneWire>
-<../src/core/ModuleLibrary.cpp>
lib_ldf_mode = off
lib_deps =
@@ -208,7 +228,6 @@ 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++17 -Og -ggdb
-Wall -Wextra
@@ -235,7 +254,6 @@ build_src_filter =
-<../lib/uuid-syslog>
-<../lib/eModbus>
-<../lib/OneWire>
-<../src/core/ModuleLibrary.cpp>
lib_ldf_mode = off
lib_deps = Unity
test_testing_command =

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ 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);
}
@@ -28,7 +29,6 @@ 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 NO_TLS_SUPPORT
#ifndef TASMOTA_SDK
if (_state.enableTLS) {
isSecure = true;
if (emsesp::EMSESP::system_.PSram() == 0) {
@@ -79,10 +79,6 @@ 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();
@@ -146,6 +142,28 @@ 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()) {
@@ -164,7 +182,7 @@ bool MqttSettingsService::configureMqtt() {
}
_reconfigureMqtt = false;
#ifndef NO_TLS_SUPPORT
#ifndef TASMOTA_SDK
if (_state.enableTLS) {
if (_state.rootCA == "insecure") {
emsesp::EMSESP::logger().debug("Start insecure MQTT");
@@ -201,7 +219,7 @@ bool MqttSettingsService::configureMqtt() {
}
void MqttSettings::read(MqttSettings & settings, JsonObject root) {
#ifndef NO_TLS_SUPPORT
#ifndef TASMOTA_SDK
root["enableTLS"] = settings.enableTLS;
root["rootCA"] = settings.rootCA;
#endif
@@ -237,10 +255,10 @@ void MqttSettings::read(MqttSettings & settings, JsonObject root) {
}
StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings) {
MqttSettings newSettings;
bool changed = false;
MqttSettings newSettings = {};
bool changed = false;
#ifndef NO_TLS_SUPPORT
#ifndef TASMOTA_SDK
newSettings.enableTLS = root["enableTLS"];
newSettings.rootCA = root["rootCA"] | "";
#else
@@ -298,10 +316,6 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
changed = true;
}
if (newSettings.ha_number_mode != settings.ha_number_mode) {
changed = true;
}
if (newSettings.entity_format != settings.entity_format) {
changed = true;
}
@@ -371,7 +385,7 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
emsesp::EMSESP::mqtt_.set_publish_time_heartbeat(newSettings.publish_time_heartbeat);
}
#ifndef NO_TLS_SUPPORT
#ifndef TASMOTA_SDK
// strip down to certificate only
newSettings.rootCA.replace("\r", "");
newSettings.rootCA.replace("\n", "");

View File

@@ -125,15 +125,16 @@ class MqttSettingsService : public StatefulService<MqttSettings> {
FSPersistence<MqttSettings> _fsPersistence;
// variable to help manage connection
volatile bool _reconfigureMqtt;
volatile unsigned long _disconnectedAt;
bool _reconfigureMqtt;
unsigned long _disconnectedAt;
// connection status
volatile espMqttClientTypes::DisconnectReason _disconnectReason;
espMqttClientTypes::DisconnectReason _disconnectReason;
// the MQTT client instance
MqttClient * _mqttClient;
void WiFiEvent(WiFiEvent_t event);
void onMqttConnect(bool sessionPresent);
void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason);
void

View File

@@ -11,6 +11,7 @@ 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);
}
@@ -19,10 +20,27 @@ void NTPSettingsService::begin() {
configureNTP();
}
void NTPSettingsService::loop() {
if (_connected != emsesp::EMSESP::system_.network_connected()) {
_connected = emsesp::EMSESP::system_.network_connected();
// 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;
configureNTP();
break;
default:
break;
}
}
@@ -37,9 +55,7 @@ void NTPSettingsService::configureNTP() {
} else {
setenv("TZ", _state.tzFormat.c_str(), 1);
tzset();
if (esp_sntp_enabled()) {
esp_sntp_stop();
}
esp_sntp_stop();
}
}
@@ -52,12 +68,7 @@ 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,14 +44,14 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin();
void loop();
static void ntp_received(struct timeval * tv);
private:
HttpEndpoint<NTPSettings> _httpEndpoint;
FSPersistence<NTPSettings> _fsPersistence;
volatile bool _connected;
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
// Network.onEvent([this](arduino_event_id_t event, arduino_event_info_t info) { WiFiEvent(event, info); });
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_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_STA);
WiFi.mode(WIFI_OFF);
WiFi.mode(WIFI_MODE_MAX);
WiFi.mode(WIFI_MODE_NULL);
// 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,118 +62,24 @@ 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.enableIPv6(true);
if (_state.staticIPConfig) {
ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
}
ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str());
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.enableIPv6(true);
#endif
if (_state.staticIPConfig) {
WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); // configure for static IP
}
@@ -387,7 +293,7 @@ const char * NetworkSettingsService::disconnectReason(uint8_t code) {
}
// handles both WiFI and Ethernet
void NetworkSettingsService::WiFiEvent(arduino_event_id_t event, arduino_event_info_t info) {
void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
#ifndef EMSESP_STANDALONE
switch (event) {
@@ -399,7 +305,7 @@ void NetworkSettingsService::WiFiEvent(arduino_event_id_t event, arduino_event_i
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
connectcount_ = connectcount_ + 1; // count the number of WiFi reconnects
connectcount_++; // count the number of WiFi reconnects
emsesp::EMSESP::logger().warning("WiFi disconnected (#%d). Reason: %s (%d)",
connectcount_,
disconnectReason(info.wifi_sta_disconnected.reason),
@@ -454,15 +360,25 @@ void NetworkSettingsService::WiFiEvent(arduino_event_id_t event, arduino_event_i
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: {
auto ip6 = IPAddress(IPv6, (uint8_t *)info.got_ip6.ip6_info.ip.addr, 0).toString();
#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
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

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

View File

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

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

View File

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

View File

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

View File

@@ -56,7 +56,6 @@ void IRAM_ATTR AnalogSensor::freqIrq2() {
#endif
void AnalogSensor::start(const bool factory_settings) {
// add hardcode sensors for BBQKees gateway boards
if (factory_settings && EMSESP::system_.board_profile() == "E32V2_2") {
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
auto newSensor = AnalogCustomization();
@@ -347,13 +346,23 @@ 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);
@@ -843,15 +852,6 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
return true;
}
if (!strcmp(cmd, F_(metrics))) {
std::string metrics = get_metrics_prometheus();
if (!metrics.empty()) {
output["api_data"] = metrics;
return true;
}
return false;
}
// this is for a specific sensor, return the value
const char * attribute_s = Command::get_attribute(cmd);
@@ -866,35 +866,6 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
return false; // not found
}
// generate Prometheus metrics format from analog values
std::string AnalogSensor::get_metrics_prometheus() {
std::string result;
result.reserve(sensors_.size() * 140);
char val[10];
for (auto & sensor : sensors_) {
result += (std::string) "# HELP emsesp_" + sensor.name() + " " + sensor.name();
if (sensor.type() != AnalogType::DIGITAL_OUT && sensor.type() != AnalogType::DIGITAL_IN) {
result += (std::string) ", " + EMSdevice::uom_to_string(sensor.uom());
} else {
result += (std::string) ", boolean";
}
result += (std::string) ", readable, visible";
if (sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::RGB || sensor.type() == AnalogType::PULSE
|| (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2)
|| (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
result += (std::string) ", writable";
}
result += (std::string) "\n# TYPE emsesp_" + sensor.name() + " gauge\n";
result += (std::string) "emsesp_" + sensor.name() + " ";
if (sensor.type() != AnalogType::DIGITAL_OUT && sensor.type() != AnalogType::DIGITAL_IN) {
result += (std::string)Helpers::render_value(val, sensor.value(), 2) + "\n";
} else {
result += (std::string)(sensor.value() == 0 ? "0\n" : "1\n");
}
}
return result;
}
// note we don't add the device and state classes here, as we do in the custom entity service
void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) {
output["name"] = (const char *)sensor.name();
@@ -1031,7 +1002,12 @@ 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

@@ -177,7 +177,6 @@ class AnalogSensor {
bool update(uint8_t gpio, const char * name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system);
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
void store_counters();
std::string get_metrics_prometheus();
static std::vector<uint8_t> exclude_types() {
return exclude_types_;
}

View File

@@ -19,7 +19,7 @@
#ifndef EMSESP_COMMAND_H_
#define EMSESP_COMMAND_H_
#include <map>
#include <unordered_map>
#include "console.h"
#include <esp32-psram.h>
@@ -153,8 +153,8 @@ class Command {
class SUrlParser {
private:
std::map<std::string, std::string> m_keysvalues;
std::vector<std::string> m_folders;
std::unordered_map<std::string, std::string> m_keysvalues;
std::vector<std::string> m_folders;
public:
SUrlParser() = default;
@@ -166,7 +166,7 @@ class SUrlParser {
return m_folders;
};
std::map<std::string, std::string> & params() {
std::unordered_map<std::string, std::string> & params() {
return m_keysvalues;
};

View File

@@ -86,6 +86,6 @@ using string_vector = std::vector<const char *>;
// Translation count - dynamically calculated at compile-time
enum { EMSESP_TRANSLATION_COUNT_END = __COUNTER__ };
static constexpr uint16_t EMSESP_TRANSLATION_COUNT = static_cast<int>(EMSESP_TRANSLATION_COUNT_END) - static_cast<int>(EMSESP_TRANSLATION_COUNT_START) - 1;
static constexpr uint16_t EMSESP_TRANSLATION_COUNT = EMSESP_TRANSLATION_COUNT_END - EMSESP_TRANSLATION_COUNT_START - 1;
#endif

View File

@@ -285,8 +285,6 @@ 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
@@ -295,9 +293,12 @@ 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

@@ -72,21 +72,6 @@ const char * EMSdevice::tag_to_mqtt(int8_t tag) {
return (DeviceValue::DeviceValueTAG_mqtt[tag > DeviceValue::NUM_TAGS ? 0 : tag]);
}
uint8_t EMSdevice::tag_to_flag(const uint8_t tag) {
if (tag >= DeviceValueTAG::TAG_HC1 && tag <= DeviceValueTAG::TAG_HC8) {
return CommandFlag::CMD_FLAG_HC;
} else if (tag >= DeviceValueTAG::TAG_DHW1 && tag <= DeviceValueTAG::TAG_DHW10) {
return CommandFlag::CMD_FLAG_DHW;
} else if (tag >= DeviceValueTAG::TAG_HS1 && tag <= DeviceValueTAG::TAG_HS16) {
return CommandFlag::CMD_FLAG_HS;
} else if (tag >= DeviceValueTAG::TAG_AHS1 && tag <= DeviceValueTAG::TAG_AHS1) {
return CommandFlag::CMD_FLAG_AHS;
} else if (tag >= DeviceValueTAG::TAG_SRC1 && tag <= DeviceValueTAG::TAG_SRC16) {
return CommandFlag::CMD_FLAG_SRC;
}
return 0;
}
// convert UOM to a char string - translating only for hours/minutes/seconds
const char * EMSdevice::uom_to_string(uint8_t uom) {
switch (uom) {
@@ -104,10 +89,7 @@ const char * EMSdevice::uom_to_string(uint8_t uom) {
}
}
std::string EMSdevice::brand_to_char() {
if (!custom_brand().empty()) {
return custom_brand();
}
const char * EMSdevice::brand_to_char() {
switch (brand_) {
case EMSdevice::Brand::BOSCH:
return F_(bosch);
@@ -331,15 +313,15 @@ uint8_t EMSdevice::decode_brand(uint8_t value) {
std::string EMSdevice::to_string() {
// for devices that haven't been lookup yet, don't show all details
if (product_id_ == 0) {
return name() + " (DeviceID:" + Helpers::hextoa(device_id_) + ")";
return std::string(name()) + " (DeviceID:" + Helpers::hextoa(device_id_) + ")";
}
if (brand_ == Brand::NO_BRAND && custom_brand().empty()) {
return name() + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) + ", Version:" + version_ + ")";
if (brand_ == Brand::NO_BRAND) {
return std::string(name()) + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) + ", Version:" + version_ + ")";
}
return brand_to_char() + " " + name() + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) + ", Version:" + version_
+ ")";
return std::string(brand_to_char()) + " " + name() + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_)
+ ", Version:" + version_ + ")";
}
// returns string of EMS device version and productID
@@ -350,7 +332,7 @@ std::string EMSdevice::to_string_version() {
// returns out brand + device name
// translated
std::string EMSdevice::to_string_short() {
if (brand_ == Brand::NO_BRAND && custom_brand().empty()) {
if (brand_ == Brand::NO_BRAND) {
return std::string(device_type_2_device_name_translated()) + ": " + name();
}
@@ -668,21 +650,25 @@ void EMSdevice::add_device_value(int8_t tag, // to b
// add a new command if it has a function attached
if (has_cmd) {
uint8_t flags = CommandFlag::ADMIN_ONLY | tag_to_flag(tag); // executing commands require admin privileges
uint8_t flags = CommandFlag::ADMIN_ONLY; // executing commands require admin privileges
if (tag >= DeviceValueTAG::TAG_HC1 && tag <= DeviceValueTAG::TAG_HC8) {
flags |= CommandFlag::CMD_FLAG_HC;
} else if (tag >= DeviceValueTAG::TAG_DHW1 && tag <= DeviceValueTAG::TAG_DHW10) {
flags |= CommandFlag::CMD_FLAG_DHW;
} else if (tag >= DeviceValueTAG::TAG_HS1 && tag <= DeviceValueTAG::TAG_HS16) {
flags |= CommandFlag::CMD_FLAG_HS;
} else if (tag >= DeviceValueTAG::TAG_AHS1 && tag <= DeviceValueTAG::TAG_AHS1) {
flags |= CommandFlag::CMD_FLAG_AHS;
} else if (tag >= DeviceValueTAG::TAG_SRC1 && tag <= DeviceValueTAG::TAG_SRC16) {
flags |= CommandFlag::CMD_FLAG_SRC;
}
// add the command to our library
Command::add(device_type_, device_id_, short_name, f, fullname, flags);
}
}
void EMSdevice::erase_device_values() {
for (auto & dv : devicevalues_) {
if (dv.has_cmd) {
Command::erase_command(device_type_, dv.short_name, tag_to_flag(dv.tag));
}
}
devicevalues_.clear();
}
// single list of options
void EMSdevice::register_device_value(int8_t tag,
void * value_p,
@@ -912,7 +898,7 @@ void EMSdevice::publish_value(void * value_p) const {
// looks up the UOM for a given key from the device value table
std::string EMSdevice::get_value_uom(const std::string & shortname) const {
for (const auto & dv : devicevalues_) {
if ((!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE)) && !strcmp(dv.short_name, shortname.c_str())) {
if ((!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE)) && (dv.short_name == shortname)) {
// ignore TIME since "minutes" is already added to the string value
if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) {
break;
@@ -1282,7 +1268,7 @@ void EMSdevice::setCustomizationEntity(const std::string & entity_id) {
// set the min / max
dv.set_custom_minmax();
if (Mqtt::ha_enabled() && dv.tag <= DeviceValueTAG::TAG_HC8 && !strcmp(dv.short_name, FL_(selRoomTemp)[0]) && (min != dv.min || max != dv.max)) {
if (Mqtt::ha_enabled() && dv.short_name == FL_(seltemp)[0] && (min != dv.min || max != dv.max)) {
set_climate_minmax(dv.tag, dv.min, dv.max);
}
@@ -1734,8 +1720,8 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
// generate Prometheus metrics format from device values
std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
std::string result;
std::map<std::string, bool> seen_metrics;
std::string result;
std::unordered_map<std::string, bool> seen_metrics;
// Helper function to check if a device value type is supported for Prometheus metrics
auto is_supported_type = [](uint8_t type) -> bool {
@@ -2120,8 +2106,9 @@ bool EMSdevice::generate_values(JsonObject output, const int8_t tag_filter, cons
// create the Home Assistant configs for each device value / entity
// this is called when an MQTT publish is done via an EMS Device in emsesp.cpp::publish_device_values()
void EMSdevice::mqtt_ha_entity_config_create() {
bool create_device_config = !ha_config_done(); // do we need to create the main Discovery device config with this entity?
uint16_t count = 0;
bool create_device_config = !ha_config_done(); // do we need to create the main Discovery device config with this entity?
uint16_t count = 0;
const char * const ** mode_options = nullptr;
// check the state of each of the device values
// create the discovery topic if if hasn't already been created, not a command (like reset) and is active and visible
@@ -2134,13 +2121,15 @@ void EMSdevice::mqtt_ha_entity_config_create() {
bool needs_update = !has_config_created || (haclimate_value == 1 ? has_climate_no_rt : !has_climate_no_rt);
if (needs_update) {
const char * const ** mode_options = nullptr;
for (const auto & d : devicevalues_) {
// make sure mode in same circuit is DeviceValueType::ENUM
if ((d.tag == dv.tag) && (d.type == DeviceValueType::ENUM) && !strcmp(d.short_name, FL_(mode)[0]) && (d.options_size > 0)) {
// get options
mode_options = d.options;
break;
// if it's a thermostat go fetch the list of modes
if (device_type() == EMSdevice::DeviceType::THERMOSTAT) {
for (auto & dv : devicevalues_) {
// make sure it's a type DeviceValueType::ENUM
if ((dv.type == DeviceValueType::ENUM) && !strcmp(dv.short_name, FL_(mode)[0])) {
// get options
mode_options = dv.options;
break;
}
}
}
@@ -2160,33 +2149,24 @@ void EMSdevice::mqtt_ha_entity_config_create() {
if (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) && dv.has_state(DeviceValueState::DV_ACTIVE)
&& !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE)) {
// create_device_config is only done once for the EMS device. It can added to any entity, so we take the first
if (Mqtt::publish_ha_sensor_config_dv(dv, name().c_str(), brand_to_char().c_str(), to_string_version().c_str(), false, create_device_config)) {
if (Mqtt::publish_ha_sensor_config_dv(dv, name().c_str(), brand_to_char(), to_string_version().c_str(), false, create_device_config)) {
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
create_device_config = false; // only create the main config once
count++;
}
// SRC thermostats mapped to connect/src1/... always contains mode, selRoomTemp, currtemp
// SRC thermostats mapped to connect/src1/...
if (dv.tag >= DeviceValueTAG::TAG_SRC1 && dv.tag <= DeviceValueTAG::TAG_SRC16 && !strcmp(dv.short_name, FL_(selRoomTemp)[0])) {
// add modes and icon if we have one
const char * icon = nullptr;
const char * const ** mode_options = nullptr;
for (const auto & d : devicevalues_) {
if ((d.tag != dv.tag) || (d.type != DeviceValueType::ENUM)) {
continue;
}
if (!strcmp(d.short_name, FL_(mode)[0]) && (d.options_size > 0)) {
mode_options = d.options;
}
if (!strcmp(d.short_name, FL_(icon)[0])) {
uint8_t val = *(uint8_t *)(d.value_p);
if (val != 0 && val < d.options_size) {
icon = d.options[val][0];
}
const char * icon = nullptr;
for (auto & d : devicevalues_) {
if (d.tag == dv.tag && !strcmp(d.short_name, FL_(icon)[0]) && *(uint8_t *)(d.value_p != 0)) {
icon = d.options[*(uint8_t *)(d.value_p)][0];
break;
}
}
Mqtt::publish_ha_climate_config(dv, true, mode_options, false, icon);
count++;
// add all modes - auto, heat, off, cool
// https://github.com/emsesp/EMS-ESP32/issues/2636
Mqtt::publish_ha_climate_config(dv, true, nullptr, false, icon);
}
#ifndef EMSESP_STANDALONE

View File

@@ -26,7 +26,7 @@
#include "emsdevicevalue.h"
#include <esp32-psram.h>
#include <map>
#include <unordered_map>
namespace emsesp {
@@ -55,7 +55,6 @@ class EMSdevice {
static const char * tag_to_mqtt(int8_t tag);
static uint8_t decode_brand(uint8_t value);
static bool export_values(uint8_t device_type, JsonObject output, const int8_t id, const uint8_t output_target);
static uint8_t tag_to_flag(const uint8_t tag);
// non static functions
@@ -63,7 +62,7 @@ class EMSdevice {
const char * device_type_2_device_name_translated(); // returns translated device type name
bool has_tags(const int8_t tag) const;
bool has_cmd(const char * cmd, const int8_t id) const;
std::string brand_to_char();
const char * brand_to_char();
std::string to_string();
std::string to_string_short();
std::string to_string_version();
@@ -90,10 +89,6 @@ class EMSdevice {
return version_;
}
void version(const char * version) {
strlcpy(version_, version, sizeof(version_));
}
uint8_t brand() const {
return brand_;
}
@@ -129,14 +124,6 @@ class EMSdevice {
return custom_name_;
}
// set custom brand
void custom_brand(std::string const & custom_brand) {
custom_brand_ = custom_brand;
}
std::string custom_brand() const {
return custom_brand_;
}
// set device model
void model(std::string const & model) {
model_ = model;
@@ -295,8 +282,6 @@ class EMSdevice {
int16_t min,
uint32_t max);
void erase_device_values();
void
register_device_value(int8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom, const cmd_function_p f);
@@ -539,13 +524,12 @@ class EMSdevice {
uint8_t device_id_ = 0;
uint8_t product_id_ = 0;
char version_[6];
const char * default_name_; // the fixed name the EMS model taken from the device library
std::string custom_name_ = ""; // custom name
std::string custom_brand_ = ""; // custom brand
std::string model_ = ""; // model, taken from the 0x01 telegram. see process_deviceName()
uint8_t flags_ = 0;
uint8_t brand_ = Brand::NO_BRAND;
bool active_ = true;
const char * default_name_; // the fixed name the EMS model taken from the device library
std::string custom_name_ = ""; // custom name
std::string model_ = ""; // model, taken from the 0x01 telegram. see process_deviceName()
uint8_t flags_ = 0;
uint8_t brand_ = Brand::NO_BRAND;
bool active_ = true;
bool ha_config_done_ = false;
bool has_update_ = false;

View File

@@ -84,7 +84,7 @@ uuid::log::Logger EMSESP::logger() {
RxService EMSESP::rxservice_; // incoming Telegram Rx handler
TxService EMSESP::txservice_; // outgoing Telegram Tx handler
Mqtt EMSESP::mqtt_; // mqtt handler
Modbus * EMSESP::modbus_ = nullptr; // modbus handler
Modbus * EMSESP::modbus_; // modbus handler
System EMSESP::system_; // core system services
TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors
AnalogSensor EMSESP::analogsensor_; // Analog sensors
@@ -1307,17 +1307,9 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
// first check to see if we already have it, if so update the record
for (auto it = emsdevices.begin(); it != emsdevices.end(); ++it) {
if ((*it) && (*it)->is_device_id(device_id)) {
if (product_id == 0) { // no product-id, ignore
return false;
}
if ((*it)->product_id() == product_id) { // update version if we have valid product_id
if (!strcmp((*it)->version(), "00.00")) {
(*it)->version(version);
}
if (product_id == 0 || (*it)->product_id() != 0) { // update only with valid product_id
return true;
}
// product-id has changed for this device-id, delete and re-add
(*it)->erase_device_values();
emsdevices.erase(it); // erase the old device without product_id and re detect
break;
}
@@ -1458,7 +1450,6 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
if ((e.device_id == device_id) && (e.product_id == product_id)) {
LOG_DEBUG("Have customizations for %s with deviceID 0x%02X productID %d", e.custom_name.c_str(), device_id, product_id);
emsdevices.back()->custom_name(e.custom_name);
emsdevices.back()->custom_brand(e.custom_brand);
break;
}
}
@@ -1600,12 +1591,10 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
wait_km_ = true;
connect_time = uuid::get_uptime_sec();
}
// this could also be by coincidence, so we should add a counter to the EMSbus class to check if the poll_id is the same as the EMS_BUS_ID for a certain number of times
if (poll_id == EMSbus::ems_bus_id()) {
EMSbus::poll_matched(uuid::get_uptime());
// TODO this could also be by coincidence, so we should add a counter to the EMSbus class to check if the poll_id is the same as the EMS_BUS_ID for a certain number of times
EMSbus::last_bus_activity(uuid::get_uptime()); // set the flag indication the EMS bus is active
}
if (wait_km_) {
if (poll_id != 0x48 && (uuid::get_uptime_sec() - connect_time) < EMS_WAIT_KM_TIMEOUT) {
return;
@@ -1711,11 +1700,6 @@ void EMSESP::start() {
bool factory_settings = false;
#endif
// start NVS storage
if (!nvs_.begin("ems-esp", false, "nvs1")) { // try bigger nvs partition on 16M flash first
nvs_.begin("ems-esp", false, "nvs"); // fallback to small nvs
}
// set valid GPIOs list based on ESP32 chip/platform type
system_.set_valid_system_gpios();
@@ -1764,7 +1748,7 @@ void EMSESP::start() {
LOG_INFO("Last system reset reason Core0: %s, Core1: %s", system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str());
#endif
// see if we're restoring a settings file
// see if we're restoring a settings file
#ifndef EMSESP_STANDALONE
if (system_.check_restore()) {
LOG_WARNING("EMS-ESP will restart to apply new settings. Please wait.");
@@ -1772,7 +1756,11 @@ void EMSESP::start() {
};
#endif
LOG_DEBUG("eFuse device information: %s", system_.getBBQKeesGatewayDetails().isEmpty() ? "not set" : system_.getBBQKeesGatewayDetails().c_str());
if (!nvs_.begin("ems-esp", false, "nvs1")) { // try bigger nvs partition on 16M flash first
nvs_.begin("ems-esp", false, "nvs"); // fallback to small nvs
}
LOG_DEBUG("NVS device information: %s", system_.getBBQKeesGatewayDetails().isEmpty() ? "not set" : system_.getBBQKeesGatewayDetails().c_str());
webSettingsService.begin(); // load EMS-ESP Application settings
@@ -1805,6 +1793,12 @@ void EMSESP::start() {
#endif
}
// start services
if (system_.modbus_enabled()) {
modbus_ = new Modbus;
modbus_->start(1, system_.modbus_port(), system_.modbus_max_clients(), system_.modbus_timeout() * 1000);
}
mqtt_.start(); // mqtt init
system_.start(); // starts commands, led, adc, button, network (sets hostname), syslog & uart
shower_.start(); // initialize shower timer and shower alert
@@ -1851,7 +1845,7 @@ void EMSESP::loop() {
return; // LED flashing is active, skip the rest of the loop
}
esp32React.loop(); // core services: Network, AP, MQTT and NTP
esp32React.loop(); // web services like network, AP, MQTT
webLogService.loop(); // log in Web UI
// run the loop, unless we're in the middle of an OTA upload

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