Merge remote-tracking branch 'emsesp/dev' into dev

This commit is contained in:
MichaelDvP
2026-09-23 19:34:42 +02:00
42 changed files with 1027 additions and 379 deletions
+25
View File
@@ -29,6 +29,8 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install pnpm - name: Install pnpm
run: npm install -g pnpm run: npm install -g pnpm
@@ -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: |
+9 -2
View File
@@ -17,7 +17,8 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- 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 - `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. EMS-ESP only contacts emsesp.org for the latest version when it's enabled, so the Version page shows the available versions - 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 ## Fixed
@@ -27,12 +28,18 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- LED stayed off on a healthy system when "Disable LED" was unchecked - LED stayed off on a healthy system when "Disable LED" was unchecked
- Ethernet MAC address changed with the new SDK, breaking DHCP reservations (currently disabled) - Ethernet MAC address changed with the new SDK, breaking DHCP reservations (currently disabled)
- "IPv4 nameserver" showed an IPv6 address when IPv6 was in use - "IPv4 nameserver" showed an IPv6 address when IPv6 was in use
- the Network status page showed at most one IPv6 address and could hide the IPv4 DNS server entirely. It only read the link-local address, so a dual-stack host's ULA and global addresses were never reported, and it only read two of the three DNS slots, which are shared between IPv4 and IPv6. It now lists every address the interface holds, each labelled with its IPv6 scope, and every configured DNS server [#3246](https://github.com/emsesp/EMS-ESP32/issues/3246)
- the Gateway was only ever reported for IPv4, so on a dual-stack network the IPv6 default router was missing from the Network status page, `show system` and the MQTT `info` topic. Routers are learnt from router advertisements rather than configured, so all of them are now listed [#3246](https://github.com/emsesp/EMS-ESP32/issues/3246)
- `show system` had the same gaps, reporting a link-local IPv6 address only and a single IPv4 DNS server. The WiFi and Ethernet sections now list every IPv6 address with its scope, and every DNS server labelled with its own family [#3246](https://github.com/emsesp/EMS-ESP32/issues/3246)
- the Access Point status page reported whichever interface happened to be active, so with the AP down it showed the station's IP and MAC address under an "Inactive" heading. It now always reports the soft-AP's own details, and shows `none` when the AP isn't running
- the MQTT `info` topic published no addresses at all when connected over Ethernet, and over WiFi only an IPv4 address plus a link-local IPv6 one. Both interfaces now report their MAC, IPv4 address and gateway, every IPv6 address and every DNS server. Note that `IPv6 address` is now an array, and `IPv4 DNS Server` is replaced by a `DNS Server` array that covers both families [#3246](https://github.com/emsesp/EMS-ESP32/issues/3246)
- 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 hourly versions.json refresh was rewriting the cache from the main loop task - 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 - 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 - 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
## Changed ## Changed
+6 -6
View File
@@ -2763,9 +2763,9 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,hpin3opt,input 3 options,string (>=0<=0), ,true,sensor.boiler_input_3_options,sensor.boiler_hpin3opt,6,0,1,198,8 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,hpin3opt,input 3 options,string (>=0<=0), ,true,sensor.boiler_input_3_options,sensor.boiler_hpin3opt,6,0,1,198,8
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,hpin4,input 4 state,boolean, ,false,binary_sensor.boiler_input_4_state,binary_sensor.boiler_hpin4,6,0,1,206,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,hpin4,input 4 state,boolean, ,false,binary_sensor.boiler_input_4_state,binary_sensor.boiler_hpin4,6,0,1,206,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,hpin4opt,input 4 options,string (>=0<=0), ,true,sensor.boiler_input_4_options,sensor.boiler_hpin4opt,6,0,1,207,8 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,hpin4opt,input 4 options,string (>=0<=0), ,true,sensor.boiler_input_4_options,sensor.boiler_hpin4opt,6,0,1,207,8
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,maxheatcomp,heat limit compressor,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW] (>=0<=0), ,true,select.boiler_heat_limit_compressor,select.boiler_maxheatcomp,6,0,1,215,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,maxheatcomp,heat limit compressor,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW\|12 kW\|15 kW] (>=0<=0), ,true,select.boiler_heat_limit_compressor,select.boiler_maxheatcomp,6,0,1,215,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,maxheatheat,heat limit heating,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW] (>=0<=0), ,true,select.boiler_heat_limit_heating,select.boiler_maxheatheat,6,0,1,216,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,maxheatheat,heat limit heating,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW\|12 kW\|15 kW] (>=0<=0), ,true,select.boiler_heat_limit_heating,select.boiler_maxheatheat,6,0,1,216,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.maxheat,heat limit,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW] (>=0<=0), ,true,select.boiler_dhw_heat_limit,select.boiler_dhw_maxheat,6,9,1,14,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.maxheat,heat limit,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW\|12 kW\|15 kW] (>=0<=0), ,true,select.boiler_dhw_heat_limit,select.boiler_dhw_maxheat,6,9,1,14,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,auxheatersource,aux heater source,enum [not installed\|electric heater\|exclusive\|parallel\|hybrid] (>=0<=0), ,true,select.boiler_aux_heater_source,select.boiler_auxheatersource,6,0,1,217,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,auxheatersource,aux heater source,enum [not installed\|electric heater\|exclusive\|parallel\|hybrid] (>=0<=0), ,true,select.boiler_aux_heater_source,select.boiler_auxheatersource,6,0,1,217,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,pvcooling,cooling only with PV,boolean (>=0<=0), ,true,switch.boiler_cooling_only_with_PV,switch.boiler_pvcooling,6,0,1,218,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,pvcooling,cooling only with PV,boolean (>=0<=0), ,true,switch.boiler_cooling_only_with_PV,switch.boiler_pvcooling,6,0,1,218,1
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,auxheateronly,aux heater only,boolean (>=0<=0), ,true,switch.boiler_aux_heater_only,switch.boiler_auxheateronly,6,0,1,219,1 "Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,auxheateronly,aux heater only,boolean (>=0<=0), ,true,switch.boiler_aux_heater_only,switch.boiler_auxheateronly,6,0,1,219,1
@@ -2982,9 +2982,9 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
"Geo 5xx",boiler,173,hpin3opt,input 3 options,string (>=0<=0), ,true,sensor.boiler_input_3_options,sensor.boiler_hpin3opt,6,0,1,198,8 "Geo 5xx",boiler,173,hpin3opt,input 3 options,string (>=0<=0), ,true,sensor.boiler_input_3_options,sensor.boiler_hpin3opt,6,0,1,198,8
"Geo 5xx",boiler,173,hpin4,input 4 state,boolean, ,false,binary_sensor.boiler_input_4_state,binary_sensor.boiler_hpin4,6,0,1,206,1 "Geo 5xx",boiler,173,hpin4,input 4 state,boolean, ,false,binary_sensor.boiler_input_4_state,binary_sensor.boiler_hpin4,6,0,1,206,1
"Geo 5xx",boiler,173,hpin4opt,input 4 options,string (>=0<=0), ,true,sensor.boiler_input_4_options,sensor.boiler_hpin4opt,6,0,1,207,8 "Geo 5xx",boiler,173,hpin4opt,input 4 options,string (>=0<=0), ,true,sensor.boiler_input_4_options,sensor.boiler_hpin4opt,6,0,1,207,8
"Geo 5xx",boiler,173,maxheatcomp,heat limit compressor,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW] (>=0<=0), ,true,select.boiler_heat_limit_compressor,select.boiler_maxheatcomp,6,0,1,215,1 "Geo 5xx",boiler,173,maxheatcomp,heat limit compressor,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW\|12 kW\|15 kW] (>=0<=0), ,true,select.boiler_heat_limit_compressor,select.boiler_maxheatcomp,6,0,1,215,1
"Geo 5xx",boiler,173,maxheatheat,heat limit heating,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW] (>=0<=0), ,true,select.boiler_heat_limit_heating,select.boiler_maxheatheat,6,0,1,216,1 "Geo 5xx",boiler,173,maxheatheat,heat limit heating,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW\|12 kW\|15 kW] (>=0<=0), ,true,select.boiler_heat_limit_heating,select.boiler_maxheatheat,6,0,1,216,1
"Geo 5xx",boiler,173,dhw.maxheat,heat limit,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW] (>=0<=0), ,true,select.boiler_dhw_heat_limit,select.boiler_dhw_maxheat,6,9,1,14,1 "Geo 5xx",boiler,173,dhw.maxheat,heat limit,enum [0 kW\|2 kW\|3 kW\|4 kW\|6 kW\|9 kW\|12 kW\|15 kW] (>=0<=0), ,true,select.boiler_dhw_heat_limit,select.boiler_dhw_maxheat,6,9,1,14,1
"Geo 5xx",boiler,173,auxheatersource,aux heater source,enum [not installed\|electric heater\|exclusive\|parallel\|hybrid] (>=0<=0), ,true,select.boiler_aux_heater_source,select.boiler_auxheatersource,6,0,1,217,1 "Geo 5xx",boiler,173,auxheatersource,aux heater source,enum [not installed\|electric heater\|exclusive\|parallel\|hybrid] (>=0<=0), ,true,select.boiler_aux_heater_source,select.boiler_auxheatersource,6,0,1,217,1
"Geo 5xx",boiler,173,pvcooling,cooling only with PV,boolean (>=0<=0), ,true,switch.boiler_cooling_only_with_PV,switch.boiler_pvcooling,6,0,1,218,1 "Geo 5xx",boiler,173,pvcooling,cooling only with PV,boolean (>=0<=0), ,true,switch.boiler_cooling_only_with_PV,switch.boiler_pvcooling,6,0,1,218,1
"Geo 5xx",boiler,173,auxheateronly,aux heater only,boolean (>=0<=0), ,true,switch.boiler_aux_heater_only,switch.boiler_auxheateronly,6,0,1,219,1 "Geo 5xx",boiler,173,auxheateronly,aux heater only,boolean (>=0<=0), ,true,switch.boiler_aux_heater_only,switch.boiler_auxheateronly,6,0,1,219,1
Can't render this file because it is too large.
+14 -12
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 {
@@ -296,16 +297,12 @@ const InstallDialog = memo(
)} )}
</Typography> </Typography>
{upgradeImportantMessageType === 2 && LL.UPGRADE_IMPORTANT_MESSAGES_2()} {upgradeImportantMessageType === 2 && LL.UPGRADE_IMPORTANT_MESSAGES_2()}
{upgradeImportantMessageType === 1 && ( {upgradeImportantMessageType === 1 && LL.UPGRADE_IMPORTANT_MESSAGES_1()}
<> <Typography sx={{ mt: 2 }}>
{LL.UPGRADE_IMPORTANT_MESSAGES_1()} <Link to="/settings/downloadUpload" style={{ color: 'lightblue' }}>
<Typography sx={{ mt: 2 }}> {LL.DOWNLOAD_SYSTEM_BACKUP()}
<Link to="/settings/downloadUpload" style={{ color: 'lightblue' }}> </Link>
{LL.DOWNLOAD_SYSTEM_BACKUP()} </Typography>
</Link>
</Typography>
</>
)}
<Typography sx={{ mt: 2 }}> <Typography sx={{ mt: 2 }}>
<Link <Link
to="https://docs.emsesp.org/FAQ#upgrading-the-firmware" to="https://docs.emsesp.org/FAQ#upgrading-the-firmware"
@@ -440,12 +437,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);
+5 -2
View File
@@ -90,7 +90,10 @@ const APStatus = () => {
<ListItemAvatar> <ListItemAvatar>
<Avatar sx={{ bgcolor: 'primary.main' }}>IP</Avatar> <Avatar sx={{ bgcolor: 'primary.main' }}>IP</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary={LL.ADDRESS_OF('IP')} secondary={data.ip_address} /> <ListItemText
primary={LL.ADDRESS_OF('IP')}
secondary={data.ip_address || 'none'}
/>
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
@@ -103,7 +106,7 @@ const APStatus = () => {
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={LL.ADDRESS_OF('MAC')} primary={LL.ADDRESS_OF('MAC')}
secondary={data.mac_address} secondary={data.mac_address || 'none'}
/> />
</ListItem> </ListItem>
+43 -19
View File
@@ -7,11 +7,13 @@ import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputCompone
import WifiIcon from '@mui/icons-material/Wifi'; import WifiIcon from '@mui/icons-material/Wifi';
import { import {
Avatar, Avatar,
Box,
Divider, Divider,
List, List,
ListItem, ListItem,
ListItemAvatar, ListItemAvatar,
ListItemText, ListItemText,
Typography,
useTheme useTheme
} from '@mui/material'; } from '@mui/material';
import type { Theme } from '@mui/material'; import type { Theme } from '@mui/material';
@@ -62,26 +64,45 @@ const networkQualityHighlight = ({ rssi }: NetworkStatusType, theme: Theme) => {
return theme.palette.success.main; return theme.palette.success.main;
}; };
const dnsServers = ({ dns_ip_1, dns_ip_2 }: NetworkStatusType) => { // one address per line, with an optional dimmed note such as the IPv6 scope
if (!dns_ip_1) { const AddressList = ({
addresses
}: {
addresses: { value: string; note?: string }[];
}) => {
if (addresses.length === 0) {
return 'none'; return 'none';
} }
return dns_ip_1 + (!dns_ip_2 || dns_ip_2 === '0.0.0.0' ? '' : ', ' + dns_ip_2); return addresses.map(({ value, note }) => (
<Box component="span" sx={{ display: 'block' }} key={value}>
{value}
{note && (
<Typography
component="span"
variant="body2"
sx={{ color: 'grey.600', ml: 1 }} // dimmer than the grey[500] secondary text
>
({note})
</Typography>
)}
</Box>
));
}; };
const IPs = (status: NetworkStatusType) => { const isSet = (ip?: string): ip is string => !!ip && ip !== '0.0.0.0' && ip !== '::';
if (
!status.local_ipv6 || const ipAddresses = ({ local_ip, ipv6 }: NetworkStatusType) => [
status.local_ipv6 === '0000:0000:0000:0000:0000:0000:0000:0000' || ...(isSet(local_ip) ? [{ value: local_ip }] : []),
status.local_ipv6 === '::' ...(ipv6 ?? []).map(({ address, scope }) => ({ value: address, note: scope }))
) { ];
return status.local_ip;
} const gateways = ({ gateway_ip, gateway_ipv6 }: NetworkStatusType) => [
if (!status.local_ip || status.local_ip === '0.0.0.0') { ...(isSet(gateway_ip) ? [{ value: gateway_ip }] : []),
return status.local_ipv6; ...(gateway_ipv6 ?? []).filter(isSet).map((value) => ({ value }))
} ];
return status.local_ip + ', ' + status.local_ipv6;
}; const dnsServers = ({ dns }: NetworkStatusType) =>
(dns ?? []).filter(isSet).map((value) => ({ value }));
const getNetworkStatusText = ( const getNetworkStatusText = (
status: NetworkConnectionStatus, status: NetworkConnectionStatus,
@@ -178,7 +199,10 @@ const NetworkStatus = () => {
<ListItemAvatar> <ListItemAvatar>
<Avatar>IP</Avatar> <Avatar>IP</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary={LL.ADDRESS_OF('IP')} secondary={IPs(data)} /> <ListItemText
primary={LL.ADDRESS_OF('IP')}
secondary={<AddressList addresses={ipAddresses(data)} />}
/>
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
<ListItem> <ListItem>
@@ -211,7 +235,7 @@ const NetworkStatus = () => {
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={LL.NETWORK_GATEWAY()} primary={LL.NETWORK_GATEWAY()}
secondary={data.gateway_ip || 'none'} secondary={<AddressList addresses={gateways(data)} />}
/> />
</ListItem> </ListItem>
<Divider variant="inset" component="li" /> <Divider variant="inset" component="li" />
@@ -223,7 +247,7 @@ const NetworkStatus = () => {
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={LL.NETWORK_DNS()} primary={LL.NETWORK_DNS()}
secondary={dnsServers(data)} secondary={<AddressList addresses={dnsServers(data)} />}
/> />
</ListItem> </ListItem>
</> </>
@@ -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}
/> />
)} )}
+4 -4
View File
@@ -311,7 +311,7 @@ const cs: Translation = {
ALLVALUES: 'Všechny hodnoty', ALLVALUES: 'Všechny hodnoty',
SPECIAL_FUNCTIONS: 'Speciální funkce', SPECIAL_FUNCTIONS: 'Speciální funkce',
WAIT_FIRMWARE: 'Firmware se nahrává a instaluje', WAIT_FIRMWARE: 'Firmware se nahrává a instaluje',
INSTALL_VERSION: 'Tímto se {0} verze {1}. Jste si jistí?', INSTALL_VERSION: 'Tímto se {0} verze {1}',
UPDATE_AVAILABLE: 'aktualizace dostupná', UPDATE_AVAILABLE: 'aktualizace dostupná',
LATEST_VERSION: 'Používáte nejnovější verzi {0}firmwaru', LATEST_VERSION: 'Používáte nejnovější verzi {0}firmwaru',
PLEASE_WAIT: 'Prosím čekejte', PLEASE_WAIT: 'Prosím čekejte',
@@ -340,7 +340,7 @@ const cs: Translation = {
ONLINE_HELP: 'online nápověda', ONLINE_HELP: 'online nápověda',
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizovat důležité zprávy', UPGRADE_IMPORTANT_MESSAGES: 'Aktualizovat důležité zprávy',
UPGRADE_IMPORTANT_MESSAGES_1: 'Tato aktualizace vyžaduje obnovení továrního nastavení. Ujistěte se, že nejprve stáhnete systémovou zálohu před pokračováním a poté nahrajte tento soubor po instalaci nové verze.', UPGRADE_IMPORTANT_MESSAGES_1: 'Tato aktualizace vyžaduje obnovení továrního nastavení. Ujistěte se, že nejprve stáhnete systémovou zálohu před pokračováním a poté nahrajte tento soubor po instalaci nové verze.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujete se na novou hlavní verzi. Ujistěte se, že jste přečetli ChangeLog pro jakékoliv závažné změny.', UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujete se na novou major verzi. Je vhodné nejprve vytvořit systémovou zálohu. Také zkontrolujte online nápovědu pro jakékoliv závažné změny.',
WARNING_SYSTEM_BACKUP: 'Toto vytvoří zálohu vašich celých systémových konfigurací a nastavení. Všechna hesla budou v zálohovém souboru čitelná. Buďte opatrní při sdílení! Opravdu chcete pokračovat?', WARNING_SYSTEM_BACKUP: 'Toto vytvoří zálohu vašich celých systémových konfigurací a nastavení. Všechna hesla budou v zálohovém souboru čitelná. Buďte opatrní při sdílení! Opravdu chcete pokračovat?',
TEST_EMAIL_SUCCESSFUL: 'Test email byl úspěšně odeslán', TEST_EMAIL_SUCCESSFUL: 'Test email byl úspěšně odeslán',
SYSTEM_NAME: 'Název systému', SYSTEM_NAME: 'Název systému',
@@ -349,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...'
}; };
+4 -4
View File
@@ -311,7 +311,7 @@ const de: Translation = {
ALLVALUES: 'Alle Werte', ALLVALUES: 'Alle Werte',
SPECIAL_FUNCTIONS: 'Sonderfunktionen', SPECIAL_FUNCTIONS: 'Sonderfunktionen',
WAIT_FIRMWARE: 'Die Firmware wird hochgeladen und installiert.', WAIT_FIRMWARE: 'Die Firmware wird hochgeladen und installiert.',
INSTALL_VERSION: 'Dadurch wird die Version {1} {0}. Sind Sie sicher?', INSTALL_VERSION: 'Dadurch wird die Version {1} {0}',
UPDATE_AVAILABLE: 'Firmware-Update verfügbar', UPDATE_AVAILABLE: 'Firmware-Update verfügbar',
LATEST_VERSION: 'Sie verwenden die neueste {0} Firmware-Version', LATEST_VERSION: 'Sie verwenden die neueste {0} Firmware-Version',
PLEASE_WAIT: 'Bitte warten', PLEASE_WAIT: 'Bitte warten',
@@ -340,7 +340,7 @@ const de: Translation = {
ONLINE_HELP: 'Online-Hilfe', ONLINE_HELP: 'Online-Hilfe',
UPGRADE_IMPORTANT_MESSAGES: 'Wichtige Nachrichten aktualisieren', UPGRADE_IMPORTANT_MESSAGES: 'Wichtige Nachrichten aktualisieren',
UPGRADE_IMPORTANT_MESSAGES_1: 'Für diese Aktualisierung ist ein Werksreset erforderlich. Stellen Sie sicher, dass Sie zuerst eine Systemsicherung herunterladen, bevor Sie fortfahren, und laden Sie diese Datei dann nach der Installation der neuen Version hoch.', UPGRADE_IMPORTANT_MESSAGES_1: 'Für diese Aktualisierung ist ein Werksreset erforderlich. Stellen Sie sicher, dass Sie zuerst eine Systemsicherung herunterladen, bevor Sie fortfahren, und laden Sie diese Datei dann nach der Installation der neuen Version hoch.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Sie aktualisieren auf eine neue Hauptversion. Stellen Sie sicher, dass Sie den ChangeLog für alle wichtigen Änderungen gelesen haben.', UPGRADE_IMPORTANT_MESSAGES_2: 'Sie aktualisieren auf eine neue major Version. Es ist ratsam, zuerst eine Systemsicherung zu erstellen. Überprüfen Sie auch die Online-Hilfe für eventuelle wichtige Änderungen.',
WARNING_SYSTEM_BACKUP: 'Dies wird eine Sicherung Ihrer vollständigen Systemkonfiguration und Einstellungen erstellen. Alle Passwörter werden in dieser Sicherungsdatei lesbar sein. Seien Sie vorsichtig beim Teilen! Möchten Sie fortfahren?', WARNING_SYSTEM_BACKUP: 'Dies wird eine Sicherung Ihrer vollständigen Systemkonfiguration und Einstellungen erstellen. Alle Passwörter werden in dieser Sicherungsdatei lesbar sein. Seien Sie vorsichtig beim Teilen! Möchten Sie fortfahren?',
TEST_EMAIL_SUCCESSFUL: 'Test email erfolgreich gesendet', TEST_EMAIL_SUCCESSFUL: 'Test email erfolgreich gesendet',
SYSTEM_NAME: 'Systemname', SYSTEM_NAME: 'Systemname',
@@ -349,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...'
}; };
+4 -4
View File
@@ -311,7 +311,7 @@ const en: Translation = {
ALLVALUES: 'All Values', ALLVALUES: 'All Values',
SPECIAL_FUNCTIONS: 'Special Functions', SPECIAL_FUNCTIONS: 'Special Functions',
WAIT_FIRMWARE: 'Firmware is uploading and installing', WAIT_FIRMWARE: 'Firmware is uploading and installing',
INSTALL_VERSION: 'This will {0} version {1}. Are you sure?', INSTALL_VERSION: 'This will {0} version {1}',
UPDATE_AVAILABLE: 'update available', UPDATE_AVAILABLE: 'update available',
LATEST_VERSION: 'You are using the latest {0} firmware version', LATEST_VERSION: 'You are using the latest {0} firmware version',
PLEASE_WAIT: 'Please wait', PLEASE_WAIT: 'Please wait',
@@ -340,7 +340,7 @@ const en: Translation = {
ONLINE_HELP: 'online help', ONLINE_HELP: 'online help',
UPGRADE_IMPORTANT_MESSAGES: 'Upgrade Important Messages', UPGRADE_IMPORTANT_MESSAGES: 'Upgrade Important Messages',
UPGRADE_IMPORTANT_MESSAGES_1: 'This upgrade requires a factory reset. Make sure you first download a System Backup before continuing, and then upload this file after the new version is installed.', UPGRADE_IMPORTANT_MESSAGES_1: 'This upgrade requires a factory reset. Make sure you first download a System Backup before continuing, and then upload this file after the new version is installed.',
UPGRADE_IMPORTANT_MESSAGES_2: 'You are upgrading to a new major version. Make sure you have read the ChangeLog for any breaking changes.', UPGRADE_IMPORTANT_MESSAGES_2: 'You are upgrading to a new major version. It is advisable to make a system backup first. Also check the online help for any breaking changes.',
WARNING_SYSTEM_BACKUP: 'This will create a backup of your full system configuration and settings. All passwords will be readable in the backup file. Be careful with sharing! Do you want to continue?', WARNING_SYSTEM_BACKUP: 'This will create a backup of your full system configuration and settings. All passwords will be readable in the backup file. Be careful with sharing! Do you want to continue?',
TEST_EMAIL_SUCCESSFUL: 'Test email sent successfully', TEST_EMAIL_SUCCESSFUL: 'Test email sent successfully',
SYSTEM_NAME: 'System Name', SYSTEM_NAME: 'System Name',
@@ -349,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...'
}; };
+4 -4
View File
@@ -311,7 +311,7 @@ const fr: Translation = {
ALLVALUES: 'Toutes les valeurs', ALLVALUES: 'Toutes les valeurs',
SPECIAL_FUNCTIONS: 'Fonctions spéciales', SPECIAL_FUNCTIONS: 'Fonctions spéciales',
WAIT_FIRMWARE: 'Firmware en cours de téléchargement et d\'installation', WAIT_FIRMWARE: 'Firmware en cours de téléchargement et d\'installation',
INSTALL_VERSION: 'Cela va {0} la version {1}. Êtes-vous sûr ?', INSTALL_VERSION: 'Cela va {0} la version {1}',
UPDATE_AVAILABLE: 'mise à jour disponible', UPDATE_AVAILABLE: 'mise à jour disponible',
LATEST_VERSION: 'Vous utilisez la dernière version {0} du firmware', LATEST_VERSION: 'Vous utilisez la dernière version {0} du firmware',
PLEASE_WAIT: 'Veuillez patienter', PLEASE_WAIT: 'Veuillez patienter',
@@ -340,7 +340,7 @@ const fr: Translation = {
ONLINE_HELP: 'aide en ligne', ONLINE_HELP: 'aide en ligne',
UPGRADE_IMPORTANT_MESSAGES: 'Mettre à jour les messages importants', UPGRADE_IMPORTANT_MESSAGES: 'Mettre à jour les messages importants',
UPGRADE_IMPORTANT_MESSAGES_1: 'Cette mise à jour nécessite une réinitialisation de fabrique. Assurez-vous de télécharger une sauvegarde système avant de continuer, et de la charger après l\'installation de la nouvelle version.', UPGRADE_IMPORTANT_MESSAGES_1: 'Cette mise à jour nécessite une réinitialisation de fabrique. Assurez-vous de télécharger une sauvegarde système avant de continuer, et de la charger après l\'installation de la nouvelle version.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Vous mettez à jour vers une nouvelle version majeure. Assurez-vous de lire le ChangeLog pour tout changement important.', UPGRADE_IMPORTANT_MESSAGES_2: 'Vous mettez à jour vers une nouvelle version majeure. Il est recommandé de créer d\'abord une sauvegarde système. Vérifiez également l\'aide en ligne pour les éventuelles modifications importantes.',
WARNING_SYSTEM_BACKUP: 'Cela créera une sauvegarde de votre configuration et paramètres complets. Tous les mots de passe seront lisibles dans le fichier de sauvegarde. Soyez prudent avec le partage ! Voulez-vous continuer ?', WARNING_SYSTEM_BACKUP: 'Cela créera une sauvegarde de votre configuration et paramètres complets. Tous les mots de passe seront lisibles dans le fichier de sauvegarde. Soyez prudent avec le partage ! Voulez-vous continuer ?',
TEST_EMAIL_SUCCESSFUL: 'Test email envoyé avec succès', TEST_EMAIL_SUCCESSFUL: 'Test email envoyé avec succès',
SYSTEM_NAME: 'Nom du système', SYSTEM_NAME: 'Nom du système',
@@ -349,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...'
}; };
+4 -4
View File
@@ -311,7 +311,7 @@ const it: Translation = {
ALLVALUES: 'Tutti i valori', ALLVALUES: 'Tutti i valori',
SPECIAL_FUNCTIONS: 'Funzioni speciali', SPECIAL_FUNCTIONS: 'Funzioni speciali',
WAIT_FIRMWARE: 'Firmware è in upload e installazione', WAIT_FIRMWARE: 'Firmware è in upload e installazione',
INSTALL_VERSION: 'Questo installerà la versione {1} {0}. Sei sicuro?', INSTALL_VERSION: 'Questo installerà la versione {1} {0}',
UPDATE_AVAILABLE: 'aggiornamento disponibile', UPDATE_AVAILABLE: 'aggiornamento disponibile',
LATEST_VERSION: 'Stai usando la versione più recente del firmware {0}', LATEST_VERSION: 'Stai usando la versione più recente del firmware {0}',
PLEASE_WAIT: 'Attendere', PLEASE_WAIT: 'Attendere',
@@ -340,7 +340,7 @@ const it: Translation = {
ONLINE_HELP: 'aiuto online', ONLINE_HELP: 'aiuto online',
UPGRADE_IMPORTANT_MESSAGES: 'Aggiorna Messaggi Importanti', UPGRADE_IMPORTANT_MESSAGES: 'Aggiorna Messaggi Importanti',
UPGRADE_IMPORTANT_MESSAGES_1: 'Questa aggiornamento richiede un ripristino di fabbrica. Assicurati di prima scaricare un backup del sistema prima di continuare, e poi caricare questo file dopo l\'installazione della nuova versione.', UPGRADE_IMPORTANT_MESSAGES_1: 'Questa aggiornamento richiede un ripristino di fabbrica. Assicurati di prima scaricare un backup del sistema prima di continuare, e poi caricare questo file dopo l\'installazione della nuova versione.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Stai aggiornando a una nuova versione principale. Assicurati di aver letto il ChangeLog per qualsiasi cambiamento importante.', UPGRADE_IMPORTANT_MESSAGES_2: 'Stai aggiornando a una nuova versione major. È consigliabile prima creare un backup del sistema. Controlla anche l\'aiuto online per eventuali modifiche importanti.',
WARNING_SYSTEM_BACKUP: 'Questo creerà un backup delle tue configurazioni e impostazioni complete. Tutte le password saranno leggibili nel file di backup. Sei sicuro di voler continuare?', WARNING_SYSTEM_BACKUP: 'Questo creerà un backup delle tue configurazioni e impostazioni complete. Tutte le password saranno leggibili nel file di backup. Sei sicuro di voler continuare?',
TEST_EMAIL_SUCCESSFUL: 'Test email inviata con successo', TEST_EMAIL_SUCCESSFUL: 'Test email inviata con successo',
SYSTEM_NAME: 'Nome del sistema', SYSTEM_NAME: 'Nome del sistema',
@@ -349,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...'
}; };
+4 -4
View File
@@ -311,7 +311,7 @@ const nl: Translation = {
ALLVALUES: 'All waarden', ALLVALUES: 'All waarden',
SPECIAL_FUNCTIONS: 'Speciale functies', SPECIAL_FUNCTIONS: 'Speciale functies',
WAIT_FIRMWARE: 'Firmware wordt geüpload en geïnstalleerd', WAIT_FIRMWARE: 'Firmware wordt geüpload en geïnstalleerd',
INSTALL_VERSION: 'Hiermee wordt versie {1} {0}. Weet je het zeker?', INSTALL_VERSION: 'Hiermee wordt versie {1} {0}',
UPDATE_AVAILABLE: 'update beschikbaar', UPDATE_AVAILABLE: 'update beschikbaar',
LATEST_VERSION: 'U gebruikt de nieuwste {0} firmwareversie', LATEST_VERSION: 'U gebruikt de nieuwste {0} firmwareversie',
PLEASE_WAIT: 'Een ogenblik geduld', PLEASE_WAIT: 'Een ogenblik geduld',
@@ -340,7 +340,7 @@ const nl: Translation = {
ONLINE_HELP: 'online help', ONLINE_HELP: 'online help',
UPGRADE_IMPORTANT_MESSAGES: 'Upgrade Belangrijke Berichten', UPGRADE_IMPORTANT_MESSAGES: 'Upgrade Belangrijke Berichten',
UPGRADE_IMPORTANT_MESSAGES_1: 'Deze upgrade vereist een fabrieksinstelling. Zorg ervoor dat u eerst een Systeem Backup download voordat u doorgaat, en upload deze file na de installatie van de nieuwe versie.', UPGRADE_IMPORTANT_MESSAGES_1: 'Deze upgrade vereist een fabrieksinstelling. Zorg ervoor dat u eerst een Systeem Backup download voordat u doorgaat, en upload deze file na de installatie van de nieuwe versie.',
UPGRADE_IMPORTANT_MESSAGES_2: 'U updatet naar een nieuwe grote versie. Zorg ervoor dat u de ChangeLog hebt gelezen voor alle brekende wijzigingen.', UPGRADE_IMPORTANT_MESSAGES_2: 'U updatet naar een nieuwe major versie. Het is raadzaam om eerst een systeem backup te maken. Controleer ook de online help voor eventuele brekende wijzigingen.',
WARNING_SYSTEM_BACKUP: 'Dit zal een back-up van uw volledige systeemconfiguratie en instellingen maken. Alle wachtwoorden zijn leesbaar in het back-upbestand. Wees voorzichtig bij delen! Wilt u doorgaan?', WARNING_SYSTEM_BACKUP: 'Dit zal een back-up van uw volledige systeemconfiguratie en instellingen maken. Alle wachtwoorden zijn leesbaar in het back-upbestand. Wees voorzichtig bij delen! Wilt u doorgaan?',
TEST_EMAIL_SUCCESSFUL: 'Test email verzonden succesvol', TEST_EMAIL_SUCCESSFUL: 'Test email verzonden succesvol',
SYSTEM_NAME: 'Systeemnaam', SYSTEM_NAME: 'Systeemnaam',
@@ -349,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...'
}; };
+4 -4
View File
@@ -311,7 +311,7 @@ const no: Translation = {
ALLVALUES: 'Alle verdier', ALLVALUES: 'Alle verdier',
SPECIAL_FUNCTIONS: 'Spesielle funksjoner', SPECIAL_FUNCTIONS: 'Spesielle funksjoner',
WAIT_FIRMWARE: 'Firmware er i opplasting og installasjon', WAIT_FIRMWARE: 'Firmware er i opplasting og installasjon',
INSTALL_VERSION: 'Dette vil {0} versjon {1}. Er du sikker?', INSTALL_VERSION: 'Dette vil {0} versjon {1}',
UPDATE_AVAILABLE: 'oppdatering tilgjengelig', UPDATE_AVAILABLE: 'oppdatering tilgjengelig',
LATEST_VERSION: 'Du bruker den nyeste {0} firmware versjonen', LATEST_VERSION: 'Du bruker den nyeste {0} firmware versjonen',
PLEASE_WAIT: 'Vennligst vent', PLEASE_WAIT: 'Vennligst vent',
@@ -340,7 +340,7 @@ const no: Translation = {
ONLINE_HELP: 'online hjelp', ONLINE_HELP: 'online hjelp',
UPGRADE_IMPORTANT_MESSAGES: 'Oppdater viktige meldinger', UPGRADE_IMPORTANT_MESSAGES: 'Oppdater viktige meldinger',
UPGRADE_IMPORTANT_MESSAGES_1: 'Denne oppdateringen krever en fabriksinstilling. Sørg for at du først lastet ned en System Backup før du fortsetter, og last denne filen etter at den nye versjonen er installert.', UPGRADE_IMPORTANT_MESSAGES_1: 'Denne oppdateringen krever en fabriksinstilling. Sørg for at du først lastet ned en System Backup før du fortsetter, og last denne filen etter at den nye versjonen er installert.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Du oppdaterer til en ny hovedversjon. Sørg for at du har lest ChangeLog for eventuelle bruddende endringer.', UPGRADE_IMPORTANT_MESSAGES_2: 'Du oppdaterer til en ny major versjon. Det er anbefalt å først lage en systembackup. Kontroller også onlinehjelp for eventuelle viktige endringer.',
WARNING_SYSTEM_BACKUP: 'Dette vil lage en sikkerhetskopi av din fullstendige systemkonfigurasjon og innstillinger. Alle passord vil være lesbare i sikkerhetskopien. Vær forsiktig med deling! Vil du fortsette?', WARNING_SYSTEM_BACKUP: 'Dette vil lage en sikkerhetskopi av din fullstendige systemkonfigurasjon og innstillinger. Alle passord vil være lesbare i sikkerhetskopien. Vær forsiktig med deling! Vil du fortsette?',
TEST_EMAIL_SUCCESSFUL: 'Test email sendt suksessfullt', TEST_EMAIL_SUCCESSFUL: 'Test email sendt suksessfullt',
SYSTEM_NAME: 'Systemnavn', SYSTEM_NAME: 'Systemnavn',
@@ -349,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...'
}; };
+4 -4
View File
@@ -311,7 +311,7 @@ const pl: BaseTranslation = {
ALLVALUES: 'Wszystkie wartości', ALLVALUES: 'Wszystkie wartości',
SPECIAL_FUNCTIONS: 'Specjalne funkcje', SPECIAL_FUNCTIONS: 'Specjalne funkcje',
WAIT_FIRMWARE: 'Firmware ściąga się i instaluje', WAIT_FIRMWARE: 'Firmware ściąga się i instaluje',
INSTALL_VERSION: 'To zainstaluje wersję {1} {0}. Jesteś pewny?', INSTALL_VERSION: 'To zainstaluje wersję {1} {0}',
UPDATE_AVAILABLE: 'aktualizacja dostępna', UPDATE_AVAILABLE: 'aktualizacja dostępna',
LATEST_VERSION: 'Jesteś używając najnowszej wersji firmware {0}', LATEST_VERSION: 'Jesteś używając najnowszej wersji firmware {0}',
PLEASE_WAIT: 'Proszę czekać', PLEASE_WAIT: 'Proszę czekać',
@@ -340,7 +340,7 @@ const pl: BaseTranslation = {
ONLINE_HELP: 'pomoc online', ONLINE_HELP: 'pomoc online',
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizuj ważne wiadomości', UPGRADE_IMPORTANT_MESSAGES: 'Aktualizuj ważne wiadomości',
UPGRADE_IMPORTANT_MESSAGES_1: 'Ta aktualizacja wymaga resetu fabrycznego. Upewnij się, że najpierw pobierzesz kopię zapasową systemu przed kontynuowaniem, a następnie przesuń tę plik po zainstalowaniu nowej wersji.', UPGRADE_IMPORTANT_MESSAGES_1: 'Ta aktualizacja wymaga resetu fabrycznego. Upewnij się, że najpierw pobierzesz kopię zapasową systemu przed kontynuowaniem, a następnie przesuń tę plik po zainstalowaniu nowej wersji.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujesz się do nowej głównej wersji. Upewnij się, że przeczytałeś ChangeLog dla wszelkich istotnych zmian.', UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujesz się na nową major wersję. Jest to zalecane, aby najpierw utworzyć kopię zapasową systemu. Sprawdź również online pomoc dla ewentualnych istotnych zmian.',
WARNING_SYSTEM_BACKUP: 'To spowoduje utworzenie kopii zapasowej całej konfiguracji i ustawień systemu. Wszystkie hasła będą widoczne w pliku kopii zapasowej. Bądź ostrożny przy udostępnianiu! Chcesz kontynuować?', WARNING_SYSTEM_BACKUP: 'To spowoduje utworzenie kopii zapasowej całej konfiguracji i ustawień systemu. Wszystkie hasła będą widoczne w pliku kopii zapasowej. Bądź ostrożny przy udostępnianiu! Chcesz kontynuować?',
TEST_EMAIL_SUCCESSFUL: 'Test email wysłany pomyślnie', TEST_EMAIL_SUCCESSFUL: 'Test email wysłany pomyślnie',
SYSTEM_NAME: 'Nazwa systemu', SYSTEM_NAME: 'Nazwa systemu',
@@ -349,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...'
}; };
+4 -4
View File
@@ -311,7 +311,7 @@ const sk: Translation = {
ALLVALUES: 'Všetky hodnoty', ALLVALUES: 'Všetky hodnoty',
SPECIAL_FUNCTIONS: 'Špeciálne funkcie', SPECIAL_FUNCTIONS: 'Špeciálne funkcie',
WAIT_FIRMWARE: 'Firmvér sa nahráva a inštaluje', WAIT_FIRMWARE: 'Firmvér sa nahráva a inštaluje',
INSTALL_VERSION: 'Týmto sa {0} verzia {1}. Si si istý?', INSTALL_VERSION: 'Týmto sa {0} verzia {1}',
UPDATE_AVAILABLE: 'dostupná aktualizácia', UPDATE_AVAILABLE: 'dostupná aktualizácia',
LATEST_VERSION: 'Používate poslednú {0} verziu firmvéru', LATEST_VERSION: 'Používate poslednú {0} verziu firmvéru',
PLEASE_WAIT: 'Čakajte prosím', PLEASE_WAIT: 'Čakajte prosím',
@@ -340,7 +340,7 @@ const sk: Translation = {
ONLINE_HELP: 'online pomoc', ONLINE_HELP: 'online pomoc',
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizovať dôležité správy', UPGRADE_IMPORTANT_MESSAGES: 'Aktualizovať dôležité správy',
UPGRADE_IMPORTANT_MESSAGES_1: 'Táto aktualizácia vyžaduje reštart základných nastavení. Uistite sa, že najprv stiahnete systémovú zálohu pred pokračovaním, a potom nahrajte tento súbor po instalácii novej verzie.', UPGRADE_IMPORTANT_MESSAGES_1: 'Táto aktualizácia vyžaduje reštart základných nastavení. Uistite sa, že najprv stiahnete systémovú zálohu pred pokračovaním, a potom nahrajte tento súbor po instalácii novej verzie.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujete sa na novú hlavnú verziu. Uistite sa, že ste prečítali ChangeLog pre akékoľvek dôležité zmeny.', UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujete sa na novú major verziu. Je vhodné najprv vytvoriť systémovú zálohu. Taktiež skontrolujte online nápovľu pre akékoľvek dôležité zmeny.',
WARNING_SYSTEM_BACKUP: 'Toto vytvorí zálohu všetkých vašich celých systémových konfigurácií a nastavení. Všetky hesla budú čitateľné v zálohovom súbore. Buďte opatrní pri zdieľaní! Chcete pokračovať?', WARNING_SYSTEM_BACKUP: 'Toto vytvorí zálohu všetkých vašich celých systémových konfigurácií a nastavení. Všetky hesla budú čitateľné v zálohovom súbore. Buďte opatrní pri zdieľaní! Chcete pokračovať?',
TEST_EMAIL_SUCCESSFUL: 'Test email bol úspešne odoslaný', TEST_EMAIL_SUCCESSFUL: 'Test email bol úspešne odoslaný',
SYSTEM_NAME: 'Názov systému', SYSTEM_NAME: 'Názov systému',
@@ -349,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...'
}; };
+4 -4
View File
@@ -311,7 +311,7 @@ const sv: Translation = {
ALLVALUES: 'Alla värden', ALLVALUES: 'Alla värden',
SPECIAL_FUNCTIONS: 'Specialfunktioner', SPECIAL_FUNCTIONS: 'Specialfunktioner',
WAIT_FIRMWARE: 'Firmware laddas upp och installeras', WAIT_FIRMWARE: 'Firmware laddas upp och installeras',
INSTALL_VERSION: 'Det här kommer {0} version {1}. Är du säker?', INSTALL_VERSION: 'Det här kommer {0} version {1}',
UPDATE_AVAILABLE: 'uppdatering tillgänglig', UPDATE_AVAILABLE: 'uppdatering tillgänglig',
LATEST_VERSION: 'Du använder den senaste {0} firmwareversionen.', LATEST_VERSION: 'Du använder den senaste {0} firmwareversionen.',
PLEASE_WAIT: 'Var god vänta', PLEASE_WAIT: 'Var god vänta',
@@ -340,7 +340,7 @@ const sv: Translation = {
ONLINE_HELP: 'online hjälp', ONLINE_HELP: 'online hjälp',
UPGRADE_IMPORTANT_MESSAGES: 'Uppdatera viktiga meddelanden', UPGRADE_IMPORTANT_MESSAGES: 'Uppdatera viktiga meddelanden',
UPGRADE_IMPORTANT_MESSAGES_1: 'Denna uppdatering kräver en fabriksåterställning. Se till att du först laddar ned en System Backup innan du fortsätter, och ladda upp denna fil efter att den nya versionen är installerad.', UPGRADE_IMPORTANT_MESSAGES_1: 'Denna uppdatering kräver en fabriksåterställning. Se till att du först laddar ned en System Backup innan du fortsätter, och ladda upp denna fil efter att den nya versionen är installerad.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Du uppdaterar till en ny huvudversion. Se till att du har läst ChangeLog för eventuella brkande ändringar.', UPGRADE_IMPORTANT_MESSAGES_2: 'Du uppdaterar till en ny major version. Det är rekommenderat att först skapa en systembackup. Kontrollera också onlinehjälpen för eventuella viktiga ändringar.',
WARNING_SYSTEM_BACKUP: 'Detta kommer att skapa en säkerhetskopia av din fullständiga systemkonfiguration och inställningar. Alla lösenord kommer att vara läsbara i säkerhetskopien. Var försiktig med att dela! Vill du fortsätta?', WARNING_SYSTEM_BACKUP: 'Detta kommer att skapa en säkerhetskopia av din fullständiga systemkonfiguration och inställningar. Alla lösenord kommer att vara läsbara i säkerhetskopien. Var försiktig med att dela! Vill du fortsätta?',
TEST_EMAIL_SUCCESSFUL: 'Test email skickad lyckades', TEST_EMAIL_SUCCESSFUL: 'Test email skickad lyckades',
SYSTEM_NAME: 'Systemnamn', SYSTEM_NAME: 'Systemnamn',
@@ -349,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...'
}; };
+4 -4
View File
@@ -311,7 +311,7 @@ const tr: Translation = {
ALLVALUES: 'Tüm Değerler', ALLVALUES: 'Tüm Değerler',
SPECIAL_FUNCTIONS: 'Özel Fonksiyonlar', SPECIAL_FUNCTIONS: 'Özel Fonksiyonlar',
WAIT_FIRMWARE: 'Firmware yükleniyor ve yükleniyor', WAIT_FIRMWARE: 'Firmware yükleniyor ve yükleniyor',
INSTALL_VERSION: 'Bu {0} sürümü {1} yükleyecek. Emin misiniz?', INSTALL_VERSION: 'Bu {0} sürümü {1} yükleyecek',
UPDATE_AVAILABLE: 'güncellendi!', UPDATE_AVAILABLE: 'güncellendi!',
LATEST_VERSION: 'En son {0} firmware sürümünü kullanıyorsunuz.', LATEST_VERSION: 'En son {0} firmware sürümünü kullanıyorsunuz.',
PLEASE_WAIT: 'Lütfen bekleyin', PLEASE_WAIT: 'Lütfen bekleyin',
@@ -340,7 +340,7 @@ const tr: Translation = {
ONLINE_HELP: 'online yardım', ONLINE_HELP: 'online yardım',
UPGRADE_IMPORTANT_MESSAGES: 'Önemli Mesajları Güncelle', UPGRADE_IMPORTANT_MESSAGES: 'Önemli Mesajları Güncelle',
UPGRADE_IMPORTANT_MESSAGES_1: 'Bu güncelleme továrnı ayarlarını gerektirir. Yapılandırmanızı ve ayarlarınızı önce yedekleyin ve ardından yeni sürüm yüklendikten sonra yükleyin.', UPGRADE_IMPORTANT_MESSAGES_1: 'Bu güncelleme továrnı ayarlarını gerektirir. Yapılandırmanızı ve ayarlarınızı önce yedekleyin ve ardından yeni sürüm yüklendikten sonra yükleyin.',
UPGRADE_IMPORTANT_MESSAGES_2: 'Yeni bir büyük sürüme yükselteceksiniz. Değişiklikleri ChangeLogı okuduğunuzdan emin olun.', UPGRADE_IMPORTANT_MESSAGES_2: 'Yeni bir major sürüme güncelleme yapıyorsunuz. Sistem yedeklemesi yapmanız önerilir. Ayrıca online yardımı kontrol ederek önemli değişiklikleri gözden geçirin.',
WARNING_SYSTEM_BACKUP: 'Bu, sistem yapılandırmanızı ve ayarlarınızın bir yedeklemesi oluşturacaktır. Tüm şifreler yedekleme dosyasında okunabilir olacaktır. Paylaşırken dikkatli olun! Devam etmek istediğinize emin misiniz?', WARNING_SYSTEM_BACKUP: 'Bu, sistem yapılandırmanızı ve ayarlarınızın bir yedeklemesi oluşturacaktır. Tüm şifreler yedekleme dosyasında okunabilir olacaktır. Paylaşırken dikkatli olun! Devam etmek istediğinize emin misiniz?',
TEST_EMAIL_SUCCESSFUL: 'Test email başarıyla gönderildi', TEST_EMAIL_SUCCESSFUL: 'Test email başarıyla gönderildi',
SYSTEM_NAME: 'Sistem Adı', SYSTEM_NAME: 'Sistem Adı',
@@ -349,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...'
}; };
+9 -4
View File
@@ -20,19 +20,24 @@ export enum WiFiEncryptionType {
WIFI_AUTH_WPA2_WPA3_PSK = 7 WIFI_AUTH_WPA2_WPA3_PSK = 7
} }
export interface IPv6AddressType {
address: string;
scope: string; // global, unique local, site local or link local
}
export interface NetworkStatusType { export interface NetworkStatusType {
status: NetworkConnectionStatus; status: NetworkConnectionStatus;
local_ip: string; local_ip: string;
local_ipv6: string; ipv6?: IPv6AddressType[];
mac_address: string; mac_address: string;
rssi: number; rssi: number;
ssid: string; ssid: string;
bssid: string; bssid: string;
channel: number; channel: number;
subnet_mask: string; subnet_mask: string;
gateway_ip: string; gateway_ip?: string;
dns_ip_1: string; gateway_ipv6?: string[];
dns_ip_2: string; dns?: string[];
hostname: string; hostname: string;
reconnect_count: number; reconnect_count: number;
} }
+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.8"
}, },
"packageManager": "pnpm@12.4.1" "packageManager": "pnpm@12.4.2"
} }
+80 -80
View File
@@ -7,153 +7,153 @@ importers:
configDependencies: {} configDependencies: {}
packageManagerDependencies: packageManagerDependencies:
pnpm: pnpm:
specifier: 12.4.1 specifier: 12.4.2
version: 12.4.1 version: 12.4.2
packages: packages:
'@pnpm/exe.android-arm64@12.4.1': '@pnpm/exe.android-arm64@12.4.2':
resolution: {integrity: sha512-/HwsqXMSmlOfgtV9+O0ratzjV6Vd/8n1hh4rGHpCGmDURvt52MwZxdbhyxP4K03ZfvgSDvotFPr8O+RzE5Eu8A==} resolution: {integrity: sha512-E255MbcQ0V1577M2BV0ajWuP7KmTPYA0jqZnk3rK6Lq3kTvZWulMMb7iqRmnLsQbyWTkMEB2CfyM70loVDA8xg==}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@pnpm/exe.android-x64@12.4.1': '@pnpm/exe.android-x64@12.4.2':
resolution: {integrity: sha512-+l74Qb4c2YjOzNKHXJLg+1wr8xHM1ckkUhnU5KRUK9TJqiiczt6yeTZqqzHIlJ8i6pAoj5V0OJevDRwnQKLgrQ==} resolution: {integrity: sha512-J1pSeCUwuKxMG70ZzpWn8JzElxiEa8NN/3N0SlZRtU2xbztChaJCKF3HaBNIj9yPfeofWzJo/lwNvDDjraQuZw==}
cpu: [x64] cpu: [x64]
os: [android] os: [android]
'@pnpm/exe.darwin-arm64@12.4.1': '@pnpm/exe.darwin-arm64@12.4.2':
resolution: {integrity: sha512-6rkZkT3iGfaxknUdGHraqSWFvTa6N0ajAHluv9Ax0GRWs0sIcGNiFhDopv6xSZCsJZmG483aNS/b6UEDy3blfw==} resolution: {integrity: sha512-A0WDo8iErfZBXgrLseQxw8i8Y9ctUpOEl/Uu+cubnTzpD8tT9ykIB548L8YTM2WD4OS+ZOHSxy8aGZcvKq8PaQ==}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@pnpm/exe.darwin-x64@12.4.1': '@pnpm/exe.darwin-x64@12.4.2':
resolution: {integrity: sha512-Vb1CHlR88HghC1qUxjxjs82zQSXnXacPD+btG2CmG8Q/hBU7Q0/b2YWJKSQqZXicunV5khFtfTlAwJddV9RkYA==} resolution: {integrity: sha512-MSgJdovBWHcb5DEOvfPH9yNi/T5O1Xa4ess+E1ESGo/5yuty7S4JoiIjami+fsNXfnoQMlwsMUqYU4zAvBcNCw==}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@pnpm/exe.freebsd-x64@12.4.1': '@pnpm/exe.freebsd-x64@12.4.2':
resolution: {integrity: sha512-iT3iHz3Nl0Sxxj7UPOtZ/aQ81AFsQhjoeWMAlPkSRO04gsgDGAXv3UYxOFaesMWsfNxaGn0A+CITbuCHFF66FA==} resolution: {integrity: sha512-h2YumlQSNvgbRPv+RXwABohX65f9bOBZn+jMIt7bFDISZPCzQ+Nvpt6Awbp4ip5PwQgYxbu5iREJ1fHE39Fm8A==}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.4.1': '@pnpm/exe.linux-arm64-musl@12.4.2':
resolution: {integrity: sha512-aBooZfNXM5f+OGUgCAMFWpE/kAhsWfvmqIyMtHy6zl3aNxyInWrcY/Saln/UElzo7lZWMC0Yktroxd/2J26lNQ==} resolution: {integrity: sha512-LwSEtSEDTv6S51YLs3YvSkPyun/QmfMic1UGICUkPWFu6ByP43RdMlkKvmVkfGhAYCpnxO057vrmyJqtfZrPCA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@pnpm/exe.linux-arm64@12.4.1': '@pnpm/exe.linux-arm64@12.4.2':
resolution: {integrity: sha512-TlOdacTTP09BgcMvwWBFRsu8VAjfwqwnslBj+XSq1JFM3ck4f3k+1O/747EuctxZxs3/o785b6Q3s7Pd92Ptsg==} resolution: {integrity: sha512-2dSiDXyhx+RTHsewxex8f/jVjqQXWJ2oow4kCVHEWdZKeBpgMxvZ6fHkTAUBJXgqhbKIDHvuNlZBP7gJfUWL5A==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@pnpm/exe.linux-ppc64@12.4.1': '@pnpm/exe.linux-ppc64@12.4.2':
resolution: {integrity: sha512-r/ab/MlIBo75oizUP5ITiziCCrnXz4SJwfErLQ+603AshB+Yq7xTMCoMtzTSCAMu6aaymIKkAFtZvd2J75Wq0w==} resolution: {integrity: sha512-8Itc+jQk+MTz04LS9D1RcH+VctAWmzM4l1cJQ+Sx7pAJNo7EKHdRMbC0Tok1jyQ7eEHNJZD5EleYneZqWfZM+g==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@pnpm/exe.linux-riscv64@12.4.1': '@pnpm/exe.linux-riscv64@12.4.2':
resolution: {integrity: sha512-C/D1QWdKMiB8+wv/spl1rGITFUuqC+aI/fb6h8NB5sqxsOaGXeYc/g891oCuzggHn6SODk9p+I09hI76x/cMNw==} resolution: {integrity: sha512-hleOeqhTVpH+z9RVMGnxvU4ZnrkBUClWjCbHD9u6kwyqhGSpevoU1wTGish+CBRhmIgMAy9pAfFpqhbAKOKNfw==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@pnpm/exe.linux-s390x@12.4.1': '@pnpm/exe.linux-s390x@12.4.2':
resolution: {integrity: sha512-nxz5zD4yXt94uzbStDk0QTPKW+aE92hH1b5tFXK9ctB1HE9Xcq1vjTiA2LsZMFC6p4gdu5OwQeFqYNAVGa6QlA==} resolution: {integrity: sha512-LAsQRRdP9aToENR6dtcIJ9l+e1zMYOX0tQcLGpRyLPVBQcYRLlvAPcmDshsiIHQjp05SDa9FI0czX1ZQ1a7a/A==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.4.1': '@pnpm/exe.linux-x64-musl@12.4.2':
resolution: {integrity: sha512-5AwgFdGhVUg2kIweYGfxzSLEHiIG77PQhZAkXC3TwofQHsu1Wr+TrV5/rNX2PopFnHRzuE581zoB8F6Wle32yg==} 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.4.1': '@pnpm/exe.linux-x64@12.4.2':
resolution: {integrity: sha512-FJOZuuuQMhp0oLzBtcKkLXknBI92hfkmSnlKc47vfin4HrmfID5khY2lGekL9tCzk1cpR+HShEw/meFl+nHtzQ==} resolution: {integrity: sha512-/pbt0UVTa8NMDhzOWLQRfZ6G9ROKXlJPZtx845BqyWfc7hCrWXhTLBd70yO2y8+E+IWN3oHM1s/JsIQNGk1yvg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@pnpm/exe.win32-arm64@12.4.1': '@pnpm/exe.win32-arm64@12.4.2':
resolution: {integrity: sha512-OO7eKBL9S+xk5hRy+JUUZSNJknGuGe3GEUffsrlC2LbqKF02g+VX1anGIzSbJDvkkdnpJhSlxF5AUz2JzJu2Jw==} resolution: {integrity: sha512-PsW19e4dAUNpZ0cS9flaxFuAmpt2dKlH/Vvi8TZ4qyJjjQzue/CEsWm+6wKVP4CJ7IT8RdL4qh+soOPWIgF2Zw==}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@pnpm/exe.win32-x64@12.4.1': '@pnpm/exe.win32-x64@12.4.2':
resolution: {integrity: sha512-x7gJHZgHo6hp354xCYA2NvoFzYJkHwovt2kVsUBK5EmXULLcP51JGMq09CX+5FRFJUZFKoXjLu3mZWmfP7o6PQ==} resolution: {integrity: sha512-+xGoeE0g55ztWvl8i5QqdmNfW3nIrTVcoQrNshEOwxthm9Ag48oXton3uxOA3/SANyz5AXexEjj2KO20NnOrEw==}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
pnpm@12.4.1: pnpm@12.4.2:
resolution: {integrity: sha512-LoHjmdc/6DkNqyXgaqeIq3pZCCSNL1o3D4K0gRR6ano2e/gEj5pv22Rg8hpm8FQt7bi5TKLIcjWWdBkgsWVtTA==} resolution: {integrity: sha512-CK3GYTGAJ1x8ntraOdzwjJxhrU5+rzMKTzRh8QKw+QdCNFTRF/mOctR/7wYWBwZE17/8lzpqV/UJCm18NosHyQ==}
engines: {node: '>=18.*'} engines: {node: '>=18.*'}
hasBin: true hasBin: true
snapshots: snapshots:
'@pnpm/exe.android-arm64@12.4.1': '@pnpm/exe.android-arm64@12.4.2':
optional: true optional: true
'@pnpm/exe.android-x64@12.4.1': '@pnpm/exe.android-x64@12.4.2':
optional: true optional: true
'@pnpm/exe.darwin-arm64@12.4.1': '@pnpm/exe.darwin-arm64@12.4.2':
optional: true optional: true
'@pnpm/exe.darwin-x64@12.4.1': '@pnpm/exe.darwin-x64@12.4.2':
optional: true optional: true
'@pnpm/exe.freebsd-x64@12.4.1': '@pnpm/exe.freebsd-x64@12.4.2':
optional: true optional: true
'@pnpm/exe.linux-arm64-musl@12.4.1': '@pnpm/exe.linux-arm64-musl@12.4.2':
optional: true optional: true
'@pnpm/exe.linux-arm64@12.4.1': '@pnpm/exe.linux-arm64@12.4.2':
optional: true optional: true
'@pnpm/exe.linux-ppc64@12.4.1': '@pnpm/exe.linux-ppc64@12.4.2':
optional: true optional: true
'@pnpm/exe.linux-riscv64@12.4.1': '@pnpm/exe.linux-riscv64@12.4.2':
optional: true optional: true
'@pnpm/exe.linux-s390x@12.4.1': '@pnpm/exe.linux-s390x@12.4.2':
optional: true optional: true
'@pnpm/exe.linux-x64-musl@12.4.1': '@pnpm/exe.linux-x64-musl@12.4.2':
optional: true optional: true
'@pnpm/exe.linux-x64@12.4.1': '@pnpm/exe.linux-x64@12.4.2':
optional: true optional: true
'@pnpm/exe.win32-arm64@12.4.1': '@pnpm/exe.win32-arm64@12.4.2':
optional: true optional: true
'@pnpm/exe.win32-x64@12.4.1': '@pnpm/exe.win32-x64@12.4.2':
optional: true optional: true
pnpm@12.4.1: pnpm@12.4.2:
optionalDependencies: optionalDependencies:
'@pnpm/exe.android-arm64': 12.4.1 '@pnpm/exe.android-arm64': 12.4.2
'@pnpm/exe.android-x64': 12.4.1 '@pnpm/exe.android-x64': 12.4.2
'@pnpm/exe.darwin-arm64': 12.4.1 '@pnpm/exe.darwin-arm64': 12.4.2
'@pnpm/exe.darwin-x64': 12.4.1 '@pnpm/exe.darwin-x64': 12.4.2
'@pnpm/exe.freebsd-x64': 12.4.1 '@pnpm/exe.freebsd-x64': 12.4.2
'@pnpm/exe.linux-arm64': 12.4.1 '@pnpm/exe.linux-arm64': 12.4.2
'@pnpm/exe.linux-arm64-musl': 12.4.1 '@pnpm/exe.linux-arm64-musl': 12.4.2
'@pnpm/exe.linux-ppc64': 12.4.1 '@pnpm/exe.linux-ppc64': 12.4.2
'@pnpm/exe.linux-riscv64': 12.4.1 '@pnpm/exe.linux-riscv64': 12.4.2
'@pnpm/exe.linux-s390x': 12.4.1 '@pnpm/exe.linux-s390x': 12.4.2
'@pnpm/exe.linux-x64': 12.4.1 '@pnpm/exe.linux-x64': 12.4.2
'@pnpm/exe.linux-x64-musl': 12.4.1 '@pnpm/exe.linux-x64-musl': 12.4.2
'@pnpm/exe.win32-arm64': 12.4.1 '@pnpm/exe.win32-arm64': 12.4.2
'@pnpm/exe.win32-x64': 12.4.1 '@pnpm/exe.win32-x64': 12.4.2
--- ---
lockfileVersion: '9.0' lockfileVersion: '9.0'
@@ -175,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.8)
prettier: prettier:
specifier: ^3.9.6 specifier: ^3.9.8
version: 3.9.6 version: 3.9.8
packages: packages:
@@ -202,8 +202,8 @@ packages:
resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/parser@7.29.8': '@babel/parser@7.29.9':
resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
@@ -258,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==}
@@ -303,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.8:
resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} resolution: {integrity: sha512-WRFq3Wn3WId7LLROfMLdH7xaFr2jR62wU8nLO6rQUOLOxNZUviyJQs1M0iIhLexSFy+L+w0ch66wtoO2jRjG0A==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
@@ -318,7 +318,7 @@ snapshots:
'@babel/generator@7.29.8': '@babel/generator@7.29.8':
dependencies: dependencies:
'@babel/parser': 7.29.8 '@babel/parser': 7.29.9
'@babel/types': 7.29.8 '@babel/types': 7.29.8
'@jridgewell/gen-mapping': 0.3.13 '@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31 '@jridgewell/trace-mapping': 0.3.31
@@ -330,14 +330,14 @@ snapshots:
'@babel/helper-validator-identifier@7.29.7': {} '@babel/helper-validator-identifier@7.29.7': {}
'@babel/parser@7.29.8': '@babel/parser@7.29.9':
dependencies: dependencies:
'@babel/types': 7.29.8 '@babel/types': 7.29.8
'@babel/template@7.29.7': '@babel/template@7.29.7':
dependencies: dependencies:
'@babel/code-frame': 7.29.7 '@babel/code-frame': 7.29.7
'@babel/parser': 7.29.8 '@babel/parser': 7.29.9
'@babel/types': 7.29.8 '@babel/types': 7.29.8
'@babel/traverse@7.29.8': '@babel/traverse@7.29.8':
@@ -345,7 +345,7 @@ snapshots:
'@babel/code-frame': 7.29.7 '@babel/code-frame': 7.29.7
'@babel/generator': 7.29.8 '@babel/generator': 7.29.8
'@babel/helper-globals': 7.29.7 '@babel/helper-globals': 7.29.7
'@babel/parser': 7.29.8 '@babel/parser': 7.29.9
'@babel/template': 7.29.7 '@babel/template': 7.29.7
'@babel/types': 7.29.8 '@babel/types': 7.29.8
debug: 4.4.3 debug: 4.4.3
@@ -373,23 +373,23 @@ 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.8)':
dependencies: dependencies:
'@babel/generator': 7.29.8 '@babel/generator': 7.29.8
'@babel/parser': 7.29.8 '@babel/parser': 7.29.9
'@babel/traverse': 7.29.8 '@babel/traverse': 7.29.8
'@babel/types': 7.29.8 '@babel/types': 7.29.8
javascript-natural-sort: 0.7.1 javascript-natural-sort: 0.7.1
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.8
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
@@ -409,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: {}
@@ -421,4 +421,4 @@ snapshots:
picocolors@1.1.1: {} picocolors@1.1.1: {}
prettier@3.9.6: {} prettier@3.9.8: {}
+43 -37
View File
@@ -98,13 +98,13 @@ let system_status = {
wifi_rssi: -41, wifi_rssi: -41,
esp_platform: 'ESP32', esp_platform: 'ESP32',
build_flags: 'DEMO', build_flags: 'DEMO',
cpu_type: 'ESP32', cpu_type: 'ESP32-D0WD-V3',
cpu_rev: 0, cpu_rev: 0,
cpu_cores: 2, cpu_cores: 2,
cpu_freq_mhz: 240, cpu_freq_mhz: 240,
max_alloc_heap: 191, max_alloc_heap: 191,
arduino_version: 'ESP32 Arduino v2.0.17', arduino_version: 'Tasmota Arduino v3.3.8',
sdk_version: 'v4.4.7', sdk_version: '5.5.4.260407',
partition: 'app0', partition: 'app0',
flash_chip_size: 16384, flash_chip_size: 16384,
flash_chip_speed: 80000000, flash_chip_speed: 80000000,
@@ -127,13 +127,13 @@ let system_status = {
}, },
{ {
partition: 'app1', partition: 'app1',
version: '3.8.1-dev.40', version: '3.8.3',
install_date: '2025-03-01T13:29:13.999Z', install_date: '2025-03-01T13:29:13.999Z',
size: 4672 size: 4672
}, },
{ {
partition: 'factory', partition: 'factory',
version: '3.8.1-dev.39', version: '3.8.4',
install_date: '2025-03-01T13:29:13.999Z', install_date: '2025-03-01T13:29:13.999Z',
size: 4672 size: 4672
} }
@@ -141,24 +141,22 @@ let system_status = {
// partitions: [], // partitions: [],
developer_mode: settings.developer_mode, developer_mode: settings.developer_mode,
disable_reset: settings.disable_reset, disable_reset: settings.disable_reset,
model: '', model: 'BBQKees Electronics E32V2.2 rev.2.2/2024074',
board: '', board: 'E32V2_2',
// model: 'BBQKees Electronics EMS Gateway E32 V2 (E32 V2.0 P3/2024011)', status: 0
// board: 'E32V2', // status: 3
// status: 0,
status: 3
}; };
// Test Versioning // Test the versioning
let DEV_VERSION_IS_UPGRADEABLE: boolean; let DEV_VERSION_IS_UPGRADEABLE: boolean;
let STABLE_VERSION_IS_UPGRADEABLE: boolean; let STABLE_VERSION_IS_UPGRADEABLE: boolean;
let THIS_VERSION: string; 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'; let LATEST_DEV_VERSION = '3.9.0-dev.1';
// scenarios for testing versioning // scenarios for testing versioning
// let version_test = 0; // on latest stable, or switch to dev 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 = 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 = 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 = 3; // upgrade dev to latest, or switch to stable
// let version_test = 4; // downgrade to an older dev, or switch back to stable // let version_test = 4; // downgrade to an older dev, or switch back to stable
@@ -210,14 +208,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
@@ -225,13 +225,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;
} }
@@ -442,25 +444,24 @@ function executeCommand(name: string) {
} }
// called by Action endpoint upgradeImportantMessages // 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) { 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; let upgradeImportantMessageType_n = 0;
// check file extensions // check file extensions first
if (version.endsWith('.md5') || version.endsWith('.json')) { if (version.endsWith('.md5') || version.endsWith('.json')) {
upgradeImportantMessageType_n = 0; // digest / backup restore: no upgrade warning upgradeImportantMessageType_n = 0; // digest / backup restore: no upgrade warning
} else if (version.endsWith('.bin')) { } 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 // 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('.'); const versionNumber = version.split('.');
if (versionNumber[0] === '3' && versionNumber[1] === '8') { if (versionNumber[0] === '3' && versionNumber[1] === '8') {
upgradeImportantMessageType_n = 1; // make it 1, factory reset required upgradeImportantMessageType_n = 2; // major upgrade
} }
} else { } else {
// this is a version string like "3.9.0" // the parameter is a version string like "3.9.0"
upgradeImportantMessageType_n = 1; // make it 1, for testing, meaning a factory reset is required upgradeImportantMessageType_n = 2; // major upgrade
} }
console.log( console.log(
@@ -576,7 +577,7 @@ let ap_settings = {
max_clients: 4 max_clients: 4
}; };
const ap_status = { const ap_status = {
status: 1, status: 0, // active - an inactive AP reports no address at all
ip_address: '192.168.4.1', ip_address: '192.168.4.1',
mac_address: '3C:61:05:03:AB:2D', mac_address: '3C:61:05:03:AB:2D',
station_num: 0 station_num: 0
@@ -608,6 +609,11 @@ let network_settings = {
const network_status = { const network_status = {
status: 3, status: 3,
local_ip: '10.10.10.101', local_ip: '10.10.10.101',
ipv6: [
{ address: '2001:db8:1234:5678::a1b2', scope: 'global' },
{ address: 'fd00:1234:5678::a1b2', scope: 'unique local' },
{ address: 'fe80::3e61:5ff:fe03:ab2c', scope: 'link local' }
],
mac_address: '3C:61:05:03:AB:2C', mac_address: '3C:61:05:03:AB:2C',
rssi: -41, rssi: -41,
ssid: 'home', ssid: 'home',
@@ -615,8 +621,8 @@ const network_status = {
channel: 11, channel: 11,
subnet_mask: '255.255.255.0', subnet_mask: '255.255.255.0',
gateway_ip: '10.10.10.1', gateway_ip: '10.10.10.1',
dns_ip_1: '10.10.10.1', gateway_ipv6: ['fe80::1', 'fe80::2'],
dns_ip_2: '0.0.0.0', dns: ['10.10.10.1', 'fd00:1234:5678::1'],
hostname: 'ems-esp', hostname: 'ems-esp',
reconnect_count: 1 reconnect_count: 1
}; };
+2 -1
View File
@@ -41,7 +41,8 @@ custom_password = admin
; ** upload settings ** ; ** upload settings **
; for direct USB, which is the default you can set an optional port for example: ; for direct USB, which is the default you can set an optional port for example:
; upload_port = /dev/ttyUSB0 or COM3 ; upload_port = /dev/ttyUSB0 or COM3
; ; upload_speed = 921600
; for direct via HTTP (OTA) set: ; for direct via HTTP (OTA) set:
; upload_protocol = custom ; upload_protocol = custom
; custom_emsesp_ip = <ip address> or ems-esp.local ; custom_emsesp_ip = <ip address> or ems-esp.local
+29 -28
View File
@@ -6,9 +6,9 @@ abluftqualität
abluftqualitätsfühler abluftqualitätsfühler
abrt abrt
absburnpow absburnpow
ACAC acac
ACAH acah
ACAO acao
accomodate accomodate
aceotrope aceotrope
activehigh activehigh
@@ -48,6 +48,7 @@ appused
apssid apssid
apsta apsta
arduhal arduhal
arduinoespressif
arduinojson arduinojson
arduinoota arduinoota
arjan arjan
@@ -70,6 +71,7 @@ ausreichend
authmode authmode
autodst autodst
automode automode
autonegotiation
autoscroll autoscroll
auxelecheatnrgcons auxelecheatnrgcons
auxelecheatnrgconsheating auxelecheatnrgconsheating
@@ -139,6 +141,7 @@ bytetime
calinttemp calinttemp
catagory catagory
cbuf cbuf
ccrc
cerapur cerapur
cfamily cfamily
changeloglevel changeloglevel
@@ -289,6 +292,7 @@ dewoffset
dewpoint dewpoint
dewtemp dewtemp
dewtemperature dewtemperature
dhcpc
dhwmintemp dhwmintemp
dhwprio dhwprio
dhwvalve dhwvalve
@@ -362,6 +366,7 @@ entitypath
enumupdate enumupdate
enviline enviline
equipt equipt
eraseap
erct erct
erdwärmetauscher erdwärmetauscher
errordisp errordisp
@@ -458,6 +463,7 @@ gpoc
greenstar greenstar
grokhotkov grokhotkov
haclimate haclimate
handshaked
hardwarestatus hardwarestatus
harwerth harwerth
headersize headersize
@@ -501,7 +507,7 @@ heatsource
heatsources heatsources
heatstarts heatstarts
heattemp heattemp
heattransfer heattransfer
heattransfersystem heattransfersystem
heatup heatup
heatvalve heatvalve
@@ -578,6 +584,7 @@ iconua
idedata idedata
iele iele
ignwork ignwork
iile
iiwi iiwi
ijoy ijoy
imlhd imlhd
@@ -615,6 +622,7 @@ itty
ivtclock ivtclock
jtag jtag
junkersclock junkersclock
juststopped
kamin kamin
karlesky karlesky
keelbypass keelbypass
@@ -706,6 +714,7 @@ mbap
mbar mbar
mbed mbed
mbedtsl mbedtsl
mbit
mdio mdio
mdns mdns
melis melis
@@ -744,7 +753,7 @@ mlongcalls
mmio mmio
mmode mmode
mmplus mmplus
mockServer mockserver
modbus modbus
modee modee
modetype modetype
@@ -762,6 +771,7 @@ msgstr
msys msys
mtext mtext
mult mult
mwdt
mysys mysys
nachheiz nachheiz
nachheizreg nachheizreg
@@ -780,6 +790,7 @@ neopixel
netcmp netcmp
netflowtemp netflowtemp
netif netif
netifs
newdatetime newdatetime
newnow newnow
newpcb newpcb
@@ -836,6 +847,7 @@ oilpreheat
oldoffset oldoffset
olimex olimex
olimexpoe olimexpoe
olimexpoew
omea omea
onchange onchange
onetimekey onetimekey
@@ -843,6 +855,7 @@ onewire
ooops ooops
operatingstate operatingstate
opmode opmode
optimisations
optipng optipng
osrc osrc
outdoorhold outdoorhold
@@ -877,7 +890,7 @@ poolsettemp
poolshunt poolshunt
poolshuntstatus poolshuntstatus
poolstarts poolstarts
pooltemp pooltemp
portx portx
powerreduction powerreduction
powervalues powervalues
@@ -944,7 +957,7 @@ rawstr
rcplus rcplus
readback readback
readelf readelf
READYCLIENT readyclient
readymail readymail
recp recp
recved recved
@@ -1000,6 +1013,7 @@ roomsettings
roomtemp roomtemp
roomtempdiff roomtempdiff
roomtemperature roomtemperature
roomtempoffset
roomthermostat roomthermostat
roomthermostats roomthermostats
rrts rrts
@@ -1029,6 +1043,7 @@ seltemp
seltemplow seltemplow
seltempoff seltempoff
seltempsingle seltempsingle
sendmail
sendtype sendtype
sensordata sensordata
sensorfails sensorfails
@@ -1037,6 +1052,7 @@ sensorname
sensorreads sensorreads
sensortype sensortype
seperator seperator
serialises
serverclass serverclass
servicecode servicecode
servicecodenumber servicecodenumber
@@ -1087,11 +1103,13 @@ soll
speicher speicher
spidqs spidqs
spiio spiio
spiram
squaremeters squaremeters
srts srts
staconnected staconnected
stadisconnected stadisconnected
staipassigned staipassigned
startm
startshp startshp
starttemp starttemp
starttemperature starttemperature
@@ -1112,6 +1130,7 @@ substract
summermode summermode
summersetmode summersetmode
summertemp summertemp
sumr
supchan supchan
suprapur suprapur
svenska svenska
@@ -1161,6 +1180,7 @@ tempparmode
temprature temprature
tempsensor tempsensor
tempstatus tempstatus
testemail
testtemperature testtemperature
thermaldisinfect thermaldisinfect
thermostate thermostate
@@ -1271,6 +1291,7 @@ werror
wextra wextra
whenmodeoff whenmodeoff
wificlient wificlient
wifioff
wifireconnects wifireconnects
wifistrength wifistrength
winsock winsock
@@ -1281,6 +1302,7 @@ workm
workmin workmin
worktime worktime
wouldblock wouldblock
wrover
wswitch wswitch
wtype wtype
wunused wunused
@@ -1330,24 +1352,3 @@ zyxwvutsrqponmlkjihgfedcba
öffnen öffnen
česky česky
živanović živanović
MWDT
juststopped
handshaked
startm
netifs
testemail
sendmail
serialises
SPIRAM
optimisations
IILE
Sumr
eraseap
roomtempoffset
dhcpc
wifioff
autonegotiation
Mbit
WROVER
OLIMEXPOEW
arduinoespressif
+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))"
+3 -3
View File
@@ -14,9 +14,9 @@ void APStatus::apStatus(AsyncWebServerRequest * request) {
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
root["status"] = _apSettingsService->getAPNetworkStatus(); root["status"] = _apSettingsService->getAPNetworkStatus();
root["ip_address"] = emsesp::EMSESP::network_.getLocalIP(); root["ip_address"] = emsesp::EMSESP::network_.getAPIP();
root["mac_address"] = emsesp::EMSESP::network_.getMacAddress(); root["mac_address"] = emsesp::EMSESP::network_.getAPMacAddress();
root["station_num"] = emsesp::EMSESP::network_.getStationNum(); root["station_num"] = emsesp::EMSESP::network_.getAPStationNum();
response->setLength(); response->setLength();
request->send(response); request->send(response);
+44 -23
View File
@@ -2,6 +2,44 @@
#include <emsesp.h> #include <emsesp.h>
namespace {
void add_ipv6(JsonObject root, NetworkInterface & netif) {
auto found = emsesp::Network::ipv6_addresses(netif);
if (found.empty()) {
return;
}
JsonArray addresses = root["ipv6"].to<JsonArray>();
for (const auto & ipv6 : found) {
JsonObject entry = addresses.add<JsonObject>();
entry["address"] = ipv6.ip.toString();
entry["scope"] = ipv6.scope;
}
}
void add_dns(JsonObject root, const NetworkInterface & netif) {
JsonArray servers = root["dns"].to<JsonArray>();
for (const auto & ip : emsesp::Network::dns_servers(netif)) {
servers.add(ip.toString());
}
}
void add_gateways(JsonObject root, NetworkInterface & netif) {
if (IPUtils::isSet(netif.gatewayIP())) {
root["gateway_ip"] = netif.gatewayIP().toString();
}
auto found = emsesp::Network::ipv6_gateways(netif);
if (found.empty()) {
return;
}
JsonArray gateways = root["gateway_ipv6"].to<JsonArray>();
for (const auto & ip : found) {
gateways.add(ip.toString());
}
}
} // namespace
NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) { NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) {
securityManager->addEndpoint(server, NETWORK_STATUS_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) { securityManager->addEndpoint(server, NETWORK_STATUS_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
networkStatus(request); networkStatus(request);
@@ -27,21 +65,13 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
if (emsesp::EMSESP::network_.ethernet_connected()) { if (emsesp::EMSESP::network_.ethernet_connected()) {
// Ethernet // Ethernet
root["local_ip"] = ETH.localIP().toString(); root["local_ip"] = ETH.localIP().toString();
root["local_ipv6"] = ETH.linkLocalIPv6().toString();
root["mac_address"] = ETH.macAddress(); root["mac_address"] = ETH.macAddress();
root["subnet_mask"] = ETH.subnetMask().toString(); root["subnet_mask"] = ETH.subnetMask().toString();
root["gateway_ip"] = ETH.gatewayIP().toString(); add_gateways(root, ETH);
IPAddress dnsIP1 = ETH.dnsIP(0); add_ipv6(root, ETH);
IPAddress dnsIP2 = ETH.dnsIP(1); add_dns(root, ETH);
if (IPUtils::isSet(dnsIP1)) {
root["dns_ip_1"] = dnsIP1.toString();
}
if (IPUtils::isSet(dnsIP2)) {
root["dns_ip_2"] = dnsIP2.toString();
}
} else if (wifi_status == WL_CONNECTED) { } else if (wifi_status == WL_CONNECTED) {
root["local_ip"] = WiFi.localIP().toString(); root["local_ip"] = WiFi.localIP().toString();
root["local_ipv6"] = WiFi.linkLocalIPv6().toString();
root["mac_address"] = WiFi.macAddress(); root["mac_address"] = WiFi.macAddress();
root["rssi"] = WiFi.RSSI(); root["rssi"] = WiFi.RSSI();
root["ssid"] = WiFi.SSID(); root["ssid"] = WiFi.SSID();
@@ -49,18 +79,9 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
root["channel"] = WiFi.channel(); root["channel"] = WiFi.channel();
root["reconnect_count"] = emsesp::EMSESP::network_.getNetworkReconnects(); root["reconnect_count"] = emsesp::EMSESP::network_.getNetworkReconnects();
root["subnet_mask"] = WiFi.subnetMask().toString(); root["subnet_mask"] = WiFi.subnetMask().toString();
add_gateways(root, WiFi.STA);
if (WiFi.gatewayIP() != INADDR_NONE) { add_ipv6(root, WiFi.STA);
root["gateway_ip"] = WiFi.gatewayIP().toString(); add_dns(root, WiFi.STA);
}
IPAddress dnsIP1 = WiFi.dnsIP(0);
IPAddress dnsIP2 = WiFi.dnsIP(1);
if (dnsIP1 != INADDR_NONE) {
root["dns_ip_1"] = dnsIP1.toString();
}
if (dnsIP2 != INADDR_NONE) {
root["dns_ip_2"] = dnsIP2.toString();
}
} }
response->setLength(); response->setLength();
+1 -1
View File
@@ -319,7 +319,7 @@ static void setup_commands(std::shared_ptr<Commands> const & commands) {
settings.eth_clock_mode = data[8]; settings.eth_clock_mode = data[8];
return StateUpdateResult::CHANGED; 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(); EMSESP::system_.system_restart();
}); });
+28 -17
View File
@@ -21,6 +21,7 @@
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
#include "esp_ota_ops.h" #include "esp_ota_ops.h"
#include "rom/rtc.h" #include "rom/rtc.h"
#include "littlefs_compat.h"
#endif #endif
static_assert(uuid::thread_safe, "uuid-common must be thread-safe"); static_assert(uuid::thread_safe, "uuid-common must be thread-safe");
@@ -1723,17 +1724,27 @@ void EMSESP::start() {
// start the file system // start the file system
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (!LittleFS.begin(true)) { if (!LittleFS.begin(false)) {
LOG_ERROR("LittleFS Mount Failed"); // A partition written by an Arduino Core 2 build is readable but records limits
return; // 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();
}
}
} }
// else { #if defined(EMSESP_DEBUG)
// output filesystem folders and files for debugging LOG_DEBUG("Listing root directory before:");
// #if defined(EMSESP_DEBUG) system_.listDir("/", 3);
// LOG_DEBUG("Listing root directory before:"); #endif
// 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
@@ -1746,7 +1757,7 @@ void EMSESP::start() {
bool factory_settings = false; bool factory_settings = false;
#endif #endif
// start NVS storage // start NVS storage
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, "nvs1")) { if (esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, "nvs1")) {
nvs_.begin("ems-esp", false, "nvs1"); nvs_.begin("ems-esp", false, "nvs1");
@@ -1766,11 +1777,11 @@ 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 // output filesystem folders and files for debugging
// #if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
// LOG_DEBUG("Listing root directory after:"); LOG_DEBUG("Listing root directory after:");
// system_.listDir("/", 3); // show the contents of the root directory system_.listDir("/", 3); // show the contents of the root directory
// #endif #endif
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (factory_settings) { if (factory_settings) {
@@ -1818,7 +1829,7 @@ void EMSESP::start() {
LOG_INFO("Last system reset reason Core0: %s, Core1: %s", system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str()); LOG_INFO("Last system reset reason Core0: %s, Core1: %s", system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str());
#endif #endif
// see if we're restoring a settings file // see if we're restoring a settings file
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
if (system_.check_restore()) { if (system_.check_restore()) {
LOG_WARNING("EMS-ESP will restart to apply new settings. Please wait."); LOG_WARNING("EMS-ESP will restart to apply new settings. Please wait.");
+282
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
// 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 <cstdint>
#include <cstdlib>
#include <cstring>
#include <esp_partition.h>
#include <sdkconfig.h>
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<uint32_t>(p[0]) << 24) | (static_cast<uint32_t>(p[1]) << 16) | (static_cast<uint32_t>(p[2]) << 8) | p[3];
}
uint32_t rd_le32(const uint8_t * p) {
return (static_cast<uint32_t>(p[3]) << 24) | (static_cast<uint32_t>(p[2]) << 16) | (static_cast<uint32_t>(p[1]) << 8) | p[0];
}
void wr_le32(uint8_t * p, uint32_t v) {
p[0] = static_cast<uint8_t>(v);
p[1] = static_cast<uint8_t>(v >> 8);
p[2] = static_cast<uint8_t>(v >> 16);
p[3] = static_cast<uint8_t>(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<const uint8_t *>(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<esp_partition_subtype_t>(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<uint8_t *>(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
+31
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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
+3 -1
View File
@@ -224,6 +224,8 @@ MAKE_NOTRANSLATION(3kW, "3 kW")
MAKE_NOTRANSLATION(4kW, "4 kW") MAKE_NOTRANSLATION(4kW, "4 kW")
MAKE_NOTRANSLATION(6kW, "6 kW") MAKE_NOTRANSLATION(6kW, "6 kW")
MAKE_NOTRANSLATION(9kW, "9 kW") MAKE_NOTRANSLATION(9kW, "9 kW")
MAKE_NOTRANSLATION(12kW, "12 kW")
MAKE_NOTRANSLATION(15kW, "15 kW")
MAKE_NOTRANSLATION(L1, "L1") MAKE_NOTRANSLATION(L1, "L1")
MAKE_NOTRANSLATION(L2, "L2") MAKE_NOTRANSLATION(L2, "L2")
MAKE_NOTRANSLATION(L3, "L3") MAKE_NOTRANSLATION(L3, "L3")
@@ -312,7 +314,7 @@ MAKE_ENUM(enum_comfort1, FL_(high_comfort), FL_(eco))
MAKE_ENUM(enum_comfort2, FL_(eco), FL_(high_comfort)) MAKE_ENUM(enum_comfort2, FL_(eco), FL_(high_comfort))
MAKE_ENUM(enum_flow, FL_(off), FL_(flow), FL_(bufferedflow), FL_(buffer), FL_(layeredbuffer)) MAKE_ENUM(enum_flow, FL_(off), FL_(flow), FL_(bufferedflow), FL_(buffer), FL_(layeredbuffer))
MAKE_ENUM(enum_reset, FL_(dash), FL_(maintenance), FL_(error), FL_(history), FL_(message), FL_(hp_error), FL_(burn_starts), FL_(factory)) MAKE_ENUM(enum_reset, FL_(dash), FL_(maintenance), FL_(error), FL_(history), FL_(message), FL_(hp_error), FL_(burn_starts), FL_(factory))
MAKE_ENUM(enum_maxHeat, FL_(0kW), FL_(2kW), FL_(3kW), FL_(4kW), FL_(6kW), FL_(9kW)) MAKE_ENUM(enum_maxHeat, FL_(0kW), FL_(2kW), FL_(3kW), FL_(4kW), FL_(6kW), FL_(9kW), FL_(12kW), FL_(15kW))
MAKE_ENUM(enum_maxHeat1, FL_(0kW), FL_(3kW), FL_(6kW), FL_(9kW)) MAKE_ENUM(enum_maxHeat1, FL_(0kW), FL_(3kW), FL_(6kW), FL_(9kW))
MAKE_ENUM(enum_maxHeat2, FL_(3kW), FL_(6kW), FL_(9kW)) MAKE_ENUM(enum_maxHeat2, FL_(3kW), FL_(6kW), FL_(9kW))
MAKE_ENUM(enum_pumpMode, FL_(proportional), FL_(deltaP1), FL_(deltaP2), FL_(deltaP3), FL_(deltaP4)) MAKE_ENUM(enum_pumpMode, FL_(proportional), FL_(deltaP1), FL_(deltaP2), FL_(deltaP3), FL_(deltaP4))
+119 -28
View File
@@ -20,6 +20,10 @@
#include "emsesp.h" #include "emsesp.h"
#if CONFIG_LWIP_IPV6 && !defined(EMSESP_STANDALONE)
#include <lwip/priv/nd6_priv.h>
#endif
#ifndef NETWORK_FALLBACK_AP_SSID #ifndef NETWORK_FALLBACK_AP_SSID
#define NETWORK_FALLBACK_AP_SSID "ems-esp" #define NETWORK_FALLBACK_AP_SSID "ems-esp"
#endif #endif
@@ -154,44 +158,131 @@ bool Network::formatBSSID([[maybe_unused]] const String & bssid, [[maybe_unused]
return true; return true;
} }
// get the local IP address of the network interface // get the soft-AP's own IP address, empty when the AP isn't running
std::string Network::getLocalIP() const { std::string Network::getAPIP() const {
switch (network_iface_) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
case NetIface::AP: const IPAddress ip = WiFi.softAPIP();
return WiFi.softAPIP().toString().c_str(); if (static_cast<uint32_t>(ip) != 0) {
case NetIface::WIFI: return ip.toString().c_str();
return WiFi.localIP().toString().c_str();
case NetIface::ETHERNET:
return ETH.localIP().toString().c_str();
case NetIface::NONE:
#endif
default:
return "";
} }
#endif
return "";
} }
// get the MAC address of the network interface
std::string Network::getMacAddress() const {
switch (network_iface_) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
case NetIface::AP: // Every IPv6 address bound to the interface, widest scope first so the address most people need
return WiFi.softAPmacAddress().c_str(); // comes out on top. linkLocalIPv6() and globalIPv6() each only ever return a single address, so a
case NetIface::WIFI: // dual-stack host holding both a ULA and a routable GUA would have one of them hidden.
return WiFi.macAddress().c_str(); std::vector<Network::IPv6Address> Network::ipv6_addresses([[maybe_unused]] NetworkInterface & netif) {
case NetIface::ETHERNET: std::vector<IPv6Address> addresses;
return ETH.macAddress().c_str();
case NetIface::NONE: #if CONFIG_LWIP_IPV6
#endif esp_ip6_addr_t raw[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
default: int found = esp_netif_get_all_ip6(netif.netif(), raw);
return ""; if (found <= 0) {
return addresses;
} }
struct Scope {
esp_ip6_addr_type_t type;
const char * name;
};
static const Scope scopes[] = {{ESP_IP6_ADDR_IS_GLOBAL, "global"},
{ESP_IP6_ADDR_IS_UNIQUE_LOCAL, "unique local"},
{ESP_IP6_ADDR_IS_SITE_LOCAL, "site local"},
{ESP_IP6_ADDR_IS_LINK_LOCAL, "link local"}};
addresses.reserve(found);
for (const auto & scope : scopes) {
for (int i = 0; i < found; i++) {
IPAddress ip(IPv6, reinterpret_cast<const uint8_t *>(raw[i].addr), raw[i].zone);
if (ip.addr_type() == scope.type) {
addresses.push_back({ip, scope.name});
}
}
}
#endif
return addresses;
}
// Every DNS server configured on the interface. The slots are shared between IPv4 and IPv6, so
// reading only the first couple can hide the IPv4 resolver entirely once IPv6 is in use.
std::vector<IPAddress> Network::dns_servers(const NetworkInterface & netif) {
std::vector<IPAddress> servers;
for (uint8_t i = 0; i < ESP_NETIF_DNS_MAX; i++) {
IPAddress ip = netif.dnsIP(i);
if (ip.type() == IPv4 ? static_cast<uint32_t>(ip) != 0 : ip != IN6ADDR_ANY) {
servers.push_back(ip);
}
}
return servers;
}
#if CONFIG_LWIP_IPV6
namespace {
struct Ipv6GatewayCtx {
int netif_index;
std::vector<IPAddress> * gateways;
};
// runs in the TCP/IP task, where neighbour discovery owns default_router_list
esp_err_t collect_ipv6_gateways(void * ctx) {
auto * args = static_cast<Ipv6GatewayCtx *>(ctx);
for (uint8_t i = 0; i < LWIP_ND6_NUM_ROUTERS; i++) {
const struct nd6_neighbor_cache_entry * router = default_router_list[i].neighbor_entry;
// a null neighbour entry is how nd6_tmr() marks a slot as unused or expired
if (router == nullptr || netif_get_index(router->netif) != args->netif_index) {
continue;
}
const ip6_addr_t * addr = &router->next_hop_address;
args->gateways->push_back(IPAddress(IPv6, reinterpret_cast<const uint8_t *>(addr->addr), ip6_addr_zone(addr)));
}
return ESP_OK;
}
} // namespace
// Every IPv6 default router known on the interface. Routers are learnt from router advertisements
// rather than configured, so there can be more than one and none of them appear in the IPv4-only
// esp_netif_ip_info_t.gw that gatewayIP() returns.
std::vector<IPAddress> Network::ipv6_gateways(NetworkInterface & netif) {
std::vector<IPAddress> gateways;
const int netif_index = esp_netif_get_netif_impl_index(netif.netif());
if (netif_index < 0) {
return gateways;
}
gateways.reserve(LWIP_ND6_NUM_ROUTERS); // allocate before we're inside the TCP/IP task
Ipv6GatewayCtx args = {netif_index, &gateways};
esp_netif_tcpip_exec(collect_ipv6_gateways, &args);
return gateways;
}
#else
std::vector<IPAddress> Network::ipv6_gateways(NetworkInterface &) {
return {};
}
#endif
#endif
// get the soft-AP's own MAC address, empty when the AP isn't running
std::string Network::getAPMacAddress() const {
#ifndef EMSESP_STANDALONE
uint8_t mac[6];
if (WiFi.softAPmacAddress(mac) != nullptr) { // null once the AP netif is gone
return WiFi.softAPmacAddress().c_str();
}
#endif
return "";
} }
// get the number of sessions connected to the AP // get the number of sessions connected to the AP
uint8_t Network::getStationNum() const { uint8_t Network::getAPStationNum() const {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
return network_iface_ == NetIface::AP ? WiFi.softAPgetStationNum() : 0; return WiFi.softAPgetStationNum();
#else #else
return 0; return 0;
#endif #endif
+19 -3
View File
@@ -21,6 +21,7 @@
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
#include <esp_mac.h> #include <esp_mac.h>
#include <esp_netif.h>
#include <esp_wifi.h> #include <esp_wifi.h>
#include <ETH.h> #include <ETH.h>
#include <WiFi.h> #include <WiFi.h>
@@ -30,6 +31,7 @@
#endif #endif
#include <cstring> #include <cstring>
#include <vector>
#include <esp32-psram.h> #include <esp32-psram.h>
#include <uuid/log.h> #include <uuid/log.h>
@@ -132,11 +134,25 @@ class Network {
return reconnect_count_; return reconnect_count_;
} }
std::string getLocalIP() const; // the soft-AP's own details, which are what the Access Point status page reports
std::string getMacAddress() const; std::string getAPIP() const;
uint8_t getStationNum() const; std::string getAPMacAddress() const;
uint8_t getAPStationNum() const;
const char * ethernet_status() const; const char * ethernet_status() const;
#ifndef EMSESP_STANDALONE
// an IPv6 address together with a readable scope, e.g. "global" or "link local"
struct IPv6Address {
IPAddress ip;
const char * scope;
};
static std::vector<IPv6Address> ipv6_addresses(NetworkInterface & netif);
static std::vector<IPAddress> dns_servers(const NetworkInterface & netif);
static std::vector<IPAddress> ipv6_gateways(NetworkInterface & netif);
#endif
void reconnect(); void reconnect();
// ask for a reconnect() to run from the next Network::loop() // ask for a reconnect() to run from the next Network::loop()
+59 -34
View File
@@ -98,16 +98,51 @@ static void sendmail_tls_handshake(bool & success) {
success = sendmail_ssl_client != nullptr && sendmail_ssl_client->connectSSL(); success = sendmail_ssl_client != nullptr && sendmail_ssl_client->connectSSL();
} }
// The DNS slots are shared between IPv4 and IPv6, so with IPv6 enabled the first slot can hold an // Print the full set of addresses for an interface. Every DNS server is listed, labelled with its
// IPv6 server. Pick the first IPv4 one so it matches the label we print it under. // own family since the slots are shared, and every IPv6 address with its scope.
static IPAddress dns_ipv4(const NetworkInterface & netif) { static void show_addresses(uuid::console::Shell & shell, NetworkInterface & netif) {
for (uint8_t i = 0; i < ESP_NETIF_DNS_MAX; i++) { shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(netif.localIP()).c_str(), uuid::printable_to_string(netif.subnetMask()).c_str());
IPAddress ip = netif.dnsIP(i); shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(netif.gatewayIP()).c_str());
if (ip.type() == IPv4 && static_cast<uint32_t>(ip) != 0) { for (const auto & dns : Network::dns_servers(netif)) {
return ip; shell.printfln(" %s DNS Server: %s", dns.type() == IPv4 ? "IPv4" : "IPv6", uuid::printable_to_string(dns).c_str());
}
for (const auto & ipv6 : Network::ipv6_addresses(netif)) {
shell.printfln(" IPv6 address: %s (%s)", uuid::printable_to_string(ipv6.ip).c_str(), ipv6.scope);
}
for (const auto & gateway : Network::ipv6_gateways(netif)) {
shell.printfln(" IPv6 gateway: %s", uuid::printable_to_string(gateway).c_str());
}
}
// Add the full set of addresses for an interface to a JSON document, both families.
static void add_addresses(JsonDocument & doc, NetworkInterface & netif) {
doc["MAC"] = netif.macAddress();
doc["IPv4 address"] = uuid::printable_to_string(netif.localIP()) + "/" + uuid::printable_to_string(netif.subnetMask());
doc["IPv4 gateway"] = uuid::printable_to_string(netif.gatewayIP());
auto servers = Network::dns_servers(netif);
if (!servers.empty()) {
JsonArray dns = doc["DNS Server"].to<JsonArray>();
for (const auto & ip : servers) {
dns.add(uuid::printable_to_string(ip));
}
}
auto found = Network::ipv6_addresses(netif);
if (!found.empty()) {
JsonArray addresses = doc["IPv6 address"].to<JsonArray>();
for (const auto & ipv6 : found) {
addresses.add(uuid::printable_to_string(ipv6.ip));
}
}
auto gateways = Network::ipv6_gateways(netif);
if (!gateways.empty()) {
JsonArray routers = doc["IPv6 gateway"].to<JsonArray>();
for (const auto & ip : gateways) {
routers.add(uuid::printable_to_string(ip));
} }
} }
return IPAddress();
} }
#endif #endif
@@ -452,7 +487,8 @@ void System::get_partition_info() {
auto current_partition = (const char *)esp_ota_get_running_partition()->label; auto current_partition = (const char *)esp_ota_get_running_partition()->label;
// update the current version and partition name in NVS if not already set // 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_.putBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, false);
EMSESP::nvs_.putString(current_partition, EMSESP_APP_VERSION); EMSESP::nvs_.putString(current_partition, EMSESP_APP_VERSION);
char c[20]; char c[20];
@@ -484,7 +520,9 @@ void System::get_partition_info() {
if (is_valid) { if (is_valid) {
PartitionInfo p_info; PartitionInfo p_info;
// if there is an entry for this partition in NVS, get it's version from NVS // 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]; char c[20];
snprintf(c, sizeof(c), "d_%s", (const char *)part->label); snprintf(c, sizeof(c), "d_%s", (const char *)part->label);
time_t d = EMSESP::nvs_.getULong(c, 0); time_t d = EMSESP::nvs_.getULong(c, 0);
@@ -893,18 +931,13 @@ void System::send_info_mqtt() {
if (EMSESP::network_.ethernet_connected()) { if (EMSESP::network_.ethernet_connected()) {
doc["network"] = "ethernet"; doc["network"] = "ethernet";
doc["hostname"] = ETH.getHostname(); doc["hostname"] = ETH.getHostname();
add_addresses(doc, ETH);
} else if (EMSESP::network_.wifi_connected()) { } else if (EMSESP::network_.wifi_connected()) {
doc["network"] = "wifi"; doc["network"] = "wifi";
doc["hostname"] = WiFi.getHostname(); doc["hostname"] = WiFi.getHostname();
doc["SSID"] = WiFi.SSID(); doc["SSID"] = WiFi.SSID();
doc["BSSID"] = WiFi.BSSIDstr(); doc["BSSID"] = WiFi.BSSIDstr();
doc["MAC"] = WiFi.macAddress(); add_addresses(doc, WiFi.STA);
doc["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask());
doc["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP());
doc["IPv4 DNS Server"] = uuid::printable_to_string(dns_ipv4(WiFi.STA));
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
doc["IPv6 address"] = uuid::printable_to_string(WiFi.linkLocalIPv6());
}
} }
#endif #endif
Mqtt::queue_publish_retain(F_(info), doc.as<JsonObject>()); // topic called "info" and it's Retained Mqtt::queue_publish_retain(F_(info), doc.as<JsonObject>()); // topic called "info" and it's Retained
@@ -1131,7 +1164,9 @@ void System::show_system(uuid::console::Shell & shell) {
// client and all the JSON work on this task, and a TLS handshake alone can want several KB // client and all the JSON work on this task, and a TLS handshake alone can want several KB
TaskHandle_t loop_task = xTaskGetHandle("loopTask"); TaskHandle_t loop_task = xTaskGetHandle("loopTask");
if (loop_task != nullptr) { if (loop_task != nullptr) {
shell.printfln(" Loop task stack min free/total: %u / %u bytes", (unsigned)uxTaskGetStackHighWaterMark(loop_task), (unsigned)getArduinoLoopTaskStackSize()); 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());
@@ -1264,12 +1299,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(" TxPower: %s dBm", Helpers::render_value(result, (double)(WiFi.getTxPower() / 4), 1)); shell.printfln(" TxPower: %s dBm", Helpers::render_value(result, (double)(WiFi.getTxPower() / 4), 1));
shell.printfln(" MAC address: %s", WiFi.macAddress().c_str()); shell.printfln(" MAC address: %s", WiFi.macAddress().c_str());
shell.printfln(" Hostname: %s", WiFi.getHostname()); shell.printfln(" Hostname: %s", WiFi.getHostname());
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str()); show_addresses(shell, WiFi.STA);
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(WiFi.gatewayIP()).c_str());
shell.printfln(" IPv4 DNS Server: %s", uuid::printable_to_string(dns_ipv4(WiFi.STA)).c_str());
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(WiFi.linkLocalIPv6()).c_str());
}
break; break;
case WL_CONNECT_FAILED: case WL_CONNECT_FAILED:
@@ -1299,12 +1329,7 @@ void System::show_system(uuid::console::Shell & shell) {
if (EMSESP::network_.ethernet_connected()) { if (EMSESP::network_.ethernet_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()); show_addresses(shell, ETH);
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(ETH.gatewayIP()).c_str());
shell.printfln(" IPv4 DNS Server: %s", uuid::printable_to_string(dns_ipv4(ETH)).c_str());
if (ETH.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && ETH.linkLocalIPv6().toString() != "::") {
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(ETH.linkLocalIPv6()).c_str());
}
} }
// show AP is connected // show AP is connected
+1 -1
View File
@@ -199,7 +199,7 @@ void TemperatureSensor::loop() {
if (firstscan_ > 0 && set_internal_) { if (firstscan_ > 0 && set_internal_) {
set_internal_ = false; set_internal_ = false;
Sensor * s = &sensors_[0]; Sensor * s = &sensors_[0];
if (firstscan_ > 1) { if (firstscan_ > 1 && EMSESP::nvs_.isKey("intTemp")) {
std::string s_nvs = EMSESP::nvs_.getString("intTemp").c_str(); std::string s_nvs = EMSESP::nvs_.getString("intTemp").c_str();
for (uint8_t i = 0; i < firstscan_; i++) { for (uint8_t i = 0; i < firstscan_; i++) {
if (s_nvs == sensors_[i].id()) { if (s_nvs == sensors_[i].id()) {
+1 -1
View File
@@ -337,7 +337,7 @@ class Boiler : public EMSdevice {
double nrgWwF_; // double calculate for nrgWw double nrgWwF_; // double calculate for nrgWw
uint8_t nomPower_; uint8_t nomPower_;
int16_t tk1_; int16_t tk1_;
/* /*
// Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat // Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat
+1 -1
View File
@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.9.0-dev.10" #define EMSESP_APP_VERSION "3.9.0-dev.12"
+18 -8
View File
@@ -44,10 +44,13 @@ void WebLogService::begin() {
// apply the user settings // apply the user settings
void WebLogService::start() { void WebLogService::start() {
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings & settings) {
maximum_log_messages_ = settings.weblog_buffer; // a stored value of 0 would make limit_log_messages_ 0, see the guard in operator<<
limit_log_messages_ = maximum_log_messages_; if (settings.weblog_buffer) {
compact_ = settings.weblog_compact; maximum_log_messages_ = settings.weblog_buffer;
level_ = (uuid::log::Level)settings.weblog_level; }
limit_log_messages_ = maximum_log_messages_;
compact_ = settings.weblog_compact;
level_ = (uuid::log::Level)settings.weblog_level;
}); });
uuid::log::Logger::register_handler(this, level_); uuid::log::Logger::register_handler(this, level_);
if (level_ == uuid::log::Level::OFF) { if (level_ == uuid::log::Level::OFF) {
@@ -80,7 +83,9 @@ void WebLogService::operator<<(std::shared_ptr<uuid::log::Message> message) {
} }
} }
#endif #endif
while (log_messages_.size() >= limit_log_messages_) { // size() is unsigned, so with limit_log_messages_ == 0 this condition is always true
// and pop_front() would run on an empty deque (undefined behaviour)
while (!log_messages_.empty() && log_messages_.size() >= limit_log_messages_) {
log_messages_.pop_front(); log_messages_.pop_front();
} }
@@ -210,9 +215,14 @@ void WebLogService::getSetValues(AsyncWebServerRequest * request, JsonVariant js
} }
// POST - write the settings // POST - write the settings
level_ = json["level"]; level_ = json["level"];
maximum_log_messages_ = json["max_messages"]; // keep the current size if the key is missing or 0 - a 0 would be stored and make
compact_ = json["compact"]; // limit_log_messages_ 0 on the next boot
const size_t max_messages = json["max_messages"] | maximum_log_messages_;
if (max_messages) {
maximum_log_messages_ = max_messages;
}
compact_ = json["compact"];
uuid::log::Logger::register_handler(this, level_); uuid::log::Logger::register_handler(this, level_);
if (level_ == uuid::log::Level::OFF) { if (level_ == uuid::log::Level::OFF) {
+9 -7
View File
@@ -335,10 +335,12 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
settings.email_subject = root["email_subject"] | FACTORY_EMAIL_SUBJECT; settings.email_subject = root["email_subject"] | FACTORY_EMAIL_SUBJECT;
// if no psram limit weblog buffer to 25 messages // if no psram limit weblog buffer to 25 messages
if (EMSESP::system_.PSram() > 0) { const uint16_t weblog_buffer_default = (EMSESP::system_.PSram() > 0) ? EMSESP_DEFAULT_WEBLOG_BUFFER : 25;
settings.weblog_buffer = root["weblog_buffer"] | EMSESP_DEFAULT_WEBLOG_BUFFER; settings.weblog_buffer = root["weblog_buffer"] | weblog_buffer_default;
} else { // a stored 0 is a valid uint16 and passes the | default above, but would make
settings.weblog_buffer = root["weblog_buffer"] | 25; // limit to 25 messages if no psram // limit_log_messages_ 0 in WebLogService
if (settings.weblog_buffer == 0) {
settings.weblog_buffer = weblog_buffer_default;
} }
// save the settings if changed from the webUI // save the settings if changed from the webUI
@@ -463,7 +465,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 eFuse value %s", bbq_board.c_str()); EMSESP::logger().debug("Overriding board profile with eFuse value %s", bbq_board.c_str());
#endif #endif
settings.board_profile = bbq_board; settings.board_profile = bbq_board;
} }
@@ -530,9 +532,9 @@ void WebSettings::set_board_profile(WebSettings & settings) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
uint32_t psram_size = ESP.getPsramSize() / 1024; // in KB uint32_t psram_size = ESP.getPsramSize() / 1024; // in KB
if (psram_size > 0) { 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 { } 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 #endif
+15 -10
View File
@@ -258,9 +258,9 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
// action = upgradeImportantMessages // action = upgradeImportantMessages
// returns the type of upgrade important message to display in the UI // returns the type of upgrade important message to display in the UI
// 0 = no message (if just a minor version upgrade) // 0 = no message (if just a minor version upgrade)
// 1 = going from <= 3.8 to 3.9 (has new partition layout) // 1 = special message for upgrades (no longer used)
// 2 = major version upgrade // 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 // 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) { uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
if (version.empty()) { if (version.empty()) {
@@ -307,9 +307,10 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
return 0; // no upgrade (same version or downgrade) 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) { // case "1" is no longer used
return 1; // upgrading to 3.9.x from anything older - new partition layout warning // 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()) { if (current_version.major() < latest_version.major()) {
return 2; // major version upgrade return 2; // major version upgrade
@@ -359,6 +360,8 @@ void WebStatusService::getVersions(JsonObject root) {
dev = versions_dev_; dev = versions_dev_;
} }
versions_fetch_requested_ = true;
if (!cache_valid) { 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;
@@ -402,12 +405,12 @@ void WebStatusService::schedule_versions_refresh() {
versions_next_fetch_ms_ = next; versions_next_fetch_ms_ = next;
} }
// periodic refresh (1 hour) of the cached versions.json, only when auto_fw_check is enabled // refresh of the cached versions.json, at most once a day
// runs on the main loop task so the blocking fetch never happens in an AsyncTCP callback
void WebStatusService::loop() { void WebStatusService::loop() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
// only reach out to emsesp.org if the user has asked us to // with auto_fw_check off we only reach out to emsesp.org when the WebUI asks for version
if (!EMSESP::system_.auto_fw_check()) { // information, so an unattended system never contacts it at all
if (!EMSESP::system_.auto_fw_check() && !versions_fetch_requested_) {
return; return;
} }
@@ -426,6 +429,8 @@ void WebStatusService::loop() {
return; return;
} }
versions_fetch_requested_ = false;
uint32_t interval; uint32_t interval;
if (refresh_versions_cache()) { if (refresh_versions_cache()) {
versions_failures_ = 0; versions_failures_ = 0;
+9 -3
View File
@@ -7,6 +7,7 @@
#include "../core/firmwareVersion.h" #include "../core/firmwareVersion.h"
#include "../emsesp_version.h" #include "../emsesp_version.h"
#include <atomic>
#include <mutex> #include <mutex>
namespace emsesp { namespace emsesp {
@@ -23,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
@@ -71,6 +73,10 @@ class WebStatusService {
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 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 // 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 // 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 // request landing mid-update reads a buffer that has just been freed
@@ -80,9 +86,9 @@ class WebStatusService {
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 the first 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 // 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 // allow, so double the retry delay per consecutive failure: 5, 10, 20, 40, 80, 160 minutes