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",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"preact": "^10.24.1",
|
||||
"preact": "^10.24.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"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 EditIcon from '@mui/icons-material/Edit';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
|
||||
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 { useTheme } from '@table-library/react-table-library/theme';
|
||||
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 { AuthenticatedContext } from 'contexts/authentication';
|
||||
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 type { DashboardItem } from './types';
|
||||
import { type DashboardItem, type DeviceValue } from './types';
|
||||
import { deviceValueItemValidation } from './validators';
|
||||
|
||||
const Dashboard = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
useLayoutTitle('Dashboard'); // TODO translate
|
||||
|
||||
@@ -34,9 +48,39 @@ const Dashboard = () => {
|
||||
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({
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: minmax(80px, auto) 120px;
|
||||
--data-table-library_grid-template-columns: minmax(80px, auto) 120px 40px;
|
||||
`,
|
||||
BaseRow: `
|
||||
font-size: 14px;
|
||||
@@ -46,10 +90,8 @@ const Dashboard = () => {
|
||||
`,
|
||||
Row: `
|
||||
background-color: #1e1e1e;
|
||||
// position: relative;
|
||||
// cursor: pointer;
|
||||
.td {
|
||||
height: 24px;
|
||||
height: 22px;
|
||||
}
|
||||
&:hover .td {
|
||||
border-top: 1px solid #177ac9;
|
||||
@@ -58,15 +100,8 @@ const Dashboard = () => {
|
||||
`
|
||||
});
|
||||
|
||||
function onTreeChange(action, state) {
|
||||
// do nothing for now
|
||||
}
|
||||
|
||||
const tree = useTree(
|
||||
{ nodes: data },
|
||||
{
|
||||
onChange: onTreeChange
|
||||
},
|
||||
{
|
||||
treeIcon: {
|
||||
margin: '4px',
|
||||
@@ -84,22 +119,38 @@ const Dashboard = () => {
|
||||
tree.fns.onToggleAll({});
|
||||
setFirstLoad(false);
|
||||
}
|
||||
}, [data]);
|
||||
});
|
||||
|
||||
const showName = (di: DashboardItem) => {
|
||||
if (di.id < 100) {
|
||||
// if its a device row
|
||||
if (di.nodes?.length) {
|
||||
return (
|
||||
<span>
|
||||
<span style={{ color: '#2196f3' }}>{di.n}</span>
|
||||
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</span>
|
||||
<>
|
||||
<span style="font-size: 14px;">
|
||||
<DeviceIcon type_id={di.t} />
|
||||
{di.n}
|
||||
</span>
|
||||
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</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 = (
|
||||
@@ -129,6 +180,7 @@ const Dashboard = () => {
|
||||
return (
|
||||
<>
|
||||
<Typography mb={2} variant="body2" color="warning">
|
||||
{/* TODO translate */}
|
||||
The dashboard shows all EMS entities that are marked as favorite, and the
|
||||
sensors.
|
||||
</Typography>
|
||||
@@ -157,6 +209,13 @@ const Dashboard = () => {
|
||||
borderRadius: 2,
|
||||
border: '1px solid grey'
|
||||
}}
|
||||
>
|
||||
<IconContext.Provider
|
||||
value={{
|
||||
color: 'lightblue',
|
||||
size: '16',
|
||||
style: { verticalAlign: 'middle' }
|
||||
}}
|
||||
>
|
||||
<Table
|
||||
data={{ nodes: data }}
|
||||
@@ -173,18 +232,49 @@ const Dashboard = () => {
|
||||
) : (
|
||||
<CellTree item={di}>{showName(di)}</CellTree>
|
||||
)}
|
||||
<Cell pinRight>{formatValue(LL, di.v, di.u)}</Cell>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/
|
||||
import { CgSmartHomeBoiler } from 'react-icons/cg';
|
||||
import { FaSolarPanel } from 'react-icons/fa';
|
||||
import { GiHeatHaze, GiTap } from 'react-icons/gi';
|
||||
import { MdPlaylistAdd } from 'react-icons/md';
|
||||
import {
|
||||
MdOutlineDevices,
|
||||
MdOutlinePool,
|
||||
@@ -11,50 +12,40 @@ import {
|
||||
import { TiFlowSwitch } from 'react-icons/ti';
|
||||
import { VscVmConnect } from 'react-icons/vsc';
|
||||
|
||||
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
||||
import type { SvgIconProps } from '@mui/material';
|
||||
|
||||
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 }) => {
|
||||
switch (type_id) {
|
||||
case DeviceType.TEMPERATURESENSOR:
|
||||
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;
|
||||
}
|
||||
const Icon = deviceIconLookup[type_id];
|
||||
return Icon ? <Icon /> : null;
|
||||
};
|
||||
|
||||
export default DeviceIcon;
|
||||
|
||||
@@ -170,7 +170,7 @@ const Devices = () => {
|
||||
common_theme,
|
||||
{
|
||||
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: `
|
||||
.td {
|
||||
@@ -527,6 +527,7 @@ const Devices = () => {
|
||||
};
|
||||
|
||||
const renderCoreData = () => (
|
||||
<>
|
||||
<IconContext.Provider
|
||||
value={{
|
||||
color: 'lightblue',
|
||||
@@ -549,7 +550,6 @@ const Devices = () => {
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
<HeaderCell stiff />
|
||||
<HeaderCell resize>{LL.DESCRIPTION()}</HeaderCell>
|
||||
<HeaderCell stiff>{LL.TYPE(0)}</HeaderCell>
|
||||
</HeaderRow>
|
||||
@@ -560,10 +560,9 @@ const Devices = () => {
|
||||
)}
|
||||
{tableList.map((device: Device) => (
|
||||
<Row key={device.id} item={device}>
|
||||
<Cell stiff>
|
||||
<DeviceIcon type_id={device.t} />
|
||||
</Cell>
|
||||
<Cell>
|
||||
<DeviceIcon type_id={device.t} />
|
||||
|
||||
{device.n}
|
||||
<span style={{ color: 'lightblue' }}>
|
||||
({device.e})
|
||||
@@ -578,6 +577,7 @@ const Devices = () => {
|
||||
</Table>
|
||||
)}
|
||||
</IconContext.Provider>
|
||||
</>
|
||||
);
|
||||
|
||||
const deviceValueDialogClose = () => {
|
||||
@@ -733,7 +733,7 @@ const Devices = () => {
|
||||
size="small"
|
||||
onClick={() => showDeviceValue(dv)}
|
||||
>
|
||||
{dv.v === '' && dv.c ? (
|
||||
{dv.v === '' ? (
|
||||
<PlayArrowIcon color="primary" sx={{ fontSize: 16 }} />
|
||||
) : (
|
||||
<EditIcon color="primary" sx={{ fontSize: 16 }} />
|
||||
|
||||
@@ -117,9 +117,13 @@ export interface CoreData {
|
||||
export interface DashboardItem {
|
||||
id: number; // unique index
|
||||
n: string; // name
|
||||
v?: unknown; // value
|
||||
v?: unknown; // value, optional
|
||||
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 {
|
||||
|
||||
@@ -144,8 +144,8 @@ const LayoutMenu = () => {
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
<ListItem disablePadding onClick={handleClick}>
|
||||
<ListItemButton>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton component="button" onClick={handleClick}>
|
||||
<ListItemIcon sx={{ color: '#9e9e9e' }}>
|
||||
<AccountCircleIcon />
|
||||
</ListItemIcon>
|
||||
|
||||
@@ -1864,7 +1864,7 @@ __metadata:
|
||||
formidable: "npm:^3.5.1"
|
||||
jwt-decode: "npm:^4.0.0"
|
||||
mime-types: "npm:^2.1.35"
|
||||
preact: "npm:^10.24.1"
|
||||
preact: "npm:^10.24.2"
|
||||
prettier: "npm:^3.3.3"
|
||||
react: "npm:^18.3.1"
|
||||
react-dom: "npm:^18.3.1"
|
||||
@@ -5793,10 +5793,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"preact@npm:^10.24.1":
|
||||
version: 10.24.1
|
||||
resolution: "preact@npm:10.24.1"
|
||||
checksum: 10c0/f9bc8b2f88d340f1b8f854208889244059c46916449b8f8f2174fcacbc0904c445c5870896fb0cfeaf442eeade975857e8e03f0785135c41d63cd32d9414c9c6
|
||||
"preact@npm:^10.24.2":
|
||||
version: 10.24.2
|
||||
resolution: "preact@npm:10.24.2"
|
||||
checksum: 10c0/d1d22c5e1abc10eb8f83501857ef22c54a3fda2d20449d06f5b3c7d5ae812bd702c16c05b672138b8906504f9c893e072e9cebcbcada8cac320edf36265788fb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -32,6 +32,32 @@ const headers = {
|
||||
let countWifiScanPoll = 0; // wifi network scan
|
||||
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) {
|
||||
const current_mask = parseInt(entity.slice(0, 2), 16);
|
||||
|
||||
@@ -1708,7 +1734,7 @@ const emsesp_devicedata_3 = {
|
||||
{
|
||||
v: 'hot',
|
||||
u: 0,
|
||||
id: '00dhw comfort',
|
||||
id: '08dhw comfort',
|
||||
c: 'dhw/comfort',
|
||||
l: ['hot', 'eco', 'intelligent']
|
||||
},
|
||||
@@ -4268,11 +4294,11 @@ function getDashboardEntityData(id: number) {
|
||||
else if (id == 10) device_data = emsesp_devicedata_10;
|
||||
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 replace id by striping off the 2-char mask
|
||||
let new_data = (device_data as any).data
|
||||
.map(({ id, c, m, x, s, h, l, ...rest }) => ({
|
||||
.map(({ id, m, x, s, ...rest }) => ({
|
||||
...rest,
|
||||
id2: id
|
||||
}))
|
||||
@@ -4281,9 +4307,14 @@ function getDashboardEntityData(id: number) {
|
||||
id: id * 100 + index, // unique id
|
||||
n: item.id2.slice(2), // name
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -4325,10 +4356,12 @@ router
|
||||
params.id ? deviceEntities(Number(params.id)) : status(404)
|
||||
)
|
||||
.get(EMSESP_DASHBOARD_DATA_ENDPOINT, () => {
|
||||
let dashboard_data = [];
|
||||
let dashboard_object = {};
|
||||
let dashboard_data: { id?: number; n?: string; t?: number; nodes?: any[] }[] =
|
||||
[];
|
||||
let dashboard_object: { id?: number; n?: string; t?: number; nodes?: any[] } =
|
||||
{};
|
||||
let fake = false;
|
||||
// let fake = true;
|
||||
// let fake = true; // fakes no data
|
||||
|
||||
if (!fake) {
|
||||
// pick EMS devices from coredata
|
||||
@@ -4338,11 +4371,12 @@ router
|
||||
dashboard_object = {
|
||||
id: id,
|
||||
n: element.n,
|
||||
t: element.t,
|
||||
nodes: getDashboardEntityData(id)
|
||||
};
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -4351,15 +4385,16 @@ router
|
||||
dashboard_object = {
|
||||
id: 99,
|
||||
n: 'Custom Entities',
|
||||
t: 4, // DeviceType::CUSTOM
|
||||
nodes: getDashboardEntityData(99)
|
||||
};
|
||||
// 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);
|
||||
}
|
||||
|
||||
// add temperature sensor data
|
||||
let sensor_data = {};
|
||||
// add temperature sensor data. no command c
|
||||
let sensor_data: any[] = [];
|
||||
sensor_data = emsesp_sensordata.ts.map((item, index) => ({
|
||||
id: 980 + index,
|
||||
n: item.n ? item.n : item.id, // name may not be set
|
||||
@@ -4369,17 +4404,17 @@ router
|
||||
dashboard_object = {
|
||||
id: 98,
|
||||
n: 'Temperature Sensors',
|
||||
t: 1, // DeviceType::TEMPERATURESENSOR
|
||||
nodes: sensor_data
|
||||
};
|
||||
// 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);
|
||||
}
|
||||
|
||||
// add analog sensor data
|
||||
// remove disabled sensors (t = 0)
|
||||
// add analog sensor data. no command c
|
||||
// remove disabled sensors first (t = 0)
|
||||
sensor_data = emsesp_sensordata.as.filter((item) => item.t !== 0);
|
||||
|
||||
sensor_data = sensor_data.map((item, index) => ({
|
||||
id: 970 + index,
|
||||
n: item.n,
|
||||
@@ -4390,10 +4425,11 @@ router
|
||||
dashboard_object = {
|
||||
id: 97,
|
||||
n: 'Analog Sensors',
|
||||
t: 2, // DeviceType::ANALOGSENSOR
|
||||
nodes: sensor_data
|
||||
};
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user