diff --git a/interface/src/project/DashboardData.tsx b/interface/src/project/DashboardData.tsx
index 3c64dc673..0ba5600c5 100644
--- a/interface/src/project/DashboardData.tsx
+++ b/interface/src/project/DashboardData.tsx
@@ -24,10 +24,11 @@ import { useSnackbar } from 'notistack';
import { Table } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
-import { useSort, HeaderCellSort } from '@table-library/react-table-library/sort';
+import { useSort } from '@table-library/react-table-library/sort';
import { Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import { useRowSelect } from '@table-library/react-table-library/select';
+import DownloadIcon from '@mui/icons-material/GetApp';
import RefreshIcon from '@mui/icons-material/Refresh';
import EditIcon from '@mui/icons-material/Edit';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
@@ -98,8 +99,8 @@ const DashboardData: FC = () => {
}
`,
HeaderRow: `
+ text-transform: uppercase;
background-color: black;
- font-size: 14px;
border-bottom: 1px solid #e0e0e0;
`,
Row: `
@@ -165,8 +166,9 @@ const DashboardData: FC = () => {
height: 32px;
`,
HeaderRow: `
+ text-transform: uppercase;
background-color: black;
- font-size: 14px;
+ color: #90CAF9;
border-bottom: 1px solid #e0e0e0;
`,
Row: `
@@ -195,6 +197,7 @@ const DashboardData: FC = () => {
}
`,
BaseCell: `
+ padding-left: 8px;
border-top: 1px solid transparent;
border-right: 1px solid transparent;
border-bottom: 1px solid transparent;
@@ -202,9 +205,22 @@ const DashboardData: FC = () => {
text-align: right;
min-width: 64px;
}
+ `,
+ HeaderCell: `
+ padding-left: 0px;
`
});
+ const getSortIcon = (state: any, sortKey: any) => {
+ if (state.sortKey === sortKey && state.reverse) {
+ return ;
+ }
+
+ if (state.sortKey === sortKey && !state.reverse) {
+ return ;
+ }
+ };
+
const analog_sort = useSort(
{ nodes: sensorData.analogs },
{
@@ -222,7 +238,8 @@ const DashboardData: FC = () => {
sortFns: {
GPIO: (array) => array.sort((a, b) => a.i - b.i),
NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
- TYPE: (array) => array.sort((a, b) => a.t - b.t)
+ TYPE: (array) => array.sort((a, b) => a.t - b.t),
+ VALUE: (array) => array.sort((a, b) => a.v.toString().localeCompare(b.v.toString()))
}
}
);
@@ -250,16 +267,21 @@ const DashboardData: FC = () => {
const dv_sort = useSort(
{ nodes: deviceData.data },
- {},
+ {
+ state: {
+ sortKey: 'NAME',
+ reverse: false
+ }
+ },
{
sortIcon: {
- margin: '0px',
iconDefault: null,
iconUp: ,
iconDown:
},
sortFns: {
- NAME: (array) => array.sort((a, b) => a.id.slice(2).localeCompare(b.id.slice(2)))
+ NAME: (array) => array.sort((a, b) => a.id.slice(2).localeCompare(b.id.slice(2))),
+ VALUE: (array) => array.sort((a, b) => a.v.toString().localeCompare(b.v.toString()))
}
}
);
@@ -279,6 +301,52 @@ const DashboardData: FC = () => {
}
}
+ const escapeCsvCell = (cell: any) => {
+ if (cell == null) {
+ return '';
+ }
+ const sc = cell.toString().trim();
+ if (sc === '' || sc === '""') {
+ return sc;
+ }
+ if (sc.includes('"') || sc.includes(',') || sc.includes('\n') || sc.includes('\r')) {
+ return '"' + sc.replace(/"/g, '""') + '"';
+ }
+ return sc;
+ };
+
+ const makeCsvData = (columns: any, data: any) => {
+ return data.reduce((csvString: any, rowItem: any) => {
+ return csvString + columns.map(({ accessor }: any) => escapeCsvCell(accessor(rowItem))).join(',') + '\r\n';
+ }, columns.map(({ name }: any) => escapeCsvCell(name)).join(',') + '\r\n');
+ };
+
+ const downloadAsCsv = (columns: any, data: any, filename: string) => {
+ const csvData = makeCsvData(columns, data);
+ const csvFile = new Blob([csvData], { type: 'text/csv' });
+ const downloadLink = document.createElement('a');
+
+ downloadLink.download = filename;
+ downloadLink.href = window.URL.createObjectURL(csvFile);
+ document.body.appendChild(downloadLink);
+ downloadLink.click();
+ document.body.removeChild(downloadLink);
+ };
+
+ const hasMask = (id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask;
+
+ const handleDownloadCsv = () => {
+ const columns = [
+ { accessor: (dv: any) => dv.id.slice(2), name: 'Entity' },
+ { accessor: (dv: any) => formatValue(dv.v, dv.u), name: 'Value' }
+ ];
+ downloadAsCsv(
+ columns,
+ onlyFav ? deviceData.data.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE)) : deviceData.data,
+ 'device_entities'
+ );
+ };
+
const refreshData = () => {
const selectedDevice = device_select.state.id;
if (selectedDevice === 'sensor') {
@@ -451,7 +519,7 @@ const DashboardData: FC = () => {
};
const addAnalogSensor = () => {
- setAnalog({ id: '0', n: '', u: 0, v: 0, o: 0, t: 0, f: 1 });
+ setAnalog({ id: '0', i: 0, n: '', u: 0, v: 0, o: 0, t: 0, f: 1 });
};
const sendSensor = async () => {
@@ -585,8 +653,6 @@ const DashboardData: FC = () => {
return ;
}
- console.log('** Rendering main data');
-
return (
{coreData.devices.length === 0 && }
@@ -647,8 +713,6 @@ const DashboardData: FC = () => {
return;
}
- const hasMask = (id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask;
-
const sendCommand = (dv: DeviceValue) => {
if (dv.c && me.admin && !hasMask(dv.id, DeviceEntityMask.DV_READONLY)) {
setDeviceValue(dv);
@@ -672,8 +736,8 @@ const DashboardData: FC = () => {
setOnlyFav(!onlyFav)} />}
- label="show favorites only"
+ control={ setOnlyFav(!onlyFav)} />}
+ label={favorites only}
/>
{
<>
-
- ENTITY NAME
-
- VALUE
+
+
+
+
+
+
@@ -745,10 +825,26 @@ const DashboardData: FC = () => {
<>
-
- NAME
-
- TEMPERATURE
+
+
+
+
+
+
@@ -784,16 +880,46 @@ const DashboardData: FC = () => {
<>
-
- GPIO
-
-
- NAME
-
-
- TYPE
-
- VALUE
+
+
+
+
+
+
+
+
+
+
+
+
@@ -803,7 +929,7 @@ const DashboardData: FC = () => {
| {a.id} |
{a.n} |
{AnalogTypeNames[a.t]} |
- {formatValue(a.v, a.u)} |
+ {a.t ? formatValue(a.v, a.u) : ''} |
{me.admin && (
updateAnalog(a)}>
@@ -824,7 +950,7 @@ const DashboardData: FC = () => {
if (analog) {
try {
const response = await EMSESP.writeAnalog({
- id: analog.id,
+ i: analog.i,
name: analog.n,
offset: analog.o,
factor: analog.f,
@@ -852,7 +978,7 @@ const DashboardData: FC = () => {
if (analog) {
try {
const response = await EMSESP.writeAnalog({
- id: analog.id,
+ i: analog.i,
name: analog.n,
offset: analog.o,
factor: analog.f,
@@ -885,11 +1011,10 @@ const DashboardData: FC = () => {
{
} variant="outlined" color="secondary" onClick={refreshData}>
Refresh
+ {device_select.state.id && device_select.state.id !== 'sensor' && (
+ } variant="outlined" onClick={handleDownloadCsv}>
+ Export
+
+ )}
);
diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts
index d3d096aea..db3433a10 100644
--- a/interface/src/project/types.ts
+++ b/interface/src/project/types.ts
@@ -88,9 +88,10 @@ export interface Sensor {
}
export interface Analog {
- id: string; // id string, is GPIO
+ id: string; // id string
+ i: number; // GPIO
n: string;
- v?: number;
+ v: number; // is optional
u: number;
o: number;
f: number;
@@ -127,10 +128,10 @@ export interface Devices {
export interface DeviceValue {
id: string; // index, contains mask+name
- v?: any; // value, in any format
+ v: any; // value, in any format
u: number; // uom
- c?: string; // command
- l?: string[]; // list
+ c?: string; // command, optional
+ l?: string[]; // list, optional
h?: string; // help text, optional
s?: string; // steps for up/down, optional
m?: string; // min, optional
@@ -277,7 +278,7 @@ export interface WriteValue {
}
export interface WriteAnalog {
- id: string;
+ i: number;
name: string;
factor: number;
offset: number;
diff --git a/mock-api/server.js b/mock-api/server.js
index 0645388ad..66fbacdf5 100644
--- a/mock-api/server.js
+++ b/mock-api/server.js
@@ -410,10 +410,10 @@ const emsesp_sensordata = {
],
// sensors: [],
analogs: [
- { id: '36', n: 'motor', u: 0, o: 17, f: 0, t: 0 },
- { id: '37', n: 'External switch', v: 13, u: 0, o: 17, f: 0, t: 1 },
- { id: '39', n: 'Pulse count', v: 144, u: 0, o: 0, f: 0, t: 2 },
- { id: '40', n: 'Pressure', v: 16, u: 17, o: 0, f: 0, t: 3 },
+ { id: '36', i: 36, n: 'motor', v: 0, u: 0, o: 17, f: 0, t: 0 },
+ { id: '37', i: 37, n: 'External switch', v: 13, u: 0, o: 17, f: 0, t: 1 },
+ { id: '39', i: 39, n: 'Pulse count', v: 144, u: 0, o: 0, f: 0, t: 2 },
+ { id: '40', i: 40, n: 'Pressure', v: 16, u: 17, o: 0, f: 0, t: 3 },
],
// analogs: [],
}
@@ -485,7 +485,7 @@ const emsesp_devicedata_1 = {
const emsesp_devicedata_2 = {
label: 'Boiler: Nefit GBx72/Trendline/Cerapur/Greenstar Si/27i',
data: [
- { u: 0, id: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] },
+ { v: 0, u: 0, id: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] },
{ v: 'false', u: 0, id: '08heating active' },
{ v: 'false', u: 0, id: '04tapwater active' },
{ v: 5, u: 1, id: '04selected flow temperature', c: 'selflowtemp' },
@@ -872,7 +872,6 @@ rest_server.get(EMSESP_CORE_DATA_ENDPOINT, (req, res) => {
})
rest_server.get(EMSESP_SENSOR_DATA_ENDPOINT, (req, res) => {
console.log('send back sensor data...')
-
res.json(emsesp_sensordata)
})
rest_server.get(EMSESP_DEVICES_ENDPOINT, (req, res) => {
@@ -996,12 +995,13 @@ rest_server.post(EMSESP_WRITE_SENSOR_ENDPOINT, (req, res) => {
rest_server.post(EMSESP_WRITE_ANALOG_ENDPOINT, (req, res) => {
const analog = req.body
console.log('Write analog: ' + JSON.stringify(analog))
- objIndex = emsesp_sensordata.analogs.findIndex((obj) => obj.id == analog.id)
+ objIndex = emsesp_sensordata.analogs.findIndex((obj) => obj.i == analog.i)
if (objIndex === -1) {
console.log('new analog')
emsesp_sensordata.analogs.push({
- id: analog.id,
+ id: analog.i.toString(),
+ i: analog.i,
n: analog.name,
f: analog.factor,
o: analog.offset,
@@ -1010,9 +1010,10 @@ rest_server.post(EMSESP_WRITE_ANALOG_ENDPOINT, (req, res) => {
})
} else {
if (analog.type === -1) {
- console.log('removing analog ' + analog.id)
+ console.log('removing analog ' + analog.i)
emsesp_sensordata.analogs[objIndex].t = -1
} else {
+ console.log('updating analog ' + analog.i)
emsesp_sensordata.analogs[objIndex].n = analog.name
emsesp_sensordata.analogs[objIndex].o = analog.offset
emsesp_sensordata.analogs[objIndex].f = analog.factor
|