24 Commits

Author SHA1 Message Date
Proddy
7d37267f57 Merge pull request #2934 from proddy/dev
a collection of small changes
2026-01-29 21:03:23 +01:00
proddy
cca6f87500 package update 2026-01-28 21:47:20 +01:00
proddy
758d76051f update discord URL 2026-01-28 21:47:13 +01:00
proddy
95f7e66cff update discord URL and tidy up 2026-01-28 21:46:58 +01:00
proddy
5ec068409f package update 2026-01-24 12:10:52 +01:00
proddy
8796b6d340 update dictionary 2026-01-24 12:10:45 +01:00
proddy
bfbb18655d memory optimzations 2026-01-23 19:37:21 +01:00
proddy
9088651e53 asyncwebserver update, improved caching 2026-01-23 19:35:53 +01:00
proddy
3e8f379502 package update 2026-01-23 19:35:37 +01:00
proddy
265c2c4231 3.8.2-dev.1 2026-01-23 12:52:55 +01:00
proddy
f671d79280 package update 2026-01-22 16:40:37 +01:00
proddy
97c89d1d13 add psram 2026-01-22 16:40:15 +01:00
proddy
e0a26a38fa replace unordered_map with map, less heap 2026-01-22 16:40:05 +01:00
proddy
038f06e59f show psram on startup 2026-01-22 16:39:14 +01:00
proddy
f4d2bae04f add psram 2026-01-22 16:38:52 +01:00
proddy
d443e275ea Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev 2026-01-21 14:46:10 +01:00
Proddy
30d2057e01 Merge pull request #2930 from jvhaarst/patch-1
Update Dutch translations for various terms
2026-01-20 08:49:45 +00:00
Jan van Haarst
d952b9aaae Update Dutch translations for various terms
As I don't have the board yet, I saw the error message "Als deze waarschuwing blijft staan na een paar seconden dan loop de instellingen na en in het bijzonder het apparaat type profiel na."
That sentence didn't really flow right for me, so I had a look at the rest of the text, with this result.
2026-01-20 08:27:24 +01:00
proddy
3402215e8d optimizations 2026-01-18 15:49:35 +00:00
proddy
f01031dc26 optimize 2026-01-18 15:00:43 +00:00
proddy
01d4d116b9 cspell updates 2026-01-18 12:36:34 +00:00
proddy
bc7f82eef1 package update 2026-01-18 12:36:20 +00:00
Proddy
efdb355033 Merge pull request #2923 from proddy/dev
v3.8.2
2026-01-12 12:07:02 +01:00
proddy
87c9fd010f v3.8.2 2026-01-11 19:49:01 +01:00
42 changed files with 1379 additions and 1273 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 [issues](https://github.com/emsesp/EMS-ESP32/issues)
- [ ] Searched the issue in [discussions](https://github.com/emsesp/EMS-ESP32/discussions) - [ ] 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 [docs](https://emsesp.org/Troubleshooting/)
- [ ] Searched the issue in the [chat](https://discord.gg/3J3GgnzpyT) - [ ] Searched the issue in the [chat](https://discord.gg/GP9DPSgeJq)
- [ ] Provide the System information in the area below, taken from `http://<IP>/api/system` - [ ] Provide the System information in the area below, taken from `http://<IP>/api/system`
```json ```json

View File

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

View File

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

View File

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

View File

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

View File

@@ -30,7 +30,7 @@
"@mui/material": "^7.3.7", "@mui/material": "^7.3.7",
"@preact/compat": "^18.3.1", "@preact/compat": "^18.3.1",
"@table-library/react-table-library": "4.1.15", "@table-library/react-table-library": "4.1.15",
"alova": "3.4.1", "alova": "3.5.0",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"etag": "^1.8.1", "etag": "^1.8.1",
"formidable": "^3.5.4", "formidable": "^3.5.4",
@@ -38,34 +38,34 @@
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
"mime-types": "^3.0.2", "mime-types": "^3.0.2",
"preact": "^10.28.2", "preact": "^10.28.2",
"react": "^19.2.3", "react": "^19.2.4",
"react-dom": "^19.2.3", "react-dom": "^19.2.4",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-router": "^7.12.0", "react-router": "^7.13.0",
"react-toastify": "^11.0.5", "react-toastify": "^11.0.5",
"typesafe-i18n": "^5.26.2", "typesafe-i18n": "^5.26.2",
"typescript": "^5.9.3" "typescript": "^5.9.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.28.5", "@babel/core": "^7.28.6",
"@eslint/js": "^9.39.2", "@eslint/js": "^9.39.2",
"@preact/compat": "^18.3.1", "@preact/compat": "^18.3.1",
"@preact/preset-vite": "^2.10.2", "@preact/preset-vite": "^2.10.3",
"@trivago/prettier-plugin-sort-imports": "^6.0.2", "@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/node": "^25.0.6", "@types/node": "^25.1.0",
"@types/react": "^19.2.8", "@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"axe-core": "^4.11.1", "axe-core": "^4.11.1",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"eslint": "^9.39.2", "eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"prettier": "^3.7.4", "prettier": "^3.8.1",
"rollup-plugin-visualizer": "^6.0.5", "rollup-plugin-visualizer": "^6.0.5",
"terser": "^5.44.1", "terser": "^5.46.0",
"typescript-eslint": "^8.52.0", "typescript-eslint": "^8.54.0",
"vite": "^7.3.1", "vite": "^7.3.1",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^6.0.4" "vite-tsconfig-paths": "^6.0.5"
}, },
"packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48" "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264"
} }

