diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md
index f212913d8..21d1c09f0 100644
--- a/CHANGELOG_LATEST.md
+++ b/CHANGELOG_LATEST.md
@@ -18,6 +18,7 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- 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
+- Automatically check and convert older 3.8.x filesystem to the new 3.9.0 format
## Fixed
diff --git a/interface/package.json b/interface/package.json
index e174c0392..d20b5736e 100644
--- a/interface/package.json
+++ b/interface/package.json
@@ -42,11 +42,11 @@
"@eslint/js": "^10.0.1",
"@preact/preset-vite": "^2.10.6",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
- "@types/node": "^26.6.1",
+ "@types/node": "^26.6.2",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"concurrently": "^10.0.5",
- "eslint": "^10.10.0",
+ "eslint": "^10.11.0",
"eslint-config-prettier": "^10.1.8",
"etag": "^1.8.1",
"mime-types": "^3.0.2",
diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml
index 394afc10a..5bd89b220 100644
--- a/interface/pnpm-lock.yaml
+++ b/interface/pnpm-lock.yaml
@@ -211,16 +211,16 @@ importers:
devDependencies:
'@eslint/js':
specifier: ^10.0.1
- version: 10.0.1(eslint@10.10.0(supports-color@10.2.2))
+ version: 10.0.1(eslint@10.11.0(supports-color@10.2.2))
'@preact/preset-vite':
specifier: ^2.10.6
- version: 2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))
+ version: 2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))
'@trivago/prettier-plugin-sort-imports':
specifier: ^6.0.2
version: 6.0.2(prettier@3.9.8)(supports-color@10.2.2)
'@types/node':
- specifier: ^26.6.1
- version: 26.6.1
+ specifier: ^26.6.2
+ version: 26.6.2
'@types/react':
specifier: ^19.3.0
version: 19.3.0
@@ -231,11 +231,11 @@ importers:
specifier: ^10.0.5
version: 10.0.5
eslint:
- specifier: ^10.10.0
- version: 10.10.0(supports-color@10.2.2)
+ specifier: ^10.11.0
+ version: 10.11.0(supports-color@10.2.2)
eslint-config-prettier:
specifier: ^10.1.8
- version: 10.1.8(eslint@10.10.0(supports-color@10.2.2))
+ version: 10.1.8(eslint@10.11.0(supports-color@10.2.2))
etag:
specifier: ^1.8.1
version: 1.8.1
@@ -256,13 +256,13 @@ importers:
version: 6.0.3
typescript-eslint:
specifier: ^8.70.0
- version: 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ version: 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
vite:
specifier: ^8.3.0
- version: 8.3.0(@types/node@26.6.1)(terser@5.51.2)
+ version: 8.3.0(@types/node@26.6.2)(terser@5.51.2)
vite-plugin-imagemin:
specifier: ^0.6.1
- version: 0.6.1(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))
+ version: 0.6.1(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))
packages:
@@ -332,8 +332,8 @@ packages:
resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==}
engines: {node: '>=6.9.0'}
- '@babel/parser@7.29.8':
- resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==}
+ '@babel/parser@7.29.9':
+ resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -843,8 +843,8 @@ packages:
resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==}
deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed.
- '@types/node@26.6.1':
- resolution: {integrity: sha512-VqGJBMCtdhqkBUCcBLvywI0NJ+KLuVzgNnlBUNFOQjqVxzo2lxLUNg1DSey8+u2u6ktswSAxg+s68QLzWHNOuA==}
+ '@types/node@26.6.2':
+ resolution: {integrity: sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -1339,8 +1339,8 @@ packages:
duplexer3@0.1.5:
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
- electron-to-chromium@1.5.431:
- resolution: {integrity: sha512-AAVihz2YwJeOdAynX8MUtqpvjY0gaiARp/7+r4kwqgzqXMrfG1qDN7vwT80uxAoOQRPcZH8t73EI+CA/1OJN8A==}
+ electron-to-chromium@1.5.433:
+ resolution: {integrity: sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==}
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -1525,8 +1525,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- eslint@10.10.0:
- resolution: {integrity: sha512-NPXn6r5zl4uET1DAVPaOwzX3rut4c0wcmw3dWJAfOsTM5+TogXo0DDjz8pwm/hL8cyVNpHqeK4JpN0NjnyFFNw==}
+ eslint@10.11.0:
+ resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
@@ -3105,8 +3105,8 @@ packages:
yaml:
optional: true
- which-typed-array@1.1.23:
- resolution: {integrity: sha512-JMh8aK+1B/0bk/YNupICmH5MgCq6yNLKYQUfsZtSDLLCCmxUkHjUY+oOlIa90lO7lHN1woAfpenLdlKpXy9o+A==}
+ which-typed-array@1.1.24:
+ resolution: {integrity: sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==}
engines: {node: '>= 0.4'}
which@1.3.1:
@@ -3197,7 +3197,7 @@ snapshots:
'@babel/helper-compilation-targets': 7.29.7
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
'@babel/helpers': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/template': 7.29.7
'@babel/traverse': 7.29.8(supports-color@10.2.2)
'@babel/types': 7.29.8
@@ -3212,7 +3212,7 @@ snapshots:
'@babel/generator@7.29.8':
dependencies:
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/types': 7.29.8
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
@@ -3261,7 +3261,7 @@ snapshots:
'@babel/template': 7.29.7
'@babel/types': 7.29.8
- '@babel/parser@7.29.8':
+ '@babel/parser@7.29.9':
dependencies:
'@babel/types': 7.29.8
@@ -3293,7 +3293,7 @@ snapshots:
'@babel/template@7.29.7':
dependencies:
'@babel/code-frame': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/types': 7.29.8
'@babel/traverse@7.29.8(supports-color@10.2.2)':
@@ -3301,7 +3301,7 @@ snapshots:
'@babel/code-frame': 7.29.7
'@babel/generator': 7.29.8
'@babel/helper-globals': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/template': 7.29.7
'@babel/types': 7.29.8
debug: 4.4.3(supports-color@10.2.2)
@@ -3411,9 +3411,9 @@ snapshots:
'@esbuild/linux-loong64@0.14.54':
optional: true
- '@eslint-community/eslint-utils@4.10.1(eslint@10.10.0(supports-color@10.2.2))':
+ '@eslint-community/eslint-utils@4.10.1(eslint@10.11.0(supports-color@10.2.2))':
dependencies:
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
@@ -3434,9 +3434,9 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
- '@eslint/js@10.0.1(eslint@10.10.0(supports-color@10.2.2))':
+ '@eslint/js@10.0.1(eslint@10.11.0(supports-color@10.2.2))':
optionalDependencies:
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
'@eslint/object-schema@3.0.5': {}
@@ -3596,19 +3596,19 @@ snapshots:
'@popperjs/core@2.11.8': {}
- '@preact/preset-vite@2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))':
+ '@preact/preset-vite@2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))':
dependencies:
'@babel/core': 7.29.7(supports-color@10.2.2)
'@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
'@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
- '@prefresh/vite': 2.4.12(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))
+ '@prefresh/vite': 2.4.12(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))
'@rollup/pluginutils': 5.4.0
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.7(supports-color@10.2.2))
debug: 4.4.3(supports-color@10.2.2)
magic-string: 0.30.21
picocolors: 1.1.1
- vite: 8.3.0(@types/node@26.6.1)(terser@5.51.2)
- vite-prerender-plugin: 0.5.13(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))
+ vite: 8.3.0(@types/node@26.6.2)(terser@5.51.2)
+ vite-prerender-plugin: 0.5.13(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))
zimmerframe: 1.1.5
transitivePeerDependencies:
- preact
@@ -3625,7 +3625,7 @@ snapshots:
'@prefresh/utils@1.2.1': {}
- '@prefresh/vite@2.4.12(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))':
+ '@prefresh/vite@2.4.12(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))':
dependencies:
'@babel/core': 7.29.7(supports-color@10.2.2)
'@prefresh/babel-plugin': 0.5.4(@babel/core@7.29.7(supports-color@10.2.2))
@@ -3633,7 +3633,7 @@ snapshots:
'@prefresh/utils': 1.2.1
'@rollup/pluginutils': 4.2.1
preact: 10.29.8
- vite: 8.3.0(@types/node@26.6.1)(terser@5.51.2)
+ vite: 8.3.0(@types/node@26.6.2)(terser@5.51.2)
transitivePeerDependencies:
- supports-color
@@ -3711,7 +3711,7 @@ snapshots:
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.8)(supports-color@10.2.2)':
dependencies:
'@babel/generator': 7.29.8
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/traverse': 7.29.8(supports-color@10.2.2)
'@babel/types': 7.29.8
javascript-natural-sort: 0.7.1
@@ -3729,7 +3729,7 @@ snapshots:
'@types/glob@7.2.0':
dependencies:
'@types/minimatch': 6.0.0
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
'@types/imagemin-gifsicle@7.0.4':
dependencies:
@@ -3758,19 +3758,19 @@ snapshots:
'@types/imagemin@7.0.1':
dependencies:
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
'@types/json-schema@7.0.15': {}
'@types/keyv@3.1.4':
dependencies:
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
'@types/minimatch@6.0.0':
dependencies:
minimatch: 10.2.6
- '@types/node@26.6.1':
+ '@types/node@26.6.2':
dependencies:
undici-types: 8.9.0
@@ -3792,21 +3792,21 @@ snapshots:
'@types/responselike@1.0.3':
dependencies:
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
'@types/svgo@2.6.4':
dependencies:
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
- '@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
+ '@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.70.0
- '@typescript-eslint/type-utils': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
- '@typescript-eslint/utils': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/type-utils': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.70.0
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
ignore: 7.0.9
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@6.0.3)
@@ -3814,14 +3814,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
+ '@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.70.0
'@typescript-eslint/types': 8.70.0
'@typescript-eslint/typescript-estree': 8.70.0(supports-color@10.2.2)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.70.0
debug: 4.4.3(supports-color@10.2.2)
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -3844,13 +3844,13 @@ snapshots:
dependencies:
typescript: 6.0.3
- '@typescript-eslint/type-utils@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
+ '@typescript-eslint/type-utils@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/types': 8.70.0
'@typescript-eslint/typescript-estree': 8.70.0(supports-color@10.2.2)(typescript@6.0.3)
- '@typescript-eslint/utils': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
debug: 4.4.3(supports-color@10.2.2)
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
@@ -3873,13 +3873,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
+ '@typescript-eslint/utils@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.10.1(eslint@10.10.0(supports-color@10.2.2))
+ '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(supports-color@10.2.2))
'@typescript-eslint/scope-manager': 8.70.0
'@typescript-eslint/types': 8.70.0
'@typescript-eslint/typescript-estree': 8.70.0(supports-color@10.2.2)(typescript@6.0.3)
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -4012,7 +4012,7 @@ snapshots:
dependencies:
baseline-browser-mapping: 2.11.25
caniuse-lite: 1.0.30001810
- electron-to-chromium: 1.5.431
+ electron-to-chromium: 1.5.433
node-releases: 2.0.56
update-browserslist-db: 1.3.3(browserslist@4.29.0)
@@ -4377,7 +4377,7 @@ snapshots:
duplexer3@0.1.5: {}
- electron-to-chromium@1.5.431: {}
+ electron-to-chromium@1.5.433: {}
emoji-regex@10.6.0: {}
@@ -4491,9 +4491,9 @@ snapshots:
escape-string-regexp@4.0.0: {}
- eslint-config-prettier@10.1.8(eslint@10.10.0(supports-color@10.2.2)):
+ eslint-config-prettier@10.1.8(eslint@10.11.0(supports-color@10.2.2)):
dependencies:
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
eslint-scope@9.1.2:
dependencies:
@@ -4506,9 +4506,9 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
- eslint@10.10.0(supports-color@10.2.2):
+ eslint@10.11.0(supports-color@10.2.2):
dependencies:
- '@eslint-community/eslint-utils': 4.10.1(eslint@10.10.0(supports-color@10.2.2))
+ '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(supports-color@10.2.2))
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.5(supports-color@10.2.2)
'@eslint/config-helpers': 0.7.0
@@ -5058,7 +5058,7 @@ snapshots:
is-typed-array@1.1.15:
dependencies:
- which-typed-array: 1.1.23
+ which-typed-array: 1.1.24
is-utf8@0.2.1: {}
@@ -5940,13 +5940,13 @@ snapshots:
dependencies:
typescript: 6.0.3
- typescript-eslint@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3):
+ typescript-eslint@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
- '@typescript-eslint/parser': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/eslint-plugin': 8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
'@typescript-eslint/typescript-estree': 8.70.0(supports-color@10.2.2)(typescript@6.0.3)
- '@typescript-eslint/utils': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
- eslint: 10.10.0(supports-color@10.2.2)
+ '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ eslint: 10.11.0(supports-color@10.2.2)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -5991,7 +5991,7 @@ snapshots:
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
- vite-plugin-imagemin@0.6.1(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2)):
+ vite-plugin-imagemin@0.6.1(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2)):
dependencies:
'@types/imagemin': 7.0.1
'@types/imagemin-gifsicle': 7.0.4
@@ -6016,11 +6016,11 @@ snapshots:
imagemin-webp: 6.1.0
jpegtran-bin: 6.0.1
pathe: 0.2.0
- vite: 8.3.0(@types/node@26.6.1)(terser@5.51.2)
+ vite: 8.3.0(@types/node@26.6.2)(terser@5.51.2)
transitivePeerDependencies:
- supports-color
- vite-prerender-plugin@0.5.13(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2)):
+ vite-prerender-plugin@0.5.13(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2)):
dependencies:
kolorist: 1.8.0
magic-string: 0.30.21
@@ -6028,9 +6028,9 @@ snapshots:
simple-code-frame: 1.3.0
source-map: 0.7.6
stack-trace: 1.0.0
- vite: 8.3.0(@types/node@26.6.1)(terser@5.51.2)
+ vite: 8.3.0(@types/node@26.6.2)(terser@5.51.2)
- vite@8.3.0(@types/node@26.6.1)(terser@5.51.2):
+ vite@8.3.0(@types/node@26.6.2)(terser@5.51.2):
dependencies:
lightningcss: 1.33.0
picomatch: 4.0.7
@@ -6038,11 +6038,11 @@ snapshots:
rolldown: 1.2.9
tinyglobby: 0.2.17
optionalDependencies:
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
fsevents: 2.3.3
terser: 5.51.2
- which-typed-array@1.1.23:
+ which-typed-array@1.1.24:
dependencies:
available-typed-arrays: 1.0.7
call-bind: 1.0.9
diff --git a/interface/src/app/settings/Version.tsx b/interface/src/app/settings/Version.tsx
index f54203f9c..e45a1c46c 100644
--- a/interface/src/app/settings/Version.tsx
+++ b/interface/src/app/settings/Version.tsx
@@ -297,16 +297,12 @@ const InstallDialog = memo(
)}
{upgradeImportantMessageType === 2 && LL.UPGRADE_IMPORTANT_MESSAGES_2()}
- {upgradeImportantMessageType === 1 && (
- <>
- {LL.UPGRADE_IMPORTANT_MESSAGES_1()}
-
-
- {LL.DOWNLOAD_SYSTEM_BACKUP()}
-
-
- >
- )}
+ {upgradeImportantMessageType === 1 && LL.UPGRADE_IMPORTANT_MESSAGES_1()}
+
+
+ {LL.DOWNLOAD_SYSTEM_BACKUP()}
+
+
=6.9.0'}
- '@babel/parser@7.29.8':
- resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==}
+ '@babel/parser@7.29.9':
+ resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -318,7 +318,7 @@ snapshots:
'@babel/generator@7.29.8':
dependencies:
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/types': 7.29.8
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
@@ -330,14 +330,14 @@ snapshots:
'@babel/helper-validator-identifier@7.29.7': {}
- '@babel/parser@7.29.8':
+ '@babel/parser@7.29.9':
dependencies:
'@babel/types': 7.29.8
'@babel/template@7.29.7':
dependencies:
'@babel/code-frame': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/types': 7.29.8
'@babel/traverse@7.29.8':
@@ -345,7 +345,7 @@ snapshots:
'@babel/code-frame': 7.29.7
'@babel/generator': 7.29.8
'@babel/helper-globals': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/template': 7.29.7
'@babel/types': 7.29.8
debug: 4.4.3
@@ -376,7 +376,7 @@ snapshots:
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.8)':
dependencies:
'@babel/generator': 7.29.8
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/traverse': 7.29.8
'@babel/types': 7.29.8
javascript-natural-sort: 0.7.1
diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts
index b561e486d..071b070f2 100644
--- a/mock-api/restServer.ts
+++ b/mock-api/restServer.ts
@@ -98,13 +98,13 @@ let system_status = {
wifi_rssi: -41,
esp_platform: 'ESP32',
build_flags: 'DEMO',
- cpu_type: 'ESP32',
+ cpu_type: 'ESP32-D0WD-V3',
cpu_rev: 0,
cpu_cores: 2,
cpu_freq_mhz: 240,
max_alloc_heap: 191,
- arduino_version: 'ESP32 Arduino v2.0.17',
- sdk_version: 'v4.4.7',
+ arduino_version: 'Tasmota Arduino v3.3.8',
+ sdk_version: '5.5.4.260407',
partition: 'app0',
flash_chip_size: 16384,
flash_chip_speed: 80000000,
@@ -127,13 +127,13 @@ let system_status = {
},
{
partition: 'app1',
- version: '3.8.1-dev.40',
+ version: '3.8.3',
install_date: '2025-03-01T13:29:13.999Z',
size: 4672
},
{
partition: 'factory',
- version: '3.8.1-dev.39',
+ version: '3.8.4',
install_date: '2025-03-01T13:29:13.999Z',
size: 4672
}
@@ -141,24 +141,22 @@ let system_status = {
// partitions: [],
developer_mode: settings.developer_mode,
disable_reset: settings.disable_reset,
- model: '',
- board: '',
- // model: 'BBQKees Electronics EMS Gateway E32 V2 (E32 V2.0 P3/2024011)',
- // board: 'E32V2',
- // status: 0,
- status: 3
+ model: 'BBQKees Electronics E32V2.2 rev.2.2/2024074',
+ board: 'E32V2_2',
+ status: 0
+ // status: 3
};
-// Test Versioning
+// Test the versioning
let DEV_VERSION_IS_UPGRADEABLE: boolean;
let STABLE_VERSION_IS_UPGRADEABLE: boolean;
let THIS_VERSION: string;
-let LATEST_STABLE_VERSION = '3.8.3';
+let LATEST_STABLE_VERSION = '3.8.4';
let LATEST_DEV_VERSION = '3.9.0-dev.1';
// scenarios for testing versioning
-// let version_test = 0; // on latest stable, or switch to dev
-let version_test = 1; // on latest dev, or switch back to stable
+let version_test = 0; // on latest stable, or switch to dev
+// let version_test = 1; // on latest dev, or switch back to stable
// let version_test = 2; // upgrade an older stable to latest stable or switch to latest dev
// let version_test = 3; // upgrade dev to latest, or switch to stable
// let version_test = 4; // downgrade to an older dev, or switch back to stable
@@ -446,25 +444,24 @@ function executeCommand(name: string) {
}
// called by Action endpoint upgradeImportantMessages
+// 0 is do nothing
+// 1 is a special upgrade and needs an export->factory reset->import
+// 2 is a major upgrade
function upgradeImportantMessages(version: string) {
- // 0 is do nothing
- // 1 means 3.9 and factory reset required
- // 2 means a major version upgrade
-
let upgradeImportantMessageType_n = 0;
- // check file extensions
+ // check file extensions first
if (version.endsWith('.md5') || version.endsWith('.json')) {
upgradeImportantMessageType_n = 0; // digest / backup restore: no upgrade warning
} else if (version.endsWith('.bin')) {
// extract the version number from the filename and if its going from 3.8.x to 3.9.x, then set upgradeImportantMessageType_n to 1
const versionNumber = version.split('.');
if (versionNumber[0] === '3' && versionNumber[1] === '8') {
- upgradeImportantMessageType_n = 1; // make it 1, factory reset required
+ upgradeImportantMessageType_n = 2; // major upgrade
}
} else {
- // this is a version string like "3.9.0"
- upgradeImportantMessageType_n = 1; // make it 1, for testing, meaning a factory reset is required
+ // the parameter is a version string like "3.9.0"
+ upgradeImportantMessageType_n = 2; // major upgrade
}
console.log(
diff --git a/pio_local.ini_example b/pio_local.ini_example
index 29a40d240..c1bd4f59d 100644
--- a/pio_local.ini_example
+++ b/pio_local.ini_example
@@ -41,7 +41,8 @@ custom_password = admin
; ** upload settings **
; for direct USB, which is the default you can set an optional port for example:
; upload_port = /dev/ttyUSB0 or COM3
-;
+; upload_speed = 921600
+
; for direct via HTTP (OTA) set:
; upload_protocol = custom
; custom_emsesp_ip = or ems-esp.local
diff --git a/project-words.txt b/project-words.txt
index 703d511a7..0e8f7a52e 100644
--- a/project-words.txt
+++ b/project-words.txt
@@ -6,9 +6,9 @@ abluftqualität
abluftqualitätsfühler
abrt
absburnpow
-ACAC
-ACAH
-ACAO
+acac
+acah
+acao
accomodate
aceotrope
activehigh
@@ -48,6 +48,7 @@ appused
apssid
apsta
arduhal
+arduinoespressif
arduinojson
arduinoota
arjan
@@ -70,6 +71,7 @@ ausreichend
authmode
autodst
automode
+autonegotiation
autoscroll
auxelecheatnrgcons
auxelecheatnrgconsheating
@@ -139,6 +141,7 @@ bytetime
calinttemp
catagory
cbuf
+ccrc
cerapur
cfamily
changeloglevel
@@ -289,6 +292,7 @@ dewoffset
dewpoint
dewtemp
dewtemperature
+dhcpc
dhwmintemp
dhwprio
dhwvalve
@@ -362,6 +366,7 @@ entitypath
enumupdate
enviline
equipt
+eraseap
erct
erdwärmetauscher
errordisp
@@ -458,6 +463,7 @@ gpoc
greenstar
grokhotkov
haclimate
+handshaked
hardwarestatus
harwerth
headersize
@@ -501,7 +507,7 @@ heatsource
heatsources
heatstarts
heattemp
-heattransfer
+heattransfer
heattransfersystem
heatup
heatvalve
@@ -578,6 +584,7 @@ iconua
idedata
iele
ignwork
+iile
iiwi
ijoy
imlhd
@@ -615,6 +622,7 @@ itty
ivtclock
jtag
junkersclock
+juststopped
kamin
karlesky
keelbypass
@@ -706,6 +714,7 @@ mbap
mbar
mbed
mbedtsl
+mbit
mdio
mdns
melis
@@ -744,7 +753,7 @@ mlongcalls
mmio
mmode
mmplus
-mockServer
+mockserver
modbus
modee
modetype
@@ -762,6 +771,7 @@ msgstr
msys
mtext
mult
+mwdt
mysys
nachheiz
nachheizreg
@@ -780,6 +790,7 @@ neopixel
netcmp
netflowtemp
netif
+netifs
newdatetime
newnow
newpcb
@@ -836,6 +847,7 @@ oilpreheat
oldoffset
olimex
olimexpoe
+olimexpoew
omea
onchange
onetimekey
@@ -843,6 +855,7 @@ onewire
ooops
operatingstate
opmode
+optimisations
optipng
osrc
outdoorhold
@@ -877,7 +890,7 @@ poolsettemp
poolshunt
poolshuntstatus
poolstarts
-pooltemp
+pooltemp
portx
powerreduction
powervalues
@@ -944,7 +957,7 @@ rawstr
rcplus
readback
readelf
-READYCLIENT
+readyclient
readymail
recp
recved
@@ -1000,6 +1013,7 @@ roomsettings
roomtemp
roomtempdiff
roomtemperature
+roomtempoffset
roomthermostat
roomthermostats
rrts
@@ -1029,6 +1043,7 @@ seltemp
seltemplow
seltempoff
seltempsingle
+sendmail
sendtype
sensordata
sensorfails
@@ -1037,6 +1052,7 @@ sensorname
sensorreads
sensortype
seperator
+serialises
serverclass
servicecode
servicecodenumber
@@ -1087,11 +1103,13 @@ soll
speicher
spidqs
spiio
+spiram
squaremeters
srts
staconnected
stadisconnected
staipassigned
+startm
startshp
starttemp
starttemperature
@@ -1112,6 +1130,7 @@ substract
summermode
summersetmode
summertemp
+sumr
supchan
suprapur
svenska
@@ -1161,6 +1180,7 @@ tempparmode
temprature
tempsensor
tempstatus
+testemail
testtemperature
thermaldisinfect
thermostate
@@ -1271,6 +1291,7 @@ werror
wextra
whenmodeoff
wificlient
+wifioff
wifireconnects
wifistrength
winsock
@@ -1281,6 +1302,7 @@ workm
workmin
worktime
wouldblock
+wrover
wswitch
wtype
wunused
@@ -1330,24 +1352,3 @@ zyxwvutsrqponmlkjihgfedcba
öffnen
česky
živanović
-MWDT
-juststopped
-handshaked
-startm
-netifs
-testemail
-sendmail
-serialises
-SPIRAM
-optimisations
-IILE
-Sumr
-eraseap
-roomtempoffset
-dhcpc
-wifioff
-autonegotiation
-Mbit
-WROVER
-OLIMEXPOEW
-arduinoespressif
\ No newline at end of file
diff --git a/src/core/console.cpp b/src/core/console.cpp
index c6ff9dcb5..60ca00a20 100644
--- a/src/core/console.cpp
+++ b/src/core/console.cpp
@@ -319,7 +319,7 @@ static void setup_commands(std::shared_ptr const & commands) {
settings.eth_clock_mode = data[8];
return StateUpdateResult::CHANGED;
});
- shell.printfln("Loaded board profile %s. Restarting...", board_profile.c_str());
+ shell.printfln("Set board profile to %s. Restarting...", board_profile.c_str());
EMSESP::system_.system_restart();
});
diff --git a/src/core/emsesp.cpp b/src/core/emsesp.cpp
index a43dd2b23..e0d5eb3d5 100644
--- a/src/core/emsesp.cpp
+++ b/src/core/emsesp.cpp
@@ -21,6 +21,7 @@
#ifndef EMSESP_STANDALONE
#include "esp_ota_ops.h"
#include "rom/rtc.h"
+#include "littlefs_compat.h"
#endif
static_assert(uuid::thread_safe, "uuid-common must be thread-safe");
@@ -1723,22 +1724,26 @@ void EMSESP::start() {
// start the file system
#ifndef EMSESP_STANDALONE
-#if defined(EMSESP_DEBUG)
- // don't format the filesystem in debug mode
if (!LittleFS.begin(false)) {
- LOG_ERROR("LittleFS Mount Failed. Attempting to convert...");
- return;
- } else {
- // it mounted, show the contents of the root directory
- LOG_DEBUG("Listing root directory before:");
- system_.listDir("/", 3);
- }
-#else
- // format filesystem if there isn't one
- if (!LittleFS.begin(true)) {
- LOG_ERROR("LittleFS Mount Failed");
- return;
+ // A partition written by an Arduino Core 2 build is readable but records limits
+ // this build refuses to mount. Rewrite the superblock and retry before resorting
+ // to a format, which would throw away the user's settings.
+ if (littlefs_migrate() && LittleFS.begin(false)) {
+ LOG_NOTICE("Converted Filesystem to new format");
+ } else if (LittleFS.begin(true)) {
+ LOG_WARNING("Formatted Filesystem, any previous settings have been lost");
+ } else {
+ // if we reach here, nothing will ever work so go into a infinite loop
+ Serial.println("LittleFS Mount Failed. Cannot start EMS-ESP");
+ while (true) {
+ delay(1000);
+ yield();
+ }
+ }
}
+#if defined(EMSESP_DEBUG)
+ LOG_DEBUG("Listing root directory before:");
+ system_.listDir("/", 3);
#endif
#endif
diff --git a/src/core/littlefs_compat.cpp b/src/core/littlefs_compat.cpp
new file mode 100644
index 000000000..54985a53f
--- /dev/null
+++ b/src/core/littlefs_compat.cpp
@@ -0,0 +1,282 @@
+/*
+ * EMS-ESP - https://github.com/emsesp/EMS-ESP
+ * Copyright 2020-2025 emsesp.org
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+// A LittleFS partition written by an Arduino Core 2 build will not mount under the
+// esp_littlefs that ships with Core 3, even though every byte of it is readable. Two
+// fields of the superblock are compatibility gates rather than real format differences,
+// and lfs_mount() rejects the filesystem on either one:
+//
+// name_max Core 2 used the upstream default of 255, while esp_littlefs derives 63 from
+// CONFIG_LITTLEFS_OBJ_NAME_LEN and refuses anything larger. Storing 0 means
+// "use the library's own limit", which is what a Core 3 format writes.
+// version Core 2 recorded v2.1 and this build writes v2.0, and a higher minor is
+// refused. The library here understands everything a v2.1 image holds, so
+// lowering the field is enough.
+//
+// Rewriting them means walking the metadata block by hand. A block holds a revision count
+// followed by a chain of commits, each one a run of tags terminated by a CCRC tag and a
+// CRC covering everything that commit wrote. Tags are stored big endian and XORed with the
+// preceding tag, so the chain has to be replayed from the start to find anything. The
+// superblock struct is not at a fixed offset either: on a filesystem that has been written
+// to, directory entries are committed ahead of it.
+
+#ifndef EMSESP_STANDALONE
+
+#include "littlefs_compat.h"
+
+#include
+#include
+#include
+
+#include
+#include
+
+namespace emsesp {
+
+namespace {
+
+#ifndef CONFIG_LITTLEFS_OBJ_NAME_LEN
+#define CONFIG_LITTLEFS_OBJ_NAME_LEN 64
+#endif
+constexpr uint32_t NAME_MAX_SUPPORTED = CONFIG_LITTLEFS_OBJ_NAME_LEN - 1;
+constexpr uint32_t DISK_VERSION_TARGET = 0x00020000;
+
+// littlefs block size on the ESP32, which is the flash sector size
+constexpr uint32_t BLOCK_SIZE = 4096;
+
+// byte offsets within lfs_superblock_t, all little endian uint32_t
+constexpr uint32_t SB_VERSION = 0;
+constexpr uint32_t SB_BLOCK_SIZE = 4;
+constexpr uint32_t SB_NAME_MAX = 12;
+constexpr uint32_t SB_SIZE = 24;
+
+// tag accessors, mirroring lfs.c
+constexpr uint32_t TYPE_INLINESTRUCT = 0x201;
+constexpr uint32_t TYPE_CCRC = 0x500;
+
+constexpr bool tag_isvalid(uint32_t t) {
+ return (t & 0x80000000) == 0;
+}
+constexpr uint32_t tag_type2(uint32_t t) {
+ return (t & 0x78000000) >> 20;
+}
+constexpr uint32_t tag_type3(uint32_t t) {
+ return (t & 0x7ff00000) >> 20;
+}
+constexpr uint32_t tag_chunk(uint32_t t) {
+ return (t & 0x0ff00000) >> 20;
+}
+constexpr uint32_t tag_id(uint32_t t) {
+ return (t & 0x000ffc00) >> 10;
+}
+constexpr uint32_t tag_size(uint32_t t) {
+ return t & 0x3ff;
+}
+// a size of 0x3ff marks a delete tag, which carries no payload
+constexpr uint32_t tag_dsize(uint32_t t) {
+ return 4 + (tag_size(t) == 0x3ff ? 0 : tag_size(t));
+}
+
+uint32_t rd_be32(const uint8_t * p) {
+ return (static_cast(p[0]) << 24) | (static_cast(p[1]) << 16) | (static_cast(p[2]) << 8) | p[3];
+}
+uint32_t rd_le32(const uint8_t * p) {
+ return (static_cast(p[3]) << 24) | (static_cast(p[2]) << 16) | (static_cast(p[1]) << 8) | p[0];
+}
+void wr_le32(uint8_t * p, uint32_t v) {
+ p[0] = static_cast(v);
+ p[1] = static_cast(v >> 8);
+ p[2] = static_cast(v >> 16);
+ p[3] = static_cast(v >> 24);
+}
+
+// the CRC-32 littlefs commits with: reflected 0x04C11DB7, seeded 0xffffffff, no final xor
+uint32_t lfs_crc(uint32_t crc, const void * buffer, size_t size) {
+ static const uint32_t rtable[16] = {
+ 0x00000000,
+ 0x1db71064,
+ 0x3b6e20c8,
+ 0x26d930ac,
+ 0x76dc4190,
+ 0x6b6b51f4,
+ 0x4db26158,
+ 0x5005713c,
+ 0xedb88320,
+ 0xf00f9344,
+ 0xd6d6a3e8,
+ 0xcb61b38c,
+ 0x9b64c2b0,
+ 0x86d3d2d4,
+ 0xa00ae278,
+ 0xbdbdf21c,
+ };
+ const uint8_t * data = static_cast(buffer);
+ for (size_t i = 0; i < size; i++) {
+ crc = (crc >> 4) ^ rtable[(crc ^ data[i]) & 0xf];
+ crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf];
+ }
+ return crc;
+}
+
+// where the superblock lives, and which bytes the CRC of its commit covers
+struct Located {
+ bool found = false;
+ uint32_t struct_off = 0; // start of the 24 byte lfs_superblock_t
+ uint32_t commit_start = 0; // first byte the commit's CRC covers
+ uint32_t crc_off = 0; // where that CRC is stored
+};
+
+// Replays a metadata block's commit chain the same way lfs_dir_fetchmatch() does, keeping
+// the last superblock struct that belongs to a commit whose CRC verifies. Reporting
+// nothing until a stored CRC matches means a successful scan also proves we are reading
+// the tag stream correctly, so we never write back a block we misunderstood.
+Located locate_superblock(const uint8_t * buf) {
+ Located found;
+ uint32_t off = 0;
+ uint32_t ptag = 0xffffffff;
+ uint32_t commit_start = 0;
+ uint32_t crc = lfs_crc(0xffffffff, buf, 4); // the revision count leads the first commit
+ bool pending = false;
+ uint32_t pending_off = 0;
+
+ while (true) {
+ off += tag_dsize(ptag);
+ if (off + 4 > BLOCK_SIZE) {
+ break;
+ }
+
+ crc = lfs_crc(crc, &buf[off], 4);
+ const uint32_t tag = rd_be32(&buf[off]) ^ ptag;
+
+ if (!tag_isvalid(tag)) {
+ break; // nothing committed beyond this point
+ }
+ if (off + tag_dsize(tag) > BLOCK_SIZE) {
+ break;
+ }
+ ptag = tag;
+
+ if (tag_type2(tag) == TYPE_CCRC) {
+ if (crc != rd_le32(&buf[off + 4])) {
+ break; // commit never completed, so the chain ends here
+ }
+ if (pending) {
+ found.found = true;
+ found.struct_off = pending_off;
+ found.commit_start = commit_start;
+ found.crc_off = off + 4;
+ pending = false;
+ }
+ ptag ^= (tag_chunk(tag) & 1u) << 31;
+ crc = 0xffffffff;
+ commit_start = off + tag_dsize(tag); // the padding after the CRC is not covered
+ continue;
+ }
+
+ crc = lfs_crc(crc, &buf[off + 4], tag_dsize(tag) - 4);
+
+ if (tag_type3(tag) == TYPE_INLINESTRUCT && tag_id(tag) == 0 && tag_size(tag) == SB_SIZE) {
+ pending = true;
+ pending_off = off + 4;
+ }
+ }
+
+ return found;
+}
+
+// resolve the same partition esp_littlefs would pick
+const esp_partition_t * find_partition(const char * partition_label) {
+ if (partition_label != nullptr) {
+ return esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, partition_label);
+ }
+ // 0x83 is the littlefs subtype, 0x82 the spiffs one that older partition tables use
+ const esp_partition_t * part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, static_cast(0x83), nullptr);
+ if (part != nullptr) {
+ return part;
+ }
+ return esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, nullptr);
+}
+
+bool patch_block(const esp_partition_t * part, uint32_t block, uint8_t * buf) {
+ const uint32_t addr = block * BLOCK_SIZE;
+
+ if (esp_partition_read(part, addr, buf, BLOCK_SIZE) != ESP_OK) {
+ return false;
+ }
+
+ const Located sb = locate_superblock(buf);
+ if (!sb.found) {
+ return false;
+ }
+
+ uint8_t * s = buf + sb.struct_off;
+ if (rd_le32(s + SB_BLOCK_SIZE) != BLOCK_SIZE) {
+ return false; // geometry we did not expect means we are not reading a superblock
+ }
+
+ bool changed = false;
+
+ if (rd_le32(s + SB_NAME_MAX) > NAME_MAX_SUPPORTED) {
+ wr_le32(s + SB_NAME_MAX, 0);
+ changed = true;
+ }
+
+ const uint32_t version = rd_le32(s + SB_VERSION);
+ if (version > DISK_VERSION_TARGET && (version >> 16) == (DISK_VERSION_TARGET >> 16)) {
+ wr_le32(s + SB_VERSION, DISK_VERSION_TARGET);
+ changed = true;
+ }
+
+ if (!changed) {
+ return false;
+ }
+
+ wr_le32(buf + sb.crc_off, lfs_crc(0xffffffff, buf + sb.commit_start, sb.crc_off - sb.commit_start));
+
+ return esp_partition_erase_range(part, addr, BLOCK_SIZE) == ESP_OK && esp_partition_write(part, addr, buf, BLOCK_SIZE) == ESP_OK;
+}
+
+} // namespace
+
+bool littlefs_migrate(const char * partition_label) {
+ const esp_partition_t * part = find_partition(partition_label);
+ if (part == nullptr) {
+ return false;
+ }
+
+ uint8_t * buf = static_cast(malloc(BLOCK_SIZE));
+ if (buf == nullptr) {
+ return false;
+ }
+
+ // Blocks 0 and 1 are the root metadata pair. Rewriting them one at a time keeps one
+ // readable copy available for littlefs to fall back on if we lose power mid-write.
+ bool patched = false;
+ for (uint32_t block = 0; block < 2; block++) {
+ if (patch_block(part, block, buf)) {
+ patched = true;
+ }
+ }
+
+ free(buf);
+ return patched;
+}
+
+} // namespace emsesp
+
+#endif
diff --git a/src/core/littlefs_compat.h b/src/core/littlefs_compat.h
new file mode 100644
index 000000000..29df7ab03
--- /dev/null
+++ b/src/core/littlefs_compat.h
@@ -0,0 +1,31 @@
+/*
+ * EMS-ESP - https://github.com/emsesp/EMS-ESP
+ * Copyright 2020-2025 emsesp.org
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef EMSESP_LITTLEFS_COMPAT_H_
+#define EMSESP_LITTLEFS_COMPAT_H_
+
+namespace emsesp {
+
+// Rewrites the superblock of a LittleFS partition created by an Arduino Core 2 build so
+// that this build's littlefs will mount it. Only call once a mount has already failed.
+// Returns true if flash was changed, meaning another mount attempt is worth making.
+bool littlefs_migrate(const char * partition_label = nullptr);
+
+} // namespace emsesp
+
+#endif
diff --git a/src/core/system.cpp b/src/core/system.cpp
index b867e8642..5a637ffe3 100644
--- a/src/core/system.cpp
+++ b/src/core/system.cpp
@@ -476,7 +476,8 @@ void System::get_partition_info() {
auto current_partition = (const char *)esp_ota_get_running_partition()->label;
// update the current version and partition name in NVS if not already set
- if (EMSESP::nvs_.getString(current_partition) != EMSESP_APP_VERSION || emsesp::EMSESP::nvs_.getBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, true)) {
+ if (!EMSESP::nvs_.isKey(current_partition) || EMSESP::nvs_.getString(current_partition) != EMSESP_APP_VERSION
+ || emsesp::EMSESP::nvs_.getBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, true)) {
EMSESP::nvs_.putBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, false);
EMSESP::nvs_.putString(current_partition, EMSESP_APP_VERSION);
char c[20];
@@ -508,7 +509,9 @@ void System::get_partition_info() {
if (is_valid) {
PartitionInfo p_info;
// if there is an entry for this partition in NVS, get it's version from NVS
- p_info.version = EMSESP::nvs_.getString(part->label, "").c_str();
+ if (EMSESP::nvs_.isKey(part->label)) {
+ p_info.version = EMSESP::nvs_.getString(part->label).c_str();
+ }
char c[20];
snprintf(c, sizeof(c), "d_%s", (const char *)part->label);
time_t d = EMSESP::nvs_.getULong(c, 0);
diff --git a/src/core/temperaturesensor.cpp b/src/core/temperaturesensor.cpp
index 4fd053ae6..aa812cf18 100644
--- a/src/core/temperaturesensor.cpp
+++ b/src/core/temperaturesensor.cpp
@@ -199,7 +199,7 @@ void TemperatureSensor::loop() {
if (firstscan_ > 0 && set_internal_) {
set_internal_ = false;
Sensor * s = &sensors_[0];
- if (firstscan_ > 1) {
+ if (firstscan_ > 1 && EMSESP::nvs_.isKey("intTemp")) {
std::string s_nvs = EMSESP::nvs_.getString("intTemp").c_str();
for (uint8_t i = 0; i < firstscan_; i++) {
if (s_nvs == sensors_[i].id()) {
diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp
index 1ae7388ae..6787a7148 100644
--- a/src/web/WebSettingsService.cpp
+++ b/src/web/WebSettingsService.cpp
@@ -465,7 +465,7 @@ void WebSettings::set_board_profile(WebSettings & settings) {
String bbq_board = EMSESP::system_.getBBQKeesGatewayDetails(FUSE_VALUE::BOARD);
if (!bbq_board.isEmpty() && settings.board_profile != "CUSTOM") {
#if defined(EMSESP_DEBUG)
- EMSESP::logger().info("Overriding board profile with eFuse value %s", bbq_board.c_str());
+ EMSESP::logger().debug("Overriding board profile with eFuse value %s", bbq_board.c_str());
#endif
settings.board_profile = bbq_board;
}
@@ -532,9 +532,9 @@ void WebSettings::set_board_profile(WebSettings & settings) {
#ifndef EMSESP_STANDALONE
uint32_t psram_size = ESP.getPsramSize() / 1024; // in KB
if (psram_size > 0) {
- EMSESP::logger().info("Loaded board profile %s (PSRAM: %lu KB)", settings.board_profile.c_str(), psram_size);
+ EMSESP::logger().info("Applied board profile %s (PSRAM: %lu KB)", settings.board_profile.c_str(), psram_size);
} else {
- EMSESP::logger().info("Loaded board profile %s (PSRAM: not available)", settings.board_profile.c_str());
+ EMSESP::logger().info("Applied board profile %s (PSRAM: not available)", settings.board_profile.c_str());
}
#endif
diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp
index 69d50103a..804e7bdff 100644
--- a/src/web/WebStatusService.cpp
+++ b/src/web/WebStatusService.cpp
@@ -258,9 +258,9 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
// action = upgradeImportantMessages
// returns the type of upgrade important message to display in the UI
-// 0 = no message (if just a minor version upgrade)
-// 1 = going from <= 3.8 to 3.9 (has new partition layout)
-// 2 = major version upgrade
+// 0 = no message (if just a minor version upgrade)
+// 1 = special message for upgrades (no longer used)
+// 2 = major version upgrade
// version can be like 3.8.2 or a filename like EMS-ESP-3_8_2-dev_13-ESP32-16MB+.bin
uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
if (version.empty()) {
@@ -307,9 +307,10 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
return 0; // no upgrade (same version or downgrade)
}
- if (current_version < FirmwareVersion("3.9.0-dev.0") && latest_version.major() == 3 && latest_version.minor() == 9) {
- return 1; // upgrading to 3.9.x from anything older - new partition layout warning
- }
+ // case "1" is no longer used
+ // if (current_version < FirmwareVersion("3.9.0-dev.0") && latest_version.major() == 3 && latest_version.minor() == 9) {
+ // return 1; // upgrading to 3.9.x from anything older - new partition layout warning
+ // }
if (current_version.major() < latest_version.major()) {
return 2; // major version upgrade