mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
enable write in dashboard
This commit is contained in:
@@ -31,7 +31,7 @@
|
|||||||
"async-validator": "^4.2.5",
|
"async-validator": "^4.2.5",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"preact": "^10.24.1",
|
"preact": "^10.24.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
|
|||||||
@@ -1,24 +1,38 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
|
import { IconContext } from 'react-icons/lib';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
|
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
|
||||||
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
|
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
|
||||||
import { Box, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material';
|
import {
|
||||||
|
Box,
|
||||||
|
IconButton,
|
||||||
|
ToggleButton,
|
||||||
|
ToggleButtonGroup,
|
||||||
|
Typography
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
import { Body, Cell, Row, Table } from '@table-library/react-table-library/table';
|
import { Body, Cell, Row, Table } from '@table-library/react-table-library/table';
|
||||||
import { useTheme } from '@table-library/react-table-library/theme';
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
import { CellTree, useTree } from '@table-library/react-table-library/tree';
|
import { CellTree, useTree } from '@table-library/react-table-library/tree';
|
||||||
import { useAutoRequest } from 'alova/client';
|
import { useAutoRequest, useRequest } from 'alova/client';
|
||||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||||
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import { readDashboard } from '../../api/app';
|
import { readDashboard, writeDeviceValue } from '../../api/app';
|
||||||
|
import DeviceIcon from './DeviceIcon';
|
||||||
|
import DashboardDevicesDialog from './DevicesDialog';
|
||||||
import { formatValue } from './deviceValue';
|
import { formatValue } from './deviceValue';
|
||||||
import type { DashboardItem } from './types';
|
import { type DashboardItem, type DeviceValue } from './types';
|
||||||
|
import { deviceValueItemValidation } from './validators';
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
const { me } = useContext(AuthenticatedContext);
|
||||||
|
|
||||||
useLayoutTitle('Dashboard'); // TODO translate
|
useLayoutTitle('Dashboard'); // TODO translate
|
||||||
|
|
||||||
@@ -34,9 +48,39 @@ const Dashboard = () => {
|
|||||||
pollingTime: 1500
|
pollingTime: 1500
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { loading: submitting, send: sendDeviceValue } = useRequest(
|
||||||
|
(data: { id: number; c: string; v: unknown }) => writeDeviceValue(data),
|
||||||
|
{
|
||||||
|
immediate: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false);
|
||||||
|
const [selectedDeviceValue, setSelectedDeviceValue] = useState<DeviceValue>();
|
||||||
|
|
||||||
|
const deviceValueDialogClose = () => {
|
||||||
|
setDeviceValueDialogOpen(false);
|
||||||
|
void sendDeviceData(selectedDevice);
|
||||||
|
};
|
||||||
|
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
||||||
|
const id = Number(device_select.state.id);
|
||||||
|
await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
|
||||||
|
.then(() => {
|
||||||
|
toast.success(LL.WRITE_CMD_SENT());
|
||||||
|
})
|
||||||
|
.catch((error: Error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
setDeviceValueDialogOpen(false);
|
||||||
|
await sendDeviceData(id);
|
||||||
|
setSelectedDeviceValue(undefined);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const dashboard_theme = useTheme({
|
const dashboard_theme = useTheme({
|
||||||
Table: `
|
Table: `
|
||||||
--data-table-library_grid-template-columns: minmax(80px, auto) 120px;
|
--data-table-library_grid-template-columns: minmax(80px, auto) 120px 40px;
|
||||||
`,
|
`,
|
||||||
BaseRow: `
|
BaseRow: `
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -46,10 +90,8 @@ const Dashboard = () => {
|
|||||||
`,
|
`,
|
||||||
Row: `
|
Row: `
|
||||||
background-color: #1e1e1e;
|
background-color: #1e1e1e;
|
||||||
// position: relative;
|
|
||||||
// cursor: pointer;
|
|
||||||
.td {
|
.td {
|
||||||
height: 24px;
|
height: 22px;
|
||||||
}
|
}
|
||||||
&:hover .td {
|
&:hover .td {
|
||||||
border-top: 1px solid #177ac9;
|
border-top: 1px solid #177ac9;
|
||||||
@@ -58,15 +100,8 @@ const Dashboard = () => {
|
|||||||
`
|
`
|
||||||
});
|
});
|
||||||
|
|
||||||
function onTreeChange(action, state) {
|
|
||||||
// do nothing for now
|
|
||||||
}
|
|
||||||
|
|
||||||
const tree = useTree(
|
const tree = useTree(
|
||||||
{ nodes: data },
|
{ nodes: data },
|
||||||
{
|
|
||||||
onChange: onTreeChange
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
treeIcon: {
|
treeIcon: {
|
||||||
margin: '4px',
|
margin: '4px',
|
||||||
@@ -84,22 +119,38 @@ const Dashboard = () => {
|
|||||||
tree.fns.onToggleAll({});
|
tree.fns.onToggleAll({});
|
||||||
setFirstLoad(false);
|
setFirstLoad(false);
|
||||||
}
|
}
|
||||||
}, [data]);
|
});
|
||||||
|
|
||||||
const showName = (di: DashboardItem) => {
|
const showName = (di: DashboardItem) => {
|
||||||
if (di.id < 100) {
|
if (di.id < 100) {
|
||||||
|
// if its a device row
|
||||||
if (di.nodes?.length) {
|
if (di.nodes?.length) {
|
||||||
return (
|
return (
|
||||||
<span>
|
<>
|
||||||
<span style={{ color: '#2196f3' }}>{di.n}</span>
|
<span style="font-size: 14px;">
|
||||||
|
<DeviceIcon type_id={di.t} />
|
||||||
|
{di.n}
|
||||||
|
</span>
|
||||||
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</span>
|
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</span>
|
||||||
</span>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <div style={{ color: '#2196f3' }}>{di.n}</div>;
|
|
||||||
}
|
}
|
||||||
|
return <div style={{ color: '#d3d3d3' }}>{di.n}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
return <div>{di.n}</div>;
|
const showDeviceValue = (di: DashboardItem) => {
|
||||||
|
// convert di to dv
|
||||||
|
// TODO should we not just use dv?
|
||||||
|
const dv: DeviceValue = {
|
||||||
|
id: ' ' + di.n,
|
||||||
|
v: di.v,
|
||||||
|
u: di.u,
|
||||||
|
c: di.c,
|
||||||
|
l: di.l
|
||||||
|
};
|
||||||
|
setSelectedDeviceValue(dv);
|
||||||
|
setDeviceValueDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShowAll = (
|
const handleShowAll = (
|
||||||
@@ -129,6 +180,7 @@ const Dashboard = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography mb={2} variant="body2" color="warning">
|
<Typography mb={2} variant="body2" color="warning">
|
||||||
|
{/* TODO translate */}
|
||||||
The dashboard shows all EMS entities that are marked as favorite, and the
|
The dashboard shows all EMS entities that are marked as favorite, and the
|
||||||
sensors.
|
sensors.
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -158,33 +210,71 @@ const Dashboard = () => {
|
|||||||
border: '1px solid grey'
|
border: '1px solid grey'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Table
|
<IconContext.Provider
|
||||||
data={{ nodes: data }}
|
value={{
|
||||||
theme={dashboard_theme}
|
color: 'lightblue',
|
||||||
layout={{ custom: true }}
|
size: '16',
|
||||||
tree={tree}
|
style: { verticalAlign: 'middle' }
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{(tableList: DashboardItem[]) => (
|
<Table
|
||||||
<Body>
|
data={{ nodes: data }}
|
||||||
{tableList.map((di: DashboardItem) => (
|
theme={dashboard_theme}
|
||||||
<Row key={di.id} item={di} disabled={di.nodes?.length === 0}>
|
layout={{ custom: true }}
|
||||||
{di.nodes?.length === 0 ? (
|
tree={tree}
|
||||||
<Cell>{showName(di)}</Cell>
|
>
|
||||||
) : (
|
{(tableList: DashboardItem[]) => (
|
||||||
<CellTree item={di}>{showName(di)}</CellTree>
|
<Body>
|
||||||
)}
|
{tableList.map((di: DashboardItem) => (
|
||||||
<Cell pinRight>{formatValue(LL, di.v, di.u)}</Cell>
|
<Row key={di.id} item={di} disabled={di.nodes?.length === 0}>
|
||||||
</Row>
|
{di.nodes?.length === 0 ? (
|
||||||
))}
|
<Cell>{showName(di)}</Cell>
|
||||||
</Body>
|
) : (
|
||||||
)}
|
<CellTree item={di}>{showName(di)}</CellTree>
|
||||||
</Table>
|
)}
|
||||||
|
<Cell pinRight>
|
||||||
|
<div style={{ color: '#d3d3d3' }}>
|
||||||
|
{formatValue(LL, di.v, di.u)}
|
||||||
|
</div>
|
||||||
|
</Cell>
|
||||||
|
|
||||||
|
<Cell stiff>
|
||||||
|
{me.admin && di.c && (
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => showDeviceValue(di)}
|
||||||
|
>
|
||||||
|
<EditIcon color="primary" sx={{ fontSize: 16 }} />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Cell>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</Body>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
</IconContext.Provider>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return <SectionContent>{renderContent()}</SectionContent>;
|
return (
|
||||||
|
<SectionContent>
|
||||||
|
{renderContent()}
|
||||||
|
{selectedDeviceValue && (
|
||||||
|
<DashboardDevicesDialog
|
||||||
|
open={deviceValueDialogOpen}
|
||||||
|
onClose={deviceValueDialogClose}
|
||||||
|
onSave={deviceValueDialogSave}
|
||||||
|
selectedItem={selectedDeviceValue}
|
||||||
|
writeable={true}
|
||||||
|
validator={deviceValueItemValidation(selectedDeviceValue)}
|
||||||
|
progress={submitting}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SectionContent>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Dashboard;
|
export default Dashboard;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/
|
|||||||
import { CgSmartHomeBoiler } from 'react-icons/cg';
|
import { CgSmartHomeBoiler } from 'react-icons/cg';
|
||||||
import { FaSolarPanel } from 'react-icons/fa';
|
import { FaSolarPanel } from 'react-icons/fa';
|
||||||
import { GiHeatHaze, GiTap } from 'react-icons/gi';
|
import { GiHeatHaze, GiTap } from 'react-icons/gi';
|
||||||
|
import { MdPlaylistAdd } from 'react-icons/md';
|
||||||
import {
|
import {
|
||||||
MdOutlineDevices,
|
MdOutlineDevices,
|
||||||
MdOutlinePool,
|
MdOutlinePool,
|
||||||
@@ -11,50 +12,40 @@ import {
|
|||||||
import { TiFlowSwitch } from 'react-icons/ti';
|
import { TiFlowSwitch } from 'react-icons/ti';
|
||||||
import { VscVmConnect } from 'react-icons/vsc';
|
import { VscVmConnect } from 'react-icons/vsc';
|
||||||
|
|
||||||
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
import type { SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
import { DeviceType } from './types';
|
import { DeviceType } from './types';
|
||||||
|
|
||||||
|
const deviceIconLookup: {
|
||||||
|
[key in DeviceType]: React.ComponentType<SvgIconProps> | undefined;
|
||||||
|
} = {
|
||||||
|
[DeviceType.TEMPERATURESENSOR]: MdOutlineSensors,
|
||||||
|
[DeviceType.ANALOGSENSOR]: MdOutlineSensors,
|
||||||
|
[DeviceType.BOILER]: CgSmartHomeBoiler,
|
||||||
|
[DeviceType.HEATSOURCE]: CgSmartHomeBoiler,
|
||||||
|
[DeviceType.THERMOSTAT]: MdThermostatAuto,
|
||||||
|
[DeviceType.MIXER]: AiOutlineControl,
|
||||||
|
[DeviceType.SOLAR]: FaSolarPanel,
|
||||||
|
[DeviceType.HEATPUMP]: GiHeatHaze,
|
||||||
|
[DeviceType.GATEWAY]: AiOutlineGateway,
|
||||||
|
[DeviceType.SWITCH]: TiFlowSwitch,
|
||||||
|
[DeviceType.CONTROLLER]: VscVmConnect,
|
||||||
|
[DeviceType.CONNECT]: VscVmConnect,
|
||||||
|
[DeviceType.ALERT]: AiOutlineAlert,
|
||||||
|
[DeviceType.EXTENSION]: MdOutlineDevices,
|
||||||
|
[DeviceType.WATER]: GiTap,
|
||||||
|
[DeviceType.POOL]: MdOutlinePool,
|
||||||
|
[DeviceType.CUSTOM]: MdPlaylistAdd,
|
||||||
|
[DeviceType.UNKNOWN]: undefined,
|
||||||
|
[DeviceType.SYSTEM]: undefined,
|
||||||
|
[DeviceType.SCHEDULER]: undefined,
|
||||||
|
[DeviceType.GENERIC]: undefined,
|
||||||
|
[DeviceType.VENTILATION]: undefined
|
||||||
|
};
|
||||||
|
|
||||||
const DeviceIcon = ({ type_id }: { type_id: DeviceType }) => {
|
const DeviceIcon = ({ type_id }: { type_id: DeviceType }) => {
|
||||||
switch (type_id) {
|
const Icon = deviceIconLookup[type_id];
|
||||||
case DeviceType.TEMPERATURESENSOR:
|
return Icon ? <Icon /> : null;
|
||||||
case DeviceType.ANALOGSENSOR:
|
|
||||||
return <MdOutlineSensors />;
|
|
||||||
case DeviceType.BOILER:
|
|
||||||
case DeviceType.HEATSOURCE:
|
|
||||||
return <CgSmartHomeBoiler />;
|
|
||||||
case DeviceType.THERMOSTAT:
|
|
||||||
return <MdThermostatAuto />;
|
|
||||||
case DeviceType.MIXER:
|
|
||||||
return <AiOutlineControl />;
|
|
||||||
case DeviceType.SOLAR:
|
|
||||||
return <FaSolarPanel />;
|
|
||||||
case DeviceType.HEATPUMP:
|
|
||||||
return <GiHeatHaze />;
|
|
||||||
case DeviceType.GATEWAY:
|
|
||||||
return <AiOutlineGateway />;
|
|
||||||
case DeviceType.SWITCH:
|
|
||||||
return <TiFlowSwitch />;
|
|
||||||
case DeviceType.CONTROLLER:
|
|
||||||
case DeviceType.CONNECT:
|
|
||||||
return <VscVmConnect />;
|
|
||||||
case DeviceType.ALERT:
|
|
||||||
return <AiOutlineAlert />;
|
|
||||||
case DeviceType.EXTENSION:
|
|
||||||
return <MdOutlineDevices />;
|
|
||||||
case DeviceType.WATER:
|
|
||||||
return <GiTap />;
|
|
||||||
case DeviceType.POOL:
|
|
||||||
return <MdOutlinePool />;
|
|
||||||
case DeviceType.CUSTOM:
|
|
||||||
return (
|
|
||||||
<PlaylistAddIcon
|
|
||||||
sx={{ color: 'lightblue', fontSize: 22, verticalAlign: 'middle' }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DeviceIcon;
|
export default DeviceIcon;
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ const Devices = () => {
|
|||||||
common_theme,
|
common_theme,
|
||||||
{
|
{
|
||||||
Table: `
|
Table: `
|
||||||
--data-table-library_grid-template-columns: 40px repeat(1, minmax(0, 1fr)) 130px;
|
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 130px;
|
||||||
`,
|
`,
|
||||||
BaseRow: `
|
BaseRow: `
|
||||||
.td {
|
.td {
|
||||||
@@ -527,57 +527,57 @@ const Devices = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderCoreData = () => (
|
const renderCoreData = () => (
|
||||||
<IconContext.Provider
|
<>
|
||||||
value={{
|
<IconContext.Provider
|
||||||
color: 'lightblue',
|
value={{
|
||||||
size: '18',
|
color: 'lightblue',
|
||||||
style: { verticalAlign: 'middle' }
|
size: '18',
|
||||||
}}
|
style: { verticalAlign: 'middle' }
|
||||||
>
|
}}
|
||||||
{!coreData.connected && (
|
>
|
||||||
<MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />
|
{!coreData.connected && (
|
||||||
)}
|
<MessageBox my={2} level="error" message={LL.EMS_BUS_WARNING()} />
|
||||||
|
)}
|
||||||
|
|
||||||
{coreData.connected && (
|
{coreData.connected && (
|
||||||
<Table
|
<Table
|
||||||
data={{ nodes: coreData.devices }}
|
data={{ nodes: coreData.devices }}
|
||||||
select={device_select}
|
select={device_select}
|
||||||
theme={device_theme}
|
theme={device_theme}
|
||||||
layout={{ custom: true }}
|
layout={{ custom: true }}
|
||||||
>
|
>
|
||||||
{(tableList: Device[]) => (
|
{(tableList: Device[]) => (
|
||||||
<>
|
<>
|
||||||
<Header>
|
<Header>
|
||||||
<HeaderRow>
|
<HeaderRow>
|
||||||
<HeaderCell stiff />
|
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
||||||
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
|
||||||
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
|
</HeaderRow>
|
||||||
</HeaderRow>
|
</Header>
|
||||||
</Header>
|
<Body>
|
||||||
<Body>
|
{tableList.length === 0 && (
|
||||||
{tableList.length === 0 && (
|
<CircularProgress sx={{ margin: 1 }} size={18} />
|
||||||
<CircularProgress sx={{ margin: 1 }} size={18} />
|
)}
|
||||||
)}
|
{tableList.map((device: Device) => (
|
||||||
{tableList.map((device: Device) => (
|
<Row key={device.id} item={device}>
|
||||||
<Row key={device.id} item={device}>
|
<Cell>
|
||||||
<Cell stiff>
|
<DeviceIcon type_id={device.t} />
|
||||||
<DeviceIcon type_id={device.t} />
|
|
||||||
</Cell>
|
{device.n}
|
||||||
<Cell>
|
<span style={{ color: 'lightblue' }}>
|
||||||
{device.n}
|
({device.e})
|
||||||
<span style={{ color: 'lightblue' }}>
|
</span>
|
||||||
({device.e})
|
</Cell>
|
||||||
</span>
|
<Cell stiff>{device.tn}</Cell>
|
||||||
</Cell>
|
</Row>
|
||||||
<Cell stiff>{device.tn}</Cell>
|
))}
|
||||||
</Row>
|
</Body>
|
||||||
))}
|
</>
|
||||||
</Body>
|
)}
|
||||||
</>
|
</Table>
|
||||||
)}
|
)}
|
||||||
</Table>
|
</IconContext.Provider>
|
||||||
)}
|
</>
|
||||||
</IconContext.Provider>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const deviceValueDialogClose = () => {
|
const deviceValueDialogClose = () => {
|
||||||
@@ -733,7 +733,7 @@ const Devices = () => {
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={() => showDeviceValue(dv)}
|
onClick={() => showDeviceValue(dv)}
|
||||||
>
|
>
|
||||||
{dv.v === '' && dv.c ? (
|
{dv.v === '' ? (
|
||||||
<PlayArrowIcon color="primary" sx={{ fontSize: 16 }} />
|
<PlayArrowIcon color="primary" sx={{ fontSize: 16 }} />
|
||||||
) : (
|
) : (
|
||||||
<EditIcon color="primary" sx={{ fontSize: 16 }} />
|
<EditIcon color="primary" sx={{ fontSize: 16 }} />
|
||||||
|
|||||||
@@ -117,9 +117,13 @@ export interface CoreData {
|
|||||||
export interface DashboardItem {
|
export interface DashboardItem {
|
||||||
id: number; // unique index
|
id: number; // unique index
|
||||||
n: string; // name
|
n: string; // name
|
||||||
v?: unknown; // value
|
v?: unknown; // value, optional
|
||||||
u: number; // uom
|
u: number; // uom
|
||||||
nodes?: DashboardItem[]; // nodes
|
t: number; // type from DeviceType
|
||||||
|
c?: string; // command, optional
|
||||||
|
l?: string[]; // list, optional
|
||||||
|
h?: string; // help text, optional
|
||||||
|
nodes?: DashboardItem[]; // nodes, optional
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardData {
|
export interface DashboardData {
|
||||||
|
|||||||
@@ -144,8 +144,8 @@ const LayoutMenu = () => {
|
|||||||
</List>
|
</List>
|
||||||
<Divider />
|
<Divider />
|
||||||
<List>
|
<List>
|
||||||
<ListItem disablePadding onClick={handleClick}>
|
<ListItem disablePadding>
|
||||||
<ListItemButton>
|
<ListItemButton component="button" onClick={handleClick}>
|
||||||
<ListItemIcon sx={{ color: '#9e9e9e' }}>
|
<ListItemIcon sx={{ color: '#9e9e9e' }}>
|
||||||
<AccountCircleIcon />
|
<AccountCircleIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
|||||||
@@ -1864,7 +1864,7 @@ __metadata:
|
|||||||
formidable: "npm:^3.5.1"
|
formidable: "npm:^3.5.1"
|
||||||
jwt-decode: "npm:^4.0.0"
|
jwt-decode: "npm:^4.0.0"
|
||||||
mime-types: "npm:^2.1.35"
|
mime-types: "npm:^2.1.35"
|
||||||
preact: "npm:^10.24.1"
|
preact: "npm:^10.24.2"
|
||||||
prettier: "npm:^3.3.3"
|
prettier: "npm:^3.3.3"
|
||||||
react: "npm:^18.3.1"
|
react: "npm:^18.3.1"
|
||||||
react-dom: "npm:^18.3.1"
|
react-dom: "npm:^18.3.1"
|
||||||
@@ -5793,10 +5793,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"preact@npm:^10.24.1":
|
"preact@npm:^10.24.2":
|
||||||
version: 10.24.1
|
version: 10.24.2
|
||||||
resolution: "preact@npm:10.24.1"
|
resolution: "preact@npm:10.24.2"
|
||||||
checksum: 10c0/f9bc8b2f88d340f1b8f854208889244059c46916449b8f8f2174fcacbc0904c445c5870896fb0cfeaf442eeade975857e8e03f0785135c41d63cd32d9414c9c6
|
checksum: 10c0/d1d22c5e1abc10eb8f83501857ef22c54a3fda2d20449d06f5b3c7d5ae812bd702c16c05b672138b8906504f9c893e072e9cebcbcada8cac320edf36265788fb
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,32 @@ const headers = {
|
|||||||
let countWifiScanPoll = 0; // wifi network scan
|
let countWifiScanPoll = 0; // wifi network scan
|
||||||
let countHardwarePoll = 0; // for during an upload
|
let countHardwarePoll = 0; // for during an upload
|
||||||
|
|
||||||
|
// DeviceTypes
|
||||||
|
const enum DeviceType {
|
||||||
|
SYSTEM = 0,
|
||||||
|
TEMPERATURESENSOR,
|
||||||
|
ANALOGSENSOR,
|
||||||
|
SCHEDULER,
|
||||||
|
CUSTOM,
|
||||||
|
BOILER,
|
||||||
|
THERMOSTAT,
|
||||||
|
MIXER,
|
||||||
|
SOLAR,
|
||||||
|
HEATPUMP,
|
||||||
|
GATEWAY,
|
||||||
|
SWITCH,
|
||||||
|
CONTROLLER,
|
||||||
|
CONNECT,
|
||||||
|
ALERT,
|
||||||
|
EXTENSION,
|
||||||
|
GENERIC,
|
||||||
|
HEATSOURCE,
|
||||||
|
VENTILATION,
|
||||||
|
WATER,
|
||||||
|
POOL,
|
||||||
|
UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
function updateMask(entity: any, de: any, dd: any) {
|
function updateMask(entity: any, de: any, dd: any) {
|
||||||
const current_mask = parseInt(entity.slice(0, 2), 16);
|
const current_mask = parseInt(entity.slice(0, 2), 16);
|
||||||
|
|
||||||
@@ -1708,7 +1734,7 @@ const emsesp_devicedata_3 = {
|
|||||||
{
|
{
|
||||||
v: 'hot',
|
v: 'hot',
|
||||||
u: 0,
|
u: 0,
|
||||||
id: '00dhw comfort',
|
id: '08dhw comfort',
|
||||||
c: 'dhw/comfort',
|
c: 'dhw/comfort',
|
||||||
l: ['hot', 'eco', 'intelligent']
|
l: ['hot', 'eco', 'intelligent']
|
||||||
},
|
},
|
||||||
@@ -4268,11 +4294,11 @@ function getDashboardEntityData(id: number) {
|
|||||||
else if (id == 10) device_data = emsesp_devicedata_10;
|
else if (id == 10) device_data = emsesp_devicedata_10;
|
||||||
else if (id == 99) device_data = emsesp_devicedata_99;
|
else if (id == 99) device_data = emsesp_devicedata_99;
|
||||||
|
|
||||||
// filter device_data, just want id, v, u
|
// filter device_data
|
||||||
// and only favorite items (bit 8 set), only for non-Custom Entities
|
// and only favorite items (bit 8 set), only for non-Custom Entities
|
||||||
// and replace id by striping off the 2-char mask
|
// and replace id by striping off the 2-char mask
|
||||||
let new_data = (device_data as any).data
|
let new_data = (device_data as any).data
|
||||||
.map(({ id, c, m, x, s, h, l, ...rest }) => ({
|
.map(({ id, m, x, s, ...rest }) => ({
|
||||||
...rest,
|
...rest,
|
||||||
id2: id
|
id2: id
|
||||||
}))
|
}))
|
||||||
@@ -4281,9 +4307,14 @@ function getDashboardEntityData(id: number) {
|
|||||||
id: id * 100 + index, // unique id
|
id: id * 100 + index, // unique id
|
||||||
n: item.id2.slice(2), // name
|
n: item.id2.slice(2), // name
|
||||||
v: item.v, // value
|
v: item.v, // value
|
||||||
u: item.u // uom
|
u: item.u, // uom
|
||||||
|
c: item.c, // command
|
||||||
|
l: item.l, // list
|
||||||
|
h: item.h // help
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// TODO only and command if not marked as READONLY
|
||||||
|
|
||||||
return new_data;
|
return new_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4325,10 +4356,12 @@ router
|
|||||||
params.id ? deviceEntities(Number(params.id)) : status(404)
|
params.id ? deviceEntities(Number(params.id)) : status(404)
|
||||||
)
|
)
|
||||||
.get(EMSESP_DASHBOARD_DATA_ENDPOINT, () => {
|
.get(EMSESP_DASHBOARD_DATA_ENDPOINT, () => {
|
||||||
let dashboard_data = [];
|
let dashboard_data: { id?: number; n?: string; t?: number; nodes?: any[] }[] =
|
||||||
let dashboard_object = {};
|
[];
|
||||||
|
let dashboard_object: { id?: number; n?: string; t?: number; nodes?: any[] } =
|
||||||
|
{};
|
||||||
let fake = false;
|
let fake = false;
|
||||||
// let fake = true;
|
// let fake = true; // fakes no data
|
||||||
|
|
||||||
if (!fake) {
|
if (!fake) {
|
||||||
// pick EMS devices from coredata
|
// pick EMS devices from coredata
|
||||||
@@ -4338,11 +4371,12 @@ router
|
|||||||
dashboard_object = {
|
dashboard_object = {
|
||||||
id: id,
|
id: id,
|
||||||
n: element.n,
|
n: element.n,
|
||||||
|
t: element.t,
|
||||||
nodes: getDashboardEntityData(id)
|
nodes: getDashboardEntityData(id)
|
||||||
};
|
};
|
||||||
|
|
||||||
// only add to dashboard if we have values
|
// only add to dashboard if we have values
|
||||||
if (dashboard_object.nodes.length > 0) {
|
if ((dashboard_object.nodes ?? []).length > 0) {
|
||||||
dashboard_data.push(dashboard_object);
|
dashboard_data.push(dashboard_object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4351,15 +4385,16 @@ router
|
|||||||
dashboard_object = {
|
dashboard_object = {
|
||||||
id: 99,
|
id: 99,
|
||||||
n: 'Custom Entities',
|
n: 'Custom Entities',
|
||||||
|
t: 4, // DeviceType::CUSTOM
|
||||||
nodes: getDashboardEntityData(99)
|
nodes: getDashboardEntityData(99)
|
||||||
};
|
};
|
||||||
// only add to dashboard if we have values
|
// only add to dashboard if we have values
|
||||||
if (dashboard_object.nodes.length > 0) {
|
if ((dashboard_object.nodes ?? []).length > 0) {
|
||||||
dashboard_data.push(dashboard_object);
|
dashboard_data.push(dashboard_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add temperature sensor data
|
// add temperature sensor data. no command c
|
||||||
let sensor_data = {};
|
let sensor_data: any[] = [];
|
||||||
sensor_data = emsesp_sensordata.ts.map((item, index) => ({
|
sensor_data = emsesp_sensordata.ts.map((item, index) => ({
|
||||||
id: 980 + index,
|
id: 980 + index,
|
||||||
n: item.n ? item.n : item.id, // name may not be set
|
n: item.n ? item.n : item.id, // name may not be set
|
||||||
@@ -4369,17 +4404,17 @@ router
|
|||||||
dashboard_object = {
|
dashboard_object = {
|
||||||
id: 98,
|
id: 98,
|
||||||
n: 'Temperature Sensors',
|
n: 'Temperature Sensors',
|
||||||
|
t: 1, // DeviceType::TEMPERATURESENSOR
|
||||||
nodes: sensor_data
|
nodes: sensor_data
|
||||||
};
|
};
|
||||||
// only add to dashboard if we have values
|
// only add to dashboard if we have values
|
||||||
if (dashboard_object.nodes.length > 0) {
|
if ((dashboard_object.nodes ?? []).length > 0) {
|
||||||
dashboard_data.push(dashboard_object);
|
dashboard_data.push(dashboard_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add analog sensor data
|
// add analog sensor data. no command c
|
||||||
// remove disabled sensors (t = 0)
|
// remove disabled sensors first (t = 0)
|
||||||
sensor_data = emsesp_sensordata.as.filter((item) => item.t !== 0);
|
sensor_data = emsesp_sensordata.as.filter((item) => item.t !== 0);
|
||||||
|
|
||||||
sensor_data = sensor_data.map((item, index) => ({
|
sensor_data = sensor_data.map((item, index) => ({
|
||||||
id: 970 + index,
|
id: 970 + index,
|
||||||
n: item.n,
|
n: item.n,
|
||||||
@@ -4390,10 +4425,11 @@ router
|
|||||||
dashboard_object = {
|
dashboard_object = {
|
||||||
id: 97,
|
id: 97,
|
||||||
n: 'Analog Sensors',
|
n: 'Analog Sensors',
|
||||||
|
t: 2, // DeviceType::ANALOGSENSOR
|
||||||
nodes: sensor_data
|
nodes: sensor_data
|
||||||
};
|
};
|
||||||
// only add to dashboard if we have values
|
// only add to dashboard if we have values
|
||||||
if (dashboard_object.nodes.length > 0) {
|
if ((dashboard_object.nodes ?? []).length > 0) {
|
||||||
dashboard_data.push(dashboard_object);
|
dashboard_data.push(dashboard_object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user