Merge branch 'dev_' into dev_no_master_thermostat

This commit is contained in:
MichaelDvP
2022-04-14 07:27:07 +02:00
55 changed files with 3607 additions and 2542 deletions

View File

@@ -5,6 +5,76 @@ 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.3.1] January 20 2022
- lastcode broke MQTT JSON structure [#228](https://github.com/emsesp/EMS-ESP32/issues/228)
- overlapping while reading sequence of EMS1.0 telegrams
- redundant telegram readings (because of offset overflow)
- added missing RC30/Moduline400 [#243](https://github.com/emsesp/EMS-ESP32/issues/243)
- check received status before toggling fetch on empty telegram [#268][#282]
# [3.3.0] November 28 2021
## Added
- Add system commands for syslog level and watch [#98](https://github.com/emsesp/EMS-ESP32/issues/98)
- Added pool data to telegrams 0x494 & 0x495 [#102](https://github.com/emsesp/EMS-ESP32/issues/102)
- Add RC300 second summermode telegram [#108](https://github.com/emsesp/EMS-ESP32/issues/108)
- Add support for the RC25 thermostat [#106](https://github.com/emsesp/EMS-ESP32/issues/106)
- Add new command 'entities' for a device, e.g. http://ems-esp/api/boiler/entities to show the shortname, description and HA Entity name (if HA enabled) [#116](https://github.com/emsesp/EMS-ESP32/issues/116)
- Support for Junkers program and remote (fb10/fb110) temperature
- Home Assistant `state_class` attribute for Wh, kWh, W and KW [#129](https://github.com/emsesp/EMS-ESP32/issues/129)
- Add current room influence for RC300 [#136](https://github.com/emsesp/EMS-ESP32/issues/136)
- Added Home Assistant device_class to sensor entities
- Added another Buderus RC10 thermostat with Product ID 65 [#160](https://github.com/emsesp/EMS-ESP32/issues/160)
- Added support for mDNS [#161](https://github.com/emsesp/EMS-ESP32/issues/161)
- Added last system ESP32 reset code to log (and `system info` output)
- Firmware Checker in WebUI [#168](https://github.com/emsesp/EMS-ESP32/issues/168)
- Added new MQTT setting for enabling 'response' topic
- Support for non-standard Thermostats like Tado [#174](https://github.com/emsesp/EMS-ESP32/issues/174)
- Include MQTT connection status in 'api/system/info'
- Include Network status in 'api/system/info' and also the MQTT topic `info` [#202](https://github.com/emsesp/EMS-ESP32/issues/202)
- Added Ethernet PHY module as an option in the Board Profile [#210](https://github.com/emsesp/EMS-ESP32/issues/210)
## Fixed
- MQTT reconnecting after WiFi reconnect [#99](https://github.com/emsesp/EMS-ESP32/issues/99)
- Manually Controlling Solar Circuit [#107](https://github.com/emsesp/EMS-ESP32/issues/107)
- Fix thermostat commands not defaulting to the master thermostat [#110](https://github.com/emsesp/EMS-ESP32/issues/110)
- Enlarge parse-buffer for long names like `cylinderpumpmodulation`
- MQTT not subscribing to all device entities [#166](https://github.com/emsesp/EMS-ESP32/issues/166)
- Help fix issues with WebUI unable to fully load UI over Ethernet [#177](https://github.com/emsesp/EMS-ESP32/issues/177)
- Shower alert never reset after limit reached when enabled [(PR #185)]
- Remove HA entity entries when a device value goes dormant [#196](https://github.com/emsesp/EMS-ESP32/issues/196)
- deciphering last error code dates on 0xC2 telegram [#204](https://github.com/emsesp/EMS-ESP32/issues/204)
## Changed
- Syslog BOM only for utf-8 messages [#91](https://github.com/emsesp/EMS-ESP32/issues/91)
- Check for KM200 by device-id 0x48, remove tx-delay [#90](https://github.com/emsesp/EMS-ESP32/issues/90)
- rename `fastheatupfactor` to `fastheatup` and add percent [#122](https://github.com/emsesp/EMS-ESP32/issues/122)
- "unit" renamed to "uom" in API call to recall a Device Value
- initial backend React changes to replace the class components (HOCs) with React Hooks
- Use program-names instead of numbers
- Boiler's maintenancemessage always published in MQTT (to prevent HA missing entity)
- Unit of Measure 'times' added to MQTT Fails, Rx fails, Rx received, Tx fails, Tx reads & Tx writes
- Improved API. Restful HTTP API works in the same way as MQTT calls
- Removed settings for MQTT subscribe format [#173](https://github.com/emsesp/EMS-ESP32/issues/173)
- Improve Nefit Moduline 200 functionality [#183](https://github.com/emsesp/EMS-ESP32/issues/183)
- `status` in the MQTT heartbeat renamed to `bus_status`
- Layout changes in the WebUI, showing stripped table rows in Dashboard
- Alternative font for log window [#219](https://github.com/emsesp/EMS-ESP32/issues/219)
## **BREAKING CHANGES**
- API: "unit" renamed to "uom" in API call to recall a Device Value
- HA: `sensor.boiler_boiler_temperature` renamed to `sensor.actual_boiler_temperature`
- HA: `binary_sensor.boiler_ww_disinfecting` renamed to `binary_sensor.boiler_ww_disinfection`
- HA: # removed from counts in MQTT Fails, Rx fails, Rx received, Tx fails, Tx reads & Tx writes
- `txread` renamed to `txreads` and `txwrite` renamed to `txwrites` in MQTT heartbeat payload
- 'dallas sensors' in api/system/info moved to the "System" section. Renamed "uptime (seconds)" and "reset reason"
- `status` in the MQTT heartbeat renamed to `bus_status`
# [3.2.1] August 8 2021
## Added

View File

@@ -26,6 +26,15 @@
- remove MQTT retained configs if discovery is disabled
- timeout 10 min for MQTT-QoS wait
- Moduline 300 auto-temperatures T1-T4, RC300 romminfluencefactor
- RC35 parameters [#392](https://github.com/emsesp/EMS-ESP32/issues/392), [#398](https://github.com/emsesp/EMS-ESP32/issues/398)
- sync time with thermostat [#386](https://github.com/emsesp/EMS-ESP32/issues/386), [#408](https://github.com/emsesp/EMS-ESP32/issues/408)
- set mode has immediate effect [#395](https://github.com/emsesp/EMS-ESP32/issues/395)
- min/max in web value setting
- Extend customization to select if an entity is to be shown in the WebUI or forced as read-only [#317](https://github.com/emsesp/EMS-ESP32/issues/317)
- Added Moduline 400 installation parameters [PR #449 by @kwertie01](https://github.com/emsesp/EMS-ESP32/pull/449)
- Read time from IVT-controller [#439](https://github.com/emsesp/EMS-ESP32/issues/439)
- Hybrid Heatpump product-id 168 [#459](https://github.com/emsesp/EMS-ESP32/issues/459), thermostat settings
- Junkers ISM2 and IPM in warm water mode [#437](https://github.com/emsesp/EMS-ESP32/issues/437)
### Fixed
@@ -43,6 +52,7 @@
- Non-nested MQTT would corrupt the json [#354](https://github.com/emsesp/EMS-ESP32/issues/354)
- Burner selected max power can have a value higher than 100% [#314](https://github.com/emsesp/EMS-ESP32/issues/314)
- some missing fahrenheit calculations
- limited number of exclusions [#339](https://github.com/emsesp/EMS-ESP32/issues/339)
### Changed
@@ -59,6 +69,9 @@
- removed system/pin command, new commands in analogsensors
- system/info device-info split to name/version/brand
- remove master-thermostat
- exclude list uses short-names, possible flags for web/api/mqtt excludes, readonly and favorite (selection not yet implemented)
- thermostat clock formate date-time: dd.mm.yyyy hh:mm
- RC300 summermode as other thermostats `winter/summer` instead of `off/on`
## **BREAKING CHANGES:**

View File

@@ -15,6 +15,7 @@ This project is the specifically for the ESP32. Compared with the previous ESP82
[![version](https://img.shields.io/github/release/emsesp/EMS-ESP32.svg?label=Latest%20Release)](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md)
[![release-date](https://img.shields.io/github/release-date/emsesp/EMS-ESP32.svg?label=Released)](https://github.com/emsesp/EMS-ESP32/commits/main)
[![license](https://img.shields.io/github/license/emsesp/EMS-ESP32.svg)](LICENSE)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=emsesp_EMS-ESP32&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=emsesp_EMS-ESP32)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9441142f49424ef891e8f5251866ee6b)](https://www.codacy.com/gh/emsesp/EMS-ESP32/dashboard?utm_source=github.com&utm_medium=referral&utm_content=emsesp/EMS-ESP32&utm_campaign=Badge_Grade)
[![downloads](https://img.shields.io/github/downloads/emsesp/EMS-ESP32/total.svg)](https://github.com/emsesp/EMS-ESP32/releases)
[![chat](https://img.shields.io/discord/816637840644505620.svg?style=flat-square&color=blueviolet)](https://discord.gg/3J3GgnzpyT)

View File

@@ -38,7 +38,7 @@ build_flags =
-D FACTORY_MQTT_PASSWORD=\"\"
-D FACTORY_MQTT_CLIENT_ID=\"ems-esp\"
-D FACTORY_MQTT_KEEP_ALIVE=60
-D FACTORY_MQTT_CLEAN_SESSION=true
-D FACTORY_MQTT_CLEAN_SESSION=false
-D FACTORY_MQTT_MAX_TOPIC_LENGTH=128
; JWT Secret

File diff suppressed because it is too large Load Diff

View File

@@ -4,32 +4,32 @@
"private": true,
"proxy": "http://localhost:3080",
"dependencies": {
"@emotion/react": "^11.8.1",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@msgpack/msgpack": "^2.7.2",
"@mui/icons-material": "^5.5.0",
"@mui/material": "^5.5.0",
"@types/lodash": "^4.14.179",
"@types/node": "^17.0.21",
"@types/react": "^17.0.40",
"@types/react-dom": "^17.0.13",
"@mui/icons-material": "^5.6.0",
"@mui/material": "^5.6.0",
"@types/lodash": "^4.14.181",
"@types/node": "^17.0.23",
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14",
"@types/react-router-dom": "^5.3.3",
"async-validator": "^4.0.7",
"axios": "^0.26.1",
"http-proxy-middleware": "^2.0.3",
"http-proxy-middleware": "^2.0.4",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"notistack": "^2.0.3",
"parse-ms": "^3.0.0",
"react": "^17.0.2",
"react-app-rewired": "^2.2.1",
"react-dom": "^17.0.2",
"react-app-rewired": "^2.2.1",
"react-dropzone": "^12.0.4",
"react-icons": "^4.3.1",
"react-router-dom": "^6.2.2",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.0",
"sockette": "^2.0.6",
"typescript": "^4.6.2"
"typescript": "^4.6.3"
},
"scripts": {
"start": "react-app-rewired start",

View File

@@ -183,6 +183,7 @@ const MqttSettingsForm: FC = () => {
control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />}
label="Publish command output to a 'response' topic"
/>
{!data.ha_enabled && (
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item>
<BlockFormControlLabel
@@ -201,6 +202,8 @@ const MqttSettingsForm: FC = () => {
</Grid>
)}
</Grid>
)}
{!data.publish_single && (
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
<Grid item>
<BlockFormControlLabel
@@ -222,6 +225,7 @@ const MqttSettingsForm: FC = () => {
</Grid>
)}
</Grid>
)}
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
Publish Intervals (in seconds, 0=automatic)
</Typography>

View File

@@ -32,11 +32,14 @@ import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } fro
import { AuthenticatedContext } from '../../contexts/authentication';
export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE;
export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED;
export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => {
switch (status) {
case NTPSyncStatus.NTP_INACTIVE:
case NTPSyncStatus.NTP_DISABLED:
return theme.palette.info.main;
case NTPSyncStatus.NTP_INACTIVE:
return theme.palette.error.main;
case NTPSyncStatus.NTP_ACTIVE:
return theme.palette.success.main;
default:
@@ -46,6 +49,8 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => {
export const ntpStatus = ({ status }: NTPStatus) => {
switch (status) {
case NTPSyncStatus.NTP_DISABLED:
return 'Disabled';
case NTPSyncStatus.NTP_INACTIVE:
return 'Inactive';
case NTPSyncStatus.NTP_ACTIVE:
@@ -143,7 +148,7 @@ const NTPStatusForm: FC = () => {
<ListItemText primary="Status" secondary={ntpStatus(data)} />
</ListItem>
<Divider variant="inset" component="li" />
{isNtpActive(data) && (
{isNtpEnabled(data) && (
<>
<ListItem>
<ListItemAvatar>

View File

@@ -46,7 +46,7 @@ const UserForm: FC<UserFormProps> = ({ creating, validator, user, setUser, onDon
};
return (
<Dialog onClose={onCancelEditing} aria-labelledby="user-form-dialog-title" open={!!user} fullWidth maxWidth="sm">
<Dialog onClose={onCancelEditing} open={!!user} fullWidth maxWidth="sm">
{user && (
<>
<DialogTitle id="user-form-dialog-title">{creating ? 'Add' : 'Modify'} User</DialogTitle>

View File

@@ -13,13 +13,14 @@ const FirmwareFileUpload: FC<UploadFirmwareProps> = ({ uploadFirmware }) => {
return (
<>
{!uploading && (
<MessageBox
message="Upload a new firmware (.bin) file below to replace the existing firmware"
level="warning"
my={2}
/>
)}
<SingleUpload
// accept="application/octet-stream"
accept=".bin"
onDrop={uploadFile}
onCancel={cancelUpload}

View File

@@ -37,6 +37,10 @@ import CancelIcon from '@mui/icons-material/Cancel';
import SendIcon from '@mui/icons-material/TrendingFlat';
import SaveIcon from '@mui/icons-material/Save';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import DeviceIcon from './DeviceIcon';
@@ -62,7 +66,8 @@ import {
AnalogType,
AnalogTypeNames,
Sensor,
Analog
Analog,
DeviceEntityMask
} from './types';
const StyledTableCell = styled(TableCell)(({ theme }) => ({
@@ -153,6 +158,8 @@ const DashboardData: FC = () => {
}
};
const isCmdOnly = (dv: DeviceValue) => dv.v === undefined && dv.c;
function formatValue(value: any, uom: number) {
if (value === undefined) {
return '';
@@ -213,12 +220,12 @@ const DashboardData: FC = () => {
if (deviceValue) {
return (
<Dialog open={deviceValue !== undefined} onClose={() => setDeviceValue(undefined)}>
<DialogTitle>Change Value</DialogTitle>
<DialogTitle>{isCmdOnly(deviceValue) ? 'Run Command' : 'Change Value'}</DialogTitle>
<DialogContent dividers>
{deviceValue.l && (
<ValidatedTextField
name="v"
label={deviceValue.n}
label={deviceValue.n.slice(2)}
value={deviceValue.v}
autoFocus
sx={{ width: '30ch' }}
@@ -233,13 +240,13 @@ const DashboardData: FC = () => {
{!deviceValue.l && (
<ValidatedTextField
name="v"
label={deviceValue.n}
label={deviceValue.n.slice(2)}
value={deviceValue.u ? numberValue(deviceValue.v) : deviceValue.v}
autoFocus
sx={{ width: '30ch' }}
type={deviceValue.u ? 'number' : 'text'}
onChange={updateValue(setDeviceValue)}
inputProps={{ step: deviceValue.s }}
inputProps={deviceValue.u ? { min: deviceValue.m, max: deviceValue.x, step: deviceValue.s } : {}}
InputProps={{
startAdornment: <InputAdornment position="start">{DeviceValueUOM_s[deviceValue.u]}</InputAdornment>
}}
@@ -485,26 +492,24 @@ const DashboardData: FC = () => {
return;
}
const hasMask = (entityName: string, mask: number) => (parseInt(entityName.slice(0, 2), 16) & mask) === mask;
const sendCommand = (dv: DeviceValue) => {
if (dv.c && me.admin) {
if (dv.c && me.admin && !hasMask(dv.n, DeviceEntityMask.DV_READONLY)) {
setDeviceValue(dv);
}
};
const renderNameCell = (dv: DeviceValue) => {
if (dv.v === undefined && dv.c) {
return (
<StyledTableCell component="th" scope="row" sx={{ color: 'yellow' }}>
command:&nbsp;{dv.n}
</StyledTableCell>
const renderNameCell = (dv: DeviceValue) => (
<>
{dv.n.slice(2)}&nbsp;
{hasMask(dv.n, DeviceEntityMask.DV_FAVORITE) && <FavoriteBorderIcon color="success" sx={{ fontSize: 12 }} />}
{hasMask(dv.n, DeviceEntityMask.DV_READONLY) && <EditOffOutlinedIcon color="primary" sx={{ fontSize: 12 }} />}
{hasMask(dv.n, DeviceEntityMask.DV_API_MQTT_EXCLUDE) && (
<CommentsDisabledOutlinedIcon color="primary" sx={{ fontSize: 12 }} />
)}
</>
);
}
return (
<StyledTableCell component="th" scope="row">
{dv.n}
</StyledTableCell>
);
};
return (
<>
@@ -515,7 +520,7 @@ const DashboardData: FC = () => {
<TableHead>
<TableRow>
<StyledTableCell padding="checkbox" style={{ width: 18 }}></StyledTableCell>
<StyledTableCell align="left">ENTITY NAME/COMMAND</StyledTableCell>
<StyledTableCell align="left">ENTITY NAME</StyledTableCell>
<StyledTableCell align="right">VALUE</StyledTableCell>
</TableRow>
</TableHead>
@@ -523,14 +528,18 @@ const DashboardData: FC = () => {
{deviceData.data.map((dv, i) => (
<StyledTableRow key={i} onClick={() => sendCommand(dv)}>
<StyledTableCell padding="checkbox">
{dv.c && me.admin && (
<IconButton size="small" aria-label="Edit">
{dv.c && me.admin && !hasMask(dv.n, DeviceEntityMask.DV_READONLY) && (
<IconButton size="small">
<EditIcon color="primary" fontSize="small" />
</IconButton>
)}
</StyledTableCell>
<StyledTableCell component="th" scope="row">
{renderNameCell(dv)}
<StyledTableCell align="right">{formatValue(dv.v, dv.u)}</StyledTableCell>
</StyledTableCell>
<StyledTableCell align="right">
{isCmdOnly(dv) ? <PlayArrowIcon color="primary" sx={{ fontSize: 14 }} /> : formatValue(dv.v, dv.u)}
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
@@ -569,7 +578,7 @@ const DashboardData: FC = () => {
<StyledTableRow key={sensor_data.n} onClick={() => updateSensor(sensor_data)}>
<StyledTableCell padding="checkbox">
{me.admin && (
<IconButton edge="start" size="small" aria-label="Edit">
<IconButton edge="start" size="small">
<EditIcon color="primary" fontSize="small" />
</IconButton>
)}
@@ -605,7 +614,7 @@ const DashboardData: FC = () => {
<StyledTableRow key={analog_data.i} onClick={() => updateAnalog(analog_data)}>
<StyledTableCell padding="checkbox">
{me.admin && (
<IconButton edge="start" size="small" aria-label="Edit">
<IconButton edge="start" size="small">
<EditIcon color="primary" fontSize="small" />
</IconButton>
)}

View File

@@ -12,7 +12,9 @@ import {
Dialog,
DialogActions,
DialogContent,
DialogTitle
DialogTitle,
ToggleButton,
ToggleButtonGroup
} from '@mui/material';
import TableCell, { tableCellClasses } from '@mui/material/TableCell';
@@ -22,8 +24,11 @@ import { styled } from '@mui/material/styles';
import { useSnackbar } from 'notistack';
import SaveIcon from '@mui/icons-material/Save';
import CloseIcon from '@mui/icons-material/Close';
import CancelIcon from '@mui/icons-material/Cancel';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
import FavoriteBorderOutlinedIcon from '@mui/icons-material/FavoriteBorderOutlined';
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
import { ButtonRow, FormLoader, ValidatedTextField, SectionContent } from '../components';
@@ -36,12 +41,7 @@ import { DeviceShort, Devices, DeviceEntity } from './types';
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: '#607d8b',
color: theme.palette.common.white,
fontSize: 11
},
[`&.${tableCellClasses.body}`]: {
fontSize: 11
backgroundColor: '#607d8b'
}
}));
@@ -54,6 +54,9 @@ const SettingsCustomization: FC = () => {
const [selectedDevice, setSelectedDevice] = useState<number>(0);
const [confirmReset, setConfirmReset] = useState<boolean>(false);
// eslint-disable-next-line
const [masks, setMasks] = useState(() => ['']);
const fetchDevices = useCallback(async () => {
try {
setDevices((await EMSESP.readDevices()).data);
@@ -62,9 +65,14 @@ const SettingsCustomization: FC = () => {
}
}, []);
const setInitialMask = (data: DeviceEntity[]) => {
setDeviceEntities(data.map((de) => ({ ...de, om: de.m })));
};
const fetchDeviceEntities = async (unique_id: number) => {
try {
setDeviceEntities((await EMSESP.readDeviceEntities({ id: unique_id })).data);
const data = (await EMSESP.readDeviceEntities({ id: unique_id })).data;
setInitialMask(data);
} catch (error: any) {
setErrorMessage(extractErrorMessage(error, 'Problem fetching device entities'));
}
@@ -109,8 +117,22 @@ const SettingsCustomization: FC = () => {
return (
<>
<Box color="warning.main">
<Typography variant="body2">
Customize which entities to exclude from all all services (MQTT, API). This will have immediate effect.
<Typography variant="body2">Select a device and customize each of its entities using the options:</Typography>
<Typography mt={1} ml={2} display="block" variant="body2" sx={{ alignItems: 'center', display: 'flex' }}>
<FavoriteBorderOutlinedIcon color="success" sx={{ fontSize: 13 }} />
&nbsp;mark it as favorite to be listed at the top of the Dashboard
</Typography>
<Typography ml={2} display="block" variant="body2" sx={{ alignItems: 'center', display: 'flex' }}>
<EditOffOutlinedIcon color="secondary" sx={{ fontSize: 13 }} />
&nbsp;make it read-only, only if it has write operation available
</Typography>
<Typography ml={2} display="block" variant="body2" sx={{ alignItems: 'center', display: 'flex' }}>
<CommentsDisabledOutlinedIcon color="secondary" sx={{ fontSize: 13 }} />
&nbsp;excluded it from MQTT and API outputs
</Typography>
<Typography ml={2} mb={1} display="block" variant="body2" sx={{ alignItems: 'center', display: 'flex' }}>
<VisibilityOffOutlinedIcon color="secondary" sx={{ fontSize: 13 }} />
&nbsp;hide it from the Dashboard
</Typography>
</Box>
<ValidatedTextField
@@ -138,11 +160,22 @@ const SettingsCustomization: FC = () => {
const saveCustomization = async () => {
if (deviceEntities && selectedDevice) {
const exclude_entities = deviceEntities.filter((de) => de.x).map((new_de) => new_de.i);
const masked_entities = deviceEntities
.filter((de) => de.m !== de.om)
.map((new_de) => new_de.m.toString(16).padStart(2, '0') + new_de.s);
if (masked_entities.length > 50) {
enqueueSnackbar(
'Too many selected entities (' + masked_entities.length + '). Limit is 50. Please Save in batches',
{ variant: 'warning' }
);
return;
}
try {
const response = await EMSESP.writeExcludeEntities({
const response = await EMSESP.writeMaskedEntities({
id: selectedDevice,
entity_ids: exclude_entities
entity_ids: masked_entities
});
if (response.status === 200) {
enqueueSnackbar('Customization saved', { variant: 'success' });
@@ -152,6 +185,7 @@ const SettingsCustomization: FC = () => {
} catch (error: any) {
enqueueSnackbar(extractErrorMessage(error, 'Problem sending entity list'), { variant: 'error' });
}
setInitialMask(deviceEntities);
}
};
@@ -160,48 +194,76 @@ const SettingsCustomization: FC = () => {
return;
}
const toggleDeviceEntity = (id: number) => {
setDeviceEntities(
deviceEntities.map((o) => {
if (o.i === id) {
return { ...o, x: !o.x };
const setMask = (de: DeviceEntity, newMask: string[]) => {
var new_mask = 0;
for (let entry of newMask) {
new_mask |= Number(entry);
}
return o;
})
);
de.m = new_mask;
setMasks(newMask);
};
const getMask = (de: DeviceEntity) => {
var new_masks = [];
if ((de.m & 1) === 1 || de.n === '') {
new_masks.push('1');
}
if ((de.m & 2) === 2) {
new_masks.push('2');
}
if ((de.m & 4) === 4 && de.w) {
new_masks.push('4');
}
if ((de.m & 8) === 8) {
new_masks.push('8');
}
return new_masks;
};
return (
<>
<Table size="small">
<Table size="small" padding="normal">
<TableHead>
<TableRow>
<StyledTableCell>
({deviceEntities.reduce((a, v) => (v.x ? a + 1 : a), 0)}/{deviceEntities.length})
</StyledTableCell>
<StyledTableCell align="left">ENTITY NAME</StyledTableCell>
<StyledTableCell>CODE</StyledTableCell>
<StyledTableCell align="center">OPTIONS</StyledTableCell>
<StyledTableCell align="left">ENTITY NAME (CODE)</StyledTableCell>
<StyledTableCell align="right">VALUE</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{deviceEntities.map((de) => (
<TableRow
key={de.i}
onClick={() => toggleDeviceEntity(de.i)}
sx={de.x ? { backgroundColor: '#f8696b' } : { backgroundColor: 'black' }}
<TableRow key={de.s} hover>
<StyledTableCell padding="none">
<ToggleButtonGroup
size="small"
color="secondary"
value={getMask(de)}
onChange={(event, mask) => {
setMask(de, mask);
}}
>
<StyledTableCell padding="checkbox">{de.x && <CloseIcon fontSize="small" />}</StyledTableCell>
<StyledTableCell component="th" scope="row">
{de.n}
<ToggleButton value="8" color="success" disabled={(de.m & 1) !== 0 || de.n === ''}>
<FavoriteBorderOutlinedIcon sx={{ fontSize: 14 }} />
</ToggleButton>
<ToggleButton value="4" disabled={!de.w}>
<EditOffOutlinedIcon sx={{ fontSize: 14 }} />
</ToggleButton>
<ToggleButton value="2">
<CommentsDisabledOutlinedIcon sx={{ fontSize: 14 }} />
</ToggleButton>
<ToggleButton value="1">
<VisibilityOffOutlinedIcon sx={{ fontSize: 14 }} />
</ToggleButton>
</ToggleButtonGroup>
</StyledTableCell>
<StyledTableCell>
{de.n}&nbsp;({de.s})
</StyledTableCell>
<StyledTableCell>{de.s}</StyledTableCell>
<StyledTableCell align="right">{formatValue(de.v)}</StyledTableCell>
</TableRow>
))}
</TableBody>
</Table>
</>
);
};

View File

@@ -12,7 +12,7 @@ import {
DeviceData,
DeviceEntity,
UniqueID,
ExcludeEntities,
MaskedEntities,
WriteValue,
WriteSensor,
WriteAnalog,
@@ -63,8 +63,8 @@ export function readDeviceEntities(unique_id: UniqueID): AxiosPromise<DeviceEnti
return AXIOS_BIN.post('/deviceEntities', unique_id);
}
export function writeExcludeEntities(excludeEntities: ExcludeEntities): AxiosPromise<void> {
return AXIOS.post('/excludeEntities', excludeEntities);
export function writeMaskedEntities(maskedEntities: MaskedEntities): AxiosPromise<void> {
return AXIOS.post('/maskedEntities', maskedEntities);
}
export function writeValue(writevalue: WriteValue): AxiosPromise<void> {
@@ -83,7 +83,6 @@ export function resetCustomizations(): AxiosPromise<void> {
return AXIOS.post('/resetCustomizations');
}
// EMS-ESP API calls
export function API(apiCall: APIcall): AxiosPromise<void> {
return AXIOS_API.post('/', apiCall);
}

View File

@@ -130,8 +130,10 @@ export interface DeviceValue {
n: string; // name
c: string; // command
l: string[]; // list
h?: string; // help text
s?: string; // steps for up/down
h?: string; // help text, optional
s?: string; // steps for up/down, optional
m?: string; // min, optional
x?: string; // max, optional
}
export interface DeviceData {
@@ -143,13 +145,14 @@ export interface DeviceEntity {
v?: any; // value, in any format
n: string; // name
s: string; // shortname
x: boolean; // excluded flag
i: number; // unique id
m: number; // mask
om?: number; // original mask before edits
w: boolean; // writeable
}
export interface ExcludeEntities {
export interface MaskedEntities {
id: number;
entity_ids: number[];
entity_ids: string[];
}
export interface UniqueID {
@@ -280,3 +283,11 @@ export interface WriteAnalog {
uom: number;
type: number;
}
export enum DeviceEntityMask {
DV_DEFAULT = 0,
DV_WEB_EXCLUDE = 1,
DV_API_MQTT_EXCLUDE = 2,
DV_READONLY = 4,
DV_FAVORITE = 8
}

View File

@@ -1,6 +1,7 @@
export enum NTPSyncStatus {
NTP_INACTIVE = 0,
NTP_ACTIVE = 1
NTP_DISABLED = 0,
NTP_INACTIVE = 1,
NTP_ACTIVE = 2
}
export interface NTPStatus {

View File

@@ -173,7 +173,7 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
ArJsonRequestHandlerFunction _onRequest;
size_t _contentLength;
#ifndef ARDUINOJSON_5_COMPATIBILITY
const size_t maxJsonBufferSize;
size_t _maxJsonBufferSize;
#endif
size_t _maxContentLength;
@@ -182,7 +182,7 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
: _uri(uri)
, _method(HTTP_POST | HTTP_PUT | HTTP_PATCH)
, _onRequest(onRequest)
, maxJsonBufferSize(maxJsonBufferSize)
, _maxJsonBufferSize(maxJsonBufferSize)
, _maxContentLength(16384) {
}
@@ -192,6 +192,9 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void setMaxJsonBufferSize(size_t maxJsonBufferSize) {
_maxJsonBufferSize = maxJsonBufferSize;
}
void onRequest(ArJsonRequestHandlerFunction fn) {
_onRequest = fn;
}
@@ -216,7 +219,7 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
virtual void handleRequest(AsyncWebServerRequest * request) override final {
if (_onRequest) {
if (request->_tempObject != NULL) {
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>();

View File

@@ -228,7 +228,15 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
changed = true;
}
// if both settings are stored from older version, HA has priority
if (newSettings.ha_enabled && newSettings.publish_single) {
newSettings.publish_single = false;
}
if (newSettings.publish_single != settings.publish_single) {
if (newSettings.publish_single) {
newSettings.ha_enabled = false;
}
changed = true;
}
@@ -242,6 +250,9 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
if (newSettings.ha_enabled != settings.ha_enabled) {
emsesp::EMSESP::mqtt_.ha_enabled(newSettings.ha_enabled);
if (newSettings.ha_enabled) {
newSettings.publish_single = false;
}
changed = true;
}

View File

@@ -50,7 +50,7 @@ static String generateClientId() {
#endif
#ifndef FACTORY_MQTT_CLEAN_SESSION
#define FACTORY_MQTT_CLEAN_SESSION true
#define FACTORY_MQTT_CLEAN_SESSION false
#endif
#ifndef FACTORY_MQTT_MAX_TOPIC_LENGTH

View File

@@ -46,8 +46,10 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
// https://werner.rothschopf.net/microcontroller/202103_arduino_esp32_ntp_en.htm
void NTPSettingsService::configureNTP() {
emsesp::EMSESP::system_.ntp_connected(false);
if (connected_ && _state.enabled) {
emsesp::EMSESP::logger().info(F("Starting NTP"));
sntp_set_time_sync_notification_cb(ntp_received);
configTzTime(_state.tzFormat.c_str(), _state.server.c_str());
} else {
setenv("TZ", _state.tzFormat.c_str(), 1);
@@ -57,11 +59,12 @@ void NTPSettingsService::configureNTP() {
}
void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVariant & json) {
if (!sntp_enabled() && json.is<JsonObject>()) {
if (json.is<JsonObject>()) {
struct tm tm = {0};
String timeLocal = json["local_time"];
char * s = strptime(timeLocal.c_str(), "%Y-%m-%dT%H:%M:%S", &tm);
if (s != nullptr) {
tm.tm_isdst = -1; // not set by strptime, tells mktime to determine daylightsaving
time_t time = mktime(&tm);
struct timeval now = {.tv_sec = time};
settimeofday(&now, nullptr);
@@ -74,3 +77,8 @@ void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVari
AsyncWebServerResponse * response = request->beginResponse(400);
request->send(response);
}
void NTPSettingsService::ntp_received(struct timeval * tv) {
// emsesp::EMSESP::logger().info(F("NTP sync to %d sec"), tv->tv_sec);
emsesp::EMSESP::system_.ntp_connected(true);
}

View File

@@ -57,6 +57,7 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin();
static void ntp_received(struct timeval * tv);
private:
HttpEndpoint<NTPSettings> _httpEndpoint;
@@ -67,6 +68,7 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
void WiFiEvent(WiFiEvent_t event);
void configureNTP();
void configureTime(AsyncWebServerRequest * request, JsonVariant & json);
};
#endif

View File

@@ -1,4 +1,5 @@
#include <NTPStatus.h>
#include "../../src/emsesp_stub.hpp" // proddy added
using namespace std::placeholders; // for `_1` etc
@@ -35,7 +36,7 @@ void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
time_t now = time(nullptr);
// only provide enabled/disabled status for now
root["status"] = sntp_enabled() ? 1 : 0;
root["status"] = sntp_enabled() ? emsesp::EMSESP::system_.ntp_connected() ? 2 : 1 : 0;
// the current time in UTC
root["utc_time"] = toUTCTimeString(gmtime(&now));

View File

@@ -187,6 +187,7 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
ArJsonRequestHandlerFunction _onRequest;
size_t _contentLength;
size_t _maxContentLength;
size_t _maxJsonBufferSize;
public:
AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
@@ -202,6 +203,9 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void setMaxJsonBufferSize(int maxJsonBufferSize) {
_maxJsonBufferSize = maxJsonBufferSize;
}
void onRequest(ArJsonRequestHandlerFunction fn) {
_onRequest = fn;
}

View File

@@ -302,7 +302,7 @@ const EMSESP_BOARDPROFILE_ENDPOINT = REST_ENDPOINT_ROOT + 'boardProfile'
const EMSESP_WRITE_VALUE_ENDPOINT = REST_ENDPOINT_ROOT + 'writeValue'
const EMSESP_WRITE_SENSOR_ENDPOINT = REST_ENDPOINT_ROOT + 'writeSensor'
const EMSESP_WRITE_ANALOG_ENDPOINT = REST_ENDPOINT_ROOT + 'writeAnalog'
const EMSESP_EXCLUDE_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'excludeEntities'
const EMSESP_MASKED_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'maskedEntities'
const EMSESP_RESET_CUSTOMIZATIONS_ENDPOINT = REST_ENDPOINT_ROOT + 'resetCustomizations'
settings = {
@@ -343,19 +343,19 @@ const emsesp_devices = {
i: 1,
d: 23,
p: 77,
s: 'Thermostat1',
s: 'Thermostat (RC20/Moduline 300)',
},
{
i: 2,
d: 8,
p: 123,
s: 'Boiler',
s: 'Boiler (Nefit GBx72/Trendline/Cerapur/Greenstar Si/27i)',
},
{
i: 4,
d: 16,
p: 165,
s: 'Thermostat2',
s: 'Thermostat (RC100/Moduline 1000/1010)',
},
],
}
@@ -376,7 +376,7 @@ const emsesp_coredata = {
{
i: 1,
t: 'Thermostat',
s: 'Thermostat1',
s: 'Thermostat',
b: '',
n: 'RC20/Moduline 300',
d: 23,
@@ -387,7 +387,7 @@ const emsesp_coredata = {
{
i: 4,
t: 'Thermostat',
s: 'Thermostat2',
s: 'Thermostat',
b: 'Buderus',
n: 'RC100/Moduline 1000/1010',
d: 16,
@@ -449,36 +449,36 @@ const status = {
// Dashboard data
const emsesp_devicedata_1 = {
label: 'RC20/Moduline 300',
label: 'Thermostat: RC20/Moduline 300',
data: [
{
v: '(0)',
u: 0,
n: 'error code',
n: '00error code',
c: '',
},
{
v: '14:54:39 06/06/2021',
u: 0,
n: 'date/time',
n: '00date/time',
c: '',
},
{
v: 18,
u: 1,
n: 'hc1 selected room temperature',
n: '00hc1 selected room temperature',
c: 'hc1/seltemp',
},
{
v: 22.6,
u: 1,
n: 'hc1 current room temperature',
n: '00hc1 current room temperature',
c: '',
},
{
v: 'auto',
u: 0,
n: 'hc1 mode',
n: '00hc1 mode',
c: 'hc1/mode',
},
],
@@ -487,107 +487,104 @@ const emsesp_devicedata_1 = {
const emsesp_devicedata_2 = {
label: 'Boiler: Nefit GBx72/Trendline/Cerapur/Greenstar Si/27i',
data: [
{ u: 0, n: 'reset', c: 'reset', l: ['-', 'maintenance', 'error'] },
{ v: 'false', u: 0, n: 'heating active' },
{ v: 'false', u: 0, n: 'tapwater active' },
{ v: 5, u: 1, n: 'selected flow temperature', c: 'selflowtemp' },
{ v: 0, u: 3, n: 'burner selected max power', c: 'selburnpow' },
{ v: 0, u: 3, n: 'heating pump modulation' },
{ v: 53.4, u: 1, n: 'current flow temperature' },
{ v: 52.7, u: 1, n: 'return temperature' },
{ v: 1.3, u: 10, n: 'system pressure' },
{ v: 54.9, u: 1, n: 'actual boiler temperature' },
{ v: 'false', u: 0, n: 'gas' },
{ v: 'false', u: 0, n: 'gas stage 2' },
{ v: 0, u: 9, n: 'flame current' },
{ v: 'false', u: 0, n: 'heating pump' },
{ v: 'false', u: 0, n: 'fan' },
{ v: 'false', u: 0, n: 'ignition' },
{ v: 'false', u: 0, n: 'oil preheating' },
{ v: 'true', u: 0, n: 'heating activated', c: 'heatingactivated', l: ['off', 'on'] },
{ v: 80, u: 1, n: 'heating temperature', c: 'heatingtemp' },
{ v: 70, u: 3, n: 'burner pump max power', c: 'pumpmodmax' },
{ v: 30, u: 3, n: 'burner pump min power', c: 'pumpmodmin' },
{ v: 1, u: 8, n: 'pump delay', c: 'pumpdelay' },
{ v: 10, u: 8, n: 'burner min period', c: 'burnminperiod' },
{ v: 0, u: 3, n: 'burner min power', c: 'burnminpower' },
{ v: 50, u: 3, n: 'burner max power', c: 'burnmaxpower' },
{ v: -6, u: 2, n: 'hysteresis on temperature', c: 'boilhyston' },
{ v: 6, u: 2, n: 'hysteresis off temperature', c: 'boilhystoff' },
{ v: 0, u: 1, n: 'set flow temperature' },
{ v: 0, u: 3, n: 'burner set power' },
{ v: 0, u: 3, n: 'burner current power' },
{ v: 326323, u: 0, n: 'burner starts' },
{ v: 553437, u: 8, n: 'total burner operating time' },
{ v: 451286, u: 8, n: 'total heat operating time' },
{ v: 4672173, u: 8, n: 'total UBA operating time' },
{ v: '1C(210) 06.06.2020 12:07 (0 min)', u: 0, n: 'last error code' },
{ v: '0H', u: 0, n: 'service code' },
{ v: 203, u: 0, n: 'service code number' },
{ v: 'H00', u: 0, n: 'maintenance message' },
{ v: 'manual', u: 0, n: 'maintenance scheduled', c: 'maintenance', l: ['off', 'time', 'date', 'manual'] },
{ v: 6000, u: 7, n: 'time to next maintenance', c: 'maintenancetime' },
{ v: '01.01.2012', u: 0, n: 'next maintenance date', c: 'maintenancedate', o: 'Format: < dd.mm.yyyy >' },
{ v: 'true', u: 0, n: 'dhw turn on/off', c: 'wwtapactivated', l: ['off', 'on'] },
{ v: 62, u: 1, n: 'dhw set temperature' },
{ v: 60, u: 1, n: 'dhw selected temperature', c: 'wwseltemp' },
{ v: 'flow', u: 0, n: 'dhw type' },
{ v: 'hot', u: 0, n: 'dhw comfort', c: 'wwcomfort', l: ['hot', 'eco', 'intelligent'] },
{ v: 40, u: 2, n: 'dhw flow temperature offset', c: 'wwflowtempoffset' },
{ v: 100, u: 3, n: 'dhw max power', c: 'wwmaxpower' },
{ v: 'false', u: 0, n: 'dhw circulation pump available', c: 'wwcircpump', l: ['off', 'on'] },
{ v: '3-way valve', u: 0, n: 'dhw charging type' },
{ v: -5, u: 2, n: 'dhw hysteresis on temperature', c: 'wwhyston' },
{ v: 0, u: 2, n: 'dhw hysteresis off temperature', c: 'wwhystoff' },
{ v: 70, u: 1, n: 'dhw disinfection temperature', c: 'wwdisinfectiontemp' },
{ u: 0, n: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] },
{ v: 'false', u: 0, n: '08heating active' },
{ v: 'false', u: 0, n: '04tapwater active' },
{ v: 5, u: 1, n: '04selected flow temperature', c: 'selflowtemp' },
{ v: 0, u: 3, n: '0Eburner selected max power', c: 'selburnpow' },
{ v: 0, u: 3, n: '00heating pump modulation' },
{ v: 53.4, u: 1, n: '00current flow temperature' },
{ v: 52.7, u: 1, n: '00return temperature' },
{ v: 1.3, u: 10, n: '00system pressure' },
{ v: 54.9, u: 1, n: '00actual boiler temperature' },
{ v: 'false', u: 0, n: '00gas' },
{ v: 'false', u: 0, n: '00gas stage 2' },
{ v: 0, u: 9, n: '00flame current' },
{ v: 'false', u: 0, n: '00heating pump' },
{ v: 'false', u: 0, n: '00fan' },
{ v: 'false', u: 0, n: '00ignition' },
{ v: 'false', u: 0, n: '00oil preheating' },
{ v: 'true', u: 0, n: '00heating activated', c: 'heatingactivated', l: ['off', 'on'] },
{ v: 80, u: 1, n: '00heating temperature', c: 'heatingtemp' },
{ v: 70, u: 3, n: '00burner pump max power', c: 'pumpmodmax' },
{ v: 30, u: 3, n: '00burner pump min power', c: 'pumpmodmin' },
{ v: 1, u: 8, n: '00pump delay', c: 'pumpdelay' },
{ v: 10, u: 8, n: '00burner min period', c: 'burnminperiod' },
{ v: 0, u: 3, n: '00burner min power', c: 'burnminpower' },
{ v: 50, u: 3, n: '00burner max power', c: 'burnmaxpower' },
{ v: -6, u: 2, n: '00hysteresis on temperature', c: 'boilhyston' },
{ v: 6, u: 2, n: '00hysteresis off temperature', c: 'boilhystoff' },
{ v: 0, u: 1, n: '00set flow temperature' },
{ v: 0, u: 3, n: '00burner set power' },
{ v: 0, u: 3, n: '00burner current power' },
{ v: 326323, u: 0, n: '00burner starts' },
{ v: 553437, u: 8, n: '00total burner operating time' },
{ v: 451286, u: 8, n: '00total heat operating time' },
{ v: 4672173, u: 8, n: '00total UBA operating time' },
{ v: '1C(210) 06.06.2020 12:07 (0 min)', u: 0, n: '00last error code' },
{ v: '0H', u: 0, n: '00service code' },
{ v: 203, u: 0, n: '00service code number' },
{ v: 'H00', u: 0, n: '00maintenance message' },
{ v: 'manual', u: 0, n: '00maintenance scheduled', c: 'maintenance', l: ['off', 'time', 'date', 'manual'] },
{ v: 6000, u: 7, n: '00time to next maintenance', c: 'maintenancetime' },
{ v: '01.01.2012', u: 0, n: '00next maintenance date', c: 'maintenancedate', o: 'Format: < dd.mm.yyyy >' },
{ v: 'true', u: 0, n: '00dhw turn on/off', c: 'wwtapactivated', l: ['off', 'on'] },
{ v: 62, u: 1, n: '00dhw set temperature' },
{ v: 60, u: 1, n: '00dhw selected temperature', c: 'wwseltemp' },
{ v: 'flow', u: 0, n: '00dhw type' },
{ v: 'hot', u: 0, n: '00dhw comfort', c: 'wwcomfort', l: ['hot', 'eco', 'intelligent'] },
{ v: 40, u: 2, n: '00dhw flow temperature offset', c: 'wwflowtempoffset' },
{ v: 100, u: 3, n: '00dhw max power', c: 'wwmaxpower' },
{ v: 'false', u: 0, n: '00dhw circulation pump available', c: 'wwcircpump', l: ['off', 'on'] },
{ v: '3-way valve', u: 0, n: '00dhw charging type' },
{ v: -5, u: 2, n: '00dhw hysteresis on temperature', c: 'wwhyston' },
{ v: 0, u: 2, n: '00dhw hysteresis off temperature', c: 'wwhystoff' },
{ v: 70, u: 1, n: '00dhw disinfection temperature', c: 'wwdisinfectiontemp' },
{
v: 'off',
u: 0,
n: 'dhw circulation pump mode',
n: '00dhw circulation pump mode',
c: 'wwcircmode',
l: ['off', '1x3min', '2x3min', '3x3min', '4x3min', '5x3min', '6x3min', 'continuous'],
},
{ v: 'false', u: 0, n: 'dhw circulation active', c: 'wwcirc', l: ['off', 'on'] },
{ v: 47.3, u: 1, n: 'dhw current intern temperature' },
{ v: 0, u: 4, n: 'dhw current tap water flow' },
{ v: 47.3, u: 1, n: 'dhw storage intern temperature' },
{ v: 'true', u: 0, n: 'dhw activated', c: 'wwactivated', l: ['off', 'on'] },
{ v: 'false', u: 0, n: 'dhw one time charging', c: 'wwonetime', l: ['off', 'on'] },
{ v: 'false', u: 0, n: 'dhw disinfecting', c: 'wwdisinfecting', l: ['off', 'on'] },
{ v: 'false', u: 0, n: 'dhw charging' },
{ v: 'false', u: 0, n: 'dhw recharging' },
{ v: 'true', u: 0, n: 'dhw temperature ok' },
{ v: 'false', u: 0, n: 'dhw active' },
{ v: 'true', u: 0, n: 'dhw 3way valve active' },
{ v: 0, u: 3, n: 'dhw set pump power' },
{ v: 288768, u: 0, n: 'dhw starts' },
{ v: 102151, u: 8, n: 'dhw active time' },
{ v: 'false', u: 0, n: '00dhw circulation active', c: 'wwcirc', l: ['off', 'on'] },
{ v: 47.3, u: 1, n: '00dhw current intern temperature' },
{ v: 0, u: 4, n: '00dhw current tap water flow' },
{ v: 47.3, u: 1, n: '00dhw storage intern temperature' },
{ v: 'true', u: 0, n: '00dhw activated', c: 'wwactivated', l: ['off', 'on'] },
{ v: 'false', u: 0, n: '00dhw one time charging', c: 'wwonetime', l: ['off', 'on'] },
{ v: 'false', u: 0, n: '00dhw disinfecting', c: 'wwdisinfecting', l: ['off', 'on'] },
{ v: 'false', u: 0, n: '00dhw charging' },
{ v: 'false', u: 0, n: '00dhw recharging' },
{ v: 'true', u: 0, n: '00dhw temperature ok' },
{ v: 'false', u: 0, n: '00dhw active' },
{ v: 'true', u: 0, n: '00dhw 3way valve active' },
{ v: 0, u: 3, n: '00dhw set pump power' },
{ v: 288768, u: 0, n: '00dhw starts' },
{ v: 102151, u: 8, n: '00dhw active time' },
],
}
const emsesp_devicedata_4 = {
label: 'RC100/Moduline 1000/1010',
label: 'Thermostat: RC100/Moduline 1000/1010',
data: [
{
v: 16,
u: 1,
n: 'hc2 selected room temperature',
n: '00hc2 selected room temperature',
c: 'hc2/seltemp',
x: false,
},
{
v: 18.6,
u: 1,
n: 'hc2 current room temperature',
n: '00hc2 current room temperature',
c: '',
x: true,
},
{
v: 'off',
u: 0,
n: 'hc2 mode',
n: '00hc2 mode',
c: 'hc2/mode',
x: true,
},
],
}
@@ -597,119 +594,119 @@ const emsesp_deviceentities_1 = [
v: '(0)',
n: 'error code',
s: 'errorcode',
x: false,
i: 1,
m: 0,
w: false,
},
{
v: '14:54:39 06/06/2021',
n: 'date/time',
s: 'datetime',
x: false,
i: 2,
m: 0,
w: false,
},
{
v: 18.22,
v: 18.2,
n: 'hc1 selected room temperature',
s: 'hc1/seltemp',
x: false,
i: 3,
m: 0,
w: true,
},
{
v: 22.6,
n: 'hc1 current room temperature',
s: 'hc1/curtemp',
x: false,
i: 4,
m: 0,
w: false,
},
{
v: 'auto',
n: 'hc1 mode',
s: 'hc1/mode',
x: false,
i: 5,
m: 0,
w: true,
},
]
const emsesp_deviceentities_2 = [
{ v: false, n: 'heating active', s: 'heatingactive', x: false, i: 1 },
{ v: false, n: 'tapwater active', s: 'tapwateractive', x: false, i: 2 },
{ v: 5, n: 'selected flow temperature', s: 'selflowtemp', x: false, i: 3 },
{ v: 0, n: 'burner selected max power', s: 'selburnpow', x: false, i: 4 },
{ v: 0, n: 'heating pump modulation', s: 'heatingpumpmod', x: false, i: 5 },
{ n: 'heating pump 2 modulation', s: 'heatingpump2mod', x: true, i: 6 },
{ n: 'outside temperature', s: 'outdoortemp', x: true, i: 7 },
{ v: 53, n: 'current flow temperature', s: 'curflowtemp', x: false, i: 8 },
{ v: 51.8, n: 'return temperature', s: 'rettemp', x: false, i: 9 },
{ n: 'mixing switch temperature', s: 'switchtemp', x: true, i: 10 },
{ v: 1.3, n: 'system pressure', s: 'syspress', x: false, i: 11 },
{ v: 54.6, n: 'actual boiler temperature', s: 'boiltemp', x: false, i: 12 },
{ n: 'exhaust temperature', s: 'exhausttemp', x: true, i: 13 },
{ v: false, n: 'gas', s: 'burngas', x: false, i: 14 },
{ v: false, n: 'gas stage 2', s: 'burngas2', x: false, i: 15 },
{ v: 0, n: 'flame current', s: 'flamecurr', x: false, i: 16 },
{ v: false, n: 'heating pump', s: 'heatingpump', x: false, i: 17 },
{ v: false, n: 'fan', s: 'fanwork', x: false, i: 18 },
{ v: false, n: 'ignition', s: 'ignwork', x: false, i: 19 },
{ v: false, n: 'oil preheating', s: 'oilpreheat', x: false, i: 20 },
{ v: true, n: 'heating activated', s: 'heatingactivated', x: false, i: 21 },
{ v: 80, n: 'heating temperature', s: 'heatingtemp', x: false, i: 22 },
{ v: 70, n: 'burner pump max power', s: 'pumpmodmax', x: false, i: 23 },
{ v: 30, n: 'burner pump min power', s: 'pumpmodmin', x: false, i: 24 },
{ v: 1, n: 'pump delay', s: 'pumpdelay', x: false, i: 25 },
{ v: 10, n: 'burner min period', s: 'burnminperiod', x: false, i: 26 },
{ v: 0, n: 'burner min power', s: 'burnminpower', x: false, i: 27 },
{ v: 50, n: 'burner max power', s: 'burnmaxpower', x: false, i: 28 },
{ v: -6, n: 'hysteresis on temperature', s: 'boilhyston', x: false, i: 29 },
{ v: 6, n: 'hysteresis off temperature', s: 'boilhystoff', x: false, i: 30 },
{ v: 0, n: 'set flow temperature', s: 'setflowtemp', x: false, i: 31 },
{ v: 0, n: 'burner set power', s: 'setburnpow', x: false, i: 32 },
{ v: 0, n: 'burner current power', s: 'curburnpow', x: false, i: 33 },
{ v: 326323, n: 'burner starts', s: 'burnstarts', x: false, i: 34 },
{ v: 553437, n: 'total burner operating time', s: 'burnworkmin', x: false, i: 35 },
{ v: 451286, n: 'total heat operating time', s: 'heatworkmin', x: false, i: 36 },
{ v: 4672175, n: 'total UBA operating time', s: 'ubauptime', x: false, i: 37 },
{ v: '1C(210) 06.06.2020 12:07 (0 min)', n: 'last error code', s: 'lastcode', x: false, i: 38 },
{ v: '0H', n: 'service code', s: 'servicecode', x: false, i: 39 },
{ v: 203, n: 'service code number', s: 'servicecodenumber', x: false, i: 40 },
{ v: 'H00', n: 'maintenance message', s: 'maintenancemessage', x: false, i: 41 },
{ v: 'manual', n: 'maintenance scheduled', s: 'maintenance', x: false, i: 42 },
{ v: 6000, n: 'time to next maintenance', s: 'maintenancetime', x: false, i: 43 },
{ v: '01.01.2012', n: 'next maintenance date', s: 'maintenancedate', x: false, i: 44 },
{ v: true, n: 'dhw turn on/off', s: 'wwtapactivated', x: false, i: 45 },
{ v: 62, n: 'dhw set temperature', s: 'wwsettemp', x: false, i: 46 },
{ v: 60, n: 'dhw selected temperature', s: 'wwseltemp', x: false, i: 47 },
{ n: 'dhw selected lower temperature', s: 'wwseltemplow', x: true, i: 48 },
{ n: 'dhw selected temperature for off', s: 'wwseltempoff', x: true, i: 49 },
{ n: 'dhw single charge temperature', s: 'wwseltempsingle', x: true, i: 50 },
{ v: 'flow', n: 'dhw type', s: 'wwtype', x: false, i: 51 },
{ v: 'hot', n: 'dhw comfort', s: 'wwcomfort', x: false, i: 52 },
{ v: 40, n: 'dhw flow temperature offset', s: 'wwflowtempoffset', x: false, i: 53 },
{ v: 100, n: 'dhw max power', s: 'wwmaxpower', x: false, i: 54 },
{ v: false, n: 'dhw circulation pump available', s: 'wwcircpump', x: false, i: 55 },
{ v: '3-way valve', n: 'dhw charging type', s: 'wwchargetype', x: false, i: 56 },
{ v: -5, n: 'dhw hysteresis on temperature', s: 'wwhyston', x: false, i: 57 },
{ v: 0, n: 'dhw hysteresis off temperature', s: 'wwhystoff', x: false, i: 58 },
{ v: 70, n: 'dhw disinfection temperature', s: 'wwdisinfectiontemp', x: false, i: 59 },
{ v: 'off', n: 'dhw circulation pump mode', s: 'wwcircmode', x: false, i: 60 },
{ v: false, n: 'dhw circulation active', s: 'wwcirc', x: false, i: 61 },
{ v: 46.4, n: 'dhw current intern temperature', s: 'wwcurtemp', x: false, i: 62 },
{ n: 'dhw current extern temperature', s: 'wwcurtemp2', x: true, i: 63 },
{ v: 0, n: 'dhw current tap water flow', s: 'wwcurflow', x: false, i: 64 },
{ v: 46.3, n: 'dhw storage intern temperature', s: 'wwstoragetemp1', x: false, i: 65 },
{ n: 'dhw storage extern temperature', s: 'wwstoragetemp2', x: true, i: 66 },
{ v: true, n: 'dhw activated', s: 'wwactivated', x: false, i: 67 },
{ v: false, n: 'dhw one time charging', s: 'wwonetime', x: false, i: 68 },
{ v: false, n: 'dhw disinfecting', s: 'wwdisinfecting', x: false, i: 69 },
{ v: false, n: 'dhw charging', s: 'wwcharging', x: false, i: 70 },
{ v: false, n: 'dhw recharging', s: 'wwrecharging', x: false, i: 71 },
{ v: true, n: 'dhw temperature ok', s: 'wwtempok', x: false, i: 72 },
{ v: false, n: 'dhw active', s: 'wwactive', x: false, i: 73 },
{ v: true, n: 'dhw 3way valve active', s: 'ww3wayvalve', x: false, i: 74 },
{ v: 0, n: 'dhw set pump power', s: 'wwsetpumppower', x: false, i: 75 },
{ n: 'dhw mixer temperature', s: 'wwmixertemp', x: true, i: 76 },
{ n: 'dhw cylinder middle temperature (TS3)', s: 'wwcylmiddletemp', x: true, i: 77 },
{ v: 288768, n: 'dhw starts', s: 'wwstarts', x: false, i: 78 },
{ v: 102151, n: 'dhw active time', s: 'wwworkm', x: false, i: 79 },
{ v: false, n: 'heating active', s: 'heatingactive', m: 0 },
{ v: false, n: 'tapwater active', s: 'tapwateractive', m: 0 },
{ v: 5, n: 'selected flow temperature', s: 'selflowtemp', m: 0 },
{ v: 0, n: 'burner selected max power', s: 'selburnpow', m: 0 },
{ v: 0, n: 'heating pump modulation', s: 'heatingpumpmod', m: 0 },
{ n: 'heating pump 2 modulation', s: 'heatingpump2mod', m: 0 },
{ n: 'outside temperature', s: 'outdoortemp', m: 0 },
{ v: 53, n: 'current flow temperature', s: 'curflowtemp', m: 0 },
{ v: 51.8, n: 'return temperature', s: 'rettemp', m: 0 },
{ n: 'mixing switch temperature', s: 'switchtemp', m: 0 },
{ v: 1.3, n: 'system pressure', s: 'syspress', m: 0 },
{ v: 54.6, n: 'actual boiler temperature', s: 'boiltemp', m: 0 },
{ n: 'exhaust temperature', s: 'exhausttemp', m: 0 },
{ v: false, n: 'gas', s: 'burngas', m: 0 },
{ v: false, n: 'gas stage 2', s: 'burngas2', m: 0 },
{ v: 0, n: 'flame current', s: 'flamecurr', m: 0 },
{ v: false, n: 'heating pump', s: 'heatingpump', m: 0 },
{ v: false, n: 'fan', s: 'fanwork', m: 0 },
{ v: false, n: 'ignition', s: 'ignwork', m: 0 },
{ v: false, n: 'oil preheating', s: 'oilpreheat', m: 0 },
{ v: true, n: 'heating activated', s: 'heatingactivated', m: 0 },
{ v: 80, n: 'heating temperature', s: 'heatingtemp', m: 0 },
{ v: 70, n: 'burner pump max power', s: 'pumpmodmax', m: 0 },
{ v: 30, n: 'burner pump min power', s: 'pumpmodmin', m: 0 },
{ v: 1, n: 'pump delay', s: 'pumpdelay', m: 0 },
{ v: 10, n: 'burner min period', s: 'burnminperiod', m: 0 },
{ v: 0, n: 'burner min power', s: 'burnminpower', m: 0 },
{ v: 50, n: 'burner max power', s: 'burnmaxpower', m: 0 },
{ v: -6, n: 'hysteresis on temperature', s: 'boilhyston', m: 0 },
{ v: 6, n: 'hysteresis off temperature', s: 'boilhystoff', m: 0 },
{ v: 0, n: 'set flow temperature', s: 'setflowtemp', m: 0 },
{ v: 0, n: 'burner set power', s: 'setburnpow', m: 0 },
{ v: 0, n: 'burner current power', s: 'curburnpow', m: 0 },
{ v: 326323, n: 'burner starts', s: 'burnstarts', m: 0 },
{ v: 553437, n: 'total burner operating time', s: 'burnworkmin', m: 0 },
{ v: 451286, n: 'total heat operating time', s: 'heatworkmin', m: 0 },
{ v: 4672175, n: 'total UBA operating time', s: 'ubauptime', m: 0 },
{ v: '1C(210) 06.06.2020 12:07 (0 min)', n: 'last error code', s: 'lastcode', m: 0 },
{ v: '0H', n: 'service code', s: 'servicecode', m: 0 },
{ v: 203, n: 'service code number', s: 'servicecodenumber', m: 0 },
{ v: 'H00', n: 'maintenance message', s: 'maintenancemessage', m: 0 },
{ v: 'manual', n: 'maintenance scheduled', s: 'maintenance', m: 0 },
{ v: 6000, n: 'time to next maintenance', s: 'maintenancetime', m: 0 },
{ v: '01.01.2012', n: 'next maintenance date', s: 'maintenancedate', m: 0 },
{ v: true, n: 'dhw turn on/off', s: 'wwtapactivated', m: 0 },
{ v: 62, n: 'dhw set temperature', s: 'wwsettemp', m: 0 },
{ v: 60, n: 'dhw selected temperature', s: 'wwseltemp', m: 0 },
{ n: 'dhw selected lower temperature', s: 'wwseltemplow', m: 2 },
{ n: 'dhw selected temperature for off', s: 'wwseltempoff', m: 2 },
{ n: 'dhw single charge temperature', s: 'wwseltempsingle', m: 2 },
{ v: 'flow', n: 'dhw type', s: 'wwtype', m: 0 },
{ v: 'hot', n: 'dhw comfort', s: 'wwcomfort', m: 0 },
{ v: 40, n: 'dhw flow temperature offset', s: 'wwflowtempoffset', m: 0 },
{ v: 100, n: 'dhw max power', s: 'wwmaxpower', m: 0 },
{ v: false, n: 'dhw circulation pump available', s: 'wwcircpump', m: 0 },
{ v: '3-way valve', n: 'dhw charging type', s: 'wwchargetype', m: 0 },
{ v: -5, n: 'dhw hysteresis on temperature', s: 'wwhyston', m: 0 },
{ v: 0, n: 'dhw hysteresis off temperature', s: 'wwhystoff', m: 0 },
{ v: 70, n: 'dhw disinfection temperature', s: 'wwdisinfectiontemp', m: 0 },
{ v: 'off', n: 'dhw circulation pump mode', s: 'wwcircmode', m: 0 },
{ v: false, n: 'dhw circulation active', s: 'wwcirc', m: 0 },
{ v: 46.4, n: 'dhw current intern temperature', s: 'wwcurtemp', m: 0 },
{ n: 'dhw current extern temperature', s: 'wwcurtemp2', m: 2 },
{ v: 0, n: 'dhw current tap water flow', s: 'wwcurflow', m: 0 },
{ v: 46.3, n: 'dhw storage intern temperature', s: 'wwstoragetemp1', m: 0 },
{ n: 'dhw storage extern temperature', s: 'wwstoragetemp2', m: 2 },
{ v: true, n: 'dhw activated', s: 'wwactivated', m: 0 },
{ v: false, n: 'dhw one time charging', s: 'wwonetime', m: 0 },
{ v: false, n: 'dhw disinfecting', s: 'wwdisinfecting', m: 0 },
{ v: false, n: 'dhw charging', s: 'wwcharging', m: 0 },
{ v: false, n: 'dhw recharging', s: 'wwrecharging', m: 0 },
{ v: true, n: 'dhw temperature ok', s: 'wwtempok', m: 0 },
{ v: false, n: 'dhw active', s: 'wwactive', m: 0 },
{ v: true, n: 'dhw 3way valve active', s: 'ww3wayvalve', m: 0 },
{ v: 0, n: 'dhw set pump power', s: 'wwsetpumppower', m: 0 },
{ n: 'dhw mixer temperature', s: 'wwmixertemp', m: 2 },
{ n: 'dhw cylinder middle temperature (TS3)', s: 'wwcylmiddletemp', m: 2 },
{ v: 288768, n: 'dhw starts', s: 'wwstarts', m: 0 },
{ v: 102151, n: 'dhw active time', s: 'wwworkm', m: 0 },
]
const emsesp_deviceentities_4 = [
@@ -717,21 +714,22 @@ const emsesp_deviceentities_4 = [
v: 16,
n: 'hc2 selected room temperature',
s: 'hc2/seltemp',
x: false,
i: 1,
m: 0,
w: true,
},
{
v: 18.5,
n: 'hc2 current room temperature',
s: 'hc2/curtemp',
x: true,
i: 2,
m: 3,
w: false,
},
{
v: 'off',
n: 'hc2 mode',
s: 'hc2/mode',
x: true,
i: 3,
m: 3,
w: true,
},
]
@@ -924,9 +922,41 @@ rest_server.post(EMSESP_DEVICEENTITIES_ENDPOINT, (req, res) => {
}
})
rest_server.post(EMSESP_EXCLUDE_ENTITIES_ENDPOINT, (req, res) => {
console.log('exclude list for productid ' + req.body.product_id + ' device_id ' + req.body.device_id + ' entities:')
function updateMask(entity, de, dd) {
const name = entity.slice(2)
const new_mask = parseInt(entity.slice(0, 2), 16)
objIndex = de.findIndex((obj) => obj.s == name)
if (objIndex !== -1) {
de[objIndex].m = new_mask
const fullname = de[objIndex].n
objIndex = dd.data.findIndex((obj) => obj.n.slice(2) == fullname)
if (objIndex !== -1) {
// see if the mask has changed
const old_mask = parseInt(dd.data[objIndex].n.slice(0, 2), 16)
if (old_mask !== new_mask) {
const mask_hex = entity.slice(0, 2)
console.log('Updating ' + dd.data[objIndex].n + ' -> ' + mask_hex + fullname)
dd.data[objIndex].n = mask_hex + fullname
}
}
} else {
console.log("can't locate record for id " + id)
}
}
rest_server.post(EMSESP_MASKED_ENTITIES_ENDPOINT, (req, res) => {
const id = req.body.id
console.log(req.body.entity_ids)
for (const entity of req.body.entity_ids) {
if (id === 1) {
updateMask(entity, emsesp_deviceentities_1, emsesp_devicedata_1)
} else if (id === 2) {
updateMask(entity, emsesp_deviceentities_2, emsesp_devicedata_2)
} else if (id === 4) {
updateMask(entity, emsesp_deviceentities_4, emsesp_devicedata_4)
}
}
res.sendStatus(200)
})

View File

@@ -281,7 +281,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
return_code = ((cf->cmdfunction_json_)(value, id, output)) ? CommandRet::OK : CommandRet::ERROR;
}
if (cf->cmdfunction_) {
if (cf->cmdfunction_ && !EMSESP::cmd_is_readonly(device_type, cmd, id)) {
return_code = ((cf->cmdfunction_)(value, id)) ? CommandRet::OK : CommandRet::ERROR;
}

View File

@@ -139,13 +139,10 @@ class Command {
}
};
using KeyValueMap_t = std::unordered_map<std::string, std::string>;
using Folder_t = std::vector<std::string>;
class SUrlParser {
private:
KeyValueMap_t m_keysvalues;
Folder_t m_folders;
std::unordered_map<std::string, std::string> m_keysvalues;
std::vector<std::string> m_folders;
public:
SUrlParser() = default;
@@ -153,11 +150,11 @@ class SUrlParser {
bool parse(const char * url);
Folder_t & paths() {
std::vector<std::string> & paths() {
return m_folders;
};
KeyValueMap_t & params() {
std::unordered_map<std::string, std::string> & params() {
return m_keysvalues;
};

View File

@@ -36,6 +36,7 @@
{132, DeviceType::BOILER, F("GC7000F"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{133, DeviceType::BOILER, F("Logano GB125/KB195i/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{167, DeviceType::BOILER, F("Cerapur Aero"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{168, DeviceType::BOILER, F("Hybrid Heatpump"), DeviceFlags::EMS_DEVICE_FLAG_HYBRID},
{170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{172, DeviceType::BOILER, F("Enviline/Compress 6000AW/Hybrid 7000iAW/SupraEco/Geo 5xx"), DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP},
{173, DeviceType::BOILER, F("Geo 5xx"), DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP},
@@ -56,6 +57,7 @@
{114, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{125, DeviceType::CONTROLLER, F("BC25"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{152, DeviceType::CONTROLLER, F("Controller"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{168, DeviceType::CONTROLLER, F("Hybrid Heatpump"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{169, DeviceType::CONTROLLER, F("BC40"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{190, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
{194, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
@@ -90,6 +92,7 @@
{165, DeviceType::THERMOSTAT, F("RC100/Moduline 1000/1010"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38
{172, DeviceType::THERMOSTAT, F("Rego 2000/3000"), DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
{216, DeviceType::THERMOSTAT, F("CRF200S"), DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
{246, DeviceType::THERMOSTAT, F("Comfort+2RF"), DeviceFlags::EMS_DEVICE_FLAG_CRF | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18
// Thermostat - Sieger - 0x10 / 0x17
{ 66, DeviceType::THERMOSTAT, F("ES72/RC20"), DeviceFlags::EMS_DEVICE_FLAG_RC20_N}, // 0x17 or remote
@@ -108,15 +111,17 @@
{191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model
{192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
// Solar Modules - 0x30, 0x2A (for ww)
// Solar Modules - 0x30 (for solar), 0x2A, 0x41 (for ww)
{ 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10},
{101, DeviceType::SOLAR, F("ISM1"), DeviceFlags::EMS_DEVICE_FLAG_ISM},
{103, DeviceType::SOLAR, F("ISM2"), DeviceFlags::EMS_DEVICE_FLAG_ISM},
{162, DeviceType::SOLAR, F("SM50"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
{163, DeviceType::SOLAR, F("SM100/MS100"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
{164, DeviceType::SOLAR, F("SM200/MS200"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
// Mixer Modules - 0x20-0x27 for HC, 0x28-0x29 for WWC and 0x11 for the MP100
{ 69, DeviceType::MIXER, F("MM10"), DeviceFlags::EMS_DEVICE_FLAG_MM10},
{100, DeviceType::MIXER, F("IPM"), DeviceFlags::EMS_DEVICE_FLAG_IPM},
{102, DeviceType::MIXER, F("IPM"), DeviceFlags::EMS_DEVICE_FLAG_IPM},
{159, DeviceType::MIXER, F("MM50"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
{160, DeviceType::MIXER, F("MM100"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
@@ -134,6 +139,7 @@
// Wireless sensor base - 0x50
{236, DeviceType::CONNECT, F("Wireless sensor base"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
{238, DeviceType::CONNECT, F("Wireless sensor base"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
// Switches - 0x11
{ 71, DeviceType::SWITCH, F("WM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE},

View File

@@ -48,6 +48,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
// the telegram handlers...
// common for all boilers
register_telegram_type(0xBF, F("ErrorMessage"), false, MAKE_PF_CB(process_ErrorMessage));
register_telegram_type(0x10, F("UBAErrorMessage1"), false, MAKE_PF_CB(process_UBAErrorMessage));
register_telegram_type(0x11, F("UBAErrorMessage2"), false, MAKE_PF_CB(process_UBAErrorMessage));
register_telegram_type(0xC2, F("UBAErrorMessage3"), false, MAKE_PF_CB(process_UBAErrorMessage2));
@@ -70,7 +71,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
}
// only EMS+
if (model() != EMSdevice::EMS_DEVICE_FLAG_EMS && model() != EMSdevice::EMS_DEVICE_FLAG_HT3) {
if (model() != EMSdevice::EMS_DEVICE_FLAG_EMS && model() != EMSdevice::EMS_DEVICE_FLAG_HT3 && model() != EMSdevice::EMS_DEVICE_FLAG_HYBRID) {
register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, MAKE_PF_CB(process_UBAOutdoorTemp));
register_telegram_type(0xE3, F("UBAMonitorSlowPlus2"), false, MAKE_PF_CB(process_UBAMonitorSlowPlus2));
register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, MAKE_PF_CB(process_UBAMonitorFastPlus));
@@ -88,6 +89,11 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_telegram_type(0x48A, F("HpPool"), true, MAKE_PF_CB(process_HpPool));
}
/*
if (model() == EMSdevice::EMS_DEVICE_FLAG_HYBRID) {
register_telegram_type(0xBB, F("HybridHp"), true, MAKE_PF_CB(process_HybridHp));
}
*/
// reset is a command uses a dummy variable which is always zero, shown as blank, but provides command enum options
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &reset_, DeviceValueType::CMD, FL_(enum_reset), FL_(reset), DeviceValueUOM::NONE, MAKE_CF_CB(set_reset));
has_update(reset_, 0);
@@ -196,6 +202,72 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
DeviceValueUOM::NONE,
MAKE_CF_CB(set_maintenancedate));
/*
// Hybrid Heatpump
if (model() == EMSdevice::EMS_DEVICE_FLAG_HYBRID) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&hybridStrategy_,
DeviceValueType::ENUM,
FL_(enum_hybridStrategy),
FL_(hybridStrategy),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_hybridStrategy));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&switchOverTemp_,
DeviceValueType::INT,
nullptr,
FL_(switchOverTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_switchOverTemp),
-20,
20);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&energyCostRatio_,
DeviceValueType::UINT,
FL_(div10),
FL_(energyCostRatio),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_energyCostRatio),
0,
19.9);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&fossileFactor_,
DeviceValueType::UINT,
FL_(div10),
FL_(fossileFactor),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_fossileFactor),
0,
5);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&electricFactor_,
DeviceValueType::UINT,
FL_(div10),
FL_(electricFactor),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_electricFactor),
0,
5);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&delayBoiler_,
DeviceValueType::UINT,
nullptr,
FL_(delayBoiler),
DeviceValueUOM::MINUTES,
MAKE_CF_CB(set_delayBoiler),
5,
120);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&tempDiffBoiler_,
DeviceValueType::UINT,
nullptr,
FL_(tempDiffBoiler),
DeviceValueUOM::DEGREES_R,
MAKE_CF_CB(set_tempDiffBoiler),
1,
99);
}
*/
// heatpump info
if (model() == EMS_DEVICE_FLAG_HEATPUMP) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &upTimeControl_, DeviceValueType::TIME, FL_(div60), FL_(upTimeControl), DeviceValueUOM::MINUTES);
@@ -631,6 +703,7 @@ void Boiler::process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, heatingPumpMod_, 9);
has_update(telegram, burnStarts_, 10, 3); // force to 3 bytes
has_update(telegram, burnWorkMin_, 13, 3); // force to 3 bytes
has_update(telegram, burn2WorkMin_, 16, 3); // force to 3 bytes
has_update(telegram, heatWorkMin_, 19, 3); // force to 3 bytes
}
@@ -655,6 +728,7 @@ void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram
has_update(telegram, exhaustTemp_, 6);
has_update(telegram, burnStarts_, 10, 3); // force to 3 bytes
has_update(telegram, burnWorkMin_, 13, 3); // force to 3 bytes
has_update(telegram, burn2WorkMin_, 16, 3); // force to 3 bytes
has_update(telegram, heatWorkMin_, 19, 3); // force to 3 bytes
has_update(telegram, heatingPumpMod_, 25);
// temperature measurements at 4, see #620
@@ -869,6 +943,11 @@ void Boiler::process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegr
}
}
// 0xBF
void Boiler::process_ErrorMessage(std::shared_ptr<const Telegram> telegram) {
EMSESP::send_read_request(0xC2, device_id()); // read last errorcode
}
// 0x10, 0x11
void Boiler::process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram) {
if (telegram->offset > 0 || telegram->message_length < 11) {
@@ -984,6 +1063,86 @@ void Boiler::process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram
has_update(maintenanceDate_, date, sizeof(maintenanceDate_));
}
}
/*
// 0xBB Heatpump optimization
// Boiler(0x08) -> Me(0x0B), ?(0xBB), data: 00 00 00 00 00 00 00 00 00 00 00 FF 02 0F 1E 0B 1A 00 14 03
void Boiler::process_HybridHp(std::shared_ptr<const Telegram> telegram) {
has_enumupdate(telegram, hybridStrategy_, 12, 1); // cost = 2, temperature = 3, mix = 4
has_update(telegram, switchOverTemp_, 13); // full degrees
has_update(telegram, energyCostRatio_, 14); // is *10
has_update(telegram, fossileFactor_, 15); // is * 10
has_update(telegram, electricFactor_, 16); // is * 10
has_update(telegram, delayBoiler_, 18); // minutes
has_update(telegram, tempDiffBoiler_, 19); // relative degrees
}
*/
/*
* Settings
*/
/*
bool Boiler::set_hybridStrategy(const char * value, const int8_t id) {
uint8_t v;
if (!Helpers::value2enum(value, v, FL_(enum_hybridStrategy))) {
return false;
}
write_command(0xBB, 12, v + 1, 0xBB);
return true;
}
bool Boiler::set_switchOverTemp(const char * value, const int8_t id) {
int v;
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0xBB, 13, v, 0xBB);
return true;
}
bool Boiler::set_energyCostRatio(const char * value, const int8_t id) {
float v;
if (!Helpers::value2float(value, v)) {
return false;
}
write_command(0xBB, 14, (uint8_t)(v * 10), 0xBB);
return true;
}
bool Boiler::set_fossileFactor(const char * value, const int8_t id) {
float v;
if (!Helpers::value2float(value, v)) {
return false;
}
write_command(0xBB, 15, (uint8_t)(v * 10), 0xBB);
return true;
}
bool Boiler::set_electricFactor(const char * value, const int8_t id) {
float v;
if (!Helpers::value2float(value, v)) {
return false;
}
write_command(0xBB, 16, (uint8_t)(v * 10), 0xBB);
return true;
}
bool Boiler::set_delayBoiler(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(0xBB, 18, v, 0xBB);
return true;
}
bool Boiler::set_tempDiffBoiler(const char * value, const int8_t id) {
int v;
if (!Helpers::value2temperature(value, v, true)) {
return false;
}
write_command(0xBB, 19, v, 0xBB);
return true;
}
*/
// Set the dhw temperature 0x33/0x35 or 0xEA
bool Boiler::set_ww_temp(const char * value, const int8_t id) {
@@ -1475,12 +1634,12 @@ bool Boiler::set_reset(const char * value, const int8_t id) {
if (num == 1) {
// LOG_INFO(F("Reset boiler maintenance message"));
write_command(0x05, 0x08, 0xFF, 0x1C);
has_update(reset_);
has_update(&reset_);
return true;
} else if (num == 2) {
// LOG_INFO(F("Reset boiler error message"));
write_command(0x05, 0x00, 0x5A); // error reset
has_update(reset_);
has_update(&reset_);
return true;
}
return false;

View File

@@ -125,9 +125,10 @@ class Boiler : public EMSdevice {
uint8_t setBurnPow_; // max output power in %
uint32_t burnStarts_; // burner restarts
uint32_t burnWorkMin_; // Total burner operating time
uint32_t burn2WorkMin_; // burner stage 2 operating time
uint32_t heatWorkMin_; // Total heat operating time
uint32_t UBAuptime_; // Total UBA working hours
char lastCode_[75]; // last error code
char lastCode_[50]; // last error code
char serviceCode_[4]; // 3 character status/service code
uint16_t serviceCodeNumber_; // error/service code
@@ -192,6 +193,17 @@ class Boiler : public EMSdevice {
// Pool unit
int8_t poolSetTemp_;
/*
// HybridHP
uint8_t hybridStrategy_; // cost = 2, temperature = 3, mix = 4
int8_t switchOverTemp_; // degrees
uint8_t energyCostRatio_; // is *10
uint8_t fossileFactor_; // is * 10
uint8_t electricFactor_; // is * 10
uint8_t delayBoiler_; // minutes
uint8_t tempDiffBoiler_; // relative temperature degrees
*/
void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram);
@@ -209,6 +221,7 @@ class Boiler : public EMSdevice {
void process_MC110Status(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram);
void process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram);
void process_ErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_UBAErrorMessage2(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorWWPlus(std::shared_ptr<const Telegram> telegram);
@@ -219,6 +232,7 @@ class Boiler : public EMSdevice {
void process_HpPower(std::shared_ptr<const Telegram> telegram);
void process_HpOutdoor(std::shared_ptr<const Telegram> telegram);
void process_HpPool(std::shared_ptr<const Telegram> telegram);
void process_HybridHp(std::shared_ptr<const Telegram> telegram);
// commands - none of these use the additional id parameter
bool set_ww_mode(const char * value, const int8_t id);
@@ -254,6 +268,15 @@ class Boiler : public EMSdevice {
bool set_ww_hyst_on(const char * value, const int8_t id);
bool set_ww_hyst_off(const char * value, const int8_t id);
bool set_pool_temp(const char * value, const int8_t id);
/*
bool set_hybridStrategy(const char * value, const int8_t id);
bool set_switchOverTemp(const char * value, const int8_t id);
bool set_energyCostRatio(const char * value, const int8_t id);
bool set_fossileFactor(const char * value, const int8_t id);
bool set_electricFactor(const char * value, const int8_t id);
bool set_delayBoiler(const char * value, const int8_t id);
bool set_tempDiffBoiler(const char * value, const int8_t id);
*/
};
} // namespace emsesp

View File

@@ -24,6 +24,28 @@ REGISTER_FACTORY(Controller, EMSdevice::DeviceType::CONTROLLER);
Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
// IVT broadcasts Thermostat time from controller (0x09) if display is off.
register_telegram_type(0x06, F("RCTime"), false, MAKE_PF_CB(process_dateTime));
register_device_value(DeviceValueTAG::TAG_NONE, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE);
}
// process_dateTime - type 0x06 - date and time from a thermostat - 14 bytes long, IVT only
void Controller::process_dateTime(std::shared_ptr<const Telegram> telegram) {
if (telegram->offset > 0 || telegram->message_length < 5) {
return;
}
char newdatetime[sizeof(dateTime_)];
// publich as dd.mm.yyyy hh:mmF
snprintf(newdatetime,
sizeof(dateTime_),
"%02d.%02d.%04d %02d:%02d",
telegram->message_data[3],
telegram->message_data[1] - 1,
(telegram->message_data[0] & 0x7F) + 2000,
telegram->message_data[2],
telegram->message_data[4]);
has_update(dateTime_, newdatetime, sizeof(dateTime_));
}
} // namespace emsesp

View File

@@ -26,6 +26,10 @@ namespace emsesp {
class Controller : public EMSdevice {
public:
Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const std::string & name, uint8_t flags, uint8_t brand);
void process_dateTime(std::shared_ptr<const Telegram> telegram);
char dateTime_[25];
};
} // namespace emsesp

View File

@@ -99,6 +99,39 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
// HT3
if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
if (device_id >= 0x40) { // special DHW pos 10
register_telegram_type(0x34, F("UBAMonitorWW"), false, MAKE_PF_CB(process_IPMMonitorWW));
register_telegram_type(0x1E, F("HydrTemp"), false, MAKE_PF_CB(process_IPMHydrTemp));
register_telegram_type(0x33, F("UBAParameterWW"), true, MAKE_PF_CB(process_IPMParameterWW));
// register_telegram_type(0x10D, F("wwNTCStatus"), false, MAKE_PF_CB(process_wwNTCStatus));
type_ = Type::WWC;
hc_ = device_id - 0x40 + 1;
uint8_t tag = DeviceValueTAG::TAG_WWC9 + hc_ - 1;
register_device_value(tag, &wwSelTemp_, DeviceValueType::UINT, nullptr, FL_(wwSelTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwSelTemp));
register_device_value(tag, &wwCurTemp_1_, DeviceValueType::USHORT, FL_(div10), FL_(wwCurTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &wwCurTemp_2_, DeviceValueType::USHORT, FL_(div10), FL_(wwCurTemp2), DeviceValueUOM::DEGREES);
register_device_value(tag, &HydrTemp_, DeviceValueType::USHORT, FL_(div10), FL_(hydrTemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, FL_(pumpStatus), DeviceValueUOM::NONE);
register_device_value(
tag, &wwFlowTempOffset_, DeviceValueType::UINT, nullptr, FL_(wwFlowTempOffset), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwFlowTempOffset));
register_device_value(tag, &wwHystOn_, DeviceValueType::INT, nullptr, FL_(wwHystOn), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwHystOn));
register_device_value(tag, &wwHystOff_, DeviceValueType::INT, nullptr, FL_(wwHystOff), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_wwHystOff));
register_device_value(tag,
&wwDisinfectionTemp_,
DeviceValueType::UINT,
nullptr,
FL_(wwDisinfectionTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_wwDisinfectionTemp));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW,
&wwCircPump_,
DeviceValueType::BOOL,
nullptr,
FL_(wwCircPump),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_wwCircPump));
register_device_value(tag, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode), FL_(wwCircMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwCircMode));
} else {
register_telegram_type(0x010C, F("IPMStatusMessage"), false, MAKE_PF_CB(process_IPMStatusMessage));
register_telegram_type(0x011E, F("IPMTempMessage"), false, MAKE_PF_CB(process_IPMTempMessage));
// register_telegram_type(0x0123, F("IPMSetMessage"), false, MAKE_PF_CB(process_IPMSetMessage));
@@ -112,6 +145,7 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
register_device_value(tag, &flowTempVf_, DeviceValueType::USHORT, FL_(div10), FL_(flowTempVf), DeviceValueUOM::DEGREES);
}
}
}
// heating circuits 0x02D7, 0x02D8 etc...
// e.g. A0 00 FF 00 01 D7 00 00 00 80 00 00 00 00 03 C5
@@ -203,6 +237,35 @@ void Mixer::process_MMPLUSConfigMessage_WWC(std::shared_ptr<const Telegram> tele
has_update(telegram, wwMaxTemp_, 10);
}
// 0x34 only 8 bytes long
// Mixer(0x41) -> All(0x00), UBAMonitorWW(0x34), data: 37 02 1E 02 1E 00 00 00 00
void Mixer::process_IPMMonitorWW(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, wwSelTemp_, 0);
has_update(telegram, wwCurTemp_1_, 1);
has_update(telegram, wwCurTemp_2_, 3);
has_bitupdate(telegram, pumpStatus_, 5, 3);
}
// Mixer(0x41) -> Me(0x0B), UBAParameterWW(0x33), data: 08 FF 46 FB FF 28 FF 07 46 00 FF 00
void Mixer::process_IPMParameterWW(std::shared_ptr<const Telegram> telegram) {
// has_update(telegram, wwActivated_, 1); // 0xFF means on
// has_update(telegram, wwSelTemp_, 2);
has_update(telegram, wwHystOn_, 3); // Hyst on (default -5)
has_update(telegram, wwHystOff_, 4); // Hyst off (default -1)
has_update(telegram, wwFlowTempOffset_, 5); // default 40
has_update(telegram, wwCircPump_, 6); // 0xFF means on
has_update(telegram, wwCircMode_, 7); // 1=1x3min 6=6x3min 7=continuous
has_update(telegram, wwDisinfectionTemp_, 8);
// has_bitupdate(telegram, wwChargeType_, 10, 0); // 0 = charge pump, 0xff = 3-way valve
}
// 0x1E, only16 bit temperature
// Mixer(0x41) -> Boiler(0x08), HydrTemp(0x1E), data: 01 D8
void Mixer::process_IPMHydrTemp(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, HydrTemp_, 0);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
@@ -241,6 +304,15 @@ void Mixer::process_IPMSetMessage(std::shared_ptr<const Telegram> telegram) {
#pragma GCC diagnostic pop
bool Mixer::set_wwSelTemp(const char * value, const int8_t id) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x35, 3, (uint8_t)temperature, 0x34);
return true;
}
bool Mixer::set_flowSetTemp(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v)) {
@@ -349,34 +421,72 @@ bool Mixer::set_wwRequiredTemp(const char * value, const int8_t id) {
}
bool Mixer::set_wwDisinfectionTemp(const char * value, const int8_t id) {
uint8_t wwc = device_id() - 0x28;
float v = 0;
if (!Helpers::value2temperature(value, v)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x33, 8, (uint8_t)v, 0x33);
} else {
uint8_t wwc = device_id() - 0x28;
write_command(0x313 + wwc, 9, (uint8_t)v, 0x313 + wwc);
}
return true;
}
bool Mixer::set_wwCircPump(const char * value, const int8_t id) {
uint8_t wwc = device_id() - 0x28;
bool v = false;
if (!Helpers::value2bool(value, v)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x33, 6, v ? 0xFF : 0x00, 0x33);
} else {
uint8_t wwc = device_id() - 0x28;
write_command(0x33B + wwc, 0, v ? 0x01 : 0x00, 0x33B + wwc);
}
return true;
}
bool Mixer::set_wwCircMode(const char * value, const int8_t id) {
uint8_t wwc = device_id() - 0x28;
uint8_t n;
if (!Helpers::value2enum(value, n, FL_(enum_wwCircMode))) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_IPM) {
write_command(0x33, 7, n, 0x33);
} else {
uint8_t wwc = device_id() - 0x28;
write_command(0x313 + wwc, 0, n, 0x313 + wwc);
}
return true;
}
bool Mixer::set_wwFlowTempOffset(const char * value, const int8_t id) {
int n;
if (!Helpers::value2number(value, n)) {
return false;
}
write_command(0x33, 5, n, 0x33);
return true;
}
bool Mixer::set_wwHystOn(const char * value, const int8_t id) {
int n;
if (!Helpers::value2number(value, n)) {
return false;
}
write_command(0x33, 3, n, 0x33);
return true;
}
bool Mixer::set_wwHystOff(const char * value, const int8_t id) {
int n;
if (!Helpers::value2number(value, n)) {
return false;
}
write_command(0x33, 4, n, 0x33);
return true;
}
} // namespace emsesp

View File

@@ -43,6 +43,10 @@ class Mixer : public EMSdevice {
void process_MMSetMessage(std::shared_ptr<const Telegram> telegram);
void process_HpPoolStatus(std::shared_ptr<const Telegram> telegram);
void process_IPMMonitorWW(std::shared_ptr<const Telegram> telegram);
void process_IPMHydrTemp(std::shared_ptr<const Telegram> telegram);
void process_IPMParameterWW(std::shared_ptr<const Telegram> telegram);
bool set_flowSetTemp(const char * value, const int8_t id);
bool set_pump(const char * value, const int8_t id);
bool set_activated(const char * value, const int8_t id);
@@ -56,6 +60,10 @@ class Mixer : public EMSdevice {
bool set_wwCircPump(const char * value, const int8_t id);
bool set_wwCircMode(const char * value, const int8_t id);
bool set_wwSelTemp(const char * value, const int8_t id);
bool set_wwFlowTempOffset(const char * value, const int8_t id);
bool set_wwHystOn(const char * value, const int8_t id);
bool set_wwHystOff(const char * value, const int8_t id);
enum class Type {
NONE,
@@ -90,6 +98,14 @@ class Mixer : public EMSdevice {
Type type_ = Type::NONE;
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET;
uint8_t poolShuntStatus__ = EMS_VALUE_UINT_NOTSET; // temp value
uint8_t wwSelTemp_;
uint16_t wwCurTemp_1_;
uint16_t wwCurTemp_2_;
uint16_t HydrTemp_;
int8_t wwHystOn_; // Hyst on (default -5)
int8_t wwHystOff_; // Hyst off (default -1)
uint8_t wwFlowTempOffset_; // default 40
};
} // namespace emsesp

View File

@@ -34,7 +34,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
}
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM100) {
if (device_id == 0x2A) {
if (device_id == 0x2A) { // SM100 DHW
register_telegram_type(0x07D6, F("SM100wwTemperature"), false, MAKE_PF_CB(process_SM100wwTemperature));
register_telegram_type(0x07AA, F("SM100wwStatus"), false, MAKE_PF_CB(process_SM100wwStatus));
register_telegram_type(0x07AB, F("SM100wwCommand"), false, MAKE_PF_CB(process_SM100wwCommand));
@@ -65,11 +65,11 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, MAKE_PF_CB(process_ISM1StatusMessage));
register_telegram_type(0x0101, F("ISM1Set"), true, MAKE_PF_CB(process_ISM1Set));
register_telegram_type(0x0104, F("ISM2StatusMessage"), false, MAKE_PF_CB(process_ISM2StatusMessage));
}
// device values...
// special case for a device_id with 0x2A where it's not actual a solar module
// special case for a SM100 DHW device_id with 0x2A where it's not actual a solar module
if (device_id == 0x2A) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwTemp_1_, DeviceValueType::USHORT, FL_(div10), FL_(wwTemp1), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwTemp_3_, DeviceValueType::USHORT, FL_(div10), FL_(wwTemp3), DeviceValueUOM::DEGREES);
@@ -84,8 +84,13 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
FL_(wwMaxTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_wwMaxTemp));
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA_WW, &wwTemp_, DeviceValueType::UINT, nullptr, FL_(wwTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_wwTemp));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW,
&wwSelTemp_,
DeviceValueType::UINT,
nullptr,
FL_(wwSelTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_wwSelTemp));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA_WW,
&wwRedTemp_,
DeviceValueType::UINT,
@@ -128,6 +133,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
return;
}
// common solar values for all modules (except dhw)
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &collectorTemp_, DeviceValueType::SHORT, FL_(div10), FL_(collectorTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &cylBottomTemp_, DeviceValueType::SHORT, FL_(div10), FL_(cylBottomTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &solarPump_, DeviceValueType::BOOL, nullptr, FL_(solarPump), DeviceValueUOM::NONE);
@@ -137,6 +143,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &collectorShutdown_, DeviceValueType::BOOL, nullptr, FL_(collectorShutdown), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &cylHeated_, DeviceValueType::BOOL, nullptr, FL_(cylHeated), DeviceValueUOM::NONE);
// values per device flag
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &solarPumpMod_, DeviceValueType::UINT, nullptr, FL_(solarPumpMod), DeviceValueUOM::PERCENT);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
@@ -191,6 +198,9 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
*/
}
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &cylMiddleTemp_, DeviceValueType::SHORT, FL_(div10), FL_(cylMiddleTemp), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &retHeatAssist_, DeviceValueType::SHORT, FL_(div10), FL_(retHeatAssist), DeviceValueUOM::DEGREES);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &m1Valve_, DeviceValueType::BOOL, nullptr, FL_(m1Valve), DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &energyLastHour_, DeviceValueType::ULONG, FL_(div10), FL_(energyLastHour), DeviceValueUOM::WH);
}
@@ -608,7 +618,7 @@ void Solar::process_SM100wwStatus(std::shared_ptr<const Telegram> telegram) {
// data: FF 05 0F 5F 00 01 3C 3C 3C 3C 28 12 46 01 3C 1E 03 07 3C 00 0F 00 05
void Solar::process_SM100wwParam(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, wwMaxTemp_, 8);
has_update(telegram, wwTemp_, 9);
has_update(telegram, wwSelTemp_, 9);
has_update(telegram, wwRedTemp_, 10);
has_update(telegram, wwDailyTemp_, 6);
has_update(telegram, wwDisinfectionTemp_, 12);
@@ -788,6 +798,18 @@ void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram)
has_bitupdate(telegram, cylHeated_, 9, 2); // cyl full
}
/*
* Junkers ISM12 Solar Module - type 0x0104 EMS+ for heat assist
* ?(0x103), data: 00 00 00 00 00 7A 01 15 00 00 05 37 F0
* ?(0x104), data: 01 A9 01 22 27 0F 27 0F 27 0F 27 0F 27 0F 27 0F
* ?(0x104), data: 01 01 00 00 00 00 00 27 0F 27 0F (offset 16)
*/
void Solar::process_ISM2StatusMessage(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, cylMiddleTemp_, 0); // Temperature Middle of Solar Boiler cyl
has_update(telegram, retHeatAssist_, 2); // return temperature from heating T4
has_bitupdate(telegram, m1Valve_, 17, 0); // return valve DUW1 (also 16,0)
}
/*
* Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values
*/
@@ -943,54 +965,54 @@ bool Solar::set_SM10MaxFlow(const char * value, const int8_t id) {
// switch heat transfer system on/off
bool Solar::set_heatTransferSystem(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 5, v ? 0x01 : 0x00, 0x358);
write_command(0x358, 5, b ? 0x01 : 0x00, 0x358);
return true;
}
// switch external cylinder on/off
bool Solar::set_externalCyl(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 9, v ? 0x01 : 0x00, 0x358);
write_command(0x358, 9, b ? 0x01 : 0x00, 0x358);
return true;
}
// switch thermal disinfection on/off
bool Solar::set_thermalDisinfect(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 10, v ? 0x01 : 0x00, 0x358);
write_command(0x358, 10, b ? 0x01 : 0x00, 0x358);
return true;
}
// switch heat metering on/off
bool Solar::set_heatMetering(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x358, 14, v ? 0x01 : 0x00, 0x358);
write_command(0x358, 14, b ? 0x01 : 0x00, 0x358);
return true;
}
// switch solar system on/off
bool Solar::set_solarEnabled(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
if (flags() == EMSdevice::EMS_DEVICE_FLAG_SM10) {
write_command(0x96, 0, v ? 0xFF : 0x00, 0x96);
write_command(0x96, 0, b ? 0xFF : 0x00, 0x96);
} else {
write_command(0x358, 19, v ? 0x01 : 0x00, 0x358);
write_command(0x358, 19, b ? 0x01 : 0x00, 0x358);
}
return true;
}
@@ -1018,71 +1040,71 @@ bool Solar::set_solarMode2(const char * value, const int8_t id) {
// switch pumpkick on/off
bool Solar::set_solarPumpKick(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35A, 9, v ? 0x01 : 0x00, 0x35A);
write_command(0x35A, 9, b ? 0x01 : 0x00, 0x35A);
return true;
}
// switch pump2kick on/off
bool Solar::set_solarPump2Kick(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35D, 0, v ? 0x01 : 0x00, 0x35D);
write_command(0x35D, 0, b ? 0x01 : 0x00, 0x35D);
return true;
}
// switch plain water mode on/off
bool Solar::set_plainWaterMode(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35A, 10, v ? 0x01 : 0x00, 0x35A);
write_command(0x35A, 10, b ? 0x01 : 0x00, 0x35A);
return true;
}
// switch double match flow on/off
bool Solar::set_doubleMatchFlow(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x35A, 11, v ? 0x01 : 0x00, 0x35A);
write_command(0x35A, 11, b ? 0x01 : 0x00, 0x35A);
return true;
}
// set climate zone number
bool Solar::set_climateZone(const char * value, const int8_t id) {
int v = 0;
if (!Helpers::value2number(value, v)) {
int zone;
if (!Helpers::value2number(value, zone)) {
return false;
}
write_command(0x380, 0, v, 0x380);
write_command(0x380, 0, zone, 0x380);
return true;
}
// collector area in squaremeters
bool Solar::set_collector1Area(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2float(value, v)) {
float area;
if (!Helpers::value2float(value, area)) {
return false;
}
write_command(0x380, 3, (uint16_t)(v * 10), 0x380);
write_command(0x380, 3, (uint16_t)(area * 10), 0x380);
return true;
}
// collector area in squaremeters
bool Solar::set_collector2Area(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2float(value, v)) {
float area;
if (!Helpers::value2float(value, area)) {
return false;
}
write_command(0x380, 6, (uint16_t)(v * 10), 0x380);
write_command(0x380, 6, (uint16_t)(area * 10), 0x380);
return true;
}
@@ -1108,101 +1130,101 @@ bool Solar::set_collector2Type(const char * value, const int8_t id) {
// priority of cylinders if there are 2
bool Solar::set_cylPriority(const char * value, const int8_t id) {
uint8_t n;
if (!Helpers::value2enum(value, n, FL_(enum_cylprio))) {
uint8_t num;
if (!Helpers::value2enum(value, num, FL_(enum_cylprio))) {
return false;
}
write_command(0x35F, 3, n, 0x35F);
write_command(0x35F, 3, num, 0x35F);
return true;
}
bool Solar::set_heatAssist(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
float temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x35C, 0, (uint8_t)(v * 10), 0x35C);
write_command(0x35C, 0, (uint8_t)(temperature * 10), 0x35C);
return true;
}
bool Solar::set_diffControl(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
float temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x361, 4, (uint8_t)(v * 10), 0x361);
write_command(0x361, 4, (uint8_t)(temperature * 10), 0x361);
return true;
}
bool Solar::set_wwTemp(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
bool Solar::set_wwSelTemp(const char * value, const int8_t id) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 9, (uint8_t)v, 0x7A6);
write_command(0x7A6, 9, (uint8_t)temperature, 0x7A6);
return true;
}
bool Solar::set_wwMaxTemp(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 8, (uint8_t)v, 0x7A6);
write_command(0x7A6, 8, (uint8_t)temperature, 0x7A6);
return true;
}
bool Solar::set_wwRedTemp(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 10, (uint8_t)v, 0x7A6);
write_command(0x7A6, 10, (uint8_t)temperature, 0x7A6);
return true;
}
bool Solar::set_wwDailyTemp(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 6, (uint8_t)v, 0x7A6);
write_command(0x7A6, 6, (uint8_t)temperature, 0x7A6);
return true;
}
bool Solar::set_wwDisinfectionTemp(const char * value, const int8_t id) {
float v = 0;
if (!Helpers::value2temperature(value, v)) {
int temperature;
if (!Helpers::value2temperature(value, temperature)) {
return false;
}
write_command(0x7A6, 12, (uint8_t)v, 0x7A6);
write_command(0x7A6, 12, (uint8_t)temperature, 0x7A6);
return true;
}
bool Solar::set_wwCirc(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x7A5, 0, v ? 0xFF : 0x00, 0x7A5);
write_command(0x7A5, 0, b ? 0xFF : 0x00, 0x7A5);
return true;
}
bool Solar::set_wwCircMode(const char * value, const int8_t id) {
uint8_t n;
if (!Helpers::value2enum(value, n, FL_(enum_wwCircMode))) {
uint8_t num;
if (!Helpers::value2enum(value, num, FL_(enum_wwCircMode))) {
return false;
}
write_command(0x7A5, 3, n, 0x7A5);
write_command(0x7A5, 3, num, 0x7A5);
return true;
}
bool Solar::set_wwKeepWarm(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
bool b;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(0x7AE, 0, v ? 0xFF : 0x00, 0x7AE);
write_command(0x7AE, 0, b ? 0xFF : 0x00, 0x7AE);
return true;
}

View File

@@ -125,7 +125,7 @@ class Solar : public EMSdevice {
// SM100wwParam - 0x07A6
uint8_t wwMaxTemp_;
uint8_t wwTemp_;
uint8_t wwSelTemp_;
uint8_t wwRedTemp_;
uint8_t wwDailyTemp_;
uint8_t wwDisinfectionTemp_;
@@ -187,8 +187,9 @@ class Solar : public EMSdevice {
void process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram);
void process_ISM1Set(std::shared_ptr<const Telegram> telegram);
void process_ISM2StatusMessage(std::shared_ptr<const Telegram> telegram);
// settings
bool set_CollectorMaxTemp(const char * value, const int8_t id);
bool set_CollectorMinTemp(const char * value, const int8_t id);
bool set_cylMaxTemp(const char * value, const int8_t id);
@@ -224,7 +225,7 @@ class Solar : public EMSdevice {
bool set_heatAssist(const char * value, const int8_t id);
bool set_diffControl(const char * value, const int8_t id);
bool set_wwTemp(const char * value, const int8_t id);
bool set_wwSelTemp(const char * value, const int8_t id);
bool set_wwMaxTemp(const char * value, const int8_t id);
bool set_wwRedTemp(const char * value, const int8_t id);
bool set_wwCirc(const char * value, const int8_t id);

View File

@@ -146,6 +146,8 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(set_typeids[i], F("JunkersSet"), false, MAKE_PF_CB(process_JunkersSet));
}
}
register_telegram_type(0xBB, F("HybridSettings"), true, MAKE_PF_CB(process_JunkersHybridSettings));
register_telegram_type(0x23, F("JunkersSetMixer"), true, MAKE_PF_CB(process_JunkersSetMixer));
register_telegram_type(0x123, F("JunkersRemote"), false, MAKE_PF_CB(process_JunkersRemoteMonitor));
}
@@ -270,6 +272,11 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
toggle_ = true;
}
// not found, search device-id types for remote thermostats
if (telegram->dest >= 0x20 && telegram->dest <= 0x27) {
hc_num = telegram->dest - 0x20;
}
// still didn't recognize it, ignore it
if (hc_num == 0) {
return nullptr;
@@ -342,8 +349,8 @@ void Thermostat::add_ha_climate(std::shared_ptr<HeatingCircuit> hc) const {
return;
}
if (Helpers::hasValue(hc->selTemp) && is_visible(&hc->selTemp)) {
if (Helpers::hasValue(hc->roomTemp) && is_visible(&hc->roomTemp)) {
if (Helpers::hasValue(hc->selTemp) && is_readable(&hc->selTemp)) {
if (Helpers::hasValue(hc->roomTemp) && is_readable(&hc->roomTemp)) {
hc->climate = 1;
} else {
hc->climate = 0;
@@ -587,7 +594,7 @@ void Thermostat::process_RC20Timer(std::shared_ptr<const Telegram> telegram) {
char data[sizeof(hc->switchtime1)];
uint8_t no = telegram->offset / 2;
uint8_t day = telegram->message_data[0] >> 5;
uint8_t temp = telegram->message_data[0] & 1;
uint8_t temp = telegram->message_data[0] & 7;
uint8_t time = telegram->message_data[1];
std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]);
@@ -805,6 +812,27 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr<const Telegram> telegram
add_ha_climate(hc);
}
// 0xBB Heatpump optimization
// ?(0xBB), data: 00 00 00 00 00 00 00 00 00 00 00 FF 02 0F 1E 0B 1A 00 14 03
void Thermostat::process_JunkersHybridSettings(std::shared_ptr<const Telegram> telegram) {
has_enumupdate(telegram, hybridStrategy_, 12, 1); // cost = 2, temperature = 3, mix = 4
has_update(telegram, switchOverTemp_, 13); // full degrees
has_update(telegram, energyCostRatio_, 14); // is *10
has_update(telegram, fossileFactor_, 15); // is * 10
has_update(telegram, electricFactor_, 16); // is * 10
has_update(telegram, delayBoiler_, 18); // minutes
has_update(telegram, tempDiffBoiler_, 19); // relative degrees
}
void Thermostat::process_JunkersSetMixer(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
has_update(telegram, hc->targetflowtemp, 0);
}
// type 0x02A5 - data from Worchester CRF200
void Thermostat::process_CRFMonitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
@@ -1009,7 +1037,18 @@ void Thermostat::process_RC30Set(std::shared_ptr<const Telegram> telegram) {
if (hc == nullptr) {
return;
}
has_update(telegram, ibaLanguage_, 0); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
has_update(telegram, ibaCalIntTemperature_, 1); // offset int. temperature sensor, by * 0.1 Kelvin
has_update(telegram, autodst_, 2); // Automatic change Daylight Saving time: (0x00 = off, 0xFF = on)
has_update(telegram, ibaBuildingType_, 4); // building type: 0 = light, 1 = medium, 2 = heavy
has_update(telegram, ibaClockOffset_, 10); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s
has_update(telegram, backlight_, 12); // Keyboard lighting: (0x00 = off, 0xFF = on)
has_update(telegram, mixingvalves_, 17); // Number of Mixing Valves: (0x00=0, 0x01=1, 0x02=2)
has_update(telegram, brightness_, 18); // Screen brightness 0F=dark F1=light
has_update(telegram, hc->mode, 23);
has_update(telegram, offtemp_, 24); // Set Temperature when mode is Off / 10 (e.g.: 0x0F = 7.5 degrees Celsius)
has_update(telegram, heatingpid_, 25); // PID setting 00=1 01=2 02=3
has_update(telegram, preheating_, 26); // Preheating in the clock program: (0x00 = off, 0xFF = on)
}
// type 0x3E (HC1), 0x48 (HC2), 0x52 (HC3), 0x5C (HC4) - data from the RC35 thermostat (0x10) - 16 bytes
@@ -1065,12 +1104,16 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
has_update(telegram, hc->wwprio, 21); // 0xFF for on
has_update(telegram, hc->summertemp, 22); // is * 1
has_update(telegram, hc->nofrosttemp, 23); // is * 1
has_update(telegram, hc->nofrostmode, 28); // 0-off, 1-outdoor, 2-roomtemp 5°C
has_update(telegram, hc->flowtempoffset, 24); // is * 1, only in mixed circuits
has_update(telegram, hc->reducemode, 25); // 0-nofrost, 1-reduce, 2-roomhold, 3-outdoorhold
has_update(telegram, hc->control, 26); // 0-off, 1-RC20 (remote), 2-RC35
has_update(telegram, hc->controlmode, 33); // 0-outdoortemp, 1-roomtemp
has_update(telegram, hc->tempautotemp, 37);
has_update(telegram, hc->noreducetemp, 38); // outdoor temperature for no reduce
has_update(telegram, hc->reducetemp, 39); // temperature for off/reduce
has_update(telegram, hc->vacreducetemp, 40); // temperature for off/reduce in vacations
has_update(telegram, hc->vacreducemode, 41); // vacations reduce mode
has_update(telegram, hc->minflowtemp, 16);
if (hc->heatingtype == 3) { // floor heating
@@ -1164,32 +1207,49 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
return;
}
if (telegram->message_data[7] & 0x0C) { // date and time not valid
if ((telegram->message_data[7] & 0x0C) && has_command(&dateTime_)) { // date and time not valid
set_datetime("ntp", -1); // set from NTP
return;
}
// render date to HH:MM:SS DD/MM/YYYY
// had to create separate buffers because of how printf works
char date[25];
char buf1[6];
char buf2[6];
char buf3[6];
char buf4[6];
char buf5[6];
char buf6[6];
snprintf(date,
sizeof(date),
"%s:%s:%s %s.%s.%s",
Helpers::smallitoa(buf1, telegram->message_data[2]), // hour
Helpers::smallitoa(buf2, telegram->message_data[4]), // minute
Helpers::smallitoa(buf3, telegram->message_data[5]), // second
Helpers::smallitoa(buf4, telegram->message_data[3]), // day
Helpers::smallitoa(buf5, telegram->message_data[1]), // month
// IVT reports Year with high bit set.?
Helpers::itoa((telegram->message_data[0] & 0x7F) + 2000, buf6) // year
);
has_update(dateTime_, date, sizeof(dateTime_));
// check clock
time_t now = time(nullptr);
tm * tm_ = localtime(&now);
bool tset_ = tm_->tm_year > 110; // year 2010 and up, time is valid
tm_->tm_year = (telegram->message_data[0] & 0x7F) + 100; // IVT
tm_->tm_mon = telegram->message_data[1] - 1;
tm_->tm_mday = telegram->message_data[3];
tm_->tm_hour = telegram->message_data[2];
tm_->tm_min = telegram->message_data[4];
tm_->tm_sec = telegram->message_data[5];
tm_->tm_isdst = telegram->message_data[7] & 0x01;
// render date to DD.MM.YYYY HH:MM and publish
char newdatetime[sizeof(dateTime_)];
strftime(newdatetime, sizeof(dateTime_), "%d.%m.%G %H:%M", tm_);
has_update(dateTime_, newdatetime, sizeof(dateTime_));
bool ivtclock = (telegram->message_data[0] & 0x80) == 0x80; // dont sync ivt-clock, #439
time_t ttime = mktime(tm_); // thermostat time
// correct thermostat clock if we have valid ntp time, and could write the command
if (!ivtclock && tset_ && EMSESP::system_.ntp_connected() && !EMSESP::system_.readonly_mode() && has_command(&dateTime_)) {
double difference = difftime(now, ttime);
if (difference > 15 || difference < -15) {
set_datetime("ntp", -1); // set from NTP
LOG_INFO(F("thermostat time correction from ntp"));
}
}
#ifndef EMSESP_STANDALONE
if (!tset_ && tm_->tm_year > 110) { // emsesp clock not set, but thermostat clock
if (ivtclock) {
tm_->tm_isdst = -1; // determine dst
ttime = mktime(tm_); // thermostat time
}
struct timeval newnow = {.tv_sec = ttime};
settimeofday(&newnow, nullptr);
LOG_INFO(F("ems-esp time set from thermostat"));
}
#endif
}
// process_RCError - type 0xA2 - error message - 14 bytes long
@@ -1237,6 +1297,76 @@ void Thermostat::process_RCErrorMessage(std::shared_ptr<const Telegram> telegram
}
}
/*
*
* *** settings ***
*
*/
// 0xBB Hybrid pump
bool Thermostat::set_hybridStrategy(const char * value, const int8_t id) {
uint8_t v;
if (!Helpers::value2enum(value, v, FL_(enum_hybridStrategy))) {
return false;
}
write_command(0xBB, 12, v + 1, 0xBB);
return true;
}
bool Thermostat::set_switchOverTemp(const char * value, const int8_t id) {
int v;
if (!Helpers::value2temperature(value, v)) {
return false;
}
write_command(0xBB, 13, v, 0xBB);
return true;
}
bool Thermostat::set_energyCostRatio(const char * value, const int8_t id) {
float v;
if (!Helpers::value2float(value, v)) {
return false;
}
write_command(0xBB, 14, (uint8_t)(v * 10), 0xBB);
return true;
}
bool Thermostat::set_fossileFactor(const char * value, const int8_t id) {
float v;
if (!Helpers::value2float(value, v)) {
return false;
}
write_command(0xBB, 15, (uint8_t)(v * 10), 0xBB);
return true;
}
bool Thermostat::set_electricFactor(const char * value, const int8_t id) {
float v;
if (!Helpers::value2float(value, v)) {
return false;
}
write_command(0xBB, 16, (uint8_t)(v * 10), 0xBB);
return true;
}
bool Thermostat::set_delayBoiler(const char * value, const int8_t id) {
int v;
if (!Helpers::value2number(value, v)) {
return false;
}
write_command(0xBB, 18, v, 0xBB);
return true;
}
bool Thermostat::set_tempDiffBoiler(const char * value, const int8_t id) {
int v;
if (!Helpers::value2temperature(value, v, true)) {
return false;
}
write_command(0xBB, 19, v, 0xBB);
return true;
}
// 0xA5 - Set minimum external temperature
bool Thermostat::set_minexttemp(const char * value, const int8_t id) {
int mt = 0;
@@ -1255,19 +1385,23 @@ bool Thermostat::set_minexttemp(const char * value, const int8_t id) {
return true;
}
// 0xA5 - Clock offset
// 0xA5/0xA7 - Clock offset
bool Thermostat::set_clockoffset(const char * value, const int8_t id) {
int co = 0;
if (!Helpers::value2number(value, co)) {
return false;
}
if (model() == EMS_DEVICE_FLAG_RC30) {
write_command(EMS_TYPE_RC30Settings, 10, co, EMS_TYPE_RC30Settings);
} else {
write_command(EMS_TYPE_IBASettings, 12, co, EMS_TYPE_IBASettings);
}
return true;
}
// 0xA5 - Calibrate internal temperature
// 0xA5/0xA7 - Calibrate internal temperature
bool Thermostat::set_calinttemp(const char * value, const int8_t id) {
float ct = 0;
if (!Helpers::value2temperature(value, ct, true)) {
@@ -1279,6 +1413,8 @@ bool Thermostat::set_calinttemp(const char * value, const int8_t id) {
if (model() == EMS_DEVICE_FLAG_RC10) {
write_command(0xB0, 0, t, 0xB0);
} else if (model() == EMS_DEVICE_FLAG_RC30) {
write_command(EMS_TYPE_RC30Settings, 1, t, EMS_TYPE_RC30Settings);
} else {
write_command(EMS_TYPE_IBASettings, 2, t, EMS_TYPE_IBASettings);
}
@@ -1298,6 +1434,18 @@ bool Thermostat::set_display(const char * value, const int8_t id) {
return true;
}
// 0xA7 - Set Screen brightness
bool Thermostat::set_brightness(const char * value, const int8_t id) {
int bo = 0;
if (!Helpers::value2number(value, bo, -15, 15)) {
return false;
}
write_command(EMS_TYPE_RC30Settings, 18, bo, EMS_TYPE_RC30Settings);
return true;
}
bool Thermostat::set_remotetemp(const char * value, const int8_t id) {
float f = 0;
if (!Helpers::value2temperature(value, f)) {
@@ -1321,7 +1469,7 @@ bool Thermostat::set_remotetemp(const char * value, const int8_t id) {
return true;
}
// 0xA5 - Set the building settings
// 0xA5/0xA7 - Set the building settings
bool Thermostat::set_building(const char * value, const int8_t id) {
uint8_t bd = 0;
if (!Helpers::value2enum(value, bd, FL_(enum_ibaBuildingType))) {
@@ -1330,6 +1478,8 @@ bool Thermostat::set_building(const char * value, const int8_t id) {
if ((model() == EMS_DEVICE_FLAG_RC300) || (model() == EMS_DEVICE_FLAG_RC100)) {
write_command(0x240, 9, bd + 1, 0x240);
} else if (model() == EMS_DEVICE_FLAG_RC30) {
write_command(EMS_TYPE_RC30Settings, 4, bd, EMS_TYPE_RC30Settings);
} else {
write_command(EMS_TYPE_IBASettings, 6, bd, EMS_TYPE_IBASettings);
}
@@ -1337,7 +1487,7 @@ bool Thermostat::set_building(const char * value, const int8_t id) {
return true;
}
// 0xB0 - Set RC10 heating pid
// 0xB0/0xA7 - Set RC10 heating pid
bool Thermostat::set_heatingpid(const char * value, const int8_t id) {
uint8_t pid = 0;
if (!Helpers::value2enum(value, pid, FL_(enum_PID))) {
@@ -1346,12 +1496,14 @@ bool Thermostat::set_heatingpid(const char * value, const int8_t id) {
if (model() == EMS_DEVICE_FLAG_RC10) {
write_command(0xB0, 6, pid, 0xB0);
} else if (model() == EMS_DEVICE_FLAG_RC30) {
write_command(EMS_TYPE_RC30Settings, 25, pid, EMS_TYPE_RC30Settings);
}
return true;
}
// 0xA5 - Set the building settings
// 0xA5 - Set the damping settings
bool Thermostat::set_damping(const char * value, const int8_t id) {
bool dmp;
if (Helpers::value2bool(value, dmp)) {
@@ -1362,14 +1514,21 @@ bool Thermostat::set_damping(const char * value, const int8_t id) {
return false;
}
// 0xA5 Set the language settings
// 0xA5/0xA7 Set the language settings
bool Thermostat::set_language(const char * value, const int8_t id) {
uint8_t lg = 0;
if (model() == EMS_DEVICE_FLAG_RC30) {
if (!Helpers::value2enum(value, lg, FL_(enum_ibaLanguage_RC30))) {
return false;
}
write_command(EMS_TYPE_RC30Settings, 0, lg, EMS_TYPE_RC30Settings);
} else {
if (!Helpers::value2enum(value, lg, FL_(enum_ibaLanguage))) {
return false;
}
write_command(EMS_TYPE_IBASettings, 1, lg, EMS_TYPE_IBASettings);
}
return true;
}
@@ -1389,7 +1548,7 @@ bool Thermostat::set_control(const char * value, const int8_t id) {
return true;
}
} else if (Helpers::value2enum(value, ctrl, FL_(enum_control))) {
write_command(set_typeids[hc->hc()], 26, ctrl);
write_command(set_typeids[hc->hc()], EMS_OFFSET_RC35Set_control, ctrl);
return true;
}
@@ -1602,14 +1761,63 @@ bool Thermostat::set_wwOneTimeKey(const char * value, const int8_t id) {
return true;
}
// only RC10, 0xB0
// for RC10, 0xB0 or RC30, 0xA7
bool Thermostat::set_backlight(const char * value, const int8_t id) {
bool b = false;
if (!Helpers::value2bool(value, b)) {
return false;
}
if (model() == EMS_DEVICE_FLAG_RC30) {
write_command(EMS_TYPE_RC30Settings, 12, b ? 0xFF : 0x00, EMS_TYPE_RC30Settings);
} else {
write_command(0xB0, 1, b ? 0xFF : 0x00, 0xB0);
}
return true;
}
bool Thermostat::set_autodst(const char * value, const int8_t id) {
bool b = false;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(EMS_TYPE_RC30Settings, 2, b ? 0xFF : 0x00, EMS_TYPE_RC30Settings);
return true;
}
bool Thermostat::set_preheating(const char * value, const int8_t id) {
bool b = false;
if (!Helpers::value2bool(value, b)) {
return false;
}
write_command(EMS_TYPE_RC30Settings, 26, b ? 0xFF : 0x00, EMS_TYPE_RC30Settings);
return true;
}
bool Thermostat::set_offtemp(const char * value, const int8_t id) {
int ot = 0;
if (!Helpers::value2temperature(value, ot, true)) {
return false;
}
auto t = (int8_t)(ot * 2);
write_command(EMS_TYPE_RC30Settings, 24, t, EMS_TYPE_RC30Settings);
return true;
}
bool Thermostat::set_mixingvalves(const char * value, const int8_t id) {
int m = 0;
if (!Helpers::value2number(value, m, 0, 2)) {
return false;
}
write_command(EMS_TYPE_RC30Settings, 17, m, EMS_TYPE_RC30Settings);
return true;
}
@@ -1715,7 +1923,7 @@ bool Thermostat::set_party(const char * value, const int8_t id) {
return true;
}
// set date&time as string hh:mm:ss-dd.mm.yyyy-dw-dst or "NTP" for setting to internet-time
// set date&time as string dd.mm.yyyy-hh:mm:ss-dw-dst or "NTP" for setting to internet-time
// dw - day of week (0..6), dst- summertime (0/1)
// id is ignored
bool Thermostat::set_datetime(const char * value, const int8_t id) {
@@ -1728,9 +1936,12 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
if (dt == "ntp") {
time_t now = time(nullptr);
tm * tm_ = localtime(&now);
if (tm_->tm_year < 110) { // no NTP time
if (tm_->tm_year < 110) { // no valid time
return false;
}
if (!EMSESP::system_.ntp_connected()) {
LOG_WARNING(F("Set date: no valid NTP data, setting from ESP Clock"));
}
data[0] = tm_->tm_year - 100; // Bosch counts from 2000
data[1] = tm_->tm_mon + 1;
@@ -1740,25 +1951,20 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
data[5] = tm_->tm_sec;
data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su
data[7] = tm_->tm_isdst + 2; // set DST and flag for ext. clock
// char time_string[25];
// strftime(time_string, 25, "%FT%T%z", tm_);
// LOG_INFO(F("Date and time: %s"), time_string);
} else if (dt.length() == 23) {
data[0] = (dt[16] - '0') * 100 + (dt[17] - '0') * 10 + (dt[18] - '0'); // year
data[1] = (dt[12] - '0') * 10 + (dt[13] - '0'); // month
data[2] = (dt[0] - '0') * 10 + (dt[1] - '0'); // hour
data[3] = (dt[9] - '0') * 10 + (dt[10] - '0'); // day
data[4] = (dt[3] - '0') * 10 + (dt[4] - '0'); // min
data[5] = (dt[6] - '0') * 10 + (dt[7] - '0'); // sec
data[0] = (dt[7] - '0') * 100 + (dt[8] - '0') * 10 + (dt[9] - '0'); // year
data[1] = (dt[3] - '0') * 10 + (dt[4] - '0'); // month
data[2] = (dt[11] - '0') * 10 + (dt[12] - '0'); // hour
data[3] = (dt[0] - '0') * 10 + (dt[1] - '0'); // day
data[4] = (dt[14] - '0') * 10 + (dt[15] - '0'); // min
data[5] = (dt[17] - '0') * 10 + (dt[18] - '0'); // sec
data[6] = (dt[20] - '0'); // day of week, Mo:0
data[7] = (dt[22] - '0') + 2; // DST and flag
// LOG_INFO(F("Date and time: %02d.%02d.2%03d-%02d:%02d:%02d"), data[3], data[1], data[0], data[2], data[4], data[5]);
} else {
LOG_WARNING(F("Set date: invalid data, wrong length"));
return false;
}
if (data[1] == 0 || data[1] > 12 || data[2] > 23 || data[3] == 0 || data[3] > 31 || data[4] > 59 || data[5] > 59 || data[6] > 6 || data[7] > 3) {
// LOG_WARNING(F("Set date: invalid data"));
LOG_WARNING(F("Invalid date/time: %02d.%02d.2%03d-%02d:%02d:%02d-%d-%d"), data[3], data[1], data[0], data[2], data[4], data[5], data[6], data[7]);
return false;
}
@@ -1896,7 +2102,8 @@ bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
break;
}
switch (model()) {
uint8_t model_ = model();
switch (model_) {
case EMSdevice::EMS_DEVICE_FLAG_RC10:
offset = 0;
validate_typeid = 0xB1;
@@ -1959,6 +2166,18 @@ bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) {
// post validate is the corresponding monitor or set type IDs as they can differ per model
write_command(set_typeid, offset, set_mode_value, validate_typeid);
// set hc->mode temporary until validate is received
if (model_ == EMSdevice::EMS_DEVICE_FLAG_RC10) {
hc->mode = set_mode_value >> 1;
} else if (model_ == EMSdevice::EMS_DEVICE_FLAG_RC300 || model_ == EMSdevice::EMS_DEVICE_FLAG_RC100) {
hc->mode = set_mode_value ? 1 : 0;
} else if (model_ == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
hc->mode = set_mode_value - 1;
} else {
hc->mode = set_mode_value;
}
has_update(&hc->mode);
return true;
}
@@ -2015,7 +2234,41 @@ bool Thermostat::set_reducemode(const char * value, const int8_t id) {
return false;
}
write_command(set_typeids[hc->hc()], 25, set, set_typeids[hc->hc()]);
write_command(set_typeids[hc->hc()], EMS_OFFSET_RC35Set_reducemode, set, set_typeids[hc->hc()]);
return true;
}
// sets the thermostat reducemode for RC35 vacations
bool Thermostat::set_vacreducemode(const char * value, const int8_t id) {
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) {
return false;
}
uint8_t set = 0xFF;
if (!Helpers::value2enum(value, set, FL_(enum_reducemode))) {
return false;
}
write_command(set_typeids[hc->hc()], EMS_OFFSET_RC35Set_vacreducemode, set, set_typeids[hc->hc()]);
return true;
}
// sets the thermostat nofrost mode for RC35
bool Thermostat::set_nofrostmode(const char * value, const int8_t id) {
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) {
return false;
}
uint8_t set = 0xFF;
if (!Helpers::value2enum(value, set, FL_(enum_nofrostmode))) {
return false;
}
write_command(set_typeids[hc->hc()], EMS_OFFSET_RC35Set_nofrostmode, set, set_typeids[hc->hc()]);
return true;
}
@@ -2159,6 +2412,8 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char
}
if (strlen(value) > 13 && value[12] == 'o') {
on = value[13] == 'n' ? 1 : 0;
} else if (strlen(value) > 13 && value[12] == 'T') {
on = value[13] - '0';
} else if (strlen(value) == 13) {
on = value[12] - '0';
}
@@ -2175,8 +2430,8 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char
data[1] = time;
}
uint8_t max_on = 3;
if ((model() == EMS_DEVICE_FLAG_RC35) || (model() == EMS_DEVICE_FLAG_RC30_N)) {
uint8_t max_on = 4;
if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) {
max_on = 1;
}
if (no > 41 || time > 0x90 || (on > max_on && on != 7)) {
@@ -2186,7 +2441,7 @@ bool Thermostat::set_switchtime(const char * value, const uint16_t type_id, char
}
if (data[0] != 0xE7) {
std::string sday = read_flash_string(FL_(enum_dayOfWeek)[day]);
if ((model() == EMS_DEVICE_FLAG_RC35) || (model() == EMS_DEVICE_FLAG_RC30_N)) {
if (model() == EMS_DEVICE_FLAG_RC35 || model() == EMS_DEVICE_FLAG_RC30_N) {
snprintf(out, len, "%02d %s %02d:%02d %s", no, sday.c_str(), time / 6, 10 * (time % 6), on ? "on" : "off");
} else if (model() == EMS_DEVICE_FLAG_RC20) {
snprintf(out, len, "%02d %s %02d:%02d T%d", no, sday.c_str(), time / 6, 10 * (time % 6), on);
@@ -2540,6 +2795,14 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
offset = EMS_OFFSET_RC35Set_noreducetemp;
factor = 1;
break;
case HeatingCircuit::Mode::REDUCE:
offset = EMS_OFFSET_RC35Set_reducetemp;
factor = 1;
break;
case HeatingCircuit::Mode::VACREDUCE:
offset = EMS_OFFSET_RC35Set_vacreducetemp;
factor = 1;
break;
case HeatingCircuit::Mode::TEMPAUTO:
offset = EMS_OFFSET_RC35Set_seltemp;
break;
@@ -2558,16 +2821,15 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
default:
// automatic selection, if no type is defined, we use the standard code
validate_typeid = monitor_typeids[hc->hc()]; //get setpoint roomtemp back
if (model == EMS_DEVICE_FLAG_RC35) {
uint8_t mode_ = hc->get_mode();
if (mode_ == HeatingCircuit::Mode::NIGHT) {
offset = EMS_OFFSET_RC35Set_temp_night;
} else if (mode_ == HeatingCircuit::Mode::DAY) {
offset = EMS_OFFSET_RC35Set_temp_day;
} else {
} else if (model == EMS_DEVICE_FLAG_RC35) {
offset = EMS_OFFSET_RC35Set_seltemp; // https://github.com/emsesp/EMS-ESP/issues/310
}
} else {
// RC30_N missing temporary auto temperature https://github.com/emsesp/EMS-ESP32/issues/395
uint8_t modetype = hc->get_mode_type();
offset = (modetype == HeatingCircuit::Mode::NIGHT) ? EMS_OFFSET_RC35Set_temp_night : EMS_OFFSET_RC35Set_temp_day;
}
@@ -2592,7 +2854,16 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
offset = EMS_OFFSET_JunkersSetMessage_day_temp;
break;
default:
// automatic selection, if no type is defined, we use the standard code
// automatic selection, if no type is defined, we check mode and modetype
uint8_t mode_ = hc->get_mode();
if (mode_ == HeatingCircuit::Mode::NIGHT || mode_ == HeatingCircuit::Mode::ECO) {
offset = EMS_OFFSET_JunkersSetMessage_night_temp;
} else if (mode_ == HeatingCircuit::Mode::DAY || mode_ == HeatingCircuit::Mode::HEAT) {
offset = EMS_OFFSET_JunkersSetMessage_day_temp;
} else if (mode_ == HeatingCircuit::Mode::NOFROST) {
offset = EMS_OFFSET_JunkersSetMessage_no_frost_temp;
} else {
// auto mode, missing temporary parameter, use modetype https://github.com/emsesp/EMS-ESP32/issues/400
uint8_t modetype = hc->get_mode_type();
if (modetype == HeatingCircuit::Mode::NIGHT || modetype == HeatingCircuit::Mode::ECO) {
offset = EMS_OFFSET_JunkersSetMessage_night_temp;
@@ -2601,6 +2872,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
} else {
offset = EMS_OFFSET_JunkersSetMessage_no_frost_temp;
}
}
break;
}
@@ -2619,7 +2891,16 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
offset = EMS_OFFSET_JunkersSetMessage2_heat_temp;
break;
default:
// automatic selection, if no type is defined, we use the standard code
// automatic selection, if no type is defined, we check mode and modetype
uint8_t mode_ = hc->get_mode();
if (mode_ == HeatingCircuit::Mode::NIGHT || mode_ == HeatingCircuit::Mode::ECO) {
offset = EMS_OFFSET_JunkersSetMessage2_eco_temp;
} else if (mode_ == HeatingCircuit::Mode::DAY || mode_ == HeatingCircuit::Mode::HEAT) {
offset = EMS_OFFSET_JunkersSetMessage2_heat_temp;
} else if (mode_ == HeatingCircuit::Mode::NOFROST) {
offset = EMS_OFFSET_JunkersSetMessage2_no_frost_temp;
} else {
// auto mode, missing temporary parameter, use modetype https://github.com/emsesp/EMS-ESP32/issues/400
uint8_t modetype = hc->get_mode_type();
if (modetype == HeatingCircuit::Mode::NIGHT || modetype == HeatingCircuit::Mode::ECO) {
offset = EMS_OFFSET_JunkersSetMessage2_eco_temp;
@@ -2628,6 +2909,7 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
} else {
offset = EMS_OFFSET_JunkersSetMessage2_no_frost_temp;
}
}
break;
}
}
@@ -2718,6 +3000,14 @@ bool Thermostat::set_noreducetemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::NOREDUCE);
}
bool Thermostat::set_reducetemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::REDUCE);
}
bool Thermostat::set_vacreducetemp(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::VACREDUCE);
}
bool Thermostat::set_flowtempoffset(const char * value, const int8_t id) {
return set_temperature_value(value, id, HeatingCircuit::Mode::FLOWOFFSET, true);
}
@@ -2867,19 +3157,72 @@ void Thermostat::register_device_values() {
break;
case EMS_DEVICE_FLAG_RC30:
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaClockOffset_,
DeviceValueType::INT,
nullptr,
FL_(ibaClockOffset),
DeviceValueUOM::SECONDS,
MAKE_CF_CB(set_clockoffset)); // offset (in sec) to clock, 0xff=-1s, 0x02=2s
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &autodst_, DeviceValueType::BOOL, nullptr, FL_(autodst), DeviceValueUOM::NONE, MAKE_CF_CB(set_autodst));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaLanguage_,
DeviceValueType::ENUM,
FL_(enum_ibaLanguage_RC30),
FL_(ibaLanguage),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_language));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaMainDisplay_,
DeviceValueType::ENUM,
FL_(enum_ibaMainDisplay),
FL_(ibaMainDisplay),
DeviceValueUOM::NONE);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), FL_(ibaLanguage), DeviceValueUOM::NONE);
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &backlight_, DeviceValueType::BOOL, nullptr, FL_(backlight), DeviceValueUOM::NONE, MAKE_CF_CB(set_backlight));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaClockOffset_,
&brightness_,
DeviceValueType::INT,
nullptr,
FL_(ibaClockOffset),
DeviceValueUOM::SECONDS); // offset (in sec) to clock, 0xff=-1s, 0x02=2s
FL_(brightness),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_brightness),
-15,
15);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&mixingvalves_,
DeviceValueType::UINT,
nullptr,
FL_(mixingvalves),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_mixingvalves),
0,
2);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaBuildingType_,
DeviceValueType::ENUM,
FL_(enum_ibaBuildingType),
FL_(ibaBuildingType),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_building));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&heatingpid_,
DeviceValueType::ENUM,
FL_(enum_PID),
FL_(heatingPID),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_heatingpid));
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &preheating_, DeviceValueType::BOOL, nullptr, FL_(preheating), DeviceValueUOM::NONE, MAKE_CF_CB(set_preheating));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&ibaCalIntTemperature_,
DeviceValueType::INT,
FL_(div10),
FL_(ibaCalIntTemperature),
DeviceValueUOM::DEGREES_R,
MAKE_CF_CB(set_calinttemp));
register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &offtemp_, DeviceValueType::UINT, FL_(div2), FL_(offtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_offtemp), 5, 30);
break;
case EMS_DEVICE_FLAG_RC30_N:
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dateTime_, DeviceValueType::STRING, nullptr, FL_(dateTime), DeviceValueUOM::NONE); // can't set datetime
@@ -3112,6 +3455,67 @@ void Thermostat::register_device_values() {
FL_(dateTime),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_datetime));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&hybridStrategy_,
DeviceValueType::ENUM,
FL_(enum_hybridStrategy),
FL_(hybridStrategy),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_hybridStrategy));
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&switchOverTemp_,
DeviceValueType::INT,
nullptr,
FL_(switchOverTemp),
DeviceValueUOM::DEGREES,
MAKE_CF_CB(set_switchOverTemp),
-20,
20);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&energyCostRatio_,
DeviceValueType::UINT,
FL_(div10),
FL_(energyCostRatio),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_energyCostRatio),
0,
19.9);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&fossileFactor_,
DeviceValueType::UINT,
FL_(div10),
FL_(fossileFactor),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_fossileFactor),
0,
5);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&electricFactor_,
DeviceValueType::UINT,
FL_(div10),
FL_(electricFactor),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_electricFactor),
0,
5);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&delayBoiler_,
DeviceValueType::UINT,
nullptr,
FL_(delayBoiler),
DeviceValueUOM::MINUTES,
MAKE_CF_CB(set_delayBoiler),
5,
120);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
&tempDiffBoiler_,
DeviceValueType::UINT,
nullptr,
FL_(tempDiffBoiler),
DeviceValueUOM::DEGREES_R,
MAKE_CF_CB(set_tempDiffBoiler),
1,
99);
break;
case EMS_DEVICE_FLAG_EASY:
// Easy TC100 have no date/time, see issue #100, not sure about CT200, so leave it.
@@ -3183,7 +3587,7 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
tag, &hc->heatingtype, DeviceValueType::ENUM, FL_(enum_heatingtype), FL_(heatingtype), DeviceValueUOM::NONE, MAKE_CF_CB(set_heatingtype));
register_device_value(
tag, &hc->summer_setmode, DeviceValueType::ENUM, FL_(enum_summermode), FL_(summersetmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_summermode));
register_device_value(tag, &hc->summermode, DeviceValueType::BOOL, nullptr, FL_(summermode), DeviceValueUOM::NONE);
register_device_value(tag, &hc->summermode, DeviceValueType::ENUM, FL_(enum_summer), FL_(summermode), DeviceValueUOM::NONE);
register_device_value(
tag, &hc->controlmode, DeviceValueType::ENUM, FL_(enum_controlmode), FL_(controlmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_controlmode));
register_device_value(tag, &hc->program, DeviceValueType::ENUM, FL_(enum_progMode), FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program));
@@ -3251,6 +3655,8 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->summermode, DeviceValueType::ENUM, FL_(enum_summer), FL_(summermode), DeviceValueUOM::NONE);
register_device_value(tag, &hc->holidaymode, DeviceValueType::BOOL, nullptr, FL_(holidaymode), DeviceValueUOM::NONE);
register_device_value(tag, &hc->nofrosttemp, DeviceValueType::INT, nullptr, FL_(nofrosttemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_nofrosttemp));
register_device_value(
tag, &hc->nofrostmode, DeviceValueType::ENUM, FL_(enum_nofrostmode), FL_(nofrostmode), DeviceValueUOM::NONE, MAKE_CF_CB(set_nofrostmode));
register_device_value(tag, &hc->roominfluence, DeviceValueType::UINT, nullptr, FL_(roominfluence), DeviceValueUOM::DEGREES_R, MAKE_CF_CB(set_roominfluence));
register_device_value(tag, &hc->minflowtemp, DeviceValueType::UINT, nullptr, FL_(minflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_minflowtemp));
register_device_value(tag, &hc->maxflowtemp, DeviceValueType::UINT, nullptr, FL_(maxflowtemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_maxflowtemp));
@@ -3269,6 +3675,10 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->party, DeviceValueType::UINT, nullptr, FL_(party), DeviceValueUOM::HOURS, MAKE_CF_CB(set_party));
register_device_value(tag, &hc->tempautotemp, DeviceValueType::UINT, FL_(div2), FL_(tempautotemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_tempautotemp));
register_device_value(tag, &hc->noreducetemp, DeviceValueType::INT, nullptr, FL_(noreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_noreducetemp));
register_device_value(tag, &hc->reducetemp, DeviceValueType::INT, nullptr, FL_(reducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_reducetemp));
register_device_value(tag, &hc->vacreducetemp, DeviceValueType::INT, nullptr, FL_(vacreducetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_vacreducetemp));
register_device_value(
tag, &hc->vacreducemode, DeviceValueType::ENUM, FL_(enum_reducemode), FL_(vacreducemode), DeviceValueUOM::NONE, MAKE_CF_CB(set_vacreducemode));
register_device_value(tag, &hc->remotetemp, DeviceValueType::SHORT, FL_(div10), FL_(remotetemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_remotetemp));
register_device_value(tag, &hc->wwprio, DeviceValueType::BOOL, nullptr, FL_(wwprio), DeviceValueUOM::NONE, MAKE_CF_CB(set_wwprio));
register_device_value(
@@ -3285,6 +3695,7 @@ void Thermostat::register_device_values_hc(std::shared_ptr<Thermostat::HeatingCi
register_device_value(tag, &hc->control, DeviceValueType::ENUM, FL_(enum_j_control), FL_(control), DeviceValueUOM::NONE, MAKE_CF_CB(set_control));
register_device_value(tag, &hc->program, DeviceValueType::ENUM, FL_(enum_progMode4), FL_(program), DeviceValueUOM::NONE, MAKE_CF_CB(set_program));
register_device_value(tag, &hc->remotetemp, DeviceValueType::SHORT, FL_(div10), FL_(remotetemp), DeviceValueUOM::DEGREES);
register_device_value(tag, &hc->targetflowtemp, DeviceValueType::UINT, nullptr, FL_(targetflowtemp), DeviceValueUOM::DEGREES);
break;
default:
break;

View File

@@ -62,12 +62,16 @@ class Thermostat : public EMSdevice {
uint8_t minflowtemp;
uint8_t maxflowtemp;
uint8_t reducemode;
uint8_t nofrostmode;
uint8_t program;
uint8_t controlmode;
uint8_t control;
uint8_t pause;
uint8_t party;
int8_t noreducetemp; // signed -20°C to +10°C
int8_t reducetemp;
int8_t vacreducetemp;
uint8_t vacreducemode;
uint8_t wwprio;
uint8_t fastHeatup;
char holiday[26];
@@ -120,6 +124,8 @@ class Thermostat : public EMSdevice {
ROOMINFLUENCE,
TEMPAUTO,
NOREDUCE,
REDUCE,
VACREDUCE,
ON,
DAYLOW,
DAYMID,
@@ -176,6 +182,11 @@ class Thermostat : public EMSdevice {
uint8_t ibaDamping_; // damping 0-off, 0xff-on
uint8_t backlight_;
uint8_t heatingpid_;
int8_t brightness_; // Screen brightness 0F=dark F1=light
uint8_t preheating_; // Preheating in the clock program: (0x00 = off, 0xFF = on)
uint8_t autodst_; // Automatic change Daylight Saving time: (0x00 = off, 0xFF = on)
uint8_t offtemp_; // Set Temperature when mode is Off / 10 (e.g.: 0x0F = 7.5 degrees Celsius)
uint8_t mixingvalves_; // Number of Mixing Valves: (0x00=0, 0x01=1, 0x02=2)
int8_t dampedoutdoortemp_;
uint16_t tempsensor1_;
@@ -205,6 +216,15 @@ class Thermostat : public EMSdevice {
uint8_t wwDailyHeating_;
uint8_t wwDailyHeatTime_;
// HybridHP
uint8_t hybridStrategy_; // co2 = 1, cost = 2, temperature = 3, mix = 4
int8_t switchOverTemp_; // degrees
uint8_t energyCostRatio_; // is *10
uint8_t fossileFactor_; // is * 10
uint8_t electricFactor_; // is * 10
uint8_t delayBoiler_; // minutes
uint8_t tempDiffBoiler_; // relative temperature degrees
std::vector<std::shared_ptr<HeatingCircuit>> heating_circuits_; // each thermostat can have multiple heating circuits
uint8_t zero_value_ = 0; // for fixing current room temperature to 0 for HA
@@ -245,6 +265,12 @@ class Thermostat : public EMSdevice {
static constexpr uint8_t EMS_OFFSET_RC35Set_targetflowtemp = 14; // target flow temperature
static constexpr uint8_t EMS_OFFSET_RC35Set_seltemp = 37; // selected temp
static constexpr uint8_t EMS_OFFSET_RC35Set_noreducetemp = 38; // temp to stop reducing
static constexpr uint8_t EMS_OFFSET_RC35Set_reducetemp = 39; // temp reducing/hold
static constexpr uint8_t EMS_OFFSET_RC35Set_vacreducetemp = 40; // temp reducing/hold in vacations
static constexpr uint8_t EMS_OFFSET_RC35Set_vacreducemode = 41; // reduce mode in vacations
static constexpr uint8_t EMS_OFFSET_RC35Set_reducemode = 25; // reduce mode in normal operation
static constexpr uint8_t EMS_OFFSET_RC35Set_nofrostmode = 28; // 0-off, 1-room, 2-outdoor
static constexpr uint8_t EMS_OFFSET_RC35Set_control = 26; // 0-off, 1-RC20, 2-RC3x
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_offset = 6;
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_flowoffset = 24;
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_design = 17;
@@ -284,6 +310,7 @@ class Thermostat : public EMSdevice {
// Installation settings
static constexpr uint8_t EMS_TYPE_IBASettings = 0xA5; // installation settings
static constexpr uint8_t EMS_TYPE_RC30Settings = 0xA7; // RC30 settings
static constexpr uint8_t EMS_TYPE_wwSettings = 0x37; // ww settings
static constexpr uint8_t EMS_TYPE_time = 0x06; // time
@@ -333,6 +360,8 @@ class Thermostat : public EMSdevice {
void process_JunkersSet2(std::shared_ptr<const Telegram> telegram);
void process_EasyMonitor(std::shared_ptr<const Telegram> telegram);
void process_JunkersRemoteMonitor(std::shared_ptr<const Telegram> telegram);
void process_JunkersHybridSettings(std::shared_ptr<const Telegram> telegram);
void process_JunkersSetMixer(std::shared_ptr<const Telegram> telegram);
// internal helper functions
bool set_mode_n(const uint8_t mode, const uint8_t hc_num);
@@ -368,6 +397,11 @@ class Thermostat : public EMSdevice {
bool set_manualtemp(const char * value, const int8_t id);
bool set_tempautotemp(const char * value, const int8_t id);
bool set_noreducetemp(const char * value, const int8_t id);
bool set_reducetemp(const char * value, const int8_t id);
bool set_vacreducetemp(const char * value, const int8_t id);
bool set_vacreducemode(const char * value, const int8_t id);
bool set_nofrostmode(const char * value, const int8_t id);
bool set_remotetemp(const char * value, const int8_t id);
bool set_roominfluence(const char * value, const int8_t id);
bool set_roominfl_factor(const char * value, const int8_t id);
@@ -413,6 +447,19 @@ class Thermostat : public EMSdevice {
bool set_reducehours(const char * value, const int8_t id);
bool set_backlight(const char * value, const int8_t id);
bool set_heatingpid(const char * value, const int8_t id);
bool set_brightness(const char * value, const int8_t id);
bool set_autodst(const char * value, const int8_t id);
bool set_preheating(const char * value, const int8_t id);
bool set_mixingvalves(const char * value, const int8_t id);
bool set_offtemp(const char * value, const int8_t id);
bool set_hybridStrategy(const char * value, const int8_t id);
bool set_switchOverTemp(const char * value, const int8_t id);
bool set_energyCostRatio(const char * value, const int8_t id);
bool set_fossileFactor(const char * value, const int8_t id);
bool set_electricFactor(const char * value, const int8_t id);
bool set_delayBoiler(const char * value, const int8_t id);
bool set_tempDiffBoiler(const char * value, const int8_t id);
};
} // namespace emsesp

View File

@@ -26,7 +26,7 @@ namespace emsesp {
uint8_t EMSdevice::count_entities() {
uint8_t count = 0;
for (auto & dv : devicevalues_) {
if (dv.has_state(DeviceValueState::DV_VISIBLE) && dv.hasValue()) {
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.hasValue()) {
count++;
}
}
@@ -258,7 +258,7 @@ bool EMSdevice::has_tag(const uint8_t tag) {
// called from the command 'entities'
void EMSdevice::list_device_entries(JsonObject & output) const {
for (const auto & dv : devicevalues_) {
if (dv.has_state(DeviceValueState::DV_VISIBLE) && dv.type != DeviceValueType::CMD && dv.full_name) {
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.type != DeviceValueType::CMD && dv.full_name) {
// if we have a tag prefix it
char key[50];
if (!EMSdevice::tag_to_mqtt(dv.tag).empty()) {
@@ -321,6 +321,11 @@ void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) const {
}
}
shell.println();
shell.printf(F(" Ignored telegram type IDs: "));
for (auto handlers : handlers_ignored_) {
shell.printf(F("0x%02X "), handlers);
}
shell.println();
}
// list all the telegram type IDs for this device, outputting to a string (max size 200)
@@ -343,10 +348,27 @@ char * EMSdevice::show_telegram_handlers(char * result, const size_t len, const
strlcat(result, Helpers::hextoa(tf.telegram_type_id_, true).c_str(), len);
}
}
if (handlers == Handlers::ALL || handlers == Handlers::IGNORED) {
i = 0;
for (auto handlers : handlers_ignored_) {
if (i++ > 0) {
strlcat(result, " ", len);
}
strlcat(result, Helpers::hextoa(handlers).c_str(), len);
}
}
return result;
}
void EMSdevice::add_handlers_ignored(const uint16_t handler) {
for (auto handlers : handlers_ignored_) {
if (handler == handlers) {
return;
}
}
handlers_ignored_.push_back(handler);
}
// list all the mqtt handlers for this device
void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) const {
Mqtt::show_topic_handlers(shell, device_type_);
@@ -405,19 +427,18 @@ void EMSdevice::register_device_value(uint8_t tag,
}
}
// this is the unique id set for the device entity. it's a simple sequence number
uint8_t dv_id = get_next_dv_id();
// determine state
uint8_t state = DeviceValueState::DV_VISIBLE; // default to visible
uint8_t state = DeviceValueState::DV_DEFAULT;
// scan through customizations to see if it's on the exclusion list by matching the productID and deviceID
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
for (EntityCustomization entityCustomization : settings.entityCustomizations) {
if ((entityCustomization.product_id == product_id()) && (entityCustomization.device_id == device_id())) {
for (uint8_t entity_id : entityCustomization.entity_ids) {
if (entity_id == dv_id) {
state = DeviceValueState::DV_DEFAULT; // it's on the exclude list, turn off active and visible flags
std::string entity = tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(short_name) : tag_to_string(tag) + "/" + read_flash_string(short_name);
for (std::string entity_id : entityCustomization.entity_ids) {
if (entity_id.substr(2) == entity) {
uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str());
state = mask << 4; // set state high bits to flag, turn off active and ha flags
break;
}
}
@@ -426,7 +447,7 @@ void EMSdevice::register_device_value(uint8_t tag,
});
// add the device
devicevalues_.emplace_back(device_type_, tag, value_p, type, options, options_size, short_name, full_name, uom, 0, has_cmd, min, max, state, dv_id);
devicevalues_.emplace_back(device_type_, tag, value_p, type, options, options_size, short_name, full_name, uom, 0, has_cmd, min, max, state);
}
// function with min and max values
@@ -454,7 +475,7 @@ void EMSdevice::register_device_value(uint8_t tag,
if (tag >= DeviceValueTAG::TAG_HC1 && tag <= DeviceValueTAG::TAG_HC8) {
flags |= CommandFlag::MQTT_SUB_FLAG_HC;
} else if (tag >= DeviceValueTAG::TAG_WWC1 && tag <= DeviceValueTAG::TAG_WWC4) {
} else if (tag >= DeviceValueTAG::TAG_WWC1 && tag <= DeviceValueTAG::TAG_WWC10) {
flags |= CommandFlag::MQTT_SUB_FLAG_WWC;
} else if (tag == DeviceValueTAG::TAG_DEVICE_DATA_WW || tag == DeviceValueTAG::TAG_BOILER_DATA_WW) {
flags |= CommandFlag::MQTT_SUB_FLAG_WW;
@@ -486,11 +507,33 @@ void EMSdevice::register_device_value(uint8_t tag,
register_device_value(tag, value_p, type, options, name, uom, nullptr, 0, 0);
}
// check if value is visible
bool EMSdevice::is_visible(const void * value_p) const {
// check if value is readable via mqtt/api
bool EMSdevice::is_readable(const void * value_p) const {
for (const auto & dv : devicevalues_) {
if (dv.value_p == value_p) {
return dv.has_state(DeviceValueState::DV_VISIBLE);
return !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE);
}
}
return false;
}
// check if value/command is readonly
bool EMSdevice::is_readonly(const std::string & cmd, const int8_t id) const {
uint8_t tag = id > 0 ? DeviceValueTAG::TAG_HC1 + id - 1 : DeviceValueTAG::TAG_NONE;
for (const auto & dv : devicevalues_) {
// check command name and tag, id -1 is default hc and only checks name
if (dv.has_cmd && read_flash_string(dv.short_name) == cmd && (dv.tag < DeviceValueTAG::TAG_HC1 || dv.tag == tag || id == -1)) {
return dv.has_state(DeviceValueState::DV_READONLY);
}
}
return true; // not found, no write
}
// check if value has a registered command
bool EMSdevice::has_command(const void * value_p) const {
for (const auto & dv : devicevalues_) {
if (dv.value_p == value_p) {
return dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
}
}
return false;
@@ -503,7 +546,7 @@ void EMSdevice::publish_value(void * value_p) const {
}
for (const auto & dv : devicevalues_) {
if (dv.value_p == value_p && dv.has_state(DeviceValueState::DV_VISIBLE)) {
if (dv.value_p == value_p && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE)) {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) {
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
@@ -528,7 +571,7 @@ void EMSdevice::publish_value(void * value_p) const {
}
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
char payload[30] = {'\0'};
char payload[50] = {'\0'};
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (dv.uom == DeviceValueUOM::DEGREES) ? 2 : (dv.uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
switch (dv.type) {
@@ -561,7 +604,7 @@ void EMSdevice::publish_value(void * value_p) const {
Helpers::render_value(payload, *(uint32_t *)(value_p), divider, fahrenheit);
break;
case DeviceValueType::BOOL: {
Helpers::render_boolean(payload, (bool)(*(uint8_t *)(value_p)));
Helpers::render_boolean(payload, (bool)*(uint8_t *)(value_p));
break;
}
case DeviceValueType::TIME:
@@ -603,7 +646,7 @@ std::string EMSdevice::get_value_uom(const char * key) const {
// look up key in our device value list
for (const auto & dv : devicevalues_) {
if ((dv.has_state(DeviceValueState::DV_VISIBLE) && dv.full_name) && (read_flash_string(dv.full_name) == key_p)) {
if ((!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.full_name) && (read_flash_string(dv.full_name) == key_p)) {
// ignore TIME since "minutes" is already added to the string value
if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) {
break;
@@ -618,26 +661,27 @@ std::string EMSdevice::get_value_uom(const char * key) const {
// prepare array of device values used for the WebUI
// this is loosely based of the function generate_values used for the MQTT and Console
// except additional data is stored in the JSON document needed for the Web UI like the UOM and command
// v = value, u=uom, n=name, c=cmd
// v=value, u=uom, n=name, c=cmd, h=help string, s=step, m=min, x=max
void EMSdevice::generate_values_web(JsonObject & output) {
output["label"] = to_string_short();
JsonArray data = output.createNestedArray("data");
// do two passes. First for all entities marked as favorites, then for all others. This sorts the list.
for (int8_t fav = 1; fav >= 0; fav--) {
for (auto & dv : devicevalues_) {
// check conditions:
// 1. full_name cannot be empty
// 2. it must have a valid value, if it is not a command like 'reset'
if (dv.has_state(DeviceValueState::DV_VISIBLE) && dv.full_name && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) {
// 3. show favorites first
bool show = (fav && dv.has_state(DeviceValueState::DV_FAVORITE)) || (!fav && !dv.has_state(DeviceValueState::DV_FAVORITE));
if (show && !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.full_name && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) {
JsonObject obj = data.createNestedObject(); // create the object, we know there is a value
uint8_t fahrenheit = 0;
// handle Booleans (true, false)
// handle Booleans (true, false), use strings, no native true/false)
if (dv.type == DeviceValueType::BOOL) {
bool value_b = *(bool *)(dv.value_p);
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
obj["v"] = value_b ? "true" : "false";
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
bool value_b = (bool)*(uint8_t *)(dv.value_p);
if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
obj["v"] = value_b ? 1 : 0;
} else {
char s[7];
@@ -682,17 +726,19 @@ void EMSdevice::generate_values_web(JsonObject & output) {
// add the unit of measure (uom)
obj["u"] = fahrenheit ? (uint8_t)DeviceValueUOM::FAHRENHEIT : dv.uom;
auto mask = Helpers::hextoa((uint8_t)(dv.state >> 4), false); // create mask to a 2-char string
// add name, prefixing the tag if it exists
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
obj["n"] = dv.full_name;
obj["n"] = mask + read_flash_string(dv.full_name);
} else if (dv.tag < DeviceValueTAG::TAG_HC1) {
obj["n"] = tag_to_string(dv.tag) + " " + read_flash_string(dv.full_name);
obj["n"] = mask + tag_to_string(dv.tag) + " " + read_flash_string(dv.full_name);
} else {
obj["n"] = tag_to_string(dv.tag) + " " + read_flash_string(dv.full_name);
obj["n"] = mask + tag_to_string(dv.tag) + " " + read_flash_string(dv.full_name);
}
// add commands and options
if (dv.has_cmd) {
if (dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY)) {
// add the name of the Command function
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["c"] = tag_to_mqtt(dv.tag) + "/" + read_flash_string(dv.short_name);
@@ -709,8 +755,9 @@ void EMSdevice::generate_values_web(JsonObject & output) {
}
} else if (dv.type == DeviceValueType::BOOL) {
JsonArray l = obj.createNestedArray("l");
l.add("off");
l.add("on");
char result[10];
l.add(Helpers::render_boolean(result, false));
l.add(Helpers::render_boolean(result, true));
}
// add command help template
else if (dv.type == DeviceValueType::STRING || dv.type == DeviceValueType::CMD) {
@@ -727,48 +774,31 @@ void EMSdevice::generate_values_web(JsonObject & output) {
} else if (divider < 0) {
obj["s"] = Helpers::render_value(s, (-1) * divider, 0);
}
int16_t dv_set_min, dv_set_max;
if (dv.get_min_max(dv_set_min, dv_set_max)) {
obj["m"] = Helpers::render_value(s, dv_set_min, 0);
obj["x"] = Helpers::render_value(s, dv_set_max, 0);
}
}
}
}
}
// reset all entities to being visible
// this is called before loading in the exclude entities list from the customization service
void EMSdevice::reset_exclude_entities() {
for (auto & dv : devicevalues_) {
dv.add_state(DeviceValueState::DV_VISIBLE);
}
}
// disable/exclude a device entity based on its unique id
void EMSdevice::exclude_entity(uint8_t id) {
for (auto & dv : devicevalues_) {
if (dv.id == id) {
#if defined(EMSESP_USE_SERIAL)
Serial.print("exclude_entity() Removing Visible for device value: ");
Serial.println(read_flash_string(dv.full_name).c_str());
#endif
dv.remove_state(DeviceValueState::DV_VISIBLE); // this will remove from MQTT payloads and showing in web & console
return;
}
}
}
// as generate_values_web() but stripped down to only show all entities and their state
// this is used only for WebCustomizationService::device_entities()
void EMSdevice::generate_values_web_all(JsonArray & output) {
for (auto & dv : devicevalues_) {
for (const auto & dv : devicevalues_) {
// also show commands and entities that have an empty full name
JsonObject obj = output.createNestedObject();
// create the value
if (dv.hasValue()) {
// handle Booleans (true, false)
// handle Booleans (true, false), use strings, no native true/false)
if (dv.type == DeviceValueType::BOOL) {
bool value_b = *(bool *)(dv.value_p);
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
obj["v"] = value_b;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
bool value_b = (bool)*(uint8_t *)(dv.value_p);
if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
obj["v"] = value_b ? 1 : 0;
} else {
char s[7];
@@ -830,8 +860,9 @@ void EMSdevice::generate_values_web_all(JsonArray & output) {
obj["n"] = name;
}
} else {
obj["n"] = "(hidden)";
obj["n"] = "";
}
// shortname
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["s"] = tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name);
@@ -839,13 +870,44 @@ void EMSdevice::generate_values_web_all(JsonArray & output) {
obj["s"] = dv.short_name;
}
// is it marked as excluded?
obj["x"] = !dv.has_state(DeviceValueState::DV_VISIBLE);
obj["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble
obj["w"] = dv.has_cmd; // if writable
}
}
// add the unique ID
obj["i"] = dv.id;
// set mask per device entity based on the id which is prefixed with the 2 char hex mask value
// returns true if the entity has a mask set (not 0 the default)
void EMSdevice::mask_entity(const std::string & entity_id) {
for (auto & dv : devicevalues_) {
std::string entity_name =
dv.tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(dv.short_name) : tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name);
if (entity_name == entity_id.substr(2)) {
// this entity has a new mask set
uint8_t current_mask = dv.state >> 4;
uint8_t new_mask = Helpers::hextoint(entity_id.substr(0, 2).c_str()); // first character contains mask flags
if (Mqtt::ha_enabled() && ((current_mask ^ new_mask) & (DeviceValueState::DV_READONLY >> 4))) {
// remove ha config on change of dv_readonly flag
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
Mqtt::publish_ha_sensor_config(dv, "", "", true); // delete topic (remove = true)
}
dv.state = ((dv.state & 0x0F) | (new_mask << 4)); // set state high bits to flag
return;
}
}
}
// populate a string vector with entities that have masks set
void EMSdevice::getMaskedEntities(std::vector<std::string> & entity_ids) {
for (auto & dv : devicevalues_) {
std::string entity_name =
dv.tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(dv.short_name) : tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name);
uint8_t mask = dv.state >> 4;
if (mask) {
entity_ids.push_back(Helpers::hextoa(mask, false) + entity_name);
}
}
}
// builds json for a specific device value / entity
// cmd is the endpoint or name of the device entity
@@ -855,7 +917,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
int8_t tag = id;
// check if we have hc or wwc or hs
if (id >= 1 && id <= 29) {
if (id >= 1 && id <= 34) {
tag = DeviceValueTAG::TAG_HC1 + id - 1;
} else if (id != -1) {
return false; // error
@@ -939,7 +1001,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
case DeviceValueType::BOOL:
if (Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
auto value_b = (bool)(*(uint8_t *)(dv.value_p));
bool value_b = (bool)*(uint8_t *)(dv.value_p);
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
json[value] = value_b;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
@@ -993,8 +1055,9 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
json["uom"] = uom_to_string(dv.uom);
}
json["writeable"] = dv.has_cmd;
json["visible"] = dv.has_state(DeviceValueState::DV_VISIBLE);
json["readable"] = !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE);
json["writeable"] = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
json["visible"] = !dv.has_state(DeviceValueState::DV_WEB_EXCLUDE);
// if there is no value, mention it
if (!json.containsKey(value)) {
@@ -1038,14 +1101,11 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
// check conditions:
// 1. it must have a valid value (state is active)
// 2. it must have a visible, unless the output_target is MQTT
// 2. it must have a visible flag
// 3. it must match the given tag filter or have an empty tag
bool conditions = ((tag_filter == DeviceValueTAG::TAG_NONE) || (tag_filter == dv.tag)) && dv.has_state(DeviceValueState::DV_ACTIVE);
// 4. for MQTT we want to always show the special HA entities (they have an empty fullname)
bool visible = ((dv.has_state(DeviceValueState::DV_VISIBLE)) || ((output_target == OUTPUT_TARGET::MQTT) && (!dv.full_name)));
conditions &= visible;
if (conditions) {
// 4. it must not have the exclude flag set or outputs to console
if (dv.has_state(DeviceValueState::DV_ACTIVE) && dv.full_name && (tag_filter == DeviceValueTAG::TAG_NONE || tag_filter == dv.tag)
&& (output_target == OUTPUT_TARGET::CONSOLE || !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE))) {
has_values = true; // flagged if we actually have data
// we have a tag if it matches the filter given, and that the tag name is not empty/""
@@ -1053,7 +1113,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
// create the name for the JSON key
char name[80];
if (output_target == OUTPUT_TARGET::API_VERBOSE) {
if (output_target == OUTPUT_TARGET::API_VERBOSE || output_target == OUTPUT_TARGET::CONSOLE) {
if (have_tag) {
snprintf(name, 80, "%s %s", tag_to_string(dv.tag).c_str(), read_flash_string(dv.full_name).c_str()); // prefix the tag
} else {
@@ -1074,7 +1134,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
// handle Booleans
if (dv.type == DeviceValueType::BOOL && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
// see how to render the value depending on the setting
auto value_b = (bool)*(uint8_t *)(dv.value_p);
bool value_b = (bool)*(uint8_t *)(dv.value_p);
if (Mqtt::ha_enabled() && (output_target == OUTPUT_TARGET::MQTT)) {
char s[7];
json[name] = Helpers::render_boolean(s, value_b); // for HA always render as string
@@ -1166,7 +1226,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
uint32_t time_value = *(uint32_t *)(dv.value_p);
time_value = Helpers::round2(time_value, divider); // sometimes we need to divide by 60
if (output_target == EMSdevice::OUTPUT_TARGET::API_VERBOSE) {
if (output_target == OUTPUT_TARGET::API_VERBOSE || output_target == OUTPUT_TARGET::CONSOLE) {
char time_s[40];
snprintf(time_s,
sizeof(time_s),
@@ -1193,12 +1253,12 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
return has_values;
}
// remove the Home Assistant configs for each device value/entity if its not visible or active
// remove the Home Assistant configs for each device value/entity if its not visible or active or marked as read-only
// this is called when an MQTT publish is done via an EMS Device in emsesp.cpp::publish_device_values()
void EMSdevice::mqtt_ha_entity_config_remove() {
for (auto & dv : devicevalues_) {
if (dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED)
&& ((!dv.has_state(DeviceValueState::DV_VISIBLE)) || (!dv.has_state(DeviceValueState::DV_ACTIVE)))) {
&& ((dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE)) || (!dv.has_state(DeviceValueState::DV_ACTIVE)))) {
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
if (dv.short_name == FL_(climate)[0]) {
Mqtt::publish_ha_climate_config(dv.tag, false, true); // delete topic (remove = true)
@@ -1218,7 +1278,7 @@ void EMSdevice::mqtt_ha_entity_config_create() {
// create climate if roomtemp is visible
// create the discovery topic if if hasn't already been created, not a command (like reset) and is active and visible
for (auto & dv : devicevalues_) {
if ((dv.short_name == FL_(climate)[0]) && dv.has_state(DeviceValueState::DV_VISIBLE) && dv.has_state(DeviceValueState::DV_ACTIVE)) {
if ((dv.short_name == FL_(climate)[0]) && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE) && dv.has_state(DeviceValueState::DV_ACTIVE)) {
if (*(int8_t *)(dv.value_p) == 1 && (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) || dv.has_state(DeviceValueState::DV_HA_CLIMATE_NO_RT))) {
dv.remove_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
@@ -1231,7 +1291,7 @@ void EMSdevice::mqtt_ha_entity_config_create() {
}
}
if (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) && (dv.type != DeviceValueType::CMD) && dv.has_state(DeviceValueState::DV_ACTIVE)
&& dv.has_state(DeviceValueState::DV_VISIBLE)) {
&& !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE)) {
// create_device_config is only done once for the EMS device. It can added to any entity, so we take the first
Mqtt::publish_ha_sensor_config(dv, name(), brand_to_string(), false, create_device_config);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);

View File

@@ -179,14 +179,16 @@ class EMSdevice {
std::string to_string() const;
std::string to_string_short() const;
enum Handlers : uint8_t { ALL, RECEIVED, FETCHED, PENDING };
enum Handlers : uint8_t { ALL, RECEIVED, FETCHED, PENDING, IGNORED };
void show_telegram_handlers(uuid::console::Shell & shell) const;
char * show_telegram_handlers(char * result, const size_t len, const uint8_t handlers);
void show_mqtt_handlers(uuid::console::Shell & shell) const;
void list_device_entries(JsonObject & output) const;
void exclude_entity(uint8_t entity_id);
void reset_exclude_entities();
void add_handlers_ignored(const uint16_t handler);
void mask_entity(const std::string & entity_id);
void getMaskedEntities(std::vector<std::string> & entity_ids);
using process_function_p = std::function<void(std::shared_ptr<const Telegram>)>;
@@ -197,7 +199,7 @@ class EMSdevice {
bool get_value_info(JsonObject & root, const char * cmd, const int8_t id);
void get_dv_info(JsonObject & json);
enum OUTPUT_TARGET : uint8_t { API_VERBOSE, API_SHORTNAMES, MQTT };
enum OUTPUT_TARGET : uint8_t { API_VERBOSE, API_SHORTNAMES, MQTT, CONSOLE };
bool generate_values(JsonObject & output, const uint8_t tag_filter, const bool nested, const uint8_t output_target);
void generate_values_web(JsonObject & output);
void generate_values_web_all(JsonArray & output);
@@ -244,7 +246,9 @@ class EMSdevice {
void read_command(const uint16_t type_id, uint8_t offset = 0, uint8_t length = 0) const;
bool is_visible(const void * value_p) const;
bool is_readable(const void * value_p) const;
bool is_readonly(const std::string & cmd, const int8_t id) const;
bool has_command(const void * value_p) const;
void publish_value(void * value_p) const;
void publish_all_values();
@@ -318,6 +322,7 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICE_FLAG_EMSPLUS = 2;
static constexpr uint8_t EMS_DEVICE_FLAG_HT3 = 3;
static constexpr uint8_t EMS_DEVICE_FLAG_HEATPUMP = 4;
static constexpr uint8_t EMS_DEVICE_FLAG_HYBRID = 5;
// Solar Module
static constexpr uint8_t EMS_DEVICE_FLAG_SM10 = 1;
@@ -384,10 +389,7 @@ class EMSdevice {
// device values
std::vector<DeviceValue> devicevalues_;
uint8_t dv_index_ = 0; // unique counter for each added device value
uint8_t get_next_dv_id() {
return (dv_index_++);
}
std::vector<uint16_t> handlers_ignored_;
};
} // namespace emsesp

View File

@@ -70,6 +70,12 @@ const __FlashStringHelper * const DeviceValue::DeviceValueTAG_s[] PROGMEM = {
F_(tag_wwc2), // "Wwc2"
F_(tag_wwc3), // "wwc3"
F_(tag_wwc4), // "wwc4"
F_(tag_wwc5), // "wwc5"
F_(tag_wwc6), // "wwc6"
F_(tag_wwc7), // "wwc7"
F_(tag_wwc8), // "wwc8"
F_(tag_wwc9), // "wwc9"
F_(tag_wwc10), // "wwc10"
F_(tag_hs1), // "hs1"
F_(tag_hs2), // "hs2"
F_(tag_hs3), // "hs3"
@@ -109,6 +115,12 @@ const __FlashStringHelper * const DeviceValue::DeviceValueTAG_mqtt[] PROGMEM = {
F_(tag_wwc2), // "Wwc2"
F_(tag_wwc3), // "wwc3"
F_(tag_wwc4), // "wwc4"
F_(tag_wwc5), // "wwc5"
F_(tag_wwc6), // "wwc6"
F_(tag_wwc7), // "wwc7"
F_(tag_wwc8), // "wwc8"
F_(tag_wwc9), // "wwc9"
F_(tag_wwc10), // "wwc10"
F_(tag_hs1), // "hs1"
F_(tag_hs2), // "hs2"
F_(tag_hs3), // "hs3"
@@ -134,7 +146,7 @@ size_t DeviceValue::tag_count = sizeof(DeviceValue::DeviceValueTAG_s) / sizeof(_
// checks whether the device value has an actual value
// returns true if its valid
// state is stored in the dv object
bool DeviceValue::hasValue() {
bool DeviceValue::hasValue() const {
bool has_value = false;
switch (type) {
case DeviceValueType::BOOL:

View File

@@ -90,6 +90,12 @@ class DeviceValue {
TAG_WWC2,
TAG_WWC3,
TAG_WWC4,
TAG_WWC5,
TAG_WWC6,
TAG_WWC7,
TAG_WWC8,
TAG_WWC9,
TAG_WWC10,
TAG_HS1,
TAG_HS2,
TAG_HS3,
@@ -110,11 +116,17 @@ class DeviceValue {
// states of a device value
enum DeviceValueState : uint8_t {
// low nibble active state of the device value
DV_DEFAULT = 0, // 0 - does not yet have a value
DV_ACTIVE = (1 << 0), // 1 - has a validated real value
DV_VISIBLE = (1 << 1), // 2 - shown on web, console and on MQTT payload. Otherwise hidden
DV_HA_CONFIG_CREATED = (1 << 2), // 4 - set if the HA config topic has been created
DV_HA_CLIMATE_NO_RT = (1 << 3) // 8 - climate created without roomTemp
DV_HA_CONFIG_CREATED = (1 << 1), // 2 - set if the HA config topic has been created
DV_HA_CLIMATE_NO_RT = (1 << 2), // 4 - climate created without roomTemp
// high nibble as mask for exclusions & special functions
DV_WEB_EXCLUDE = (1 << 4), // 16 - not shown on web
DV_API_MQTT_EXCLUDE = (1 << 5), // 32 - not shown on mqtt, API
DV_READONLY = (1 << 6), // 64 - read only
DV_FAVORITE = (1 << 7) // 128 - sort to front
};
uint8_t device_type; // EMSdevice::DeviceType
@@ -131,7 +143,6 @@ class DeviceValue {
int16_t min; // min range
uint16_t max; // max range
uint8_t state; // DeviceValueState::*
uint8_t id; // internal unique counter
DeviceValue(uint8_t device_type,
uint8_t tag,
@@ -146,8 +157,7 @@ class DeviceValue {
bool has_cmd,
int16_t min,
uint16_t max,
uint8_t state,
uint8_t id)
uint8_t state)
: device_type(device_type)
, tag(tag)
, value_p(value_p)
@@ -161,24 +171,23 @@ class DeviceValue {
, has_cmd(has_cmd)
, min(min)
, max(max)
, state(state)
, id(id) {
, state(state) {
}
bool hasValue();
bool hasValue() const;
bool get_min_max(int16_t & dv_set_min, int16_t & dv_set_max);
// state flags
inline void add_state(uint8_t s) {
void add_state(uint8_t s) {
state |= s;
}
inline bool has_state(uint8_t s) const {
bool has_state(uint8_t s) const {
return (state & s) == s;
}
inline void remove_state(uint8_t s) {
void remove_state(uint8_t s) {
state &= ~s;
}
inline uint8_t get_state() const {
uint8_t get_state() const {
return state;
}

View File

@@ -105,6 +105,15 @@ void EMSESP::fetch_device_values_type(const uint8_t device_type) {
}
}
bool EMSESP::cmd_is_readonly(const uint8_t device_type, const char * cmd, const int8_t id) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
return emsdevice->is_readonly(cmd, id);
}
}
return false;
}
// clears list of recognized devices
void EMSESP::clear_all_devices() {
// temporarily removed: clearing the list causes a crash, the associated commands and mqtt should also be removed.
@@ -308,7 +317,7 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XXLARGE_DYN); // use max size
JsonObject json = doc.to<JsonObject>();
emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::API_VERBOSE); // verbose mode and nested
emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::CONSOLE); // verbose mode and nested
// print line
uint8_t id = 0;
@@ -845,6 +854,9 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
if (wait_validate_ == telegram->type_id) {
wait_validate_ = 0;
}
if (!found && emsdevice->is_device_id(telegram->src) && telegram->message_length > 0) {
emsdevice->add_handlers_ignored(telegram->type_id);
}
break;
}
}
@@ -1090,7 +1102,7 @@ bool EMSESP::command_commands(uint8_t device_type, JsonObject & output, const in
bool EMSESP::command_info(uint8_t device_type, JsonObject & output, const int8_t id, const uint8_t output_target) {
bool has_value = false;
uint8_t tag;
if (id >= 1 && id <= 29) {
if (id >= 1 && id <= 34) {
tag = DeviceValueTAG::TAG_HC1 + id - 1; // this sets also WWC and HS
} else if (id == -1 || id == 0) {
tag = DeviceValueTAG::TAG_NONE;

View File

@@ -128,6 +128,7 @@ class EMSESP {
static void send_raw_telegram(const char * data);
static bool device_exists(const uint8_t device_id);
static bool cmd_is_readonly(const uint8_t device_type, const char * cmd, const int8_t id);
static uint8_t count_devices(const uint8_t device_type);
static uint8_t count_devices();

View File

@@ -189,12 +189,6 @@ char * Helpers::render_boolean(char * result, bool value) {
return result;
}
// // render for native char strings
// char * Helpers::render_value(char * result, const char * value, const int8_t format __attribute__((unused))) {
// strcpy(result, value); // un-safe but we don't care
// 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) {

View File

@@ -37,7 +37,6 @@ class Helpers {
static char * render_value(char * result, const uint32_t value, const int8_t format, const uint8_t fahrenheit = 0);
static char * render_value(char * result, const int16_t value, const int8_t format, const uint8_t fahrenheit = 0);
static char * render_value(char * result, const int32_t value, const int8_t format, const uint8_t fahrenheit = 0);
// static char * render_value(char * result, const char * value, const int8_t format);
static char * render_boolean(char * result, bool value);
static char * hextoa(char * result, const uint8_t value);

View File

@@ -218,6 +218,12 @@ MAKE_PSTR(tag_wwc1, "wwc1")
MAKE_PSTR(tag_wwc2, "wwc2")
MAKE_PSTR(tag_wwc3, "wwc3")
MAKE_PSTR(tag_wwc4, "wwc4")
MAKE_PSTR(tag_wwc5, "wwc5")
MAKE_PSTR(tag_wwc6, "wwc6")
MAKE_PSTR(tag_wwc7, "wwc7")
MAKE_PSTR(tag_wwc8, "wwc8")
MAKE_PSTR(tag_wwc9, "wwc9")
MAKE_PSTR(tag_wwc10, "wwc10")
MAKE_PSTR(tag_hs1, "hs1")
MAKE_PSTR(tag_hs2, "hs2")
MAKE_PSTR(tag_hs3, "hs3")
@@ -330,7 +336,7 @@ MAKE_PSTR(functioning_mode, "functioning mode")
MAKE_PSTR(smoke_temperature, "Abgastemperatur")
// thermostat lists
MAKE_PSTR_LIST(tpl_datetime, F("Format: < hh:mm:ss dd/mm/yyyy-dw-dst | NTP >"))
MAKE_PSTR_LIST(tpl_datetime, F("Format: < NTP | dd.mm.yyyy-hh:mm:ss-dw-dst >"))
MAKE_PSTR_LIST(tpl_switchtime, F("Format: < nn.d.o.hh:mm >"))
MAKE_PSTR_LIST(tpl_holidays, F("Format: < dd.mm.yyyy-dd.mm.yyyy >"))
MAKE_PSTR_LIST(enum_ibaMainDisplay,
@@ -344,6 +350,7 @@ MAKE_PSTR_LIST(enum_ibaMainDisplay,
F_(date),
F_(smoke_temperature))
MAKE_PSTR_LIST(enum_ibaLanguage, F_(german), F_(dutch), F_(french), F_(italian))
MAKE_PSTR_LIST(enum_ibaLanguage_RC30, F_(german), F_(dutch))
MAKE_PSTR_LIST(enum_floordrystatus, F_(off), F_(start), F_(heat), F_(hold), F_(cool), F_(end))
MAKE_PSTR_LIST(enum_ibaBuildingType, F_(light), F_(medium), F_(heavy)) // RC300
MAKE_PSTR_LIST(enum_PID, F("fast"), F_(medium), F("slow"))
@@ -369,6 +376,7 @@ MAKE_PSTR_LIST(enum_modetype4, F_(nofrost), F_(eco), F_(heat))
MAKE_PSTR_LIST(enum_modetype5, F_(off), F_(on))
MAKE_PSTR_LIST(enum_reducemode, F_(nofrost), F_(reduce), F_(room), F_(outdoor))
MAKE_PSTR_LIST(enum_nofrostmode, F_(off), F_(room), F_(outdoor))
MAKE_PSTR_LIST(enum_controlmode, F_(off), F_(optimized), F_(simple), F_(mpc), F_(room), F_(power), F_(constant))
MAKE_PSTR_LIST(enum_controlmode2, F_(outdoor), F_(room))
@@ -451,6 +459,7 @@ MAKE_PSTR_LIST(setBurnPow, F("setburnpow"), F("Sollwert Brennerleistung"))
MAKE_PSTR_LIST(curBurnPow, F("curburnpow"), F("Brennerleistung"))
MAKE_PSTR_LIST(burnStarts, F("burnstarts"), F("Brenner # starts"))
MAKE_PSTR_LIST(burnWorkMin, F("burnworkmin"), F("Brenner Laufzeit"))
MAKE_PSTR_LIST(burn2WorkMin, F("burn2workmin"), F("Brenner Stufe 2 Laufzeit"))
MAKE_PSTR_LIST(heatWorkMin, F("heatworkmin"), F("Heizung Laufzeit"))
MAKE_PSTR_LIST(UBAuptime, F("ubauptime"), F("gesamte Laufzeit"))
MAKE_PSTR_LIST(lastCode, F("lastcode"), F("Fehlerspeicher"))
@@ -514,6 +523,16 @@ MAKE_PSTR_LIST(hpTl2, F("hptl2"), F("Außenlufttemperaturfühler (TL2)"))
MAKE_PSTR_LIST(hpPl1, F("hppl1"), F("Niedrigdruckfühler (PL1)"))
MAKE_PSTR_LIST(hpPh1, F("hpph1"), F("Hochdruckfühler (PH1)"))
// hybrid heatpump
MAKE_PSTR_LIST(enum_hybridStrategy, F("co2-optimized"), F("cost-optimized"), F("outside-temp-switched"), F("co2-cost-mix"))
MAKE_PSTR_LIST(hybridStrategy, F("hybridstrategy"), F("hybrid control strategy"))
MAKE_PSTR_LIST(switchOverTemp, F("switchovertemp"), F("outside switchover temperature"))
MAKE_PSTR_LIST(energyCostRatio, F("energycostratio"), F("energy cost ratio"))
MAKE_PSTR_LIST(fossileFactor, F("fossilefactor"), F("fossile energy factor"))
MAKE_PSTR_LIST(electricFactor, F("electricfactor"), F("electric energy factor"))
MAKE_PSTR_LIST(delayBoiler, F("delayboiler"), F("delay boiler support"))
MAKE_PSTR_LIST(tempDiffBoiler, F("tempdiffboiler"), F("tempediff boiler support"))
// the following are dhw for the boiler and automatically tagged with 'ww'
MAKE_PSTR_LIST(wWSelTemp, F("wwseltemp"), F("gewählte Temperatur"))
MAKE_PSTR_LIST(wwSelTempLow, F("wwseltemplow"), F("selected lower temperature"))
@@ -556,6 +575,7 @@ MAKE_PSTR_LIST(wwMaxTemp, F("wwmaxtemp"), F("Maximale Temperatur"))
MAKE_PSTR_LIST(wwOneTimeKey, F("wwonetimekey"), F("Einmalladungstaste"))
// mqtt values / commands
MAKE_PSTR_LIST(switchtime, F("switchtime"), F("program switchtime"))
MAKE_PSTR_LIST(switchtime1, F("switchtime1"), F("own1 program switchtime"))
MAKE_PSTR_LIST(switchtime2, F("switchtime2"), F("own2 program switchtime"))
MAKE_PSTR_LIST(wwswitchtime, F("wwswitchtime"), F("program switchtime"))
@@ -576,6 +596,11 @@ MAKE_PSTR_LIST(tempsensor2, F("inttemp2"), F("Temperatursensor 2"))
MAKE_PSTR_LIST(dampedoutdoortemp, F("dampedoutdoortemp"), F("gedämpfte Aussentemperatur"))
MAKE_PSTR_LIST(floordrystatus, F("floordry"), F("Estrichtrocknung"))
MAKE_PSTR_LIST(floordrytemp, F("floordrytemp"), F("Estrichtrocknungs Temperatur"))
MAKE_PSTR_LIST(brightness, F("brightness"), F("bildschirmhelligkeit"))
MAKE_PSTR_LIST(autodst, F("autodst"), F("automatische sommerzeit umstellung"))
MAKE_PSTR_LIST(preheating, F("preheating"), F("vorheizen im uhrenprogramm"))
MAKE_PSTR_LIST(offtemp, F("offtemp"), F("temperatur bei ausgeschaltetem modus"))
MAKE_PSTR_LIST(mixingvalves, F("mixingvalves"), F("mischventile"))
// thermostat ww
MAKE_PSTR_LIST(wwMode, F("wwmode"), F("modus"))
MAKE_PSTR_LIST(wwSetTempLow, F("wwsettemplow"), F("untere Solltemperatur"))
@@ -632,6 +657,10 @@ MAKE_PSTR_LIST(holidaymode, F("holidaymode"), F("Urlaubsbetrieb"))
MAKE_PSTR_LIST(flowtempoffset, F("flowtempoffset"), F("Flusstemperaturanhebung"))
MAKE_PSTR_LIST(reducemode, F("reducemode"), F("Absenkmodus"))
MAKE_PSTR_LIST(noreducetemp, F("noreducetemp"), F("Absenkung unterbrechen unter Temperatur"))
MAKE_PSTR_LIST(reducetemp, F("reducetemp"), F("Absenkmodus unter Temperatur"))
MAKE_PSTR_LIST(vacreducetemp, F("vacreducetemp"), F("Urlaub Absenkmodus unter Temperatur"))
MAKE_PSTR_LIST(vacreducemode, F("vacreducemode"), F("Urlaub Absenkmodus"))
MAKE_PSTR_LIST(nofrostmode, F("nofrostmode"), F("Frostschutz Modus"))
MAKE_PSTR_LIST(remotetemp, F("remotetemp"), F("Raumtemperatur der Fernsteuerung"))
MAKE_PSTR_LIST(reducehours, F("reducehours"), F("duration for nighttemp"))
MAKE_PSTR_LIST(reduceminutes, F("reduceminutes"), F("remaining time for nightmode"))
@@ -656,6 +685,7 @@ MAKE_PSTR_LIST(poolSetTemp, F("poolsettemp"), F("pool set temperature"))
MAKE_PSTR_LIST(poolTemp, F("pooltemp"), F("pool temperature"))
MAKE_PSTR_LIST(poolShuntStatus, F("poolshuntstatus"), F("pool shunt status opening/closing"))
MAKE_PSTR_LIST(poolShunt, F("poolshunt"), F("pool shunt open/close (0% = pool / 100% = heat)"))
MAKE_PSTR_LIST(hydrTemp, F("hydrTemp"), F("hydraulic header temperature"))
// solar
MAKE_PSTR_LIST(collectorTemp, F("collectortemp"), F("Kollektortemperatur (TS1)"))

View File

@@ -218,6 +218,12 @@ MAKE_PSTR(tag_wwc1, "wwc1")
MAKE_PSTR(tag_wwc2, "wwc2")
MAKE_PSTR(tag_wwc3, "wwc3")
MAKE_PSTR(tag_wwc4, "wwc4")
MAKE_PSTR(tag_wwc5, "wwc5")
MAKE_PSTR(tag_wwc6, "wwc6")
MAKE_PSTR(tag_wwc7, "wwc7")
MAKE_PSTR(tag_wwc8, "wwc8")
MAKE_PSTR(tag_wwc9, "wwc9")
MAKE_PSTR(tag_wwc10, "wwc10")
MAKE_PSTR(tag_hs1, "hs1")
MAKE_PSTR(tag_hs2, "hs2")
MAKE_PSTR(tag_hs3, "hs3")
@@ -331,7 +337,7 @@ MAKE_PSTR(functioning_mode, "functioning mode")
MAKE_PSTR(smoke_temperature, "smoke temperature")
// thermostat lists
MAKE_PSTR_LIST(tpl_datetime, F("Format: < NTP | hh:mm:ss dd.mm.yyyy-dw-dst >"))
MAKE_PSTR_LIST(tpl_datetime, F("Format: < NTP | dd.mm.yyyy-hh:mm:ss-dw-dst >"))
MAKE_PSTR_LIST(tpl_switchtime, F("Format: <nn> [ not_set | day hh:mm on|off ]"))
MAKE_PSTR_LIST(tpl_holidays, F("format: < dd.mm.yyyy-dd.mm.yyyy >"))
MAKE_PSTR_LIST(enum_ibaMainDisplay,
@@ -345,6 +351,7 @@ MAKE_PSTR_LIST(enum_ibaMainDisplay,
F_(date),
F_(smoke_temperature))
MAKE_PSTR_LIST(enum_ibaLanguage, F_(german), F_(dutch), F_(french), F_(italian))
MAKE_PSTR_LIST(enum_ibaLanguage_RC30, F_(german), F_(dutch))
MAKE_PSTR_LIST(enum_floordrystatus, F_(off), F_(start), F_(heat), F_(hold), F_(cool), F_(end))
MAKE_PSTR_LIST(enum_ibaBuildingType, F_(light), F_(medium), F_(heavy))
MAKE_PSTR_LIST(enum_PID, F("fast"), F_(medium), F("slow"))
@@ -370,6 +377,7 @@ MAKE_PSTR_LIST(enum_modetype4, F_(nofrost), F_(eco), F_(heat))
MAKE_PSTR_LIST(enum_modetype5, F_(off), F_(on))
MAKE_PSTR_LIST(enum_reducemode, F_(nofrost), F_(reduce), F_(room), F_(outdoor))
MAKE_PSTR_LIST(enum_nofrostmode, F_(off), F_(room), F_(outdoor))
MAKE_PSTR_LIST(enum_controlmode, F_(off), F_(optimized), F_(simple), F_(mpc), F_(room), F_(power), F_(constant))
MAKE_PSTR_LIST(enum_controlmode2, F_(outdoor), F_(room))
@@ -441,6 +449,7 @@ MAKE_PSTR_LIST(setBurnPow, F("setburnpow"), F("burner set power"))
MAKE_PSTR_LIST(curBurnPow, F("curburnpow"), F("burner current power"))
MAKE_PSTR_LIST(burnStarts, F("burnstarts"), F("burner starts"))
MAKE_PSTR_LIST(burnWorkMin, F("burnworkmin"), F("total burner operating time"))
MAKE_PSTR_LIST(burn2WorkMin, F("burn2workmin"), F("burner stage 2 operating time"))
MAKE_PSTR_LIST(heatWorkMin, F("heatworkmin"), F("total heat operating time"))
MAKE_PSTR_LIST(UBAuptime, F("ubauptime"), F("total UBA operating time"))
MAKE_PSTR_LIST(lastCode, F("lastcode"), F("last error code"))
@@ -452,7 +461,7 @@ MAKE_PSTR_LIST(maintenanceType, F_(maintenance), F("maintenance scheduled"))
MAKE_PSTR_LIST(maintenanceTime, F("maintenancetime"), F("time to next maintenance"))
// heatpump/compress specific
MAKE_PSTR_LIST(upTimeControl, F("uptimecontrol"), F("operating time total heat"))
MAKE_PSTR_LIST(upTimeControl, F("uptimecontrol"), F("total operating time heat"))
MAKE_PSTR_LIST(upTimeCompHeating, F("uptimecompheating"), F("operating time compressor heating"))
MAKE_PSTR_LIST(upTimeCompCooling, F("uptimecompcooling"), F("operating time compressor cooling"))
MAKE_PSTR_LIST(upTimeCompWw, F("uptimecompww"), F("operating time compressor dhw"))
@@ -462,7 +471,7 @@ MAKE_PSTR_LIST(heatingStarts, F("heatingstarts"), F("heating control starts"))
MAKE_PSTR_LIST(coolingStarts, F("coolingstarts"), F("cooling control starts"))
MAKE_PSTR_LIST(poolStarts, F("poolstarts"), F("pool control starts"))
MAKE_PSTR_LIST(nrgConsTotal, F("nrgconstotal"), F("total energy consumption"))
MAKE_PSTR_LIST(nrgConsCompTotal, F("nrgconscomptotal"), F("energy consumption compressor total"))
MAKE_PSTR_LIST(nrgConsCompTotal, F("nrgconscomptotal"), F("total energy consumption compressor"))
MAKE_PSTR_LIST(nrgConsCompHeating, F("nrgconscompheating"), F("energy consumption compressor heating"))
MAKE_PSTR_LIST(nrgConsCompWw, F("nrgconscompww"), F("energy consumption compressor dhw"))
MAKE_PSTR_LIST(nrgConsCompCooling, F("nrgconscompcooling"), F("energy consumption compressor cooling"))
@@ -472,7 +481,7 @@ MAKE_PSTR_LIST(nrgSuppHeating, F("nrgsuppheating"), F("total energy supplied hea
MAKE_PSTR_LIST(nrgSuppWw, F("nrgsuppww"), F("total energy warm supplied dhw"))
MAKE_PSTR_LIST(nrgSuppCooling, F("nrgsuppcooling"), F("total energy supplied cooling"))
MAKE_PSTR_LIST(nrgSuppPool, F("nrgsupppool"), F("total energy supplied pool"))
MAKE_PSTR_LIST(auxElecHeatNrgConsTotal, F("auxelecheatnrgconstotal"), F("auxiliary electrical heater energy consumption total"))
MAKE_PSTR_LIST(auxElecHeatNrgConsTotal, F("auxelecheatnrgconstotal"), F("total auxiliary electrical heater energy consumption"))
MAKE_PSTR_LIST(auxElecHeatNrgConsHeating, F("auxelecheatnrgconsheating"), F("auxiliary electrical heater energy consumption heating"))
MAKE_PSTR_LIST(auxElecHeatNrgConsWW, F("auxelecheatnrgconsww"), F("auxiliary electrical heater energy consumption dhw"))
MAKE_PSTR_LIST(auxElecHeatNrgConsPool, F("auxelecheatnrgconspool"), F("auxiliary electrical heater energy consumption pool"))
@@ -504,6 +513,16 @@ MAKE_PSTR_LIST(hpTl2, F("hptl2"), F("air inlet temperature (TL2)"))
MAKE_PSTR_LIST(hpPl1, F("hppl1"), F("low pressure side temperature (PL1)"))
MAKE_PSTR_LIST(hpPh1, F("hpph1"), F("high pressure side temperature (PH1)"))
// hybrid heatpump
MAKE_PSTR_LIST(enum_hybridStrategy, F("co2-optimized"), F("cost-optimized"), F("outside-temp-switched"), F("co2-cost-mix"))
MAKE_PSTR_LIST(hybridStrategy, F("hybridstrategy"), F("hybrid control strategy"))
MAKE_PSTR_LIST(switchOverTemp, F("switchovertemp"), F("outside switchover temperature"))
MAKE_PSTR_LIST(energyCostRatio, F("energycostratio"), F("energy cost ratio"))
MAKE_PSTR_LIST(fossileFactor, F("fossilefactor"), F("fossile energy factor"))
MAKE_PSTR_LIST(electricFactor, F("electricfactor"), F("electric energy factor"))
MAKE_PSTR_LIST(delayBoiler, F("delayboiler"), F("delay boiler support"))
MAKE_PSTR_LIST(tempDiffBoiler, F("tempdiffboiler"), F("tempediff boiler support"))
// the following are dhw for the boiler and automatically tagged with 'ww'
MAKE_PSTR_LIST(wwSelTemp, F("wwseltemp"), F("selected temperature"))
MAKE_PSTR_LIST(wwSelTempLow, F("wwseltemplow"), F("selected lower temperature"))
@@ -536,7 +555,7 @@ MAKE_PSTR_LIST(wwSetPumpPower, F("wwsetpumppower"), F("set pump power"))
MAKE_PSTR_LIST(wwMixerTemp, F("wwmixertemp"), F("mixer temperature"))
MAKE_PSTR_LIST(wwCylMiddleTemp, F("wwcylmiddletemp"), F("cylinder middle temperature (TS3)"))
MAKE_PSTR_LIST(wwStarts, F("wwstarts"), F("starts"))
MAKE_PSTR_LIST(wwStarts2, F("wwstarts2"), F("control starts"))
MAKE_PSTR_LIST(wwStarts2, F("wwstarts2"), F("control starts2"))
MAKE_PSTR_LIST(wwWorkM, F("wwworkm"), F("active time"))
MAKE_PSTR_LIST(wwHystOn, F("wwhyston"), F("hysteresis on temperature"))
MAKE_PSTR_LIST(wwHystOff, F("wwhystoff"), F("hysteresis off temperature"))
@@ -567,6 +586,11 @@ MAKE_PSTR_LIST(tempsensor2, F("inttemp2"), F("temperature sensor 2"))
MAKE_PSTR_LIST(dampedoutdoortemp, F("dampedoutdoortemp"), F("damped outdoor temperature"))
MAKE_PSTR_LIST(floordrystatus, F("floordry"), F("floor drying"))
MAKE_PSTR_LIST(floordrytemp, F("floordrytemp"), F("floor drying temperature"))
MAKE_PSTR_LIST(brightness, F("brightness"), F("screen brightness"))
MAKE_PSTR_LIST(autodst, F("autodst"), F("automatic change daylight saving time"))
MAKE_PSTR_LIST(preheating, F("preheating"), F("preheating in the clock program"))
MAKE_PSTR_LIST(offtemp, F("offtemp"), F("temperature when mode is off"))
MAKE_PSTR_LIST(mixingvalves, F("mixingvalves"), F("mixing valves"))
// thermostat ww
MAKE_PSTR_LIST(wwMode, F("wwmode"), F("mode"))
MAKE_PSTR_LIST(wwSetTempLow, F("wwsettemplow"), F("set low temperature"))
@@ -623,6 +647,10 @@ MAKE_PSTR_LIST(holidaymode, F("holidaymode"), F("holiday mode"))
MAKE_PSTR_LIST(flowtempoffset, F("flowtempoffset"), F("flow temperature offset for mixer"))
MAKE_PSTR_LIST(reducemode, F("reducemode"), F("reduce mode"))
MAKE_PSTR_LIST(noreducetemp, F("noreducetemp"), F("no reduce below temperature"))
MAKE_PSTR_LIST(reducetemp, F("reducetemp"), F("off/reduce switch temperature"))
MAKE_PSTR_LIST(vacreducetemp, F("vacreducetemp"), F("vacations off/reduce switch temperature"))
MAKE_PSTR_LIST(vacreducemode, F("vacreducemode"), F("vacations reduce mode"))
MAKE_PSTR_LIST(nofrostmode, F("nofrostmode"), F("nofrost mode"))
MAKE_PSTR_LIST(remotetemp, F("remotetemp"), F("room temperature from remote"))
MAKE_PSTR_LIST(reducehours, F("reducehours"), F("duration for nighttemp"))
MAKE_PSTR_LIST(reduceminutes, F("reduceminutes"), F("remaining time for nightmode"))
@@ -647,6 +675,7 @@ MAKE_PSTR_LIST(poolSetTemp, F("poolsettemp"), F("pool set temperature"))
MAKE_PSTR_LIST(poolTemp, F("pooltemp"), F("pool temperature"))
MAKE_PSTR_LIST(poolShuntStatus, F("poolshuntstatus"), F("pool shunt status opening/closing"))
MAKE_PSTR_LIST(poolShunt, F("poolshunt"), F("pool shunt open/close (0% = pool / 100% = heat)"))
MAKE_PSTR_LIST(hydrTemp, F("hydrTemp"), F("hydraulic header temperature"))
// solar
MAKE_PSTR_LIST(collectorTemp, F("collectortemp"), F("collector temperature (TS1)"))
@@ -675,8 +704,8 @@ MAKE_PSTR_LIST(pumpWorkTime, F("pumpworktime"), F("pump working time"))
MAKE_PSTR_LIST(pump2WorkTime, F("pump2worktime"), F("pump 2 working time"))
MAKE_PSTR_LIST(m1WorkTime, F("m1worktime"), F("differential control working time"))
MAKE_PSTR_LIST(energyLastHour, F("energylasthour"), F("energy last hour"))
MAKE_PSTR_LIST(energyTotal, F("energytotal"), F("energy total"))
MAKE_PSTR_LIST(energyToday, F("energytoday"), F("energy today"))
MAKE_PSTR_LIST(energyTotal, F("energytotal"), F("total energy"))
MAKE_PSTR_LIST(energyToday, F("energytoday"), F("total energy today"))
MAKE_PSTR_LIST(pumpMinMod, F("pumpminmod"), F("minimum pump modulation"))
MAKE_PSTR_LIST(maxFlow, F("maxflow"), F("maximum solar flow"))
MAKE_PSTR_LIST(solarPower, F("solarpower"), F("actual solar power"))

View File

@@ -386,6 +386,10 @@ void Mqtt::on_publish(uint16_t packetId) const {
// called when MQTT settings have changed via the Web forms
void Mqtt::reset_mqtt() {
if (!mqtt_enabled_) {
mqtt_messages_.clear();
}
if (!mqttClient_) {
return;
}
@@ -648,6 +652,7 @@ void Mqtt::ha_status() {
}
publish_system_ha_sensor_config(DeviceValueType::INT, F("Uptime"), F("uptime"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Uptime (sec)"), F("uptime_sec"), DeviceValueUOM::SECONDS);
publish_system_ha_sensor_config(DeviceValueType::BOOL, F("NTP status"), F("ntp_status"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Free memory"), F("freemem"), DeviceValueUOM::KB);
publish_system_ha_sensor_config(DeviceValueType::INT, F("MQTT fails"), F("mqttfails"), DeviceValueUOM::NONE);
publish_system_ha_sensor_config(DeviceValueType::INT, F("Rx received"), F("rxreceived"), DeviceValueUOM::NONE);
@@ -918,6 +923,10 @@ void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model,
int16_t dv_set_min, dv_set_max;
(void)dv.get_min_max(dv_set_min, dv_set_max);
// determine if we're creating the command topics which we use special HA configs
// unless the entity has been marked as read-only and so it'll default to using the sensor/ type
bool has_cmd = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
publish_ha_sensor_config(dv.type,
dv.tag,
dv.full_name,
@@ -925,7 +934,7 @@ void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model,
dv.short_name,
dv.uom,
remove,
dv.has_cmd,
has_cmd,
dv.options,
dv.options_size,
dv_set_min,
@@ -988,7 +997,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
// create the topic, depending on the type and whether the device entity is writable (a command)
// https://developers.home-assistant.io/docs/core/entity
char topic[MQTT_TOPIC_MAX_SIZE];
// if it's a command then we can use Number, Switch. Otherwise stick to Sensor
// if it's a command then we can use Number, Switch, Select. Otherwise stick to Sensor
if (has_cmd) {
switch (type) {
case DeviceValueType::INT:
@@ -996,13 +1005,13 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
case DeviceValueType::SHORT:
case DeviceValueType::USHORT:
case DeviceValueType::ULONG:
// number - https://www.home-assistant.io/integrations/number.mqtt/
// number - https://www.home-assistant.io/integrations/number.mqtt
// https://developers.home-assistant.io/docs/core/entity/number
snprintf(topic, sizeof(topic), "number/%s/%s/config", mqtt_base_.c_str(), uniq);
break;
case DeviceValueType::BOOL:
// switch - https://www.home-assistant.io/integrations/switch.mqtt/
// switch - https://www.home-assistant.io/integrations/switch.mqtt
snprintf(topic, sizeof(topic), "switch/%s/%s/config", mqtt_base_.c_str(), uniq);
break;
case DeviceValueType::ENUM:

View File

@@ -543,6 +543,18 @@ bool System::heartbeat_json(JsonObject & output) {
output["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
output["uptime_sec"] = uuid::get_uptime_sec();
bool value_b = EMSESP::system_.ntp_connected();
if (Mqtt::ha_enabled()) {
char s[7];
output["ntp_status"] = Helpers::render_boolean(s, value_b); // for HA always render as string
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
output["ntp_status"] = value_b;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
output["ntp_status"] = value_b ? 1 : 0;
} else {
char s[7];
output["ntp_status"] = Helpers::render_boolean(s, value_b);
}
output["rxreceived"] = EMSESP::rxservice_.telegram_count();
output["rxfails"] = EMSESP::rxservice_.telegram_error_count();
output["txreads"] = EMSESP::txservice_.telegram_read_count();
@@ -949,6 +961,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
node["publish_time_other"] = settings.publish_time_other;
node["publish_time_sensor"] = settings.publish_time_sensor;
node["publish_single"] = settings.publish_single;
node["publish_2_command"] = settings.publish_single2cmd;
node["send_response"] = settings.send_response;
});
@@ -1016,7 +1029,6 @@ bool System::command_customizations(const char * value, const int8_t id, JsonObj
JsonObject node = output.createNestedObject("Customizations");
// hide ssid from this list
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
// sensors
JsonArray sensorsJson = node.createNestedArray("sensors");
@@ -1051,16 +1063,16 @@ bool System::command_customizations(const char * value, const int8_t id, JsonObj
}
}
// exclude entities
JsonArray exclude_entitiesJson = node.createNestedArray("exclude_entities");
// masked entities
JsonArray mask_entitiesJson = node.createNestedArray("masked_entities");
for (const auto & entityCustomization : settings.entityCustomizations) {
JsonObject entityJson = exclude_entitiesJson.createNestedObject();
JsonObject entityJson = mask_entitiesJson.createNestedObject();
entityJson["product_id"] = entityCustomization.product_id;
entityJson["device_id"] = entityCustomization.device_id;
JsonArray exclude_entityJson = entityJson.createNestedArray("entity_ids");
for (uint8_t entity_id : entityCustomization.entity_ids) {
exclude_entityJson.add(entity_id);
JsonArray mask_entityJson = entityJson.createNestedArray("entities");
for (std::string entity_id : entityCustomization.entity_ids) {
mask_entityJson.add(entity_id);
}
}
});
@@ -1079,6 +1091,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
node["version"] = EMSESP_APP_VERSION;
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["uptime (seconds)"] = uuid::get_uptime_sec();
node["network time"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected";
#ifndef EMSESP_STANDALONE
node["freemem"] = ESP.getFreeHeap() / 1000L; // kilobytes
@@ -1191,7 +1204,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
obj["product id"] = emsdevice->product_id();
obj["version"] = emsdevice->version();
obj["entities"] = emsdevice->count_entities();
char result[250];
char result[300];
(void)emsdevice->show_telegram_handlers(result, sizeof(result), EMSdevice::Handlers::RECEIVED);
if (result[0] != '\0') {
obj["handlers received"] = result; // don't show handlers if there aren't any
@@ -1204,6 +1217,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
if (result[0] != '\0') {
obj["handlers pending"] = result;
}
(void)emsdevice->show_telegram_handlers(result, sizeof(result), EMSdevice::Handlers::IGNORED);
if (result[0] != '\0') {
obj["handlers ignored"] = result;
}
}
}
}
@@ -1321,4 +1338,13 @@ std::string System::reset_reason(uint8_t cpu) const {
return ("Unkonwn");
}
// set NTP status
void System::ntp_connected(bool b) {
if (b != ntp_connected_) {
LOG_INFO(b ? F("NTP connected") : F("NTP disconnected"));
}
ntp_connected_ = b;
ntp_last_check_ = b ? uuid::get_uptime_sec() : 0;
}
} // namespace emsesp

View File

@@ -154,6 +154,16 @@ class System {
ethernet_connected_ = b;
}
void ntp_connected(bool b);
bool ntp_connected() {
// timeout 2 hours, ntp sync is normally every hour.
if ((uuid::get_uptime_sec() - ntp_last_check_ > 7201) && ntp_connected_) {
ntp_connected(false);
}
return ntp_connected_;
}
bool network_connected() {
#ifndef EMSESP_STANDALONE
return (ethernet_connected() || WiFi.isConnected());
@@ -218,6 +228,9 @@ class System {
bool upload_status_ = false; // true if we're in the middle of a OTA firmware upload
bool ethernet_connected_ = false;
bool ntp_connected_ = false;
uint32_t ntp_last_check_ = 0;
// EMS-ESP settings
// copies from WebSettings class in WebSettingsService.h and loaded with reload_settings()
std::string hostname_ = FACTORY_WIFI_HOSTNAME;

View File

@@ -595,8 +595,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
EMSESP::system_.healthcheck(n);
}
if (command == "exclude") {
shell.printfln(F("Testing exclude entities"));
if (command == "masked") {
shell.printfln(F("Testing masked entities"));
Mqtt::ha_enabled(true);
Mqtt::send_response(false);
@@ -609,8 +609,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
// toggle mode
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->unique_id() == 1) { // boiler
uint8_t entity_id = 47; // wwseltemp
emsdevice->exclude_entity(entity_id);
std::string a = "07wwseltemp";
emsdevice->mask_entity(a);
break;
}
}

View File

@@ -31,12 +31,12 @@ namespace emsesp {
// #define EMSESP_DEBUG_DEFAULT "mixer"
// #define EMSESP_DEBUG_DEFAULT "web"
// #define EMSESP_DEBUG_DEFAULT "mqtt"
// #define EMSESP_DEBUG_DEFAULT "general"
#define EMSESP_DEBUG_DEFAULT "general"
// #define EMSESP_DEBUG_DEFAULT "boiler"
// #define EMSESP_DEBUG_DEFAULT "mqtt2"
// #define EMSESP_DEBUG_DEFAULT "mqtt_nested"
#define EMSESP_DEBUG_DEFAULT "ha"
// #define EMSESP_DEBUG_DEFAULT "exclude"
// #define EMSESP_DEBUG_DEFAULT "ha"
// #define EMSESP_DEBUG_DEFAULT "masked"
// #define EMSESP_DEBUG_DEFAULT "board_profile"
// #define EMSESP_DEBUG_DEFAULT "shower_alert"
// #define EMSESP_DEBUG_DEFAULT "310"

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.4.0b8"
#define EMSESP_APP_VERSION "3.4.0b12"

View File

@@ -31,8 +31,8 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f
securityManager,
AuthenticationPredicates::IS_AUTHENTICATED)
, _fsPersistence(WebCustomization::read, WebCustomization::update, this, fs, EMSESP_CUSTOMIZATION_FILE)
, _exclude_entities_handler(EXCLUDE_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::exclude_entities, this, _1, _2),
, _masked_entities_handler(MASKED_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::masked_entities, this, _1, _2),
AuthenticationPredicates::IS_AUTHENTICATED))
, _device_entities_handler(DEVICE_ENTITIES_PATH,
securityManager->wrapCallback(std::bind(&WebCustomizationService::device_entities, this, _1, _2),
@@ -45,16 +45,17 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f
HTTP_POST,
securityManager->wrapRequest(std::bind(&WebCustomizationService::reset_customization, this, _1), AuthenticationPredicates::IS_ADMIN));
_exclude_entities_handler.setMethod(HTTP_POST);
_exclude_entities_handler.setMaxContentLength(1024);
server->addHandler(&_exclude_entities_handler);
_masked_entities_handler.setMethod(HTTP_POST);
_masked_entities_handler.setMaxContentLength(4096);
_masked_entities_handler.setMaxJsonBufferSize(4096);
server->addHandler(&_masked_entities_handler);
_device_entities_handler.setMethod(HTTP_POST);
_device_entities_handler.setMaxContentLength(256);
server->addHandler(&_device_entities_handler);
}
// this creates the customization file, saving to the FS
// this creates the customization file, saving it to the FS
void WebCustomization::read(WebCustomization & settings, JsonObject & root) {
// Dallas Sensor customization
JsonArray sensorsJson = root.createNestedArray("sensors");
@@ -77,21 +78,21 @@ void WebCustomization::read(WebCustomization & settings, JsonObject & root) {
sensorJson["type"] = sensor.type; // t
}
// Exclude entities customization
JsonArray exclude_entitiesJson = root.createNestedArray("exclude_entities");
// Masked entities customization
JsonArray masked_entitiesJson = root.createNestedArray("masked_entities");
for (const EntityCustomization & entityCustomization : settings.entityCustomizations) {
JsonObject entityJson = exclude_entitiesJson.createNestedObject();
JsonObject entityJson = masked_entitiesJson.createNestedObject();
entityJson["product_id"] = entityCustomization.product_id;
entityJson["device_id"] = entityCustomization.device_id;
JsonArray exclude_entityJson = entityJson.createNestedArray("entity_ids");
for (uint8_t entity_id : entityCustomization.entity_ids) {
exclude_entityJson.add(entity_id);
JsonArray masked_entityJson = entityJson.createNestedArray("entity_ids");
for (std::string entity_id : entityCustomization.entity_ids) {
masked_entityJson.add(entity_id);
}
}
}
// call on initialization and also when the page is saved via web
// call on initialization and also when the page is saved via web UI
// this loads the data into the internal class
StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization & settings) {
// Dallas Sensor customization
@@ -123,17 +124,20 @@ StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization &
}
}
// load array of entities id's to exclude, building up the object class
// load array of entities id's with masks, building up the object class
settings.entityCustomizations.clear();
if (root["exclude_entities"].is<JsonArray>()) {
for (const JsonObject exclude_entities : root["exclude_entities"].as<JsonArray>()) {
if (root["masked_entities"].is<JsonArray>()) {
for (const JsonObject masked_entities : root["masked_entities"].as<JsonArray>()) {
auto new_entry = EntityCustomization();
new_entry.product_id = exclude_entities["product_id"];
new_entry.device_id = exclude_entities["device_id"];
new_entry.product_id = masked_entities["product_id"];
new_entry.device_id = masked_entities["device_id"];
for (const JsonVariant exclude_entity_id : exclude_entities["entity_ids"].as<JsonArray>()) {
new_entry.entity_ids.push_back(exclude_entity_id.as<uint8_t>()); // add entity list
for (const JsonVariant masked_entity_id : masked_entities["entity_ids"].as<JsonArray>()) {
if (masked_entity_id.is<std::string>()) {
new_entry.entity_ids.push_back(masked_entity_id.as<std::string>()); // add entity list
}
}
settings.entityCustomizations.push_back(new_entry); // save the new object
}
}
@@ -156,7 +160,7 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques
#endif
}
// send back a short list devices used in the customization page
// send back a list of devices used to the customization web page
void WebCustomizationService::devices(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN);
JsonObject root = response->getRoot();
@@ -167,14 +171,16 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) {
JsonObject obj = devices.createNestedObject();
obj["i"] = emsdevice->unique_id(); // a unique id
// shortname - we prefix the count to make it unique
/*
uint8_t device_index = EMSESP::device_index(emsdevice->device_type(), emsdevice->unique_id());
if (device_index) {
char s[10];
obj["s"] = emsdevice->device_type_name() + Helpers::smallitoa(s, device_index);
obj["s"] = emsdevice->device_type_name() + Helpers::smallitoa(s, device_index) + " (" + emsdevice->name() + ")"; // shortname - we prefix the count to make it unique
} else {
obj["s"] = emsdevice->device_type_name();
obj["s"] = emsdevice->device_type_name() + " (" + emsdevice->name() + ")";
}
*/
obj["s"] = emsdevice->device_type_name() + " (" + emsdevice->name() + ")";
}
}
@@ -182,10 +188,10 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) {
request->send(response);
}
// send back list device entities
// send back list of device entities
void WebCustomizationService::device_entities(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
auto * response = new MsgpackAsyncJsonResponse(true, EMSESP_JSON_SIZE_XXLARGE_DYN);
auto * response = new MsgpackAsyncJsonResponse(true, EMSESP_JSON_SIZE_XXXLARGE_DYN);
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->unique_id() == json["id"]) {
#ifndef EMSESP_STANDALONE
@@ -204,52 +210,53 @@ void WebCustomizationService::device_entities(AsyncWebServerRequest * request, J
request->send(response);
}
// takes a list of excluded ids send from the webUI
// takes a list of updated entities with new masks from the web UI
// saves it in the customization service
// and updates the entity list real-time
void WebCustomizationService::exclude_entities(AsyncWebServerRequest * request, JsonVariant & json) {
void WebCustomizationService::masked_entities(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
// find the device using the unique_id
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
uint8_t unique_device_id = json["id"];
if (emsdevice->unique_id() == unique_device_id) {
// first reset all the entity ids
emsdevice->reset_exclude_entities();
// build a list of entities to exclude and then set the flag to non-visible
JsonArray entity_ids_json = json["entity_ids"];
std::vector<uint8_t> entity_ids;
for (JsonVariant id : entity_ids_json) {
uint8_t entity_id = id.as<int>();
emsdevice->exclude_entity(entity_id); // this will have immediate affect
entity_ids.push_back(entity_id);
}
// Save the list to the customization file
uint8_t product_id = emsdevice->product_id();
uint8_t device_id = emsdevice->device_id();
// and set the mask immediately for the changed entities
JsonArray entity_ids_json = json["entity_ids"];
for (const JsonVariant id : entity_ids_json) {
emsdevice->mask_entity(id.as<std::string>());
}
// Save the list to the customization file
EMSESP::webCustomizationService.update(
[&](WebCustomization & settings) {
// if it exists (productid and deviceid match) overwrite it
for (auto & entityCustomization : settings.entityCustomizations) {
if ((entityCustomization.product_id == product_id) && (entityCustomization.device_id == device_id)) {
// already exists, clear the list and add the new values
entityCustomization.entity_ids.clear();
for (uint8_t i = 0; i < entity_ids.size(); i++) {
entityCustomization.entity_ids.push_back(entity_ids[i]);
}
return StateUpdateResult::CHANGED;
// see if we already have a mask list for this device, if so remove it
for (auto it = settings.entityCustomizations.begin(); it != settings.entityCustomizations.end();) {
if ((*it).product_id == product_id && (*it).device_id == device_id) {
it = settings.entityCustomizations.erase(it);
break;
} else {
++it;
}
}
// create a new entry in the list
if (!entity_ids_json.size()) {
return StateUpdateResult::UNCHANGED; // nothing to add
}
// create a new entry for this device if there are values
EntityCustomization new_entry;
new_entry.product_id = product_id;
new_entry.device_id = device_id;
for (uint8_t i = 0; i < entity_ids.size(); i++) {
new_entry.entity_ids.push_back(entity_ids[i]);
}
// get list of entities that have masks
std::vector<std::string> entity_ids;
emsdevice->getMaskedEntities(entity_ids);
new_entry.entity_ids = entity_ids;
// add the record and save
settings.entityCustomizations.push_back(new_entry);
return StateUpdateResult::CHANGED;
},

View File

@@ -27,7 +27,7 @@
// POST
#define DEVICE_ENTITIES_PATH "/rest/deviceEntities"
#define EXCLUDE_ENTITIES_PATH "/rest/excludeEntities"
#define MASKED_ENTITIES_PATH "/rest/maskedEntities"
#define RESET_CUSTOMIZATION_SERVICE_PATH "/rest/resetCustomizations"
namespace emsesp {
@@ -63,15 +63,14 @@ class EntityCustomization {
public:
uint8_t product_id; // device's product id
uint8_t device_id; // device's device id
std::vector<uint8_t> entity_ids; // array of entity ids to exclude
std::vector<std::string> entity_ids; // array of entity ids with masks
};
class WebCustomization {
public:
std::list<SensorCustomization> sensorCustomizations; // for sensor names and offsets
std::list<AnalogCustomization> analogCustomizations; // for analog sensors
std::list<EntityCustomization> entityCustomizations; // for a list of entities that should be excluded from the device list
std::list<EntityCustomization> entityCustomizations; // for a list of entities that have a special mask set
static void read(WebCustomization & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, WebCustomization & settings);
};
@@ -94,11 +93,11 @@ class WebCustomizationService : public StatefulService<WebCustomization> {
void devices(AsyncWebServerRequest * request);
// POST
void exclude_entities(AsyncWebServerRequest * request, JsonVariant & json);
void masked_entities(AsyncWebServerRequest * request, JsonVariant & json);
void device_entities(AsyncWebServerRequest * request, JsonVariant & json);
void reset_customization(AsyncWebServerRequest * request);
AsyncCallbackJsonWebHandler _exclude_entities_handler, _device_entities_handler;
AsyncCallbackJsonWebHandler _masked_entities_handler, _device_entities_handler;
};
} // namespace emsesp

View File

@@ -77,7 +77,7 @@ void WebDataService::core_data(AsyncWebServerRequest * request) {
// Ignore Contoller
JsonArray devices = root.createNestedArray("devices");
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice && emsdevice->device_type() != EMSdevice::DeviceType::CONTROLLER) {
if (emsdevice && (emsdevice->device_type() != EMSdevice::DeviceType::CONTROLLER || emsdevice->count_entities() > 0)) {
JsonObject obj = devices.createNestedObject();
obj["i"] = emsdevice->unique_id(); // a unique id
obj["t"] = emsdevice->device_type_name(); // type