Merge pull request #1834 from proddy/dev

Allow device name to be customized
This commit is contained in:
Proddy
2024-07-03 13:29:44 +02:00
committed by GitHub
35 changed files with 2316 additions and 301 deletions

View File

@@ -21,10 +21,11 @@
- Added scripts for OTA (scripts/upload.py and upload_cli.py) [#1738](https://github.com/emsesp/EMS-ESP32/issues/1738) - Added scripts for OTA (scripts/upload.py and upload_cli.py) [#1738](https://github.com/emsesp/EMS-ESP32/issues/1738)
- timeout for remote thermostat emulation [#1680](https://github.com/emsesp/EMS-ESP32/discussions/1680), [#1774](https://github.com/emsesp/EMS-ESP32/issues/1774) - timeout for remote thermostat emulation [#1680](https://github.com/emsesp/EMS-ESP32/discussions/1680), [#1774](https://github.com/emsesp/EMS-ESP32/issues/1774)
- CR120 thermostat as own model() [#1779](https://github.com/emsesp/EMS-ESP32/discussions/1779) - CR120 thermostat as own model() [#1779](https://github.com/emsesp/EMS-ESP32/discussions/1779)
- Modules - external linkable module library [#1778](https://github.com/emsesp/EMS-ESP32/issues/1778) - modules - external linkable module library [#1778](https://github.com/emsesp/EMS-ESP32/issues/1778)
- Scheduler onChange and Conditions [#1806](https://github.com/emsesp/EMS-ESP32/issues/1806) - scheduler onChange and Conditions [#1806](https://github.com/emsesp/EMS-ESP32/issues/1806)
- make remote control timeout editable [#1774](https://github.com/emsesp/EMS-ESP32/issues/1774) - make remote control timeout editable [#1774](https://github.com/emsesp/EMS-ESP32/issues/1774)
- added extra pump characteristics (mode and pressure for EMS+) by @SLTKA [#1802](https://github.com/emsesp/EMS-ESP32/pull/1802) - added extra pump characteristics (mode and pressure for EMS+) by @SLTKA [#1802](https://github.com/emsesp/EMS-ESP32/pull/1802)
- allow device name to be customized [#1174](https://github.com/emsesp/EMS-ESP32/issues/1174)
## Fixed ## Fixed

View File

@@ -47,7 +47,7 @@
"react-router-dom": "^6.24.0", "react-router-dom": "^6.24.0",
"react-toastify": "^10.0.5", "react-toastify": "^10.0.5",
"typesafe-i18n": "^5.26.2", "typesafe-i18n": "^5.26.2",
"typescript": "^5.5.2" "typescript": "^5.5.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.24.7", "@babel/core": "^7.24.7",
@@ -59,12 +59,12 @@
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"eslint": "^9.6.0", "eslint": "^9.6.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"preact": "^10.22.0", "preact": "^10.22.1",
"prettier": "^3.3.2", "prettier": "^3.3.2",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"terser": "^5.31.1", "terser": "^5.31.1",
"typescript-eslint": "^7.14.1", "typescript-eslint": "^7.15.0",
"vite": "^5.3.2", "vite": "^5.3.3",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^4.3.2" "vite-tsconfig-paths": "^4.3.2"
}, },

View File

@@ -331,7 +331,8 @@ const de: Translation = {
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename' // TODO translate
}; };
export default de; export default de;

View File

@@ -331,7 +331,8 @@ const en: Translation = {
MODULES: 'Modules', MODULES: 'Modules',
MODULES_UPDATED: 'Modules updated', MODULES_UPDATED: 'Modules updated',
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules',
MODULES_NONE: 'No external modules detected' MODULES_NONE: 'No external modules detected',
RENAME: 'Rename'
}; };
export default en; export default en;

View File

@@ -331,7 +331,8 @@ const fr: Translation = {
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename' // TODO translate
}; };
export default fr; export default fr;

View File

@@ -331,7 +331,8 @@ const it: Translation = {
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename' // TODO translate
}; };
export default it; export default it;

View File

@@ -331,7 +331,8 @@ const nl: Translation = {
MODULES: 'Module', MODULES: 'Module',
MODULES_UPDATED: 'Modules geüpdatet', MODULES_UPDATED: 'Modules geüpdatet',
MODULES_DESCRIPTION: 'Klik op de module om EMS-ESP library modules te activeren of te deactiveren', MODULES_DESCRIPTION: 'Klik op de module om EMS-ESP library modules te activeren of te deactiveren',
MODULES_NONE: 'Geen externe modules gedetecteerd' MODULES_NONE: 'Geen externe modules gedetecteerd',
RENAME: 'Hernoemen'
}; };
export default nl; export default nl;

View File

@@ -331,7 +331,8 @@ const no: Translation = {
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename' // TODO translate
}; };
export default no; export default no;

View File

@@ -331,7 +331,8 @@ const pl: BaseTranslation = {
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename' // TODO translate
}; };
export default pl; export default pl;

View File

@@ -331,7 +331,7 @@ const sk: Translation = {
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename' // TODO translate
}; };
export default sk; export default sk;

View File

@@ -331,7 +331,8 @@ const sv: Translation = {
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename' // TODO translate
}; };
export default sv; export default sv;

View File

@@ -331,7 +331,8 @@ const tr: Translation = {
MODULES: 'Module', // TODO translate MODULES: 'Module', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected' // TODO translate MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename' // TODO translate
}; };
export default tr; export default tr;

View File

