Merge pull request #3240 from proddy/dev

more memory debug, optimized http, better Eth error handling
This commit is contained in:
Proddy
2026-09-18 08:35:29 +02:00
committed by GitHub
70 changed files with 1001 additions and 916 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ BreakBeforeBinaryOperators: NonAssignment
BreakConstructorInitializersBeforeComma: true # Always break constructor initializers before commas and align the commas with the colon.
ExperimentalAutoDetectBinPacking: false
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 4
MaxEmptyLinesToKeep: 1
PenaltyBreakBeforeFirstCallParameter: 200
PenaltyExcessCharacter: 10
PointerAlignment: Middle
+4 -2
View File
@@ -1,8 +1,10 @@
{
"name": "EMS-ESP Devcontainer",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
"image": "mcr.microsoft.com/devcontainers/typescript-node:5-26-bookworm",
"features": {
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers/features/node:1": {
"version": "26"
},
"ghcr.io/devcontainers-extra/features/pnpm:2": {},
"ghcr.io/devcontainers/features/python:1": {},
"ghcr.io/shyim/devcontainers-features/bun:0": {}
+4 -4
View File
@@ -22,16 +22,16 @@ jobs:
with:
python-version: '3.13'
- name: Install Node.js 24
- name: Install Node.js 26
uses: actions/setup-node@v6
with:
node-version: 24
node-version: 26
- name: Checkout repository
uses: actions/checkout@v6
- name: Enable Corepack
run: corepack enable pnpm
- name: Install pnpm
run: npm install -g pnpm
- name: Get the EMS-ESP version
id: build_info
+4 -4
View File
@@ -20,16 +20,16 @@ jobs:
with:
python-version: '3.13'
- name: Install Node.js 24
- name: Install Node.js 26
uses: actions/setup-node@v6
with:
node-version: 24
node-version: 26
- name: Checkout repository
uses: actions/checkout@v6
- name: Enable Corepack
run: corepack enable pnpm
- name: Install pnpm
run: npm install -g pnpm
- name: Get the EMS-ESP version
+4 -4
View File
@@ -22,16 +22,16 @@ jobs:
with:
python-version: '3.13'
- name: Install Node.js 24
- name: Install Node.js 26
uses: actions/setup-node@v6
with:
node-version: 24
node-version: 26
- name: Checkout repository
uses: actions/checkout@v6
- name: Enable Corepack
run: corepack enable pnpm
- name: Install pnpm
run: npm install -g pnpm
- name: Get the EMS-ESP version
id: build_info
+7
View File
@@ -16,6 +16,8 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- cleaner MD5 and firmware uploading in WebUI [#3222](https://github.com/emsesp/EMS-ESP32/issues/3222)
- pc0Flow in l/min [#3224](https://github.com/emsesp/EMS-ESP32/issues/3224)
- remove HA config for excluded entities [#3229](https://github.com/emsesp/EMS-ESP32/issues/3229)
- `show system` reports the Ethernet status on boards with a PHY, so a failed initialisation or a link that never came up is visible instead of Ethernet being omitted completely. Also shows the loop task's lowest free stack, to help diagnose reboots
- new Application Setting "Automatically check for firmware updates", off by default. When enabled EMS-ESP asks emsesp.org for the latest version once a day on its own, otherwise it only asks when the WebUI wants to show it, so an unattended system never contacts emsesp.org
## Fixed
@@ -28,6 +30,9 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- HA Discovery warning on Uptime after EMS-ESP boot due to NTP not ready
- hc/control setting for UI800 thermostats [#3181](https://github.com/emsesp/EMS-ESP32/discussions/3181)
- solar module switches [#3223](https://github.com/emsesp/EMS-ESP32/issues/3223)
- possible crash when the WebUI asked for version information while the versions.json refresh was rewriting the cache from the main loop task
- HTTP client could write past a fixed 64 byte buffer when a URL had a hostname of 64 characters or more, could busy-spin for the whole read budget if the socket reported bytes it wouldn't return, and buffered responses without any size limit
- the "Ethernet clock mode (GPIO16/17) conflicts with PSRAM" error was never logged because Ethernet had already been skipped by the time the check ran
## Changed
@@ -42,4 +47,6 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- set `None` for undefined values in HA `val_tpl`
- changes in WebUI - remove multiple calls systemInfo endpoint, Vewrsion pages checks for internet connection [#3195](https://github.com/emsesp/EMS-ESP32/pull/3195)
- multiple optimization in network, power down WiFi when Ethernet and WiFi nosleep is instant when selected [#3213](https://github.com/emsesp/EMS-ESP32/pull/3213)
- TLS buffers (~17KB per HTTPS request) are taken from PSRAM on boards that have it, instead of fragmenting the internal heap
- failed versions.json fetches back off from 5 up to 160 minutes instead of retrying every 5 minutes, as each attempt blocks the main loop for as long as the DNS, connect and read timeouts allow
+29 -188
View File
@@ -1,7 +1,6 @@
{
"type": "systembackup",
"version": "3.9.0",
"date": "2026-06-14T10:59:16",
"systembackup": [
{
"type": "settings",
@@ -20,7 +19,7 @@
"eth_10mbit": false
},
"AP": {
"provision_mode": 2,
"provision_mode": 1,
"ssid": "ems-esp",
"password": "ems-esp-neo",
"channel": 1,
@@ -33,13 +32,13 @@
"MQTT": {
"enableTLS": false,
"rootCA": "",
"enabled": true,
"host": "192.168.1.2",
"enabled": false,
"host": "",
"port": 1883,
"base": "ems-esp",
"username": "username",
"password": "password",
"client_id": "esp32",
"username": "",
"password": "",
"client_id": "ems-esp",
"keep_alive": 60,
"clean_session": false,
"entity_format": 1,
@@ -53,20 +52,23 @@
"publish_time_heartbeat": 60,
"mqtt_qos": 0,
"mqtt_retain": false,
"ha_enabled": true,
"ha_enabled": false,
"nested_format": 1,
"discovery_prefix": "homeassistant",
"discovery_type": 0,
"ha_number_mode": 1,
"ha_number_mode": 0,
"publish_single": false,
"publish_single2cmd": false,
"send_response": false
},
"NTP": {
"enabled": true,
"enabled": false,
"server": "time.google.com",
"tz_label": "Europe/Amsterdam",
"tz_format": "CET-1CEST,M3.5.0,M10.5.0/3"
"tz_format": "CET-1CEST,M3.5.0,M10.5.0/3",
"thermostat_sync": 1,
"tz_label_t": "Europe/Amsterdam",
"tz_format_t": "CET-1CEST,M3.5.0,M10.5.0/3"
},
"Security": {
"jwt_secret": "ems-esp-neo",
@@ -87,9 +89,9 @@
"version": "3.9.0",
"board_profile": "E32V2_2",
"platform": "ESP32",
"system_name": "prod",
"system_name": "",
"locale": "en",
"tx_mode": 1,
"tx_mode": 5,
"ems_bus_id": 73,
"syslog_enabled": false,
"syslog_level": 3,
@@ -100,7 +102,7 @@
"boiler_heatingoff": false,
"remote_timeout": 24,
"remote_timeout_en": false,
"shower_timer": true,
"shower_timer": false,
"shower_alert": false,
"shower_alert_coldshot": 10,
"shower_alert_trigger": 7,
@@ -110,7 +112,7 @@
"dallas_gpio": 14,
"dallas_parasite": false,
"led_gpio": 32,
"hide_led": true,
"hide_led": false,
"led_type": 1,
"low_clock": false,
"telnet_enabled": true,
@@ -134,139 +136,43 @@
"modbus_port": 502,
"modbus_max_clients": 10,
"modbus_timeout": 300,
"developer_mode": true,
"developer_mode": false,
"disable_reset": false,
"email_enabled": true,
"email_enabled": false,
"email_security": 2,
"email_server": "smtp.gmail.com",
"email_server": "smtp.example.net",
"email_port": 587,
"email_login": "test@emsesp.org",
"email_pass": "password",
"email_sender": "test@emsesp.org",
"email_recp": "test@emsesp.org",
"email_login": "",
"email_pass": "",
"email_sender": "ems-esp@example.net",
"email_recp": "",
"email_subject": "ems-esp notification"
}
},
{
"type": "commands",
"Commands": {
"commands": [
{
"id": 1,
"cmd": "{\"url\":\"https://api.pushover.net/1/messages.json\"}",
"value": "{\"user\":\"userkey\", \"token\":\"tokenkey\", \"message\":\"custom/message2\"}",
"name": "push_over"
},
{
"id": 2,
"cmd": "system/message",
"value": "hello",
"name": "sendmessage"
},
{
"id": 3,
"cmd": "system/message",
"value": "{\"url\":\"http://emsesp.org/versions.json\"}",
"name": "sendversions"
},
{
"id": 4,
"cmd": "custom/message",
"value": "{\"url\":\"http://emsesp.org/versions.json\"}",
"name": "getversions"
},
{
"id": 5,
"cmd": "system/sendmail",
"value": "\"test email with \" + custom/message2",
"name": "sendmail"
}
]
"commands": []
}
},
{
"type": "schedule",
"Schedule": {
"schedule": [
{
"id": 1,
"flags": 128,
"active": false,
"time": "",
"cmd_name": "sendmail",
"name": "sendmailtest"
}
]
"schedule": []
}
},
{
"type": "customizations",
"Customizations": {
"as": [
{
"gpio": 39,
"name": "core_voltage",
"offset": 0,
"factor": 0.003771,
"uom": 23,
"type": 3,
"is_system": true
},
{
"gpio": 36,
"name": "supply_voltage",
"offset": 0,
"factor": 0.017,
"uom": 23,
"type": 3,
"is_system": true
},
{
"gpio": 2,
"name": "led",
"offset": 0,
"factor": 1,
"uom": 0,
"type": 6,
"is_system": true
}
],
"ts": [],
"as": [],
"masked_entities": []
}
},
{
"type": "entities",
"Entities": {
"entities": [
{
"id": 0,
"ram": 1,
"device_id": 0,
"type_id": 0,
"offset": 0,
"factor": 1,
"name": "message",
"uom": 0,
"value_type": 9,
"writeable": true,
"hide": false,
"value": "{\"stable\":{\"version\":\"3.8.3\",\"date\":\"2026-08-01\"},\"dev\":{\"version\":\"3.9.0-dev.0\",\"date\":\"2026-08-01T09:55:29Z\"}}"
},
{
"id": 1,
"ram": 1,
"device_id": 0,
"type_id": 0,
"offset": 0,
"factor": 1,
"name": "message2",
"uom": 0,
"value_type": 9,
"writeable": true,
"hide": false,
"value": "testing"
}
]
"entities": []
}
},
{
@@ -274,71 +180,6 @@
"Modules": {
"modules": []
}
},
{
"type": "nvs",
"nvs": [
{
"type": 1,
"key": "nompower",
"value": 30
},
{
"type": 33,
"key": "boot",
"value": "3.9.0"
},
{
"type": 17,
"key": "led",
"value": 0
},
{
"type": 33,
"key": "intTemp",
"value": "28_1767_7B13_2502"
},
{
"type": 4,
"key": "d_boot",
"value": 1
},
{
"type": 33,
"key": "app1",
"value": "3.9.0"
},
{
"type": 33,
"key": "app0",
"value": "3.9.0"
},
{
"type": 66,
"key": "nrgheat",
"value": 49355.29793
},
{
"type": 4,
"key": "d_app1",
"value": 1781336521
},
{
"type": 1,
"key": "fresh_firmware",
"value": 0
},
{
"type": 4,
"key": "d_app0",
"value": 1781363944
},
{
"type": 66,
"key": "nrgww",
"value": 41852.79107
}
]
}
]
}
+7 -7
View File
@@ -32,8 +32,8 @@
"alova": "^3.5.5",
"jwt-decode": "^4.0.0",
"preact": "^10.29.8",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-icons": "^5.7.0",
"react-router": "^8.3.1",
"typesafe-i18n": "^5.27.1"
@@ -42,9 +42,9 @@
"@eslint/js": "^10.0.1",
"@preact/preset-vite": "^2.10.6",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/node": "^26.5.0",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.7",
"@types/node": "^26.5.1",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"concurrently": "^10.0.5",
"eslint": "^10.10.0",
"eslint-config-prettier": "^10.1.8",
@@ -55,8 +55,8 @@
"terser": "^5.51.2",
"typescript": "^6.0.3",
"typescript-eslint": "^8.70.0",
"vite": "^8.2.2",
"vite": "^8.3.0",
"vite-plugin-imagemin": "^0.6.1"
},
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457"
"packageManager": "pnpm@12.4.1"
}
+341 -280
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -115,7 +115,6 @@ export const writeSchedule = (data: Schedule) =>
// Commands
export const readCommands = () =>
alovaInstance.Get<CommandItem[]>('/rest/commands', {
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
transform(data) {
const commands = (data as Commands).commands;
return commands.map((ci) => ({
+1 -2
View File
@@ -19,7 +19,6 @@ export const uploadFile = (file: File) => {
const formData = new FormData();
formData.append('file', file);
return alovaInstance.Post('/rest/uploadFile', formData, {
timeout: UPLOAD_TIMEOUT,
enableUpload: true
timeout: UPLOAD_TIMEOUT
});
};
+1
View File
@@ -55,6 +55,7 @@ export interface Settings {
email_subject: string;
developer_mode: boolean;
disable_reset: boolean;
auto_fw_check: boolean;
}
export enum busConnectionStatus {
@@ -512,6 +512,18 @@ const ApplicationSettings = () => {
</Grid>
</>
)}
<Typography color="secondary">Firmware</Typography>
<BlockFormControlLabel
control={
<Checkbox
checked={data.auto_fw_check}
onChange={updateFormValue}
name="auto_fw_check"
/>
}
label={LL.AUTO_FW_CHECK()}
/>
<Typography sx={{ pb: 1, pt: 2 }} variant="h6" color="primary">
{LL.SENSORS()}
</Typography>
+8 -2
View File
@@ -1,4 +1,4 @@
import { memo, useContext, useEffect, useState } from 'react';
import { memo, useContext, useEffect, useRef, useState } from 'react';
import { Link } from 'react-router';
import CancelIcon from '@mui/icons-material/Cancel';
@@ -52,6 +52,7 @@ const STABLE_RELNOTES_URL =
const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/';
const DEV_RELNOTES_URL =
'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md';
const MAX_VERSION_POLLS = 5; // approx 15 seconds of waiting
// Types for better type safety
interface PartitionData {
@@ -440,12 +441,17 @@ const Version = () => {
const stableUpgradeAvailable = versions?.stable?.upgradeable ?? false;
const devUpgradeAvailable = versions?.dev?.upgradeable ?? false;
const internetLive = Boolean(versions?.stable || versions?.dev);
const versionPolls = useRef(0);
useEffect(() => {
if (internetLive) {
if (internetLive || versionPolls.current >= MAX_VERSION_POLLS) {
return;
}
const interval = setInterval(() => {
versionPolls.current += 1;
if (versionPolls.current >= MAX_VERSION_POLLS) {
clearInterval(interval);
}
void refreshVersions();
}, 3000);
return () => clearInterval(interval);
@@ -93,7 +93,7 @@ const SingleUpload = ({ doRestart, text }: SingleUploadProps) => {
</>
) : (
<DragNdrop
text={(md5 ? LL.UPLOAD_MD5_RECEIVED() : text) + '...'}
text={md5 ? LL.UPLOAD_MD5_RECEIVED() : text}
onFileSelected={setFile}
/>
)}
+3 -2
View File
@@ -321,6 +321,7 @@ const cs: Translation = {
DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Režim vývojáře',
DISABLE_RESET: 'Zakázat obnovení továrního nastavení',
AUTO_FW_CHECK: 'Automaticky kontrolovat aktualizace firmwaru',
BYTES: 'Bajty',
BITMASK: 'Bit Mask',
DUPLICATE: 'Duplikát',
@@ -348,8 +349,8 @@ const cs: Translation = {
COMMAND_EXECUTED: 'Příkaz úspěšně spuštěn',
RUN: 'Spustit příkaz',
INTERNET_CONNECTION_CHECK: 'Kontrola online pro dostupné verze',
UPLOAD_BACKUP_TEXT: 'Klikněte, abyste vybrali nebo přetáhli a pustili soubor zálohy (.json) sem',
UPLOAD_FIRMWARE_TEXT: 'Klikněte, abyste vybrali nebo přetáhli a pustili soubor firmware .bin/.md5 sem',
UPLOAD_BACKUP_TEXT: 'Klikněte, abyste vybrali, nebo drop soubor zálohy (.json) sem',
UPLOAD_FIRMWARE_TEXT: 'Klikněte, abyste vybrali, nebo drop soubor firmware .bin/.md5 sem',
UPLOAD_MD5_RECEIVED: 'Digest přijat, nyní nahrajte firmware .bin',
UPLOAD_MD5_MATCHED: 'MD5 souhlasí, firmware se instaluje...'
};
+3 -2
View File
@@ -321,6 +321,7 @@ const de: Translation = {
DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Entwicklermodus',
DISABLE_RESET: 'Werkseinstellungen deaktivieren',
AUTO_FW_CHECK: 'Automatisch nach Firmware-Updates suchen',
BYTES: 'Bytes',
BITMASK: 'Bit Maske',
DUPLICATE: 'Kopieren',
@@ -348,8 +349,8 @@ const de: Translation = {
COMMAND_EXECUTED: 'Befehl erfolgreich ausgeführt',
RUN: 'Befehl ausführen',
INTERNET_CONNECTION_CHECK: 'Online-Prüfung auf verfügbare Versionen',
UPLOAD_BACKUP_TEXT: 'Klicken Sie, um eine Sicherungsdatei (.json) auszuwählen oder zu ziehen und abzulegen',
UPLOAD_FIRMWARE_TEXT: 'Klicken Sie, um eine Firmware-Datei (.bin/.md5) auszuwählen oder zu ziehen und abzulegen',
UPLOAD_BACKUP_TEXT: 'Klicken Sie, um eine Sicherungsdatei (.json) auszuwählen, oder drop ein',
UPLOAD_FIRMWARE_TEXT: 'Klicken Sie, um eine Firmware-Datei (.bin/.md5) auszuwählen, oder drop ein',
UPLOAD_MD5_RECEIVED: 'Prüfsumme empfangen, jetzt die Firmware-Datei (.bin) hochladen',
UPLOAD_MD5_MATCHED: 'MD5 stimmt überein, Firmware wird installiert...'
};
+3 -2
View File
@@ -321,6 +321,7 @@ const en: Translation = {
DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Developer Mode',
DISABLE_RESET: 'Disable Factory Reset',
AUTO_FW_CHECK: 'Automatically check for firmware updates',
BYTES: 'Bytes',
BITMASK: 'Bit Mask',
DUPLICATE: 'Duplicate',
@@ -348,8 +349,8 @@ const en: Translation = {
COMMAND_EXECUTED: 'Command executed successfully',
RUN: 'Run',
INTERNET_CONNECTION_CHECK: 'Checking online for available versions',
UPLOAD_BACKUP_TEXT: 'Click to select or Drag&Drop a backup file (.json) here',
UPLOAD_FIRMWARE_TEXT: 'Click to select or Drag&Drop a firmware .bin/.md5 file',
UPLOAD_BACKUP_TEXT: 'Click to select, or drop a backup file (.json) here',
UPLOAD_FIRMWARE_TEXT: 'Click to select, or drop a firmware .bin/.md5 file here',
UPLOAD_MD5_RECEIVED: 'Digest received, now upload the firmware .bin file',
UPLOAD_MD5_MATCHED: 'MD5 matches, installing firmware...'
};
+3 -2
View File
@@ -321,6 +321,7 @@ const fr: Translation = {
DASHBOARD: 'Tableau de bord',
DEVELOPER_MODE: 'Mode développeur',
DISABLE_RESET: 'Désactiver la réinitialisation de fabrique',
AUTO_FW_CHECK: 'Vérifier automatiquement les mises à jour du firmware',
BYTES: 'Octets',
BITMASK: 'Masque de bits',
DUPLICATE: 'Dupliquer',
@@ -348,8 +349,8 @@ const fr: Translation = {
COMMAND_EXECUTED: 'Commande exécutée avec succès',
RUN: 'Exécuter une commande',
INTERNET_CONNECTION_CHECK: 'Vérification en ligne pour les versions disponibles',
UPLOAD_BACKUP_TEXT: 'Cliquez pour sélectionner ou faites glisser et déposez un fichier de sauvegarde (.json) ici',
UPLOAD_FIRMWARE_TEXT: 'Cliquez pour sélectionner ou faites glisser et déposez un fichier firmware .bin/.md5 ici',
UPLOAD_BACKUP_TEXT: 'Cliquez pour sélectionner, ou drop un fichier de sauvegarde (.json) ici',
UPLOAD_FIRMWARE_TEXT: 'Cliquez pour sélectionner, ou drop un fichier firmware .bin/.md5 ici',
UPLOAD_MD5_RECEIVED: 'Empreinte reçue, téléversez maintenant le fichier firmware .bin',
UPLOAD_MD5_MATCHED: 'MD5 concordant, installation du firmware...'
};
+3 -2
View File
@@ -321,6 +321,7 @@ const it: Translation = {
DASHBOARD: 'Pannello di controllo',
DEVELOPER_MODE: 'Modalità sviluppatore',
DISABLE_RESET: 'Disabilita la ripristino di fabbrica',
AUTO_FW_CHECK: 'Controlla automaticamente gli aggiornamenti del firmware',
BYTES: 'Byte',
BITMASK: 'Bitmask',
DUPLICATE: 'Duplicato',
@@ -348,8 +349,8 @@ const it: Translation = {
COMMAND_EXECUTED: 'Comando eseguito con successo',
RUN: 'Esegui comando',
INTERNET_CONNECTION_CHECK: 'Controllo online per le versioni disponibili',
UPLOAD_BACKUP_TEXT: 'Klik per selezionare o trascinare e rilasciare un file di backup (.json) qui',
UPLOAD_FIRMWARE_TEXT: 'Klik per selezionare o trascinare e rilasciare un file firmware .bin/.md5 qui',
UPLOAD_BACKUP_TEXT: 'Klik per selezionare, o drop un file di backup (.json) qui',
UPLOAD_FIRMWARE_TEXT: 'Klik per selezionare, o drop un file firmware .bin/.md5 qui',
UPLOAD_MD5_RECEIVED: 'Digest ricevuto, ora caricare il file firmware .bin',
UPLOAD_MD5_MATCHED: 'MD5 corrispondente, installazione firmware...'
};
+3 -2
View File
@@ -321,6 +321,7 @@ const nl: Translation = {
DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Ontwikkelaarsmodus',
DISABLE_RESET: 'Fabrieksinstellingen uitschakelen',
AUTO_FW_CHECK: 'Automatisch controleren op firmware-updates',
BYTES: 'Bytes',
BITMASK: 'Bit Mask',
DUPLICATE: 'Duplicaat',
@@ -348,8 +349,8 @@ const nl: Translation = {
COMMAND_EXECUTED: 'Commando uitgevoerd succesvol',
RUN: 'Commando uitvoeren',
INTERNET_CONNECTION_CHECK: 'Controleren op online beschikbare versies',
UPLOAD_BACKUP_TEXT: 'Klik voor selecteren of sleep en drop een back-upbestand (.json) hier',
UPLOAD_FIRMWARE_TEXT: 'Klik voor selecteren of sleep en drop een firmware .bin/.md5 bestand hier',
UPLOAD_BACKUP_TEXT: 'Klik voor selecteren, of drop een back-upbestand (.json) hier',
UPLOAD_FIRMWARE_TEXT: 'Klik voor selecteren, of drop een firmware .bin/.md5 bestand hier',
UPLOAD_MD5_RECEIVED: 'Digest ontvangen, upload nu het firmware .bin-bestand',
UPLOAD_MD5_MATCHED: 'MD5 komt overeen, firmware wordt geïnstalleerd...'
};
+3 -2
View File
@@ -321,6 +321,7 @@ const no: Translation = {
DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Utvikler modus',
DISABLE_RESET: 'Deaktiver fabrikkinnstillinger',
AUTO_FW_CHECK: 'Se automatisk etter nye firmware versjoner',
BYTES: 'Bytes',
BITMASK: 'Bitmask',
DUPLICATE: 'Duplikat',
@@ -348,8 +349,8 @@ const no: Translation = {
COMMAND_EXECUTED: 'Kommando kjørt lykkes',
RUN: 'Kjør',
INTERNET_CONNECTION_CHECK: 'Kontrollerer online for tilgjengelige versjoner',
UPLOAD_BACKUP_TEXT: 'Klikk for å velge eller dra og slipp en sikkerhetskopi (.json) her',
UPLOAD_FIRMWARE_TEXT: 'Klikk for å velge eller dra og slipp en firmware .bin/.md5 her',
UPLOAD_BACKUP_TEXT: 'Klikk for å velge, eller drop en sikkerhetskopi (.json) her',
UPLOAD_FIRMWARE_TEXT: 'Klikk for å velge, eller drop en firmware .bin/.md5 her',
UPLOAD_MD5_RECEIVED: 'MD5 mottatt, last nå opp firmware .bin-filen',
UPLOAD_MD5_MATCHED: 'MD5 stemmer, firmware installeres...'
};
+3 -2
View File
@@ -321,6 +321,7 @@ const pl: BaseTranslation = {
DASHBOARD: 'Pulpit',
DEVELOPER_MODE: 'Tryb programisty',
DISABLE_RESET: 'Wyłącz reset fabryczny',
AUTO_FW_CHECK: 'Automatycznie sprawdzaj aktualizacje firmware',
BYTES: 'Bajty',
BITMASK: 'Bit Mask',
DUPLICATE: 'Duplicate',
@@ -348,8 +349,8 @@ const pl: BaseTranslation = {
COMMAND_EXECUTED: 'Komenda wykonana pomyślnie',
RUN: 'Wykonaj',
INTERNET_CONNECTION_CHECK: 'Sprawdzanie online dla dostępnych wersji',
UPLOAD_BACKUP_TEXT: 'Kliknij, aby wybrać lub przeciągnij i upuść plik kopii zapasowej (.json) tutaj',
UPLOAD_FIRMWARE_TEXT: 'Kliknij, aby wybrać lub przeciągnij i upuść plik firmware .bin/.md5',
UPLOAD_BACKUP_TEXT: 'Kliknij, aby wybrać, lub drop plik kopii zapasowej (.json) tutaj',
UPLOAD_FIRMWARE_TEXT: 'Kliknij, aby wybrać, lub drop plik firmware .bin/.md5 tutaj',
UPLOAD_MD5_RECEIVED: 'Suma MD5 odebrana, teraz wgraj plik firmware .bin',
UPLOAD_MD5_MATCHED: 'MD5 zgodne, instalowanie firmware...'
};
+3 -2
View File
@@ -321,6 +321,7 @@ const sk: Translation = {
DASHBOARD: 'Panel',
DEVELOPER_MODE: 'Režim vývojára',
DISABLE_RESET: 'Zakázať továrenské nastavenia',
AUTO_FW_CHECK: 'Automaticky kontrolovať aktualizácie firmvéru',
BYTES: 'Bytov',
BITMASK: 'Bitová maska',
DUPLICATE: 'Duplicitné',
@@ -348,8 +349,8 @@ const sk: Translation = {
COMMAND_EXECUTED: 'Príkaz vykonaný úspešne',
RUN: 'Volať príkaz',
INTERNET_CONNECTION_CHECK: 'Kontrola online pre dostupné verzie',
UPLOAD_BACKUP_TEXT: 'Kliknite, aby ste vybrali alebo presuňte a upustili súbor zálohy (.json) sem',
UPLOAD_FIRMWARE_TEXT: 'Kliknite, aby ste vybrali alebo presuňte a upustili súbor firmware .bin/.md5',
UPLOAD_BACKUP_TEXT: 'Kliknite, aby ste vybrali, alebo drop súbor zálohy (.json) sem',
UPLOAD_FIRMWARE_TEXT: 'Kliknite, aby ste vybrali, alebo drop súbor firmware .bin/.md5 sem',
UPLOAD_MD5_RECEIVED: 'Suma MD5 prijatá, teraz nahrajte firmware .bin',
UPLOAD_MD5_MATCHED: 'MD5 súhlasí, firmvér sa inštaluje...'
};
+3 -2
View File
@@ -321,6 +321,7 @@ const sv: Translation = {
DASHBOARD: 'Kontrollpanel',
DEVELOPER_MODE: 'Utvecklarläge',
DISABLE_RESET: 'Inaktivera fabriksåterställning',
AUTO_FW_CHECK: 'Sök automatiskt efter nya firmwareversioner',
BYTES: 'Bytes',
BITMASK: 'Bitmask',
DUPLICATE: 'Dublett',
@@ -348,8 +349,8 @@ const sv: Translation = {
COMMAND_EXECUTED: 'Kommando kört lyckades',
RUN: 'Kör kommando',
INTERNET_CONNECTION_CHECK: 'Kontrollerar online för tillgängliga versioner',
UPLOAD_BACKUP_TEXT: 'Klicka för att välja eller dra och släpp en säkerhetskopia (.json) här',
UPLOAD_FIRMWARE_TEXT: 'Klicka för att välja eller dra och släpp en firmware .bin/.md5 här',
UPLOAD_BACKUP_TEXT: 'Klicka för att välja, eller drop en säkerhetskopia (.json) här',
UPLOAD_FIRMWARE_TEXT: 'Klicka för att välja, eller drop en firmware .bin/.md5 här',
UPLOAD_MD5_RECEIVED: 'MD5 mottagen, ladda nu upp firmware .bin-filen',
UPLOAD_MD5_MATCHED: 'MD5 stämmer, firmware installeras...'
};
+3 -2
View File
@@ -321,6 +321,7 @@ const tr: Translation = {
DASHBOARD: 'Kontrol Paneli',
DEVELOPER_MODE: 'Geliştirici Modu',
DISABLE_RESET: 'Fabrika ayarlarını devre dışı bırak',
AUTO_FW_CHECK: 'Firmware güncellemelerini otomatik olarak denetle',
BYTES: 'Bayt',
BITMASK: 'Bit Maskesi',
DUPLICATE: 'Çift',
@@ -348,8 +349,8 @@ const tr: Translation = {
COMMAND_EXECUTED: 'Komut başarıyla çalıştırıldı',
RUN: 'Komutu çalıştır',
INTERNET_CONNECTION_CHECK: 'Online bağlantısı kontrol ediliyor',
UPLOAD_BACKUP_TEXT: 'Yeni bir firmware dosyası (.bin) veya yedek dosyası (.json) yükle',
UPLOAD_FIRMWARE_TEXT: 'Bir firmware .bin dosyası veya buraya tıklayın',
UPLOAD_BACKUP_TEXT: 'Bir yedek dosyası (.json), buraya drop yapın',
UPLOAD_FIRMWARE_TEXT: 'Bir firmware .bin dosyası, buraya drop yapın',
UPLOAD_MD5_RECEIVED: 'Özet alındı, şimdi firmware .bin dosyasını yükleyin',
UPLOAD_MD5_MATCHED: 'MD5 eşleşti, firmware yükleniyor...'
};
+1 -1
View File
@@ -138,7 +138,7 @@ const runRule = async (
export default class Schema {
private readonly rules: Rules;
private requiredMessage?: string;
private requiredMessage?: string | undefined;
constructor(descriptor: Rules) {
this.rules = descriptor;
+4 -1
View File
@@ -71,10 +71,13 @@ class Preferences {
return true;
}
bool clear() {
return true;
}
// unused....
// void end();
// bool clear();
// size_t putShort(const char * key, int16_t value);
// size_t putUShort(const char * key, uint16_t value);
// size_t putInt(const char * key, int32_t value);
+1 -1
View File
@@ -17,5 +17,5 @@
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"prettier": "^3.9.6"
},
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457"
"packageManager": "pnpm@12.4.1"
}
+94 -37
View File
@@ -7,96 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.3.4
version: 12.3.4
specifier: 12.4.1
version: 12.4.1
packages:
'@pnpm/exe.darwin-arm64@12.3.4':
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
'@pnpm/exe.android-arm64@12.4.1':
resolution: {integrity: sha512-/HwsqXMSmlOfgtV9+O0ratzjV6Vd/8n1hh4rGHpCGmDURvt52MwZxdbhyxP4K03ZfvgSDvotFPr8O+RzE5Eu8A==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.4.1':
resolution: {integrity: sha512-+l74Qb4c2YjOzNKHXJLg+1wr8xHM1ckkUhnU5KRUK9TJqiiczt6yeTZqqzHIlJ8i6pAoj5V0OJevDRwnQKLgrQ==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.4.1':
resolution: {integrity: sha512-6rkZkT3iGfaxknUdGHraqSWFvTa6N0ajAHluv9Ax0GRWs0sIcGNiFhDopv6xSZCsJZmG483aNS/b6UEDy3blfw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.3.4':
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
'@pnpm/exe.darwin-x64@12.4.1':
resolution: {integrity: sha512-Vb1CHlR88HghC1qUxjxjs82zQSXnXacPD+btG2CmG8Q/hBU7Q0/b2YWJKSQqZXicunV5khFtfTlAwJddV9RkYA==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.linux-arm64-musl@12.3.4':
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
'@pnpm/exe.freebsd-x64@12.4.1':
resolution: {integrity: sha512-iT3iHz3Nl0Sxxj7UPOtZ/aQ81AFsQhjoeWMAlPkSRO04gsgDGAXv3UYxOFaesMWsfNxaGn0A+CITbuCHFF66FA==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.4.1':
resolution: {integrity: sha512-aBooZfNXM5f+OGUgCAMFWpE/kAhsWfvmqIyMtHy6zl3aNxyInWrcY/Saln/UElzo7lZWMC0Yktroxd/2J26lNQ==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.3.4':
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
'@pnpm/exe.linux-arm64@12.4.1':
resolution: {integrity: sha512-TlOdacTTP09BgcMvwWBFRsu8VAjfwqwnslBj+XSq1JFM3ck4f3k+1O/747EuctxZxs3/o785b6Q3s7Pd92Ptsg==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.3.4':
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
'@pnpm/exe.linux-ppc64@12.4.1':
resolution: {integrity: sha512-r/ab/MlIBo75oizUP5ITiziCCrnXz4SJwfErLQ+603AshB+Yq7xTMCoMtzTSCAMu6aaymIKkAFtZvd2J75Wq0w==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.4.1':
resolution: {integrity: sha512-C/D1QWdKMiB8+wv/spl1rGITFUuqC+aI/fb6h8NB5sqxsOaGXeYc/g891oCuzggHn6SODk9p+I09hI76x/cMNw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.4.1':
resolution: {integrity: sha512-nxz5zD4yXt94uzbStDk0QTPKW+aE92hH1b5tFXK9ctB1HE9Xcq1vjTiA2LsZMFC6p4gdu5OwQeFqYNAVGa6QlA==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.4.1':
resolution: {integrity: sha512-5AwgFdGhVUg2kIweYGfxzSLEHiIG77PQhZAkXC3TwofQHsu1Wr+TrV5/rNX2PopFnHRzuE581zoB8F6Wle32yg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.3.4':
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
'@pnpm/exe.linux-x64@12.4.1':
resolution: {integrity: sha512-FJOZuuuQMhp0oLzBtcKkLXknBI92hfkmSnlKc47vfin4HrmfID5khY2lGekL9tCzk1cpR+HShEw/meFl+nHtzQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.3.4':
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
'@pnpm/exe.win32-arm64@12.4.1':
resolution: {integrity: sha512-OO7eKBL9S+xk5hRy+JUUZSNJknGuGe3GEUffsrlC2LbqKF02g+VX1anGIzSbJDvkkdnpJhSlxF5AUz2JzJu2Jw==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.3.4':
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
'@pnpm/exe.win32-x64@12.4.1':
resolution: {integrity: sha512-x7gJHZgHo6hp354xCYA2NvoFzYJkHwovt2kVsUBK5EmXULLcP51JGMq09CX+5FRFJUZFKoXjLu3mZWmfP7o6PQ==}
cpu: [x64]
os: [win32]
pnpm@12.3.4:
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
pnpm@12.4.1:
resolution: {integrity: sha512-LoHjmdc/6DkNqyXgaqeIq3pZCCSNL1o3D4K0gRR6ano2e/gEj5pv22Rg8hpm8FQt7bi5TKLIcjWWdBkgsWVtTA==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.darwin-arm64@12.3.4':
'@pnpm/exe.android-arm64@12.4.1':
optional: true
'@pnpm/exe.darwin-x64@12.3.4':
'@pnpm/exe.android-x64@12.4.1':
optional: true
'@pnpm/exe.linux-arm64-musl@12.3.4':
'@pnpm/exe.darwin-arm64@12.4.1':
optional: true
'@pnpm/exe.linux-arm64@12.3.4':
'@pnpm/exe.darwin-x64@12.4.1':
optional: true
'@pnpm/exe.linux-x64-musl@12.3.4':
'@pnpm/exe.freebsd-x64@12.4.1':
optional: true
'@pnpm/exe.linux-x64@12.3.4':
'@pnpm/exe.linux-arm64-musl@12.4.1':
optional: true
'@pnpm/exe.win32-arm64@12.3.4':
'@pnpm/exe.linux-arm64@12.4.1':
optional: true
'@pnpm/exe.win32-x64@12.3.4':
'@pnpm/exe.linux-ppc64@12.4.1':
optional: true
pnpm@12.3.4:
'@pnpm/exe.linux-riscv64@12.4.1':
optional: true
'@pnpm/exe.linux-s390x@12.4.1':
optional: true
'@pnpm/exe.linux-x64-musl@12.4.1':
optional: true
'@pnpm/exe.linux-x64@12.4.1':
optional: true
'@pnpm/exe.win32-arm64@12.4.1':
optional: true
'@pnpm/exe.win32-x64@12.4.1':
optional: true
pnpm@12.4.1:
optionalDependencies:
'@pnpm/exe.darwin-arm64': 12.3.4
'@pnpm/exe.darwin-x64': 12.3.4
'@pnpm/exe.linux-arm64': 12.3.4
'@pnpm/exe.linux-arm64-musl': 12.3.4
'@pnpm/exe.linux-x64': 12.3.4
'@pnpm/exe.linux-x64-musl': 12.3.4
'@pnpm/exe.win32-arm64': 12.3.4
'@pnpm/exe.win32-x64': 12.3.4
'@pnpm/exe.android-arm64': 12.4.1
'@pnpm/exe.android-x64': 12.4.1
'@pnpm/exe.darwin-arm64': 12.4.1
'@pnpm/exe.darwin-x64': 12.4.1
'@pnpm/exe.freebsd-x64': 12.4.1
'@pnpm/exe.linux-arm64': 12.4.1
'@pnpm/exe.linux-arm64-musl': 12.4.1
'@pnpm/exe.linux-ppc64': 12.4.1
'@pnpm/exe.linux-riscv64': 12.4.1
'@pnpm/exe.linux-s390x': 12.4.1
'@pnpm/exe.linux-x64': 12.4.1
'@pnpm/exe.linux-x64-musl': 12.4.1
'@pnpm/exe.win32-arm64': 12.4.1
'@pnpm/exe.win32-x64': 12.4.1
---
lockfileVersion: '9.0'
+16 -11
View File
@@ -66,6 +66,7 @@ let settings = {
modbus_timeout: 10000,
developer_mode: true,
disable_reset: false,
auto_fw_check: false,
email_enabled: false,
email_security: 2,
email_server: 'smtp.gmail.com',
@@ -209,14 +210,16 @@ switch (emulate_esp) {
// ESP32 4MB
case 'ESP32':
system_status.esp_platform = 'ESP32';
system_status.cpu_type = 'ESP32';
system_status.arduino_version = 'Tasmota Arduino v2.0.17';
system_status.sdk_version = 'v4.4.7';
system_status.psram = false;
system_status.psram_size = 0;
system_status.free_psram = 0;
settings.board_profile = 'E32V2';
settings.platform = 'ESP32';
system_status.cpu_type = 'ESP32-D0WD-V3';
system_status.arduino_version = 'Tasmota Arduino v3.3.8';
system_status.sdk_version = '5.5.4.260407';
system_status.psram = true;
system_status.psram_size = 4096;
system_status.free_psram = 4055;
system_status.model = 'BBQKees Electronics E32V2.2 rev.2.2/2024073';
system_status.board = 'E32V2_2';
settings.board_profile = 'E32V2_2';
break;
// ESP32 S3
@@ -224,13 +227,15 @@ switch (emulate_esp) {
default:
system_status.esp_platform = 'ESP32S3';
system_status.cpu_type = 'ESP32-S3';
system_status.arduino_version = 'ESP32 Arduino v2.0.18';
system_status.sdk_version = 'v4.4.7';
system_status.arduino_version = 'Tasmota Arduino v3.3.8';
system_status.sdk_version = '5.5.4.260407';
system_status.psram = true;
system_status.psram_size = 8189;
system_status.free_psram = 8166;
system_status.model = 'BBQKees Electronics S32';
system_status.board = 'S32S3';
settings.board_profile = 'S32S3';
settings.platform = 'ESP32S3';
break;
}
+4 -1
View File
@@ -43,6 +43,9 @@ build_flags =
; ESPAsyncWebServer
; -D WS_MAX_QUEUED_MESSAGES=0 ; not used, default 8
; -D SSE_MAX_QUEUED_MESSAGES=1 ; for log messages, default 32
; ESP_SSLClient
-D ENABLE_PSRAM ; take the ~17KB of TLS buffers from PSRAM instead of the internal heap. The library
; needs BOARD_HAS_PSRAM too, so boards without PSRAM keep using the internal heap
-D EMSESP_MQTT_RUNNING_CORE=1 ; default 1
; -D EMSESP_MQTT_STACKSIZE=5120 ; default
-D EMSESP_UART_RUNNING_CORE=1 ; default any core
@@ -90,7 +93,7 @@ board_build.littlefs_version = 2.0
lib_deps =
bblanchon/ArduinoJson @ 7.4.3
ESP32Async/AsyncTCP @ 3.5.0
ESP32Async/ESPAsyncWebServer @ 3.12.0
ESP32Async/ESPAsyncWebServer @ 3.12.1
https://github.com/mobizt/ReadyMail.git @ 0.4.2
https://github.com/mobizt/ESP_SSLClient.git @ 3.1.3
; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
+2 -1
View File
@@ -1349,4 +1349,5 @@ wifioff
autonegotiation
Mbit
WROVER
OLIMEXPOEW
OLIMEXPOEW
arduinoespressif
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
#
# Builds a LittleFS image from the data/ folder without going through PlatformIO.
# Mirrors build_fs_image() in the pioarduino espressif32 platform builder, so the
# output is interchangeable with "platformio run --target buildfs".
#
# Needs littlefs-python, which is already installed in the PlatformIO venv:
# ~/.platformio/penv/bin/python scripts/build_littlefs.py
#
# Defaults match the s_16M_P board (2MB spiffs partition, littlefs 2.0).
import argparse
from pathlib import Path
from littlefs import LittleFS
def auto_int(value):
return int(value, 0)
def build(source_dir, target_file, fs_size, block_size, disk_version):
fs = LittleFS(
block_size=block_size,
block_count=fs_size // block_size,
read_size=1,
prog_size=1,
cache_size=block_size,
lookahead_size=32,
block_cycles=500,
name_max=64, # ESP-IDF default filename length
disk_version=disk_version,
mount=False,
)
# explicit format() + mount() on a fresh instance, see the platform builder
# for why the constructor's auto-mount is avoided
fs.format()
fs.mount()
for item in sorted(source_dir.rglob("*")):
rel_path = item.relative_to(source_dir)
fs_path = rel_path.as_posix()
if item.is_dir():
fs.makedirs(fs_path, exist_ok=True)
else:
if rel_path.parent != Path("."):
fs.makedirs(rel_path.parent.as_posix(), exist_ok=True)
with fs.open(fs_path, "wb") as dest:
dest.write(item.read_bytes())
print(f" {fs_path}")
# ESP-IDF compatible mtime attribute
mtime = int(item.stat().st_mtime)
fs.setattr(fs_path, "t", mtime.to_bytes(4, "little"))
target_file.parent.mkdir(parents=True, exist_ok=True)
target_file.write_bytes(fs.context.buffer)
def main():
parser = argparse.ArgumentParser(description="Build a LittleFS image from a folder")
parser.add_argument("source", nargs="?", default="data", type=Path)
parser.add_argument("target", nargs="?", default=Path(".pio/build/s_16M_P/littlefs.bin"), type=Path)
parser.add_argument("--size", default=0x200000, type=auto_int, help="partition size, default 0x200000")
parser.add_argument("--block", default=0x1000, type=auto_int, help="block size, default 0x1000")
parser.add_argument("--version", default="2.0", help="littlefs disk version, default 2.0")
args = parser.parse_args()
major, _, minor = args.version.partition(".")
disk_version = (int(major) << 16) | int(minor or 0)
print(f"Building {args.target} ({args.size // 1024} KB) from {args.source}/")
build(args.source, args.target, args.size, args.block, disk_version)
print(f"Wrote {args.target.stat().st_size} bytes")
if __name__ == "__main__":
main()
+26
View File
@@ -0,0 +1,26 @@
ESPTOOL=~/.platformio/penv/bin/esptool
PORT=/dev/ttyUSB0
BUILD=../.pio/build/s_16M_P
BOOT_APP0=~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin
# erase
$ESPTOOL --chip esp32 --port $PORT --after no-reset erase-flash
# create the filesystem
~/.platformio/penv/bin/python build_littlefs.py data $BUILD/littlefs.bin
# upload filesystem (spiffs partition @ 0xdf0000), leave halted so it doesn't boot yet
$ESPTOOL --chip esp32 --port $PORT --baud 460800 \
--before default-reset --after no-reset \
write-flash -z --flash-mode dio --flash-freq 80m --flash-size detect \
0xdf0000 $BUILD/littlefs.bin
# flash bootloader + partition table + app (app0 @ 0x490000), then boot
$ESPTOOL --chip esp32 --port $PORT --baud 460800 \
--before default-reset --after hard-reset \
write-flash -z --flash-mode dio --flash-freq 80m --flash-size detect \
0x1000 $BUILD/bootloader.bin \
0x8000 $BUILD/partitions.bin \
0xe000 $BOOT_APP0 \
0x490000 $BUILD/firmware.bin
+2 -2
View File
@@ -8,7 +8,7 @@ set -e
cd interface
rm -rf node_modules
corepack use pnpm@latest
pnpm self-update
pnpm update
pnpm install
pnpm format
@@ -17,7 +17,7 @@ pnpm lint
cd ../mock-api
rm -rf node_modules
corepack use pnpm@latest
pnpm self-update
pnpm update
pnpm install
pnpm format
+1
View File
@@ -87,6 +87,7 @@ void AnalogSensor::start(const bool factory_settings) {
return StateUpdateResult::CHANGED; // persist the change
});
LOG_DEBUG("Adding system sensors for core_voltage, supply_voltage and led");
}
reload(true); // fetch the list of sensors from our customization service
-2
View File
@@ -108,7 +108,6 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
return std::vector<std::string>{"system", "users", "devices", "log", "ems", "values", "mqtt", "commands"};
});
//
// System commands
//
@@ -294,7 +293,6 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
EMSESP::network_.reconnect();
});
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
string_vector{F_(set), F_(board_profile)},
+4
View File
@@ -121,6 +121,10 @@
#define EMSESP_DEFAULT_TELNET_ENABLED true
#endif
#ifndef EMSESP_DEFAULT_AUTO_FW_CHECK
#define EMSESP_DEFAULT_AUTO_FW_CHECK false
#endif
#ifndef EMSESP_DEFAULT_MODBUS_ENABLED
#define EMSESP_DEFAULT_MODBUS_ENABLED false
#endif
-1
View File
@@ -237,7 +237,6 @@ const char * const DeviceValue::DeviceValueTAG_mqtt[] = {
FL_(tag_src15)[0], // "src15"
FL_(tag_src16)[0] // "src16"
};
// count #tags once at compile time
+42 -12
View File
@@ -1696,7 +1696,6 @@ void EMSESP::scheduled_fetch_values() {
}
// EMSESP main class
EMSESP::EMSESP()
#ifndef EMSESP_STANDALONE
: telnet_([this](Stream & stream, const IPAddress & addr, uint16_t port) -> std::shared_ptr<uuid::console::Shell> {
@@ -1728,6 +1727,13 @@ void EMSESP::start() {
LOG_ERROR("LittleFS Mount Failed");
return;
}
// else {
// output filesystem folders and files for debugging
// #if defined(EMSESP_DEBUG)
// LOG_DEBUG("Listing root directory before:");
// system_.listDir("/", 3); // show the contents of the root directory
// #endif
// }
#endif
// do a quick scan of the filesystem to see if we a settings file in the /config folder
@@ -1748,7 +1754,7 @@ void EMSESP::start() {
nvs_.begin("ems-esp", false, "nvs"); // fallback to small nvs
}
#else
nvs_.begin("ems-esp", false, "nvs");
nvs_.begin("ems-esp", false, "nvs");
#endif
// set valid GPIOs list based on ESP32 chip/platform type
@@ -1760,9 +1766,15 @@ void EMSESP::start() {
// loads core system services settings (mqtt, ap, ntp etc)
esp32React.begin();
// output filesystem folders and files for debugging
// #if defined(EMSESP_DEBUG)
// LOG_DEBUG("Listing root directory after:");
// system_.listDir("/", 3); // show the contents of the root directory
// #endif
#ifndef EMSESP_STANDALONE
if (factory_settings) {
LOG_WARNING("No settings found on filesystem. Using factory settings.");
LOG_WARNING("No settings found on filesystem. Initialising with factory settings...");
// make sure OTAdata is updated with core3 (v3.9.0) format
esp_ota_set_boot_partition(esp_ota_get_running_partition());
}
@@ -1777,11 +1789,16 @@ void EMSESP::start() {
LOG_DEBUG("System is running in Debug mode");
bool check_internal_sensors = false; // true to check for internal sensors
bool force_analog_sensors = false; // true to add core_voltage, supply_voltage and led
bool force_temperature_sensors = false; // true to add gateway_temperature
// check if the firmware is fresh, i.e. a new install or a new version has been uploaded
// this is set in UploadFileService::uploadComplete()
// this is also set in UploadFileService::uploadComplete()
// and reset in System::set_partition_install_date()
if (EMSESP::nvs_.getBool(EMSESP_NVS_BOOT_NEW_FIRMWARE)) {
LOG_DEBUG("Firmware is a new install");
check_internal_sensors = true; // force check for internal sensors
} else {
// check if the firmware has been uploaded via Serial/USB
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
@@ -1809,10 +1826,11 @@ void EMSESP::start() {
};
#endif
LOG_DEBUG("eFuse device information: %s", system_.getBBQKeesGatewayDetails().isEmpty() ? "not set" : system_.getBBQKeesGatewayDetails().c_str());
webSettingsService.begin(); // load EMS-ESP Application settings
LOG_DEBUG("System name: %s", system_.system_name().c_str());
LOG_DEBUG("eFuse BBQKees Gateway Model: %s", system_.getBBQKeesGatewayDetails().isEmpty() ? "not set" : system_.getBBQKeesGatewayDetails().c_str());
// start network services. This will initialise WiFi or Ethernet depending on the settings.
network_.begin();
@@ -1823,12 +1841,24 @@ void EMSESP::start() {
webCustomEntityService.begin(); // load the custom telegram reads
webSchedulerService.begin(); // load the scheduler events
// perform any system upgrades
// check for any changes to the settings. skipped if it's a fresh factory install
if (!factory_settings) {
// perform any system upgrades
if (system_.check_upgrade()) {
LOG_WARNING("System needs a restart to apply new settings. Please wait.");
system_.system_restart();
};
// if it's a new install and we have nothing in the analog or temperature customizations
// then assume its a fresh install and add back the internal sensors
// we could also check for core_voltage, supply_voltage, led and gateway_temperature and add them if they are not present
if (check_internal_sensors) {
auto check_empty_customizations = [&](const WebCustomization & settings) {
force_analog_sensors = settings.analogCustomizations.empty();
force_temperature_sensors = settings.sensorCustomizations.empty();
};
webCustomizationService.read(check_empty_customizations);
}
}
// Load our library of known devices into stack mem. Names are stored in Flash memory
@@ -1848,11 +1878,11 @@ void EMSESP::start() {
#endif
}
mqtt_.start(); // mqtt init
system_.start(); // starts commands, led, adc, button, network (sets hostname), syslog & uart
shower_.start(); // initialize shower timer and shower alert
temperaturesensor_.start(factory_settings); // Temperature external sensors
analogsensor_.start(factory_settings); // Analog external sensors
mqtt_.start(); // mqtt init
system_.start(); // starts commands, led, adc, button, network (sets hostname), syslog & uart
shower_.start(); // initialize shower timer and shower alert
temperaturesensor_.start(factory_settings || force_temperature_sensors); // Temperature external sensors
analogsensor_.start(factory_settings || force_analog_sensors); // Analog external sensors
// start web services
LOG_INFO("Starting Web Server");
-1
View File
@@ -1042,5 +1042,4 @@ int16_t Helpers::calc_dew(int16_t temp, uint8_t humi) {
return dt;
}
} // namespace emsesp
+37 -10
View File
@@ -35,6 +35,31 @@ int HttpClient::request(std::string url, const std::string & method, const std::
return 0; // unsupported scheme
}
// split the host from the path before anything is allocated, so a bad URL costs nothing
url.replace(0, is_https ? 8 : 7, "");
std::string host = url;
auto index = url.find_first_of('/');
if (index != std::string::npos) {
host = url.substr(0, index);
url.replace(0, index, "");
} else {
url = "/";
}
if (host.empty()) {
return 0;
}
// ESP_SSLClient keeps the host in a fixed char[64] and strcpy()s into it without a length
// check, so a longer name would corrupt whatever follows it and crash somewhere unrelated
if (host.length() > MAX_HOSTNAME_LENGTH) {
EMSESP::logger().warning("%s hostname is too long (%u characters, max is %u)",
is_https ? "HTTPS" : "HTTP",
(unsigned)host.length(),
(unsigned)MAX_HOSTNAME_LENGTH);
return 0;
}
WiFiClient * basic_client = new WiFiClient;
ESP_SSLClient * ssl_client = new ESP_SSLClient;
if (is_https) {
@@ -50,16 +75,6 @@ int HttpClient::request(std::string url, const std::string & method, const std::
ssl_client->setTimeout(5); // seconds, drives BearSSL only - unused on the plain HTTP path
ssl_client->setClient(basic_client, is_https); // enableSSL = false for plain HTTP
url.replace(0, is_https ? 8 : 7, "");
std::string host = url;
auto index = url.find_first_of('/');
if (index != std::string::npos) {
host = url.substr(0, index);
url.replace(0, index, "");
} else {
url = "/";
}
const uint16_t port = is_https ? 443 : 80;
if (ssl_client->connect(host.c_str(), port)) {
bool content_set = false;
@@ -97,16 +112,25 @@ int HttpClient::request(std::string url, const std::string & method, const std::
// the stream goes idle, or the overall budget runs out
const uint32_t started = millis();
uint32_t last_data = started;
bool truncated = false;
while (millis() - started < TOTAL_TIMEOUT_MS) {
const int avail = ssl_client->available();
if (avail > 0) {
if (result.length() >= MAX_RESPONSE_BYTES) {
truncated = true;
break;
}
uint8_t buf[128];
const size_t want = (avail < (int)sizeof(buf)) ? (size_t)avail : sizeof(buf);
const int len = ssl_client->read(buf, want);
if (len > 0) {
result.append(reinterpret_cast<const char *>(buf), len);
last_data = millis();
continue;
}
// available() reported bytes the client won't hand over. Yielding here keeps a
// stale count from busy-spinning the whole read budget on this task's core
delay(1);
continue;
}
if (!ssl_client->connected()) {
@@ -121,6 +145,9 @@ int HttpClient::request(std::string url, const std::string & method, const std::
ssl_client->stop();
const auto received = result.length();
if (truncated) {
EMSESP::logger().warning("%s response from %s truncated at %u bytes", is_https ? "HTTPS" : "HTTP", host.c_str(), (unsigned)received);
}
// parse the status line "HTTP/1.x <code> <reason>". stoi() would abort rather than throw on
// a malformed response, since the firmware is built with -fno-exceptions
+6
View File
@@ -34,6 +34,12 @@ class HttpClient {
static constexpr uint32_t FIRST_BYTE_TIMEOUT_MS = 8000; // how long the server may take to start replying
static constexpr uint32_t IDLE_TIMEOUT_MS = 500; // gap in the stream that marks the end of a response
static constexpr uint32_t TOTAL_TIMEOUT_MS = 10000; // ceiling for the whole read
// ceiling on the response we'll buffer. Without it a chatty or hijacked endpoint can grow the
// result string until the heap runs out, which aborts the firmware since it's built -fno-exceptions
static constexpr size_t MAX_RESPONSE_BYTES = 32 * 1024;
static constexpr size_t MAX_HOSTNAME_LENGTH = 63; // ESP_SSLClient stores the host in a char[64]
};
} // namespace emsesp
-2
View File
@@ -238,7 +238,6 @@ ModbusMessage Modbus::handleRead(const ModbusMessage & request) {
key,
[](const EntityModbusInfo & a, const EntityModbusInfoKey & b) { return a.isLessThan(b); });
if (modbusInfo == std::end(modbus_register_mappings) || !modbusInfo->equals(key)) {
// combination of device_type/tag_type/register_offset does not exist
LOG_ERROR("combination of device_type/tag_type/register_offset does not exist");
@@ -324,7 +323,6 @@ ModbusMessage Modbus::handleWrite(const ModbusMessage & request) {
LOG_DEBUG("Tag %d, offset %d", tag, register_offset);
// binary search in modbus infos
auto key = EntityModbusInfoKey(device_type, tag_type, register_offset);
auto modbusInfo = std::lower_bound(std::begin(modbus_register_mappings),
+44 -9
View File
@@ -88,6 +88,11 @@ void Network::begin() {
pinMode(eth_power_, OUTPUT);
digitalWrite(eth_power_, LOW);
}
// report it here rather than in startEthernet(), which is never reached because initialPhase() has already skipped Ethernet
if (phy_type_ != PHY_type::PHY_TYPE_NONE && ethClockConflictsPsram()) {
LOG_ERROR("Ethernet clock mode %d (GPIO16/17) conflicts with PSRAM - disabling Ethernet", eth_clock_mode_);
}
#endif
// avoid duplicate registration, so register the lambdas only once across the lifetime of this Network instance
@@ -225,15 +230,22 @@ void Network::reconnect() {
begin();
}
// the RMII clock output on GPIO16/17 shares its pins with PSRAM, so Ethernet can't be used at all on
// a board that has both. Ethernet is skipped for the whole boot when this is the case
bool Network::ethClockConflictsPsram() const {
#if CONFIG_IDF_TARGET_ESP32 && !defined(EMSESP_STANDALONE)
return ESP.getPsramSize() && ((eth_clock_mode_t)eth_clock_mode_ == ETH_CLOCK_GPIO16_OUT || (eth_clock_mode_t)eth_clock_mode_ == ETH_CLOCK_GPIO17_OUT);
#else
return false;
#endif
}
// pick the first phase that has the hardware/config to even be attempted on this device.
// boards without an Ethernet PHY skip straight to WIFI; without a configured SSID, straight to AP
// (unless AP provision mode is Never — then stay on WIFI and keep waiting)
NetPhase Network::initialPhase() const {
#if CONFIG_IDF_TARGET_ESP32
bool eth_clock_conflicts_psram =
ESP.getPsramSize() && ((eth_clock_mode_t)eth_clock_mode_ == ETH_CLOCK_GPIO16_OUT || (eth_clock_mode_t)eth_clock_mode_ == ETH_CLOCK_GPIO17_OUT);
if (phy_type_ != PHY_type::PHY_TYPE_NONE && !eth_clock_conflicts_psram) {
if (phy_type_ != PHY_type::PHY_TYPE_NONE && !ethClockConflictsPsram()) {
return NetPhase::ETHERNET;
}
#endif
@@ -605,11 +617,6 @@ void Network::startEthernet() {
return;
}
if (ESP.getPsramSize() && ((eth_clock_mode_t)eth_clock_mode_ == ETH_CLOCK_GPIO16_OUT || (eth_clock_mode_t)eth_clock_mode_ == ETH_CLOCK_GPIO17_OUT)) {
LOG_ERROR("Ethernet clock mode %d (GPIO16/17) conflicts with PSRAM - disabling Ethernet", eth_clock_mode_);
return;
}
/*
// The Tasmota SDK is built with CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=2, so the Ethernet MAC is derived as
// a locally-administered address from base+1 instead of the universal base+3 that Arduino Core 2 used.
@@ -656,9 +663,11 @@ void Network::startEthernet() {
ETH.setFullDuplex(false);
}
// start the Eth module
if (ETH.begin(type, eth_phy_addr_, 23, 18, eth_power_, (eth_clock_mode_t)eth_clock_mode_)) {
// LOG_DEBUG("Ethernet module found - initialising");
ethernet_started_ = true;
ethernet_init_failed_ = false;
ethernet_connect_pending_ = true;
// check whether DHCP already announced us under lwIP's default hostname, before we correct it
@@ -687,12 +696,38 @@ void Network::startEthernet() {
ETH.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_);
}
} else {
ethernet_init_failed_ = true;
LOG_ERROR("Failed to start Ethernet");
}
#endif
#endif
}
// Ethernet state as a printable string, for the console and the API
// returns nullptr when this board has no Ethernet PHY configured, so callers can omit Ethernet completely
const char * Network::ethernet_status() const {
#if CONFIG_IDF_TARGET_ESP32 && !defined(EMSESP_STANDALONE)
if (phy_type_ == PHY_type::PHY_TYPE_NONE) {
return nullptr;
}
if (ethClockConflictsPsram()) {
return "disabled - clock mode GPIO16/17 conflicts with PSRAM";
}
if (ethernet_connected()) {
return "connected";
}
if (ethernet_init_failed_) {
return "failed to initialize - no PHY found, check the board profile";
}
if (!ethernet_started_) {
return "not started";
}
return ethernet_connect_pending_ ? "connecting" : "no connection";
#else
return nullptr;
#endif
}
// check if the network is connected and set network_ip_ / network_iface_ / has_ipv6_
// Iterates over every esp-netif that exists, prioritizing Ethernet > WiFi > AP
void Network::findNetworks() {
+6 -3
View File
@@ -132,9 +132,10 @@ class Network {
return reconnect_count_;
}
std::string getLocalIP() const;
std::string getMacAddress() const;
uint8_t getStationNum() const;
std::string getLocalIP() const;
std::string getMacAddress() const;
uint8_t getStationNum() const;
const char * ethernet_status() const;
void reconnect();
@@ -192,6 +193,7 @@ class Network {
const char * disconnectReason(uint8_t code);
void stopAP();
NetPhase initialPhase() const;
bool ethClockConflictsPsram() const;
#if defined(__clang__)
#pragma clang diagnostic push
@@ -216,6 +218,7 @@ class Network {
bool wifi_events_registered_ = false; // ensure WiFi.onEvent() handlers are registered only once across begin()/reconnect() cycles
bool eth_hostname_handler_registered_ = false; // for the ETHERNET_EVENT_START hostname handler
bool ethernet_started_ = false; // ETH.begin() succeeded; the driver runs for the lifetime of the boot
bool ethernet_init_failed_ = false; // ETH.begin() failed, so the driver was never installed
bool wifi_ever_connected_ = false; // set true once we've successfully obtained an IP
bool ethernet_ever_connected_ = false; // set true once we've successfully obtained an IP
bool wifi_radio_off_ = false; // STA powered down because Ethernet is carrying the traffic
+71 -5
View File
@@ -460,6 +460,7 @@ void System::get_partition_info() {
auto t = time(nullptr);
// write timestamp always with new version, if clock is not set, this will be updated with ntp
EMSESP::nvs_.putULong(c, t);
LOG_DEBUG("Updated NVS partition due to version found");
}
// Loop through all available partitions and update map with the version info pulled from NVS
@@ -719,6 +720,7 @@ void System::store_settings(WebSettings & settings) {
system_name_ = settings.system_name;
developer_mode_ = settings.developer_mode;
disable_reset_ = settings.disable_reset;
auto_fw_check_ = settings.auto_fw_check;
}
// Starts up core services
@@ -867,7 +869,7 @@ bool System::loop() {
void System::send_info_mqtt() {
static uint8_t _connection = 0;
uint8_t connection = (EMSESP::network_.ethernet_connected() ? 1 : 0) + (EMSESP::network_.wifi_connected() ? 2 : 0) + (ntp_connected_ ? 4 : 0)
+ (EMSESP::network_.has_ipv6() ? 8 : 0);
+ (EMSESP::network_.has_ipv6() ? 8 : 0);
// check if connection status has changed
if (!Mqtt::connected() || connection == _connection) {
return;
@@ -1092,7 +1094,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.println();
shell.println("System:");
shell.printfln(" Version: %s", EMSESP_APP_VERSION);
shell.printfln(" System name: %s", system_name_.c_str());
shell.printfln(" System name: %s", system_name().c_str());
#ifndef EMSESP_STANDALONE
shell.printfln(" Platform: %s (%s)", EMSESP_PLATFORM, ESP.getChipModel());
shell.printfln(" Model: %s", getBBQKeesGatewayDetails().c_str());
@@ -1125,6 +1127,12 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" Internal heap free/largest block: %u KB / %u KB",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) / 1024,
heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) / 1024);
// Lowest the loop task's stack has ever been since boot. EMSESP::loop() runs the blocking HTTP
// client and all the JSON work on this task, and a TLS handshake alone can want several KB
TaskHandle_t loop_task = xTaskGetHandle("loopTask");
if (loop_task != nullptr) {
shell.printfln(" Loop task stack min free/total: %u / %u bytes", (unsigned)uxTaskGetStackHighWaterMark(loop_task), (unsigned)getArduinoLoopTaskStackSize());
}
#endif
shell.printfln(" App used/free: %lu KB / %lu KB", appUsed(), appFree());
uint32_t FSused = LittleFS.usedBytes() / 1024;
@@ -1182,6 +1190,55 @@ void System::show_system(uuid::console::Shell & shell) {
installed.c_str(),
(strcmp(esp_ota_get_running_partition()->label, partition.first.c_str()) == 0) ? "** active **" : "");
}
// List all NVS values
#ifndef EMSESP_STANDALONE
shell.println(" NVS values:");
const char * nvs_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, "nvs1") ? "nvs1" : "nvs"; // nvs1 is on 16MBs
nvs_iterator_t it = nullptr;
esp_err_t err = nvs_entry_find(nvs_part, "ems-esp", NVS_TYPE_ANY, &it);
while (err == ESP_OK) {
nvs_entry_info_t info;
nvs_entry_info(it, &info);
shell.printf(" %s", info.key);
// also print the value depending on the type
switch (info.type) {
case NVS_TYPE_I8:
shell.printfln(" = %d", (int)EMSESP::nvs_.getChar(info.key));
break;
case NVS_TYPE_U8:
shell.printfln(" = %u", (unsigned int)EMSESP::nvs_.getUChar(info.key));
break;
case NVS_TYPE_I32:
shell.printfln(" = %d", (int)EMSESP::nvs_.getInt(info.key));
break;
case NVS_TYPE_U32:
shell.printfln(" = %u", (unsigned int)EMSESP::nvs_.getUInt(info.key));
break;
case NVS_TYPE_I64:
shell.printfln(" = %lld", (long long)EMSESP::nvs_.getLong64(info.key));
break;
case NVS_TYPE_U64:
shell.printfln(" = %llu", (unsigned long long)EMSESP::nvs_.getULong64(info.key));
break;
case NVS_TYPE_BLOB:
shell.printfln(" = %f", EMSESP::nvs_.getDouble(info.key)); // bytes used for double values
break;
case NVS_TYPE_STR:
shell.printfln(" = %s", EMSESP::nvs_.getString(info.key).c_str());
break;
default:
shell.printfln(" = unknown");
break;
}
err = nvs_entry_next(&it);
}
if (it != nullptr) {
nvs_release_iterator(it); // just in case
}
if (err == ESP_OK) {
shell.println();
}
#endif
shell.println();
shell.println("Network:");
@@ -1234,10 +1291,12 @@ void System::show_system(uuid::console::Shell & shell) {
break;
}
// show Ethernet if connected
// show the Ethernet state, and the details when it's actually up
const char * ethernet_status = EMSESP::network_.ethernet_status();
if (ethernet_status != nullptr) {
shell.printfln(" Ethernet Status: %s", ethernet_status);
}
if (EMSESP::network_.ethernet_connected()) {
shell.println();
shell.printfln(" Ethernet Status: connected");
shell.printfln(" Ethernet MAC address: %s", ETH.macAddress().c_str());
shell.printfln(" Hostname: %s", ETH.getHostname());
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str());
@@ -2675,6 +2734,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["forceHeatingOff"] = settings.boiler_heatingoff;
node["developerMode"] = settings.developer_mode;
node["disableReset"] = settings.disable_reset;
node["autoFwCheck"] = settings.auto_fw_check;
});
// Devices - show EMS devices if we have any
@@ -2918,6 +2978,12 @@ bool System::command_format(const char * value, const int8_t id) {
LOG_ERROR("Format command not available in standalone or test mode");
#endif
// in debug we also remove all the NVS keys
#ifdef EMSESP_DEBUG
LOG_DEBUG("Setting NVS fresh firmware flag");
EMSESP::nvs_.putBool(EMSESP_NVS_BOOT_NEW_FIRMWARE, true);
#endif
// restart will be handled by the main loop
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
return true;
+9
View File
@@ -207,6 +207,14 @@ class System {
disable_reset_ = disable_reset;
}
bool auto_fw_check() {
return auto_fw_check_;
}
void auto_fw_check(bool auto_fw_check) {
auto_fw_check_ = auto_fw_check;
}
// Boolean Format API/MQTT
uint8_t bool_format() {
return bool_format_;
@@ -447,6 +455,7 @@ class System {
uint32_t modbus_timeout_;
bool developer_mode_;
bool disable_reset_;
bool auto_fw_check_;
uint32_t fstotal_;
uint32_t psram_;
uint32_t appused_;
+1 -1
View File
@@ -213,7 +213,7 @@ void TemperatureSensor::loop() {
if (!EMSESP::nvs_.isKey("intTemp")) {
EMSESP::nvs_.putString("intTemp", s->id());
}
// LOG_NOTICE("Adding system sensor for gateway temperature %s", s->id().c_str());
LOG_DEBUG("Adding system sensor for gateway_temperature");
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
auto newSensor = SensorCustomization();
strlcpy(newSensor.id, s->id(), sizeof(newSensor.id));
-2
View File
@@ -1145,7 +1145,6 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
EMSESP::send_read_request(0xC2, device_id, 0, 20); // read last errorcode on start (not broadcasted)
EMSESP::send_read_request(0xC6, device_id, 0, 21); // read last errorcode on start (not broadcasted)
if (!isHeatPump() && model() != EMSdevice::EMS_DEVICE_FLAG_HIU) {
register_telegram_type(0x04, "UBAFactory", true, MAKE_PF_CB(process_UBAFactory), 21);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nomPower_, DeviceValueType::UINT8, FL_(nomPower), DeviceValueUOM::KW, MAKE_CF_CB(set_nomPower));
@@ -2452,7 +2451,6 @@ bool Boiler::set_ww_temp(const char * value, const int8_t id) {
return true;
}
// Set the lower dhw temperature 0xEA
bool Boiler::set_ww_temp_low(const char * value, const int8_t id) {
int v;
-1
View File
@@ -66,7 +66,6 @@ Extension::Extension(uint8_t device_type, uint8_t device_id, uint8_t product_id,
// register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &errorState_, DeviceValueType::BOOL, FL_(error), DeviceValueUOM::NONE);
}
// extension(0x15) -W-> Me(0x0B), EM100SetMessage(0x0935), data: 00 00 64 50 14
// need to be fetched
void Extension::process_EM100SetMessage(const std::shared_ptr<const Telegram> & telegram) {
-1
View File
@@ -40,7 +40,6 @@ class Extension : public EMSdevice {
bool set_minT(const char * value, const int8_t id);
bool set_maxT(const char * value, const int8_t id);
int16_t headerTemp_; // T0
uint8_t input_; // IO1
// uint8_t errorState_; // OE1
-1
View File
@@ -81,7 +81,6 @@ class Heatsource : public EMSdevice {
void process_amCommandMessage(const std::shared_ptr<const Telegram> & telegram);
void process_amExtraMessage(const std::shared_ptr<const Telegram> & telegram);
bool set_vr2Config(const char * value, const int8_t id); // pos 12: off(00)/Keelbypass(01)/(hc1pump(02) only standalone)
bool set_ahsActivated(const char * value, const int8_t id); // pos 00: Alternate heat source activation: No(00),Yes(01)
bool set_aPumpConfig(const char * value, const int8_t id); // pos 04: Buffer primary pump->Config pump: No(00),Yes(01)
-1
View File
@@ -115,7 +115,6 @@ class Solar : public EMSdevice {
// telegram 0x361 Differential control
uint8_t diffControl_; // is *10
// telegram 0x380
uint8_t climateZone_; // climate zone identifier
uint16_t collector1Area_; // Area of collector field 1
-3
View File
@@ -3012,7 +3012,6 @@ bool Thermostat::set_R3000Holiday(const char * value, const int8_t id) {
return true;
}
// set pause in hours
bool Thermostat::set_pause(const char * value, const int8_t id) {
auto hc = heating_circuit(id);
@@ -3668,7 +3667,6 @@ bool Thermostat::set_reducemode(const char * value, const int8_t id) {
return false;
}
// sets the thermostat reducemode for RC35 vacations
bool Thermostat::set_vacreducemode(const char * value, const int8_t id) {
auto hc = heating_circuit(id);
@@ -3983,7 +3981,6 @@ bool Thermostat::set_wwSwitchTime(const char * value, const int8_t id) {
}
char out[sizeof(dhw->wwSwitchTime_)] = {'\0'};
if (set_switchtime(value, 0x38, out, sizeof(out))) {
if (strlen(out)) {
has_update(dhw->wwSwitchTime_, out, sizeof(dhw->wwSwitchTime_));
-2
View File
@@ -233,7 +233,6 @@ void Water::process_IPMParameterWW(const std::shared_ptr<const Telegram> & teleg
// has_bitupdate(telegram, wwChargeType_, 10, 0); // 0 = charge pump, 0xff = 3-way valve
}
// 0x1E, only16 bit temperature
// Mixer(0x41) -> Boiler(0x08), HydrTemp(0x1E), data: 01 D8
void Water::process_IPMHydrTemp(const std::shared_ptr<const Telegram> & telegram) {
@@ -399,7 +398,6 @@ bool Water::set_wwRequiredTemp(const char * value, const int8_t id) {
return false;
}
bool Water::set_wwFlowTempOffset(const char * value, const int8_t id) {
int n;
if (!Helpers::value2number(value, n)) {
-2
View File
@@ -81,7 +81,6 @@ class Water : public EMSdevice {
int8_t wwHystOff_; // Hyst off (default -1)
uint8_t wwFlowTempOffset_; // default 40
void process_SM100wwTemperature(const std::shared_ptr<const Telegram> & telegram);
void process_SM100wwStatus(const std::shared_ptr<const Telegram> & telegram);
void process_SM100wwStatus2(const std::shared_ptr<const Telegram> & telegram);
@@ -100,7 +99,6 @@ class Water : public EMSdevice {
void process_IPMHydrTemp(const std::shared_ptr<const Telegram> & telegram);
void process_IPMParameterWW(const std::shared_ptr<const Telegram> & telegram);
bool set_wwSelTemp(const char * value, const int8_t id);
bool set_wwMaxTemp(const char * value, const int8_t id);
bool set_wwRedTemp(const char * value, const int8_t id);
+1 -1
View File
@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.9.0-dev.9"
#define EMSESP_APP_VERSION "3.9.0-dev.10"
-2
View File
@@ -77,7 +77,6 @@ bool Test::test(const std::string & cmd, int8_t id1, int8_t id2) {
return true;
}
//
// the tests take a lot of memory when built for the ESP32
// so only including the full set in standalone, otherwise a limited selection of basic tests
@@ -665,7 +664,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::webAPIService.webAPIService(&request);
Serial.println();
request.url("/api/thermostat/hc2/entities");
EMSESP::webAPIService.webAPIService(&request);
Serial.println();
-1
View File
@@ -16,7 +16,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* ESP32 UART port by @ArwedL and improved by @MichaelDvP. See https://github.com/emsesp/EMS-ESP/issues/380
*/
+2 -2
View File
@@ -57,7 +57,7 @@ void WebCustomization::read(WebCustomization & customizations, JsonObject root)
sensorJson["id"] = (const char *)sensor.id; // ID of dallas temperature sensor chip
sensorJson["name"] = (const char *)sensor.name; // n
sensorJson["offset"] = sensor.offset; // o
sensorJson["is_system"] = sensor.is_system; // s for gateway_temperature
sensorJson["is_system"] = sensor.is_system; // s true for gateway_temperature
}
// Analog Sensor customization
@@ -70,7 +70,7 @@ void WebCustomization::read(WebCustomization & customizations, JsonObject root)
sensorJson["factor"] = sensor.factor; // f
sensorJson["uom"] = sensor.uom; // u
sensorJson["type"] = sensor.type; // t
sensorJson["is_system"] = sensor.is_system; // s for core_voltage, supply_voltage
sensorJson["is_system"] = sensor.is_system; // s true for core_voltage, supply_voltage
}
// Masked entities customization and custom device name (optional)
+1 -1
View File
@@ -94,7 +94,7 @@ void WebLogService::show(Shell & shell) {
}
shell.println();
shell.printfln("Recent Log:");
shell.printfln("Last %d messages:", log_messages_.size());
shell.println();
for (const auto & message : log_messages_) {
+8 -1
View File
@@ -85,6 +85,7 @@ void WebSettings::read(WebSettings & settings, JsonObject root) {
root["modbus_timeout"] = settings.modbus_timeout;
root["developer_mode"] = settings.developer_mode;
root["disable_reset"] = settings.disable_reset;
root["auto_fw_check"] = settings.auto_fw_check;
root["email_enabled"] = settings.email_enabled;
root["email_security"] = settings.email_security;
root["email_server"] = settings.email_server;
@@ -310,6 +311,12 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
settings.disable_reset = root["disable_reset"];
EMSESP::system_.disable_reset(settings.disable_reset);
settings.auto_fw_check = root["auto_fw_check"] | EMSESP_DEFAULT_AUTO_FW_CHECK;
EMSESP::system_.auto_fw_check(settings.auto_fw_check);
if (settings.auto_fw_check && !original_settings.auto_fw_check) {
EMSESP::webStatusService.schedule_versions_refresh(); // just switched on, so look straight away
}
settings.bool_dashboard = root["bool_dashboard"] | EMSESP_DEFAULT_BOOL_FORMAT;
EMSESP::system_.bool_dashboard(settings.bool_dashboard);
@@ -456,7 +463,7 @@ void WebSettings::set_board_profile(WebSettings & settings) {
String bbq_board = EMSESP::system_.getBBQKeesGatewayDetails(FUSE_VALUE::BOARD);
if (!bbq_board.isEmpty() && settings.board_profile != "CUSTOM") {
#if defined(EMSESP_DEBUG)
EMSESP::logger().info("Overriding board profile with fuse value %s", bbq_board.c_str());
EMSESP::logger().info("Overriding board profile with eFuse value %s", bbq_board.c_str());
#endif
settings.board_profile = bbq_board;
}
+1
View File
@@ -122,6 +122,7 @@ class WebSettings {
bool developer_mode; // developer mode
bool disable_reset; // disable reset
bool auto_fw_check; // periodically check emsesp.org for a newer firmware version
static void read(WebSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, WebSettings & settings);
+55 -18
View File
@@ -348,7 +348,20 @@ void WebStatusService::getVersions(JsonObject root) {
}
}
if (!versions_cache_valid_) {
// take a snapshot, since the loop task can be rewriting the cache while we run on AsyncTCP
VersionInfo stable;
VersionInfo dev;
bool cache_valid;
{
std::lock_guard<std::mutex> lock{versions_mutex_};
cache_valid = versions_cache_valid_;
stable = versions_stable_;
dev = versions_dev_;
}
versions_fetch_requested_ = true;
if (!cache_valid) {
// no successful fetch yet (no network, fetch pending, or parse error)
return;
}
@@ -364,8 +377,8 @@ void WebStatusService::getVersions(JsonObject root) {
out["upgradeable"] = info.upgradeable;
};
add_section("stable", versions_stable_);
add_section("dev", versions_dev_);
add_section("stable", stable);
add_section("dev", dev);
#else
// standalone/test build: provide deterministic dummy data
JsonObject stable_out = root["stable"].to<JsonObject>();
@@ -383,17 +396,23 @@ void WebStatusService::getVersions(JsonObject root) {
// schedule the next versions.json fetch a few seconds out so the network stack has time to settle
// (DHCP completion, default-netif assignment and DNS server propagation through lwip)
void WebStatusService::schedule_versions_refresh() {
uint32_t next = uuid::get_uptime() + VERSIONS_INITIAL_FETCH_DELAY_MS;
versions_failures_ = 0; // a fresh link is a fresh chance, so start again from the short retry delay
uint32_t next = uuid::get_uptime() + VERSIONS_INITIAL_FETCH_DELAY_MS;
if (next == 0) {
next = 1; // 0 is the "idle" sentinel — never let the wrap land there
}
versions_next_fetch_ms_ = next;
}
// periodic refresh (1 hour) of the cached versions.json
// runs on the main loop task, which has a much bigger stack than AsyncTCP needed for https
// refresh of the cached versions.json, at most once a day
void WebStatusService::loop() {
#ifndef EMSESP_STANDALONE
// with auto_fw_check off we only reach out to emsesp.org when the WebUI asks for version
// information, so an unattended system never contacts it at all
if (!EMSESP::system_.auto_fw_check() && !versions_fetch_requested_) {
return;
}
// need a network
if (!EMSESP::network_.network_connected()) {
return;
@@ -409,8 +428,20 @@ void WebStatusService::loop() {
return;
}
bool ok = refresh_versions_cache();
uint32_t next = uuid::get_uptime() + (ok ? VERSIONS_REFRESH_INTERVAL_MS : VERSIONS_RETRY_INTERVAL_MS);
versions_fetch_requested_ = false;
uint32_t interval;
if (refresh_versions_cache()) {
versions_failures_ = 0;
interval = VERSIONS_REFRESH_INTERVAL_MS;
} else {
if (versions_failures_ < VERSIONS_MAX_BACKOFF_STEPS) {
versions_failures_++;
}
interval = VERSIONS_RETRY_INTERVAL_MS << (versions_failures_ - 1);
}
uint32_t next = uuid::get_uptime() + interval;
if (next == 0) {
next = 1;
}
@@ -451,16 +482,21 @@ bool WebStatusService::refresh_versions_cache() {
out.upgradeable = !out.version.empty() && FirmwareVersion(out.version) > current_version;
};
read_section("stable", versions_stable_);
read_section("dev", versions_dev_);
// parse into locals first, then publish in one go so the AsyncTCP task never sees a half-written cache
VersionInfo stable;
VersionInfo dev;
read_section("stable", stable);
read_section("dev", dev);
{
std::lock_guard<std::mutex> lock{versions_mutex_};
versions_stable_ = stable;
versions_dev_ = dev;
versions_cache_valid_ = true;
}
versions_cache_valid_ = true;
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Fetched from %s: stable=%s, dev=%s, current=%s",
VERSIONS_URL,
versions_stable_.version.c_str(),
versions_dev_.version.c_str(),
current_version_s.c_str());
EMSESP::logger().debug("Fetched from %s: stable=%s, dev=%s, current=%s", VERSIONS_URL, stable.version.c_str(), dev.version.c_str(), current_version_s.c_str());
#endif
return true;
#endif
@@ -468,11 +504,12 @@ bool WebStatusService::refresh_versions_cache() {
// returns if current dev/stable is upgradeable
bool WebStatusService::current_upgradeable() const {
FirmwareVersion current_version(current_version_s);
bool is_dev = current_version.prerelease().find("dev") != std::string::npos;
std::lock_guard<std::mutex> lock{versions_mutex_};
if (!versions_cache_valid_) {
return false;
}
FirmwareVersion current_version(current_version_s);
bool is_dev = current_version.prerelease().find("dev") != std::string::npos;
return is_dev ? versions_dev_.upgradeable : versions_stable_.upgradeable;
}
+21 -3
View File
@@ -7,6 +7,9 @@
#include "../core/firmwareVersion.h"
#include "../emsesp_version.h"
#include <atomic>
#include <mutex>
namespace emsesp {
class WebStatusService {
@@ -21,6 +24,7 @@ class WebStatusService {
// called from EMSESP::loop() to refresh the cached versions.json from emsesp.org
// so that the web request handler never has to do blocking HTTPS on the small AsyncTCP stack
// fetches on a daily schedule when auto_fw_check is on, otherwise only when getVersions asks
void loop();
// true once we've had at least one successful versions.json fetch
@@ -67,14 +71,28 @@ class WebStatusService {
VersionInfo versions_dev_;
bool versions_cache_valid_ = false; // true once we've had at least one successful fetch
uint32_t versions_next_fetch_ms_ = 0; // uuid::get_uptime() of the next attempt; 0 = idle
uint8_t versions_failures_ = 0; // consecutive failed fetches, drives the retry backoff
// set by getVersions() on the AsyncTCP task, consumed by the loop task, so that the WebUI still
// gets version information when auto_fw_check is off
std::atomic<bool> versions_fetch_requested_{false};
// the cache is written by the loop task in refresh_versions_cache() and read by the AsyncTCP
// task in getVersions()/current_upgradeable(), so the std::strings need guarding - without it a
// request landing mid-update reads a buffer that has just been freed
mutable std::mutex versions_mutex_;
bool refresh_versions_cache(); // does the actual HTTPS fetch + parse, returns true on success
static constexpr const char * VERSIONS_URL = "http://emsesp.org/versions.json";
static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour on success
static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after failure
static constexpr uint32_t VERSIONS_INITIAL_FETCH_DELAY_MS = 5UL * 1000UL; // 5 s after a link comes up
static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 24UL * 60UL * 60UL * 1000UL; // once a day on success
static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after the first failure
static constexpr uint32_t VERSIONS_INITIAL_FETCH_DELAY_MS = 5UL * 1000UL; // 5 s after a link comes up
// each attempt blocks the loop task for as long as the DNS lookup, connect and read timeouts
// allow, so double the retry delay per consecutive failure: 5, 10, 20, 40, 80, 160 minutes
static constexpr uint8_t VERSIONS_MAX_BACKOFF_STEPS = 6;
};
} // namespace emsesp
+1 -2
View File
@@ -4,8 +4,7 @@
# Command line test for the API
#
# emsesp_url="http://ems-esp.local"
emsesp_url="http://ems-espT.local"
emsesp_url="http://ems-esp.local"
# get the token from the Security page. This is the token for the admin user, unless changed it'll always be the same
emsesp_token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWV9.2bHpWya2C7Q12WjNUBD6_7N3RCD7CMl-EGhyQVzFdDg"
File diff suppressed because one or more lines are too long
-250
View File
@@ -1,250 +0,0 @@
{
"type": "systembackup",
"version": "3.9.0",
"date": "2026-08-02T08:06:45",
"systembackup": [
{
"type": "settings",
"Network": {
"ssid": "",
"bssid": "",
"password": "",
"hostname": "ems-esp",
"static_ip_config": false,
"bandwidth20": false,
"nosleep": true,
"enableMDNS": true,
"enableCORS": false,
"CORSOrigin": "*",
"tx_power": 0
},
"AP": {
"provision_mode": 2,
"ssid": "ems-esp",
"password": "ems-esp-neo",
"channel": 1,
"ssid_hidden": false,
"max_clients": 4,
"local_ip": "192.168.X.X",
"gateway_ip": "192.168.X.X",
"subnet_mask": "255.255.255.0"
},
"MQTT": {
"enableTLS": false,
"rootCA": "",
"enabled": true,
"host": "192.168.X.X",
"port": 1883,
"base": "ems-esp",
"username": "xxxx",
"password": "xxxx",
"client_id": "esp32-395c7bcc",
"keep_alive": 60,
"clean_session": false,
"entity_format": 1,
"publish_time_boiler": 60,
"publish_time_thermostat": 60,
"publish_time_solar": 60,
"publish_time_mixer": 60,
"publish_time_water": 60,
"publish_time_other": 60,
"publish_time_sensor": 60,
"publish_time_heartbeat": 10,
"mqtt_qos": 0,
"mqtt_retain": false,
"ha_enabled": true,
"nested_format": 1,
"discovery_prefix": "homeassistant",
"discovery_type": 0,
"ha_number_mode": 1,
"publish_single": false,
"publish_single2cmd": false,
"send_response": false
},
"NTP": {
"enabled": true,
"server": "time.google.com",
"tz_label": "Europe/Amsterdam",
"tz_format": "CET-1CEST,M3.5.0,M10.5.0/3"
},
"Security": {
"jwt_secret": "ems-esp-neo",
"users": [
{
"username": "admin",
"password": "admin",
"admin": true
},
{
"username": "guest",
"password": "guest",
"admin": false
}
]
},
"Settings": {
"version": "3.9.0",
"board_profile": "E32V2",
"platform": "ESP32",
"locale": "en",
"tx_mode": 1,
"ems_bus_id": 11,
"syslog_enabled": false,
"syslog_level": 3,
"trace_raw": false,
"syslog_mark_interval": 0,
"syslog_host": "",
"syslog_port": 514,
"boiler_heatingoff": false,
"remote_timeout": 24,
"remote_timeout_en": false,
"shower_timer": true,
"shower_alert": false,
"shower_alert_coldshot": 10,
"shower_alert_trigger": 7,
"shower_min_duration": 180,
"rx_gpio": 4,
"tx_gpio": 5,
"dallas_gpio": 14,
"dallas_parasite": false,
"led_gpio": 2,
"hide_led": true,
"led_type": 0,
"low_clock": false,
"telnet_enabled": true,
"notoken_api": false,
"readonly_mode": false,
"analog_enabled": true,
"pbutton_gpio": 34,
"solar_maxflow": 30,
"fahrenheit": false,
"bool_format": 1,
"bool_dashboard": 1,
"enum_format": 1,
"weblog_level": 6,
"weblog_buffer": 75,
"weblog_compact": true,
"phy_type": 1,
"eth_power": 15,
"eth_phy_addr": 0,
"eth_clock_mode": 1,
"modbus_enabled": false,
"modbus_port": 502,
"modbus_max_clients": 10,
"modbus_timeout": 300,
"developer_mode": true,
"disable_reset": false
}
},
{
"type": "schedule",
"Schedule": {
"schedule": []
}
},
{
"type": "customizations",
"Customizations": {
"ts": [],
"as": [],
"masked_entities": [
{
"product_id": 77,
"device_id": 23,
"custom_name": "",
"custom_brand": "",
"entity_ids": [
"08datetime",
"08hc1/seltemp|<30",
"08hc1/currtemp",
"08hc1/mode"
]
},
{
"product_id": 123,
"device_id": 8,
"custom_name": "",
"custom_brand": "",
"entity_ids": [
"08heatingactive",
"08burngas",
"08fanwork",
"08ignwork",
"08burnmaxpower|>23<121",
"08burnminperiod|<120",
"08lastcode",
"08servicecode"
]
}
]
}
},
{
"type": "entities",
"Entities": {
"entities": []
}
},
{
"type": "modules",
"Modules": {
"modules": []
}
},
{
"type": "nvs",
"nvs": [
{
"type": 1,
"key": "nompower",
"value": 30
},
{
"type": 33,
"key": "boot",
"value": "3.8.1-dev.4"
},
{
"type": 4,
"key": "d_boot",
"value": 1767525325
},
{
"type": 33,
"key": "app1",
"value": "3.8.2-dev.13"
},
{
"type": 33,
"key": "app0",
"value": "3.8.1"
},
{
"type": 4,
"key": "d_app0",
"value": 1774988066
},
{
"type": 1,
"key": "fresh_firmware",
"value": 0
},
{
"type": 4,
"key": "d_app1",
"value": 1776194060
},
{
"type": 66,
"key": "nrgheat",
"value": 485871.5481
},
{
"type": 66,
"key": "nrgww",
"value": 101649.2176
}
]
}
]
}