983
interface/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -27,7 +27,7 @@ const nl: Translation = {
REFRESH: 'Ververs', REFRESH: 'Ververs',
EXPORT: 'Export', EXPORT: 'Export',
FAVORITES: "Favorieten", FAVORITES: "Favorieten",
DEVICE_DETAILS: 'Device Gegevens', DEVICE_DETAILS: 'Apparaat Gegevens',
ID_OF: '{0} ID', ID_OF: '{0} ID',
DEVICE: 'Apparaat', DEVICE: 'Apparaat',
PRODUCT: 'Product', PRODUCT: 'Product',
@@ -65,7 +65,7 @@ const nl: Translation = {
TEMP_SENSOR: 'Temperatuur sensor', TEMP_SENSOR: 'Temperatuur sensor',
TEMP_SENSORS: 'Temperatuur Sensoren', TEMP_SENSORS: 'Temperatuur Sensoren',
WRITE_CMD_SENT: 'Schrijf commando gestuurd', WRITE_CMD_SENT: 'Schrijf commando gestuurd',
EMS_BUS_WARNING: 'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden dan loop de instellingen na en in het bijzonder het apparaat type profiel na.', EMS_BUS_WARNING: 'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden loop dan de instellingen na en in het bijzonder het apparaat type profiel.',
EMS_BUS_SCANNING: 'Scannen naar EMS apparaten...', EMS_BUS_SCANNING: 'Scannen naar EMS apparaten...',
CONNECTED: 'Verbonden', CONNECTED: 'Verbonden',
TX_ISSUES: 'Tx bus probleem. Probeer een andere Tx verzendmodus', TX_ISSUES: 'Tx bus probleem. Probeer een andere Tx verzendmodus',
@@ -75,7 +75,7 @@ const nl: Translation = {
EMS_DEVICE: 'EMS Apparaat', EMS_DEVICE: 'EMS Apparaat',
SUCCESS: 'SUCCESS', SUCCESS: 'SUCCESS',
FAIL: 'MISLUKT', FAIL: 'MISLUKT',
QUALITY: 'QUALITEIT', QUALITY: 'KWALITEIT',
SCAN: 'Scan', SCAN: 'Scan',
STATUS_NAMES: [ STATUS_NAMES: [
'EMS Telegrammen ontvangen (Rx)', 'EMS Telegrammen ontvangen (Rx)',
@@ -120,7 +120,7 @@ const nl: Translation = {
ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)', ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)',
ENABLE_SHOWER_ALERT: 'Activeer Douchemelding', ENABLE_SHOWER_ALERT: 'Activeer Douchemelding',
TRIGGER_TIME: 'Trigger tijd', TRIGGER_TIME: 'Trigger tijd',
COLD_SHOT_DURATION: 'Tijd Shot koud water', COLD_SHOT_DURATION: 'Lengte koud water puls',
FORMATTING_OPTIONS: 'Formatteringsopties', FORMATTING_OPTIONS: 'Formatteringsopties',
BOOLEAN_FORMAT_DASHBOARD: 'Boolean formaat web', BOOLEAN_FORMAT_DASHBOARD: 'Boolean formaat web',
BOOLEAN_FORMAT_API: 'Boolean formaat API/MQTT', 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_FULL: 'Te veel entiteiten geselecteerd. Sla op in delen aub',
CUSTOMIZATIONS_SAVED: 'Custom aanpassingen opgeslagen', CUSTOMIZATIONS_SAVED: 'Custom aanpassingen opgeslagen',
CUSTOMIZATIONS_HELP_1: 'Selecteer een apparaat en pas de entiteiten aan door middel van de opties', CUSTOMIZATIONS_HELP_1: 'Selecteer een apparaat en pas de entiteiten aan door middel van de opties',
CUSTOMIZATIONS_HELP_2: 'Markeer as favoriet', CUSTOMIZATIONS_HELP_2: 'Markeer als favoriet',
CUSTOMIZATIONS_HELP_3: 'Zet schrijfacties uit', CUSTOMIZATIONS_HELP_3: 'Zet schrijfacties uit',
CUSTOMIZATIONS_HELP_4: 'Uitsluiten van MQTT en API', CUSTOMIZATIONS_HELP_4: 'Uitsluiten van MQTT en API',
CUSTOMIZATIONS_HELP_5: 'verbergen voor apparaten', CUSTOMIZATIONS_HELP_5: 'verbergen voor apparaten',
@@ -191,7 +191,7 @@ const nl: Translation = {
UPLOAD_DROP_TEXT: 'Sleep en firmware .bin bestand hierheen of klik hier', UPLOAD_DROP_TEXT: 'Sleep en firmware .bin bestand hierheen of klik hier',
ERROR: 'Onverwachte fout, probeer opnieuw', ERROR: 'Onverwachte fout, probeer opnieuw',
TIME_SET: 'Tijd ingesteld', TIME_SET: 'Tijd ingesteld',
MANAGE_USERS: 'Beheer Gebruikers', MANAGE_USERS: 'Gebruikersbeheer',
IS_ADMIN: 'is Admin', IS_ADMIN: 'is Admin',
USER_WARNING: 'U dient tenminste 1 admin gebruiker te configureren', USER_WARNING: 'U dient tenminste 1 admin gebruiker te configureren',
ADD: 'Toevoegen', ADD: 'Toevoegen',
@@ -200,7 +200,7 @@ const nl: Translation = {
GENERATING_TOKEN: 'Token aan het genereren', GENERATING_TOKEN: 'Token aan het genereren',
USER: 'Gebruiker', USER: 'Gebruiker',
MODIFY: 'Aanpassen', MODIFY: 'Aanpassen',
SU_TEXT: 'Het su (super user) wachtwoord wordt gebruikt om authorisatie tokens te signeren en ook om admin privileges te activeren in de console.', SU_TEXT: 'Het su (super user) wachtwoord wordt gebruikt om authorisatie tokens te ondertekenen en ook om admin privileges te activeren in de console.',
NOT_ENABLED: 'Niet geactiveerd', NOT_ENABLED: 'Niet geactiveerd',
ERRORS_OF: '{0} Foutmeldingen', ERRORS_OF: '{0} Foutmeldingen',
DISCONNECT_REASON: 'Verbinding verbroken vanwege', DISCONNECT_REASON: 'Verbinding verbroken vanwege',

View File

@@ -34,6 +34,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <esp32-psram.h>
#include <uuid/common.h> #include <uuid/common.h>
#include <uuid/log.h> #include <uuid/log.h>
@@ -1647,13 +1648,13 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
Stream & stream_; /*!< Stream used for the input/output of this shell. @since 3.0.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::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 */ 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 */ unsigned int flags_ = 0; /*!< Current flags for this shell. Affects which commands are available. @since 0.1.0 */
#if UUID_CONSOLE_THREAD_SAFE #if UUID_CONSOLE_THREAD_SAFE
mutable std::mutex mutex_; /*!< Mutex for queued log messages. @since 1.0.0 */ mutable std::mutex mutex_; /*!< Mutex for queued log messages. @since 1.0.0 */
#endif #endif
unsigned long log_message_id_ = 0; /*!< The next identifier to use for queued log messages. @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 */ 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 */ 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 */ 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 */ size_t maximum_command_line_length_ = MAX_COMMAND_LINE_LENGTH; /*!< Maximum command line length in bytes. @since 0.6.0 */
@@ -1786,7 +1787,7 @@ class CommandLine {
* @return A reference to the parameters. * @return A reference to the parameters.
* @since 0.6.0 * @since 0.6.0
*/ */
inline std::vector<std::string> & operator*() { inline std::vector<std::string, AllocatorPSRAM<std::string>> & operator*() {
return parameters_; return parameters_;
} }
/** /**
@@ -1795,7 +1796,7 @@ class CommandLine {
* @return A reference to the parameters. * @return A reference to the parameters.
* @since 0.6.0 * @since 0.6.0
*/ */
inline const std::vector<std::string> & operator*() const { inline const std::vector<std::string, AllocatorPSRAM<std::string>> & operator*() const {
return parameters_; return parameters_;
} }
/** /**
@@ -1804,7 +1805,7 @@ class CommandLine {
* @return A pointer to the parameters. * @return A pointer to the parameters.
* @since 0.4.0 * @since 0.4.0
*/ */
inline std::vector<std::string> * operator->() { inline std::vector<std::string, AllocatorPSRAM<std::string>> * operator->() {
return &parameters_; return &parameters_;
} }
/** /**
@@ -1813,7 +1814,7 @@ class CommandLine {
* @return A pointer to the parameters. * @return A pointer to the parameters.
* @since 0.4.0 * @since 0.4.0
*/ */
inline const std::vector<std::string> * operator->() const { inline const std::vector<std::string, AllocatorPSRAM<std::string>> * operator->() const {
return &parameters_; return &parameters_;
} }
@@ -1843,7 +1844,7 @@ class CommandLine {
bool trailing_space = false; /*!< Command line has a trailing space. @since 0.4.0 */ bool trailing_space = false; /*!< Command line has a trailing space. @since 0.4.0 */
private: private:
std::vector<std::string> parameters_; /*!< Separate command line parameters. @since 0.4.0 */ std::vector<std::string, AllocatorPSRAM<std::string>> parameters_; /*!< Separate command line parameters. @since 0.4.0 */
size_t escape_parameters_ = std::numeric_limits<size_t>::max(); /*!< Number of initial arguments to escape in output. @since 0.5.0 */ 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 { void Logger::vlog(Level level, Facility facility, const char * format, va_list ap) const {
std::vector<char> text(MAX_LOG_LENGTH + 1); std::vector<char, AllocatorPSRAM<char>> text(MAX_LOG_LENGTH + 1);
if (vsnprintf(text.data(), text.size(), format, ap) <= 0) { if (vsnprintf(text.data(), text.size(), format, ap) <= 0) {
return; return;
@@ -241,7 +241,7 @@ void Logger::vlog(Level level, Facility facility, const char * format, va_list a
dispatch(level, facility, text); dispatch(level, facility, text);
} }
void Logger::dispatch(Level level, Facility facility, std::vector<char> & text) const { void Logger::dispatch(Level level, Facility facility, std::vector<char, AllocatorPSRAM<char>> & text) const {
std::shared_ptr<Message> message = std::make_shared<Message>(get_uptime_ms(), level, facility, name_, text.data()); std::shared_ptr<Message> message = std::make_shared<Message>(get_uptime_ms(), level, facility, name_, text.data());
text.resize(0); text.resize(0);

View File

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

View File

@@ -28,6 +28,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <esp32-psram.h>
#include <uuid/log.h> #include <uuid/log.h>
#ifndef UUID_LOG_THREAD_SAFE #ifndef UUID_LOG_THREAD_SAFE
@@ -321,7 +322,7 @@ class SyslogService : public uuid::log::Handler {
#endif #endif
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum number of log messages to buffer before they are output. @since 1.0.0 */ 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 */ unsigned long log_message_id_ = 0; /*!< The next identifier to use for queued log messages. @since 1.0.0 */
std::list<QueuedLogMessage> log_messages_; /*!< Queued log messages, in the order they were received. @since 1.0.0 */ std::list<QueuedLogMessage, AllocatorPSRAM<QueuedLogMessage>> log_messages_; /*!< Queued log messages, in the order they were received. @since 1.0.0 */
uint64_t mark_interval_ = 0; /*!< Mark interval in milliseconds. @since 2.0.0 */ uint64_t mark_interval_ = 0; /*!< Mark interval in milliseconds. @since 2.0.0 */
uint64_t last_message_ = 0; /*!< Last message/mark time. @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) { size_t TelnetStream::write(const uint8_t * buffer, size_t size) {
std::vector<unsigned char> data; std::vector<unsigned char, AllocatorPSRAM<unsigned char>> data;
data.reserve(size); data.reserve(size);
while (size-- > 0) { while (size-- > 0) {
@@ -310,7 +310,7 @@ size_t TelnetStream::raw_write(unsigned char data) {
return 1; return 1;
} }
size_t TelnetStream::raw_write(const std::vector<unsigned char> & data) { size_t TelnetStream::raw_write(const std::vector<unsigned char, AllocatorPSRAM<unsigned char>> & data) {
return raw_write(reinterpret_cast<const unsigned char *>(data.data()), data.size()); return raw_write(reinterpret_cast<const unsigned char *>(data.data()), data.size());
} }

View File

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

View File

@@ -19,12 +19,164 @@ class AsyncClient {
class AsyncServer { class AsyncServer {
public: public:
AsyncServer(uint16_t port) AsyncServer(uint16_t port)
: _port(port){}; : _port(port) {};
~AsyncServer(){}; ~AsyncServer() {};
protected: protected:
uint16_t _port; 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 #endif

View File

@@ -13,7 +13,7 @@
"@trivago/prettier-plugin-sort-imports": "^6.0.2", "@trivago/prettier-plugin-sort-imports": "^6.0.2",
"formidable": "^3.5.4", "formidable": "^3.5.4",
"itty-router": "^5.0.22", "itty-router": "^5.0.22",
"prettier": "^3.7.4" "prettier": "^3.8.1"
}, },
"packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48" "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264"
} }

View File

@@ -13,7 +13,7 @@ importers:
version: 3.1.3 version: 3.1.3
'@trivago/prettier-plugin-sort-imports': '@trivago/prettier-plugin-sort-imports':
specifier: ^6.0.2 specifier: ^6.0.2
version: 6.0.2(prettier@3.7.4) version: 6.0.2(prettier@3.8.1)
formidable: formidable:
specifier: ^3.5.4 specifier: ^3.5.4
version: 3.5.4 version: 3.5.4
@@ -21,17 +21,17 @@ importers:
specifier: ^5.0.22 specifier: ^5.0.22
version: 5.0.22 version: 5.0.22
prettier: prettier:
specifier: ^3.7.4 specifier: ^3.8.1
version: 3.7.4 version: 3.8.1
packages: packages:
'@babel/code-frame@7.27.1': '@babel/code-frame@7.28.6':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/generator@7.28.5': '@babel/generator@7.28.6':
resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/helper-globals@7.28.0': '@babel/helper-globals@7.28.0':
@@ -46,21 +46,21 @@ packages:
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/parser@7.28.5': '@babel/parser@7.28.6':
resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
'@babel/template@7.27.2': '@babel/template@7.28.6':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/traverse@7.28.5': '@babel/traverse@7.28.6':
resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/types@7.28.5': '@babel/types@7.28.6':
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@jridgewell/gen-mapping@0.3.13': '@jridgewell/gen-mapping@0.3.13':
@@ -145,8 +145,8 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
hasBin: true hasBin: true
lodash-es@4.17.22: lodash-es@4.17.23:
resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==} resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==}
minimatch@9.0.5: minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
@@ -167,8 +167,8 @@ packages:
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
prettier@3.7.4: prettier@3.8.1:
resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
@@ -177,16 +177,16 @@ packages:
snapshots: snapshots:
'@babel/code-frame@7.27.1': '@babel/code-frame@7.28.6':
dependencies: dependencies:
'@babel/helper-validator-identifier': 7.28.5 '@babel/helper-validator-identifier': 7.28.5
js-tokens: 4.0.0 js-tokens: 4.0.0
picocolors: 1.1.1 picocolors: 1.1.1
'@babel/generator@7.28.5': '@babel/generator@7.28.6':
dependencies: dependencies:
'@babel/parser': 7.28.5 '@babel/parser': 7.28.6
'@babel/types': 7.28.5 '@babel/types': 7.28.6
'@jridgewell/gen-mapping': 0.3.13 '@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31 '@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0 jsesc: 3.1.0
@@ -197,29 +197,29 @@ snapshots:
'@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-identifier@7.28.5': {}
'@babel/parser@7.28.5': '@babel/parser@7.28.6':
dependencies: dependencies:
'@babel/types': 7.28.5 '@babel/types': 7.28.6
'@babel/template@7.27.2': '@babel/template@7.28.6':
dependencies: dependencies:
'@babel/code-frame': 7.27.1 '@babel/code-frame': 7.28.6
'@babel/parser': 7.28.5 '@babel/parser': 7.28.6
'@babel/types': 7.28.5 '@babel/types': 7.28.6
'@babel/traverse@7.28.5': '@babel/traverse@7.28.6':
dependencies: dependencies:
'@babel/code-frame': 7.27.1 '@babel/code-frame': 7.28.6
'@babel/generator': 7.28.5 '@babel/generator': 7.28.6
'@babel/helper-globals': 7.28.0 '@babel/helper-globals': 7.28.0
'@babel/parser': 7.28.5 '@babel/parser': 7.28.6
'@babel/template': 7.27.2 '@babel/template': 7.28.6
'@babel/types': 7.28.5 '@babel/types': 7.28.6
debug: 4.4.3 debug: 4.4.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@babel/types@7.28.5': '@babel/types@7.28.6':
dependencies: dependencies:
'@babel/helper-string-parser': 7.27.1 '@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5 '@babel/helper-validator-identifier': 7.28.5
@@ -246,17 +246,17 @@ snapshots:
dependencies: dependencies:
'@noble/hashes': 1.8.0 '@noble/hashes': 1.8.0
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.7.4)': '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.8.1)':
dependencies: dependencies:
'@babel/generator': 7.28.5 '@babel/generator': 7.28.6
'@babel/parser': 7.28.5 '@babel/parser': 7.28.6
'@babel/traverse': 7.28.5 '@babel/traverse': 7.28.6
'@babel/types': 7.28.5 '@babel/types': 7.28.6
javascript-natural-sort: 0.7.1 javascript-natural-sort: 0.7.1
lodash-es: 4.17.22 lodash-es: 4.17.23
minimatch: 9.0.5 minimatch: 9.0.5
parse-imports-exports: 0.2.4 parse-imports-exports: 0.2.4
prettier: 3.7.4 prettier: 3.8.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -291,7 +291,7 @@ snapshots:
jsesc@3.1.0: {} jsesc@3.1.0: {}
lodash-es@4.17.22: {} lodash-es@4.17.23: {}
minimatch@9.0.5: minimatch@9.0.5:
dependencies: dependencies:
@@ -311,6 +311,6 @@ snapshots:
picocolors@1.1.1: {} picocolors@1.1.1: {}
prettier@3.7.4: {} prettier@3.8.1: {}
wrappy@1.0.2: {} wrappy@1.0.2: {}

View File

@@ -106,7 +106,7 @@ board_build.filesystem = littlefs
lib_deps = lib_deps =
bblanchon/ArduinoJson @ 7.4.2 bblanchon/ArduinoJson @ 7.4.2
ESP32Async/AsyncTCP @ 3.4.10 ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.9.4 ESP32Async/ESPAsyncWebServer @ 3.9.5
https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8 https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -255,7 +255,7 @@ void MqttSettings::read(MqttSettings & settings, JsonObject root) {
} }
StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings) { StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings) {
MqttSettings newSettings = {}; MqttSettings newSettings;
bool changed = false; bool changed = false;
#ifndef TASMOTA_SDK #ifndef TASMOTA_SDK

View File

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

View File

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

View File

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

View File

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

View File

@@ -1721,7 +1721,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
// generate Prometheus metrics format from device values // generate Prometheus metrics format from device values
std::string EMSdevice::get_metrics_prometheus(const int8_t tag) { std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
std::string result; std::string result;
std::unordered_map<std::string, bool> seen_metrics; std::map<std::string, bool> seen_metrics;
// Helper function to check if a device value type is supported for Prometheus metrics // Helper function to check if a device value type is supported for Prometheus metrics
auto is_supported_type = [](uint8_t type) -> bool { auto is_supported_type = [](uint8_t type) -> bool {

View File

@@ -26,7 +26,7 @@
#include "emsdevicevalue.h" #include "emsdevicevalue.h"
#include <esp32-psram.h> #include <esp32-psram.h>
#include <unordered_map> #include <map>
namespace emsesp { namespace emsesp {

View File

@@ -24,7 +24,7 @@
#include <string> #include <string>
#include <functional> #include <functional>
#include <deque> #include <deque>
#include <unordered_map> #include <map>
#include <list> #include <list>
#include <ArduinoJson.h> #include <ArduinoJson.h>

View File

@@ -19,14 +19,15 @@
#ifndef EMSESP_EMSFACTORY_H_ #ifndef EMSESP_EMSFACTORY_H_
#define EMSESP_EMSFACTORY_H_ #define EMSESP_EMSFACTORY_H_
#include <map>
#include <memory> // for unique_ptr #include <memory> // for unique_ptr
#include <map>
#include "emsdevice.h" // Forward declaration
namespace emsesp {
class EMSdevice;
}
// Macro for class registration // Macro for class registration
// Anonymous namespace is used to make the definitions here private to the current
// compilation unit (current file). It is equivalent to the old C static keyword.
#define REGISTER_FACTORY(derivedClass, device_type) \ #define REGISTER_FACTORY(derivedClass, device_type) \
namespace { \ namespace { \
auto registry_##derivedClass = ConcreteEMSFactory<derivedClass>(device_type); \ auto registry_##derivedClass = ConcreteEMSFactory<derivedClass>(device_type); \
@@ -34,30 +35,29 @@
namespace emsesp { namespace emsesp {
class EMSdevice; // forward declaration, for gcc linking
class EMSFactory { class EMSFactory {
public: public:
virtual ~EMSFactory() = default; virtual ~EMSFactory() = default;
// Register factory object of derived class
// using the device_type as the unique identifier
static auto registerFactory(const uint8_t device_type, EMSFactory * factory) -> void {
auto & reg = EMSFactory::getRegister();
reg[device_type] = factory;
}
using FactoryMap = std::map<uint8_t, EMSFactory *>; using FactoryMap = std::map<uint8_t, EMSFactory *>;
// Register factory object of derived class using the device_type as the unique identifier
static inline auto registerFactory(const uint8_t device_type, EMSFactory * factory) -> void {
getRegister()[device_type] = factory;
}
// returns all registered classes (really only for debugging) // returns all registered classes (really only for debugging)
static auto device_handlers() -> FactoryMap { static inline auto device_handlers() -> const FactoryMap & {
return EMSFactory::getRegister(); return getRegister();
} }
// Construct derived class returning an unique ptr // Construct derived class returning an unique ptr
static auto add(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * default_name, uint8_t flags, uint8_t brand) static auto add(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * default_name, uint8_t flags, uint8_t brand)
-> std::unique_ptr<EMSdevice> { -> std::unique_ptr<EMSdevice> {
return std::unique_ptr<EMSdevice>(EMSFactory::makeRaw(device_type, device_id, product_id, version, default_name, flags, brand)); if (auto * ptr = makeRaw(device_type, device_id, product_id, version, default_name, flags, brand)) {
return std::unique_ptr<EMSdevice>(ptr);
}
return nullptr;
} }
virtual auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand) const virtual auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand) const
@@ -65,7 +65,7 @@ class EMSFactory {
private: private:
// Force global variable to be initialized, thus it avoids the "initialization order fiasco" // Force global variable to be initialized, thus it avoids the "initialization order fiasco"
static auto getRegister() -> FactoryMap & { static inline auto getRegister() -> FactoryMap & {
static FactoryMap classRegister{}; static FactoryMap classRegister{};
return classRegister; return classRegister;
} }
@@ -74,11 +74,9 @@ class EMSFactory {
// find which EMS device it is and use that class // find which EMS device it is and use that class
static auto makeRaw(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand) static auto makeRaw(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand)
-> EMSdevice * { -> EMSdevice * {
auto it = EMSFactory::getRegister().find(device_type); const auto & reg = getRegister();
if (it != EMSFactory::getRegister().end()) { const auto it = reg.find(device_type);
return it->second->construct(device_type, device_id, product_id, version, name, flags, brand); return (it != reg.end()) ? it->second->construct(device_type, device_id, product_id, version, name, flags, brand) : nullptr;
}
return nullptr;
} }
}; };
@@ -86,7 +84,7 @@ template <typename DerivedClass>
class ConcreteEMSFactory : EMSFactory { class ConcreteEMSFactory : EMSFactory {
public: public:
// Register this global object on the EMSFactory register // Register this global object on the EMSFactory register
ConcreteEMSFactory(const uint8_t device_type) { explicit ConcreteEMSFactory(const uint8_t device_type) {
EMSFactory::registerFactory(device_type, this); EMSFactory::registerFactory(device_type, this);
} }

View File

@@ -344,7 +344,11 @@ bool isnum(const std::string & s) {
std::string commands(std::string & expr, bool quotes) { std::string commands(std::string & expr, bool quotes) {
auto expr_new = Helpers::toLower(expr); auto expr_new = Helpers::toLower(expr);
for (uint8_t device = 0; device < EMSdevice::DeviceType::UNKNOWN; device++) { for (uint8_t device = 0; device < EMSdevice::DeviceType::UNKNOWN; device++) {
std::string d = (std::string)EMSdevice::device_type_2_device_name(device) + "/"; // Optimized: build string with reserve to avoid temporary allocations
std::string d;
d.reserve(32); // typical device name length + "/"
d = EMSdevice::device_type_2_device_name(device);
d += "/";
auto f = expr_new.find(d); auto f = expr_new.find(d);
while (f != std::string::npos) { while (f != std::string::npos) {
// entity names are alphanumeric or _ // entity names are alphanumeric or _
@@ -367,9 +371,11 @@ std::string commands(std::string & expr, bool quotes) {
JsonDocument doc_in; JsonDocument doc_in;
JsonObject output = doc_out.to<JsonObject>(); JsonObject output = doc_out.to<JsonObject>();
JsonObject input = doc_in.to<JsonObject>(); JsonObject input = doc_in.to<JsonObject>();
std::string cmd_s = "api/" + std::string(cmd); // Optimized: use stack buffer for small strings to avoid heap allocation
char cmd_s[COMMAND_MAX_LENGTH + 5]; // "api/" prefix + cmd
snprintf(cmd_s, sizeof(cmd_s), "api/%s", cmd);
auto return_code = Command::process(cmd_s.c_str(), true, input, output); auto return_code = Command::process(cmd_s, true, input, output);
// check for no value (entity is valid but has no value set) // check for no value (entity is valid but has no value set)
if (return_code != CommandRet::OK && return_code != CommandRet::NO_VALUE) { if (return_code != CommandRet::OK && return_code != CommandRet::NO_VALUE) {
return expr = ""; return expr = "";
@@ -725,7 +731,7 @@ std::string compute(const std::string & expr) {
// if there is data, force a POST // if there is data, force a POST
if (value.length() || Helpers::toLower(method) == "post") { if (value.length() || Helpers::toLower(method) == "post") {
if (value.find_first_of('{') != std::string::npos) { if (value.find_first_of('{') != std::string::npos) {
http.addHeader("Content-Type", "application/json"); // auto-set to JSON http.addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON
} }
httpResult = http.POST(value.c_str()); httpResult = http.POST(value.c_str());
} else { } else {

View File

@@ -1762,7 +1762,7 @@ void System::get_value_json(JsonObject output, const std::string & circuit, cons
// generate Prometheus metrics format from system values // generate Prometheus metrics format from system values
std::string System::get_metrics_prometheus() { std::string System::get_metrics_prometheus() {
std::string result; std::string result;
std::unordered_map<std::string, bool> seen_metrics; std::map<std::string, bool> seen_metrics;
result.reserve(16000); result.reserve(16000);

View File

@@ -68,7 +68,7 @@ class Connect : public EMSdevice {
bool set_childlock(const char * value, const int8_t id); bool set_childlock(const char * value, const int8_t id);
bool set_icon(const char * value, const int8_t id); bool set_icon(const char * value, const int8_t id);
std::vector<std::shared_ptr<Connect::RoomCircuit>> room_circuits_; std::vector<std::shared_ptr<Connect::RoomCircuit>, AllocatorPSRAM<std::shared_ptr<Connect::RoomCircuit>>> room_circuits_;
void process_OutdoorTemp(std::shared_ptr<const Telegram> telegram); void process_OutdoorTemp(std::shared_ptr<const Telegram> telegram);
void process_RCTime(std::shared_ptr<const Telegram> telegram); void process_RCTime(std::shared_ptr<const Telegram> telegram);

View File

@@ -59,7 +59,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) { if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) {
monitor_typeids = {0xB1}; monitor_typeids = {0xB1};
set_typeids = {0xB0}; set_typeids = {0xB0};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { const size_t size = monitor_typeids.size();
for (uint8_t i = 0; i < size; i++) {
register_telegram_type(monitor_typeids[i], "RC10Monitor", false, MAKE_PF_CB(process_RC10Monitor)); register_telegram_type(monitor_typeids[i], "RC10Monitor", false, MAKE_PF_CB(process_RC10Monitor));
register_telegram_type(set_typeids[i], "RC10Set", false, MAKE_PF_CB(process_RC10Set)); register_telegram_type(set_typeids[i], "RC10Set", false, MAKE_PF_CB(process_RC10Set));
} }
@@ -70,7 +71,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
set_typeids = {0x3D, 0x47, 0x51, 0x5B}; set_typeids = {0x3D, 0x47, 0x51, 0x5B};
timer_typeids = {0x3F, 0x49, 0x53, 0x5D}; timer_typeids = {0x3F, 0x49, 0x53, 0x5D};
timer2_typeids = {0x42, 0x4C, 0x56, 0x60}; timer2_typeids = {0x42, 0x4C, 0x56, 0x60};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { const size_t size = monitor_typeids.size();
for (uint8_t i = 0; i < size; i++) {
register_telegram_type(monitor_typeids[i], "RC35Monitor", false, MAKE_PF_CB(process_RC35Monitor)); register_telegram_type(monitor_typeids[i], "RC35Monitor", false, MAKE_PF_CB(process_RC35Monitor));
register_telegram_type(set_typeids[i], "RC35Set", false, MAKE_PF_CB(process_RC35Set)); register_telegram_type(set_typeids[i], "RC35Set", false, MAKE_PF_CB(process_RC35Set));
register_telegram_type(timer_typeids[i], "RC35Timer", false, MAKE_PF_CB(process_RC35Timer)); register_telegram_type(timer_typeids[i], "RC35Timer", false, MAKE_PF_CB(process_RC35Timer));
@@ -88,7 +90,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
set_typeids = {0xA8}; set_typeids = {0xA8};
curve_typeids = {0x90}; curve_typeids = {0x90};
timer_typeids = {0x8F}; timer_typeids = {0x8F};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { const size_t size = monitor_typeids.size();
for (uint8_t i = 0; i < size; i++) {
register_telegram_type(monitor_typeids[i], "RC20Monitor", false, MAKE_PF_CB(process_RC20Monitor)); register_telegram_type(monitor_typeids[i], "RC20Monitor", false, MAKE_PF_CB(process_RC20Monitor));
register_telegram_type(set_typeids[i], "RC20Set", false, MAKE_PF_CB(process_RC20Set)); register_telegram_type(set_typeids[i], "RC20Set", false, MAKE_PF_CB(process_RC20Set));
register_telegram_type(curve_typeids[i], "RC20Temp", false, MAKE_PF_CB(process_RC20Temp)); register_telegram_type(curve_typeids[i], "RC20Temp", false, MAKE_PF_CB(process_RC20Temp));
@@ -103,7 +106,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
if (device_id == 0x17) { // master if (device_id == 0x17) { // master
monitor_typeids = {0xAE}; monitor_typeids = {0xAE};
set_typeids = {0xAD}; set_typeids = {0xAD};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { const size_t size = monitor_typeids.size();
for (uint8_t i = 0; i < size; i++) {
register_telegram_type(monitor_typeids[i], "RC20Monitor", false, MAKE_PF_CB(process_RC20Monitor_2)); register_telegram_type(monitor_typeids[i], "RC20Monitor", false, MAKE_PF_CB(process_RC20Monitor_2));
register_telegram_type(set_typeids[i], "RC20Set", false, MAKE_PF_CB(process_RC20Set_2)); register_telegram_type(set_typeids[i], "RC20Set", false, MAKE_PF_CB(process_RC20Set_2));
} }
@@ -117,7 +121,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
set_typeids = {0xA7}; set_typeids = {0xA7};
curve_typeids = {0x40}; curve_typeids = {0x40};
timer_typeids = {0x3F}; timer_typeids = {0x3F};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { const size_t size = monitor_typeids.size();
for (uint8_t i = 0; i < size; i++) {
register_telegram_type(monitor_typeids[i], "RC30Monitor", false, MAKE_PF_CB(process_RC30Monitor)); register_telegram_type(monitor_typeids[i], "RC30Monitor", false, MAKE_PF_CB(process_RC30Monitor));
register_telegram_type(set_typeids[i], "RC30Set", false, MAKE_PF_CB(process_RC30Set)); register_telegram_type(set_typeids[i], "RC30Set", false, MAKE_PF_CB(process_RC30Set));
register_telegram_type(curve_typeids[i], "RC30Temp", false, MAKE_PF_CB(process_RC30Temp)); register_telegram_type(curve_typeids[i], "RC30Temp", false, MAKE_PF_CB(process_RC30Temp));
@@ -131,15 +136,16 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
// EASY // EASY
} else if (model == EMSdevice::EMS_DEVICE_FLAG_EASY) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_EASY) {
monitor_typeids = {0x0A}; monitor_typeids = {0x0A};
set_typeids = {}; set_typeids.clear();
register_telegram_type(monitor_typeids[0], "EasyMonitor", true, MAKE_PF_CB(process_EasyMonitor)); register_telegram_type(monitor_typeids[0], "EasyMonitor", true, MAKE_PF_CB(process_EasyMonitor));
register_telegram_type(0x02A5, "EasyMonitor", false, MAKE_PF_CB(process_EasyMonitor)); register_telegram_type(0x02A5, "EasyMonitor", false, MAKE_PF_CB(process_EasyMonitor));
// CRF // CRF
} else if (model == EMSdevice::EMS_DEVICE_FLAG_CRF) { } else if (model == EMSdevice::EMS_DEVICE_FLAG_CRF) {
monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8}; monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8};
set_typeids = {}; set_typeids.clear();
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { const size_t size = monitor_typeids.size();
for (uint8_t i = 0; i < size; i++) {
register_telegram_type(monitor_typeids[i], "CRFMonitor", false, MAKE_PF_CB(process_CRFMonitor)); register_telegram_type(monitor_typeids[i], "CRFMonitor", false, MAKE_PF_CB(process_CRFMonitor));
} }
@@ -163,14 +169,16 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
summer2_typeids = {0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0476, 0x0477, 0x0478}; summer2_typeids = {0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0476, 0x0477, 0x0478};
hp_typeids = {0x0467, 0x0468, 0x0469, 0x046A}; hp_typeids = {0x0467, 0x0468, 0x0469, 0x046A};
hpmode_typeids = {0x0291, 0x0292, 0x0293, 0x0294}; hpmode_typeids = {0x0291, 0x0292, 0x0293, 0x0294};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { const size_t monitor_size = monitor_typeids.size();
for (uint8_t i = 0; i < monitor_size; i++) {
register_telegram_type(monitor_typeids[i], "RC300Monitor", false, MAKE_PF_CB(process_RC300Monitor)); register_telegram_type(monitor_typeids[i], "RC300Monitor", false, MAKE_PF_CB(process_RC300Monitor));
register_telegram_type(set_typeids[i], "RC300Set", false, MAKE_PF_CB(process_RC300Set)); register_telegram_type(set_typeids[i], "RC300Set", false, MAKE_PF_CB(process_RC300Set));
register_telegram_type(summer_typeids[i], "RC300Summer", false, MAKE_PF_CB(process_RC300Summer)); register_telegram_type(summer_typeids[i], "RC300Summer", false, MAKE_PF_CB(process_RC300Summer));
register_telegram_type(curve_typeids[i], "RC300Curves", false, MAKE_PF_CB(process_RC300Curve)); register_telegram_type(curve_typeids[i], "RC300Curves", false, MAKE_PF_CB(process_RC300Curve));
register_telegram_type(summer2_typeids[i], "RC300Summer2", false, MAKE_PF_CB(process_RC300Summer2)); register_telegram_type(summer2_typeids[i], "RC300Summer2", false, MAKE_PF_CB(process_RC300Summer2));
} }
for (uint8_t i = 0; i < set2_typeids.size(); i++) { const size_t set2_size = set2_typeids.size();
for (uint8_t i = 0; i < set2_size; i++) {
// register_telegram_type(set2_typeids[i], "RC300Set2", false, MAKE_PF_CB(process_RC300Set2)); // register_telegram_type(set2_typeids[i], "RC300Set2", false, MAKE_PF_CB(process_RC300Set2));
register_telegram_type(set2_typeids[i], "RC300Set2", false, MAKE_PF_CB(process_PID)); register_telegram_type(set2_typeids[i], "RC300Set2", false, MAKE_PF_CB(process_PID));
register_telegram_type(hp_typeids[i], "HPSet", false, MAKE_PF_CB(process_HPSet)); register_telegram_type(hp_typeids[i], "HPSet", false, MAKE_PF_CB(process_HPSet));
@@ -210,19 +218,20 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
} }
monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172}; monitor_typeids = {0x016F, 0x0170, 0x0171, 0x0172};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { const size_t junkers_size = monitor_typeids.size();
for (uint8_t i = 0; i < junkers_size; i++) {
register_telegram_type(monitor_typeids[i], "JunkersMonitor", false, MAKE_PF_CB(process_JunkersMonitor)); register_telegram_type(monitor_typeids[i], "JunkersMonitor", false, MAKE_PF_CB(process_JunkersMonitor));
} }
if (has_flags(EMSdevice::EMS_DEVICE_FLAG_JUNKERS_OLD)) { if (has_flags(EMSdevice::EMS_DEVICE_FLAG_JUNKERS_OLD)) {
// FR120, FR100 // FR120, FR100
set_typeids = {0x0179, 0x017A, 0x017B, 0x017C}; set_typeids = {0x0179, 0x017A, 0x017B, 0x017C};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < junkers_size; i++) {
register_telegram_type(set_typeids[i], "JunkersSet", false, MAKE_PF_CB(process_JunkersSet2)); register_telegram_type(set_typeids[i], "JunkersSet", false, MAKE_PF_CB(process_JunkersSet2));
} }
} else { } else {
set_typeids = {0x0165, 0x0166, 0x0167, 0x0168}; set_typeids = {0x0165, 0x0166, 0x0167, 0x0168};
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { for (uint8_t i = 0; i < junkers_size; i++) {
register_telegram_type(set_typeids[i], "JunkersSet", false, MAKE_PF_CB(process_JunkersSet)); register_telegram_type(set_typeids[i], "JunkersSet", false, MAKE_PF_CB(process_JunkersSet));
} }
} }
@@ -237,10 +246,12 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
// query all the heating circuits. This is only done once. // query all the heating circuits. This is only done once.
// The automatic fetch will from now on only update the active heating circuits // The automatic fetch will from now on only update the active heating circuits
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { const size_t monitor_size_final = monitor_typeids.size();
for (uint8_t i = 0; i < monitor_size_final; i++) {
EMSESP::send_read_request(monitor_typeids[i], device_id); EMSESP::send_read_request(monitor_typeids[i], device_id);
} }
for (uint8_t i = 0; i < set_typeids.size(); i++) { const size_t set_size = set_typeids.size();
for (uint8_t i = 0; i < set_size; i++) {
EMSESP::send_read_request(set_typeids[i], device_id); EMSESP::send_read_request(set_typeids[i], device_id);
} }
@@ -293,7 +304,8 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// not found, search monitor message types // not found, search monitor message types
if (hc_num == 0) { if (hc_num == 0) {
for (uint8_t i = 0; i < monitor_typeids.size(); i++) { const size_t monitor_size = monitor_typeids.size();
for (uint8_t i = 0; i < monitor_size; i++) {
if (monitor_typeids[i] == telegram->type_id) { if (monitor_typeids[i] == telegram->type_id) {
hc_num = i + 1; hc_num = i + 1;
toggle_ = true; toggle_ = true;
@@ -304,7 +316,8 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// not found, search status message/set types // not found, search status message/set types
if (hc_num == 0) { if (hc_num == 0) {
for (uint8_t i = 0; i < set_typeids.size(); i++) { const size_t set_size = set_typeids.size();
for (uint8_t i = 0; i < set_size; i++) {
if (set_typeids[i] == telegram->type_id) { if (set_typeids[i] == telegram->type_id) {
hc_num = i + 1; hc_num = i + 1;
break; break;
@@ -314,7 +327,8 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// not found, search set2 types // not found, search set2 types
if (hc_num == 0) { if (hc_num == 0) {
for (uint8_t i = 0; i < set2_typeids.size(); i++) { const size_t set2_size = set2_typeids.size();
for (uint8_t i = 0; i < set2_size; i++) {
if (set2_typeids[i] == telegram->type_id) { if (set2_typeids[i] == telegram->type_id) {
hc_num = i + 1; hc_num = i + 1;
break; break;
@@ -324,7 +338,8 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// not found, search summer message types // not found, search summer message types
if (hc_num == 0) { if (hc_num == 0) {
for (uint8_t i = 0; i < summer_typeids.size(); i++) { const size_t summer_size = summer_typeids.size();
for (uint8_t i = 0; i < summer_size; i++) {
if (summer_typeids[i] == telegram->type_id) { if (summer_typeids[i] == telegram->type_id) {
hc_num = i + 1; hc_num = i + 1;
break; break;
@@ -334,7 +349,8 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// not found, search summer message types // not found, search summer message types
if (hc_num == 0) { if (hc_num == 0) {
for (uint8_t i = 0; i < summer2_typeids.size(); i++) { const size_t summer2_size = summer2_typeids.size();
for (uint8_t i = 0; i < summer2_size; i++) {
if (summer2_typeids[i] == telegram->type_id) { if (summer2_typeids[i] == telegram->type_id) {
hc_num = i + 1; hc_num = i + 1;
break; break;
@@ -344,7 +360,8 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// not found, search heating_curve message types // not found, search heating_curve message types
if (hc_num == 0) { if (hc_num == 0) {
for (uint8_t i = 0; i < curve_typeids.size(); i++) { const size_t curve_size = curve_typeids.size();
for (uint8_t i = 0; i < curve_size; i++) {
if (curve_typeids[i] == telegram->type_id) { if (curve_typeids[i] == telegram->type_id) {
hc_num = i + 1; hc_num = i + 1;
break; break;
@@ -354,7 +371,8 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// not found, search timer message types // not found, search timer message types
if (hc_num == 0) { if (hc_num == 0) {
for (uint8_t i = 0; i < timer_typeids.size(); i++) { const size_t timer_size = timer_typeids.size();
for (uint8_t i = 0; i < timer_size; i++) {
if (timer_typeids[i] == telegram->type_id) { if (timer_typeids[i] == telegram->type_id) {
hc_num = i + 1; hc_num = i + 1;
break; break;
@@ -364,7 +382,8 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// not found, search timer message types // not found, search timer message types
if (hc_num == 0) { if (hc_num == 0) {
for (uint8_t i = 0; i < timer2_typeids.size(); i++) { const size_t timer2_size = timer2_typeids.size();
for (uint8_t i = 0; i < timer2_size; i++) {
if (timer2_typeids[i] == telegram->type_id) { if (timer2_typeids[i] == telegram->type_id) {
hc_num = i + 1; hc_num = i + 1;
break; break;
@@ -374,7 +393,8 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// not found, search heatpump message types // not found, search heatpump message types
if (hc_num == 0) { if (hc_num == 0) {
for (uint8_t i = 0; i < hp_typeids.size(); i++) { const size_t hp_size = hp_typeids.size();
for (uint8_t i = 0; i < hp_size; i++) {
if (hp_typeids[i] == telegram->type_id) { if (hp_typeids[i] == telegram->type_id) {
hc_num = i + 1; hc_num = i + 1;
break; break;
@@ -383,7 +403,8 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
} }
if (hc_num == 0) { if (hc_num == 0) {
for (uint8_t i = 0; i < hpmode_typeids.size(); i++) { const size_t hpmode_size = hpmode_typeids.size();
for (uint8_t i = 0; i < hpmode_size; i++) {
if (hpmode_typeids[i] == telegram->type_id) { if (hpmode_typeids[i] == telegram->type_id) {
hc_num = i + 1; hc_num = i + 1;
break; break;

View File

@@ -244,16 +244,16 @@ class Thermostat : public EMSdevice {
} }
// each thermostat has a list of heating controller type IDs for reading and writing // each thermostat has a list of heating controller type IDs for reading and writing
std::vector<uint16_t> monitor_typeids; std::vector<uint16_t, AllocatorPSRAM<uint16_t>> monitor_typeids;
std::vector<uint16_t> set_typeids; std::vector<uint16_t, AllocatorPSRAM<uint16_t>> set_typeids;
std::vector<uint16_t> set2_typeids; std::vector<uint16_t, AllocatorPSRAM<uint16_t>> set2_typeids;
std::vector<uint16_t> timer_typeids; std::vector<uint16_t, AllocatorPSRAM<uint16_t>> timer_typeids;
std::vector<uint16_t> timer2_typeids; std::vector<uint16_t, AllocatorPSRAM<uint16_t>> timer2_typeids;
std::vector<uint16_t> summer_typeids; std::vector<uint16_t, AllocatorPSRAM<uint16_t>> summer_typeids;
std::vector<uint16_t> summer2_typeids; std::vector<uint16_t, AllocatorPSRAM<uint16_t>> summer2_typeids;
std::vector<uint16_t> curve_typeids; std::vector<uint16_t, AllocatorPSRAM<uint16_t>> curve_typeids;
std::vector<uint16_t> hp_typeids; std::vector<uint16_t, AllocatorPSRAM<uint16_t>> hp_typeids;
std::vector<uint16_t> hpmode_typeids; std::vector<uint16_t, AllocatorPSRAM<uint16_t>> hpmode_typeids;
// standard for all thermostats // standard for all thermostats
char status_[20]; // online or offline char status_[20]; // online or offline
@@ -305,8 +305,8 @@ class Thermostat : public EMSdevice {
uint8_t pvRaiseHeat_; uint8_t pvRaiseHeat_;
uint8_t pvLowerCool_; uint8_t pvLowerCool_;
std::vector<std::shared_ptr<HeatingCircuit>> heating_circuits_; // each thermostat can have multiple heating circuits std::vector<std::shared_ptr<HeatingCircuit>, AllocatorPSRAM<std::shared_ptr<HeatingCircuit>>> heating_circuits_; // each thermostat can have multiple heating circuits
std::vector<std::shared_ptr<DhwCircuit>> dhw_circuits_; // each thermostat can have multiple dhw circuits std::vector<std::shared_ptr<DhwCircuit>, AllocatorPSRAM<std::shared_ptr<DhwCircuit>>> dhw_circuits_; // each thermostat can have multiple dhw circuits
// Generic Types // Generic Types
static constexpr uint16_t EMS_TYPE_RCTime = 0x06; // time static constexpr uint16_t EMS_TYPE_RCTime = 0x06; // time

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.8.1" #define EMSESP_APP_VERSION "3.8.2-dev.1"

View File

@@ -248,18 +248,24 @@ void WebSchedulerService::publish(const bool force) {
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", scheduleItem.name); snprintf(val_obj, sizeof(val_obj), "value_json['%s']", scheduleItem.name);
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj); snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
// Optimized: use stack buffer instead of string concatenation to avoid heap allocations
char val_tpl[150];
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) { if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}"; snprintf(val_tpl, sizeof(val_tpl), "{{%s if %s}}", val_obj, val_cond);
} else { } else {
config["val_tpl"] = (std::string) "{{" + val_obj + "}}"; // omit value conditional Jinja2 template code snprintf(val_tpl, sizeof(val_tpl), "{{%s}}", val_obj); // omit value conditional Jinja2 template code
} }
config["val_tpl"] = val_tpl;
char uniq_s[70]; char uniq_s[70];
snprintf(uniq_s, sizeof(uniq_s), "%s_%s", F_(scheduler), scheduleItem.name); snprintf(uniq_s, sizeof(uniq_s), "%s_%s", F_(scheduler), scheduleItem.name);
config["uniq_id"] = uniq_s; config["uniq_id"] = uniq_s;
config["name"] = (const char *)scheduleItem.name; config["name"] = (const char *)scheduleItem.name;
config["def_ent_id"] = std::string("switch.") + uniq_s; // Optimized: use stack buffer instead of string concatenation
char def_ent_id[80];
snprintf(def_ent_id, sizeof(def_ent_id), "switch.%s", uniq_s);
config["def_ent_id"] = def_ent_id;
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
@@ -331,7 +337,7 @@ bool WebSchedulerService::command(const char * name, const std::string & command
int httpResult = 0; int httpResult = 0;
if (value.length() || method == "post") { // we have all lowercase if (value.length() || method == "post") { // we have all lowercase
if (value.find_first_of('{') != std::string::npos) { if (value.find_first_of('{') != std::string::npos) {
http.addHeader("Content-Type", "application/json"); // auto-set to JSON http.addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON
} }
httpResult = http.POST(value.c_str()); httpResult = http.POST(value.c_str());
} else { } else {

View File

@@ -496,7 +496,15 @@ void WebSettings::set_board_profile(WebSettings & settings) {
System::load_board_profile(data, settings.board_profile.c_str()); System::load_board_profile(data, settings.board_profile.c_str());
} }
EMSESP::logger().info("Loaded board profile %s", settings.board_profile.c_str()); // log board profile and PSRAM info
#ifndef EMSESP_STANDALONE
uint32_t psram_size = ESP.getPsramSize() / 1024; // in KB
if (psram_size > 0) {
EMSESP::logger().info("Loaded board profile %s, PSRAM: %lu KB", settings.board_profile.c_str(), psram_size);
} else {
EMSESP::logger().info("Loaded board profile %s, PSRAM: not available", settings.board_profile.c_str());
}
#endif
// apply the new board profile settings // apply the new board profile settings
// 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type // 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type