@@ -4,7 +4,9 @@ import { useBlocker, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import EditIcon from '@mui/icons-material/Edit';
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import SaveIcon from '@mui/icons-material/Save';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
@@ -71,21 +73,31 @@ const Customization: FC = () => {
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [selectedDeviceEntity, setSelectedDeviceEntity] = useState<DeviceEntity>(); const [selectedDeviceEntity, setSelectedDeviceEntity] = useState<DeviceEntity>();
const [dialogOpen, setDialogOpen] = useState<boolean>(false); const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const [rename, setRename] = useState<boolean>(false);
useLayoutTitle(LL.CUSTOMIZATIONS()); useLayoutTitle(LL.CUSTOMIZATIONS());
// fetch devices first // fetch devices first
const { data: devices } = useRequest(EMSESP.readDevices); const { data: devices, send: fetchDevices } = useRequest(EMSESP.readDevices);
const [selectedDevice, setSelectedDevice] = useState<number>( const [selectedDevice, setSelectedDevice] = useState<number>(
Number(useLocation().state) || -1 Number(useLocation().state) || -1
); );
const [selectedDeviceTypeNameURL, setSelectedDeviceTypeNameURL] =
useState<string>(''); // needed for API URL
const [selectedDeviceName, setSelectedDeviceName] = useState<string>(''); const [selectedDeviceName, setSelectedDeviceName] = useState<string>('');
const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), { const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), {
immediate: false immediate: false
}); });
const { send: writeDeviceName } = useRequest(
(data: { id: number; name: string }) => EMSESP.writeDeviceName(data),
{
immediate: false
}
);
const { send: writeCustomizationEntities } = useRequest( const { send: writeCustomizationEntities } = useRequest(
(data: { id: number; entity_ids: string[] }) => (data: { id: number; entity_ids: string[] }) =>
EMSESP.writeCustomizationEntities(data), EMSESP.writeCustomizationEntities(data),
@@ -223,9 +235,10 @@ const Customization: FC = () => {
const id = devices.devices.findIndex((d) => d.i === selectedDevice); const id = devices.devices.findIndex((d) => d.i === selectedDevice);
if (id === -1) { if (id === -1) {
setSelectedDevice(-1); setSelectedDevice(-1);
setSelectedDeviceName(''); setSelectedDeviceTypeNameURL('');
} else { } else {
setSelectedDeviceName(devices.devices[id].tn || ''); setSelectedDeviceTypeNameURL(devices.devices[id].url || '');
setSelectedDeviceName(devices.devices[id].s);
setNumChanges(0); setNumChanges(0);
setRestartNeeded(false); setRestartNeeded(false);
} }
@@ -388,31 +401,87 @@ const Customization: FC = () => {
} }
}; };
const renameDevice = async () => {
await writeDeviceName({ id: selectedDevice, name: selectedDeviceName })
.then(() => {
toast.success(LL.UPDATED_OF(LL.NAME(1)));
})
.catch(() => {
toast.error(LL.UPDATE_OF(LL.NAME(1)) + ' ' + LL.FAILED(1));
})
.finally(async () => {
setRename(false);
await fetchDevices();
});
};
const renderDeviceList = () => ( const renderDeviceList = () => (
<> <>
<Box mb={1} color="warning.main"> <Box mb={1} color="warning.main">
<Typography variant="body2">{LL.CUSTOMIZATIONS_HELP_1()}.</Typography> <Typography variant="body2">{LL.CUSTOMIZATIONS_HELP_1()}.</Typography>
</Box> </Box>
<TextField <Box display="flex" flexWrap="wrap" alignItems="center" gap={2}>
name="device" {rename ? (
label={LL.EMS_DEVICE()} <TextField
variant="outlined" name="device"
fullWidth label={LL.EMS_DEVICE()}
value={selectedDevice} fullWidth
disabled={numChanges !== 0} variant="outlined"
onChange={(e) => setSelectedDevice(parseInt(e.target.value))} value={selectedDeviceName}
margin="normal" onChange={(e) => setSelectedDeviceName(e.target.value)}
select margin="normal"
> />
<MenuItem disabled key={-1} value={-1}> ) : (
{LL.SELECT_DEVICE()}... <TextField
</MenuItem> name="device"
{devices.devices.map((device: DeviceShort) => ( label={LL.EMS_DEVICE()}
<MenuItem key={device.i} value={device.i}> variant="outlined"
{device.s} value={selectedDevice}
</MenuItem> disabled={numChanges !== 0}
))} onChange={(e) => setSelectedDevice(parseInt(e.target.value))}
</TextField> margin="normal"
style={{ minWidth: '50%' }}
select
>
<MenuItem disabled key={-1} value={-1}>
{LL.SELECT_DEVICE()}...
</MenuItem>
{devices.devices.map((device: DeviceShort) => (
<MenuItem key={device.i} value={device.i}>
{device.s}&nbsp;({device.tn})
</MenuItem>
))}
</TextField>
)}
{selectedDevice !== -1 &&
(rename ? (
<ButtonRow>
<Button
startIcon={<SaveIcon />}
variant="contained"
onClick={() => renameDevice()}
>
{LL.UPDATE()}
</Button>
<Button
startIcon={<CancelIcon />}
variant="outlined"
color="secondary"
onClick={() => setRename(false)}
>
{LL.CANCEL()}
</Button>
</ButtonRow>
) : (
<Button
startIcon={<EditIcon />}
variant="outlined"
onClick={() => setRename(true)}
>
{LL.RENAME()}
</Button>
))}
</Box>
</> </>
); );
@@ -545,7 +614,7 @@ const Customization: FC = () => {
{formatName(de, false)}&nbsp;( {formatName(de, false)}&nbsp;(
<Link <Link
target="_blank" target="_blank"
href={APIURL + selectedDeviceName + '/' + de.id} href={APIURL + selectedDeviceTypeNameURL + '/' + de.id}
> >
{de.id} {de.id}
</Link> </Link>
@@ -600,7 +669,7 @@ const Customization: FC = () => {
const renderContent = () => ( const renderContent = () => (
<> <>
{devices && renderDeviceList()} {devices && renderDeviceList()}
{selectedDevice !== -1 && renderDeviceData()} {selectedDevice !== -1 && !rename && renderDeviceData()}
{restartNeeded && ( {restartNeeded && (
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}> <MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
<Button <Button

View File

@@ -646,8 +646,8 @@ const Devices: FC = () => {
> >
<Box sx={{ border: '1px solid #177ac9' }}> <Box sx={{ border: '1px solid #177ac9' }}>
<Typography noWrap variant="subtitle1" color="warning.main" sx={{ ml: 1 }}> <Typography noWrap variant="subtitle1" color="warning.main" sx={{ ml: 1 }}>
{coreData.devices[deviceIndex].tn}&nbsp;&#124;&nbsp; {coreData.devices[deviceIndex].n}&nbsp;(
{coreData.devices[deviceIndex].n} {coreData.devices[deviceIndex].tn})
</Typography> </Typography>
<Grid container justifyContent="space-between"> <Grid container justifyContent="space-between">

View File

@@ -169,7 +169,6 @@ const Modules: FC = () => {
const colorStatus = (status: number) => { const colorStatus = (status: number) => {
if (status === 1) { if (status === 1) {
// TODO translate
return <div style={{ color: 'red' }}>Pending Activation</div>; return <div style={{ color: 'red' }}>Pending Activation</div>;
} }
return <div style={{ color: '#00FF7F' }}>Activated</div>; return <div style={{ color: '#00FF7F' }}>Activated</div>;
@@ -191,10 +190,8 @@ const Modules: FC = () => {
<HeaderRow> <HeaderRow>
<HeaderCell /> <HeaderCell />
<HeaderCell>{LL.NAME(0)}</HeaderCell> <HeaderCell>{LL.NAME(0)}</HeaderCell>
{/* TODO translate */}
<HeaderCell>Author</HeaderCell> <HeaderCell>Author</HeaderCell>
<HeaderCell>{LL.VERSION()}</HeaderCell> <HeaderCell>{LL.VERSION()}</HeaderCell>
{/* TODO translate */}
<HeaderCell>Message</HeaderCell> <HeaderCell>Message</HeaderCell>
<HeaderCell>{LL.STATUS_OF('')}</HeaderCell> <HeaderCell>{LL.STATUS_OF('')}</HeaderCell>
</HeaderRow> </HeaderRow>

View File

@@ -66,7 +66,6 @@ const ModulesDialog = ({
name="enabled" name="enabled"
/> />
} }
// TODO translate
label="Enabled" label="Enabled"
/> />
</Grid> </Grid>

View File

@@ -24,7 +24,7 @@ export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`);
export const readDeviceData = (id: number) => export const readDeviceData = (id: number) =>
alovaInstance.Get<DeviceData>('/rest/deviceData', { alovaInstance.Get<DeviceData>('/rest/deviceData', {
// alovaInstance.Get<DeviceData>(`/rest/deviceData/${id}`, { // alovaInstance.Get<DeviceData>(`/rest/deviceData/${id}`, {
params: { id }, // TODO replace later with id params: { id },
responseType: 'arraybuffer' // uses msgpack responseType: 'arraybuffer' // uses msgpack
}); });
export const writeDeviceValue = (data: { id: number; c: string; v: unknown }) => export const writeDeviceValue = (data: { id: number; c: string; v: unknown }) =>
@@ -66,7 +66,7 @@ export const getSchedule = () => alovaInstance.Get('/rest/getSchedule');
export const readDeviceEntities = (id: number) => export const readDeviceEntities = (id: number) =>
// alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities/${id}`, { // alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities/${id}`, {
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, { alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
params: { id }, // TODO replace later with id params: { id },
responseType: 'arraybuffer', responseType: 'arraybuffer',
transformData(data) { transformData(data) {
return (data as DeviceEntity[]).map((de: DeviceEntity) => ({ return (data as DeviceEntity[]).map((de: DeviceEntity) => ({
@@ -85,6 +85,8 @@ export const writeCustomizationEntities = (data: {
id: number; id: number;
entity_ids: string[]; entity_ids: string[];
}) => alovaInstance.Post('/rest/customizationEntities', data); }) => alovaInstance.Post('/rest/customizationEntities', data);
export const writeDeviceName = (data: { id: number; name: string }) =>
alovaInstance.Post('/rest/writeDeviceName', data);
// SettingsScheduler // SettingsScheduler
export const readSchedule = () => export const readSchedule = () =>

View File

@@ -113,7 +113,8 @@ export interface DeviceShort {
p?: number; // productid p?: number; // productid
s: string; // shortname s: string; // shortname
t?: number; // device type id t?: number; // device type id
tn?: string; // device type internal name tn?: string; // device type internal name (translated)
url?: string; // lowercase type name used in API URL
} }
export interface Devices { export interface Devices {

View File

@@ -1861,15 +1861,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/eslint-plugin@npm:7.14.1": "@typescript-eslint/eslint-plugin@npm:7.15.0":
version: 7.14.1 version: 7.15.0
resolution: "@typescript-eslint/eslint-plugin@npm:7.14.1" resolution: "@typescript-eslint/eslint-plugin@npm:7.15.0"
dependencies: dependencies:
"@eslint-community/regexpp": "npm:^4.10.0" "@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:7.14.1" "@typescript-eslint/scope-manager": "npm:7.15.0"
"@typescript-eslint/type-utils": "npm:7.14.1" "@typescript-eslint/type-utils": "npm:7.15.0"
"@typescript-eslint/utils": "npm:7.14.1" "@typescript-eslint/utils": "npm:7.15.0"
"@typescript-eslint/visitor-keys": "npm:7.14.1" "@typescript-eslint/visitor-keys": "npm:7.15.0"
graphemer: "npm:^1.4.0" graphemer: "npm:^1.4.0"
ignore: "npm:^5.3.1" ignore: "npm:^5.3.1"
natural-compare: "npm:^1.4.0" natural-compare: "npm:^1.4.0"
@@ -1880,44 +1880,44 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10c0/7c2b9b98a38d78326b0ff7348fe001203eda10817ca7834a7a01f492ae7c2508469bbafaa933208d6459f8ff6685277685983cf6f6843e556a6ab2aa5c05080c checksum: 10c0/7ed4ef8355cb60f02ed603673ef749928a001931c534960d1f3f9f9b8092f4abd7ec1e80a33b4c38efb6e8e66c902583bd56a4c4d6ccbd870677a40680a7d1f5
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/parser@npm:7.14.1": "@typescript-eslint/parser@npm:7.15.0":
version: 7.14.1 version: 7.15.0
resolution: "@typescript-eslint/parser@npm:7.14.1" resolution: "@typescript-eslint/parser@npm:7.15.0"
dependencies: dependencies:
"@typescript-eslint/scope-manager": "npm:7.14.1" "@typescript-eslint/scope-manager": "npm:7.15.0"
"@typescript-eslint/types": "npm:7.14.1" "@typescript-eslint/types": "npm:7.15.0"
"@typescript-eslint/typescript-estree": "npm:7.14.1" "@typescript-eslint/typescript-estree": "npm:7.15.0"
"@typescript-eslint/visitor-keys": "npm:7.14.1" "@typescript-eslint/visitor-keys": "npm:7.15.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10c0/db3169d4852685cfb27db741c557f58a3e52104bfacc7621beb7c94ec36ac2a08d4e410ac86745db52f482fbfc87e99fa0a26c1d7a10d37a215cce85e1661f0e checksum: 10c0/8dcad9b84e2cbf89afea97ee7f690f91b487eed21d01997126f98cb7dd56d3b6c98c7ecbdbeda35904af521c4ed746c47887e908f8a1e2148d47c05b491d7b9d
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/scope-manager@npm:7.14.1": "@typescript-eslint/scope-manager@npm:7.15.0":
version: 7.14.1 version: 7.15.0
resolution: "@typescript-eslint/scope-manager@npm:7.14.1" resolution: "@typescript-eslint/scope-manager@npm:7.15.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:7.14.1" "@typescript-eslint/types": "npm:7.15.0"
"@typescript-eslint/visitor-keys": "npm:7.14.1" "@typescript-eslint/visitor-keys": "npm:7.15.0"
checksum: 10c0/f8c05a0d6f8de4cc19b90a4da308817c66e53f36f7ec48f6cc23e93c7399bc418643d8135933aaf5fc013199cbef0e1ea4223f5147db5ca401b239eaf087011e checksum: 10c0/781ec31a07ab7f0bdfb07dd271ef6553aa98f8492f1b3a67c65d178c94d590f4fd2e0916450f2446f1da2fbe007f3454c360ccb25f4d69612f782eb499f400ab
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/type-utils@npm:7.14.1": "@typescript-eslint/type-utils@npm:7.15.0":
version: 7.14.1 version: 7.15.0
resolution: "@typescript-eslint/type-utils@npm:7.14.1" resolution: "@typescript-eslint/type-utils@npm:7.15.0"
dependencies: dependencies:
"@typescript-eslint/typescript-estree": "npm:7.14.1" "@typescript-eslint/typescript-estree": "npm:7.15.0"
"@typescript-eslint/utils": "npm:7.14.1" "@typescript-eslint/utils": "npm:7.15.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.3.0" ts-api-utils: "npm:^1.3.0"
peerDependencies: peerDependencies:
@@ -1925,23 +1925,23 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10c0/bd1c4a8db6273e24156fb10da2cbeb52b4eb03f819da193d4b6bd5a95db3b5524c6fe00d088308d8855b9ae60a3b82afa3a06e89982a09a8573561da960758fd checksum: 10c0/06189eb05d741f05977bbc029c6ac46edd566e0136f2f2c22429fd5f2be1224e2d9135b7021bc686871bfaec9c05a5c9990a321762d3abd06e457486956326ba
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/types@npm:7.14.1": "@typescript-eslint/types@npm:7.15.0":
version: 7.14.1 version: 7.15.0
resolution: "@typescript-eslint/types@npm:7.14.1" resolution: "@typescript-eslint/types@npm:7.15.0"
checksum: 10c0/5b7bda83c47a9b386482e63447c6b0ed7bd4e82eb43f11a180c6e2f3d2e7a2828f57bcbed82196ad761c49e363cccf4c81a89f1fc976e9f5f0a79dcc928fa2d2 checksum: 10c0/935387b21d9fdff65de86f6350cdda1f0614e269324f3a4f0a2ca1b0d72ef4b1d40c7de2f3a20a6f8c83edca6507bfbac3168c860625859e59fc455c80392bed
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/typescript-estree@npm:7.14.1": "@typescript-eslint/typescript-estree@npm:7.15.0":
version: 7.14.1 version: 7.15.0
resolution: "@typescript-eslint/typescript-estree@npm:7.14.1" resolution: "@typescript-eslint/typescript-estree@npm:7.15.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:7.14.1" "@typescript-eslint/types": "npm:7.15.0"
"@typescript-eslint/visitor-keys": "npm:7.14.1" "@typescript-eslint/visitor-keys": "npm:7.15.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
globby: "npm:^11.1.0" globby: "npm:^11.1.0"
is-glob: "npm:^4.0.3" is-glob: "npm:^4.0.3"
@@ -1951,31 +1951,31 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10c0/a8da9bcc4de3334a225424946abd99374de05c42098455419224bc0f46bb1b66115f8bd6ae268461294b90943ed4a407bcd255c0fa60eb76ba4cdc5fc7c20855 checksum: 10c0/0d6e61cb36c4612147ceea796c2bdbb65fca59170d9d768cff314146c5564253a058cbcb9e251722cd76c92a90c257e1210a69f8d4377c8002f211c574d18d24
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/utils@npm:7.14.1": "@typescript-eslint/utils@npm:7.15.0":
version: 7.14.1 version: 7.15.0
resolution: "@typescript-eslint/utils@npm:7.14.1" resolution: "@typescript-eslint/utils@npm:7.15.0"
dependencies: dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0" "@eslint-community/eslint-utils": "npm:^4.4.0"
"@typescript-eslint/scope-manager": "npm:7.14.1" "@typescript-eslint/scope-manager": "npm:7.15.0"
"@typescript-eslint/types": "npm:7.14.1" "@typescript-eslint/types": "npm:7.15.0"
"@typescript-eslint/typescript-estree": "npm:7.14.1" "@typescript-eslint/typescript-estree": "npm:7.15.0"
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
checksum: 10c0/c7f635a3c2c6c085e1d51a52088e55cad9d7e1257b1f60378e5eeb6eb0871db027d42747e9ef60a2f557cf9dd68b2ce014d488d795db8f771506290b164b0e5a checksum: 10c0/26aced17976cee0aa39a79201f68b384bbce1dc96e1c70d0e5f790e1e5655b1b1ddb2afd9eaf3fce9a48c0fb69daecd37a99fdbcdbf1cb58c65ae89ecac88a2c
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/visitor-keys@npm:7.14.1": "@typescript-eslint/visitor-keys@npm:7.15.0":
version: 7.14.1 version: 7.15.0
resolution: "@typescript-eslint/visitor-keys@npm:7.14.1" resolution: "@typescript-eslint/visitor-keys@npm:7.15.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:7.14.1" "@typescript-eslint/types": "npm:7.15.0"
eslint-visitor-keys: "npm:^3.4.3" eslint-visitor-keys: "npm:^3.4.3"
checksum: 10c0/39ac489990fcfdcee442f27658431a0eb44ccf694f701a45df2a108c47cea9582e0955bff0d449047549149385f72895a5d7e6c1622ece1fe32594b7cecb85f3 checksum: 10c0/7509f01c8cd2126a38213bc735a77aa7e976340af0d664be5b2ccd01b8211724b2ea129e33bfd32fe5feac848b7b68ca55bb533f6ccfeec1d2f26a91240489b9
languageName: node languageName: node
linkType: hard linkType: hard
@@ -2010,7 +2010,7 @@ __metadata:
jwt-decode: "npm:^4.0.0" jwt-decode: "npm:^4.0.0"
lodash-es: "npm:^4.17.21" lodash-es: "npm:^4.17.21"
mime-types: "npm:^2.1.35" mime-types: "npm:^2.1.35"
preact: "npm:^10.22.0" preact: "npm:^10.22.1"
prettier: "npm:^3.3.2" prettier: "npm:^3.3.2"
react: "npm:latest" react: "npm:latest"
react-dom: "npm:latest" react-dom: "npm:latest"
@@ -2021,9 +2021,9 @@ __metadata:
rollup-plugin-visualizer: "npm:^5.12.0" rollup-plugin-visualizer: "npm:^5.12.0"
terser: "npm:^5.31.1" terser: "npm:^5.31.1"
typesafe-i18n: "npm:^5.26.2" typesafe-i18n: "npm:^5.26.2"
typescript: "npm:^5.5.2" typescript: "npm:^5.5.3"
typescript-eslint: "npm:^7.14.1" typescript-eslint: "npm:^7.15.0"
vite: "npm:^5.3.2" vite: "npm:^5.3.3"
vite-plugin-imagemin: "npm:^0.6.1" vite-plugin-imagemin: "npm:^0.6.1"
vite-tsconfig-paths: "npm:^4.3.2" vite-tsconfig-paths: "npm:^4.3.2"
languageName: unknown languageName: unknown
@@ -5879,6 +5879,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"picocolors@npm:^1.0.1":
version: 1.0.1
resolution: "picocolors@npm:1.0.1"
checksum: 10c0/c63cdad2bf812ef0d66c8db29583802355d4ca67b9285d846f390cc15c2f6ccb94e8cb7eb6a6e97fc5990a6d3ad4ae42d86c84d3146e667c739a4234ed50d400
languageName: node
linkType: hard
"picomatch@npm:^2.2.2, picomatch@npm:^2.3.1": "picomatch@npm:^2.2.2, picomatch@npm:^2.3.1":
version: 2.3.1 version: 2.3.1
resolution: "picomatch@npm:2.3.1" resolution: "picomatch@npm:2.3.1"
@@ -5936,21 +5943,21 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"postcss@npm:^8.4.38": "postcss@npm:^8.4.39":
version: 8.4.38 version: 8.4.39
resolution: "postcss@npm:8.4.38" resolution: "postcss@npm:8.4.39"
dependencies: dependencies:
nanoid: "npm:^3.3.7" nanoid: "npm:^3.3.7"
picocolors: "npm:^1.0.0" picocolors: "npm:^1.0.1"
source-map-js: "npm:^1.2.0" source-map-js: "npm:^1.2.0"
checksum: 10c0/955407b8f70cf0c14acf35dab3615899a2a60a26718a63c848cf3c29f2467b0533991b985a2b994430d890bd7ec2b1963e36352b0774a19143b5f591540f7c06 checksum: 10c0/16f5ac3c4e32ee76d1582b3c0dcf1a1fdb91334a45ad755eeb881ccc50318fb8d64047de4f1601ac96e30061df203f0f2e2edbdc0bfc49b9c57bc9fb9bedaea3
languageName: node languageName: node
linkType: hard linkType: hard
"preact@npm:^10.22.0": "preact@npm:^10.22.1":
version: 10.22.0 version: 10.22.1
resolution: "preact@npm:10.22.0" resolution: "preact@npm:10.22.1"
checksum: 10c0/dc5466c5968c56997e917580c00983cec2f6486a89ea9ba29f1bb88dcfd2f9ff67c8d561a69a1b3acdab17f2bb36b311fef0c348b62e89c332d00c674f7871f0 checksum: 10c0/9163b97d6fc0ce6b945ed77695d00c4fa07e317d0723e7b9d10c748153d30596abab8b26861ae45591e47bff25515da91406ce7f1c9e66cd9cac7e7f6c927930
languageName: node languageName: node
linkType: hard linkType: hard
@@ -7160,39 +7167,39 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"typescript-eslint@npm:^7.14.1": "typescript-eslint@npm:^7.15.0":
version: 7.14.1 version: 7.15.0
resolution: "typescript-eslint@npm:7.14.1" resolution: "typescript-eslint@npm:7.15.0"
dependencies: dependencies:
"@typescript-eslint/eslint-plugin": "npm:7.14.1" "@typescript-eslint/eslint-plugin": "npm:7.15.0"
"@typescript-eslint/parser": "npm:7.14.1" "@typescript-eslint/parser": "npm:7.15.0"
"@typescript-eslint/utils": "npm:7.14.1" "@typescript-eslint/utils": "npm:7.15.0"
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10c0/93439aa50a814a2d7e117c7d9b54982292986e3c090950961d13dcb07b804ae31e43300e094fd5d27d1208c1647ebbb95fda4bc89163b1c0b2e36e8a777a6800 checksum: 10c0/98293831f7557831b80143b0e717d2a61dca289d637ef464da524880fab2ea62fb61dd952707c571719914c1565942504db2b4ccfe7178a48971e69f270c1abc
languageName: node languageName: node
linkType: hard linkType: hard
"typescript@npm:^5.5.2": "typescript@npm:^5.5.3":
version: 5.5.2 version: 5.5.3
resolution: "typescript@npm:5.5.2" resolution: "typescript@npm:5.5.3"
bin: bin:
tsc: bin/tsc tsc: bin/tsc
tsserver: bin/tsserver tsserver: bin/tsserver
checksum: 10c0/8ca39b27b5f9bd7f32db795045933ab5247897660627251e8254180b792a395bf061ea7231947d5d7ffa5cb4cc771970fd4ef543275f9b559f08c9325cccfce3 checksum: 10c0/f52c71ccbc7080b034b9d3b72051d563601a4815bf3e39ded188e6ce60813f75dbedf11ad15dd4d32a12996a9ed8c7155b46c93a9b9c9bad1049766fe614bbdd
languageName: node languageName: node
linkType: hard linkType: hard
"typescript@patch:typescript@npm%3A^5.5.2#optional!builtin<compat/typescript>": "typescript@patch:typescript@npm%3A^5.5.3#optional!builtin<compat/typescript>":
version: 5.5.2 version: 5.5.3
resolution: "typescript@patch:typescript@npm%3A5.5.2#optional!builtin<compat/typescript>::version=5.5.2&hash=b45daf" resolution: "typescript@patch:typescript@npm%3A5.5.3#optional!builtin<compat/typescript>::version=5.5.3&hash=b45daf"
bin: bin:
tsc: bin/tsc tsc: bin/tsc
tsserver: bin/tsserver tsserver: bin/tsserver
checksum: 10c0/6721ac8933a70c252d7b640b345792e103d881811ff660355617c1836526dbb71c2044e2e77a8823fb3570b469f33276875a4cab6d3c4de4ae7d7ee1c3074ae4 checksum: 10c0/5a437c416251334deeaf29897157032311f3f126547cfdc4b133768b606cb0e62bcee733bb97cf74c42fe7268801aea1392d8e40988cdef112e9546eba4c03c5
languageName: node languageName: node
linkType: hard linkType: hard
@@ -7361,13 +7368,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"vite@npm:^5.3.2": "vite@npm:^5.3.3":
version: 5.3.2 version: 5.3.3
resolution: "vite@npm:5.3.2" resolution: "vite@npm:5.3.3"
dependencies: dependencies:
esbuild: "npm:^0.21.3" esbuild: "npm:^0.21.3"
fsevents: "npm:~2.3.3" fsevents: "npm:~2.3.3"
postcss: "npm:^8.4.38" postcss: "npm:^8.4.39"
rollup: "npm:^4.13.0" rollup: "npm:^4.13.0"
peerDependencies: peerDependencies:
"@types/node": ^18.0.0 || >=20.0.0 "@types/node": ^18.0.0 || >=20.0.0
@@ -7397,7 +7404,7 @@ __metadata:
optional: true optional: true
bin: bin:
vite: bin/vite.js vite: bin/vite.js
checksum: 10c0/3ed576d18c6c008ae92be68f9c79bf7556dae10978a3e8b4c4b42199424755e5c99836e6d4f81fb8b533b957d7f4e351abc60b8a93f4c4b5eb9890b5b6450926 checksum: 10c0/a796872e1d11875d994615cd00da185c80eeb7753034d35c096050bf3c269c02004070cf623c5fe2a4a90ea2f12488e6f9d13933ec810f117f1b931e1b5e3385
languageName: node languageName: node
linkType: hard linkType: hard

View File

@@ -4076,7 +4076,7 @@ template <typename T, typename Enable = void>
struct Comparer; struct Comparer;
template <typename T> template <typename T>
struct Comparer<T, enable_if_t<IsString<T>::value>> : ComparerBase { struct Comparer<T, enable_if_t<IsString<T>::value>> : ComparerBase {
T rhs; // TODO: store adapted string? T rhs;
explicit Comparer(T value) explicit Comparer(T value)
: rhs(value) { : rhs(value) {
} }
@@ -5143,7 +5143,7 @@ class StringBuilder {
append(*s++); append(*s++);
} }
void append(const char * s, size_t n) { void append(const char * s, size_t n) {
while (n-- > 0) // TODO: memcpy while (n-- > 0)
append(*s++); append(*s++);
} }
void append(char c) { void append(char c) {

File diff suppressed because it is too large Load Diff

View File

@@ -38,6 +38,7 @@ unbuild_flags =
${common.core_unbuild_flags} ${common.core_unbuild_flags}
[espressi32_base] [espressi32_base]
; 6.7.0 = Arduino v2.0.16 (based on IDF v4.4.7). See https://github.com/platformio/platform-espressif32/releases/tag/v6.7.0
platform = espressif32@6.7.0 platform = espressif32@6.7.0
framework = arduino framework = arduino
board_build.filesystem = littlefs board_build.filesystem = littlefs
@@ -51,7 +52,9 @@ extra_scripts =
[espressi32_base_tasmota] [espressi32_base_tasmota]
; use Tasmota's library which removes some unused libs (like mbedtsl, so no WiFi_secure.h) and increases available heap ; use Tasmota's library which removes some unused libs (like mbedtsl, so no WiFi_secure.h) and increases available heap
; Tasmota Arduino Core 2.0.17 with IPv6 support, based on IDF 4.4.7 ; Tasmota Arduino Core 2.0.17 with IPv6 support, based on IDF 4.4.7
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.05.00/platform-espressif32.zip ; platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.05.00/platform-espressif32.zip
; Tasmota Arduino Core 2.0.18 with IPv6 support, based on IDF 4.4.8
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.00/platform-espressif32.zip
; Tasmota Arduino Core 3.0.1.240605 based on IDF v5.1.4.240602 ; Tasmota Arduino Core 3.0.1.240605 based on IDF v5.1.4.240602
; platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.10/platform-espressif32.zip ; platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.10/platform-espressif32.zip
framework = arduino framework = arduino

View File

@@ -274,8 +274,13 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pumpModMin_, DeviceValueType::UINT8, FL_(pumpModMin), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_min_pump)); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &pumpModMin_, DeviceValueType::UINT8, FL_(pumpModMin), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_min_pump));
register_device_value( register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &pumpMode_, DeviceValueType::ENUM, FL_(enum_pumpMode), FL_(pumpMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_pumpMode)); DeviceValueTAG::TAG_DEVICE_DATA, &pumpMode_, DeviceValueType::ENUM, FL_(enum_pumpMode), FL_(pumpMode), DeviceValueUOM::NONE, MAKE_CF_CB(set_pumpMode));
register_device_value( register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
DeviceValueTAG::TAG_DEVICE_DATA, &pumpCharacter_, DeviceValueType::ENUM, FL_(enum_pumpCharacter), FL_(pumpCharacter), DeviceValueUOM::NONE, MAKE_CF_CB(set_pumpCharacter)); &pumpCharacter_,
DeviceValueType::ENUM,
FL_(enum_pumpCharacter),
FL_(pumpCharacter),
DeviceValueUOM::NONE,
MAKE_CF_CB(set_pumpCharacter));
register_device_value( register_device_value(
DeviceValueTAG::TAG_DEVICE_DATA, &pumpDelay_, DeviceValueType::UINT8, FL_(pumpDelay), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_pump_delay), 0, 60); DeviceValueTAG::TAG_DEVICE_DATA, &pumpDelay_, DeviceValueType::UINT8, FL_(pumpDelay), DeviceValueUOM::MINUTES, MAKE_CF_CB(set_pump_delay), 0, 60);
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &setFlowTemp_, DeviceValueType::UINT8, FL_(setFlowTemp), DeviceValueUOM::DEGREES); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &setFlowTemp_, DeviceValueType::UINT8, FL_(setFlowTemp), DeviceValueUOM::DEGREES);

View File

@@ -292,14 +292,14 @@ uint8_t EMSdevice::decode_brand(uint8_t value) {
const std::string EMSdevice::to_string() { const std::string EMSdevice::to_string() {
// for devices that haven't been lookup yet, don't show all details // for devices that haven't been lookup yet, don't show all details
if (product_id_ == 0) { if (product_id_ == 0) {
return std::string(name_) + " (DeviceID:" + Helpers::hextoa(device_id_) + ")"; return std::string(name()) + " (DeviceID:" + Helpers::hextoa(device_id_) + ")";
} }
if (brand_ == Brand::NO_BRAND) { if (brand_ == Brand::NO_BRAND) {
return std::string(name_) + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) + ", Version:" + version_ + ")"; return std::string(name()) + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) + ", Version:" + version_ + ")";
} }
return std::string(brand_to_char()) + " " + name_ + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_) return std::string(brand_to_char()) + " " + name() + " (DeviceID:" + Helpers::hextoa(device_id_) + ", ProductID:" + Helpers::itoa(product_id_)
+ ", Version:" + version_ + ")"; + ", Version:" + version_ + ")";
} }
@@ -307,10 +307,10 @@ const std::string EMSdevice::to_string() {
// translated // translated
const std::string EMSdevice::to_string_short() { const std::string EMSdevice::to_string_short() {
if (brand_ == Brand::NO_BRAND) { if (brand_ == Brand::NO_BRAND) {
return std::string(device_type_2_device_name_translated()) + ": " + name_; return std::string(device_type_2_device_name_translated()) + ": " + name();
} }
return std::string(device_type_2_device_name_translated()) + ": " + brand_to_char() + " " + name_; return std::string(device_type_2_device_name_translated()) + ": " + brand_to_char() + " " + name();
} }
// for each telegram that has the fetch value set (true) do a read request // for each telegram that has the fetch value set (true) do a read request
@@ -1176,7 +1176,7 @@ void EMSdevice::setCustomizationEntity(const std::string & entity_id) {
} }
} }
// populate a string vector with entities that have masks set or have a custom name // populate a string vector with entities that have masks set or have a custom entity name
void EMSdevice::getCustomizationEntities(std::vector<std::string> & entity_ids) { void EMSdevice::getCustomizationEntities(std::vector<std::string> & entity_ids) {
for (const auto & dv : devicevalues_) { for (const auto & dv : devicevalues_) {
char name[100]; char name[100];
@@ -1215,7 +1215,7 @@ void EMSdevice::getCustomizationEntities(std::vector<std::string> & entity_ids)
void EMSdevice::dump_value_info() { void EMSdevice::dump_value_info() {
for (auto & dv : devicevalues_) { for (auto & dv : devicevalues_) {
if (dv.fullname != nullptr) { if (dv.fullname != nullptr) {
Serial.print(name_); Serial.print(default_name());
Serial.print(','); Serial.print(',');
Serial.print(device_type_name()); Serial.print(device_type_name());
Serial.print(','); Serial.print(',');
@@ -1782,7 +1782,7 @@ void EMSdevice::mqtt_ha_entity_config_create() {
if (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) && dv.has_state(DeviceValueState::DV_ACTIVE) if (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) && dv.has_state(DeviceValueState::DV_ACTIVE)
&& !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE)) { && !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 // create_device_config is only done once for the EMS device. It can added to any entity, so we take the first
if (Mqtt::publish_ha_sensor_config(dv, name(), brand_to_char(), false, create_device_config)) { if (Mqtt::publish_ha_sensor_config(dv, name().c_str(), brand_to_char(), false, create_device_config)) {
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED); dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
create_device_config = false; // only create the main config once create_device_config = false; // only create the main config once
count++; count++;
@@ -1893,4 +1893,12 @@ void EMSdevice::read_command(const uint16_t type_id, const uint8_t offset, const
EMSESP::send_read_request(type_id, device_id(), offset, length); EMSESP::send_read_request(type_id, device_id(), offset, length);
} }
// returns either default or custom name
std::string EMSdevice::name() {
if (custom_name_.empty()) {
return default_name_;
}
return custom_name_;
}
} // namespace emsesp } // namespace emsesp

View File

@@ -34,14 +34,15 @@ class EMSdevice {
using process_function_p = std::function<void(std::shared_ptr<const Telegram>)>; using process_function_p = std::function<void(std::shared_ptr<const Telegram>)>;
// device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc.. // device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc..
EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand) EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * default_name, uint8_t flags, uint8_t brand)
: device_type_(device_type) : device_type_(device_type)
, device_id_(device_id) , device_id_(device_id)
, product_id_(product_id) , product_id_(product_id)
, name_(name) , default_name_(default_name)
, flags_(flags) , flags_(flags)
, brand_(brand) { , brand_(brand) {
strlcpy(version_, version, sizeof(version_)); strlcpy(version_, version, sizeof(version_));
custom_name_ = ""; // init custom name to blank
} }
// static functions, used outside the class like in console.cpp, command.cpp, emsesp.cpp, mqtt.cpp // static functions, used outside the class like in console.cpp, command.cpp, emsesp.cpp, mqtt.cpp
@@ -111,12 +112,18 @@ class EMSdevice {
return brand_; return brand_;
} }
inline void name(const char * name) { // set custom device name
name_ = name; inline void custom_name(std::string & custom_name) {
custom_name_ = custom_name;
} }
std::string name(); // returns either default or custom name if defined
inline const char * name() const { // default name
return name_; inline void default_name(const char * default_name) {
default_name_ = default_name;
}
inline const char * default_name() const {
return default_name_;
} }
inline uint8_t unique_id() const { inline uint8_t unique_id() const {
@@ -479,7 +486,8 @@ class EMSdevice {
uint8_t device_id_ = 0; uint8_t device_id_ = 0;
uint8_t product_id_ = 0; uint8_t product_id_ = 0;
char version_[6]; char version_[6];
const char * name_; // the long name for the EMS model const char * default_name_; // the fixed name the EMS model taken from the device library
std::string custom_name_; // custom name
uint8_t flags_ = 0; uint8_t flags_ = 0;
uint8_t brand_ = Brand::NO_BRAND; uint8_t brand_ = Brand::NO_BRAND;

View File

@@ -359,7 +359,7 @@ void EMSESP::dump_all_values(uuid::console::Shell & shell) {
// add the device and print out all the entities // add the device and print out all the entities
// for testing the mixer use ... if (device.product_id == 69) { // for testing the mixer use ... if (device.product_id == 69) {
emsdevices.push_back( emsdevices.push_back(
EMSFactory::add(device.device_type, device_id, device.product_id, "1.0", device.name, device.flags, EMSdevice::Brand::NO_BRAND)); EMSFactory::add(device.device_type, device_id, device.product_id, "1.0", device.default_name, device.flags, EMSdevice::Brand::NO_BRAND));
emsdevices.back()->dump_value_info(); emsdevices.back()->dump_value_info();
} }
} }
@@ -400,7 +400,7 @@ void EMSESP::dump_all_telegrams(uuid::console::Shell & shell) {
// add the device and print out all the entities // add the device and print out all the entities
emsdevices.push_back( emsdevices.push_back(
EMSFactory::add(device.device_type, device_id, device.product_id, "1.0", device.name, device.flags, EMSdevice::Brand::NO_BRAND)); EMSFactory::add(device.device_type, device_id, device.product_id, "1.0", device.default_name, device.flags, EMSdevice::Brand::NO_BRAND));
// add to our vector list // add to our vector list
emsdevices.back()->dump_telegram_info(telegram_functions_dump); emsdevices.back()->dump_telegram_info(telegram_functions_dump);
} }
@@ -1167,17 +1167,20 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
if (product_id == 0 || emsdevice->product_id() != 0) { // update only with valid product_id if (product_id == 0 || emsdevice->product_id() != 0) { // update only with valid product_id
return true; return true;
} }
LOG_DEBUG("Updating details for already active deviceID 0x%02X", device_id); LOG_DEBUG("Updating details for already active deviceID 0x%02X", device_id);
emsdevice->product_id(product_id); emsdevice->product_id(product_id);
emsdevice->version(version); emsdevice->version(version);
// only set brand if it doesn't already exist // only set brand if it doesn't already exist
if (emsdevice->brand() == EMSdevice::Brand::NO_BRAND) { if (emsdevice->brand() == EMSdevice::Brand::NO_BRAND) {
emsdevice->brand(brand); emsdevice->brand(brand);
} }
// find the name and flags in our database
// find the name and flags in our device library database
for (const auto & device : device_library_) { for (const auto & device : device_library_) {
if (device.product_id == product_id && device.device_type == emsdevice->device_type()) { if (device.product_id == product_id && device.device_type == emsdevice->device_type()) {
emsdevice->name(device.name); emsdevice->default_name(device.default_name); // custom name is set later
emsdevice->add_flags(device.flags); emsdevice->add_flags(device.flags);
} }
} }
@@ -1192,7 +1195,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
if (device.product_id == product_id) { if (device.product_id == product_id) {
// sometimes boilers share the same productID as controllers // sometimes boilers share the same productID as controllers
// so only add boilers if the device_id is 0x08 // so only add boilers if the device_id is 0x08
// cascaded boilers with 0x70.., map to heatsources // cascaded boilers with 0x70..., map to heatsources
if (device.device_type == DeviceType::BOILER) { if (device.device_type == DeviceType::BOILER) {
if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER) { if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER) {
device_p = &device; device_p = &device;
@@ -1204,7 +1207,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
break; break;
} }
} else { } else {
// it's not a boiler, but we have a match // it's not a boiler, but we still have a match
device_p = &device; device_p = &device;
break; break;
} }
@@ -1219,84 +1222,99 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
return false; // not found return false; // not found
} }
auto name = device_p->name; auto default_name = device_p->default_name;
auto device_type = device_p->device_type; auto device_type = device_p->device_type;
auto flags = device_p->flags; auto flags = device_p->flags;
// check for integrated modules with same product id, but different function (device_id) // check for integrated modules with same product id, but different function (device_id)
// and force set the correct device type and hardcoded name
if (device_type == DeviceType::HEATPUMP) { if (device_type == DeviceType::HEATPUMP) {
if (device_id == EMSdevice::EMS_DEVICE_ID_MODEM) { if (device_id == EMSdevice::EMS_DEVICE_ID_MODEM) {
device_type = DeviceType::GATEWAY; device_type = DeviceType::GATEWAY;
name = "WiFi module"; default_name = "WiFi module";
} else if (device_id == EMSdevice::EMS_DEVICE_ID_RFBASE) { } else if (device_id == EMSdevice::EMS_DEVICE_ID_RFBASE) {
device_type = DeviceType::CONNECT; device_type = DeviceType::CONNECT;
name = "Wireless sensor base"; default_name = "Wireless sensor base";
} }
} }
if (device_id >= EMSdevice::EMS_DEVICE_ID_DHW1 && device_id <= EMSdevice::EMS_DEVICE_ID_DHW8) { if (device_id >= EMSdevice::EMS_DEVICE_ID_DHW1 && device_id <= EMSdevice::EMS_DEVICE_ID_DHW8) {
device_type = DeviceType::WATER; device_type = DeviceType::WATER;
} }
// CR120 have version 22.xx, RC400/CW100 uses version 42.xx, see https://github.com/emsesp/EMS-ESP32/discussions/1779 // CR120 have version 22.xx, RC400/CW100 uses version 42.xx, see https://github.com/emsesp/EMS-ESP32/discussions/1779
if (product_id == 157 && version[0] == '2') { if (product_id == 157 && version[0] == '2') {
flags = DeviceFlags::EMS_DEVICE_FLAG_CR120; flags = DeviceFlags::EMS_DEVICE_FLAG_CR120;
name = "CR120"; default_name = "CR120";
} }
// empty reply to version, read a generic device from database // empty reply to version, read a generic device from database
if (product_id == 0) { if (product_id == 0) {
// check for known device IDs // check for known device IDs
if (device_id == EMSdevice::EMS_DEVICE_ID_RFSENSOR) { if (device_id == EMSdevice::EMS_DEVICE_ID_RFSENSOR) {
// see: https://github.com/emsesp/EMS-ESP32/issues/103#issuecomment-911717342 and https://github.com/emsesp/EMS-ESP32/issues/624 // see: https://github.com/emsesp/EMS-ESP32/issues/103#issuecomment-911717342 and https://github.com/emsesp/EMS-ESP32/issues/624
name = "RF room temperature sensor"; default_name = "RF room temperature sensor";
device_type = DeviceType::THERMOSTAT; device_type = DeviceType::THERMOSTAT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_ROOMTHERMOSTAT || device_id == EMSdevice::EMS_DEVICE_ID_TADO_OLD) { } else if (device_id == EMSdevice::EMS_DEVICE_ID_ROOMTHERMOSTAT || device_id == EMSdevice::EMS_DEVICE_ID_TADO_OLD) {
// see https://github.com/emsesp/EMS-ESP32/issues/174 // see https://github.com/emsesp/EMS-ESP32/issues/174
name = "Generic thermostat"; default_name = "Generic Thermostat";
device_type = DeviceType::THERMOSTAT; device_type = DeviceType::THERMOSTAT;
flags = DeviceFlags::EMS_DEVICE_FLAG_RC10 | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE; flags = DeviceFlags::EMS_DEVICE_FLAG_RC10 | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_RS232) { } else if (device_id == EMSdevice::EMS_DEVICE_ID_RS232) {
name = "RS232"; default_name = "RS232";
device_type = DeviceType::CONNECT; device_type = DeviceType::CONNECT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_TERMINAL) { } else if (device_id == EMSdevice::EMS_DEVICE_ID_TERMINAL) {
name = "Terminal"; default_name = "Terminal";
device_type = DeviceType::CONNECT; device_type = DeviceType::CONNECT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_SERVICEKEY) { } else if (device_id == EMSdevice::EMS_DEVICE_ID_SERVICEKEY) {
name = "Service Key"; default_name = "Service Key";
device_type = DeviceType::CONNECT; device_type = DeviceType::CONNECT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_CASCADE) { } else if (device_id == EMSdevice::EMS_DEVICE_ID_CASCADE) {
name = "Cascade"; default_name = "Cascade";
device_type = DeviceType::CONNECT; device_type = DeviceType::CONNECT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_EASYCOM } else if (device_id == EMSdevice::EMS_DEVICE_ID_EASYCOM
|| (device_id >= EMSdevice::EMS_DEVICE_ID_MODEM && device_id <= EMSdevice::EMS_DEVICE_ID_MODEM + 5)) { || (device_id >= EMSdevice::EMS_DEVICE_ID_MODEM && device_id <= EMSdevice::EMS_DEVICE_ID_MODEM + 5)) {
// see https://github.com/emsesp/EMS-ESP/issues/460#issuecomment-709553012 // see https://github.com/emsesp/EMS-ESP/issues/460#issuecomment-709553012
name = "Modem"; default_name = "Modem";
device_type = DeviceType::CONNECT; device_type = DeviceType::CONNECT;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_CONVERTER) { } else if (device_id == EMSdevice::EMS_DEVICE_ID_CONVERTER) {
name = "Converter"; // generic default_name = "Converter"; // generic
} else if (device_id == EMSdevice::EMS_DEVICE_ID_CLOCK) { } else if (device_id == EMSdevice::EMS_DEVICE_ID_CLOCK) {
name = "Clock"; // generic default_name = "Clock"; // generic
device_type = DeviceType::CONTROLLER; device_type = DeviceType::CONTROLLER;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_CONTROLLER) { } else if (device_id == EMSdevice::EMS_DEVICE_ID_CONTROLLER) {
name = "Generic Controller"; default_name = "Generic Controller";
device_type = DeviceType::CONTROLLER; device_type = DeviceType::CONTROLLER;
} else if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER) { } else if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER) {
name = "Generic Boiler"; default_name = "Generic Boiler";
device_type = DeviceType::BOILER; device_type = DeviceType::BOILER;
flags = DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP; flags = DeviceFlags::EMS_DEVICE_FLAG_HEATPUMP;
LOG_WARNING("Unknown EMS boiler. Using generic profile. Please report on GitHub."); LOG_WARNING("Unknown EMS boiler. Using generic profile. Please report on GitHub.");
} else if (device_id >= 0x68 && device_id <= 0x6F) { } else if (device_id >= 0x68 && device_id <= 0x6F) {
// test for https://github.com/emsesp/EMS-ESP32/issues/882 // test for https://github.com/emsesp/EMS-ESP32/issues/882
name = "Cascaded Controller"; default_name = "Cascaded Controller";
device_type = DeviceType::CONTROLLER; device_type = DeviceType::CONTROLLER;
} else { } else {
LOG_WARNING("Unrecognized EMS device (device ID 0x%02X, no product ID). Please report on GitHub.", device_id); LOG_WARNING("Unrecognized EMS device (device ID 0x%02X, no product ID). Please report on GitHub.", device_id);
return false; return false;
} }
} }
LOG_DEBUG("Adding new device %s (deviceID 0x%02X, productID %d, version %s)", name, device_id, product_id, version); LOG_DEBUG("Adding new device %s (deviceID 0x%02X, productID %d, version %s)", default_name, device_id, product_id, version);
emsdevices.push_back(EMSFactory::add(device_type, device_id, product_id, version, name, flags, brand)); emsdevices.push_back(EMSFactory::add(device_type, device_id, product_id, version, default_name, flags, brand));
// assign a unique ID. Note that this is not actual unique after a restart as it's dependent on the order that devices are found // see if we have a custom device name in our Customizations list, and if so set it
webCustomizationService.read([&](WebCustomization & settings) {
for (EntityCustomization e : settings.entityCustomizations) {
if ((e.device_id == device_id) && (e.product_id == product_id)) {
LOG_DEBUG("Have customizations for %s with deviceID 0x%02X productID %d", e.custom_name.c_str(), device_id, product_id);
emsdevices.back()->custom_name(e.custom_name);
break;
}
}
});
// assign a unique ID. Note that this is not actual remain unique after a restart as it's dependent on the order that devices are found
// can't be 0 otherwise web won't work // can't be 0 otherwise web won't work
emsdevices.back()->unique_id(++unique_id_count_); emsdevices.back()->unique_id(++unique_id_count_);
@@ -1305,7 +1323,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
return a->device_type() < b->device_type(); return a->device_type() < b->device_type();
}); });
fetch_device_values(device_id); // go and fetch its data fetch_device_values(device_id); // go and fetch its device entity data
// Print to LOG showing we've added a new device // Print to LOG showing we've added a new device
LOG_INFO("Recognized new %s with deviceID 0x%02X", EMSdevice::device_type_2_device_name(device_type), device_id); LOG_INFO("Recognized new %s with deviceID 0x%02X", EMSdevice::device_type_2_device_name(device_type), device_id);

View File

@@ -252,7 +252,7 @@ class EMSESP {
struct Device_record { struct Device_record {
uint8_t product_id; uint8_t product_id;
EMSdevice::DeviceType device_type; EMSdevice::DeviceType device_type;
const char * name; const char * default_name;
uint8_t flags; uint8_t flags;
}; };
static std::vector<Device_record> device_library_; static std::vector<Device_record> device_library_;

View File

@@ -55,9 +55,9 @@ class EMSFactory {
} }
// Construct derived class returning an unique ptr // Construct derived class returning an unique ptr
static auto add(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand) static auto add(const uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * default_name, uint8_t flags, uint8_t brand)
-> std::unique_ptr<EMSdevice> { -> std::unique_ptr<EMSdevice> {
return std::unique_ptr<EMSdevice>(EMSFactory::makeRaw(device_type, device_id, product_id, version, name, flags, brand)); return std::unique_ptr<EMSdevice>(EMSFactory::makeRaw(device_type, device_id, product_id, version, default_name, flags, brand));
} }
virtual auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand) const virtual auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand) const

View File

@@ -1102,6 +1102,7 @@ bool System::check_restore() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
// see if we have a temp file, if so try and read it // see if we have a temp file, if so try and read it
// TODO find a nicer way to see if a file exists without reporting an error
File new_file = LittleFS.open(TEMP_FILENAME_PATH); File new_file = LittleFS.open(TEMP_FILENAME_PATH);
if (new_file) { if (new_file) {
JsonDocument jsonDocument; JsonDocument jsonDocument;
@@ -1556,7 +1557,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
if (emsdevice && (emsdevice->device_type() == device_class.first)) { if (emsdevice && (emsdevice->device_type() == device_class.first)) {
JsonObject obj = devices.add<JsonObject>(); JsonObject obj = devices.add<JsonObject>();
obj["type"] = emsdevice->device_type_name(); // non translated name obj["type"] = emsdevice->device_type_name(); // non translated name
obj["name"] = emsdevice->name(); obj["name"] = emsdevice->name(); // custom name
obj["device id"] = Helpers::hextoa(emsdevice->device_id()); obj["device id"] = Helpers::hextoa(emsdevice->device_id());
obj["product id"] = emsdevice->product_id(); obj["product id"] = emsdevice->product_id();
obj["version"] = emsdevice->version(); obj["version"] = emsdevice->version();

View File

@@ -611,8 +611,8 @@ bool TemperatureSensor::Sensor::apply_customization() {
auto sensors = settings.sensorCustomizations; auto sensors = settings.sensorCustomizations;
if (!sensors.empty()) { if (!sensors.empty()) {
for (const auto & sensor : sensors) { for (const auto & sensor : sensors) {
LOG_DEBUG("Loading customization for temperature sensor %s", sensor.id.c_str());
if (id_ == sensor.id) { if (id_ == sensor.id) {
LOG_DEBUG("Loading customization for temperature sensor %s", sensor.id.c_str());
set_name(sensor.name); set_name(sensor.name);
set_offset(sensor.offset); set_offset(sensor.offset);
return true; return true;

View File

@@ -313,17 +313,20 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
if (command == "general") { if (command == "general") {
shell.printfln("Testing adding a boiler, thermostat, all sensors, scheduler and custom entities..."); shell.printfln("Testing adding a boiler, thermostat, all sensors, scheduler and custom entities...");
test("general");
// setup fake data // setup fake data
EMSESP::webCustomizationService.test(); // set customizations EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the file
EMSESP::temperaturesensor_.test(); // add temperature sensors
EMSESP::webSchedulerService.test(); // add scheduler items
EMSESP::webCustomEntityService.test(); // add custom entities
// shell.invoke_command("show devices"); // add devices
test("general");
EMSESP::temperaturesensor_.test(); // add temperature sensors
EMSESP::webSchedulerService.test(); // add scheduler items
EMSESP::webCustomEntityService.test(); // add custom entities
shell.invoke_command("show devices");
// shell.invoke_command("show values"); // shell.invoke_command("show values");
shell.invoke_command("call system allvalues"); // shell.invoke_command("call system allvalues");
// shell.invoke_command("call system publish"); // shell.invoke_command("call system publish");
// shell.invoke_command("show mqtt"); // shell.invoke_command("show mqtt");
ok = true; ok = true;

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.7.0-dev.19" #define EMSESP_APP_VERSION "3.7.0-dev.20"

View File

@@ -24,6 +24,7 @@ bool WebCustomization::_start = true;
WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _fsPersistence(WebCustomization::read, WebCustomization::update, this, fs, EMSESP_CUSTOMIZATION_FILE) { : _fsPersistence(WebCustomization::read, WebCustomization::update, this, fs, EMSESP_CUSTOMIZATION_FILE) {
// GET
server->on(DEVICE_ENTITIES_PATH, server->on(DEVICE_ENTITIES_PATH,
HTTP_GET, HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { device_entities(request); }, AuthenticationPredicates::IS_AUTHENTICATED)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { device_entities(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
@@ -32,10 +33,15 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f
HTTP_GET, HTTP_GET,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { devices(request); }, AuthenticationPredicates::IS_AUTHENTICATED)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { devices(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
// POST
server->on(RESET_CUSTOMIZATION_SERVICE_PATH, server->on(RESET_CUSTOMIZATION_SERVICE_PATH,
HTTP_POST, HTTP_POST,
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { reset_customization(request); }, AuthenticationPredicates::IS_ADMIN)); securityManager->wrapRequest([this](AsyncWebServerRequest * request) { reset_customization(request); }, AuthenticationPredicates::IS_ADMIN));
server->on(WRITE_DEVICE_NAME_PATH,
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { writeDeviceName(request, json); },
AuthenticationPredicates::IS_AUTHENTICATED));
server->on(CUSTOMIZATION_ENTITIES_PATH, server->on(CUSTOMIZATION_ENTITIES_PATH,
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { customization_entities(request, json); }, securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { customization_entities(request, json); },
AuthenticationPredicates::IS_AUTHENTICATED)); AuthenticationPredicates::IS_AUTHENTICATED));
@@ -65,12 +71,13 @@ void WebCustomization::read(WebCustomization & customizations, JsonObject root)
sensorJson["type"] = sensor.type; // t sensorJson["type"] = sensor.type; // t
} }
// Masked entities customization // Masked entities customization and custom device name (optional)
JsonArray masked_entitiesJson = root["masked_entities"].to<JsonArray>(); JsonArray masked_entitiesJson = root["masked_entities"].to<JsonArray>();
for (const EntityCustomization & entityCustomization : customizations.entityCustomizations) { for (const EntityCustomization & entityCustomization : customizations.entityCustomizations) {
JsonObject entityJson = masked_entitiesJson.add<JsonObject>(); JsonObject entityJson = masked_entitiesJson.add<JsonObject>();
entityJson["product_id"] = entityCustomization.product_id; entityJson["product_id"] = entityCustomization.product_id;
entityJson["device_id"] = entityCustomization.device_id; entityJson["device_id"] = entityCustomization.device_id;
entityJson["custom_name"] = entityCustomization.custom_name;
// entries are in the form <XX><shortname>[optional customname] e.g "08heatingactive|heating is on" // entries are in the form <XX><shortname>[optional customname] e.g "08heatingactive|heating is on"
JsonArray masked_entityJson = entityJson["entity_ids"].to<JsonArray>(); JsonArray masked_entityJson = entityJson["entity_ids"].to<JsonArray>();
@@ -120,9 +127,10 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
customizations.entityCustomizations.clear(); customizations.entityCustomizations.clear();
if (root["masked_entities"].is<JsonArray>()) { if (root["masked_entities"].is<JsonArray>()) {
for (const JsonObject masked_entities : root["masked_entities"].as<JsonArray>()) { for (const JsonObject masked_entities : root["masked_entities"].as<JsonArray>()) {
auto emsEntity = EntityCustomization(); auto emsEntity = EntityCustomization();
emsEntity.product_id = masked_entities["product_id"]; emsEntity.product_id = masked_entities["product_id"];
emsEntity.device_id = masked_entities["device_id"]; emsEntity.device_id = masked_entities["device_id"];
emsEntity.custom_name = masked_entities["custom_name"].as<std::string>();
for (const JsonVariant masked_entity_id : masked_entities["entity_ids"].as<JsonArray>()) { for (const JsonVariant masked_entity_id : masked_entities["entity_ids"].as<JsonArray>()) {
if (masked_entity_id.is<std::string>()) { if (masked_entity_id.is<std::string>()) {
@@ -163,10 +171,11 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) {
for (const auto & emsdevice : EMSESP::emsdevices) { for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice->has_entities()) { if (emsdevice->has_entities()) {
JsonObject obj = devices.add<JsonObject>(); JsonObject obj = devices.add<JsonObject>();
obj["i"] = emsdevice->unique_id(); // its unique id obj["i"] = emsdevice->unique_id(); // its unique id
obj["s"] = std::string(emsdevice->device_type_2_device_name_translated()) + " (" + emsdevice->name() + ")"; // shortname, is device type translated obj["s"] = emsdevice->name(); // custom name
obj["tn"] = emsdevice->device_type_name(); // non-translated, lower-case obj["t"] = emsdevice->device_type(); // internal device type ID
obj["t"] = emsdevice->device_type(); // internal device type ID obj["tn"] = std::string(emsdevice->device_type_2_device_name_translated()); // translated device type name
obj["url"] = emsdevice->device_type_name(); // non-translated, lower-case, used for API URL
} }
} }
@@ -211,6 +220,58 @@ void WebCustomizationService::device_entities(AsyncWebServerRequest * request) {
request->send(response); request->send(response);
} }
// renames a device
// takes the unique ID and the custom name
void WebCustomizationService::writeDeviceName(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"];
// find product id and device id using the unique id
if (emsdevice->unique_id() == unique_device_id) {
uint8_t product_id = emsdevice->product_id();
uint8_t device_id = emsdevice->device_id();
auto custom_name = json["name"].as<std::string>();
// emsesp::EMSESP::logger().info("Found Device ID: %d, Product ID: %d", device_id, product_id);
// updates current record or creates a new one
bool entry_exists = false;
update([&](WebCustomization & settings) {
for (auto it = settings.entityCustomizations.begin(); it != settings.entityCustomizations.end();) {
if ((*it).product_id == product_id && (*it).device_id == device_id) {
(*it).custom_name = custom_name;
entry_exists = true;
break;
} else {
++it;
}
}
// if we don't have any customization for this device, create a new entry
if (!entry_exists) {
EntityCustomization new_entry;
new_entry.product_id = product_id;
new_entry.device_id = device_id;
new_entry.custom_name = custom_name;
settings.entityCustomizations.push_back(new_entry);
}
return StateUpdateResult::CHANGED;
});
// update the EMS Device record real-time
emsdevice->custom_name(custom_name);
}
}
}
}
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
}
// takes a list of updated entities with new masks from the web UI // takes a list of updated entities with new masks from the web UI
// saves it in the customization service // saves it in the customization service
// and updates the entity list real-time // and updates the entity list real-time
@@ -236,7 +297,6 @@ void WebCustomizationService::customization_entities(AsyncWebServerRequest * req
} else { } else {
emsdevice->setCustomizationEntity(id_s); emsdevice->setCustomizationEntity(id_s);
} }
// emsesp::EMSESP::logger().info(id.as<const char *>());
} }
// add deleted entities from file // add deleted entities from file
@@ -271,7 +331,7 @@ void WebCustomizationService::customization_entities(AsyncWebServerRequest * req
// Save the list to the customization file // Save the list to the customization file
update([&](WebCustomization & settings) { update([&](WebCustomization & settings) {
// see if we already have a mask list for this device, if so remove it // see if we already have a mask list for this device, if so remove the entry
for (auto it = settings.entityCustomizations.begin(); it != settings.entityCustomizations.end();) { for (auto it = settings.entityCustomizations.begin(); it != settings.entityCustomizations.end();) {
if ((*it).product_id == product_id && (*it).device_id == device_id) { if ((*it).product_id == product_id && (*it).device_id == device_id) {
it = settings.entityCustomizations.erase(it); it = settings.entityCustomizations.erase(it);
@@ -281,16 +341,14 @@ void WebCustomizationService::customization_entities(AsyncWebServerRequest * req
} }
} }
// create a new entry for this device if there are values // re-create a new entry
EntityCustomization new_entry; EntityCustomization new_entry;
new_entry.product_id = product_id; new_entry.product_id = product_id;
new_entry.device_id = device_id; new_entry.device_id = device_id;
new_entry.entity_ids = entity_ids; new_entry.entity_ids = entity_ids;
// add the record and save
settings.entityCustomizations.push_back(new_entry); settings.entityCustomizations.push_back(new_entry);
return StateUpdateResult::CHANGED;
return StateUpdateResult::CHANGED; // save the changes
}); });
break; break;
@@ -308,7 +366,6 @@ void WebCustomizationService::begin() {
_fsPersistence.readFromFS(); _fsPersistence.readFromFS();
} }
// hard coded tests // hard coded tests
#ifdef EMSESP_TEST #ifdef EMSESP_TEST
void WebCustomizationService::test() { void WebCustomizationService::test() {
@@ -321,11 +378,11 @@ void WebCustomizationService::test() {
sensor.offset = 0; sensor.offset = 0;
webCustomization.sensorCustomizations.push_back(sensor); webCustomization.sensorCustomizations.push_back(sensor);
sensor = SensorCustomization(); auto sensor2 = SensorCustomization();
sensor.id = "0B-0C0D-0E0F-1011"; sensor2.id = "0B-0C0D-0E0F-1011";
sensor.name = "test_sensor2"; sensor2.name = "test_sensor2";
sensor.offset = 4; sensor2.offset = 4;
webCustomization.sensorCustomizations.push_back(sensor); webCustomization.sensorCustomizations.push_back(sensor2);
// Analog sensors // Analog sensors
// This actually adds the sensors as we use customizations to store them // This actually adds the sensors as we use customizations to store them
@@ -350,9 +407,10 @@ void WebCustomizationService::test() {
// EMS entities // EMS entities
webCustomization.entityCustomizations.clear(); webCustomization.entityCustomizations.clear();
auto emsEntity = EntityCustomization(); auto emsEntity = EntityCustomization();
emsEntity.product_id = 123; emsEntity.product_id = 123;
emsEntity.device_id = 8; emsEntity.device_id = 8;
emsEntity.custom_name = "Custom Name!!";
emsEntity.entity_ids.push_back("08heatingactive|is my heating on?"); emsEntity.entity_ids.push_back("08heatingactive|is my heating on?");
webCustomization.entityCustomizations.push_back(emsEntity); webCustomization.entityCustomizations.push_back(emsEntity);

View File

@@ -28,6 +28,7 @@
// POST // POST
#define CUSTOMIZATION_ENTITIES_PATH "/rest/customizationEntities" #define CUSTOMIZATION_ENTITIES_PATH "/rest/customizationEntities"
#define RESET_CUSTOMIZATION_SERVICE_PATH "/rest/resetCustomizations" #define RESET_CUSTOMIZATION_SERVICE_PATH "/rest/resetCustomizations"
#define WRITE_DEVICE_NAME_PATH "/rest/writeDeviceName"
namespace emsesp { namespace emsesp {
@@ -60,9 +61,10 @@ class AnalogCustomization {
// we use product_id and device_id to make the device unique // we use product_id and device_id to make the device unique
class EntityCustomization { class EntityCustomization {
public: public:
uint8_t product_id; // device's product id uint8_t product_id; // device's product id
uint8_t device_id; // device's device id uint8_t device_id; // device's device id
std::vector<std::string> entity_ids; // array of entity ids with masks and optional custom fullname std::string custom_name; // custom device name
std::vector<std::string> entity_ids; // array of entity ids with masks and optional custom fullname
}; };
class WebCustomization { class WebCustomization {
@@ -100,6 +102,7 @@ class WebCustomizationService : public StatefulService<WebCustomization> {
// POST // POST
void customization_entities(AsyncWebServerRequest * request, JsonVariant json); void customization_entities(AsyncWebServerRequest * request, JsonVariant json);
void writeDeviceName(AsyncWebServerRequest * request, JsonVariant json);
void reset_customization(AsyncWebServerRequest * request); // command void reset_customization(AsyncWebServerRequest * request); // command
}; };

View File

@@ -73,7 +73,7 @@ void WebDataService::core_data(AsyncWebServerRequest * request) {
obj["tn"] = emsdevice->device_type_2_device_name_translated(); // translated device type name obj["tn"] = emsdevice->device_type_2_device_name_translated(); // translated device type name
obj["t"] = emsdevice->device_type(); // device type number obj["t"] = emsdevice->device_type(); // device type number
obj["b"] = emsdevice->brand_to_char(); // brand obj["b"] = emsdevice->brand_to_char(); // brand
obj["n"] = emsdevice->name(); // name obj["n"] = emsdevice->name(); // custom name
obj["d"] = emsdevice->device_id(); // deviceid obj["d"] = emsdevice->device_id(); // deviceid
obj["p"] = emsdevice->product_id(); // productid obj["p"] = emsdevice->product_id(); // productid
obj["v"] = emsdevice->version(); // version obj["v"] = emsdevice->version(); // version