Merge branch 'dev'

This commit is contained in:
proddy
2026-01-11 19:14:11 +01:00
152 changed files with 18175 additions and 9460 deletions
+3 -4
View File
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.8.0]
## [3.8.0] 31 December 2025
## Added
@@ -26,8 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- FW200 display options [#2610](https://github.com/emsesp/EMS-ESP32/discussions/2610)
- CR11 mode settings OFF/MANUAL depends on selTemp [#2437](https://github.com/emsesp/EMS-ESP32/issues/2437)
- implemented eFuse settings for BBQKees boards to store model type and ESP chipset
- Analogsensors for pulse output [#2624](https://github.com/emsesp/EMS-ESP32/discussions/2624)
- Analogsensors frequency input [#2631](https://github.com/emsesp/EMS-ESP32/discussions/2631)
- analogsensors for pulse output [#2624](https://github.com/emsesp/EMS-ESP32/discussions/2624)
- analogsensors frequency input [#2631](https://github.com/emsesp/EMS-ESP32/discussions/2631)
- SRC plus thermostats [#2636](https://github.com/emsesp/EMS-ESP32/issues/2636)
- Greenstar 2000 [#2645](https://github.com/emsesp/EMS-ESP32/issues/2645)
- RC3xx `dhw modetype` [#2659](https://github.com/emsesp/EMS-ESP32/discussions/2659)
@@ -90,7 +90,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- added Home Assistant device details to MQTT Discovery for all devices
- device_class and state_class changes for HA MQTT Discovery [#2825](https://github.com/emsesp/EMS-ESP32/issues/2825)
## [3.7.2] 22 March 2025
## Added
+28
View File
@@ -1,3 +1,31 @@
# Changelog
For more details go to [emsesp.org](https://emsesp.org/).
## [3.8.1]
## Added
- update time saved in nvs
- heatpump entities [#2883](https://github.com/emsesp/EMS-ESP32/issues/2883)
- HA input number format (mode) selectable box/slider (slider for max range 100) [#2900](https://github.com/emsesp/EMS-ESP32/discussions/2900)
## Fixed
- fix EMS bus disconnected errors on some systems [#2881](https://github.com/emsesp/EMS-ESP32/issues/2881)
- selflowtemp fix [#2876](https://github.com/emsesp/EMS-ESP32/issues/2876)
- updated valid GPIOs for ESP32S2, ESP32S3 and ESP32 that caused custom systems to block gpios [#2887](https://github.com/emsesp/EMS-ESP32/issues/2887)
- Junkers wwcharge offset [#2860](https://github.com/emsesp/EMS-ESP32/issues/2860)
- fixed minflowtemp [#2890](https://github.com/emsesp/EMS-ESP32/issues/2890)
- don't add HA uom/classes for bool values [#2885](https://github.com/emsesp/EMS-ESP32/issues/2885)
- fixed missing progress bar on web firmware uploads
## Changed
- snapshot gpios stored in temporary ram
- GPIOs stored along with the name and reported in log if conflicting
- free GPIOs depend on board profile [#2901](https://github.com/emsesp/EMS-ESP32/issues/2901)
- prefer PSram for mqtt queue [#2889](https://github.com/emsesp/EMS-ESP32/issues/2889)
- day schedule defult to all days, no day selected is not allowed
- board profile `CUSTOM` can only be selected in developer mode
- mqtt sends round values without decimals (`28` instead of `28.0`)
+1 -1
View File
@@ -67,7 +67,7 @@ DEFINES += -DARDUINOJSON_ENABLE -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSO
DEFINES += -DEMSESP_STANDALONE -DEMSESP_TEST -DEMSESP_DEBUG -DEMC_RX_BUFFER_SIZE=1500
DEFINES += $(ARGS)
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.8.0-dev.0\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32S3\"
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32S3\"
#----------------------------------------------------------------------
# Sources & Files
+2 -4
View File
@@ -78,15 +78,13 @@ For a live demo go to [demo.emsesp.org](https://demo.emsesp.org). Pick a languag
## 💖  **Contributors**
EMS-ESP is a project created by [proddy](https://github.com/proddy) and owned and maintained by both [proddy](https://github.com/proddy) and [MichaelDvP](https://github.com/MichaelDvP) with support from [BBQKees Electronics](https://bbqkees-electronics.nl).
EMS-ESP is a project originally created by [proddy](https://github.com/proddy) and maintained by the ems-esp community.
If you like **EMS-ESP**, please give it a ✨ on GitHub, or even better fork it and contribute. You can also offer a small donation. This is an open-source project maintained by volunteers, and your support is greatly appreciated.
## 📦  **Building**
To build the web interface only, run `platformio run -e build_webUI`. This will install the necessary dependencies and build the web interface and also create the embedded code used need to build the firmware. You can run the web interface locally by going to the `interface` directory and running `pnpm standalone`.
To build the firmware, run `platformio run`. This will build the firmware for all ESP32 modules and place the binaries in the `build/firmware` folder. If you want to configure the build for a single platform create a local `pio_local.ni` file in the root directory (see example in `pio_local.ini_example`).
See the [Building Guide](https://emsesp.org/Building) section of the documentation for instructions on how to build EMS-ESP.
## 📢  **Libraries used**
+5
View File
@@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Please report any security vulnerabilities using the [Contact Form](https://emsesp.org/About/#-contact).
+2 -2
View File
@@ -37,8 +37,8 @@
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"use_1200bps_touch": false,
"wait_for_upload_port": false,
"require_upload_port": true,
"speed": 921600
},
+2 -1
View File
@@ -35,6 +35,7 @@
"managed_components/**",
"pnpm-*.yaml",
"vite.config.ts",
"lib/esp32-psram/**"
"lib/esp32-psram/**",
"test/test_api/test_api.h"
]
}
File diff suppressed because it is too large Load Diff
+3957 -3948
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -111,10 +111,10 @@ telegram_type_id,name,is_fetched
0x02A0,RC300Curves,
0x02A1,RC300Curves,
0x02A2,RC300Curves,
0x02A5,RC300Monitor,fetched
0x02A5,RC300Monitor,
0x02A6,RC300Monitor,
0x02A7,RC300Monitor,
0x02A8,RC300Monitor,
0x02A8,CRFMonitor,
0x02A9,RC300Monitor,
0x02AA,RC300Monitor,
0x02AB,RC300Monitor,
1 telegram_type_id name is_fetched
111 0x02A0 RC300Curves
112 0x02A1 RC300Curves
113 0x02A2 RC300Curves
114 0x02A5 RC300Monitor fetched
115 0x02A6 RC300Monitor
116 0x02A7 RC300Monitor
117 0x02A8 RC300Monitor CRFMonitor
118 0x02A9 RC300Monitor
119 0x02AA RC300Monitor
120 0x02AB RC300Monitor
+13 -13
View File
@@ -12,7 +12,7 @@
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"build-hosted": "typesafe-i18n && vite build --mode hosted",
"build-hosted": "typesafe-i18n --no-watch && vite build --mode hosted",
"mock-rest": "bun --watch ../mock-api/restServer.ts",
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"pnpm:mock-rest\" \"vite preview\"",
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite dev\"",
@@ -26,8 +26,8 @@
"@alova/adapter-xhr": "2.3.1",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.6",
"@mui/material": "^7.3.6",
"@mui/icons-material": "^7.3.7",
"@mui/material": "^7.3.7",
"@preact/compat": "^18.3.1",
"@table-library/react-table-library": "4.1.15",
"alova": "3.4.1",
@@ -37,11 +37,11 @@
"jwt-decode": "^4.0.0",
"magic-string": "^0.30.21",
"mime-types": "^3.0.2",
"preact": "^10.28.1",
"preact": "^10.28.2",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-icons": "^5.5.0",
"react-router": "^7.11.0",
"react-router": "^7.12.0",
"react-toastify": "^11.0.5",
"typesafe-i18n": "^5.26.2",
"typescript": "^5.9.3"
@@ -51,21 +51,21 @@
"@eslint/js": "^9.39.2",
"@preact/compat": "^18.3.1",
"@preact/preset-vite": "^2.10.2",
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
"@types/node": "^25.0.3",
"@types/react": "^19.2.7",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/node": "^25.0.6",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"axe-core": "^4.11.0",
"axe-core": "^4.11.1",
"concurrently": "^9.2.1",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.7.4",
"rollup-plugin-visualizer": "^6.0.5",
"terser": "^5.44.1",
"typescript-eslint": "^8.51.0",
"vite": "^7.3.0",
"typescript-eslint": "^8.52.0",
"vite": "^7.3.1",
"vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^6.0.3"
"vite-tsconfig-paths": "^6.0.4"
},
"packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
"packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48"
}
+6111
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -788,10 +788,12 @@ const Customizations = () => {
</>
);
return (
return restarting ? (
<SystemMonitor />
) : (
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <SystemMonitor /> : renderContent()}
{renderContent()}
{selectedDeviceEntity && (
<SettingsCustomizationsDialog
open={dialogOpen}
+2 -1
View File
@@ -41,6 +41,7 @@ const MIN_ID = -100;
const MAX_ID = 100;
const ICON_SIZE = 16;
const SCHEDULE_FLAG_THRESHOLD = 127;
const FLAG_ALL_DAYS = 127;
const REFERENCE_YEAR = 2017;
const REFERENCE_MONTH = '01';
const LOG_2 = Math.log(2);
@@ -51,7 +52,7 @@ const WEEK_DAYS = [1, 2, 3, 4, 5, 6, 7] as const;
const DEFAULT_SCHEDULE_ITEM: Omit<ScheduleItem, 'id' | 'o_id'> = {
active: false,
deleted: false,
flags: ScheduleFlag.SCHEDULE_DAY,
flags: FLAG_ALL_DAYS,
time: '',
cmd: '',
value: '',
+6 -4
View File
@@ -33,7 +33,8 @@ import type { ScheduleItem } from './types';
// Constants
const FLAG_MASK_127 = 127;
const SCHEDULE_TYPE_THRESHOLD = 128;
const SCHEDULE_TYPE_THRESHOLD = 127;
const FLAG_ALL_DAYS = 127;
const DEFAULT_TIME = '00:00';
const TYPOGRAPHY_FONT_SIZE = 10;
@@ -104,7 +105,7 @@ const SchedulerDialog = ({
// 130 is on condition
// 132 is immediate
setScheduleType(
selectedItem.flags < SCHEDULE_TYPE_THRESHOLD
selectedItem.flags <= SCHEDULE_TYPE_THRESHOLD
? ScheduleFlag.SCHEDULE_DAY
: selectedItem.flags
);
@@ -181,7 +182,7 @@ const SchedulerDialog = ({
setScheduleType(flag);
// wipe the time field when changing the schedule type
// set the flags based on type
const newFlags = flag === ScheduleFlag.SCHEDULE_DAY ? 0 : flag;
const newFlags = flag === ScheduleFlag.SCHEDULE_DAY ? FLAG_ALL_DAYS : flag;
setEditItem((prev) => ({ ...prev, time: '', flags: newFlags }));
}
},
@@ -190,7 +191,8 @@ const SchedulerDialog = ({
const handleDOWChange = useCallback(
(_event: React.SyntheticEvent<HTMLElement>, flags: string[]) => {
const newFlags = getFlagDOWnumber(flags);
const newFlags =
getFlagDOWnumber(flags) === 0 ? FLAG_ALL_DAYS : getFlagDOWnumber(flags);
setEditItem((prev) => ({ ...prev, flags: newFlags }));
},
[getFlagDOWnumber]
+1 -1
View File
@@ -196,7 +196,7 @@ export enum DeviceValueUOM {
MBAR,
LH,
CTKWH,
HZ
HERTZ
}
export const DeviceValueUOM_s = [
@@ -489,17 +489,25 @@ const ApplicationSettings = () => {
name="board_profile"
label={LL.BOARD_PROFILE()}
value={data.board_profile}
disabled={processingBoard || hardwareData.model.startsWith('BBQKees')}
disabled={processingBoard}
variant="outlined"
onChange={changeBoardProfile}
margin="normal"
select
>
{boardProfileItems}
<Divider />
<MenuItem key={'CUSTOM'} value={'CUSTOM'}>
{LL.CUSTOM()}&hellip;
</MenuItem>
{hardwareData.model.startsWith('BBQKees') ? (
<MenuItem key={hardwareData.board} value={hardwareData.board}>
{BOARD_PROFILES[hardwareData.board as BoardProfileKey]}
</MenuItem>
) : (
boardProfileItems
)}
{(data.board_profile === 'CUSTOM' || data.developer_mode) && <Divider />}
{(data.board_profile === 'CUSTOM' || data.developer_mode) && (
<MenuItem key={'CUSTOM'} value={'CUSTOM'}>
{LL.CUSTOM()}&hellip;
</MenuItem>
)}
</TextField>
{data.board_profile === 'CUSTOM' && (
<>
@@ -896,10 +904,12 @@ const ApplicationSettings = () => {
);
};
return (
return restarting ? (
<SystemMonitor />
) : (
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <SystemMonitor /> : content()}
{content()}
</SectionContent>
);
};
+84 -68
View File
@@ -345,75 +345,91 @@ const MqttSettings = () => {
</Grid>
)}
</Grid>
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<BlockFormControlLabel
control={
<Checkbox
name="ha_enabled"
checked={data.ha_enabled}
onChange={updateFormValue}
disabled={data.publish_single}
/>
}
label={LL.MQTT_PUBLISH_TEXT_3()}
/>
</Grid>
{data.ha_enabled && (
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<TextField
name="discovery_type"
label={LL.MQTT_PUBLISH_TEXT_5()}
value={data.discovery_type}
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={0}>Home Assistant</MenuItem>
<MenuItem value={1}>Domoticz</MenuItem>
<MenuItem value={2}>Domoticz (latest)</MenuItem>
</TextField>
</Grid>
<Grid>
<TextField
name="discovery_prefix"
label={LL.MQTT_PUBLISH_TEXT_4()}
variant="outlined"
value={data.discovery_prefix}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid>
<TextField
name="entity_format"
label={LL.MQTT_ENTITY_FORMAT()}
value={data.entity_format}
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
<MenuItem value={3}>
{LL.MQTT_ENTITY_FORMAT_1()}&nbsp;(v3.5)
</MenuItem>
<MenuItem value={4}>
{LL.MQTT_ENTITY_FORMAT_2()}&nbsp;(v3.5)
</MenuItem>
<MenuItem value={1}>
{LL.MQTT_ENTITY_FORMAT_1()}&nbsp;(latest)
</MenuItem>
<MenuItem value={2}>
{LL.MQTT_ENTITY_FORMAT_2()}&nbsp;(latest)
</MenuItem>
</TextField>
</Grid>
</Grid>
)}
{/* <Grid container spacing={2} rowSpacing={0}> */}
<Grid>
<BlockFormControlLabel
control={
<Checkbox
name="ha_enabled"
checked={data.ha_enabled}
onChange={updateFormValue}
disabled={data.publish_single}
/>
}
label={LL.MQTT_PUBLISH_TEXT_3()}
/>
</Grid>
{data.ha_enabled && (
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<TextField
name="discovery_type"
label={LL.MQTT_PUBLISH_TEXT_5()}
value={data.discovery_type}
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={0}>Home Assistant</MenuItem>
<MenuItem value={1}>Domoticz</MenuItem>
<MenuItem value={2}>Domoticz (latest)</MenuItem>
</TextField>
</Grid>
<Grid>
<TextField
name="discovery_prefix"
label={LL.MQTT_PUBLISH_TEXT_4()}
variant="outlined"
value={data.discovery_prefix}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid>
<TextField
name="entity_format"
label={LL.MQTT_ENTITY_FORMAT()}
value={data.entity_format}
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
<MenuItem value={3}>
{LL.MQTT_ENTITY_FORMAT_1()}&nbsp;(v3.5)
</MenuItem>
<MenuItem value={4}>
{LL.MQTT_ENTITY_FORMAT_2()}&nbsp;(v3.5)
</MenuItem>
<MenuItem value={1}>
{LL.MQTT_ENTITY_FORMAT_1()}&nbsp;(latest)
</MenuItem>
<MenuItem value={2}>
{LL.MQTT_ENTITY_FORMAT_2()}&nbsp;(latest)
</MenuItem>
</TextField>
</Grid>
<Grid>
{data.discovery_type === 0 && (
<TextField
name="ha_number_mode"
label={LL.MQTT_INPUT_NUMBER_FORMAT()}
value={data.ha_number_mode}
variant="outlined"
onChange={updateFormValue}
sx={{ width: '20ch' }}
margin="normal"
select
>
<MenuItem value={0}>Box</MenuItem>
<MenuItem value={1}>Slider</MenuItem>
</TextField>
)}
</Grid>
</Grid>
)}
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
{LL.MQTT_PUBLISH_INTERVALS()}&nbsp;(0=auto)
</Typography>
+1 -1
View File
@@ -182,7 +182,7 @@ const Settings = () => {
restarting
]);
return <SectionContent>{restarting ? <SystemMonitor /> : content}</SectionContent>;
return restarting ? <SystemMonitor /> : <SectionContent>{content}</SectionContent>;
};
export default Settings;
@@ -397,10 +397,12 @@ const NetworkSettings = () => {
);
};
return (
return restarting ? (
<SystemMonitor />
) : (
<SectionContent>
{blocker ? <BlockNavigation blocker={blocker} /> : null}
{restarting ? <SystemMonitor /> : content()}
{content()}
</SectionContent>
);
};
+1 -1
View File
@@ -443,7 +443,7 @@ const SystemStatus = () => {
renderRestartDialog
]);
return <SectionContent>{restarting ? <SystemMonitor /> : content}</SectionContent>;
return restarting ? <SystemMonitor /> : <SectionContent>{content}</SectionContent>;
};
export default SystemStatus;
+2 -2
View File
@@ -103,8 +103,8 @@ const SystemMonitor = () => {
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backdropFilter: 'blur(8px)'
justifyContent: 'center'
// backdropFilter: 'blur(8px)'
}}
>
<Box
+1 -1
View File
@@ -868,7 +868,7 @@ const Version = () => {
installPartitionFirmware
]);
return <SectionContent>{restarting ? <SystemMonitor /> : content}</SectionContent>;
return restarting ? <SystemMonitor /> : <SectionContent>{content}</SectionContent>;
};
export default memo(Version);
+2 -2
View File
@@ -1,9 +1,9 @@
import { memo } from 'react';
import { type FC, type PropsWithChildren, memo } from 'react';
import { Box } from '@mui/material';
import type { BoxProps } from '@mui/material';
const ButtonRow = memo<BoxProps>(({ children, ...rest }) => (
const ButtonRow: FC<PropsWithChildren<BoxProps>> = memo(({ children, ...rest }) => (
<Box
sx={{
'& button, & a, & .MuiCard-root': {
+3 -2
View File
@@ -1,4 +1,4 @@
import { type FC, memo, useMemo } from 'react';
import { type FC, type PropsWithChildren, memo, useMemo } from 'react';
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
import ErrorIcon from '@mui/icons-material/Error';
@@ -12,6 +12,7 @@ type MessageBoxLevel = 'warning' | 'success' | 'info' | 'error';
export interface MessageBoxProps extends BoxProps {
level: MessageBoxLevel;
message?: string;
children?: React.ReactNode;
}
const LEVEL_ICONS: Record<MessageBoxLevel, React.ComponentType<SvgIconProps>> = {
@@ -28,7 +29,7 @@ const LEVEL_PALETTE_PATHS: Record<MessageBoxLevel, string> = {
error: 'error.dark'
};
const MessageBox: FC<MessageBoxProps> = ({
const MessageBox: FC<PropsWithChildren<MessageBoxProps>> = ({
level,
message,
sx,
@@ -39,9 +39,8 @@ const LayoutMenuItemComponent = ({
left: 0,
top: 0,
bottom: 0,
width: selected ? '4px' : '0px',
width: selected ? '3px' : '0px',
backgroundColor: '#90caf9',
borderRadius: '0 2px 2px 0',
transition: 'width 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)'
}
}),
+1
View File
@@ -219,6 +219,7 @@ const cz: Translation = {
MQTT_PUBLISH_TEXT_3: 'Povolit MQTT Discovery',
MQTT_PUBLISH_TEXT_4: 'Prefix pro Discovery témata',
MQTT_PUBLISH_TEXT_5: 'Typ Discovery',
MQTT_INPUT_NUMBER_FORMAT: 'Formát čísla',
MQTT_PUBLISH_INTERVALS: 'Intervaly publikování',
MQTT_INT_BOILER: 'Kotle a tepelná čerpadla',
MQTT_INT_THERMOSTATS: 'Termostaty',
+1
View File
@@ -219,6 +219,7 @@ const de: Translation = {
MQTT_PUBLISH_TEXT_3: 'Aktiviere `MQTT Discovery`',
MQTT_PUBLISH_TEXT_4: 'Prefix für die `Discovery`-Topics',
MQTT_PUBLISH_TEXT_5: 'Discovery Typ',
MQTT_INPUT_NUMBER_FORMAT: 'Zahlenformat',
MQTT_PUBLISH_INTERVALS: 'Veröffentlichungs-Intervalle',
MQTT_INT_BOILER: 'Boiler und Wärmepumpen',
MQTT_INT_THERMOSTATS: 'Thermostate',
+1
View File
@@ -219,6 +219,7 @@ const en: Translation = {
MQTT_PUBLISH_TEXT_3: 'Enable MQTT Discovery',
MQTT_PUBLISH_TEXT_4: 'Prefix for the Discovery topics',
MQTT_PUBLISH_TEXT_5: 'Discovery type',
MQTT_INPUT_NUMBER_FORMAT: 'Input Number Fomat',
MQTT_PUBLISH_INTERVALS: 'Publish Intervals',
MQTT_INT_BOILER: 'Boilers and Heat Pumps',
MQTT_INT_THERMOSTATS: 'Thermostats',
+1
View File
@@ -219,6 +219,7 @@ const fr: Translation = {
MQTT_PUBLISH_TEXT_3: 'Activer la découverte MQTT',
MQTT_PUBLISH_TEXT_4: 'Préfixe pour les topics découverte',
MQTT_PUBLISH_TEXT_5: 'Type de découverte',
MQTT_INPUT_NUMBER_FORMAT: 'Format des nombres',
MQTT_PUBLISH_INTERVALS: 'Intervalles de publication',
MQTT_INT_BOILER: 'Chaudières et pompes à chaleur',
MQTT_INT_THERMOSTATS: 'Thermostats',
+1
View File
@@ -219,6 +219,7 @@ const it: Translation = {
MQTT_PUBLISH_TEXT_3: 'Abilita rilevamento MQTT (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'Prefisso per gli argomenti di scoperta',
MQTT_PUBLISH_TEXT_5: 'Discovery type',
MQTT_INPUT_NUMBER_FORMAT: 'Formato numero di input',
MQTT_PUBLISH_INTERVALS: 'Pubblica intervalli',
MQTT_INT_BOILER: 'Caldaie e Pompe di Calore',
MQTT_INT_THERMOSTATS: 'Termostati',
+1
View File
@@ -219,6 +219,7 @@ const nl: Translation = {
MQTT_PUBLISH_TEXT_3: 'Activeer MQTT Discovery',
MQTT_PUBLISH_TEXT_4: 'Prefix voor de Discovery topics',
MQTT_PUBLISH_TEXT_5: 'Discovery type',
MQTT_INPUT_NUMBER_FORMAT: 'Getalformaat',
MQTT_PUBLISH_INTERVALS: 'Publicatie intervallen',
MQTT_INT_BOILER: 'CV ketels en warmtepompen',
MQTT_INT_THERMOSTATS: 'Thermostaten',
+1
View File
@@ -219,6 +219,7 @@ const no: Translation = {
MQTT_PUBLISH_TEXT_3: 'Aktiver MQTT Discovery',
MQTT_PUBLISH_TEXT_4: 'Prefiks for Discovery topics',
MQTT_PUBLISH_TEXT_5: 'Discovery type',
MQTT_INPUT_NUMBER_FORMAT: 'Nummerformat',
MQTT_PUBLISH_INTERVALS: 'Publiseringsintervall',
MQTT_INT_BOILER: 'Fyr/Varmepumpe',
MQTT_INT_THERMOSTATS: 'Termostat',
+1
View File
@@ -219,6 +219,7 @@ const pl: BaseTranslation = {
MQTT_PUBLISH_TEXT_3: 'Włącz opcję "MQTT discovery"',
MQTT_PUBLISH_TEXT_4: 'Prefiks dla "MQTT discovery"',
MQTT_PUBLISH_TEXT_5: 'Typ "MQTT discovery"',
MQTT_INPUT_NUMBER_FORMAT: 'Format liczby',
MQTT_PUBLISH_INTERVALS: 'Interwały publikowania',
MQTT_INT_BOILER: 'Kotły i pompy ciepła',
MQTT_INT_THERMOSTATS: 'Termostaty',
+1
View File
@@ -219,6 +219,7 @@ const sk: Translation = {
MQTT_PUBLISH_TEXT_3: 'Povolenie zisťovania MQTT',
MQTT_PUBLISH_TEXT_4: 'Predpona tém Discovery',
MQTT_PUBLISH_TEXT_5: 'Typ zistenia',
MQTT_INPUT_NUMBER_FORMAT: 'Formát čísla',
MQTT_PUBLISH_INTERVALS: 'Intervaly zverejňovania',
MQTT_INT_BOILER: 'Kotly a tepelné čerpadlá',
MQTT_INT_THERMOSTATS: 'Termostaty',
+1
View File
@@ -219,6 +219,7 @@ const sv: Translation = {
MQTT_PUBLISH_TEXT_3: 'Aktivera MQTT Discovery',
MQTT_PUBLISH_TEXT_4: 'Prefix för Discovery topics',
MQTT_PUBLISH_TEXT_5: 'Discoverytyp',
MQTT_INPUT_NUMBER_FORMAT: 'Nummerformat',
MQTT_PUBLISH_INTERVALS: 'Publiceringsintervall',
MQTT_INT_BOILER: 'Värmepump/panna',
MQTT_INT_THERMOSTATS: 'Termostater',
+1
View File
@@ -219,6 +219,7 @@ const tr: Translation = {
MQTT_PUBLISH_TEXT_3: 'MQTT keşfi etkinleştir (Home Assistant, Domoticz)',
MQTT_PUBLISH_TEXT_4: 'Keşif konuları için ön ek',
MQTT_PUBLISH_TEXT_5: 'Domoticz Format',
MQTT_INPUT_NUMBER_FORMAT: 'Sayı Biçimi',
MQTT_PUBLISH_INTERVALS: 'Yayınlama aralıkları',
MQTT_INT_BOILER: 'Kazanlar ve Isı Pompaları',
MQTT_INT_THERMOSTATS: 'Termostatlar',
+1
View File
@@ -49,4 +49,5 @@ export interface MqttSettingsType {
publish_single2cmd: boolean;
discovery_prefix: string;
discovery_type: number;
ha_number_mode: number;
}
+1
View File
@@ -50,6 +50,7 @@ export interface SystemStatus {
free_psram?: number;
free_caps: number;
model: string;
board: string;
has_loader: boolean;
has_partition: boolean;
partitions: {
+10 -3
View File
@@ -136,9 +136,16 @@ const createManualChunks = (detailed = false) => {
return 'vendor';
}
if (detailed) {
if (id.includes('components/')) return 'components';
if (id.includes('app/')) return 'app';
if (id.includes('utils/')) return 'utils';
// Group circularly dependent modules together to avoid circular chunk warnings
// components, app, and utils are tightly coupled, so combine them
if (
id.includes('components/') ||
id.includes('app/') ||
id.includes('utils/')
) {
return 'app';
}
// Keep api separate as it's typically more independent
if (id.includes('api/')) return 'api';
}
return undefined;
+2 -2
View File
@@ -52,7 +52,7 @@ ModbusClientTCPasync::~ModbusClientTCPasync() {
}
}
// force close client
MTA_client.close(true);
MTA_client.close();
}
// optionally manually connect to modbus server. Otherwise connection will be made upon first request
@@ -79,7 +79,7 @@ void ModbusClientTCPasync::connect(IPAddress host, uint16_t port) {
// manually disconnect from modbus server. Connection will also auto close after idle time
void ModbusClientTCPasync::disconnect(bool force) {
LOG_D("disconnecting\n");
MTA_client.close(force);
MTA_client.close();
}
// Set timeout value
+1 -1
View File
@@ -253,7 +253,7 @@ void ModbusServerTCPasync::onClientConnect(AsyncClient* client) {
LOG_D("nr clients: %u\n", clients.size());
} else {
LOG_D("max number of clients reached, closing new\n");
client->close(true);
client->close();
delete client;
}
}
+8 -1
View File
@@ -321,7 +321,14 @@ bool Packet::_allocate(size_t remainingLength, bool check) {
#if EMC_USE_MEMPOOL
_data = reinterpret_cast<uint8_t*>(_memPool.malloc(_size));
#else
_data = reinterpret_cast<uint8_t*>(malloc(_size));
#ifdef EMSESP_STANDALONE
_data = reinterpret_cast<uint8_t*>(malloc(_size));
#else
_data = reinterpret_cast<uint8_t*>(ps_malloc(_size));
if (!_data) {
_data = reinterpret_cast<uint8_t*>(malloc(_size));
}
#endif
#endif
if (!_data) {
_size = 0;
@@ -42,7 +42,7 @@ int ClientAsync::read(uint8_t* buf, size_t size) {
}
void ClientAsync::stop() {
client.close(false);
client.close();
}
bool ClientAsync::connected() {
+4
View File
@@ -76,6 +76,10 @@ void ledcWrite(uint8_t chan, uint32_t duty);
void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val);
void rgbLedWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val);
inline int rtc_get_reset_reason(int cpu_no) {
return 0;
}
#define PROGMEM
#define PGM_P const char *
+1
View File
@@ -36,6 +36,7 @@ class DummySettings {
uint8_t nested_format = 1; // 1=nested 2=single
String discovery_prefix = "homeassistant";
uint8_t discovery_type = 0; // HA
uint8_t ha_number_mode = 0; // box
bool ha_enabled = true;
String base = "ems-esp";
bool publish_single = false;
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -13
View File
@@ -1,13 +1 @@
When developing and testing the web interface, it's handy not to bother with re-flashing an ESP32 each time. The idea is to mimic the ESP using a mock/stub server that responds to the REST (HTTP POST & GET) and WebSocket calls.
# prerequisites
- Install the latest LTS of NodeJS
- install corepack `npm install -g corepack@latest` and `orepack enable`
- Install pnpm (`corepack use pnpm@latest-10` on linux or `winget install -e --id pnpm.pnpm` on windows). See <https://pnpm.io/installation>
- Install bun (<https://bun.sh/docs/installation>)
- type `pnpm install` from this `mock-api` folder to build
# To run
- `pnpm standalone` from the main `interface` folder and then navigate to <http://localhost:3000>
Refer to the [EMS-ESP documentation](https://emsesp.org/Building#webui-development) on how to run the mock Web server.
+2 -2
View File
@@ -10,10 +10,10 @@
},
"dependencies": {
"@msgpack/msgpack": "^3.1.3",
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"formidable": "^3.5.4",
"itty-router": "^5.0.22",
"prettier": "^3.7.4"
},
"packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
"packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48"
}
+316
View File
@@ -0,0 +1,316 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
'@msgpack/msgpack':
specifier: ^3.1.3
version: 3.1.3
'@trivago/prettier-plugin-sort-imports':
specifier: ^6.0.2
version: 6.0.2(prettier@3.7.4)
formidable:
specifier: ^3.5.4
version: 3.5.4
itty-router:
specifier: ^5.0.22
version: 5.0.22
prettier:
specifier: ^3.7.4
version: 3.7.4
packages:
'@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'}
'@babel/generator@7.28.5':
resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-globals@7.28.0':
resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
engines: {node: '>=6.9.0'}
'@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.28.5':
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
'@babel/parser@7.28.5':
resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
'@babel/traverse@7.28.5':
resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
engines: {node: '>=6.9.0'}
'@babel/types@7.28.5':
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
engines: {node: '>=6.9.0'}
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@msgpack/msgpack@3.1.3':
resolution: {integrity: sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==}
engines: {node: '>= 18'}
'@noble/hashes@1.8.0':
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
engines: {node: ^14.21.3 || >=16}
'@paralleldrive/cuid2@2.3.1':
resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==}
'@trivago/prettier-plugin-sort-imports@6.0.2':
resolution: {integrity: sha512-3DgfkukFyC/sE/VuYjaUUWoFfuVjPK55vOFDsxD56XXynFMCZDYFogH2l/hDfOsQAm1myoU/1xByJ3tWqtulXA==}
engines: {node: '>= 20'}
peerDependencies:
'@vue/compiler-sfc': 3.x
prettier: 2.x - 3.x
prettier-plugin-ember-template-tag: '>= 2.0.0'
prettier-plugin-svelte: 3.x
svelte: 4.x || 5.x
peerDependenciesMeta:
'@vue/compiler-sfc':
optional: true
prettier-plugin-ember-template-tag:
optional: true
prettier-plugin-svelte:
optional: true
svelte:
optional: true
asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dezalgo@1.0.4:
resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==}
formidable@3.5.4:
resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==}
engines: {node: '>=14.0.0'}
itty-router@5.0.22:
resolution: {integrity: sha512-9hmdGErWdYDOurGYxSbqLhy4EFReIwk71hMZTJ5b+zfa2zjMNV1ftFno2b8VjAQvX615gNB8Qxbl9JMRqHnIVA==}
javascript-natural-sort@0.7.1:
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
hasBin: true
lodash-es@4.17.22:
resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
parse-imports-exports@0.2.4:
resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==}
parse-statements@1.0.11:
resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
prettier@3.7.4:
resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==}
engines: {node: '>=14'}
hasBin: true
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
snapshots:
'@babel/code-frame@7.27.1':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
js-tokens: 4.0.0
picocolors: 1.1.1
'@babel/generator@7.28.5':
dependencies:
'@babel/parser': 7.28.5
'@babel/types': 7.28.5
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
'@babel/helper-globals@7.28.0': {}
'@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.28.5': {}
'@babel/parser@7.28.5':
dependencies:
'@babel/types': 7.28.5
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/parser': 7.28.5
'@babel/types': 7.28.5
'@babel/traverse@7.28.5':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/generator': 7.28.5
'@babel/helper-globals': 7.28.0
'@babel/parser': 7.28.5
'@babel/template': 7.27.2
'@babel/types': 7.28.5
debug: 4.4.3
transitivePeerDependencies:
- supports-color
'@babel/types@7.28.5':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping': 0.3.31
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.31':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@msgpack/msgpack@3.1.3': {}
'@noble/hashes@1.8.0': {}
'@paralleldrive/cuid2@2.3.1':
dependencies:
'@noble/hashes': 1.8.0
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.7.4)':
dependencies:
'@babel/generator': 7.28.5
'@babel/parser': 7.28.5
'@babel/traverse': 7.28.5
'@babel/types': 7.28.5
javascript-natural-sort: 0.7.1
lodash-es: 4.17.22
minimatch: 9.0.5
parse-imports-exports: 0.2.4
prettier: 3.7.4
transitivePeerDependencies:
- supports-color
asap@2.0.6: {}
balanced-match@1.0.2: {}
brace-expansion@2.0.2:
dependencies:
balanced-match: 1.0.2
debug@4.4.3:
dependencies:
ms: 2.1.3
dezalgo@1.0.4:
dependencies:
asap: 2.0.6
wrappy: 1.0.2
formidable@3.5.4:
dependencies:
'@paralleldrive/cuid2': 2.3.1
dezalgo: 1.0.4
once: 1.4.0
itty-router@5.0.22: {}
javascript-natural-sort@0.7.1: {}
js-tokens@4.0.0: {}
jsesc@3.1.0: {}
lodash-es@4.17.22: {}
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.2
ms@2.1.3: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
parse-imports-exports@0.2.4:
dependencies:
parse-statements: 1.0.11
parse-statements@1.0.11: {}
picocolors@1.1.1: {}
prettier@3.7.4: {}
wrappy@1.0.2: {}
+3
View File
@@ -130,7 +130,9 @@ let system_status = {
// partitions: [],
developer_mode: true,
model: '',
board: '',
// model: 'BBQKees Electronics EMS Gateway E32 V2 (E32 V2.0 P3/2024011)',
// board: 'E32V2',
// status: 0,
status: 3
};
@@ -584,6 +586,7 @@ let mqtt_settings = {
keep_alive: 60,
clean_session: true,
entity_format: 1,
ha_number_mode: 0,
publish_time_boiler: 10,
publish_time_thermostat: 10,
publish_time_solar: 10,
+4 -4
View File
@@ -105,8 +105,8 @@ build_type = release
board_build.filesystem = littlefs
lib_deps =
bblanchon/ArduinoJson @ 7.4.2
ESP32Async/AsyncTCP @ 3.4.9
ESP32Async/ESPAsyncWebServer @ 3.9.3
ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.9.4
https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
@@ -228,7 +228,7 @@ build_src_flags =
-DEMSESP_STANDALONE -DEMSESP_TEST
-DEMSESP_UNITY
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.8.0-dev.0\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
-std=gnu++17 -Og -ggdb
-Wall -Wextra
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
@@ -284,7 +284,7 @@ build_flags =
[env:standalone]
extends = env:native
build_flags =
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.8.0-dev.0\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
; Modbus
; Creates the file modbus_entity_parameters.hpp
+3
View File
@@ -1446,3 +1446,6 @@ vchip
SPIIO
SPIDQS
txpause
relais
pinout
TXEN
+7 -5
View File
@@ -94,8 +94,9 @@ def print_device_entities(device_name, device_entities):
type_suffix = split_type[1] if len(split_type) > 1 else ""
# Optimize type_rest extraction and int range detection
type_rest_str = type_suffix
type_rest_str = ""
if "int" in type_base and "(" in type_suffix:
print(type_base)
try:
# Extract inner part of parentheses
range_inner = type_suffix[type_suffix.index("(")+1:type_suffix.index(")")]
@@ -106,14 +107,12 @@ def print_device_entities(device_name, device_entities):
elif "/" in range_inner:
min_value, max_value = range_inner.split("/")
if min_value is not None and max_value is not None:
type_rest_str = f"(&gt;={min_value.strip()}&lt;={max_value.strip()})"
else:
type_rest_str = ""
type_rest_str = f" (&gt;={min_value.strip()}&lt;={max_value.strip()})"
except Exception:
# fallback to original
pass
print(f"| {entity['shortname']} | {entity['fullname']} | {type_base} {type_rest_str} | "
print(f"| {entity['shortname']} | {entity['fullname']} | {type_base}{type_rest_str} | "
f"{entity['uom']} | {entity['writeable']} | {tag_type} | {entity['modbus offset']} | "
f"{entity['modbus count']} | {entity['modbus scale factor']} |")
print()
@@ -135,6 +134,9 @@ def print_device_type_devices(device_type, devices):
def print_header():
"""Print the markdown document header."""
print("---")
print("id: Modbus-Entity-Registers")
print("---")
print("# Modbus Entity/Register Mapping")
print()
print(":::warning")
+6 -6
View File
@@ -58,12 +58,12 @@ enum APNetworkStatus { ACTIVE = 0, INACTIVE, LINGERING };
class APSettings {
public:
uint8_t provisionMode; // 0 = on, 2 = off
String ssid;
String password;
uint8_t channel;
bool ssidHidden;
uint8_t maxClients;
uint8_t provisionMode = FACTORY_AP_PROVISION_MODE; // 0 = on, 2 = off
String ssid = FACTORY_AP_SSID;
String password = FACTORY_AP_PASSWORD;
uint8_t channel = FACTORY_AP_CHANNEL;
bool ssidHidden = FACTORY_AP_SSID_HIDDEN;
uint8_t maxClients = FACTORY_AP_MAX_CLIENTS;
IPAddress localIP;
IPAddress gatewayIP;
+4
View File
@@ -70,6 +70,10 @@ class FSPersistence {
// failed to open file, return false
if (!settingsFile || !jsonObject.size()) {
#if defined(EMSESP_DEBUG)
Serial.printf("Failed to write file %s", _filePath);
Serial.println();
#endif
return false;
}
+2
View File
@@ -248,6 +248,7 @@ void MqttSettings::read(MqttSettings & settings, JsonObject root) {
root["nested_format"] = settings.nested_format;
root["discovery_prefix"] = settings.discovery_prefix;
root["discovery_type"] = settings.discovery_type;
root["ha_number_mode"] = settings.ha_number_mode;
root["publish_single"] = settings.publish_single;
root["publish_single2cmd"] = settings.publish_single2cmd;
root["send_response"] = settings.send_response;
@@ -288,6 +289,7 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
newSettings.nested_format = static_cast<uint8_t>(root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT);
newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX;
newSettings.discovery_type = static_cast<uint8_t>(root["discovery_type"] | EMSESP_DEFAULT_DISCOVERY_TYPE);
newSettings.ha_number_mode = static_cast<uint8_t>(root["ha_number_mode"] | EMSESP_DEFAULT_HA_NUMBER_MODE);
newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE;
newSettings.publish_single2cmd = root["publish_single2cmd"] | EMSESP_DEFAULT_PUBLISH_SINGLE2CMD;
newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE;
+30 -28
View File
@@ -8,6 +8,7 @@
#include <espMqttClient.h>
#include <uuid/common.h>
#include <default_settings.h>
#define MQTT_RECONNECTION_DELAY 2000 // 2 seconds
@@ -65,37 +66,38 @@
class MqttSettings {
public:
bool enabled;
String host;
uint16_t port;
String rootCA;
bool enableTLS;
String username;
String password;
bool enabled = FACTORY_MQTT_ENABLED;
String host = FACTORY_MQTT_HOST;
uint16_t port = FACTORY_MQTT_PORT;
String rootCA = "";
bool enableTLS = false;
String username = FACTORY_MQTT_USERNAME;
String password = FACTORY_MQTT_PASSWORD;
String clientId;
uint16_t keepAlive;
bool cleanSession;
uint16_t keepAlive = FACTORY_MQTT_KEEP_ALIVE;
bool cleanSession = FACTORY_MQTT_CLEAN_SESSION;
// EMS-ESP specific
String base;
uint16_t publish_time_boiler;
uint16_t publish_time_thermostat;
uint16_t publish_time_solar;
uint16_t publish_time_mixer;
uint16_t publish_time_water;
uint16_t publish_time_other;
uint16_t publish_time_sensor;
uint16_t publish_time_heartbeat;
uint8_t mqtt_qos;
bool mqtt_retain;
bool ha_enabled;
uint8_t nested_format;
String discovery_prefix;
uint8_t discovery_type;
bool publish_single;
bool publish_single2cmd;
bool send_response;
uint8_t entity_format;
String base = FACTORY_MQTT_BASE;
uint16_t publish_time_boiler = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_thermostat = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_solar = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_mixer = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_water = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_other = EMSESP_DEFAULT_PUBLISH_TIME_OTHER;
uint16_t publish_time_sensor = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_heartbeat = EMSESP_DEFAULT_PUBLISH_HEARTBEAT;
uint8_t mqtt_qos = EMSESP_DEFAULT_MQTT_QOS;
bool mqtt_retain = EMSESP_DEFAULT_MQTT_RETAIN;
bool ha_enabled = EMSESP_DEFAULT_HA_ENABLED;
uint8_t nested_format = EMSESP_DEFAULT_NESTED_FORMAT;
String discovery_prefix = EMSESP_DEFAULT_DISCOVERY_PREFIX;
uint8_t discovery_type = EMSESP_DEFAULT_DISCOVERY_TYPE;
uint8_t ha_number_mode = EMSESP_DEFAULT_HA_NUMBER_MODE;
bool publish_single = EMSESP_DEFAULT_PUBLISH_SINGLE;
bool publish_single2cmd = EMSESP_DEFAULT_PUBLISH_SINGLE2CMD;
bool send_response = EMSESP_DEFAULT_SEND_RESPONSE;
uint8_t entity_format = EMSESP_DEFAULT_ENTITY_FORMAT;
static void read(MqttSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, MqttSettings & settings);
+4 -4
View File
@@ -30,10 +30,10 @@
class NTPSettings {
public:
bool enabled;
String tzLabel;
String tzFormat;
String server;
bool enabled = FACTORY_NTP_ENABLED;
String tzLabel = FACTORY_NTP_TIME_ZONE_LABEL;
String tzFormat = FACTORY_NTP_TIME_ZONE_FORMAT;
String server = FACTORY_NTP_SERVER;
static void read(NTPSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, NTPSettings & settings);
+11 -11
View File
@@ -64,17 +64,17 @@
class NetworkSettings {
public:
// core wifi configuration
String ssid;
String bssid;
String password;
String hostname;
bool staticIPConfig;
bool bandwidth20;
uint8_t tx_power;
bool nosleep;
bool enableMDNS;
bool enableCORS;
String CORSOrigin;
String ssid = FACTORY_WIFI_SSID;
String bssid = "";
String password = FACTORY_WIFI_PASSWORD;
String hostname = FACTORY_WIFI_HOSTNAME;
bool staticIPConfig = false;
bool bandwidth20 = false;
uint8_t tx_power = 0;
bool nosleep = true;
bool enableMDNS = true;
bool enableCORS = false;
String CORSOrigin = "*";
// optional configuration for static IP address
IPAddress localIP;
-1
View File
@@ -9,7 +9,6 @@
#include <list>
#define ACCESS_TOKEN_PARAMATER "access_token"
#define AUTHORIZATION_HEADER "Authorization"
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
+1 -1
View File
@@ -172,7 +172,7 @@ void UploadFileService::handleError(AsyncWebServerRequest * request, int code) {
// check for invalid extension and immediately kill the connection, which will throw an error
// that is caught by the web code. Unfortunately the http error code is not sent to the client on fast network connections
if (code == 406) {
request->client()->close(true);
request->client()->close();
_is_firmware = false;
Update.abort();
}
+12 -3
View File
@@ -56,8 +56,7 @@ void IRAM_ATTR AnalogSensor::freqIrq2() {
#endif
void AnalogSensor::start(const bool factory_settings) {
// if (factory_settings && EMSESP::nvs_.getString("boot").equals("E32V2_2")) {
if (factory_settings && analogReadMilliVolts(39) > 700) { // core voltage > 2.6V
if (factory_settings && EMSESP::system_.board_profile() == "E32V2_2") {
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
auto newSensor = AnalogCustomization();
strcpy(newSensor.name, "core_voltage");
@@ -68,12 +67,22 @@ void AnalogSensor::start(const bool factory_settings) {
newSensor.type = AnalogType::ADC;
newSensor.is_system = true;
settings.analogCustomizations.push_back(newSensor);
EMSESP::system_.add_gpio(newSensor.gpio, newSensor.name);
strcpy(newSensor.name, "supply_voltage");
newSensor.gpio = 36;
newSensor.factor = 0.017; // Divider 24k - 1,5k
newSensor.is_system = true;
settings.analogCustomizations.push_back(newSensor);
EMSESP::system_.add_gpio(newSensor.gpio, newSensor.name);
strcpy(newSensor.name, "led");
newSensor.gpio = 2;
newSensor.type = AnalogType::DIGITAL_OUT;
newSensor.uom = DeviceValueUOM::NONE; // unchanged after restart
newSensor.factor = 1; // active high
settings.analogCustomizations.push_back(newSensor);
EMSESP::system_.add_gpio(newSensor.gpio, newSensor.name);
return StateUpdateResult::CHANGED; // persist the change
});
@@ -576,7 +585,7 @@ bool AnalogSensor::update(uint8_t gpio, const char * org_name, double offset, do
newSensor.is_system = is_system;
settings.analogCustomizations.push_back(newSensor);
// check the gpio again and add to used list
if (EMSESP::system_.add_gpio(gpio, "Analog Sensor")) {
if (EMSESP::system_.add_gpio(gpio, name)) {
LOG_DEBUG("Adding customization for analog sensor GPIO %02d", gpio);
return StateUpdateResult::CHANGED; // persist the change
} else {
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,7 +1,7 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+5 -5
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -25,10 +25,6 @@
#define EMSESP_DEFAULT_LOCALE EMSESP_LOCALE_EN // English
#endif
#ifndef EMSESP_DEFAULT_VERSION
#define EMSESP_DEFAULT_VERSION ""
#endif
#ifndef EMSESP_DEFAULT_TX_MODE
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
#endif
@@ -214,6 +210,10 @@
#define EMSESP_DEFAULT_DISCOVERY_TYPE 0 // HA
#endif
#ifndef EMSESP_DEFAULT_HA_NUMBER_MODE
#define EMSESP_DEFAULT_HA_NUMBER_MODE 0 // BOX
#endif
#ifndef EMSESP_DEFAULT_PUBLISH_SINGLE
#define EMSESP_DEFAULT_PUBLISH_SINGLE false
#endif
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+61 -37
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -547,40 +547,56 @@ void EMSdevice::add_device_value(int8_t tag, // to b
int16_t min, // min allowed value
uint32_t max // max allowed value
) {
// initialize the device value depending on it's type
if (type == DeviceValueType::STRING) {
*(char *)(value_p) = {'\0'}; // this is important for string functions like strlen() to work later
} else if (type == DeviceValueType::INT8) {
*(int8_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_INT8_DUMMY : EMS_VALUE_DEFAULT_INT8;
} else if (type == DeviceValueType::UINT8) {
*(uint8_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_UINT8_DUMMY : EMS_VALUE_DEFAULT_UINT8;
} else if (type == DeviceValueType::INT16) {
*(int16_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_INT16_DUMMY : EMS_VALUE_DEFAULT_INT16;
} else if (type == DeviceValueType::UINT16) {
*(uint16_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_UINT16_DUMMY : EMS_VALUE_DEFAULT_UINT16;
} else if ((type == DeviceValueType::UINT24) || (type == DeviceValueType::TIME) || (type == DeviceValueType::UINT32)) {
*(uint32_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_UINT24_DUMMY : EMS_VALUE_DEFAULT_UINT24;
} else if (type == DeviceValueType::BOOL) {
*(int8_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_BOOL_DUMMY : EMS_VALUE_DEFAULT_BOOL; // bool is uint8_t, but other initial value
} else if (type == DeviceValueType::ENUM) {
*(uint8_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_ENUM_DUMMY : EMS_VALUE_DEFAULT_ENUM; // enums behave as uint8_t
} else if (type == DeviceValueType::CMD) {
if (uom == DeviceValueUOM::NONE) {
*(uint8_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_ENUM_DUMMY : EMS_VALUE_DEFAULT_ENUM; // enums behave as uint8_t
} else if (uom == DeviceValueUOM::DEGREES) {
*(int16_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_INT16_DUMMY : EMS_VALUE_DEFAULT_INT16;
} else if (uom == DeviceValueUOM::PERCENT) {
*(uint8_t *)(value_p) = System::test_set_all_active() ? EMS_VALUE_DEFAULT_UINT8_DUMMY : EMS_VALUE_DEFAULT_UINT8;
}
}
uint8_t state = DeviceValueState::DV_DEFAULT; // determine state
auto custom_fullname = std::string(""); // custom fullname
auto short_name = name[0]; // entity name
bool has_cmd = (f != nullptr); // is it a command?
bool ignore = false; // ignore this entity?
// check test mode, which populates all device values with some dummy date
bool test_active = System::test_set_all_active();
// if writable and in test mode, set to max value directly; otherwise set to default/dummy
bool use_max = test_active && has_cmd;
switch (type) {
case DeviceValueType::STRING:
*(char *)(value_p) = {'\0'}; // this is important for string functions like strlen() to work later
break;
case DeviceValueType::INT8:
*(int8_t *)(value_p) = use_max ? static_cast<int8_t>(max) : (test_active ? EMS_VALUE_DEFAULT_INT8_DUMMY : EMS_VALUE_DEFAULT_INT8);
break;
case DeviceValueType::UINT8:
*(uint8_t *)(value_p) = use_max ? static_cast<uint8_t>(max) : (test_active ? EMS_VALUE_DEFAULT_UINT8_DUMMY : EMS_VALUE_DEFAULT_UINT8);
break;
case DeviceValueType::INT16:
*(int16_t *)(value_p) = use_max ? static_cast<int16_t>(max) : (test_active ? EMS_VALUE_DEFAULT_INT16_DUMMY : EMS_VALUE_DEFAULT_INT16);
break;
case DeviceValueType::UINT16:
*(uint16_t *)(value_p) = use_max ? static_cast<uint16_t>(max) : (test_active ? EMS_VALUE_DEFAULT_UINT16_DUMMY : EMS_VALUE_DEFAULT_UINT16);
break;
case DeviceValueType::UINT24:
case DeviceValueType::TIME:
case DeviceValueType::UINT32:
*(uint32_t *)(value_p) = use_max ? static_cast<uint32_t>(max) : (test_active ? EMS_VALUE_DEFAULT_UINT24_DUMMY : EMS_VALUE_DEFAULT_UINT24);
break;
case DeviceValueType::BOOL:
*(int8_t *)(value_p) = use_max ? static_cast<int8_t>(max ? 1 : 0) : (test_active ? EMS_VALUE_DEFAULT_BOOL_DUMMY : EMS_VALUE_DEFAULT_BOOL);
break;
case DeviceValueType::ENUM:
*(uint8_t *)(value_p) = use_max ? static_cast<uint8_t>(max) : (test_active ? EMS_VALUE_DEFAULT_ENUM_DUMMY : EMS_VALUE_DEFAULT_ENUM);
break;
case DeviceValueType::CMD:
if (uom == DeviceValueUOM::NONE) {
*(uint8_t *)(value_p) = use_max ? static_cast<uint8_t>(max) : (test_active ? EMS_VALUE_DEFAULT_ENUM_DUMMY : EMS_VALUE_DEFAULT_ENUM);
} else if (uom == DeviceValueUOM::DEGREES) {
*(int16_t *)(value_p) = use_max ? static_cast<int16_t>(max) : (test_active ? EMS_VALUE_DEFAULT_INT16_DUMMY : EMS_VALUE_DEFAULT_INT16);
} else if (uom == DeviceValueUOM::PERCENT) {
*(uint8_t *)(value_p) = use_max ? static_cast<uint8_t>(max) : (test_active ? EMS_VALUE_DEFAULT_UINT8_DUMMY : EMS_VALUE_DEFAULT_UINT8);
}
break;
}
// get fullname, getting translation if it exists
const char * const * fullname;
if (Helpers::count_items(name) == 1) {
@@ -1295,11 +1311,21 @@ void EMSdevice::getCustomizationEntities(std::vector<std::string> & entity_ids)
// dumps all entity values in native English
// the code is intended to run only once standalone, outside the ESP32 so not optimized for memory efficiency
// pipe symbols (|) are escaped so they can be converted to Markdown in the Wiki
// format is: device name,device type,product id,shortname,fullname,type [options...] \\| (min/max),uom,writeable,discovery entityid v3.4, discovery entityid
// format is: device name,device type,product id,shortname,fullname,type [options...] [(min/max)],uom,writeable,discovery entityid v3.4, discovery entityid
#if defined(EMSESP_STANDALONE)
void EMSdevice::dump_devicevalue_info() {
for (auto & dv : devicevalues_) {
if (dv.fullname != nullptr) {
// for debugging, finding max values
/*
if (dv.has_cmd) {
if (dv.max - dv.min > 400) {
Serial.printf("shortname:%s, min:%d, max:%d\n", dv.short_name, dv.min, dv.max);
}
}
continue;
*/
Serial.print('\"');
Serial.print(default_name());
Serial.print('\"');
@@ -1323,7 +1349,7 @@ void EMSdevice::dump_devicevalue_info() {
Serial.print(dv.fullname[0]);
Serial.print(',');
// per type
// type [options...] [(min/max)]
switch (dv.type) {
case DeviceValueType::ENUM:
case DeviceValueType::CMD:
@@ -1383,14 +1409,12 @@ void EMSdevice::dump_devicevalue_info() {
break;
}
// min/max range
int16_t dv_set_min;
uint32_t dv_set_max;
if (dv.get_min_max(dv_set_min, dv_set_max)) {
// min/max range, if writeable
if (dv.has_cmd) {
Serial.print(" (>=");
Serial.print(dv_set_min);
Serial.print(dv.min);
Serial.print("<=");
Serial.print(dv_set_max);
Serial.print(dv.max);
Serial.print(")");
}
Serial.print(",");
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+2 -2
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -103,7 +103,7 @@ DeviceValue::DeviceValue(uint8_t device_type,
}
// mapping of UOM, to match order in DeviceValueUOM enum emsdevicevalue.h
// also maps to DeviceValueUOM in interface/src/project/types.ts for the Web UI
// also maps to DeviceValueUOM in interface/src/app/main/types.ts for the Web UI
// must be an int of 4 bytes, 32bit aligned
const char * DeviceValue::DeviceValueUOM_s[] = {
+2 -2
View File
@@ -1,7 +1,7 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -76,7 +76,7 @@ class DeviceValue {
MBAR, // 24 - mbar - atmospheric pressure
LH, // 25 - l/h - volume flow rate
CTKWH, // 26 - ct/kWh - monetary
HZ, // 27 - Hz - frequency
HERTZ, // 27 - Hz - frequency
CONNECTIVITY // 28 - used in HA - connectivity
};
+23 -6
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -20,6 +20,7 @@
#ifndef EMSESP_STANDALONE
#include "esp_ota_ops.h"
#include "rom/rtc.h"
#endif
static_assert(uuid::thread_safe, "uuid-common must be thread-safe");
@@ -1721,15 +1722,31 @@ void EMSESP::start() {
LOG_INFO("EMS-ESP version %s", EMSESP_APP_VERSION);
#endif
// check if the firmware is fresh
LOG_DEBUG("System is running in Debug mode");
// check if the firmware is fresh, i.e. a new install or a new version has been uploaded
// this is set in UploadFileService::uploadComplete()
// and reset in System::set_partition_install_date()
if (!EMSESP::nvs_.getBool(EMSESP_NVS_BOOT_NEW_FIRMWARE)) {
LOG_DEBUG("Firmware is fresh");
if (EMSESP::nvs_.getBool(EMSESP_NVS_BOOT_NEW_FIRMWARE)) {
LOG_DEBUG("Firmware is a new install");
} else {
// check if the firmware has been uploaded via Serial/USB
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
if (rtc_get_reset_reason(0) == 11) { // ESP_RST_USB (Reset by USB peripheral, on CPU 0 only
#else
if ((rtc_get_reset_reason(0) == 14 || rtc_get_reset_reason(1) == 14)) { // APP CPU reset by PRO CPU, can be either CPUs
#endif
LOG_DEBUG("Firmware is a new install, uploaded via Serial/USB");
EMSESP::nvs_.putBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, true); // set flag so it's picked up later to set the install date
}
}
LOG_DEBUG("System is running in Debug mode");
// S2 are C3 are both single core
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
LOG_INFO("Last system reset reason Core0: %s", system_.reset_reason(0).c_str());
#else
LOG_INFO("Last system reset reason Core0: %s, Core1: %s", system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str());
#endif
// see if we're restoring a settings file
#ifndef EMSESP_STANDALONE
@@ -1837,7 +1854,7 @@ void EMSESP::loop() {
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO) {
static bool only_once = false;
if (!only_once) {
LOG_ERROR("Invalid GPIOs used. Please check your settings and log");
LOG_ERROR("Invalid GPIOs used. Please check your settings and the system log");
only_once = true;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+17 -8
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -207,6 +207,18 @@ char * Helpers::render_boolean(char * result, const bool value, const bool dashb
return result;
}
char * Helpers::remove_trailing_zero(char * result) {
if (strchr(result, '.')) {
while (result[strlen(result) - 1] == '0') {
result[strlen(result) - 1] = '\0';
}
if (result[strlen(result) - 1] == '.') {
result[strlen(result) - 1] = '\0';
}
}
return result;
}
// convert unsigned int (single byte) to text value and returns it
// format: 255(0xFF)=boolean, 0=no formatting, otherwise divide by format
char * Helpers::render_value(char * result, uint8_t value, int8_t format, const uint8_t fahrenheit) {
@@ -240,14 +252,11 @@ char * Helpers::render_value(char * result, uint8_t value, int8_t format, const
strlcpy(result, itoa(new_value >> 1, s2, 10), 5);
strlcat(result, ".", 5);
strlcat(result, ((new_value & 0x01) ? "5" : "0"), 7);
return result;
} else if (format == 4) {
strlcpy(result, itoa(new_value >> 2, s2, 10), 5);
strlcat(result, ".", 5);
new_value = (new_value & 0x03) * 25;
strlcat(result, itoa(new_value, s2, 10), 7);
return result;
} else if (format > 0) {
strlcpy(result, itoa(new_value / format, s2, 10), 5);
strlcat(result, ".", 5);
@@ -256,7 +265,7 @@ char * Helpers::render_value(char * result, uint8_t value, int8_t format, const
strlcpy(result, itoa(new_value * format * -1, s2, 10), 5);
}
return result;
return remove_trailing_zero(result);
}
// float: convert float to char
@@ -297,7 +306,7 @@ char * Helpers::render_value(char * result, const double value, const int8_t for
}
itoa(decimal, result, 10);
return ret;
return remove_trailing_zero(ret);
}
// int32: convert signed 32bit to text string and returns string
@@ -335,7 +344,7 @@ char * Helpers::render_value(char * result, const int32_t value, const int8_t fo
strlcat(result, itoa(new_value * format * -1, s, 10), sizeof(s));
}
return result;
return remove_trailing_zero(result);
}
// int16: convert short (two bytes) to text string and prints it
@@ -397,7 +406,7 @@ char * Helpers::render_value(char * result, const uint32_t value, const int8_t f
}
#endif
return result;
return remove_trailing_zero(result);
}
// convert special Latin1 characters to UTF8
+3 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -88,6 +88,8 @@ class Helpers {
#ifdef EMSESP_STANDALONE
static char * ultostr(char * ptr, uint32_t value, const uint8_t base);
#endif
private:
static char * remove_trailing_zero(char * result);
};
} // namespace emsesp
+3 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -245,6 +245,7 @@ MAKE_NOTRANSLATION(tag_device_data, "")
// Unit Of Measurement mapping - maps to DeviceValueUOM_s in emsdevice.cpp
// Translating hours/minute/seconds are done in emsdevice.cpp (uom_to_string())
// to keep to some form of consistency, all UOMs follow Home Assistant naming conventions - https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes
MAKE_WORD_CUSTOM(uom_blank, " ")
MAKE_WORD_CUSTOM(uom_percent, "%")
MAKE_WORD_CUSTOM(uom_degrees, "°C")
@@ -381,6 +382,7 @@ MAKE_ENUM(enum_lowNoiseMode, FL_(off), FL_(reduced_output), FL_(switchoff), FL_(
// heat pump
MAKE_ENUM(enum_hpactivity, FL_(off), FL_(heating), FL_(cooling), FL_(hot_water), FL_(pool), FL_(pool_heating), FL_(defrost), FL_(compressor_alarm)) // BV name COMPRESSOR_E21_STATUS
MAKE_ENUM(enum_hpactivity1, FL_(off), FL_(heating), FL_(hot_water), FL_(defrost), FL_(compressor_alarm))
MAKE_ENUM(enum_silentMode, FL_(off), FL_(auto), FL_(on))
MAKE_ENUM(enum_4way, FL_(cool_defrost), FL_(heat_ww))
MAKE_ENUM(enum_auxHeaterSource, FL_(not_installed), FL_(el_heater), FL_(exclusive), FL_(parallel), FL_(hybrid))
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+27 -24
View File
@@ -504,30 +504,33 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpPh1), 13, 1), // hpph1
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatingPumpMod), 14, 1), // heatingpumpmod
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpCompSpd), 15, 1), // hpcompspd
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hybridStrategy), 16, 1), // hybridstrategy
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(lowNoiseMode), 17, 1), // lownoisemode
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(lowNoiseStart), 18, 1), // lownoisestart
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(lowNoiseStop), 19, 1), // lownoisestop
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(energyPriceGas), 20, 1), // energypricegas
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(energyPriceEl), 21, 1), // energypriceel
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(energyPricePV), 22, 1), // energyfeedpv
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(switchOverTemp), 23, 1), // switchovertemp
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(airPurgeMode), 24, 1), // airpurgemode
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatPumpOutput), 25, 1), // heatpumpoutput
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(coolingCircuit), 26, 1), // coolingcircuit
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(compStartMod), 27, 1), // compstartmod
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatDrainPan), 28, 1), // heatdrainpan
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatCable), 29, 1), // heatcable
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(nrgTotal), 30, 2), // nrgtotal
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(nrgHeat), 32, 2), // nrgheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterTotal), 34, 2), // metertotal
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterComp), 36, 2), // metercomp
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterEHeat), 38, 2), // metereheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterHeat), 40, 2), // meterheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatingStarts), 42, 2), // heatingstarts
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(fuelHeat), 44, 2), // fuelheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(elHeat), 46, 2), // elheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(elGenHeat), 48, 2), // elgenheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpActivity), 16, 1), // hpactivity
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpPower), 17, 1), // hppower
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hpCurrPower), 18, 1), // hpcurrpower
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(hybridStrategy), 19, 1), // hybridstrategy
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(lowNoiseMode), 20, 1), // lownoisemode
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(lowNoiseStart), 21, 1), // lownoisestart
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(lowNoiseStop), 22, 1), // lownoisestop
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(energyPriceGas), 23, 1), // energypricegas
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(energyPriceEl), 24, 1), // energypriceel
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(energyPricePV), 25, 1), // energyfeedpv
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(switchOverTemp), 26, 1), // switchovertemp
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(airPurgeMode), 27, 1), // airpurgemode
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatPumpOutput), 28, 1), // heatpumpoutput
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(coolingCircuit), 29, 1), // coolingcircuit
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(compStartMod), 30, 1), // compstartmod
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatDrainPan), 31, 1), // heatdrainpan
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatCable), 32, 1), // heatcable
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(nrgTotal), 33, 2), // nrgtotal
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(nrgHeat), 35, 2), // nrgheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterTotal), 37, 2), // metertotal
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterComp), 39, 2), // metercomp
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterEHeat), 41, 2), // metereheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(meterHeat), 43, 2), // meterheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(heatingStarts), 45, 2), // heatingstarts
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(fuelHeat), 47, 2), // fuelheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(elHeat), 49, 2), // elheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(elGenHeat), 51, 2), // elgenheat
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DHW, FL_(hybridDHW), 0, 1), // hybriddhw
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DHW, FL_(nrgWw), 1, 2), // nrg
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DHW, FL_(meterWw), 3, 2), // meter
+41 -31
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -43,6 +43,7 @@ bool Mqtt::ha_enabled_;
uint8_t Mqtt::nested_format_;
std::string Mqtt::discovery_prefix_;
uint8_t Mqtt::discovery_type_;
uint8_t Mqtt::ha_number_mode_;
bool Mqtt::send_response_;
bool Mqtt::publish_single_;
bool Mqtt::publish_single2cmd_;
@@ -343,6 +344,7 @@ void Mqtt::load_settings() {
discovery_prefix_ = mqttSettings.discovery_prefix.c_str();
entity_format_ = mqttSettings.entity_format;
discovery_type_ = mqttSettings.discovery_type;
ha_number_mode_ = mqttSettings.ha_number_mode;
// convert to milliseconds
publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000;
@@ -562,8 +564,8 @@ void Mqtt::ha_status() {
// Note we don't use camelCase as it would change the HA entity_id and impact historic data
#ifndef EMSESP_STANDALONE
if (!EMSESP::system_.ethernet_connected() || WiFi.isConnected()) {
publish_system_ha_sensor_config(DeviceValueType::INT8, "WiFi RSSI", "rssi", DeviceValueUOM::DBM);
publish_system_ha_sensor_config(DeviceValueType::INT8, "WiFi strength", "wifistrength", DeviceValueUOM::PERCENT);
publish_system_ha_sensor_config(DeviceValueType::INT8, "RSSI", "rssi", DeviceValueUOM::DBM);
publish_system_ha_sensor_config(DeviceValueType::INT8, "Signal", "wifistrength", DeviceValueUOM::PERCENT);
}
#endif
@@ -1024,8 +1026,12 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
snprintf(sample_val, sizeof(sample_val), "'%s'", Helpers::translated_word(options[0]));
}
} else if (type != DeviceValueType::STRING && type != DeviceValueType::BOOL) {
// For numeric's add the range
doc["mode"] = "box"; // auto, slider or box
// For numeric's add the range and mode
if (ha_number_mode_ == 1 && (dv_set_max - dv_set_min) <= 100) {
doc["mode"] = "slider";
} else {
doc["mode"] = "box"; // auto, slider or box
}
if (num_op > 0) {
doc["step"] = 1.0 / num_op;
} else if (num_op < 0) {
@@ -1132,6 +1138,9 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
if (device_type == EMSdevice::DeviceType::SYSTEM) {
doc["ent_cat"] = "diagnostic"; // instead of 'config'
}
if (type == DeviceValueType::BOOL) {
return;
}
// For display_only, we don't use the aliases, also we don't display the icon
const char * dc_ha = display_only ? "device_class" : "dev_cla"; // device class, dev_cla
@@ -1146,24 +1155,24 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
// set uom, unless boolean - as HA is fussy with the naming and is case sensitive
// map too HA uom specific codes from https://github.com/home-assistant/core/blob/dev/homeassistant/const.py
if (type != DeviceValueType::BOOL) {
if (uom == DeviceValueUOM::HOURS) {
doc[uom_ha] = "h";
} else if (uom == DeviceValueUOM::MINUTES) {
doc[uom_ha] = "min";
} else if (uom == DeviceValueUOM::SECONDS) {
doc[uom_ha] = "s";
} else if (uom == DeviceValueUOM::KB) {
doc[uom_ha] = "kB";
} else if (uom == DeviceValueUOM::LMIN) {
doc[uom_ha] = "L/min";
} else if (uom == DeviceValueUOM::LH) {
doc[uom_ha] = "L/h";
} else if (uom != DeviceValueUOM::NONE) {
doc[uom_ha] = EMSdevice::uom_to_string(uom); // use default
} else if (discovery_type() != discoveryType::HOMEASSISTANT) {
doc[uom_ha] = " "; // Domoticz uses " " for a no-uom
}
if (uom == DeviceValueUOM::HOURS) {
doc[uom_ha] = "h";
} else if (uom == DeviceValueUOM::MINUTES) {
doc[uom_ha] = "min";
} else if (uom == DeviceValueUOM::SECONDS) {
doc[uom_ha] = "s";
} else if (uom == DeviceValueUOM::KB) {
doc[uom_ha] = "kB";
} else if (uom == DeviceValueUOM::LMIN) {
doc[uom_ha] = "L/min";
} else if (uom == DeviceValueUOM::LH) {
doc[uom_ha] = "L/h";
} else if (uom == DeviceValueUOM::L) {
doc[uom_ha] = "L";
} else if (uom != DeviceValueUOM::NONE) {
doc[uom_ha] = EMSdevice::uom_to_string(uom); // use default
} else if (discovery_type() != discoveryType::HOMEASSISTANT) {
doc[uom_ha] = " "; // Domoticz uses " " for a no-uom
}
// set state and device class - always
@@ -1261,7 +1270,7 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
doc[sc_ha] = sc_ha_total_increasing;
doc[dc_ha] = "monetary";
break;
case DeviceValueUOM::HZ:
case DeviceValueUOM::HERTZ:
doc[sc_ha] = sc_ha_measurement;
doc[dc_ha] = "frequency";
break;
@@ -1280,9 +1289,8 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
// DeviceValueUOM::NONE:
// DeviceValueUOM::KMIN:
// for device entities which have numerical values, with no UOM
if ((type != DeviceValueType::STRING)
&& (type == DeviceValueType::INT8 || type == DeviceValueType::UINT8 || type == DeviceValueType::INT16 || type == DeviceValueType::UINT16
|| type == DeviceValueType::UINT24 || type == DeviceValueType::UINT32)) {
if (type == DeviceValueType::INT8 || type == DeviceValueType::UINT8 || type == DeviceValueType::INT16 || type == DeviceValueType::UINT16
|| type == DeviceValueType::UINT24 || type == DeviceValueType::UINT32) {
if (display_only) {
doc[ic_ha] = F_(iconnum); // set icon
}
@@ -1418,11 +1426,13 @@ bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_room
doc["mode_cmd_t"] = mode_cmd_s;
// add hvac_action - https://github.com/emsesp/EMS-ESP32/discussions/2562
doc["act_t"] = "~/boiler_data";
doc["act_tpl"] = "{% if value_json.hpactivity=='cooling'%}cooling{%elif value_json.heatingactive=='on'%}heating{%else%}idle{%endif%}";
doc["act_t"] = "~/boiler_data";
char on_string[12];
doc["act_tpl"] = "{% if value_json.hpactivity=='cooling'%}cooling{%elif value_json.heatingactive=='" + std::string(Helpers::render_boolean(on_string, true))
+ "'%}heating{%else%}idle{%endif%}"; // uses boolean translation for on/true
// map EMS modes to HA climate modes
// EMS modes: auto, manual, heat, off, night, day, nofrost, eco, comfort, cool)
// EMS modes: auto, manual, heat, off, night, day, nofrost, eco, comfort, cool
// HA supports: auto, off, cool, heat, dry, fan_only
bool found_auto = false;
bool found_heat = false;
@@ -1628,4 +1638,4 @@ void Mqtt::add_value_bool(JsonObject doc, const char * name, bool value) {
}
}
} // namespace emsesp
} // namespace emsesp
+2 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -341,6 +341,7 @@ class Mqtt {
static uint8_t entity_format_;
static std::string discovery_prefix_;
static uint8_t discovery_type_;
static uint8_t ha_number_mode_;
static bool publish_single_;
static bool publish_single2cmd_;
static bool send_response_;
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+259 -136
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -105,10 +105,8 @@ uint32_t System::led_flash_duration_ = 0;
bool System::led_flash_timer_ = false;
// GPIOs
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::valid_system_gpios_;
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::used_gpios_;
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::snapshot_used_gpios_;
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::snapshot_valid_system_gpios_;
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::valid_system_gpios_;
std::vector<System::GpioUsage, AllocatorPSRAM<System::GpioUsage>> System::used_gpios_;
// find the index of the language
// 0 = EN, 1 = DE, etc...
@@ -318,13 +316,15 @@ void System::get_partition_info() {
auto current_partition = (const char *)esp_ota_get_running_partition()->label;
// update the current version and partition name in NVS if not already set (saves on flash wearing)
if (EMSESP::nvs_.isKey(current_partition)) {
if (EMSESP::nvs_.getString(current_partition) != EMSESP_APP_VERSION) {
EMSESP::nvs_.putString(current_partition, EMSESP_APP_VERSION);
}
} else {
EMSESP::nvs_.putString(current_partition, EMSESP_APP_VERSION); // create new entry
// 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)) {
EMSESP::nvs_.putBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, false);
EMSESP::nvs_.putString(current_partition, EMSESP_APP_VERSION);
char c[20];
snprintf(c, sizeof(c), "d_%s", current_partition);
auto t = time(nullptr);
// write timestamp always with new version, if clock is not set, this will be updated with ntp
EMSESP::nvs_.putULong(c, t);
}
// Loop through all available partitions and update map with the version info pulled from NVS
@@ -347,16 +347,17 @@ void System::get_partition_info() {
// get the version from the NVS store, and add to map
if (is_valid) {
PartitionInfo p_info;
p_info.version = "";
p_info.install_date = ""; // this will be added later when NTP is connected
// 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();
char c[20];
snprintf(c, sizeof(c), "d_%s", (const char *)part->label);
time_t d = EMSESP::nvs_.getULong(c, 0);
char time_string[25];
strftime(time_string, sizeof(time_string), "%FT%T", localtime(&d));
p_info.install_date = d > 1500000000L ? time_string : "";
p_info.size = part->size / 1024; // set size in KB
// if there is an entry for this partition in NVS, get it's version from NVS
if (EMSESP::nvs_.isKey(part->label)) {
p_info.version = EMSESP::nvs_.getString(part->label).c_str();
}
partition_info_[part->label] = p_info;
}
@@ -368,36 +369,21 @@ void System::get_partition_info() {
// set NTP install time/date for the current partition
// assumes NTP is connected and working
void System::set_partition_install_date(bool override) {
void System::set_partition_install_date() {
#ifndef EMSESP_STANDALONE
auto current_partition = (const char *)esp_ota_get_running_partition()->label;
if (current_partition == nullptr) {
return; // fail-safe
}
// skip if it already has an install date, unless override is true
if (!partition_info_[current_partition].install_date.empty() && !override) {
return;
char c[20];
snprintf(c, sizeof(c), "d_%s", current_partition);
time_t d = EMSESP::nvs_.getULong(c, 0);
if (d < 1500000000L) {
LOG_INFO("Firmware is fresh, setting the new install date in partition %s", current_partition);
auto t = time(nullptr) - uuid::get_uptime_sec();
EMSESP::nvs_.putULong(c, t);
}
// EMSESP_NVS_BOOT_NEW_FIRMWARE is set in UploadFileService::uploadComplete()
auto is_fresh_firmware = EMSESP::nvs_.getBool(EMSESP_NVS_BOOT_NEW_FIRMWARE);
// always reset flag after setting the install date
EMSESP::nvs_.putBool(EMSESP_NVS_BOOT_NEW_FIRMWARE, false);
// check if the firmware is a new upload
if (is_fresh_firmware) {
LOG_DEBUG("Firmware is fresh, setting the new install date in partition %s", current_partition);
} else {
return; // skip, not new
}
// set current date/time from NTP
char time_string[25];
time_t now = time(nullptr);
strftime(time_string, sizeof(time_string), "%FT%T", localtime(&now));
partition_info_[current_partition].install_date = time_string;
LOG_DEBUG("Adding NTP install date %s to partition %s", time_string, current_partition);
#endif
}
@@ -1228,16 +1214,16 @@ void System::show_system(uuid::console::Shell & shell) {
// GPIOs
shell.println(" GPIOs:");
shell.printf(" in use:");
for (const auto & gpio : used_gpios_) {
shell.printf(" %d", gpio);
for (const auto & usage : used_gpios_) {
shell.printf(" %d(%s)", usage.pin, usage.source.c_str());
}
shell.printfln(" (total %d)", used_gpios_.size());
shell.printfln(" [total %d]", used_gpios_.size());
auto available = available_gpios();
shell.printf(" available:");
for (const auto & gpio : available) {
shell.printf(" %d", gpio);
}
shell.printfln(" (total %d)", available.size());
shell.printfln(" [total %d]", available.size());
// List all partitions and their version info
shell.println(" Partitions:");
for (const auto & partition : partition_info_) {
@@ -1422,7 +1408,7 @@ bool System::check_upgrade() {
// see if we're missing a version, will be < 3.5.0b13 from Dec 23 2022
missing_version = (settingsVersion.empty() || (settingsVersion.length() < 5));
if (missing_version) {
LOG_WARNING("No version information found");
LOG_WARNING("No version information found. Assuming version 3.5.0");
settingsVersion = "3.5.0"; // this was the last stable version without version info
}
@@ -1434,7 +1420,7 @@ bool System::check_upgrade() {
bool save_version = true;
bool reboot_required = false;
LOG_DEBUG("Checking for version upgrades (settings file is v%d.%d.%d%s)",
LOG_DEBUG("Checking for version upgrades from v%d.%d.%d%s",
settings_version.major(),
settings_version.minor(),
settings_version.patch(),
@@ -1444,7 +1430,7 @@ bool System::check_upgrade() {
if (this_version > settings_version) {
// we need to do an upgrade
if (missing_version) {
LOG_NOTICE("Upgrading to version %d.%d.%d%s", this_version.major(), this_version.minor(), this_version.patch(), this_version_type);
LOG_NOTICE("Upgrading to version %d.%d.%d%s", this_version.major(), this_version.minor(), this_version.patch(), this_version_type.c_str());
} else {
LOG_NOTICE("Upgrading from version %d.%d.%d%s to %d.%d.%d%s",
settings_version.major(),
@@ -1511,7 +1497,15 @@ bool System::check_upgrade() {
});
} else if (this_version < settings_version) {
// downgrading
LOG_NOTICE("Downgrading to version %d.%d.%d%s", this_version.major(), this_version.minor(), this_version.patch(), this_version_type.c_str());
LOG_NOTICE("Downgrading from version %d.%d.%d%s to version %d.%d.%d%s",
settings_version.major(),
settings_version.minor(),
settings_version.patch(),
settings_version_type.c_str(),
this_version.major(),
this_version.minor(),
this_version.patch(),
this_version_type.c_str());
} else {
save_version = false; // same version, do nothing
}
@@ -1749,9 +1743,10 @@ void System::get_value_json(JsonObject output, const std::string & circuit, cons
if (circuit.length()) {
output["circuit"] = circuit;
}
output["readable"] = true;
output["writeable"] = (name == "showerTimer" || name == "showerAlert" || name == "enabled" || name == "hideLed" || name == "analogEnabled");
output["visible"] = true;
output["readable"] = true;
output["writeable"] =
(name == "txpause" || name == "showerTimer" || name == "showerAlert" || name == "enabled" || name == "hideLed" || name == "analogEnabled");
output["visible"] = true;
if (val.is<bool>()) {
output["value"] = val.as<bool>();
output["type"] = "boolean";
@@ -1842,7 +1837,6 @@ std::string System::get_metrics_prometheus() {
for (JsonPair p : obj) {
std::string key = p.key().c_str();
std::string path = prefix.empty() ? key : prefix + "." + key;
std::string metric_name = prefix.empty() ? key : prefix + "_" + key;
if (should_ignore(prefix, key)) {
@@ -1981,6 +1975,7 @@ std::string System::get_metrics_prometheus() {
}
result += info_metric;
// TODO fix, as local_info_labels is always empty here
if (!local_info_labels.empty()) {
result += "{";
bool first = true;
@@ -2042,8 +2037,9 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
#else
node["version"] = EMSESP_APP_VERSION;
#endif
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["uptimeSec"] = uuid::get_uptime_sec();
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["uptimeSec"] = uuid::get_uptime_sec();
node["resetReason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);
#ifndef EMSESP_STANDALONE
node["platform"] = EMSESP_PLATFORM;
node["cpuType"] = ESP.getChipModel();
@@ -2056,10 +2052,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["freeApp"] = EMSESP::system_.appFree(); // kilobytes
node["partition"] = (const char *)esp_ota_get_running_partition()->label; // active partition
node["flash_chip_size"] = ESP.getFlashChipSize() / 1024; // kilobytes
#endif
node["resetReason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);
#ifndef EMSESP_STANDALONE
node["psram"] = (EMSESP::system_.PSram() > 0); // make boolean
node["psram"] = (EMSESP::system_.PSram() > 0); // make boolean
if (EMSESP::system_.PSram()) {
node["psramSize"] = EMSESP::system_.PSram();
node["freePsram"] = ESP.getFreePsram() / 1024;
@@ -2068,8 +2061,27 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
node["temperature"] = EMSESP::system_.temperature();
#endif
#endif
node["txpause"] = EMSbus::tx_mode() == EMS_TXMODE_OFF;
// GPIO information
std::string gpios_in_use_str;
for (const auto & usage : EMSESP::system_.used_gpios_) {
if (!gpios_in_use_str.empty()) {
gpios_in_use_str += ", ";
}
gpios_in_use_str += Helpers::itoa(usage.pin);
}
node["gpios_in_use"] = gpios_in_use_str;
std::string gpios_available_str;
for (const auto & gpio : EMSESP::system_.available_gpios()) {
if (!gpios_available_str.empty()) {
gpios_available_str += ", ";
}
gpios_available_str += Helpers::itoa(gpio);
}
node["gpios_available"] = gpios_available_str;
// Network Status
node = output["network"].to<JsonObject>();
@@ -2387,35 +2399,62 @@ bool System::load_board_profile(std::vector<int8_t> & data, const std::string &
if (board_profile == "default") {
return false; // unknown, return false
} else if (board_profile == "S32") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // BBQKees Gateway S32
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // BBQKees Gateway S32
valid_system_gpios_ = {0, 2, 5, 18, 23};
} else if (board_profile == "E32") {
data = {2, 4, 5, 17, 33, PHY_type::PHY_TYPE_LAN8720, 16, 1, 0, 0}; // BBQKees Gateway E32
data = {2, 4, 5, 17, 33, PHY_type::PHY_TYPE_LAN8720, 16, 1, 0, 0}; // BBQKees Gateway E32
valid_system_gpios_ = {0, 2, 4, 5, 16, 17, 33};
} else if (board_profile == "E32V2") {
data = {2, 14, 4, 5, 34, PHY_type::PHY_TYPE_LAN8720, 15, 0, 1, 0}; // BBQKees Gateway E32 V2
data = {2, 14, 4, 5, 34, PHY_type::PHY_TYPE_LAN8720, 15, 0, 1, 0}; // BBQKees Gateway E32 V2
valid_system_gpios_ = {0, 2, 4, 5, 14, 15, 34};
} else if (board_profile == "E32V2_2") {
data = {32, 14, 4, 5, 34, PHY_type::PHY_TYPE_LAN8720, 15, 0, 1, 1}; // BBQKees Gateway E32 V2.2, rgb led
data = {32, 14, 4, 5, 34, PHY_type::PHY_TYPE_LAN8720, 15, 0, 1, 1}; // BBQKees Gateway E32 V2.2, rgb led
valid_system_gpios_ = {0, 2, 4, 5, 14, 15, 32, 34, 36, 39}; // system analogs 36, 39, led 2
} else if (board_profile == "MH-ET") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // MH-ET Live D1 Mini
// allow only pins that are marked as `can always be used`
valid_system_gpios_ = {0, 2, 5, 18, 23, 12, 13, 14, 15, 16, 17, 26, 27, 33};
// can always be used: 12, 13 ,14, 15, 16, 17, 26, 27, 33
// can be used if no other function 2, 4, 5, 9, 10, 18, 19, 21, 22, 23, 25, 34, 35, 36, 39
} else if (board_profile == "NODEMCU") {
data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // NodeMCU 32S
// https://blog.berrybase.de/esp32-node-mcu-module-anfaenger-guide/
// all available pins, exclude uart0
valid_system_gpios_ = {0, 2, 5, 18, 23, 4, 12, 13, 14, 15, 16, 17, 21, 22, 25, 26, 27, 32, 33, 34, 35, 36, 39};
} else if (board_profile == "LOLIN") {
data = {2, 18, 17, 16, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // Lolin D32
// https://www.wemos.cc/en/latest/d32/d32.html
valid_system_gpios_ = {2, 18, 17, 16, 0, 4, 5, 12, 13, 14, 15, 21, 22, 25, 26, 27, 32, 33, 34, 35, 36, 39};
} else if (board_profile == "OLIMEX") {
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720, -1, 0, 0, 0}; // Olimex ESP32-EVB (uses U1TXD/U1RXD/BUTTON, no LED or Temperature sensor)
// https://github.com/OLIMEX/ESP32-EVB/blob/master/HARDWARE/REV-K1/ESP32-EVB_Rev_K1.pdf
// uart0 = 1, 3; CAN = 5, 35; relais = 32, 33; ir = 12(tx), 39(rx); SD-card = 2, 14, 15, button = 34
// relais and ir can be configured as analog sensor
valid_system_gpios_ = {4, 34, 36, 12, 13, 21, 22, 25, 26, 27, 32, 33, 39};
} else if (board_profile == "OLIMEXPOE") {
data = {0, 0, 36, 4, 34, PHY_type::PHY_TYPE_LAN8720, 12, 0, 3, 0}; // Olimex ESP32-POE
// https://github.com/OLIMEX/ESP32-POE/blob/master/HARDWARE/ESP32-PoE-hardware-revision-L1/ESP32-PoE_Rev_L1.pdf
// uart0 = 1, 3; SD-card = 2, 14, 15; button = 34;
valid_system_gpios_ = {4, 34, 36, 12, 13, 21, 22, 25, 26, 27, 32, 33, 39};
} else if (board_profile == "C3MINI") {
#if defined(BOARD_C3_MINI_V1)
data = {7, 1, 4, 5, 9, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // Lolin C3 Mini V1
#else
data = {7, 1, 4, 5, 9, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 1}; // Lolin C3 Mini with RGB Led
#endif
// https://www.wemos.cc/en/latest/c3/c3_mini.html
valid_system_gpios_ = {0, 1, 3, 4, 5, 6, 7, 9, 10, 20, 21};
} else if (board_profile == "S2MINI") {
data = {15, 7, 11, 12, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // Lolin S2 Mini
// https://www.wemos.cc/en/latest/s2/s2_mini.html
set_valid_system_gpios();
} else if (board_profile == "S3MINI") {
data = {17, 18, 8, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // Liligo S3
// https://lilygo.cc/products/t7-s3
set_valid_system_gpios();
} else if (board_profile == "S32S3") {
data = {2, 18, 5, 17, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // BBQKees Gateway S3
data = {2, 18, 5, 17, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // BBQKees Gateway S3
valid_system_gpios_ = {0, 2, 5, 17, 18};
} else {
return false; // unknown, return false
}
@@ -2458,15 +2497,15 @@ bool System::command_txpause(const char * value, const int8_t id) {
// format command - factory reset, removing all config files
bool System::command_format(const char * value, const int8_t id) {
#if !defined(EMSESP_STANDALONE) && !defined(EMSESP_DEBUG)
// don't really format the filesystem in debug or standalone mode
#if !defined(EMSESP_STANDALONE) && !defined(EMSESP_TEST)
// don't really format the filesystem in test or standalone mode
if (LittleFS.format()) {
LOG_INFO("Filesystem formatted successfully. All config files removed.");
} else {
LOG_ERROR("Format failed");
}
#else
LOG_INFO("Format command not available in standalone or debug mode");
LOG_ERROR("Format command not available in standalone or test mode");
#endif
// restart will be handled by the main loop
@@ -2540,11 +2579,7 @@ void System::ntp_connected(bool b) {
if (b != ntp_connected_) {
if (b) {
LOG_INFO("NTP connected");
#ifdef EMSESP_DEBUG // reset the install date on startup if in debug mode
set_partition_install_date(true);
#else
set_partition_install_date(false);
#endif
set_partition_install_date();
} else {
LOG_WARNING("NTP disconnected"); // if turned off report it
}
@@ -2567,21 +2602,6 @@ bool System::ntp_connected() {
// see if its a BBQKees Gateway by checking the nvs values
String System::getBBQKeesGatewayDetails(uint8_t detail) {
#ifndef EMSESP_STANDALONE
/*
if (!EMSESP::nvs_.isKey("mfg")) {
return "";
}
// mfg can be either "BBQKees" or "BBQKees Electronics"
auto mfg = EMSESP::nvs_.getString("mfg");
if (mfg) {
if (!mfg.startsWith("BBQKees")) {
return "";
}
}
return "BBQKees Gateway Model " + EMSESP::nvs_.getString("model") + " v" + EMSESP::nvs_.getString("hwrevision") + "/" + EMSESP::nvs_.getString("batch");
*/
union {
struct {
uint32_t no : 4;
@@ -2594,14 +2614,17 @@ String System::getBBQKeesGatewayDetails(uint8_t detail) {
};
uint32_t reg;
} gw;
for (uint8_t reg = 0; reg < 8; reg++) {
gw.reg = esp_efuse_read_reg(EFUSE_BLK3, reg);
if (reg == 7 || esp_efuse_read_reg(EFUSE_BLK3, reg + 1) == 0)
break;
}
const char * mfg[] = {"unknown", "BBQKees Electronics", "", "", "", "", "", ""};
const char * model[] = {"unknown", "S3", "E32V2", "E32V2.2", "S32", "E32", "", "", ""};
const char * board[] = {"CUSTOM", "S32S3", "E32V2", "E32V2_2", "S32", "E32", "", "", ""};
switch (detail) {
case FUSE_VALUE::MFG:
return gw.mfg < 2 ? String(mfg[gw.mfg]) : "unknown";
@@ -2619,9 +2642,11 @@ String System::getBBQKeesGatewayDetails(uint8_t detail) {
default:
break;
}
if (!gw.reg || gw.mfg > 1 || gw.model > 5) {
return "";
}
return String(mfg[gw.mfg]) + " " + String(model[gw.model]) + " rev." + String(gw.rev_major) + "." + String(gw.rev_minor) + "/" + String(2000 + gw.year)
+ (gw.month < 10 ? "0" : "") + String(gw.month) + String(gw.no);
#else
@@ -2698,6 +2723,9 @@ bool System::uploadFirmwareURL(const char * url) {
// we're about to start the upload, set the status so the Web System Monitor spots it
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING);
// set a callback so we can monitor progress in the WebUI
Update.onProgress([](size_t progress, size_t total) { EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING + (progress * 100 / total)); });
// get tcp stream and send it to Updater
WiFiClient * stream = http.getStreamPtr();
if (Update.writeStream(*stream) != firmware_size) {
@@ -2786,10 +2814,15 @@ bool System::command_read(const char * value, const int8_t id) {
}
// set the system status code - SYSTEM_STATUS in system.h
// this is also used in the SystemMonitor.tsx WebUI to show the progress of the firmware upload, start at 100
void System::systemStatus(uint8_t status_code) {
if (systemStatus_ != status_code) {
systemStatus_ = status_code;
LOG_DEBUG("Setting System status code %d", status_code);
#ifdef EMSESP_DEBUG
if (status_code < SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING) {
LOG_DEBUG("Setting System status code %d", status_code);
}
#endif
}
}
@@ -2797,8 +2830,11 @@ uint8_t System::systemStatus() {
return systemStatus_;
}
// takes a string range like "6-11, 1, 23, 24-48" which has optional ranges and single values and converts to a vector of ints
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::string_range_to_vector(const std::string & range) {
// takes two arguments:
// the first is the full range of pins to consider
// the second is a string range of GPIOs to exclude, like "6-11, 1, 23, 24-48"
// returns a vector array of GPIOs that are valid for use
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::string_range_to_vector(const std::string & range, const std::string & exclude) {
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> gpios;
std::string::size_type pos = 0;
std::string::size_type prev = 0;
@@ -2830,41 +2866,138 @@ std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::string_range_to_vector(con
// handle the last part
process_part(range.substr(prev));
// if exclude list is provided, parse it and remove excluded GPIOs
if (!exclude.empty()) {
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> exclude_gpios;
pos = 0;
prev = 0;
auto process_exclude = [&exclude_gpios](std::string part) {
// trim whitespace
part.erase(0, part.find_first_not_of(" \t"));
part.erase(part.find_last_not_of(" \t") + 1);
// check if it's a range (contains '-')
std::string::size_type dash_pos = part.find('-');
if (dash_pos != std::string::npos) {
// it's a range like "6-11"
int start = std::stoi(part.substr(0, dash_pos));
int end = std::stoi(part.substr(dash_pos + 1));
for (int i = start; i <= end; i++) {
exclude_gpios.push_back(static_cast<uint8_t>(i));
}
} else {
exclude_gpios.push_back(static_cast<uint8_t>(std::stoi(part)));
}
};
while ((pos = exclude.find(',', prev)) != std::string::npos) {
process_exclude(exclude.substr(prev, pos - prev));
prev = pos + 1;
}
// handle the last part
process_exclude(exclude.substr(prev));
// remove excluded GPIOs from the main list
gpios.erase(std::remove_if(gpios.begin(),
gpios.end(),
[&exclude_gpios](uint8_t gpio) { return std::find(exclude_gpios.begin(), exclude_gpios.end(), gpio) != exclude_gpios.end(); }),
gpios.end());
}
return gpios;
}
// initialize a list of valid GPIOs based on the ESP32 board
// note: we always allow 0, which is used to indicate Dallas or LED is disabled
// string_to_vector() take two strings, the first is the range of GPIOs to use, the second is a list of GPIOs to exclude
// notes:
// we always allow 0 (which is usually a strapping pin), because it's used to indicate whether EMS-ESP Dallas or the LED is disabled
// we allow UART0, 1 and 2 as they are configurable
// strapping pins are disabled as they can affect boot behaviour
// we accept GPIOs that are fixed on BBQKees boards
//
void System::set_valid_system_gpios() {
valid_system_gpios_.clear(); // reset system list
used_gpios_.clear(); // reset used list
// get free gpios based on board/platform type
#if CONFIG_IDF_TARGET_ESP32C3
// https://www.wemos.cc/en/latest/c3/c3_mini.html
valid_system_gpios_ = string_range_to_vector("0-10"); // UART0=20,21
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-reference/peripherals/gpio.html
// excluded:
// GPIO2, GPIO8 - GPIO9 = strapping pins
// GPIO12 - GPIO17 = used for SPI flash and PSRAM
// GPIO18 - GPIO19 = USB-JTAG
//
// notes on what is allowed:
// GPIO09 = button on BOARD_C3_MINI_V1
// GPIO20 - GPIO21 = UART0, , no chip connected because native USB
valid_system_gpios_ = string_range_to_vector("0-21", "2, 8, 12-17, 18-19");
#elif CONFIG_IDF_TARGET_ESP32S2
// 43 and 44 are UART0 pins
// 38 and 39 are strapping pins, input only
valid_system_gpios_ = string_range_to_vector("0-14, 19, 20, 21, 33-37, 45, 46");
// 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
// GPIO39 - GPIO42 = USB-JTAG
// GPIO22 - GPIO25 = don't exist
// GPIO19 - GPIO20 = USB
//
// notes on what is allowed:
// GPIO43, GPIO44 = UART0, no chip connected because native USB
valid_system_gpios_ = string_range_to_vector("0-46", "19, 20, 26-32, 45-46, 39-42, 22-25");
#elif CONFIG_IDF_TARGET_ESP32S3
// 43 and 44 are UART0 pins
// 33-37 for Octal SPI (SPIIO4 through SPIIO7 and SPIDQS)
// 38 and 39 are input only
// 45 and 36 are strapping pins, input only
valid_system_gpios_ = string_range_to_vector("0-14, 17, 18, 21, 33-39, 45, 46");
// 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
// GPIO33 - GPIO37 = Octal flash/PSRAM
// GPIO19 - GPIO20 = USB-JTAG
// GPIO22 - GPIO25 = don't exist
//
// notes on what is allowed:
// GPIO11 - GPIO19 = ADC analog input only pins
// GPIO47 - GPIO48 = valid on a Wemos S3
// GPIO8 = used by Liligo S3 board profile for Rx
valid_system_gpios_ = string_range_to_vector("0-48", "3, 45-46, 26-32, 33-37, 19-20, 22-25");
#elif CONFIG_IDF_TARGET_ESP32
// 1 and 3 are UART0 pins, but used for some eth-boards (BBQKees-E32, OlimexPOE)
// 32-39 is ADC1, input only
// 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
// GPIO01, GPIO03 = UART0, normal connected to UART/USB chip
//
// notes on known boards:
// boards have valid gpios depending on pinout and internal used gpios, see: `load_board_profile()`
//
// notes on BBQKees boards:
// *** We block all free GPIOS in load_board_profile() ***
// GPIO2, GPIO4, GPIO5, GPIO14 = used on BBQKees boards for either LED, Dallas or Rx
// GPIO12, GPIO13, GPIO35 = BBQKees E32V2_2 internal system pins
// GPIO33 = BBQKees E32V_2 unused internal NTC system sensor
// GPIO36 = used on BBQKees boards for supply_voltage (E32V2.2)
// GPIO39 = used on BBQKees boards for core_voltage (E32V2.2)
//
// notes on what is allowed with special functions:
// GPIO01, GPIO03 = UART0, not always connected to uart/usb chip
// GPIO12 - GPIO15 = JTAG, normally not used
// GPIO00, GPIO05, GPIO12, GPIO15 = strapping pins, can be used with care
// GPIO34, GPIO35, GPIO37 = input only
// GPIO23 and GPIO18 are used by Ethernet, excluded later by eth config
// GPIO00, GPIO02, GPIO04, GPIO12 - GPIO15, GPIO25 - GPIO27 = ADC2 (10 ch), used by WiFI-driver
// GPIO32 - GPIO39 = ADC1 (8 ch), can always be used
if (ESP.getPsramSize() > 0) {
// if psram is enabled remove pins 16 and 17 from the list
valid_system_gpios_ = string_range_to_vector("0-5, 12-15, 18-19, 23, 25-27, 32-39");
// remove SPI0/1 PSRAM pins GPIO16 (CS) and GPIO17 (CLK) from the list
valid_system_gpios_ = string_range_to_vector("0-39", "6-11, 16, 17, 20, 24, 28-31");
} else {
valid_system_gpios_ = string_range_to_vector("0-5, 12-19, 23, 25-27, 32-39");
valid_system_gpios_ = string_range_to_vector("0-39", "6-11, 20, 24, 28-31");
}
#elif defined(EMSESP_STANDALONE)
valid_system_gpios_ = string_range_to_vector("0-5, 12-19, 23, 25-27, 32-39");
valid_system_gpios_ = string_range_to_vector("0-39");
#endif
valid_system_gpios_.shrink_to_fit();
}
// check if a pin is valid ESP32 pin and if not already used, add to the used gpio list
@@ -2873,8 +3006,9 @@ bool System::add_gpio(uint8_t pin, const char * source_name) {
// check if this is a valid user GPIO
if (std::find(valid_system_gpios_.begin(), valid_system_gpios_.end(), pin) != valid_system_gpios_.end()) {
// It's valid now check if it's already in the used list
if (std::find(used_gpios_.begin(), used_gpios_.end(), pin) != used_gpios_.end()) {
LOG_WARNING("GPIO %d for %s is already in use", pin, source_name);
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_WARNING("GPIO %d for %s is already in use by %s", pin, source_name, it->source.c_str());
return false; // Pin is already used
}
} else {
@@ -2887,24 +3021,24 @@ bool System::add_gpio(uint8_t pin, const char * source_name) {
remove_gpio(pin);
LOG_DEBUG("Adding GPIO %d for %s to used gpio list", pin, source_name);
used_gpios_.push_back(pin); // add to used list
used_gpios_.push_back({pin, source_name}); // add to used list
return true;
}
// remove a gpio from both valid and used lists
void System::remove_gpio(uint8_t pin, bool also_system) {
auto it = std::find(used_gpios_.begin(), used_gpios_.end(), pin);
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("GPIO %d removed from used gpio list", pin);
used_gpios_.erase(it);
}
if (also_system) {
it = std::find(valid_system_gpios_.begin(), valid_system_gpios_.end(), pin);
if (it != valid_system_gpios_.end()) {
auto it_sys = std::find(valid_system_gpios_.begin(), valid_system_gpios_.end(), pin);
if (it_sys != valid_system_gpios_.end()) {
LOG_DEBUG("GPIO %d removed from valid gpio list", pin);
valid_system_gpios_.erase(it);
valid_system_gpios_.erase(it_sys);
}
}
}
@@ -2913,7 +3047,7 @@ void System::remove_gpio(uint8_t pin, bool also_system) {
std::vector<uint8_t> System::available_gpios() {
std::vector<uint8_t> gpios;
for (const auto & gpio : valid_system_gpios_) {
if (std::find(used_gpios_.begin(), used_gpios_.end(), gpio) == used_gpios_.end()) {
if (std::find_if(used_gpios_.begin(), used_gpios_.end(), [gpio](const GpioUsage & usage) { return usage.pin == gpio; }) == used_gpios_.end()) {
gpios.push_back(gpio); // didn't find it in used_gpios_, so it's available
}
}
@@ -2921,37 +3055,26 @@ std::vector<uint8_t> System::available_gpios() {
}
// make a snapshot of the current GPIOs
void System::make_snapshot_gpios() {
snapshot_used_gpios_.clear();
for (const auto & gpio : used_gpios_) {
snapshot_used_gpios_.push_back(gpio);
void System::make_snapshot_gpios(std::vector<int8_t> & u_gpios, std::vector<int8_t> & s_gpios) {
for (const auto & usage : used_gpios_) {
u_gpios.push_back(usage.pin);
}
snapshot_valid_system_gpios_.clear();
for (const auto & gpio : valid_system_gpios_) {
snapshot_valid_system_gpios_.push_back(gpio);
s_gpios.push_back(gpio);
}
}
// restore the GPIOs from the snapshot
void System::restore_snapshot_gpios() {
void System::restore_snapshot_gpios(std::vector<int8_t> & u_gpios, std::vector<int8_t> & s_gpios) {
used_gpios_.clear();
for (const auto & gpio : snapshot_used_gpios_) {
used_gpios_.push_back(gpio);
for (const auto & gpio : u_gpios) {
used_gpios_.push_back({static_cast<uint8_t>(gpio), "restored"});
}
valid_system_gpios_.clear();
for (const auto & gpio : snapshot_valid_system_gpios_) {
for (const auto & gpio : s_gpios) {
valid_system_gpios_.push_back(gpio);
}
// clear the snapshot
clear_snapshot_gpios();
}
void System::clear_snapshot_gpios() {
snapshot_used_gpios_.clear();
snapshot_valid_system_gpios_.clear();
}
} // namespace emsesp
+11 -11
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -159,13 +159,11 @@ class System {
static void extractSettings(const char * filename, const char * section, JsonObject output);
static bool saveSettings(const char * filename, const char * section, JsonObject input);
// GPIOs
static bool add_gpio(uint8_t pin, const char * source_name);
static std::vector<uint8_t> available_gpios();
static bool load_board_profile(std::vector<int8_t> & data, const std::string & board_profile);
static void make_snapshot_gpios();
static void restore_snapshot_gpios();
static void clear_snapshot_gpios();
static void make_snapshot_gpios(std::vector<int8_t> & u_gpios, std::vector<int8_t> & s_gpios);
static void restore_snapshot_gpios(std::vector<int8_t> & u_gpios, std::vector<int8_t> & s_gpios);
static bool readCommand(const char * data);
@@ -392,7 +390,7 @@ class System {
uint8_t systemStatus_; // uses SYSTEM_STATUS enum
void set_partition_install_date(bool override = false);
void set_partition_install_date();
// button
static PButton myPButton_; // PButton instance
@@ -435,13 +433,15 @@ class System {
void led_monitor();
void system_check();
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> string_range_to_vector(const std::string & range);
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> string_range_to_vector(const std::string & range, const std::string & exclude = "");
// GPIOs
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> valid_system_gpios_; // list of valid GPIOs for the ESP32 board that can be used
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> used_gpios_; // list of GPIOs used by the application
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> snapshot_used_gpios_; // snapshot of the used GPIOs
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> snapshot_valid_system_gpios_; // snapshot of the valid GPIOs
struct GpioUsage {
uint8_t pin;
std::string source;
};
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> valid_system_gpios_; // list of valid GPIOs for the ESP32 board that can be used
static std::vector<GpioUsage, AllocatorPSRAM<GpioUsage>> used_gpios_; // list of GPIOs used by the application
int8_t wifi_quality(int8_t dBm);
+5 -5
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -221,14 +221,14 @@ void RxService::add(uint8_t * data, uint8_t length) {
LOG_TRACE("Rx: %s", Helpers::data_to_hex(data, length).c_str());
}
LOG_DEBUG("New Rx telegram, message length %d", message_length);
// if we don't have a type_id exit,
// do not exit on empty message, it is checked for toggle fetch
// if we don't have a type_id exit
// do not exit on empty message, it is checked later for toggle fetch
if (type_id == 0) {
return;
}
LOG_DEBUG("New Rx telegram, message length %d", message_length);
// create the telegram
auto telegram = std::make_shared<Telegram>(operation, src, dest, type_id, offset, message_data, message_length);
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+2 -3
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
@@ -33,8 +33,7 @@ uuid::log::Logger TemperatureSensor::logger_{F_(temperaturesensor), uuid::log::F
// start the 1-wire
void TemperatureSensor::start(const bool factory_settings) {
// set_internal_ = factory_settings && EMSESP::nvs_.getString("boot").equals("E32V2_2") && EMSESP::nvs_.getString("hwrevision").equals("3.0");
set_internal_ = factory_settings && analogReadMilliVolts(39) > 700; // core voltage > 2.6V
set_internal_ = factory_settings && EMSESP::system_.board_profile() == "E32V2_2";
reload();
if (!dallas_gpio_) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2025 emsesp.org - proddy, MichaelDvP
* 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

Some files were not shown because too many files have changed in this diff Show More