Merge pull request #413 from proddy/dev

rename exclude_entities to masked_entities - fixes for #411
This commit is contained in:
Proddy
2022-03-27 22:19:35 +02:00
committed by GitHub
16 changed files with 302 additions and 234 deletions

View File

@@ -30,6 +30,7 @@
- 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)
### Fixed

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

@@ -20,7 +20,8 @@ import {
ListItem,
ListItemText,
Grid,
useMediaQuery
useMediaQuery,
Tooltip
} from '@mui/material';
import TableCell, { tableCellClasses } from '@mui/material/TableCell';
@@ -524,8 +525,10 @@ const DashboardData: FC = () => {
<StyledTableRow key={i} onClick={() => sendCommand(dv)}>
<StyledTableCell padding="checkbox">
{dv.c && me.admin && (
<IconButton size="small" aria-label="Edit">
<IconButton size="small">
<Tooltip title="Change value...">
<EditIcon color="primary" fontSize="small" />
</Tooltip>
</IconButton>
)}
</StyledTableCell>
@@ -569,7 +572,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 +608,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,10 @@ import {
Dialog,
DialogActions,
DialogContent,
DialogTitle
DialogTitle,
ToggleButton,
ToggleButtonGroup,
Tooltip
} from '@mui/material';
import TableCell, { tableCellClasses } from '@mui/material/TableCell';
@@ -22,8 +25,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';
@@ -53,6 +59,7 @@ const SettingsCustomization: FC = () => {
const [errorMessage, setErrorMessage] = useState<string>();
const [selectedDevice, setSelectedDevice] = useState<number>(0);
const [confirmReset, setConfirmReset] = useState<boolean>(false);
const [masks, setMasks] = useState(() => ['1']);
const fetchDevices = useCallback(async () => {
try {
@@ -110,7 +117,8 @@ const SettingsCustomization: FC = () => {
<>
<Box color="warning.main">
<Typography variant="body2">
Customize which entities to exclude from all all services (MQTT, API). This will have immediate effect.
You can mark an entity as a favorite to be listed first in the Web Dashboard, or remove it from the
Dashboard, or disable it's write operation or exclude it from the MQTT and API outputs.
</Typography>
</Box>
<ValidatedTextField
@@ -138,11 +146,13 @@ const SettingsCustomization: FC = () => {
const saveCustomization = async () => {
if (deviceEntities && selectedDevice) {
const exclude_entities = deviceEntities.filter((de) => de.x).map((new_de) => "07" + new_de.s);
const masked_entities = deviceEntities
.filter((de) => de.m)
.map((new_de) => new_de.m.toString(16).padStart(2, '0') + new_de.s);
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' });
@@ -160,48 +170,94 @@ 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;
if (newMask.includes('1')) {
new_mask |= 1;
}
return o;
})
);
if (newMask.includes('2')) {
new_mask |= 2;
}
if (newMask.includes('4')) {
new_mask |= 4;
}
if (newMask.includes('8')) {
new_mask |= 8;
}
de.m = new_mask;
setMasks(newMask);
};
const getMask = (de: DeviceEntity) => {
var new_masks = [];
if ((de.m & 1) === 1) {
new_masks.push('1');
}
if ((de.m & 2) === 2) {
new_masks.push('2');
}
if ((de.m & 4) === 4) {
new_masks.push('4');
}
if ((de.m & 8) === 8) {
new_masks.push('8');
}
return new_masks;
};
return (
<>
<Table size="small">
<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.i}>
<StyledTableCell padding="checkbox">
<ToggleButtonGroup
size="small"
color="error"
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}>
<Tooltip title="Favorite">
<FavoriteBorderOutlinedIcon fontSize="small" />
</Tooltip>
</ToggleButton>
<ToggleButton value="4" disabled={!de.w}>
<Tooltip title="Force read-only">
<EditOffOutlinedIcon fontSize="small" />
</Tooltip>
</ToggleButton>
<ToggleButton value="2">
<Tooltip title="Exclude in MQTT and API">
<CommentsDisabledOutlinedIcon fontSize="small" />
</Tooltip>
</ToggleButton>
<ToggleButton value="1">
<Tooltip title="Don't show Web Dashboard">
<VisibilityOffOutlinedIcon fontSize="small" />
</Tooltip>
</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> {

View File

@@ -146,11 +146,12 @@ export interface DeviceEntity {
v?: any; // value, in any format
n: string; // name
s: string; // shortname
x: boolean; // excluded flag
m: number; // mask
w?: boolean; // writeable
i: number; // unique id
}
export interface ExcludeEntities {
export interface MaskedEntities {
id: number;
entity_ids: string[];
}

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 = {
@@ -344,19 +344,19 @@ const emsesp_devices = {
i: 1,
d: 23,
p: 77,
s: 'Thermostat1',
s: 'Thermostat1 (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: 'Thermostat2 (RC100/Moduline 1000/1010)',
},
],
}
@@ -450,7 +450,7 @@ const status = {
// Dashboard data
const emsesp_devicedata_1 = {
label: 'RC20/Moduline 300',
label: 'Thermostat: RC20/Moduline 300',
data: [
{
v: '(0)',
@@ -567,28 +567,25 @@ const emsesp_devicedata_2 = {
}
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',
c: 'hc2/seltemp',
x: false,
},
{
v: 18.6,
u: 1,
n: 'hc2 current room temperature',
c: '',
x: true,
},
{
v: 'off',
u: 0,
n: 'hc2 mode',
c: 'hc2/mode',
x: true,
},
],
}
@@ -598,119 +595,121 @@ const emsesp_deviceentities_1 = [
v: '(0)',
n: 'error code',
s: 'errorcode',
x: false,
m: 0,
i: 1,
},
{
v: '14:54:39 06/06/2021',
n: 'date/time',
s: 'datetime',
x: false,
m: 0,
i: 2,
},
{
v: 18.22,
n: 'hc1 selected room temperature',
s: 'hc1/seltemp',
x: false,
m: 0,
w: true,
i: 3,
},
{
v: 22.6,
n: 'hc1 current room temperature',
s: 'hc1/curtemp',
x: false,
m: 0,
i: 4,
},
{
v: 'auto',
n: 'hc1 mode',
s: 'hc1/mode',
x: false,
m: 0,
w: true,
i: 5,
},
]
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, i: 1 },
{ v: false, n: 'tapwater active', s: 'tapwateractive', m: 0, i: 2 },
{ v: 5, n: 'selected flow temperature', s: 'selflowtemp', m: 0, i: 3 },
{ v: 0, n: 'burner selected max power', s: 'selburnpow', m: 0, i: 4 },
{ v: 0, n: 'heating pump modulation', s: 'heatingpumpmod', m: 0, i: 5 },
{ n: 'heating pump 2 modulation', s: 'heatingpump2mod', m: 2, i: 6 },
{ n: 'outside temperature', s: 'outdoortemp', m: 2, i: 7 },
{ v: 53, n: 'current flow temperature', s: 'curflowtemp', m: 0, i: 8 },
{ v: 51.8, n: 'return temperature', s: 'rettemp', m: 0, i: 9 },
{ n: 'mixing switch temperature', s: 'switchtemp', m: 2, i: 10 },
{ v: 1.3, n: 'system pressure', s: 'syspress', m: 0, i: 11 },
{ v: 54.6, n: 'actual boiler temperature', s: 'boiltemp', m: 0, i: 12 },
{ n: 'exhaust temperature', s: 'exhausttemp', m: 2, i: 13 },
{ v: false, n: 'gas', s: 'burngas', m: 0, i: 14 },
{ v: false, n: 'gas stage 2', s: 'burngas2', m: 0, i: 15 },
{ v: 0, n: 'flame current', s: 'flamecurr', m: 0, i: 16 },
{ v: false, n: 'heating pump', s: 'heatingpump', m: 0, i: 17 },
{ v: false, n: 'fan', s: 'fanwork', m: 0, i: 18 },
{ v: false, n: 'ignition', s: 'ignwork', m: 0, i: 19 },
{ v: false, n: 'oil preheating', s: 'oilpreheat', m: 0, i: 20 },
{ v: true, n: 'heating activated', s: 'heatingactivated', m: 0, i: 21 },
{ v: 80, n: 'heating temperature', s: 'heatingtemp', m: 0, i: 22 },
{ v: 70, n: 'burner pump max power', s: 'pumpmodmax', m: 0, i: 23 },
{ v: 30, n: 'burner pump min power', s: 'pumpmodmin', m: 0, i: 24 },
{ v: 1, n: 'pump delay', s: 'pumpdelay', m: 0, i: 25 },
{ v: 10, n: 'burner min period', s: 'burnminperiod', m: 0, i: 26 },
{ v: 0, n: 'burner min power', s: 'burnminpower', m: 0, i: 27 },
{ v: 50, n: 'burner max power', s: 'burnmaxpower', m: 0, i: 28 },
{ v: -6, n: 'hysteresis on temperature', s: 'boilhyston', m: 0, i: 29 },
{ v: 6, n: 'hysteresis off temperature', s: 'boilhystoff', m: 0, i: 30 },
{ v: 0, n: 'set flow temperature', s: 'setflowtemp', m: 0, i: 31 },
{ v: 0, n: 'burner set power', s: 'setburnpow', m: 0, i: 32 },
{ v: 0, n: 'burner current power', s: 'curburnpow', m: 0, i: 33 },
{ v: 326323, n: 'burner starts', s: 'burnstarts', m: 0, i: 34 },
{ v: 553437, n: 'total burner operating time', s: 'burnworkmin', m: 0, i: 35 },
{ v: 451286, n: 'total heat operating time', s: 'heatworkmin', m: 0, i: 36 },
{ v: 4672175, n: 'total UBA operating time', s: 'ubauptime', m: 0, i: 37 },
{ v: '1C(210) 06.06.2020 12:07 (0 min)', n: 'last error code', s: 'lastcode', m: 0, i: 38 },
{ v: '0H', n: 'service code', s: 'servicecode', m: 0, i: 39 },
{ v: 203, n: 'service code number', s: 'servicecodenumber', m: 0, i: 40 },
{ v: 'H00', n: 'maintenance message', s: 'maintenancemessage', m: 0, i: 41 },
{ v: 'manual', n: 'maintenance scheduled', s: 'maintenance', m: 0, i: 42 },
{ v: 6000, n: 'time to next maintenance', s: 'maintenancetime', m: 0, i: 43 },
{ v: '01.01.2012', n: 'next maintenance date', s: 'maintenancedate', m: 0, i: 44 },
{ v: true, n: 'dhw turn on/off', s: 'wwtapactivated', m: 0, i: 45 },
{ v: 62, n: 'dhw set temperature', s: 'wwsettemp', m: 0, i: 46 },
{ v: 60, n: 'dhw selected temperature', s: 'wwseltemp', m: 0, i: 47 },
{ n: 'dhw selected lower temperature', s: 'wwseltemplow', m: 2, i: 48 },
{ n: 'dhw selected temperature for off', s: 'wwseltempoff', m: 2, i: 49 },
{ n: 'dhw single charge temperature', s: 'wwseltempsingle', m: 2, i: 50 },
{ v: 'flow', n: 'dhw type', s: 'wwtype', m: 0, i: 51 },
{ v: 'hot', n: 'dhw comfort', s: 'wwcomfort', m: 0, i: 52 },
{ v: 40, n: 'dhw flow temperature offset', s: 'wwflowtempoffset', m: 0, i: 53 },
{ v: 100, n: 'dhw max power', s: 'wwmaxpower', m: 0, i: 54 },
{ v: false, n: 'dhw circulation pump available', s: 'wwcircpump', m: 0, i: 55 },
{ v: '3-way valve', n: 'dhw charging type', s: 'wwchargetype', m: 0, i: 56 },
{ v: -5, n: 'dhw hysteresis on temperature', s: 'wwhyston', m: 0, i: 57 },
{ v: 0, n: 'dhw hysteresis off temperature', s: 'wwhystoff', m: 0, i: 58 },
{ v: 70, n: 'dhw disinfection temperature', s: 'wwdisinfectiontemp', m: 0, i: 59 },
{ v: 'off', n: 'dhw circulation pump mode', s: 'wwcircmode', m: 0, i: 60 },
{ v: false, n: 'dhw circulation active', s: 'wwcirc', m: 0, i: 61 },
{ v: 46.4, n: 'dhw current intern temperature', s: 'wwcurtemp', m: 0, i: 62 },
{ n: 'dhw current extern temperature', s: 'wwcurtemp2', m: 2, i: 63 },
{ v: 0, n: 'dhw current tap water flow', s: 'wwcurflow', m: 0, i: 64 },
{ v: 46.3, n: 'dhw storage intern temperature', s: 'wwstoragetemp1', m: 0, i: 65 },
{ n: 'dhw storage extern temperature', s: 'wwstoragetemp2', m: 2, i: 66 },
{ v: true, n: 'dhw activated', s: 'wwactivated', m: 0, i: 67 },
{ v: false, n: 'dhw one time charging', s: 'wwonetime', m: 0, i: 68 },
{ v: false, n: 'dhw disinfecting', s: 'wwdisinfecting', m: 0, i: 69 },
{ v: false, n: 'dhw charging', s: 'wwcharging', m: 0, i: 70 },
{ v: false, n: 'dhw recharging', s: 'wwrecharging', m: 0, i: 71 },
{ v: true, n: 'dhw temperature ok', s: 'wwtempok', m: 0, i: 72 },
{ v: false, n: 'dhw active', s: 'wwactive', m: 0, i: 73 },
{ v: true, n: 'dhw 3way valve active', s: 'ww3wayvalve', m: 0, i: 74 },
{ v: 0, n: 'dhw set pump power', s: 'wwsetpumppower', m: 0, i: 75 },
{ n: 'dhw mixer temperature', s: 'wwmixertemp', m: 2, i: 76 },
{ n: 'dhw cylinder middle temperature (TS3)', s: 'wwcylmiddletemp', m: 2, i: 77 },
{ v: 288768, n: 'dhw starts', s: 'wwstarts', m: 0, i: 78 },
{ v: 102151, n: 'dhw active time', s: 'wwworkm', m: 0, i: 79 },
]
const emsesp_deviceentities_4 = [
@@ -718,20 +717,20 @@ const emsesp_deviceentities_4 = [
v: 16,
n: 'hc2 selected room temperature',
s: 'hc2/seltemp',
x: false,
m: 0,
i: 1,
},
{
n: 'hc2 current room temperature',
s: 'hc2/curtemp',
x: true,
m: 3,
i: 2,
},
{
v: 'off',
n: 'hc2 mode',
s: 'hc2/mode',
x: true,
m: 3,
i: 3,
},
]
@@ -925,8 +924,8 @@ rest_server.post(EMSESP_DEVICEENTITIES_ENDPOINT, (req, res) => {
}
})
rest_server.post(EMSESP_EXCLUDE_ENTITIES_ENDPOINT, (req, res) => {
console.log('exclude list for unique id ' + req.body.id + ' and entities:')
rest_server.post(EMSESP_MASKED_ENTITIES_ENDPOINT, (req, res) => {
console.log('list for unique id ' + req.body.id + ' and entities:')
console.log(req.body.entity_ids)
res.sendStatus(200)
})

View File

@@ -1251,13 +1251,13 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
LOG_INFO(F("thermostat time correction from ntp"));
}
}
#ifndef EMSESP_STANDALONE
if (!ntp_ && tm_->tm_year > 110) { // emsesp clock not set, but thermostat clock
struct timeval newnow = {.tv_sec = ttime};
#ifndef EMSESP_STANDALONE
settimeofday(&newnow, nullptr);
#endif
LOG_INFO(F("ems-esp time set from thermostat"));
}
#endif
}
// process_RCError - type 0xA2 - error message - 14 bytes long

View File

@@ -632,11 +632,16 @@ 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");
// sort the device values
std::sort(devicevalues_.begin(), devicevalues_.end(), [](const emsesp::DeviceValue & a, const emsesp::DeviceValue & b) {
return a.has_state(DeviceValueState::DV_FAVORITE);
});
for (auto & dv : devicevalues_) {
// check conditions:
// 1. full_name cannot be empty
@@ -752,32 +757,8 @@ void EMSdevice::generate_values_web(JsonObject & output) {
}
}
// reset all entities to being visible
// this is called before loading in the exclude entities list from the customization service
void EMSdevice::reset_entity_masks() {
for (auto & dv : devicevalues_) {
dv.state &= 0x0F;
}
}
// disable/exclude/mask_out a device entity based on the id
void EMSdevice::mask_entity(std::string entity_id) {
// first character contains mask flags
uint8_t flag = Helpers::hextoint(entity_id.substr(0, 2).c_str());
for (auto & dv : devicevalues_) {
std::string entity = dv.tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(dv.short_name) : tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name);
if (entity == entity_id.substr(2)) {
#if defined(EMSESP_USE_SERIAL)
Serial.print("mask_entity() Removing Visible for device value: ");
Serial.println(read_flash_string(dv.full_name).c_str());
#endif
dv.state = (dv.state & 0x0F) | (flag << 4); // set state high bits to flag, turn off active and ha flags
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_) {
// also show commands and entities that have an empty full name
@@ -854,6 +835,7 @@ void EMSdevice::generate_values_web_all(JsonArray & output) {
} else {
obj["n"] = "(hidden)";
}
// shortname
if (dv.tag >= DeviceValueTAG::TAG_HC1) {
obj["s"] = tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name);
@@ -861,11 +843,34 @@ 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_WEB_EXCLUDE);
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
obj["i"] = dv.id; // add the unique ID
}
}
// add the unique ID
obj["i"] = dv.id;
// reset all entities to being visible
// this is called before loading in the exclude entities list from the customization service
void EMSdevice::reset_entity_masks() {
for (auto & dv : devicevalues_) {
dv.state &= 0x0F;
}
}
// disable/exclude/mask_out a device entity based on the id
void EMSdevice::mask_entity(std::string entity_id) {
// first character contains mask flags
uint8_t flag = Helpers::hextoint(entity_id.substr(0, 2).c_str());
for (auto & dv : devicevalues_) {
std::string entity = dv.tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(dv.short_name) : tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name);
if (entity == entity_id.substr(2)) {
#if defined(EMSESP_USE_SERIAL)
Serial.print("mask_entity() Removing Visible for device value: ");
Serial.println(read_flash_string(dv.full_name).c_str());
#endif
dv.state = (dv.state & 0x0F) | (flag << 4); // set state high bits to flag, turn off active and ha flags
return;
}
}
}

