32 Commits

Author SHA1 Message Date
Proddy
ad5d13168b Merge pull request #3066 from MichaelDvP/dev
fix modbus start #3064
2026-05-07 18:05:48 +02:00
MichaelDvP
ae5beccb9d dev.20, fixes #3064, handling of optional gpios 2026-05-07 12:02:33 +02:00
MichaelDvP
764c660b14 Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2026-05-06 09:23:27 +02:00
MichaelDvP
ca0f32b087 update pkg 2026-05-06 09:06:15 +02:00
Proddy
8ad91de54a Merge pull request #3061 from MichaelDvP/dev 2026-05-06 09:05:10 +02:00
MichaelDvP
58da157272 chore: update generated files for v3.8.2-dev.20 2026-05-05 16:04:29 +00:00
MichaelDvP
b5014bf9ac dev.20, check and set 0x470 to summer2_typeids[0] ony if received. #2686 2026-05-05 17:51:52 +02:00
MichaelDvP
14351436a7 fixes #3055, revert commit daffdcf 2026-05-05 10:33:56 +02:00
Proddy
469d412951 Merge pull request #3045 from MichaelDvP/dev
fix legegram length, #2969
2026-04-24 17:14:58 +02:00
MichaelDvP
6edbac86e2 fix legegram length, #2969 2026-04-24 14:46:53 +02:00
proddy
db2be70d66 chore: update generated files for v3.8.2-dev.18 2026-04-22 14:22:25 +00:00
Proddy
c36f231990 Merge pull request #3042 from proddy/dev
minor updates
2026-04-22 16:10:20 +02:00
proddy
26102121e1 async-validator fixes 2026-04-22 16:07:56 +02:00
proddy
8e64c6303e package update 2026-04-22 15:43:58 +02:00
proddy
74c76eb90b remove YIELD 2026-04-22 15:43:29 +02:00
proddy
daffdcf58e https://github.com/emsesp/EMS-ESP32/issues/2686 2026-04-22 15:43:20 +02:00
Proddy
4bc4fa903f Merge pull request #3040 from MichaelDvP/dev
version checks prelease
2026-04-22 15:11:02 +02:00
MichaelDvP
29380f0303 version checks prelease 2026-04-22 14:59:46 +02:00
Proddy
6b2370b79d Merge pull request #3035 from mattreim/dev
Update German translation
2026-04-22 08:34:14 +02:00
Proddy
dbc636c9bf Merge pull request #3036 from MichaelDvP/dev
small fixes
2026-04-22 08:33:38 +02:00
MichaelDvP
0c0660c04b Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2026-04-22 07:41:29 +02:00
mattreim
c9fd076394 Update German translation 2026-04-22 01:05:05 +02:00
MichaelDvP
35550553be check fetch length for custom entities, dev17 2026-04-21 19:51:48 +02:00
MichaelDvP
06ff219385 version check order 2026-04-21 18:58:42 +02:00
MichaelDvP
e705a5629f fetch length of holiday to 18 2026-04-21 18:58:13 +02:00
Proddy
cb3c9653ce Merge pull request #3032 from proddy/dev
rename build_webUI for Python
2026-04-20 15:38:52 +02:00
proddy
0b5a83f6ae package update (vite fix) 2026-04-20 15:37:50 +02:00
MichaelDvP
a079169005 backup nvs1 if exist 2026-04-20 13:18:50 +02:00
proddy
845c51d5f9 rename build_webUI for Python 2026-04-19 21:23:59 +02:00
MichaelDvP
854f4d559a Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev 2026-04-19 15:40:15 +02:00
MichaelDvP
d9b6de0652 Merge branch 'dev' of https://github.com/proddy/EMS-ESP32 into dev 2026-04-19 13:41:01 +02:00
MichaelDvP
c54da18822 remove pr#3021 2026-04-19 13:40:51 +02:00
37 changed files with 567 additions and 509 deletions

View File

