Merge branch 'dev' into fix/maxheat-enum-12-15kw

This commit is contained in:
Proddy
2026-09-18 15:08:00 +02:00
committed by GitHub
71 changed files with 1165 additions and 977 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. BreakConstructorInitializersBeforeComma: true # Always break constructor initializers before commas and align the commas with the colon.
ExperimentalAutoDetectBinPacking: false ExperimentalAutoDetectBinPacking: false
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 4 MaxEmptyLinesToKeep: 1
PenaltyBreakBeforeFirstCallParameter: 200 PenaltyBreakBeforeFirstCallParameter: 200
PenaltyExcessCharacter: 10 PenaltyExcessCharacter: 10
PointerAlignment: Middle PointerAlignment: Middle
+4 -2
View File
@@ -1,8 +1,10 @@
{ {
"name": "EMS-ESP Devcontainer", "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": { "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-extra/features/pnpm:2": {},
"ghcr.io/devcontainers/features/python:1": {}, "ghcr.io/devcontainers/features/python:1": {},
"ghcr.io/shyim/devcontainers-features/bun:0": {} "ghcr.io/shyim/devcontainers-features/bun:0": {}
+29 -4
View File
@@ -22,16 +22,18 @@ jobs:
with: with:
python-version: '3.13' python-version: '3.13'
- name: Install Node.js 24 - name: Install Node.js 26
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 26
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Enable Corepack - name: Install pnpm
run: corepack enable pnpm run: npm install -g pnpm
- name: Get the EMS-ESP version - name: Get the EMS-ESP version
id: build_info id: build_info
@@ -39,6 +41,13 @@ jobs:
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/emsesp_version.h | awk -F'"' '{print $2}'` version=`grep -E '^#define EMSESP_APP_VERSION' ./src/emsesp_version.h | awk -F'"' '{print $2}'`
echo "VERSION=$version" >> $GITHUB_OUTPUT echo "VERSION=$version" >> $GITHUB_OUTPUT
# must run before the release moves the 'latest' tag to this build
- name: Find the previous dev release
id: previous
run: |
prev=`git rev-parse -q --verify 'refs/tags/latest^{commit}' || git describe --tags --abbrev=0 --match 'v*'`
echo "REF=$prev" >> $GITHUB_OUTPUT
- name: Install PlatformIO - name: Install PlatformIO
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
@@ -66,12 +75,28 @@ jobs:
with: with:
commit_message: "chore: update generated files for v${{steps.build_info.outputs.VERSION}}" commit_message: "chore: update generated files for v${{steps.build_info.outputs.VERSION}}"
# an empty body falls back to the commit list generated by the release action
- name: Build the release notes
id: notes
env:
PREV_REF: ${{ steps.previous.outputs.REF }}
VERSION: ${{ steps.build_info.outputs.VERSION }}
run: |
./scripts/release_notes.sh "$PREV_REF" "$VERSION" > release_notes.md || :
cat release_notes.md
{
echo 'BODY<<__RELEASE_NOTES_EOF__'
cat release_notes.md
echo '__RELEASE_NOTES_EOF__'
} >> $GITHUB_OUTPUT
- name: Create GitHub Release - name: Create GitHub Release
id: 'automatic_releases' id: 'automatic_releases'
uses: emsesp/action-automatic-releases@v1.0.1 uses: emsesp/action-automatic-releases@v1.0.1
with: with:
repo_token: '${{ secrets.GITHUB_TOKEN }}' repo_token: '${{ secrets.GITHUB_TOKEN }}'
title: Development Build v${{steps.build_info.outputs.VERSION}} title: Development Build v${{steps.build_info.outputs.VERSION}}
body: ${{ steps.notes.outputs.BODY }}
automatic_release_tag: 'latest' automatic_release_tag: 'latest'
prerelease: true prerelease: true
files: | files: |
+4 -4
View File
@@ -20,16 +20,16 @@ jobs:
with: with:
python-version: '3.13' python-version: '3.13'
- name: Install Node.js 24 - name: Install Node.js 26
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 26
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Enable Corepack - name: Install pnpm
run: corepack enable pnpm run: npm install -g pnpm
- name: Get the EMS-ESP version - name: Get the EMS-ESP version
+4 -4
View File
@@ -22,16 +22,16 @@ jobs:
with: with:
python-version: '3.13' python-version: '3.13'
- name: Install Node.js 24 - name: Install Node.js 26
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 26
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Enable Corepack - name: Install pnpm
run: corepack enable pnpm run: npm install -g pnpm
- name: Get the EMS-ESP version - name: Get the EMS-ESP version
id: build_info 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) - 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) - 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) - 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 ## 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 - 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) - 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) - 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
- heat limit entities (`maxheatcomp`, `maxheatheat`, `dhw.maxheat`) read as unknown on heat pumps with an auxiliary heater larger than 9 kW - `enum_maxHeat` was missing the 12 kW and 15 kW steps - heat limit entities (`maxheatcomp`, `maxheatheat`, `dhw.maxheat`) read as unknown on heat pumps with an auxiliary heater larger than 9 kW - `enum_maxHeat` was missing the 12 kW and 15 kW steps
## Changed ## Changed
@@ -43,4 +48,6 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- set `None` for undefined values in HA `val_tpl` - 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) - 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) - 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", "type": "systembackup",
"version": "3.9.0", "version": "3.9.0",
"date": "2026-06-14T10:59:16",
"systembackup": [ "systembackup": [
{ {
"type": "settings", "type": "settings",
@@ -20,7 +19,7 @@
"eth_10mbit": false "eth_10mbit": false
}, },
"AP": { "AP": {
"provision_mode": 2, "provision_mode": 1,
"ssid": "ems-esp", "ssid": "ems-esp",
"password": "ems-esp-neo", "password": "ems-esp-neo",
"channel": 1, "channel": 1,
@@ -33,13 +32,13 @@
"MQTT": { "MQTT": {
"enableTLS": false, "enableTLS": false,
"rootCA": "", "rootCA": "",
"enabled": true, "enabled": false,
"host": "192.168.1.2", "host": "",
"port": 1883, "port": 1883,
"base": "ems-esp", "base": "ems-esp",
"username": "username", "username": "",
"password": "password", "password": "",
"client_id": "esp32", "client_id": "ems-esp",
"keep_alive": 60, "keep_alive": 60,
"clean_session": false, "clean_session": false,
"entity_format": 1, "entity_format": 1,
@@ -53,20 +52,23 @@
"publish_time_heartbeat": 60, "publish_time_heartbeat": 60,
"mqtt_qos": 0, "mqtt_qos": 0,
"mqtt_retain": false, "mqtt_retain": false,
"ha_enabled": true, "ha_enabled": false,
"nested_format": 1, "nested_format": 1,
"discovery_prefix": "homeassistant", "discovery_prefix": "homeassistant",
"discovery_type": 0, "discovery_type": 0,
"ha_number_mode": 1, "ha_number_mode": 0,
"publish_single": false, "publish_single": false,
"publish_single2cmd": false, "publish_single2cmd": false,
"send_response": false "send_response": false
}, },
"NTP": { "NTP": {
"enabled": true, "enabled": false,
"server": "time.google.com", "server": "time.google.com",
"tz_label": "Europe/Amsterdam", "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": { "Security": {
"jwt_secret": "ems-esp-neo", "jwt_secret": "ems-esp-neo",
@@ -87,9 +89,9 @@
"version": "3.9.0", "version": "3.9.0",
"board_profile": "E32V2_2", "board_profile": "E32V2_2",
"platform": "ESP32", "platform": "ESP32",
"system_name": "prod", "system_name": "",
"locale": "en", "locale": "en",
"tx_mode": 1, "tx_mode": 5,
"ems_bus_id": 73, "ems_bus_id": 73,
"syslog_enabled": false, "syslog_enabled": false,
"syslog_level": 3, "syslog_level": 3,
@@ -100,7 +102,7 @@
"boiler_heatingoff": false, "boiler_heatingoff": false,
"remote_timeout": 24, "remote_timeout": 24,
"remote_timeout_en": false, "remote_timeout_en": false,
"shower_timer": true, "shower_timer": false,
"shower_alert": false, "shower_alert": false,
"shower_alert_coldshot": 10, "shower_alert_coldshot": 10,
"shower_alert_trigger": 7, "shower_alert_trigger": 7,
@@ -110,7 +112,7 @@
"dallas_gpio": 14, "dallas_gpio": 14,
"dallas_parasite": false, "dallas_parasite": false,
"led_gpio": 32, "led_gpio": 32,
"hide_led": true, "hide_led": false,
"led_type": 1, "led_type": 1,
"low_clock": false, "low_clock": false,
"telnet_enabled": true, "telnet_enabled": true,
@@ -134,139 +136,43 @@
"modbus_port": 502, "modbus_port": 502,
"modbus_max_clients": 10, "modbus_max_clients": 10,
"modbus_timeout": 300, "modbus_timeout": 300,
"developer_mode": true, "developer_mode": false,
"disable_reset": false, "disable_reset": false,
"email_enabled": true, "email_enabled": false,
"email_security": 2, "email_security": 2,
"email_server": "smtp.gmail.com", "email_server": "smtp.example.net",
"email_port": 587, "email_port": 587,
"email_login": "test@emsesp.org", "email_login": "",
"email_pass": "password", "email_pass": "",
"email_sender": "test@emsesp.org", "email_sender": "ems-esp@example.net",
"email_recp": "test@emsesp.org", "email_recp": "",
"email_subject": "ems-esp notification" "email_subject": "ems-esp notification"
} }
}, },
{ {
"type": "commands", "type": "commands",
"Commands": { "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"
}
]
} }
}, },
{ {
"type": "schedule", "type": "schedule",
"Schedule": { "Schedule": {
"schedule": [ "schedule": []
{
"id": 1,
"flags": 128,
"active": false,
"time": "",
"cmd_name": "sendmail",
"name": "sendmailtest"
}
]
} }
}, },
{ {
"type": "customizations", "type": "customizations",
"Customizations": { "Customizations": {
"as": [ "ts": [],
{ "as": [],
"gpio": 39,
"name": "core_voltage",
"offset": 0,
"factor": 0.003771,
"uom": 23,
"type": 3,
"is_system": true
},
{
"gpio": 36,
"name": "supply_voltage",
"offset": 0,
"factor": 0.017,
"uom": 23,
"type": 3,
"is_system": true
},
{
"gpio": 2,
"name": "led",
"offset": 0,
"factor": 1,
"uom": 0,
"type": 6,
"is_system": true
}
],
"masked_entities": [] "masked_entities": []
} }
}, },
{ {
"type": "entities", "type": "entities",
"Entities": { "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"
}
]
} }
}, },
{ {
@@ -274,71 +180,6 @@
"Modules": { "Modules": {
"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
}
]
} }
] ]
} }
+9 -9
View File
@@ -32,31 +32,31 @@
"alova": "^3.5.5", "alova": "^3.5.5",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"preact": "^10.29.8", "preact": "^10.29.8",
"react": "^19.2.8", "react": "^19.3.0",
"react-dom": "^19.2.8", "react-dom": "^19.3.0",
"react-icons": "^5.7.0", "react-icons": "^5.7.0",
"react-router": "^8.3.1", "react-router": "^8.4.0",
"typesafe-i18n": "^5.27.1" "typesafe-i18n": "^5.27.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@preact/preset-vite": "^2.10.6", "@preact/preset-vite": "^2.10.6",
"@trivago/prettier-plugin-sort-imports": "^6.0.2", "@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/node": "^26.5.0", "@types/node": "^26.6.1",
"@types/react": "^19.2.18", "@types/react": "^19.3.0",
"@types/react-dom": "^19.2.7", "@types/react-dom": "^19.3.0",
"concurrently": "^10.0.5", "concurrently": "^10.0.5",
"eslint": "^10.10.0", "eslint": "^10.10.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"etag": "^1.8.1", "etag": "^1.8.1",
"mime-types": "^3.0.2", "mime-types": "^3.0.2",
"prettier": "^3.9.6", "prettier": "^3.9.7",
"rollup-plugin-visualizer": "^7.1.1", "rollup-plugin-visualizer": "^7.1.1",
"terser": "^5.51.2", "terser": "^5.51.2",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"typescript-eslint": "^8.70.0", "typescript-eslint": "^8.70.0",
"vite": "^8.2.2", "vite": "^8.3.0",
"vite-plugin-imagemin": "^0.6.1" "vite-plugin-imagemin": "^0.6.1"
}, },
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" "packageManager": "pnpm@12.4.2"
} }
+393 -326
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 // Commands
export const readCommands = () => export const readCommands = () =>
alovaInstance.Get<CommandItem[]>('/rest/commands', { alovaInstance.Get<CommandItem[]>('/rest/commands', {
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
transform(data) { transform(data) {
const commands = (data as Commands).commands; const commands = (data as Commands).commands;
return commands.map((ci) => ({ return commands.map((ci) => ({
+1 -2
View File
@@ -19,7 +19,6 @@ export const uploadFile = (file: File) => {
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
return alovaInstance.Post('/rest/uploadFile', formData, { return alovaInstance.Post('/rest/uploadFile', formData, {
timeout: UPLOAD_TIMEOUT, timeout: UPLOAD_TIMEOUT
enableUpload: true
}); });
}; };
+1
View File
@@ -55,6 +55,7 @@ export interface Settings {
email_subject: string; email_subject: string;
developer_mode: boolean; developer_mode: boolean;
disable_reset: boolean; disable_reset: boolean;
auto_fw_check: boolean;
} }
export enum busConnectionStatus { export enum busConnectionStatus {
@@ -512,6 +512,18 @@ const ApplicationSettings = () => {
</Grid> </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"> <Typography sx={{ pb: 1, pt: 2 }} variant="h6" color="primary">
{LL.SENSORS()} {LL.SENSORS()}
</Typography> </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 { Link } from 'react-router';
import CancelIcon from '@mui/icons-material/Cancel'; 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_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/';
const DEV_RELNOTES_URL = const DEV_RELNOTES_URL =
'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md'; '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 // Types for better type safety
interface PartitionData { interface PartitionData {
@@ -440,12 +441,17 @@ const Version = () => {
const stableUpgradeAvailable = versions?.stable?.upgradeable ?? false; const stableUpgradeAvailable = versions?.stable?.upgradeable ?? false;
const devUpgradeAvailable = versions?.dev?.upgradeable ?? false; const devUpgradeAvailable = versions?.dev?.upgradeable ?? false;
const internetLive = Boolean(versions?.stable || versions?.dev); const internetLive = Boolean(versions?.stable || versions?.dev);
const versionPolls = useRef(0);
useEffect(() => { useEffect(() => {
if (internetLive) { if (internetLive || versionPolls.current >= MAX_VERSION_POLLS) {
return; return;
} }
const interval = setInterval(() => { const interval = setInterval(() => {
versionPolls.current += 1;
if (versionPolls.current >= MAX_VERSION_POLLS) {
clearInterval(interval);
}
void refreshVersions(); void refreshVersions();
}, 3000); }, 3000);
return () => clearInterval(interval); return () => clearInterval(interval);
@@ -93,7 +93,7 @@ const SingleUpload = ({ doRestart, text }: SingleUploadProps) => {
</> </>
) : ( ) : (
<DragNdrop <DragNdrop
text={(md5 ? LL.UPLOAD_MD5_RECEIVED() : text) + '...'} text={md5 ? LL.UPLOAD_MD5_RECEIVED() : text}
onFileSelected={setFile} onFileSelected={setFile}
/> />
)} )}
+3 -2
View File
@@ -321,6 +321,7 @@ const cs: Translation = {
DASHBOARD: 'Dashboard', DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Režim vývojáře', DEVELOPER_MODE: 'Režim vývojáře',
DISABLE_RESET: 'Zakázat obnovení továrního nastavení', DISABLE_RESET: 'Zakázat obnovení továrního nastavení',
AUTO_FW_CHECK: 'Automaticky kontrolovat aktualizace firmwaru',
BYTES: 'Bajty', BYTES: 'Bajty',
BITMASK: 'Bit Mask', BITMASK: 'Bit Mask',
DUPLICATE: 'Duplikát', DUPLICATE: 'Duplikát',
@@ -348,8 +349,8 @@ const cs: Translation = {
COMMAND_EXECUTED: 'Příkaz úspěšně spuštěn', COMMAND_EXECUTED: 'Příkaz úspěšně spuštěn',
RUN: 'Spustit příkaz', RUN: 'Spustit příkaz',
INTERNET_CONNECTION_CHECK: 'Kontrola online pro dostupné verze', 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_BACKUP_TEXT: 'Klikněte, abyste vybrali, nebo drop soubor zálohy (.json) sem',
UPLOAD_FIRMWARE_TEXT: 'Klikněte, abyste vybrali nebo přetáhli a pustili soubor firmware .bin/.md5 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_RECEIVED: 'Digest přijat, nyní nahrajte firmware .bin',
UPLOAD_MD5_MATCHED: 'MD5 souhlasí, firmware se instaluje...' UPLOAD_MD5_MATCHED: 'MD5 souhlasí, firmware se instaluje...'
}; };
+3 -2
View File
@@ -321,6 +321,7 @@ const de: Translation = {
DASHBOARD: 'Dashboard', DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Entwicklermodus', DEVELOPER_MODE: 'Entwicklermodus',
DISABLE_RESET: 'Werkseinstellungen deaktivieren', DISABLE_RESET: 'Werkseinstellungen deaktivieren',
AUTO_FW_CHECK: 'Automatisch nach Firmware-Updates suchen',
BYTES: 'Bytes', BYTES: 'Bytes',
BITMASK: 'Bit Maske', BITMASK: 'Bit Maske',
DUPLICATE: 'Kopieren', DUPLICATE: 'Kopieren',
@@ -348,8 +349,8 @@ const de: Translation = {
COMMAND_EXECUTED: 'Befehl erfolgreich ausgeführt', COMMAND_EXECUTED: 'Befehl erfolgreich ausgeführt',
RUN: 'Befehl ausführen', RUN: 'Befehl ausführen',
INTERNET_CONNECTION_CHECK: 'Online-Prüfung auf verfügbare Versionen', 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_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 zu ziehen und abzulegen', 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_RECEIVED: 'Prüfsumme empfangen, jetzt die Firmware-Datei (.bin) hochladen',
UPLOAD_MD5_MATCHED: 'MD5 stimmt überein, Firmware wird installiert...' UPLOAD_MD5_MATCHED: 'MD5 stimmt überein, Firmware wird installiert...'
}; };
+3 -2
View File
@@ -321,6 +321,7 @@ const en: Translation = {
DASHBOARD: 'Dashboard', DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Developer Mode', DEVELOPER_MODE: 'Developer Mode',
DISABLE_RESET: 'Disable Factory Reset', DISABLE_RESET: 'Disable Factory Reset',
AUTO_FW_CHECK: 'Automatically check for firmware updates',
BYTES: 'Bytes', BYTES: 'Bytes',
BITMASK: 'Bit Mask', BITMASK: 'Bit Mask',
DUPLICATE: 'Duplicate', DUPLICATE: 'Duplicate',
@@ -348,8 +349,8 @@ const en: Translation = {
COMMAND_EXECUTED: 'Command executed successfully', COMMAND_EXECUTED: 'Command executed successfully',
RUN: 'Run', RUN: 'Run',
INTERNET_CONNECTION_CHECK: 'Checking online for available versions', INTERNET_CONNECTION_CHECK: 'Checking online for available versions',
UPLOAD_BACKUP_TEXT: 'Click to select or Drag&Drop a backup file (.json) here', UPLOAD_BACKUP_TEXT: 'Click to select, or drop a backup file (.json) here',
UPLOAD_FIRMWARE_TEXT: 'Click to select or Drag&Drop a firmware .bin/.md5 file', 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_RECEIVED: 'Digest received, now upload the firmware .bin file',
UPLOAD_MD5_MATCHED: 'MD5 matches, installing firmware...' UPLOAD_MD5_MATCHED: 'MD5 matches, installing firmware...'
}; };
+3 -2
View File
@@ -321,6 +321,7 @@ const fr: Translation = {
DASHBOARD: 'Tableau de bord', DASHBOARD: 'Tableau de bord',
DEVELOPER_MODE: 'Mode développeur', DEVELOPER_MODE: 'Mode développeur',
DISABLE_RESET: 'Désactiver la réinitialisation de fabrique', DISABLE_RESET: 'Désactiver la réinitialisation de fabrique',
AUTO_FW_CHECK: 'Vérifier automatiquement les mises à jour du firmware',
BYTES: 'Octets', BYTES: 'Octets',
BITMASK: 'Masque de bits', BITMASK: 'Masque de bits',
DUPLICATE: 'Dupliquer', DUPLICATE: 'Dupliquer',
@@ -348,8 +349,8 @@ const fr: Translation = {
COMMAND_EXECUTED: 'Commande exécutée avec succès', COMMAND_EXECUTED: 'Commande exécutée avec succès',
RUN: 'Exécuter une commande', RUN: 'Exécuter une commande',
INTERNET_CONNECTION_CHECK: 'Vérification en ligne pour les versions disponibles', 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_BACKUP_TEXT: 'Cliquez pour sélectionner, ou drop 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_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_RECEIVED: 'Empreinte reçue, téléversez maintenant le fichier firmware .bin',
UPLOAD_MD5_MATCHED: 'MD5 concordant, installation du firmware...' UPLOAD_MD5_MATCHED: 'MD5 concordant, installation du firmware...'
}; };
+3 -2
View File
@@ -321,6 +321,7 @@ const it: Translation = {
DASHBOARD: 'Pannello di controllo', DASHBOARD: 'Pannello di controllo',
DEVELOPER_MODE: 'Modalità sviluppatore', DEVELOPER_MODE: 'Modalità sviluppatore',
DISABLE_RESET: 'Disabilita la ripristino di fabbrica', DISABLE_RESET: 'Disabilita la ripristino di fabbrica',
AUTO_FW_CHECK: 'Controlla automaticamente gli aggiornamenti del firmware',
BYTES: 'Byte', BYTES: 'Byte',
BITMASK: 'Bitmask', BITMASK: 'Bitmask',
DUPLICATE: 'Duplicato', DUPLICATE: 'Duplicato',
@@ -348,8 +349,8 @@ const it: Translation = {
COMMAND_EXECUTED: 'Comando eseguito con successo', COMMAND_EXECUTED: 'Comando eseguito con successo',
RUN: 'Esegui comando', RUN: 'Esegui comando',
INTERNET_CONNECTION_CHECK: 'Controllo online per le versioni disponibili', 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_BACKUP_TEXT: 'Klik per selezionare, o drop un file di backup (.json) qui',
UPLOAD_FIRMWARE_TEXT: 'Klik per selezionare o trascinare e rilasciare un file firmware .bin/.md5 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_RECEIVED: 'Digest ricevuto, ora caricare il file firmware .bin',
UPLOAD_MD5_MATCHED: 'MD5 corrispondente, installazione firmware...' UPLOAD_MD5_MATCHED: 'MD5 corrispondente, installazione firmware...'
}; };
+3 -2
View File
@@ -321,6 +321,7 @@ const nl: Translation = {
DASHBOARD: 'Dashboard', DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Ontwikkelaarsmodus', DEVELOPER_MODE: 'Ontwikkelaarsmodus',
DISABLE_RESET: 'Fabrieksinstellingen uitschakelen', DISABLE_RESET: 'Fabrieksinstellingen uitschakelen',
AUTO_FW_CHECK: 'Automatisch controleren op firmware-updates',
BYTES: 'Bytes', BYTES: 'Bytes',
BITMASK: 'Bit Mask', BITMASK: 'Bit Mask',
DUPLICATE: 'Duplicaat', DUPLICATE: 'Duplicaat',
@@ -348,8 +349,8 @@ const nl: Translation = {
COMMAND_EXECUTED: 'Commando uitgevoerd succesvol', COMMAND_EXECUTED: 'Commando uitgevoerd succesvol',
RUN: 'Commando uitvoeren', RUN: 'Commando uitvoeren',
INTERNET_CONNECTION_CHECK: 'Controleren op online beschikbare versies', INTERNET_CONNECTION_CHECK: 'Controleren op online beschikbare versies',
UPLOAD_BACKUP_TEXT: 'Klik voor selecteren of sleep en drop een back-upbestand (.json) hier', UPLOAD_BACKUP_TEXT: 'Klik voor selecteren, of drop een back-upbestand (.json) hier',
UPLOAD_FIRMWARE_TEXT: 'Klik voor selecteren of sleep en drop een firmware .bin/.md5 bestand 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_RECEIVED: 'Digest ontvangen, upload nu het firmware .bin-bestand',
UPLOAD_MD5_MATCHED: 'MD5 komt overeen, firmware wordt geïnstalleerd...' UPLOAD_MD5_MATCHED: 'MD5 komt overeen, firmware wordt geïnstalleerd...'
}; };
+3 -2
View File
@@ -321,6 +321,7 @@ const no: Translation = {
DASHBOARD: 'Dashboard', DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Utvikler modus', DEVELOPER_MODE: 'Utvikler modus',
DISABLE_RESET: 'Deaktiver fabrikkinnstillinger', DISABLE_RESET: 'Deaktiver fabrikkinnstillinger',
AUTO_FW_CHECK: 'Se automatisk etter nye firmware versjoner',
BYTES: 'Bytes', BYTES: 'Bytes',
BITMASK: 'Bitmask', BITMASK: 'Bitmask',
DUPLICATE: 'Duplikat', DUPLICATE: 'Duplikat',
@@ -348,8 +349,8 @@ const no: Translation = {
COMMAND_EXECUTED: 'Kommando kjørt lykkes', COMMAND_EXECUTED: 'Kommando kjørt lykkes',
RUN: 'Kjør', RUN: 'Kjør',
INTERNET_CONNECTION_CHECK: 'Kontrollerer online for tilgjengelige versjoner', INTERNET_CONNECTION_CHECK: 'Kontrollerer online for tilgjengelige versjoner',
UPLOAD_BACKUP_TEXT: 'Klikk for å velge eller dra og slipp en sikkerhetskopi (.json) her', UPLOAD_BACKUP_TEXT: 'Klikk for å velge, eller drop en sikkerhetskopi (.json) her',
UPLOAD_FIRMWARE_TEXT: 'Klikk for å velge eller dra og slipp en firmware .bin/.md5 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_RECEIVED: 'MD5 mottatt, last nå opp firmware .bin-filen',
UPLOAD_MD5_MATCHED: 'MD5 stemmer, firmware installeres...' UPLOAD_MD5_MATCHED: 'MD5 stemmer, firmware installeres...'
}; };
+3 -2
View File
@@ -321,6 +321,7 @@ const pl: BaseTranslation = {
DASHBOARD: 'Pulpit', DASHBOARD: 'Pulpit',
DEVELOPER_MODE: 'Tryb programisty', DEVELOPER_MODE: 'Tryb programisty',
DISABLE_RESET: 'Wyłącz reset fabryczny', DISABLE_RESET: 'Wyłącz reset fabryczny',
AUTO_FW_CHECK: 'Automatycznie sprawdzaj aktualizacje firmware',
BYTES: 'Bajty', BYTES: 'Bajty',
BITMASK: 'Bit Mask', BITMASK: 'Bit Mask',
DUPLICATE: 'Duplicate', DUPLICATE: 'Duplicate',
@@ -348,8 +349,8 @@ const pl: BaseTranslation = {
COMMAND_EXECUTED: 'Komenda wykonana pomyślnie', COMMAND_EXECUTED: 'Komenda wykonana pomyślnie',
RUN: 'Wykonaj', RUN: 'Wykonaj',
INTERNET_CONNECTION_CHECK: 'Sprawdzanie online dla dostępnych wersji', 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_BACKUP_TEXT: 'Kliknij, aby wybrać, lub drop plik kopii zapasowej (.json) tutaj',
UPLOAD_FIRMWARE_TEXT: 'Kliknij, aby wybrać lub przeciągnij i upuść plik firmware .bin/.md5', 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_RECEIVED: 'Suma MD5 odebrana, teraz wgraj plik firmware .bin',
UPLOAD_MD5_MATCHED: 'MD5 zgodne, instalowanie firmware...' UPLOAD_MD5_MATCHED: 'MD5 zgodne, instalowanie firmware...'
}; };
+3 -2
View File
@@ -321,6 +321,7 @@ const sk: Translation = {
DASHBOARD: 'Panel', DASHBOARD: 'Panel',
DEVELOPER_MODE: 'Režim vývojára', DEVELOPER_MODE: 'Režim vývojára',
DISABLE_RESET: 'Zakázať továrenské nastavenia', DISABLE_RESET: 'Zakázať továrenské nastavenia',
AUTO_FW_CHECK: 'Automaticky kontrolovať aktualizácie firmvéru',
BYTES: 'Bytov', BYTES: 'Bytov',
BITMASK: 'Bitová maska', BITMASK: 'Bitová maska',
DUPLICATE: 'Duplicitné', DUPLICATE: 'Duplicitné',
@@ -348,8 +349,8 @@ const sk: Translation = {
COMMAND_EXECUTED: 'Príkaz vykonaný úspešne', COMMAND_EXECUTED: 'Príkaz vykonaný úspešne',
RUN: 'Volať príkaz', RUN: 'Volať príkaz',
INTERNET_CONNECTION_CHECK: 'Kontrola online pre dostupné verzie', 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_BACKUP_TEXT: 'Kliknite, aby ste vybrali, alebo drop súbor zálohy (.json) sem',
UPLOAD_FIRMWARE_TEXT: 'Kliknite, aby ste vybrali alebo presuňte a upustili súbor firmware .bin/.md5', 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_RECEIVED: 'Suma MD5 prijatá, teraz nahrajte firmware .bin',
UPLOAD_MD5_MATCHED: 'MD5 súhlasí, firmvér sa inštaluje...' UPLOAD_MD5_MATCHED: 'MD5 súhlasí, firmvér sa inštaluje...'
}; };
+3 -2
View File
@@ -321,6 +321,7 @@ const sv: Translation = {
DASHBOARD: 'Kontrollpanel', DASHBOARD: 'Kontrollpanel',
DEVELOPER_MODE: 'Utvecklarläge', DEVELOPER_MODE: 'Utvecklarläge',
DISABLE_RESET: 'Inaktivera fabriksåterställning', DISABLE_RESET: 'Inaktivera fabriksåterställning',
AUTO_FW_CHECK: 'Sök automatiskt efter nya firmwareversioner',
BYTES: 'Bytes', BYTES: 'Bytes',
BITMASK: 'Bitmask', BITMASK: 'Bitmask',
DUPLICATE: 'Dublett', DUPLICATE: 'Dublett',
@@ -348,8 +349,8 @@ const sv: Translation = {
COMMAND_EXECUTED: 'Kommando kört lyckades', COMMAND_EXECUTED: 'Kommando kört lyckades',
RUN: 'Kör kommando', RUN: 'Kör kommando',
INTERNET_CONNECTION_CHECK: 'Kontrollerar online för tillgängliga versioner', 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_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 dra och släpp en firmware .bin/.md5 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_RECEIVED: 'MD5 mottagen, ladda nu upp firmware .bin-filen',
UPLOAD_MD5_MATCHED: 'MD5 stämmer, firmware installeras...' UPLOAD_MD5_MATCHED: 'MD5 stämmer, firmware installeras...'
}; };
+3 -2
View File
@@ -321,6 +321,7 @@ const tr: Translation = {
DASHBOARD: 'Kontrol Paneli', DASHBOARD: 'Kontrol Paneli',
DEVELOPER_MODE: 'Geliştirici Modu', DEVELOPER_MODE: 'Geliştirici Modu',
DISABLE_RESET: 'Fabrika ayarlarını devre dışı bırak', DISABLE_RESET: 'Fabrika ayarlarını devre dışı bırak',
AUTO_FW_CHECK: 'Firmware güncellemelerini otomatik olarak denetle',
BYTES: 'Bayt', BYTES: 'Bayt',
BITMASK: 'Bit Maskesi', BITMASK: 'Bit Maskesi',
DUPLICATE: 'Çift', DUPLICATE: 'Çift',
@@ -348,8 +349,8 @@ const tr: Translation = {
COMMAND_EXECUTED: 'Komut başarıyla çalıştırıldı', COMMAND_EXECUTED: 'Komut başarıyla çalıştırıldı',
RUN: 'Komutu çalıştır', RUN: 'Komutu çalıştır',
INTERNET_CONNECTION_CHECK: 'Online bağlantısı kontrol ediliyor', INTERNET_CONNECTION_CHECK: 'Online bağlantısı kontrol ediliyor',
UPLOAD_BACKUP_TEXT: 'Yeni bir firmware dosyası (.bin) veya yedek dosyası (.json) yükle', UPLOAD_BACKUP_TEXT: 'Bir yedek dosyası (.json), buraya drop yapın',
UPLOAD_FIRMWARE_TEXT: 'Bir firmware .bin dosyası veya buraya tıklayı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_RECEIVED: 'Özet alındı, şimdi firmware .bin dosyasını yükleyin',
UPLOAD_MD5_MATCHED: 'MD5 eşleşti, firmware yükleniyor...' 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 { export default class Schema {
private readonly rules: Rules; private readonly rules: Rules;
private requiredMessage?: string; private requiredMessage?: string | undefined;
constructor(descriptor: Rules) { constructor(descriptor: Rules) {
this.rules = descriptor; this.rules = descriptor;
+4 -1
View File
@@ -71,10 +71,13 @@ class Preferences {
return true; return true;
} }
bool clear() {
return true;
}
// unused.... // unused....
// void end(); // void end();
// bool clear();
// size_t putShort(const char * key, int16_t value); // size_t putShort(const char * key, int16_t value);
// size_t putUShort(const char * key, uint16_t value); // size_t putUShort(const char * key, uint16_t value);
// size_t putInt(const char * key, int32_t value); // size_t putInt(const char * key, int32_t value);
+2 -2
View File
@@ -15,7 +15,7 @@
}, },
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^6.0.2", "@trivago/prettier-plugin-sort-imports": "^6.0.2",
"prettier": "^3.9.6" "prettier": "^3.9.7"
}, },
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" "packageManager": "pnpm@12.4.2"
} }
+106 -49
View File
@@ -7,96 +7,153 @@ importers:
configDependencies: {} configDependencies: {}
packageManagerDependencies: packageManagerDependencies:
pnpm: pnpm:
specifier: 12.3.4 specifier: 12.4.2
version: 12.3.4 version: 12.4.2
packages: packages:
'@pnpm/exe.darwin-arm64@12.3.4': '@pnpm/exe.android-arm64@12.4.2':
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} resolution: {integrity: sha512-E255MbcQ0V1577M2BV0ajWuP7KmTPYA0jqZnk3rK6Lq3kTvZWulMMb7iqRmnLsQbyWTkMEB2CfyM70loVDA8xg==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.4.2':
resolution: {integrity: sha512-J1pSeCUwuKxMG70ZzpWn8JzElxiEa8NN/3N0SlZRtU2xbztChaJCKF3HaBNIj9yPfeofWzJo/lwNvDDjraQuZw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.4.2':
resolution: {integrity: sha512-A0WDo8iErfZBXgrLseQxw8i8Y9ctUpOEl/Uu+cubnTzpD8tT9ykIB548L8YTM2WD4OS+ZOHSxy8aGZcvKq8PaQ==}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@pnpm/exe.darwin-x64@12.3.4': '@pnpm/exe.darwin-x64@12.4.2':
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} resolution: {integrity: sha512-MSgJdovBWHcb5DEOvfPH9yNi/T5O1Xa4ess+E1ESGo/5yuty7S4JoiIjami+fsNXfnoQMlwsMUqYU4zAvBcNCw==}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@pnpm/exe.linux-arm64-musl@12.3.4': '@pnpm/exe.freebsd-x64@12.4.2':
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} resolution: {integrity: sha512-h2YumlQSNvgbRPv+RXwABohX65f9bOBZn+jMIt7bFDISZPCzQ+Nvpt6Awbp4ip5PwQgYxbu5iREJ1fHE39Fm8A==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.4.2':
resolution: {integrity: sha512-LwSEtSEDTv6S51YLs3YvSkPyun/QmfMic1UGICUkPWFu6ByP43RdMlkKvmVkfGhAYCpnxO057vrmyJqtfZrPCA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@pnpm/exe.linux-arm64@12.3.4': '@pnpm/exe.linux-arm64@12.4.2':
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} resolution: {integrity: sha512-2dSiDXyhx+RTHsewxex8f/jVjqQXWJ2oow4kCVHEWdZKeBpgMxvZ6fHkTAUBJXgqhbKIDHvuNlZBP7gJfUWL5A==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.3.4': '@pnpm/exe.linux-ppc64@12.4.2':
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} resolution: {integrity: sha512-8Itc+jQk+MTz04LS9D1RcH+VctAWmzM4l1cJQ+Sx7pAJNo7EKHdRMbC0Tok1jyQ7eEHNJZD5EleYneZqWfZM+g==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.4.2':
resolution: {integrity: sha512-hleOeqhTVpH+z9RVMGnxvU4ZnrkBUClWjCbHD9u6kwyqhGSpevoU1wTGish+CBRhmIgMAy9pAfFpqhbAKOKNfw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.4.2':
resolution: {integrity: sha512-LAsQRRdP9aToENR6dtcIJ9l+e1zMYOX0tQcLGpRyLPVBQcYRLlvAPcmDshsiIHQjp05SDa9FI0czX1ZQ1a7a/A==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.4.2':
resolution: {integrity: sha512-kzfzH2/0BWdTABK14Yj5a1xsdkTEQUp2eXEPNakaD9jKL025lq3hyaKHIz/gIZaPDMe/1bFFK/En4ztFlbBJxw==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@pnpm/exe.linux-x64@12.3.4': '@pnpm/exe.linux-x64@12.4.2':
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} resolution: {integrity: sha512-/pbt0UVTa8NMDhzOWLQRfZ6G9ROKXlJPZtx845BqyWfc7hCrWXhTLBd70yO2y8+E+IWN3oHM1s/JsIQNGk1yvg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@pnpm/exe.win32-arm64@12.3.4': '@pnpm/exe.win32-arm64@12.4.2':
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} resolution: {integrity: sha512-PsW19e4dAUNpZ0cS9flaxFuAmpt2dKlH/Vvi8TZ4qyJjjQzue/CEsWm+6wKVP4CJ7IT8RdL4qh+soOPWIgF2Zw==}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@pnpm/exe.win32-x64@12.3.4': '@pnpm/exe.win32-x64@12.4.2':
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} resolution: {integrity: sha512-+xGoeE0g55ztWvl8i5QqdmNfW3nIrTVcoQrNshEOwxthm9Ag48oXton3uxOA3/SANyz5AXexEjj2KO20NnOrEw==}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
pnpm@12.3.4: pnpm@12.4.2:
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} resolution: {integrity: sha512-CK3GYTGAJ1x8ntraOdzwjJxhrU5+rzMKTzRh8QKw+QdCNFTRF/mOctR/7wYWBwZE17/8lzpqV/UJCm18NosHyQ==}
engines: {node: '>=18.*'} engines: {node: '>=18.*'}
hasBin: true hasBin: true
snapshots: snapshots:
'@pnpm/exe.darwin-arm64@12.3.4': '@pnpm/exe.android-arm64@12.4.2':
optional: true optional: true
'@pnpm/exe.darwin-x64@12.3.4': '@pnpm/exe.android-x64@12.4.2':
optional: true optional: true
'@pnpm/exe.linux-arm64-musl@12.3.4': '@pnpm/exe.darwin-arm64@12.4.2':
optional: true optional: true
'@pnpm/exe.linux-arm64@12.3.4': '@pnpm/exe.darwin-x64@12.4.2':
optional: true optional: true
'@pnpm/exe.linux-x64-musl@12.3.4': '@pnpm/exe.freebsd-x64@12.4.2':
optional: true optional: true
'@pnpm/exe.linux-x64@12.3.4': '@pnpm/exe.linux-arm64-musl@12.4.2':
optional: true optional: true
'@pnpm/exe.win32-arm64@12.3.4': '@pnpm/exe.linux-arm64@12.4.2':
optional: true optional: true
'@pnpm/exe.win32-x64@12.3.4': '@pnpm/exe.linux-ppc64@12.4.2':
optional: true optional: true
pnpm@12.3.4: '@pnpm/exe.linux-riscv64@12.4.2':
optional: true
'@pnpm/exe.linux-s390x@12.4.2':
optional: true
'@pnpm/exe.linux-x64-musl@12.4.2':
optional: true
'@pnpm/exe.linux-x64@12.4.2':
optional: true
'@pnpm/exe.win32-arm64@12.4.2':
optional: true
'@pnpm/exe.win32-x64@12.4.2':
optional: true
pnpm@12.4.2:
optionalDependencies: optionalDependencies:
'@pnpm/exe.darwin-arm64': 12.3.4 '@pnpm/exe.android-arm64': 12.4.2
'@pnpm/exe.darwin-x64': 12.3.4 '@pnpm/exe.android-x64': 12.4.2
'@pnpm/exe.linux-arm64': 12.3.4 '@pnpm/exe.darwin-arm64': 12.4.2
'@pnpm/exe.linux-arm64-musl': 12.3.4 '@pnpm/exe.darwin-x64': 12.4.2
'@pnpm/exe.linux-x64': 12.3.4 '@pnpm/exe.freebsd-x64': 12.4.2
'@pnpm/exe.linux-x64-musl': 12.3.4 '@pnpm/exe.linux-arm64': 12.4.2
'@pnpm/exe.win32-arm64': 12.3.4 '@pnpm/exe.linux-arm64-musl': 12.4.2
'@pnpm/exe.win32-x64': 12.3.4 '@pnpm/exe.linux-ppc64': 12.4.2
'@pnpm/exe.linux-riscv64': 12.4.2
'@pnpm/exe.linux-s390x': 12.4.2
'@pnpm/exe.linux-x64': 12.4.2
'@pnpm/exe.linux-x64-musl': 12.4.2
'@pnpm/exe.win32-arm64': 12.4.2
'@pnpm/exe.win32-x64': 12.4.2
--- ---
lockfileVersion: '9.0' lockfileVersion: '9.0'
@@ -118,10 +175,10 @@ importers:
devDependencies: devDependencies:
'@trivago/prettier-plugin-sort-imports': '@trivago/prettier-plugin-sort-imports':
specifier: ^6.0.2 specifier: ^6.0.2
version: 6.0.2(prettier@3.9.6) version: 6.0.2(prettier@3.9.7)
prettier: prettier:
specifier: ^3.9.6 specifier: ^3.9.7
version: 3.9.6 version: 3.9.7
packages: packages:
@@ -201,8 +258,8 @@ packages:
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@2.1.4: brace-expansion@2.1.7:
resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==}
debug@4.4.3: debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
@@ -246,8 +303,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.9.6: prettier@3.9.7:
resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} resolution: {integrity: sha512-T3mF5P7HFzZVLDQuCUNtJj6WK1pcpLMweMNUVwGb01bLNkH9AkKxtZvmInw8K6bnn2O3d6/5RHl6zSHiBAD0Og==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
@@ -316,7 +373,7 @@ snapshots:
'@msgpack/msgpack@3.1.3': {} '@msgpack/msgpack@3.1.3': {}
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.6)': '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.7)':
dependencies: dependencies:
'@babel/generator': 7.29.8 '@babel/generator': 7.29.8
'@babel/parser': 7.29.8 '@babel/parser': 7.29.8
@@ -326,13 +383,13 @@ snapshots:
lodash-es: 4.18.1 lodash-es: 4.18.1
minimatch: 9.0.9 minimatch: 9.0.9
parse-imports-exports: 0.2.4 parse-imports-exports: 0.2.4
prettier: 3.9.6 prettier: 3.9.7
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
brace-expansion@2.1.4: brace-expansion@2.1.7:
dependencies: dependencies:
balanced-match: 1.0.2 balanced-match: 1.0.2
@@ -352,7 +409,7 @@ snapshots:
minimatch@9.0.9: minimatch@9.0.9:
dependencies: dependencies:
brace-expansion: 2.1.4 brace-expansion: 2.1.7
ms@2.1.3: {} ms@2.1.3: {}
@@ -364,4 +421,4 @@ snapshots:
picocolors@1.1.1: {} picocolors@1.1.1: {}
prettier@3.9.6: {} prettier@3.9.7: {}
+16 -11
View File
@@ -66,6 +66,7 @@ let settings = {
modbus_timeout: 10000, modbus_timeout: 10000,
developer_mode: true, developer_mode: true,
disable_reset: false, disable_reset: false,
auto_fw_check: false,
email_enabled: false, email_enabled: false,
email_security: 2, email_security: 2,
email_server: 'smtp.gmail.com', email_server: 'smtp.gmail.com',
@@ -209,14 +210,16 @@ switch (emulate_esp) {
// ESP32 4MB // ESP32 4MB
case 'ESP32': case 'ESP32':
system_status.esp_platform = 'ESP32'; system_status.esp_platform = 'ESP32';
system_status.cpu_type = 'ESP32'; system_status.cpu_type = 'ESP32-D0WD-V3';
system_status.arduino_version = 'Tasmota Arduino v2.0.17'; system_status.arduino_version = 'Tasmota Arduino v3.3.8';
system_status.sdk_version = 'v4.4.7'; system_status.sdk_version = '5.5.4.260407';
system_status.psram = false; system_status.psram = true;
system_status.psram_size = 0; system_status.psram_size = 4096;
system_status.free_psram = 0; system_status.free_psram = 4055;
settings.board_profile = 'E32V2'; system_status.model = 'BBQKees Electronics E32V2.2 rev.2.2/2024073';
settings.platform = 'ESP32'; system_status.board = 'E32V2_2';
settings.board_profile = 'E32V2_2';
break; break;
// ESP32 S3 // ESP32 S3
@@ -224,13 +227,15 @@ switch (emulate_esp) {
default: default:
system_status.esp_platform = 'ESP32S3'; system_status.esp_platform = 'ESP32S3';
system_status.cpu_type = 'ESP32-S3'; system_status.cpu_type = 'ESP32-S3';
system_status.arduino_version = 'ESP32 Arduino v2.0.18'; system_status.arduino_version = 'Tasmota Arduino v3.3.8';
system_status.sdk_version = 'v4.4.7'; system_status.sdk_version = '5.5.4.260407';
system_status.psram = true; system_status.psram = true;
system_status.psram_size = 8189; system_status.psram_size = 8189;
system_status.free_psram = 8166; system_status.free_psram = 8166;
system_status.model = 'BBQKees Electronics S32';
system_status.board = 'S32S3';
settings.board_profile = 'S32S3'; settings.board_profile = 'S32S3';
settings.platform = 'ESP32S3';
break; break;
} }
+4 -1
View File
@@ -43,6 +43,9 @@ build_flags =
; ESPAsyncWebServer ; ESPAsyncWebServer
; -D WS_MAX_QUEUED_MESSAGES=0 ; not used, default 8 ; -D WS_MAX_QUEUED_MESSAGES=0 ; not used, default 8
; -D SSE_MAX_QUEUED_MESSAGES=1 ; for log messages, default 32 ; -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_RUNNING_CORE=1 ; default 1
; -D EMSESP_MQTT_STACKSIZE=5120 ; default ; -D EMSESP_MQTT_STACKSIZE=5120 ; default
-D EMSESP_UART_RUNNING_CORE=1 ; default any core -D EMSESP_UART_RUNNING_CORE=1 ; default any core
@@ -90,7 +93,7 @@ board_build.littlefs_version = 2.0
lib_deps = lib_deps =
bblanchon/ArduinoJson @ 7.4.3 bblanchon/ArduinoJson @ 7.4.3
ESP32Async/AsyncTCP @ 3.5.0 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/ReadyMail.git @ 0.4.2
https://github.com/mobizt/ESP_SSLClient.git @ 3.1.3 https://github.com/mobizt/ESP_SSLClient.git @ 3.1.3
; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8 ; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
+2 -1
View File
@@ -1349,4 +1349,5 @@ wifioff
autonegotiation autonegotiation
Mbit Mbit
WROVER 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
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
#
# Builds the release body for a dev build and writes it to stdout.
#
# scripts/release_notes.sh <previous release ref> <version>
#
# The summary is the set of CHANGELOG_LATEST.md lines added since the previous
# dev release, kept under their own headings. The full changelog and the commit
# list follow, both collapsed.
set -euo pipefail
PREV_REF=${1:?usage: release_notes.sh <previous release ref> <version>}
VERSION=${2:?usage: release_notes.sh <previous release ref> <version>}
CHANGELOG=CHANGELOG_LATEST.md
REPO_URL="https://github.com/${GITHUB_REPOSITORY:-emsesp/EMS-ESP32}"
MAX_COMMITS=250
prev_version=$(git show "${PREV_REF}:src/emsesp_version.h" 2>/dev/null |
awk -F'"' '/EMSESP_APP_VERSION/ { print $2 }')
added=$(mktemp)
trap 'rm -f "${added}"' EXIT
git diff -U0 "${PREV_REF}..HEAD" -- "${CHANGELOG}" |
sed -n 's/^+\([^+].*\)/\1/p' >"${added}"
# walk the changelog so the added lines keep the order and headings they have there
summary=$(awk '
NR == FNR { if (NF) wanted[$0] = 1; next }
/^#+ / { sub(/^#+ /, ""); heading = $0; next }
$0 in wanted && !seen[$0]++ {
if (heading != shown) { printf "\n### %s\n\n", heading; shown = heading }
print
}
' "${added}" "${CHANGELOG}")
echo "Development build of EMS-ESP v${VERSION}."
echo
if [ -n "${summary}" ]; then
echo "## Changes since ${prev_version:-the previous dev build}"
echo "${summary}"
echo
elif [ -n "${prev_version}" ]; then
echo "No changelog entries were added since ${prev_version}, see the commits below."
echo
fi
echo "<details>"
echo "<summary>Full v${VERSION%%-*} changelog so far</summary>"
echo
sed '1,/^For more details go to/d' "${CHANGELOG}" | awk 'NF || seen { print; seen = 1 }'
echo
echo "</details>"
echo
count=$(git rev-list --no-merges --count "${PREV_REF}..HEAD")
echo "<details>"
echo "<summary>Commits (${count})</summary>"
echo
git log --no-merges --max-count="${MAX_COMMITS}" \
--pretty=format:"- [\`%h\`](${REPO_URL}/commit/%H) %s (%an)" "${PREV_REF}..HEAD"
echo
if [ "${count}" -gt "${MAX_COMMITS}" ]; then
echo
echo "... and $((count - MAX_COMMITS)) more."
fi
echo
echo "</details>"
echo
echo "[Compare all changes](${REPO_URL}/compare/${PREV_REF}...$(git rev-parse HEAD))"
+2 -2
View File
@@ -8,7 +8,7 @@ set -e
cd interface cd interface
rm -rf node_modules rm -rf node_modules
corepack use pnpm@latest pnpm self-update
pnpm update pnpm update
pnpm install pnpm install
pnpm format pnpm format
@@ -17,7 +17,7 @@ pnpm lint
cd ../mock-api cd ../mock-api
rm -rf node_modules rm -rf node_modules
corepack use pnpm@latest pnpm self-update
pnpm update pnpm update
pnpm install pnpm install
pnpm format pnpm format
+1
View File
@@ -87,6 +87,7 @@ void AnalogSensor::start(const bool factory_settings) {
return StateUpdateResult::CHANGED; // persist the change 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 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"}; return std::vector<std::string>{"system", "users", "devices", "log", "ems", "values", "mqtt", "commands"};
}); });
// //
// System commands // System commands
// //
@@ -294,7 +293,6 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
EMSESP::network_.reconnect(); EMSESP::network_.reconnect();
}); });
commands->add_command(ShellContext::MAIN, commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN, CommandFlags::ADMIN,
string_vector{F_(set), F_(board_profile)}, string_vector{F_(set), F_(board_profile)},
+4
View File
@@ -121,6 +121,10 @@
#define EMSESP_DEFAULT_TELNET_ENABLED true #define EMSESP_DEFAULT_TELNET_ENABLED true
#endif #endif
#ifndef EMSESP_DEFAULT_AUTO_FW_CHECK
#define EMSESP_DEFAULT_AUTO_FW_CHECK false
#endif
#ifndef EMSESP_DEFAULT_MODBUS_ENABLED #ifndef EMSESP_DEFAULT_MODBUS_ENABLED
#define EMSESP_DEFAULT_MODBUS_ENABLED false #define EMSESP_DEFAULT_MODBUS_ENABLED false
#endif #endif
-1
View File
@@ -237,7 +237,6 @@ const char * const DeviceValue::DeviceValueTAG_mqtt[] = {
FL_(tag_src15)[0], // "src15" FL_(tag_src15)[0], // "src15"
FL_(tag_src16)[0] // "src16" FL_(tag_src16)[0] // "src16"
}; };
// count #tags once at compile time // count #tags once at compile time
+42 -12
View File
@@ -1696,7 +1696,6 @@ void EMSESP::scheduled_fetch_values() {
} }
// EMSESP main class // EMSESP main class
EMSESP::EMSESP() EMSESP::EMSESP()
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
: telnet_([this](Stream & stream, const IPAddress & addr, uint16_t port) -> std::shared_ptr<uuid::console::Shell> { : 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"); LOG_ERROR("LittleFS Mount Failed");
return; 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 #endif
// do a quick scan of the filesystem to see if we a settings file in the /config folder // 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 nvs_.begin("ems-esp", false, "nvs"); // fallback to small nvs
} }
#else #else
nvs_.begin("ems-esp", false, "nvs"); nvs_.begin("ems-esp", false, "nvs");
#endif #endif
// set valid GPIOs list based on ESP32 chip/platform type // 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) // loads core system services settings (mqtt, ap, ntp etc)
esp32React.begin(); 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 #ifndef EMSESP_STANDALONE
if (factory_settings) { 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 // make sure OTAdata is updated with core3 (v3.9.0) format
esp_ota_set_boot_partition(esp_ota_get_running_partition()); 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"); 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 // 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() // and reset in System::set_partition_install_date()
if (EMSESP::nvs_.getBool(EMSESP_NVS_BOOT_NEW_FIRMWARE)) { if (EMSESP::nvs_.getBool(EMSESP_NVS_BOOT_NEW_FIRMWARE)) {
LOG_DEBUG("Firmware is a new install"); LOG_DEBUG("Firmware is a new install");
check_internal_sensors = true; // force check for internal sensors
} else { } else {
// check if the firmware has been uploaded via Serial/USB // check if the firmware has been uploaded via Serial/USB
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
@@ -1809,10 +1826,11 @@ void EMSESP::start() {
}; };
#endif #endif
LOG_DEBUG("eFuse device information: %s", system_.getBBQKeesGatewayDetails().isEmpty() ? "not set" : system_.getBBQKeesGatewayDetails().c_str());
webSettingsService.begin(); // load EMS-ESP Application settings 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. // start network services. This will initialise WiFi or Ethernet depending on the settings.
network_.begin(); network_.begin();
@@ -1823,12 +1841,24 @@ void EMSESP::start() {
webCustomEntityService.begin(); // load the custom telegram reads webCustomEntityService.begin(); // load the custom telegram reads
webSchedulerService.begin(); // load the scheduler events 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) { if (!factory_settings) {
// perform any system upgrades
if (system_.check_upgrade()) { if (system_.check_upgrade()) {
LOG_WARNING("System needs a restart to apply new settings. Please wait."); LOG_WARNING("System needs a restart to apply new settings. Please wait.");
system_.system_restart(); 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 // Load our library of known devices into stack mem. Names are stored in Flash memory
@@ -1848,11 +1878,11 @@ void EMSESP::start() {
#endif #endif
} }
mqtt_.start(); // mqtt init mqtt_.start(); // mqtt init
system_.start(); // starts commands, led, adc, button, network (sets hostname), syslog & uart system_.start(); // starts commands, led, adc, button, network (sets hostname), syslog & uart
shower_.start(); // initialize shower timer and shower alert shower_.start(); // initialize shower timer and shower alert
temperaturesensor_.start(factory_settings); // Temperature external sensors temperaturesensor_.start(factory_settings || force_temperature_sensors); // Temperature external sensors
analogsensor_.start(factory_settings); // Analog external sensors analogsensor_.start(factory_settings || force_analog_sensors); // Analog external sensors
// start web services // start web services
LOG_INFO("Starting Web Server"); 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; return dt;
} }
} // namespace emsesp } // 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 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; WiFiClient * basic_client = new WiFiClient;
ESP_SSLClient * ssl_client = new ESP_SSLClient; ESP_SSLClient * ssl_client = new ESP_SSLClient;
if (is_https) { 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->setTimeout(5); // seconds, drives BearSSL only - unused on the plain HTTP path
ssl_client->setClient(basic_client, is_https); // enableSSL = false for plain HTTP 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; const uint16_t port = is_https ? 443 : 80;
if (ssl_client->connect(host.c_str(), port)) { if (ssl_client->connect(host.c_str(), port)) {
bool content_set = false; 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 // the stream goes idle, or the overall budget runs out
const uint32_t started = millis(); const uint32_t started = millis();
uint32_t last_data = started; uint32_t last_data = started;
bool truncated = false;
while (millis() - started < TOTAL_TIMEOUT_MS) { while (millis() - started < TOTAL_TIMEOUT_MS) {
const int avail = ssl_client->available(); const int avail = ssl_client->available();
if (avail > 0) { if (avail > 0) {
if (result.length() >= MAX_RESPONSE_BYTES) {
truncated = true;
break;
}
uint8_t buf[128]; uint8_t buf[128];
const size_t want = (avail < (int)sizeof(buf)) ? (size_t)avail : sizeof(buf); const size_t want = (avail < (int)sizeof(buf)) ? (size_t)avail : sizeof(buf);
const int len = ssl_client->read(buf, want); const int len = ssl_client->read(buf, want);
if (len > 0) { if (len > 0) {
result.append(reinterpret_cast<const char *>(buf), len); result.append(reinterpret_cast<const char *>(buf), len);
last_data = millis(); 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; continue;
} }
if (!ssl_client->connected()) { if (!ssl_client->connected()) {
@@ -121,6 +145,9 @@ int HttpClient::request(std::string url, const std::string & method, const std::
ssl_client->stop(); ssl_client->stop();
const auto received = result.length(); 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 // 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 // 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 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 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 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 } // namespace emsesp
-2
View File
@@ -238,7 +238,6 @@ ModbusMessage Modbus::handleRead(const ModbusMessage & request) {
key, key,
[](const EntityModbusInfo & a, const EntityModbusInfoKey & b) { return a.isLessThan(b); }); [](const EntityModbusInfo & a, const EntityModbusInfoKey & b) { return a.isLessThan(b); });
if (modbusInfo == std::end(modbus_register_mappings) || !modbusInfo->equals(key)) { if (modbusInfo == std::end(modbus_register_mappings) || !modbusInfo->equals(key)) {
// combination of device_type/tag_type/register_offset does not exist // combination of device_type/tag_type/register_offset does not exist
LOG_ERROR("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); LOG_DEBUG("Tag %d, offset %d", tag, register_offset);
// binary search in modbus infos // binary search in modbus infos
auto key = EntityModbusInfoKey(device_type, tag_type, register_offset); auto key = EntityModbusInfoKey(device_type, tag_type, register_offset);
auto modbusInfo = std::lower_bound(std::begin(modbus_register_mappings), 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); pinMode(eth_power_, OUTPUT);
digitalWrite(eth_power_, LOW); 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 #endif
// avoid duplicate registration, so register the lambdas only once across the lifetime of this Network instance // avoid duplicate registration, so register the lambdas only once across the lifetime of this Network instance
@@ -225,15 +230,22 @@ void Network::reconnect() {
begin(); 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. // 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 // 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) // (unless AP provision mode is Never — then stay on WIFI and keep waiting)
NetPhase Network::initialPhase() const { NetPhase Network::initialPhase() const {
#if CONFIG_IDF_TARGET_ESP32 #if CONFIG_IDF_TARGET_ESP32
if (phy_type_ != PHY_type::PHY_TYPE_NONE && !ethClockConflictsPsram()) {
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) {
return NetPhase::ETHERNET; return NetPhase::ETHERNET;
} }
#endif #endif
@@ -605,11 +617,6 @@ void Network::startEthernet() {
return; 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 // 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. // 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); 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_)) { if (ETH.begin(type, eth_phy_addr_, 23, 18, eth_power_, (eth_clock_mode_t)eth_clock_mode_)) {
// LOG_DEBUG("Ethernet module found - initialising"); // LOG_DEBUG("Ethernet module found - initialising");
ethernet_started_ = true; ethernet_started_ = true;
ethernet_init_failed_ = false;
ethernet_connect_pending_ = true; ethernet_connect_pending_ = true;
// check whether DHCP already announced us under lwIP's default hostname, before we correct it // 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_); ETH.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_);
} }
} else { } else {
ethernet_init_failed_ = true;
LOG_ERROR("Failed to start Ethernet"); LOG_ERROR("Failed to start Ethernet");
} }
#endif #endif
#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_ // 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 // Iterates over every esp-netif that exists, prioritizing Ethernet > WiFi > AP
void Network::findNetworks() { void Network::findNetworks() {
+6 -3
View File
@@ -132,9 +132,10 @@ class Network {
return reconnect_count_; return reconnect_count_;
} }
std::string getLocalIP() const; std::string getLocalIP() const;
std::string getMacAddress() const; std::string getMacAddress() const;
uint8_t getStationNum() const; uint8_t getStationNum() const;
const char * ethernet_status() const;
void reconnect(); void reconnect();
@@ -192,6 +193,7 @@ class Network {
const char * disconnectReason(uint8_t code); const char * disconnectReason(uint8_t code);
void stopAP(); void stopAP();
NetPhase initialPhase() const; NetPhase initialPhase() const;
bool ethClockConflictsPsram() const;
#if defined(__clang__) #if defined(__clang__)
#pragma clang diagnostic push #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 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 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_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 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 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 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); auto t = time(nullptr);
// write timestamp always with new version, if clock is not set, this will be updated with ntp // write timestamp always with new version, if clock is not set, this will be updated with ntp
EMSESP::nvs_.putULong(c, t); 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 // 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; system_name_ = settings.system_name;
developer_mode_ = settings.developer_mode; developer_mode_ = settings.developer_mode;
disable_reset_ = settings.disable_reset; disable_reset_ = settings.disable_reset;
auto_fw_check_ = settings.auto_fw_check;
} }
// Starts up core services // Starts up core services
@@ -867,7 +869,7 @@ bool System::loop() {
void System::send_info_mqtt() { void System::send_info_mqtt() {
static uint8_t _connection = 0; static uint8_t _connection = 0;
uint8_t connection = (EMSESP::network_.ethernet_connected() ? 1 : 0) + (EMSESP::network_.wifi_connected() ? 2 : 0) + (ntp_connected_ ? 4 : 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 // check if connection status has changed
if (!Mqtt::connected() || connection == _connection) { if (!Mqtt::connected() || connection == _connection) {
return; return;
@@ -1092,7 +1094,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.println(); shell.println();
shell.println("System:"); shell.println("System:");
shell.printfln(" Version: %s", EMSESP_APP_VERSION); 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 #ifndef EMSESP_STANDALONE
shell.printfln(" Platform: %s (%s)", EMSESP_PLATFORM, ESP.getChipModel()); shell.printfln(" Platform: %s (%s)", EMSESP_PLATFORM, ESP.getChipModel());
shell.printfln(" Model: %s", getBBQKeesGatewayDetails().c_str()); 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", 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_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) / 1024,
heap_caps_get_largest_free_block(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 #endif
shell.printfln(" App used/free: %lu KB / %lu KB", appUsed(), appFree()); shell.printfln(" App used/free: %lu KB / %lu KB", appUsed(), appFree());
uint32_t FSused = LittleFS.usedBytes() / 1024; uint32_t FSused = LittleFS.usedBytes() / 1024;
@@ -1182,6 +1190,55 @@ void System::show_system(uuid::console::Shell & shell) {
installed.c_str(), installed.c_str(),
(strcmp(esp_ota_get_running_partition()->label, partition.first.c_str()) == 0) ? "** active **" : ""); (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();
shell.println("Network:"); shell.println("Network:");
@@ -1234,10 +1291,12 @@ void System::show_system(uuid::console::Shell & shell) {
break; 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()) { if (EMSESP::network_.ethernet_connected()) {
shell.println();
shell.printfln(" Ethernet Status: connected");
shell.printfln(" Ethernet MAC address: %s", ETH.macAddress().c_str()); shell.printfln(" Ethernet MAC address: %s", ETH.macAddress().c_str());
shell.printfln(" Hostname: %s", ETH.getHostname()); 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()); 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["forceHeatingOff"] = settings.boiler_heatingoff;
node["developerMode"] = settings.developer_mode; node["developerMode"] = settings.developer_mode;
node["disableReset"] = settings.disable_reset; node["disableReset"] = settings.disable_reset;
node["autoFwCheck"] = settings.auto_fw_check;
}); });
// Devices - show EMS devices if we have any // 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"); LOG_ERROR("Format command not available in standalone or test mode");
#endif #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 // restart will be handled by the main loop
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED); EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_RESTART_REQUESTED);
return true; return true;
+9
View File
@@ -207,6 +207,14 @@ class System {
disable_reset_ = disable_reset; 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 // Boolean Format API/MQTT
uint8_t bool_format() { uint8_t bool_format() {
return bool_format_; return bool_format_;
@@ -447,6 +455,7 @@ class System {
uint32_t modbus_timeout_; uint32_t modbus_timeout_;
bool developer_mode_; bool developer_mode_;
bool disable_reset_; bool disable_reset_;
bool auto_fw_check_;
uint32_t fstotal_; uint32_t fstotal_;
uint32_t psram_; uint32_t psram_;
uint32_t appused_; uint32_t appused_;
+1 -1
View File
@@ -213,7 +213,7 @@ void TemperatureSensor::loop() {
if (!EMSESP::nvs_.isKey("intTemp")) { if (!EMSESP::nvs_.isKey("intTemp")) {
EMSESP::nvs_.putString("intTemp", s->id()); 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) { EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
auto newSensor = SensorCustomization(); auto newSensor = SensorCustomization();
strlcpy(newSensor.id, s->id(), sizeof(newSensor.id)); 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(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) EMSESP::send_read_request(0xC6, device_id, 0, 21); // read last errorcode on start (not broadcasted)
if (!isHeatPump() && model() != EMSdevice::EMS_DEVICE_FLAG_HIU) { if (!isHeatPump() && model() != EMSdevice::EMS_DEVICE_FLAG_HIU) {
register_telegram_type(0x04, "UBAFactory", true, MAKE_PF_CB(process_UBAFactory), 21); 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)); 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; return true;
} }
// Set the lower dhw temperature 0xEA // Set the lower dhw temperature 0xEA
bool Boiler::set_ww_temp_low(const char * value, const int8_t id) { bool Boiler::set_ww_temp_low(const char * value, const int8_t id) {
int v; 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); // 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 // extension(0x15) -W-> Me(0x0B), EM100SetMessage(0x0935), data: 00 00 64 50 14
// need to be fetched // need to be fetched
void Extension::process_EM100SetMessage(const std::shared_ptr<const Telegram> & telegram) { 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_minT(const char * value, const int8_t id);
bool set_maxT(const char * value, const int8_t id); bool set_maxT(const char * value, const int8_t id);
int16_t headerTemp_; // T0 int16_t headerTemp_; // T0
uint8_t input_; // IO1 uint8_t input_; // IO1
// uint8_t errorState_; // OE1 // 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_amCommandMessage(const std::shared_ptr<const Telegram> & telegram);
void process_amExtraMessage(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_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_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) 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 // telegram 0x361 Differential control
uint8_t diffControl_; // is *10 uint8_t diffControl_; // is *10
// telegram 0x380 // telegram 0x380
uint8_t climateZone_; // climate zone identifier uint8_t climateZone_; // climate zone identifier
uint16_t collector1Area_; // Area of collector field 1 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; return true;
} }
// set pause in hours // set pause in hours
bool Thermostat::set_pause(const char * value, const int8_t id) { bool Thermostat::set_pause(const char * value, const int8_t id) {
auto hc = heating_circuit(id); auto hc = heating_circuit(id);
@@ -3668,7 +3667,6 @@ bool Thermostat::set_reducemode(const char * value, const int8_t id) {
return false; return false;
} }
// sets the thermostat reducemode for RC35 vacations // sets the thermostat reducemode for RC35 vacations
bool Thermostat::set_vacreducemode(const char * value, const int8_t id) { bool Thermostat::set_vacreducemode(const char * value, const int8_t id) {
auto hc = heating_circuit(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'}; char out[sizeof(dhw->wwSwitchTime_)] = {'\0'};
if (set_switchtime(value, 0x38, out, sizeof(out))) { if (set_switchtime(value, 0x38, out, sizeof(out))) {
if (strlen(out)) { if (strlen(out)) {
has_update(dhw->wwSwitchTime_, out, sizeof(dhw->wwSwitchTime_)); 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 // has_bitupdate(telegram, wwChargeType_, 10, 0); // 0 = charge pump, 0xff = 3-way valve
} }
// 0x1E, only16 bit temperature // 0x1E, only16 bit temperature
// Mixer(0x41) -> Boiler(0x08), HydrTemp(0x1E), data: 01 D8 // Mixer(0x41) -> Boiler(0x08), HydrTemp(0x1E), data: 01 D8
void Water::process_IPMHydrTemp(const std::shared_ptr<const Telegram> & telegram) { 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; return false;
} }
bool Water::set_wwFlowTempOffset(const char * value, const int8_t id) { bool Water::set_wwFlowTempOffset(const char * value, const int8_t id) {
int n; int n;
if (!Helpers::value2number(value, n)) { if (!Helpers::value2number(value, n)) {
-2
View File
@@ -81,7 +81,6 @@ class Water : public EMSdevice {
int8_t wwHystOff_; // Hyst off (default -1) int8_t wwHystOff_; // Hyst off (default -1)
uint8_t wwFlowTempOffset_; // default 40 uint8_t wwFlowTempOffset_; // default 40
void process_SM100wwTemperature(const std::shared_ptr<const Telegram> & telegram); void process_SM100wwTemperature(const std::shared_ptr<const Telegram> & telegram);
void process_SM100wwStatus(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); 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_IPMHydrTemp(const std::shared_ptr<const Telegram> & telegram);
void process_IPMParameterWW(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_wwSelTemp(const char * value, const int8_t id);
bool set_wwMaxTemp(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); 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; return true;
} }
// //
// the tests take a lot of memory when built for the ESP32 // 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 // 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); EMSESP::webAPIService.webAPIService(&request);
Serial.println(); Serial.println();
request.url("/api/thermostat/hc2/entities"); request.url("/api/thermostat/hc2/entities");
EMSESP::webAPIService.webAPIService(&request); EMSESP::webAPIService.webAPIService(&request);
Serial.println(); Serial.println();
-1
View File
@@ -16,7 +16,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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 * 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["id"] = (const char *)sensor.id; // ID of dallas temperature sensor chip
sensorJson["name"] = (const char *)sensor.name; // n sensorJson["name"] = (const char *)sensor.name; // n
sensorJson["offset"] = sensor.offset; // o 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 // Analog Sensor customization
@@ -70,7 +70,7 @@ void WebCustomization::read(WebCustomization & customizations, JsonObject root)
sensorJson["factor"] = sensor.factor; // f sensorJson["factor"] = sensor.factor; // f
sensorJson["uom"] = sensor.uom; // u sensorJson["uom"] = sensor.uom; // u
sensorJson["type"] = sensor.type; // t 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) // Masked entities customization and custom device name (optional)
+1 -1
View File
@@ -94,7 +94,7 @@ void WebLogService::show(Shell & shell) {
} }
shell.println(); shell.println();
shell.printfln("Recent Log:"); shell.printfln("Last %d messages:", log_messages_.size());
shell.println(); shell.println();
for (const auto & message : log_messages_) { 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["modbus_timeout"] = settings.modbus_timeout;
root["developer_mode"] = settings.developer_mode; root["developer_mode"] = settings.developer_mode;
root["disable_reset"] = settings.disable_reset; root["disable_reset"] = settings.disable_reset;
root["auto_fw_check"] = settings.auto_fw_check;
root["email_enabled"] = settings.email_enabled; root["email_enabled"] = settings.email_enabled;
root["email_security"] = settings.email_security; root["email_security"] = settings.email_security;
root["email_server"] = settings.email_server; root["email_server"] = settings.email_server;
@@ -310,6 +311,12 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
settings.disable_reset = root["disable_reset"]; settings.disable_reset = root["disable_reset"];
EMSESP::system_.disable_reset(settings.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; settings.bool_dashboard = root["bool_dashboard"] | EMSESP_DEFAULT_BOOL_FORMAT;
EMSESP::system_.bool_dashboard(settings.bool_dashboard); 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); String bbq_board = EMSESP::system_.getBBQKeesGatewayDetails(FUSE_VALUE::BOARD);
if (!bbq_board.isEmpty() && settings.board_profile != "CUSTOM") { if (!bbq_board.isEmpty() && settings.board_profile != "CUSTOM") {
#if defined(EMSESP_DEBUG) #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 #endif
settings.board_profile = bbq_board; settings.board_profile = bbq_board;
} }
+1
View File
@@ -122,6 +122,7 @@ class WebSettings {
bool developer_mode; // developer mode bool developer_mode; // developer mode
bool disable_reset; // disable reset 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 void read(WebSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, WebSettings & settings); 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) // no successful fetch yet (no network, fetch pending, or parse error)
return; return;
} }
@@ -364,8 +377,8 @@ void WebStatusService::getVersions(JsonObject root) {
out["upgradeable"] = info.upgradeable; out["upgradeable"] = info.upgradeable;
}; };
add_section("stable", versions_stable_); add_section("stable", stable);
add_section("dev", versions_dev_); add_section("dev", dev);
#else #else
// standalone/test build: provide deterministic dummy data // standalone/test build: provide deterministic dummy data
JsonObject stable_out = root["stable"].to<JsonObject>(); 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 // 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) // (DHCP completion, default-netif assignment and DNS server propagation through lwip)
void WebStatusService::schedule_versions_refresh() { 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) { if (next == 0) {
next = 1; // 0 is the "idle" sentinel — never let the wrap land there next = 1; // 0 is the "idle" sentinel — never let the wrap land there
} }
versions_next_fetch_ms_ = next; versions_next_fetch_ms_ = next;
} }
// periodic refresh (1 hour) of the cached versions.json // refresh of the cached versions.json, at most once a day
// runs on the main loop task, which has a much bigger stack than AsyncTCP needed for https
void WebStatusService::loop() { void WebStatusService::loop() {
#ifndef EMSESP_STANDALONE #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 // need a network
if (!EMSESP::network_.network_connected()) { if (!EMSESP::network_.network_connected()) {
return; return;
@@ -409,8 +428,20 @@ void WebStatusService::loop() {
return; return;
} }
bool ok = refresh_versions_cache(); versions_fetch_requested_ = false;
uint32_t next = uuid::get_uptime() + (ok ? VERSIONS_REFRESH_INTERVAL_MS : VERSIONS_RETRY_INTERVAL_MS);
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) { if (next == 0) {
next = 1; next = 1;
} }
@@ -451,16 +482,21 @@ bool WebStatusService::refresh_versions_cache() {
out.upgradeable = !out.version.empty() && FirmwareVersion(out.version) > current_version; out.upgradeable = !out.version.empty() && FirmwareVersion(out.version) > current_version;
}; };
read_section("stable", versions_stable_); // parse into locals first, then publish in one go so the AsyncTCP task never sees a half-written cache
read_section("dev", versions_dev_); 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) #if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Fetched from %s: stable=%s, dev=%s, current=%s", 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());
VERSIONS_URL,
versions_stable_.version.c_str(),
versions_dev_.version.c_str(),
current_version_s.c_str());
#endif #endif
return true; return true;
#endif #endif
@@ -468,11 +504,12 @@ bool WebStatusService::refresh_versions_cache() {
// returns if current dev/stable is upgradeable // returns if current dev/stable is upgradeable
bool WebStatusService::current_upgradeable() const { 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_) { if (!versions_cache_valid_) {
return false; 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; return is_dev ? versions_dev_.upgradeable : versions_stable_.upgradeable;
} }
+21 -3
View File
@@ -7,6 +7,9 @@
#include "../core/firmwareVersion.h" #include "../core/firmwareVersion.h"
#include "../emsesp_version.h" #include "../emsesp_version.h"
#include <atomic>
#include <mutex>
namespace emsesp { namespace emsesp {
class WebStatusService { class WebStatusService {
@@ -21,6 +24,7 @@ class WebStatusService {
// called from EMSESP::loop() to refresh the cached versions.json from emsesp.org // 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 // 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(); void loop();
// true once we've had at least one successful versions.json fetch // true once we've had at least one successful versions.json fetch
@@ -67,14 +71,28 @@ class WebStatusService {
VersionInfo versions_dev_; VersionInfo versions_dev_;
bool versions_cache_valid_ = false; // true once we've had at least one successful fetch 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 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 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 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_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 failure 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 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 } // namespace emsesp
+1 -2
View File
@@ -4,8 +4,7 @@
# Command line test for the API # Command line test for the API
# #
# emsesp_url="http://ems-esp.local" emsesp_url="http://ems-esp.local"
emsesp_url="http://ems-espT.local"
# get the token from the Security page. This is the token for the admin user, unless changed it'll always be the same # 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" 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
}
]
}
]
}