View File

@@ -110,11 +110,13 @@ 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_HA_CONFIG_CREATED = (1 << 1), // 2 - set if the HA config topic has been created
DV_HA_CLIMATE_NO_RT = (1 << 2), // 3 - climate created without roomTemp
// high nibble as mask for exclusions
// 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

View File

@@ -1018,7 +1018,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");
@@ -1053,7 +1052,7 @@ bool System::command_customizations(const char * value, const int8_t id, JsonObj
}
}
// exclude entities
// masked entities
JsonArray mask_entitiesJson = node.createNestedArray("masked_entities");
for (const auto & entityCustomization : settings.entityCustomizations) {
JsonObject entityJson = mask_entitiesJson.createNestedObject();

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);

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.0b9"
#define EMSESP_APP_VERSION "3.4.0b10"

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,17 +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(2048);
_exclude_entities_handler.setMaxJsonBufferSize(2048);
server->addHandler(&_exclude_entities_handler);
_masked_entities_handler.setMethod(HTTP_POST);
_masked_entities_handler.setMaxContentLength(2048);
_masked_entities_handler.setMaxJsonBufferSize(2048);
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");
@@ -78,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");
JsonArray masked_entityJson = entityJson.createNestedArray("entity_ids");
for (std::string entity_id : entityCustomization.entity_ids) {
exclude_entityJson.add(entity_id);
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
@@ -124,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<std::string>()); // 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
}
}
@@ -157,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();
@@ -172,9 +175,9 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) {
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() + ")";
} else {
obj["s"] = emsdevice->device_type_name();
obj["s"] = emsdevice->device_type_name() + " (" + emsdevice->name() + ")";
}
}
}
@@ -183,10 +186,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
@@ -205,10 +208,10 @@ void WebCustomizationService::device_entities(AsyncWebServerRequest * request, J
request->send(response);
}
// takes a list of excluded ids send from the webUI
// takes a list of masked ids send from the webUI
// 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) {
@@ -218,7 +221,7 @@ void WebCustomizationService::exclude_entities(AsyncWebServerRequest * request,
// first reset all the entity ids
emsdevice->reset_entity_masks();
// build a list of entities to exclude and then set the flag to non-visible
// build a list of entities
JsonArray entity_ids_json = json["entity_ids"];
std::vector<std::string> entity_ids;
for (JsonVariant id : entity_ids_json) {

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<std::string> 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