@@ -19,6 +19,8 @@ For more details go to [emsesp.org](https://emsesp.org/).
- SRC climate creation [#2936](https://github.com/emsesp/EMS-ESP32/issues/2936) and [#2960](https://github.com/emsesp/EMS-ESP32/issues/2960)
- missing translations [#3015](https://github.com/emsesp/EMS-ESP32/issues/3015)
- custom entities check fetch length
- modbus initialization [#3064](https://github.com/emsesp/EMS-ESP32/issues/3064)
## Changed
@@ -33,3 +35,4 @@ For more details go to [emsesp.org](https://emsesp.org/).
- fetch telegrams: set length to fetch [#3017](https://github.com/emsesp/EMS-ESP32/issues/3017)
- move http client from stack to heap
- heap optimizations [#3021](https://github.com/emsesp/EMS-ESP32/discussions/3021)
- check and read 0x470 as summer2_typeids[0] only if received [#2686](https://github.com/emsesp/EMS-ESP32/issues/2686), [#3055](https://github.com/emsesp/EMS-ESP32/issues/3055)

View File

@@ -112,10 +112,10 @@ telegram_type_id,name,is_fetched
0x02A0,RC300Curves,
0x02A1,RC300Curves,
0x02A2,RC300Curves,
0x02A5,RC300Monitor,fetched
0x02A6,CRFMonitor,
0x02A5,RC300Monitor,
0x02A6,RC300Monitor,
0x02A7,RC300Monitor,
0x02A8,CRFMonitor,
0x02A8,RC300Monitor,
0x02A9,RC300Monitor,
0x02AA,RC300Monitor,
0x02AB,RC300Monitor,
@@ -171,6 +171,7 @@ telegram_type_id,name,is_fetched
0x0468,HPSet,
0x0469,HPSet,
0x046A,HPSet,
0x0470,RC300Summer2,fetched
0x0471,RC300Summer2,
0x0472,RC300Summer2,
0x0473,RC300Summer2,
1 telegram_type_id name is_fetched
112 0x02A0 RC300Curves
113 0x02A1 RC300Curves
114 0x02A2 RC300Curves
115 0x02A5 RC300Monitor fetched
116 0x02A6 CRFMonitor RC300Monitor
117 0x02A7 RC300Monitor
118 0x02A8 CRFMonitor RC300Monitor
119 0x02A9 RC300Monitor
120 0x02AA RC300Monitor
121 0x02AB RC300Monitor
171 0x0468 HPSet
172 0x0469 HPSet
173 0x046A HPSet
174 0x0470 RC300Summer2 fetched
175 0x0471 RC300Summer2
176 0x0472 RC300Summer2
177 0x0473 RC300Summer2

View File

@@ -26,8 +26,8 @@
"@alova/adapter-xhr": "2.3.1",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^9.0.0",
"@mui/material": "^9.0.0",
"@mui/icons-material": "^9.0.1",
"@mui/material": "^9.0.1",
"@preact/compat": "^18.3.2",
"@table-library/react-table-library": "4.1.15",
"alova": "3.5.1",
@@ -38,11 +38,11 @@
"magic-string": "^0.30.21",
"mime-types": "^3.0.2",
"preact": "^10.29.1",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-icons": "^5.6.0",
"react-router": "^7.14.1",
"react-toastify": "^11.0.5",
"react-router": "^7.15.0",
"react-toastify": "^11.1.0",
"typesafe-i18n": "^5.27.1",
"typescript": "^6.0.3"
},
@@ -55,17 +55,17 @@
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"axe-core": "^4.11.3",
"axe-core": "^4.11.4",
"concurrently": "^9.2.1",
"eslint": "^10.2.1",
"eslint": "^10.3.0",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.8.3",
"rollup-plugin-visualizer": "^7.0.1",
"terser": "^5.46.1",
"typescript-eslint": "^8.58.2",
"vite": "^8.0.8",
"terser": "^5.47.0",
"typescript-eslint": "^8.59.2",
"vite": "^8.0.11",
"vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^6.1.1"
},
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
"packageManager": "pnpm@10.33.4"
}

754
interface/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@ import { PROJECT_NAME } from 'env';
import { useI18nContext } from 'i18n/i18n-react';
import type { SignInRequest } from 'types';
import { onEnterCallback, updateValue } from 'utils';
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
import { SIGN_IN_REQUEST_VALIDATOR, ValidationError, validate } from 'validators';
const SignIn = memo(() => {
const authenticationContext = useContext(AuthenticationContext);
@@ -74,7 +74,7 @@ const SignIn = memo(() => {
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
await signIn();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
setProcessing(false);
}
}, [signInRequest, signIn, LL]);

View File

@@ -28,7 +28,7 @@ import type { ValidateFieldsError } from 'async-validator';
import { BlockFormControlLabel, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { DeviceValueType, DeviceValueTypeNames, DeviceValueUOM_s } from './types';
import type { EntityItem } from './types';
@@ -136,7 +136,7 @@ const CustomEntitiesDialog = ({
}
onSave(processedItem);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [validator, editItem, onSave]);
@@ -215,7 +215,7 @@ const CustomEntitiesDialog = ({
name="value"
label={LL.DEFAULT(0) + ' ' + LL.VALUE(0)}
type="string"
value={editItem.value as string}
value={editItem.value}
variant="outlined"
onChange={updateFormValue}
fullWidth
@@ -260,7 +260,7 @@ const CustomEntitiesDialog = ({
margin="normal"
sx={{ width: '11ch' }}
type="string"
value={editItem.device_id as string}
value={editItem.device_id}
onChange={updateFormValue}
slotProps={{
input: {
@@ -280,7 +280,7 @@ const CustomEntitiesDialog = ({
margin="normal"
sx={{ width: '11ch' }}
type="string"
value={editItem.type_id as string}
value={editItem.type_id}
onChange={updateFormValue}
slotProps={{
input: {
@@ -381,7 +381,7 @@ const CustomEntitiesDialog = ({
fieldErrors={fieldErrors || {}}
name="factor"
label={LL.BITMASK()}
value={editItem.factor as string}
value={editItem.factor}
sx={{ width: '11ch' }}
variant="outlined"
onChange={updateFormValue}

View File

@@ -24,7 +24,7 @@ import type { ValidateFieldsError } from 'async-validator';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
import type { DeviceValue } from './types';
@@ -67,7 +67,7 @@ const DevicesDialog = ({
await validate(validator, editItem);
onSave(editItem);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [validator, editItem, onSave]);

View File

@@ -26,7 +26,7 @@ import type { ValidateFieldsError } from 'async-validator';
import { BlockFormControlLabel, ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { ScheduleFlag } from './types';
import type { ScheduleItem } from './types';
@@ -120,7 +120,7 @@ const SchedulerDialog = ({
await validate(validator, itemToSave);
onSave(itemToSave);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
},
[validator, onSave]

View File

@@ -23,7 +23,7 @@ import type { ValidateFieldsError } from 'async-validator';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types';
import type { AnalogSensor } from './types';
@@ -172,7 +172,7 @@ const SensorsAnalogDialog = ({
await validate(validator, editItem);
onSave(editItem);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [validator, editItem, onSave]);

View File

@@ -21,7 +21,7 @@ import type { ValidateFieldsError } from 'async-validator';
import { ValidatedTextField } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import type { TemperatureSensor } from './types';
@@ -84,7 +84,7 @@ const SensorsTemperatureDialog = ({
await validate(validator, editItem);
onSave(editItem);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [validator, editItem, onSave]);

View File

@@ -21,7 +21,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import type { APSettingsType } from 'types';
import { APProvisionMode } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils';
import { createAPSettingsValidator, validate } from 'validators';
import { ValidationError, createAPSettingsValidator, validate } from 'validators';
export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
provision_mode === APProvisionMode.AP_MODE_ALWAYS ||
@@ -86,7 +86,7 @@ const APSettings = () => {
await validate(createAPSettingsValidator(data), data);
await saveData();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [data, saveData]);

View File

@@ -33,7 +33,7 @@ import {
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { numberValue, updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { API, getBoardProfile, readSettings, writeSettings } from '../../api/app';
import { BOARD_PROFILES } from '../main/types';
@@ -153,7 +153,7 @@ const ApplicationSettings = () => {
setFieldErrors(undefined);
await validate(createSettingsValidator(data), data);
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
} finally {
await saveData();
}

View File

@@ -31,7 +31,7 @@ import {
import { useI18nContext } from 'i18n/i18n-react';
import type { MqttSettingsType } from 'types';
import { numberValue, updateValueDirty, useRest } from 'utils';
import { createMqttSettingsValidator, validate } from 'validators';
import { ValidationError, createMqttSettingsValidator, validate } from 'validators';
import { callAction } from '../../api/app';
@@ -94,7 +94,7 @@ const MqttSettings = () => {
await validate(createMqttSettingsValidator(data), data);
await saveData();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [data, saveData]);

View File

@@ -36,7 +36,7 @@ import {
import { useI18nContext } from 'i18n/i18n-react';
import type { NTPSettingsType, Time } from 'types';
import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
import { TIME_ZONES, selectedTimeZone, useTimeZoneSelectItems } from './TZ';
@@ -133,7 +133,7 @@ const NTPSettings = () => {
await validate(NTP_SETTINGS_VALIDATOR, data);
await saveData();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [data, saveData]);

View File

@@ -40,7 +40,7 @@ import {
import { useI18nContext } from 'i18n/i18n-react';
import type { NetworkSettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
import { createNetworkSettingsValidator } from 'validators/network';
import SystemMonitor from '../../status/SystemMonitor';
@@ -116,7 +116,7 @@ const NetworkSettings = () => {
await validate(createNetworkSettingsValidator(data), data);
await saveData();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
deselectNetwork();
}, [data, saveData, deselectNetwork]);

View File

@@ -19,7 +19,7 @@ import { AuthenticatedContext } from 'contexts/authentication';
import { useI18nContext } from 'i18n/i18n-react';
import type { SecuritySettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils';
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
import { SECURITY_SETTINGS_VALIDATOR, ValidationError, validate } from 'validators';
const SecuritySettings = () => {
const { LL } = useI18nContext();
@@ -58,7 +58,7 @@ const SecuritySettings = () => {
await saveData();
await authenticatedContext.refresh();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}, [data, saveData, authenticatedContext]);

View File

@@ -24,7 +24,7 @@ import {
import { useI18nContext } from 'i18n/i18n-react';
import type { UserType } from 'types';
import { updateValue } from 'utils';
import { validate } from 'validators';
import { ValidationError, validate } from 'validators';
interface UserFormProps {
creating: boolean;
@@ -69,7 +69,7 @@ const User: FC<UserFormProps> = ({
await validate(validator, user);
onDoneEditing();
} catch (error) {
setFieldErrors(error as ValidateFieldsError);
setFieldErrors((error as ValidationError).fieldErrors);
}
}
}, [user, validator, onDoneEditing]);

View File

@@ -187,7 +187,7 @@ const de: Translation = {
COMPACT: 'Kompakte Darstellung',
DOWNLOAD_SETTINGS_TEXT: 'Erstellen Sie eine Sicherung Ihrer Konfigurationen und Einstellungen',
DOWNLOAD_SETTINGS_TEXT2: 'Exportiere alle Werte',
DOWNLOAD_SYSTEM_BACKUP: 'System Sicherung',
DOWNLOAD_SYSTEM_BACKUP: 'Systemsicherung',
UPLOAD_TEXT: 'Laden Sie eine neue Firmware-Datei (.bin) oder eine Sicherungsdatei (.json) hoch',
UPLOAD_DROP_TEXT: 'Legen Sie eine Firmware-Datei (.bin) ab oder klicken Sie hier',
ERROR: 'Unerwarteter Fehler, bitte versuchen Sie es erneut.',
@@ -362,9 +362,9 @@ const de: Translation = {
STORED_VERSIONS: 'Gespeicherte Versionen',
ONLINE_HELP: 'Online-Hilfe',
UPGRADE_IMPORTANT_MESSAGES: 'Wichtige Nachrichten aktualisieren',
UPGRADE_IMPORTANT_MESSAGES_1: 'Diese Aktualisierung erfordert eine Werkseinstellung. 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.',
WARNING_SYSTEM_BACKUP: 'Dies wird eine Sicherung Ihrer vollständigen Systemkonfiguration und -einstellungen erstellen. Alle Passwörter werden im 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?'
};

View File

@@ -1,6 +1,20 @@
import type { InternalRuleItem, ValidateOption } from 'async-validator';
import type {
InternalRuleItem,
ValidateFieldsError,
ValidateOption
} from 'async-validator';
import type Schema from 'async-validator';
export class ValidationError extends Error {
readonly fieldErrors: ValidateFieldsError;
constructor(fieldErrors: ValidateFieldsError) {
super('Validation failed');
this.name = 'ValidationError';
this.fieldErrors = fieldErrors;
}
}
export const validate = <T extends object>(
validator: Schema,
source: Partial<T>,
@@ -8,7 +22,7 @@ export const validate = <T extends object>(
): Promise<T> =>
new Promise((resolve, reject) => {
void validator.validate(source, options ?? {}, (errors, fieldErrors) => {
errors ? reject(fieldErrors as Error) : resolve(source as T);
errors ? reject(new ValidationError(fieldErrors)) : resolve(source as T);
});
});

View File

@@ -15,5 +15,5 @@
"itty-router": "^5.0.23",
"prettier": "^3.8.3"
},
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
"packageManager": "pnpm@10.33.1+sha512.05ba3c1d5d1c18f68df06470d74055e62d41fc110a0c660db1b2dfb2785327f04cf0f68345d4609bc52089e7fa0343c31593b2f9594e2c5d5da426230acc9820"
}

View File

@@ -59,7 +59,7 @@ framework = arduino
board_build.partitions = partitions/esp32_partition_16M.csv
board_upload.flash_size = 16MB
board_build.app_partition_name = app0
platform = espressif32@6.13.0 ; Arduino Core 2.0.17 / IDF 4.4.7
platform = espressif32@7.0.0 ; Arduino Core 2.0.17 / IDF 4.4.7
; 32MB Flash variants
[espressif32_base_32M]
@@ -67,7 +67,7 @@ framework = arduino
board_build.partitions = partitions/esp32_partition_32M.csv
board_upload.flash_size = 32MB
board_build.app_partition_name = app0
platform = espressif32@6.13.0 ; Arduino Core 2.0.17 / IDF 4.4.7
platform = espressif32@7.0.0 ; Arduino Core 2.0.17 / IDF 4.4.7
; use Tasmota's library for 4MB Flash variants.
; Removes libs (like mbedtsl, so no WiFi_secure.h) to increase available heap
@@ -105,7 +105,7 @@ board_build.filesystem = littlefs
lib_deps =
bblanchon/ArduinoJson @ 7.4.3
ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.10.3
ESP32Async/ESPAsyncWebServer @ 3.11.0
; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
; builds the web interface only, not the firmware

View File

@@ -102,7 +102,7 @@ def buildWeb():
return False
def build-webUI(*args, **kwargs):
def build_webUI(*args, **kwargs):
success = buildWeb()
if not success:
print("Web interface build failed!")
@@ -114,7 +114,7 @@ def build-webUI(*args, **kwargs):
env.AddCustomTarget(
name="build",
dependencies=None,
actions=[build-webUI],
actions=[build_webUI],
title="build web interface",
description="installs pnpm packages, updates libraries and builds web UI",
always_build=True

View File

@@ -70,16 +70,12 @@ void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument & jsonDocument) {
*/
String ArduinoJsonJWT::sign(String & payload) {
std::array<unsigned char, 32> hmacResult{};
{
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
mbedtls_md_hmac_starts(&ctx, reinterpret_cast<const unsigned char *>(_secret.c_str()), _secret.length());
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char *>(payload.c_str()), payload.length());
mbedtls_md_hmac_finish(&ctx, hmacResult.data());
mbedtls_md_free(&ctx);
}
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
reinterpret_cast<const unsigned char *>(_secret.c_str()),
_secret.length(),
reinterpret_cast<const unsigned char *>(payload.c_str()),
payload.length(),
hmacResult.data());
return encode(reinterpret_cast<const char *>(hmacResult.data()), hmacResult.size());
}

View File

@@ -185,10 +185,14 @@ bool MqttSettingsService::configureMqtt() {
#ifndef TASMOTA_SDK
if (_state.enableTLS) {
if (_state.rootCA == "insecure") {
#if defined(EMSESP_DEBUG)
emsesp::EMSESP::logger().debug("Start insecure MQTT");
#endif
static_cast<espMqttClientSecure *>(_mqttClient)->setInsecure();
} else {
#if defined(EMSESP_DEBUG)
emsesp::EMSESP::logger().debug("Start secure MQTT with rootCA");
#endif
String certificate = "-----BEGIN CERTIFICATE-----\n" + _state.rootCA + "\n-----END CERTIFICATE-----\n";
static_cast<espMqttClientSecure *>(_mqttClient)->setCACert(certificate.c_str());
}

View File

@@ -69,7 +69,9 @@ class EMSESP_Version {
return a.major_ < b.major_;
if (a.minor_ != b.minor_)
return a.minor_ < b.minor_;
return a.patch_ < b.patch_;
if (a.patch_ != b.patch_)
return a.patch_ < b.patch_;
return a.prerelease_ < b.prerelease_;
}
friend bool operator>(const EMSESP_Version & a, const EMSESP_Version & b) {
@@ -77,7 +79,7 @@ class EMSESP_Version {
}
friend bool operator==(const EMSESP_Version & a, const EMSESP_Version & b) {
return a.major_ == b.major_ && a.minor_ == b.minor_ && a.patch_ == b.patch_;
return a.major_ == b.major_ && a.minor_ == b.minor_ && a.patch_ == b.patch_ && a.prerelease_ == b.prerelease_;
}
friend bool operator!=(const EMSESP_Version & a, const EMSESP_Version & b) {

View File

@@ -387,10 +387,10 @@ void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) {
}
// get status of automatic fetch for a telegramID
bool EMSdevice::is_fetch(uint16_t telegram_id) const {
bool EMSdevice::is_fetch(uint16_t telegram_id, uint8_t len) const {
for (const auto & tf : telegram_functions_) {
if (tf.telegram_type_id_ == telegram_id) {
return tf.fetch_;
return tf.fetch_ && tf.length_ >= len;
}
}
return false;

View File

@@ -362,7 +362,7 @@ class EMSdevice {
const char * telegram_type_name(std::shared_ptr<const Telegram> telegram);
void fetch_values();
void toggle_fetch(uint16_t telegram_id, bool toggle);
bool is_fetch(uint16_t telegram_id) const;
bool is_fetch(uint16_t telegram_id, uint8_t len = 0) const;
bool is_received(uint16_t telegram_id) const;
bool has_telegram_id(uint16_t id) const;
void ha_config_clear();

View File

@@ -526,6 +526,29 @@ void System::syslog_init() {
#endif
}
// start or reconfigure modbus
void System::modbus_init() {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
if (settings.modbus_enabled) {
if (EMSESP::modbus_ == nullptr) {
EMSESP::modbus_ = new Modbus;
EMSESP::modbus_->start(1, settings.modbus_port, settings.modbus_max_clients, settings.modbus_timeout * 1000);
} else if (settings.modbus_port != modbus_port_ || settings.modbus_max_clients != modbus_max_clients_ || settings.modbus_timeout != modbus_timeout_) {
EMSESP::modbus_->stop();
EMSESP::modbus_->start(1, settings.modbus_port, settings.modbus_max_clients, settings.modbus_timeout * 1000);
}
} else if (EMSESP::modbus_ != nullptr) {
EMSESP::modbus_->stop();
delete EMSESP::modbus_;
EMSESP::modbus_ = nullptr;
}
modbus_enabled_ = settings.modbus_enabled;
modbus_port_ = settings.modbus_port;
modbus_max_clients_ = settings.modbus_max_clients;
modbus_timeout_ = settings.modbus_timeout;
});
}
// read specific major system settings to store locally for faster access
void System::store_settings(WebSettings & settings) {
version_ = settings.version;
@@ -563,25 +586,6 @@ void System::store_settings(WebSettings & settings) {
locale_ = settings.locale;
developer_mode_ = settings.developer_mode;
// start services
if (settings.modbus_enabled) {
if (EMSESP::modbus_ == nullptr) {
EMSESP::modbus_ = new Modbus;
EMSESP::modbus_->start(1, settings.modbus_port, settings.modbus_max_clients, settings.modbus_timeout * 1000);
} else if (settings.modbus_port != modbus_port_ || settings.modbus_max_clients != modbus_max_clients_ || settings.modbus_timeout != modbus_timeout_) {
EMSESP::modbus_->stop();
EMSESP::modbus_->start(1, settings.modbus_port, settings.modbus_max_clients, settings.modbus_timeout * 1000);
}
} else if (EMSESP::modbus_ != nullptr) {
EMSESP::modbus_->stop();
delete EMSESP::modbus_;
EMSESP::modbus_ = nullptr;
}
modbus_enabled_ = settings.modbus_enabled;
modbus_port_ = settings.modbus_port;
modbus_max_clients_ = settings.modbus_max_clients;
modbus_timeout_ = settings.modbus_timeout;
}
// Starts up core services
@@ -631,6 +635,7 @@ void System::start() {
network_init(); // network
uart_init(); // start UART
syslog_init(); // start syslog
modbus_init(); // start modbus
}
// button single click
@@ -1713,7 +1718,7 @@ void System::exportSystemBackup(JsonObject output) {
output["version"] = EMSESP_APP_VERSION; // add the version to the output
#ifndef EMSESP_STANDALONE
// add date/time if NTP enabled and active
// add date/time if NTP enabled and active
if ((esp_sntp_enabled()) && (EMSESP::system_.ntp_connected())) {
time_t now = time(nullptr);
if (now > 1500000000L) {
@@ -1767,7 +1772,7 @@ void System::exportSystemBackup(JsonObject output) {
node = nodes.add<JsonObject>();
node["type"] = "nvs";
const char * nvs_part = "nvs";
const char * nvs_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, "nvs1") ? "nvs1" : "nvs"; // nvs1 is on 16MBs
nvs_iterator_t it = nullptr;
#if ESP_IDF_VERSION_MAJOR < 5
it = nvs_entry_find(nvs_part, "ems-esp", NVS_TYPE_ANY);
@@ -3266,7 +3271,7 @@ void System::set_valid_system_gpios() {
valid_system_gpios_ = string_range_to_vector("0-21", "2, 8, 12-17, 18-19");
#elif CONFIG_IDF_TARGET_ESP32S2
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32s2/api-reference/peripherals/gpio.html
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32s2/api-reference/peripherals/gpio.html
// excluded:
// GPIO26 - GPIO32 = SPI flash and PSRAM
// GPIO45 - GPIO46 = strapping pins
@@ -3279,7 +3284,7 @@ void System::set_valid_system_gpios() {
valid_system_gpios_ = string_range_to_vector("0-46", "19, 20, 26-32, 45-46, 39-42, 22-25");
#elif CONFIG_IDF_TARGET_ESP32S3
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-reference/peripherals/gpio.html
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-reference/peripherals/gpio.html
// excluded:
// GPIO3, GPIO45 - GPIO46 = strapping pins
// GPIO26 - GPIO32 = SPI flash and PSRAM and not recommended
@@ -3298,7 +3303,7 @@ void System::set_valid_system_gpios() {
}
#elif CONFIG_IDF_TARGET_ESP32
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/gpio.html
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/gpio.html
// excluded:
// GPIO6 - GPIO11, GPIO16 - GPIO17 = used for SPI flash and PSRAM (dio mode only GPIO06-GPIO08, GPIO11)
// GPIO20, GPIO24, GPIO28 - GPIO31 = don't exist
@@ -3378,6 +3383,24 @@ void System::remove_gpio(uint8_t pin, bool also_system) {
}
}
// remove a gpio that has 0 for disable
void System::remove_optional_gpio(uint8_t pin) {
if (pin) {
remove_gpio(pin, false);
}
}
// set unused gpios to default state input high-Z
void System::reset_unused_gpios() {
for (const auto & pin : valid_system_gpios_) {
auto it = std::find_if(used_gpios_.begin(), used_gpios_.end(), [pin](const GpioUsage & usage) { return usage.pin == pin; });
if (it == used_gpios_.end()) {
LOG_DEBUG("reset pin %d", pin);
pinMode(pin, INPUT);
}
}
}
// return a list of GPIO's available for use
std::vector<uint8_t> System::available_gpios() {
std::vector<uint8_t> gpios;

View File

@@ -122,6 +122,7 @@ class System {
void show_mem(const char * note);
void store_settings(class WebSettings & settings);
void syslog_init();
void modbus_init();
bool check_upgrade();
bool check_restore();
void heartbeat_json(JsonObject output);
@@ -376,6 +377,8 @@ class System {
#endif
static void remove_gpio(uint8_t pin, bool also_system = false); // remove a gpio from both valid (optional) and used lists
static void remove_optional_gpio(uint8_t pin);
static void reset_unused_gpios();
// Partition info map: partition name -> {version, size, install_date}
std::map<std::string, PartitionInfo, std::less<>, AllocatorPSRAM<std::pair<const std::string, PartitionInfo>>> partition_info_;

View File

@@ -21,12 +21,6 @@
#include "temperaturesensor.h"
#include "emsesp.h"
#ifdef ESP32
#define YIELD
#else
#define YIELD yield()
#endif
namespace emsesp {
uuid::log::Logger TemperatureSensor::logger_{F_(temperaturesensor), uuid::log::Facility::DAEMON};
@@ -81,7 +75,6 @@ void TemperatureSensor::loop() {
LOG_DEBUG("Read sensor temperature");
#endif
if (bus_.reset() || parasite_) {
YIELD;
bus_.skip();
bus_.write(CMD_CONVERT_TEMP, parasite_ ? 1 : 0);
state_ = State::READING;
@@ -260,19 +253,16 @@ int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) {
LOG_ERROR("Bus reset failed before reading scratchpad from %s", Sensor(addr).id());
return EMS_VALUE_INT16_NOTSET;
}
YIELD;
uint8_t scratchpad[SCRATCHPAD_LEN] = {0};
bus_.select(addr);
bus_.write(CMD_READ_SCRATCHPAD);
bus_.read_bytes(scratchpad, SCRATCHPAD_LEN);
YIELD;
if (!bus_.reset()) {
LOG_ERROR("Bus reset failed after reading scratchpad from %s", Sensor(addr).id());
return EMS_VALUE_INT16_NOTSET;
}
YIELD;
if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) {
LOG_WARNING("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from sensor %s",

View File

@@ -173,7 +173,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
for (uint8_t i = 0; i < monitor_size; i++) {
register_telegram_type(monitor_typeids[i], "RC300Monitor", false, MAKE_PF_CB(process_RC300Monitor), 33);
register_telegram_type(set_typeids[i], "RC300Set", false, MAKE_PF_CB(process_RC300Set), 29);
register_telegram_type(summer_typeids[i], "RC300Summer", false, MAKE_PF_CB(process_RC300Summer), 13);
register_telegram_type(summer_typeids[i], "RC300Summer", false, MAKE_PF_CB(process_RC300Summer), 14);
register_telegram_type(curve_typeids[i], "RC300Curves", false, MAKE_PF_CB(process_RC300Curve), 9);
register_telegram_type(summer2_typeids[i], "RC300Summer2", false, MAKE_PF_CB(process_RC300Summer2), 8);
}
@@ -202,11 +202,12 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
if (model == EMSdevice::EMS_DEVICE_FLAG_RC100) {
register_telegram_type(0x43F, "CRHolidays", true, MAKE_PF_CB(process_RC300Holiday), 6);
} else {
register_telegram_type(0x269, "RC300Holiday", true, MAKE_PF_CB(process_RC300Holiday), 6);
register_telegram_type(0x269, "RC300Holiday", true, MAKE_PF_CB(process_RC300Holiday), 18);
}
register_telegram_type(0x16E, "Absent", true, MAKE_PF_CB(process_Absent), 1);
register_telegram_type(0xBF, "ErrorMessage", false, MAKE_PF_CB(process_ErrorMessageBF));
register_telegram_type(0xC0, "RCErrorMessage", false, MAKE_PF_CB(process_RCErrorMessage2));
register_telegram_type(0x470, "RC300Summer2", true, MAKE_PF_CB(process_RC300Summer2), 8);
EMSESP::send_read_request(0xC0, device_id, 0, 20); // read last errorcode on start (only published on errors)
// JUNKERS/HT3
@@ -1268,7 +1269,13 @@ void Thermostat::process_RC300Summer(std::shared_ptr<const Telegram> telegram) {
void Thermostat::process_RC300Summer2(std::shared_ptr<const Telegram> telegram) {
auto hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
// telegram 0x470 see https://github.com/emsesp/EMS-ESP32/issues/2686
if (telegram->type_id == 0x470 && telegram->message_length > 2) {
hc = heating_circuit(1);
summer2_typeids[0] = 0x470;
} else {
return;
}
}
if (hc->statusbyte & 1) {
has_update(telegram, hc->summersetmode, 0);

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.8.2-dev.16"
#define EMSESP_APP_VERSION "3.8.2-dev.21"

View File

@@ -166,7 +166,9 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
bool WebCustomEntityService::command_setvalue(const char * value, const int8_t id, const char * name) {
// don't write if there is no value, to prevent setting an empty value by mistake when parsing attributes
if (!strlen(value)) {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("can't set empty value!");
#endif
return false;
}
@@ -686,7 +688,7 @@ void WebCustomEntityService::fetch() {
uint8_t stop = (entity.offset + len[entity.value_type]) % fetchblock;
bool is_fetched = start < fetchblock && stop < fetchblock; // make sure the complete value is a a fetched block
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->is_device_id(entity.device_id) && emsdevice->is_fetch(entity.type_id)
if (emsdevice->is_device_id(entity.device_id) && emsdevice->is_fetch(entity.type_id, entity.offset + len[entity.value_type])
&& (is_fetched || entity.value_type == DeviceValueType::STRING)) {
needFetch = false;
break;

View File

@@ -465,7 +465,9 @@ void WebSchedulerService::condition() {
} else if (match.length() == 1 && match[0] == '0' && scheduleItem.retry_cnt == 1) {
scheduleItem.retry_cnt = 0xFF;
} else if (match.length() != 1) { // the match is not boolean
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("condition result: %s", match.c_str());
#endif
}
}
}

View File

@@ -20,7 +20,7 @@
namespace emsesp {
uint8_t WebSettings::flags_ = 0;
uint16_t WebSettings::flags_ = 0;
WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager)
@@ -114,11 +114,11 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
reset_flags();
// before loading new board profile free old gpios from used list to allow remapping
EMSESP::system_.remove_gpio(original_settings.led_gpio);
EMSESP::system_.remove_gpio(original_settings.dallas_gpio);
EMSESP::system_.remove_optional_gpio(original_settings.led_gpio);
EMSESP::system_.remove_optional_gpio(original_settings.dallas_gpio);
EMSESP::system_.remove_gpio(original_settings.pbutton_gpio);
EMSESP::system_.remove_gpio(original_settings.rx_gpio);
EMSESP::system_.remove_gpio(original_settings.tx_gpio);
EMSESP::system_.remove_optional_gpio(original_settings.rx_gpio);
EMSESP::system_.remove_optional_gpio(original_settings.tx_gpio);
// see if the user has changed the board profile
// this will set: led_gpio, dallas_gpio, rx_gpio, tx_gpio, pbutton_gpio, phy_type, eth_power, eth_phy_addr, eth_clock_mode, led_type
@@ -243,13 +243,13 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
// Modbus settings
settings.modbus_enabled = root["modbus_enabled"] | EMSESP_DEFAULT_MODBUS_ENABLED;
check_flag(original_settings.modbus_enabled, settings.modbus_enabled, ChangeFlags::RESTART);
check_flag(original_settings.modbus_enabled, settings.modbus_enabled, ChangeFlags::MODBUS);
settings.modbus_port = root["modbus_port"] | EMSESP_DEFAULT_MODBUS_PORT;
check_flag(original_settings.modbus_port, settings.modbus_port, ChangeFlags::RESTART);
check_flag(original_settings.modbus_port, settings.modbus_port, ChangeFlags::MODBUS);
settings.modbus_max_clients = root["modbus_max_clients"] | EMSESP_DEFAULT_MODBUS_MAX_CLIENTS;
check_flag(original_settings.modbus_max_clients, settings.modbus_max_clients, ChangeFlags::RESTART);
check_flag(original_settings.modbus_max_clients, settings.modbus_max_clients, ChangeFlags::MODBUS);
settings.modbus_timeout = root["modbus_timeout"] | EMSESP_DEFAULT_MODBUS_TIMEOUT;
check_flag(original_settings.modbus_timeout, settings.modbus_timeout, ChangeFlags::RESTART);
check_flag(original_settings.modbus_timeout, settings.modbus_timeout, ChangeFlags::MODBUS);
//
// these may need mqtt restart to rebuild HA discovery topics
@@ -372,7 +372,11 @@ void WebSettingsService::onUpdate() {
Mqtt::reset_mqtt(); // reload MQTT, init HA etc
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::MODBUS)) {
EMSESP::system_.modbus_init();
}
WebSettings::reset_flags();
EMSESP::system_.reset_unused_gpios();
}
void WebSettingsService::begin() {
@@ -523,7 +527,7 @@ void WebSettings::set_board_profile(WebSettings & settings) {
}
// returns true if the value was changed
bool WebSettings::check_flag(int prev_v, int new_v, uint8_t flag) {
bool WebSettings::check_flag(int prev_v, int new_v, uint16_t flag) {
if (prev_v != new_v) {
add_flags(flag);
#if defined(EMSESP_DEBUG)
@@ -534,11 +538,11 @@ bool WebSettings::check_flag(int prev_v, int new_v, uint8_t flag) {
return false;
}
void WebSettings::add_flags(uint8_t flags) {
void WebSettings::add_flags(uint16_t flags) {
flags_ |= flags;
}
bool WebSettings::has_flags(uint8_t flags) {
bool WebSettings::has_flags(uint16_t flags) {
return (flags_ & flags) == flags;
}
@@ -546,7 +550,7 @@ void WebSettings::reset_flags() {
flags_ = ChangeFlags::NONE;
}
uint8_t WebSettings::get_flags() {
uint16_t WebSettings::get_flags() {
return flags_;
}

View File

@@ -88,7 +88,7 @@ class WebSettings {
static void read(WebSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, WebSettings & settings);
enum ChangeFlags : uint8_t {
enum ChangeFlags : uint16_t {
NONE = 0,
UART = (1 << 0), // 1 - uart
SYSLOG = (1 << 1), // 2 - syslog
@@ -98,19 +98,20 @@ class WebSettings {
LED = (1 << 5), // 32 - led
BUTTON = (1 << 6), // 64 - button
MQTT = (1 << 7), // 128 - mqtt
RESTART = 0xFF // 255 - restart request (all changes)
MODBUS = (1 << 8), // 256 - modbus
RESTART = 0xFFFF // restart request (all changes)
};
static bool check_flag(int prev_v, int new_v, uint8_t flag);
static void add_flags(uint8_t flags);
static bool has_flags(uint8_t flags);
static void reset_flags();
static uint8_t get_flags();
static bool check_flag(int prev_v, int new_v, uint16_t flag);
static void add_flags(uint16_t flags);
static bool has_flags(uint16_t flags);
static void reset_flags();
static uint16_t get_flags();
private:
static void set_board_profile(WebSettings & settings);
static uint8_t flags_;
static uint16_t flags_;
};
class WebSettingsService : public StatefulService<WebSettings> {

View File

@@ -296,10 +296,6 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
version::EMSESP_Version current_version(current_version_s); // get current version
if (latest_version > current_version && current_version.minor() < latest_version.minor()) {
return 0; // if it's just a minor version upgrade return 0
}
if ((current_version.major() <= 3 && current_version.minor() <= 8) && (latest_version.major() == 3 && latest_version.minor() == 9)) {
return 1; // if moving from below 3.8.x to 3.9.x return 1
}
@@ -308,6 +304,10 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
return 2; // if it's a major version upgrade return 2
}
if (latest_version > current_version && current_version.minor() < latest_version.minor()) {
return 0; // if it's just a minor version upgrade return 0
}
return 0; // if it's not a valid version upgrade return 0
}