From abeac4f49aba617bd8d6674b9fa95bf15c1cbfe1 Mon Sep 17 00:00:00 2001 From: Noe Date: Tue, 25 Feb 2025 10:23:37 +0100 Subject: [PATCH] Differentiate between Salimax and Salidomo (csv and json) so that the frontend supports both --- .../dashboards/BatteryView/BatteryView.tsx | 340 ++--- .../BatteryView/BatteryViewSalidomo.tsx | 493 +++++++ .../BatteryView/DetailedBatteryView.tsx | 704 ++++----- .../DetailedBatteryViewSalidomo.tsx | 1312 +++++++++++++++++ .../dashboards/BatteryView/MainStats.tsx | 8 +- .../BatteryView/MainStatsSalidomo.tsx | 812 ++++++++++ .../dashboards/Installations/Installation.tsx | 25 +- .../dashboards/Installations/index.tsx | 2 +- .../SalidomoInstallations/Installation.tsx | 6 +- .../SalidomoInstallations/index.tsx | 2 +- .../SodiohomeInstallations/Installation.tsx | 4 +- .../SodiohomeInstallations/index.tsx | 2 +- .../frontend-marios2/src/interfaces/Chart.tsx | 424 +++--- 13 files changed, 3270 insertions(+), 864 deletions(-) create mode 100644 typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSalidomo.tsx create mode 100644 typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryViewSalidomo.tsx create mode 100644 typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStatsSalidomo.tsx diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx index 652504a90..4927b0e14 100644 --- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx @@ -11,6 +11,7 @@ import { TableRow, Typography } from '@mui/material'; +import { TopologyValues } from '../Log/graph.util'; import { Link, Route, @@ -18,17 +19,16 @@ import { useLocation, useNavigate } from 'react-router-dom'; +import Button from '@mui/material/Button'; +import { FormattedMessage } from 'react-intl'; import { I_S3Credentials } from '../../../interfaces/S3Types'; import routes from '../../../Resources/routes.json'; import CircularProgress from '@mui/material/CircularProgress'; -import { JSONRecordData } from '../Log/graph.util'; -import Button from '@mui/material/Button'; -import { FormattedMessage } from 'react-intl'; -import MainStats from './MainStats'; import DetailedBatteryView from './DetailedBatteryView'; +import MainStats from './MainStats'; interface BatteryViewProps { - values: JSONRecordData; + values: TopologyValues; s3Credentials: I_S3Credentials; installationId: number; productNum: number; @@ -39,15 +39,12 @@ function BatteryView(props: BatteryViewProps) { if (props.values === null && props.connected == true) { return null; } - const currentLocation = useLocation(); const navigate = useNavigate(); - - const sortedBatteryView = Object.entries(props.values.Battery.Devices) - .map(([BatteryId, battery]) => { - return { BatteryId, battery }; // Here we return an object with the id and device - }) - .sort((a, b) => parseInt(b.BatteryId) - parseInt(a.BatteryId)); + const sortedBatteryView = + props.values != null + ? [...props.values.batteryView].sort((a, b) => b.BatteryId - a.BatteryId) + : []; const [loading, setLoading] = useState(sortedBatteryView.length == 0); @@ -55,13 +52,13 @@ function BatteryView(props: BatteryViewProps) { navigate(routes.mainstats); }; - // const findBatteryData = (batteryId: number) => { - // for (let i = 0; i < props.values.batteryView.length; i++) { - // if (props.values.batteryView[i].BatteryId == batteryId) { - // return props.values.batteryView[i]; - // } - // } - // }; + const findBatteryData = (batteryId: number) => { + for (let i = 0; i < props.values.batteryView.length; i++) { + if (props.values.batteryView[i].BatteryId == batteryId) { + return props.values.batteryView[i]; + } + } + }; useEffect(() => { if (sortedBatteryView.length == 0) { @@ -179,23 +176,20 @@ function BatteryView(props: BatteryViewProps) { > } /> - {Object.entries(props.values.Battery.Devices).map( - ([BatteryId, battery]) => ( - - } - /> - ) - )} + {props.values.batteryView.map((battery) => ( + + } + /> + ))} @@ -225,9 +219,9 @@ function BatteryView(props: BatteryViewProps) { - {sortedBatteryView.map(({ BatteryId, battery }) => ( + {sortedBatteryView.map((battery) => ( - {'Node ' + BatteryId} + {'Node ' + battery.BatteryId} - {battery.Dc.Power.value + ' W'} + {battery.Power.value + ' ' + battery.Power.unit} 57 + battery.Voltage.value < 44 || + battery.Voltage.value > 57 ? '#FF033E' : '#32CD32', - color: battery.Dc.Voltage.value ? 'inherit' : 'white' + color: + battery.Voltage.value === '' ? 'white' : 'inherit' }} > - {battery.Dc.Voltage.value + ' V'} + {battery.Voltage.value + ' ' + battery.Voltage.unit} - {battery.Soc.value + ' %'} + {battery.Soc.value + ' ' + battery.Soc.unit} 300 + battery.AverageTemperature.value > 300 ? '#FF033E' - : battery.Temperatures.Cells.Average.value > 280 + : battery.AverageTemperature.value > 280 ? '#ffbf00' - : battery.Temperatures.Cells.Average.value < 245 + : battery.AverageTemperature.value < 245 ? '#008FFB' : '#32CD32' }} > - {battery.Temperatures.Cells.Average.value + ' C'} + {battery.AverageTemperature.value + + ' ' + + battery.AverageTemperature.unit} - {/*{props.productNum === 0 && (*/} - {/* <>*/} - {/* */} - {/* {battery.Warnings.value === '' ? (*/} - {/* 'None'*/} - {/* ) : battery.Warnings.value.toString().split('-')*/} - {/* .length > 1 ? (*/} - {/* */} - {/* Multiple Warnings*/} - {/* */} - {/* ) : (*/} - {/* battery.Warnings.value*/} - {/* )}*/} - {/* */} - {/* */} - {/* {battery.Alarms.value === '' ? (*/} - {/* 'None'*/} - {/* ) : battery.Alarms.value.toString().split('-')*/} - {/* .length > 1 ? (*/} - {/* */} - {/* Multiple Alarms*/} - {/* */} - {/* ) : (*/} - {/* battery.Alarms.value*/} - {/* )}*/} - {/* */} - {/* */} - {/*)}*/} - - {props.productNum === 1 && ( - <> - + {battery.Warnings.value === '' ? ( + 'None' + ) : battery.Warnings.value.toString().split('-').length > + 1 ? ( + - {Number(battery.Warnings) === 0 ? ( - 'None' - ) : Number(battery.Warnings) === 1 ? ( - - New Warning - - ) : ( - - Multiple Warnings - - )} - - + ) : ( + battery.Warnings.value + )} + + + {battery.Alarms.value === '' ? ( + 'None' + ) : battery.Alarms.value.toString().split('-').length > + 1 ? ( + - {Number(battery.Alarms) === 0 ? ( - 'None' - ) : Number(battery.Alarms) === 1 ? ( - - New Alarm - - ) : ( - - Multiple Alarms - - )} - - - )} + Multiple Alarms + + ) : ( + battery.Alarms.value + )} + ))} diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSalidomo.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSalidomo.tsx new file mode 100644 index 000000000..9dad16594 --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSalidomo.tsx @@ -0,0 +1,493 @@ +import React, { useEffect, useState } from 'react'; +import { + Container, + Grid, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography +} from '@mui/material'; +import { + Link, + Route, + Routes, + useLocation, + useNavigate +} from 'react-router-dom'; +import { I_S3Credentials } from '../../../interfaces/S3Types'; +import routes from '../../../Resources/routes.json'; +import CircularProgress from '@mui/material/CircularProgress'; +import { JSONRecordData } from '../Log/graph.util'; +import Button from '@mui/material/Button'; +import { FormattedMessage } from 'react-intl'; +import MainStatsSalidomo from './MainStatsSalidomo'; +import DetailedBatteryViewSalidomo from './DetailedBatteryViewSalidomo'; + +interface BatteryViewProps { + values: JSONRecordData; + s3Credentials: I_S3Credentials; + installationId: number; + productNum: number; + connected: boolean; +} + +function BatteryViewSalidomo(props: BatteryViewProps) { + if (props.values === null && props.connected == true) { + return null; + } + + const currentLocation = useLocation(); + const navigate = useNavigate(); + + const sortedBatteryView = Object.entries(props.values.Battery.Devices) + .map(([BatteryId, battery]) => { + return { BatteryId, battery }; // Here we return an object with the id and device + }) + .sort((a, b) => parseInt(b.BatteryId) - parseInt(a.BatteryId)); + + const [loading, setLoading] = useState(sortedBatteryView.length == 0); + + const handleMainStatsButton = () => { + navigate(routes.mainstats); + }; + + useEffect(() => { + if (sortedBatteryView.length == 0) { + setLoading(true); + } else { + setLoading(false); + } + }, [sortedBatteryView]); + + return ( + <> + {!props.connected && ( + + + + Unable to communicate with the installation + + + Please wait or refresh the page + + + )} + {loading && props.connected && ( + + + + Battery service is not available at the moment + + + Please wait or refresh the page + + + )} + + {!loading && props.connected && ( + + + + + + + + + + + + + } + /> + {Object.entries(props.values.Battery.Devices).map( + ([BatteryId, battery]) => ( + + } + /> + ) + )} + + + + + + + + Battery + Firmware + Power + Voltage + SoC + Temperature + Warnings + Alarms + + + + {sortedBatteryView.map(({ BatteryId, battery }) => ( + + + + {'Node ' + BatteryId} + + + + {battery.FwVersion.value} + + + {battery.Dc.Power.value + ' W'} + + 57 + ? '#FF033E' + : '#32CD32', + color: battery.Dc.Voltage.value ? 'inherit' : 'white' + }} + > + {battery.Dc.Voltage.value + ' V'} + + + {battery.Soc.value + ' %'} + + 300 + ? '#FF033E' + : battery.Temperatures.Cells.Average.value > 280 + ? '#ffbf00' + : battery.Temperatures.Cells.Average.value < 245 + ? '#008FFB' + : '#32CD32' + }} + > + {battery.Temperatures.Cells.Average.value + ' C'} + + + {/*{props.productNum === 0 && (*/} + {/* <>*/} + {/* */} + {/* {battery.Warnings.value === '' ? (*/} + {/* 'None'*/} + {/* ) : battery.Warnings.value.toString().split('-')*/} + {/* .length > 1 ? (*/} + {/* */} + {/* Multiple Warnings*/} + {/* */} + {/* ) : (*/} + {/* battery.Warnings.value*/} + {/* )}*/} + {/* */} + {/* */} + {/* {battery.Alarms.value === '' ? (*/} + {/* 'None'*/} + {/* ) : battery.Alarms.value.toString().split('-')*/} + {/* .length > 1 ? (*/} + {/* */} + {/* Multiple Alarms*/} + {/* */} + {/* ) : (*/} + {/* battery.Alarms.value*/} + {/* )}*/} + {/* */} + {/* */} + {/*)}*/} + + {props.productNum === 1 && ( + <> + + {Number(battery.Warnings) === 0 ? ( + 'None' + ) : Number(battery.Warnings) === 1 ? ( + + New Warning + + ) : ( + + Multiple Warnings + + )} + + + {Number(battery.Alarms) === 0 ? ( + 'None' + ) : Number(battery.Alarms) === 1 ? ( + + New Alarm + + ) : ( + + Multiple Alarms + + )} + + + )} + + ))} + +
+
+
+ )} + + ); +} + +export default BatteryViewSalidomo; diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx index 1100ab672..f5edd711c 100644 --- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx @@ -16,7 +16,7 @@ import { TableRow, Typography } from '@mui/material'; -import { Device } from '../Log/graph.util'; +import { Battery } from '../Log/graph.util'; import { useNavigate } from 'react-router-dom'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import Button from '@mui/material/Button'; @@ -25,9 +25,8 @@ import { UserType } from '../../../interfaces/UserTypes'; import { UserContext } from '../../../contexts/userContext'; interface DetailedBatteryViewProps { - batteryId: number; s3Credentials: I_S3Credentials; - batteryData: Device; + batteryData: Battery; installationId: number; productNum: number; } @@ -36,7 +35,6 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { if (props.batteryData === null) { return null; } - const navigate = useNavigate(); const [openModalFirmwareUpdate, setOpenModalFirmwareUpdate] = useState(false); const [openModalResultFirmwareUpdate, setOpenModalResultFirmwareUpdate] = @@ -67,35 +65,35 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { }; const [GreenisBlinking, setGreenisBlinking] = useState( - props.batteryData.Leds.Green.value === 'Blinking' + props.batteryData.GreenLeds.value === 'Blinking' ); const [AmberisBlinking, setAmberisBlinking] = useState( - props.batteryData.Leds.Amber.value === 'Blinking' + props.batteryData.AmberLeds.value === 'Blinking' ); const [RedisBlinking, setRedisBlinking] = useState( - props.batteryData.Leds.Red.value === 'Blinking' + props.batteryData.RedLeds.value === 'Blinking' ); const [BlueisBlinking, setBlueisBlinking] = useState( - props.batteryData.Leds.Blue.value === 'Blinking' + props.batteryData.BlueLeds.value === 'Blinking' ); useEffect(() => { const intervalId = setInterval(() => { - if (props.batteryData.Leds.Amber.value === 'Blinking') { + if (props.batteryData.AmberLeds.value === 'Blinking') { setAmberisBlinking((prevIsBlinking) => !prevIsBlinking); } - if (props.batteryData.Leds.Red.value === 'Blinking') { + if (props.batteryData.RedLeds.value === 'Blinking') { setRedisBlinking((prevIsBlinking) => !prevIsBlinking); } - if (props.batteryData.Leds.Blue.value === 'Blinking') { + if (props.batteryData.BlueLeds.value === 'Blinking') { setBlueisBlinking((prevIsBlinking) => !prevIsBlinking); } - if (props.batteryData.Leds.Green.value === 'Blinking') { + if (props.batteryData.GreenLeds.value === 'Blinking') { setGreenisBlinking((prevIsBlinking) => !prevIsBlinking); } }, 500); // Blink every 500 milliseconds @@ -131,7 +129,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { const res = await axiosConfig .post( - `/UpdateFirmware?batteryNode=${props.batteryId.toString()}&installationId=${ + `/UpdateFirmware?batteryNode=${props.batteryData.BatteryId.toString()}&installationId=${ props.installationId }&version=${selectedVersion}` ) @@ -171,7 +169,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { try { // Start the job to generate the battery log const startRes = await axiosConfig.post( - `/StartDownloadBatteryLog?batteryNode=${props.batteryId.toString()}&installationId=${ + `/StartDownloadBatteryLog?batteryNode=${props.batteryData.BatteryId.toString()}&installationId=${ props.installationId }` ); @@ -637,7 +635,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { fontWeight: 'bold' }} > - {'Node ' + props.batteryId} + {'Node ' + props.batteryData.BatteryId}
@@ -645,11 +643,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { style={{ ...batteryStringStyle, backgroundColor: - props.batteryData.BatteryStrings.String1Active.value == - 'True' || - Number( - props.batteryData.BatteryStrings.String1Active.value - ) == 0 + props.batteryData.String1Active.value == 'True' || + Number(props.batteryData.String1Active.value) == 0 ? '#32CD32' : '#FF033E' }} @@ -658,11 +653,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { style={{ ...batteryStringStyle, backgroundColor: - props.batteryData.BatteryStrings.String2Active.value == - 'True' || - Number( - props.batteryData.BatteryStrings.String2Active.value - ) == 0 + props.batteryData.String2Active.value == 'True' || + Number(props.batteryData.String2Active.value) == 0 ? '#32CD32' : '#FF033E' }} @@ -671,11 +663,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { style={{ ...batteryStringStyle, backgroundColor: - props.batteryData.BatteryStrings.String3Active.value == - 'True' || - Number( - props.batteryData.BatteryStrings.String3Active.value - ) == 0 + props.batteryData.String3Active.value == 'True' || + Number(props.batteryData.String3Active.value) == 0 ? '#32CD32' : '#FF033E' }} @@ -684,11 +673,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { style={{ ...batteryStringStyle, backgroundColor: - props.batteryData.BatteryStrings.String4Active.value == - 'True' || - Number( - props.batteryData.BatteryStrings.String4Active.value - ) == 0 + props.batteryData.String4Active.value == 'True' || + Number(props.batteryData.String4Active.value) == 0 ? '#32CD32' : '#FF033E' }} @@ -697,11 +683,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { style={{ ...batteryStringStyle, backgroundColor: - props.batteryData.BatteryStrings.String5Active.value == - 'True' || - Number( - props.batteryData.BatteryStrings.String5Active.value - ) == 0 + props.batteryData.String5Active.value == 'True' || + Number(props.batteryData.String5Active.value) == 0 ? '#32CD32' : '#FF033E' }} @@ -716,7 +699,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { marginTop: '-10px', borderRadius: '50%', backgroundColor: - props.batteryData.Leds.Green.value === 'On' || + props.batteryData.GreenLeds.value === 'On' || GreenisBlinking ? 'green' : 'transparent' @@ -731,7 +714,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { marginTop: '10px', borderRadius: '50%', backgroundColor: - props.batteryData.Leds.Amber.value === 'On' || + props.batteryData.AmberLeds.value === 'On' || AmberisBlinking ? 'orange' : 'transparent' @@ -746,7 +729,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { marginTop: '10px', borderRadius: '50%', backgroundColor: - props.batteryData.Leds.Blue.value === 'On' || BlueisBlinking + props.batteryData.BlueLeds.value === 'On' || BlueisBlinking ? '#00ccff' : 'transparent' }} @@ -760,7 +743,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { marginTop: '10px', borderRadius: '50%', backgroundColor: - props.batteryData.Leds.Red.value === 'On' || RedisBlinking + props.batteryData.RedLeds.value === 'On' || RedisBlinking ? 'red' : 'transparent' }} @@ -820,7 +803,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.Dc.Voltage.value + ' V'} + {props.batteryData.Voltage.value + + ' ' + + props.batteryData.Voltage.unit} @@ -840,7 +825,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.Dc.Current.value + ' A'} + {props.batteryData.Current.value + + ' ' + + props.batteryData.Current.unit} @@ -860,7 +847,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.Dc.Power.value + ' W'} + {props.batteryData.Power.value + + ' ' + + props.batteryData.Power.unit} @@ -881,7 +870,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.BusCurrent.value + ' A'} + {props.batteryData.BusCurrent.value + + ' ' + + props.batteryData.BusCurrent.unit} @@ -901,7 +892,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.CellsCurrent.value + ' A'} + {props.batteryData.CellsCurrent.value + + ' ' + + props.batteryData.CellsCurrent.unit} @@ -921,7 +914,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.HeatingCurrent.value + ' A'} + {props.batteryData.HeatingCurrent.value + + ' ' + + props.batteryData.HeatingCurrent.unit} @@ -942,7 +937,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.Soc.value + ' %'} + {props.batteryData.Soc.value + + ' ' + + props.batteryData.Soc.unit} @@ -952,200 +949,200 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { {/*----------------------------------------------------------------------------------------------------------------------------------*/} - {/*{props.productNum === 0 && (*/} - {/* <>*/} - {/* */} - {/* */} - {/* */} - {/* Temperature*/} - {/* */} + {props.productNum === 0 && ( + <> + + + + Temperature + - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Heating*/} - {/* */} - {/* */} - {/* {props.batteryData.HeatingTemperature.value +*/} - {/* ' ' +*/} - {/* props.batteryData.HeatingTemperature.unit}*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Board Temperature*/} - {/* */} - {/* */} - {/* {props.batteryData.BoardTemperature.value +*/} - {/* ' ' +*/} - {/* props.batteryData.BoardTemperature.unit}*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Center Cells Temperature*/} - {/* */} - {/* */} - {/* {props.batteryData.AverageTemperature.value +*/} - {/* ' ' +*/} - {/* props.batteryData.AverageTemperature.unit}*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Left Cells Temperature*/} - {/* */} - {/* */} - {/* {props.batteryData.LeftCellsTemperature.value +*/} - {/* ' ' +*/} - {/* props.batteryData.LeftCellsTemperature.unit}*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Right Cells Temperature*/} - {/* */} - {/* */} - {/* {props.batteryData.RightCellsTemperature.value +*/} - {/* ' ' +*/} - {/* props.batteryData.RightCellsTemperature.unit}*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Average Temperature*/} - {/* */} - {/* */} - {/* {props.batteryData.AverageTemperature.value +*/} - {/* ' ' +*/} - {/* props.batteryData.AverageTemperature.unit}*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* State*/} - {/* */} - {/* */} - {/* {props.batteryData.StateTemperature.value +*/} - {/* ' ' +*/} - {/* props.batteryData.StateTemperature.unit}*/} - {/* */} - {/* */} - {/* */} - {/*
*/} - {/* */} - {/*
*/} - {/*
*/} - {/* */} - {/*)}*/} + + + + + + Heating + + + {props.batteryData.HeatingTemperature.value + + ' ' + + props.batteryData.HeatingTemperature.unit} + + + + + Board Temperature + + + {props.batteryData.BoardTemperature.value + + ' ' + + props.batteryData.BoardTemperature.unit} + + + + + Center Cells Temperature + + + {props.batteryData.AverageTemperature.value + + ' ' + + props.batteryData.AverageTemperature.unit} + + + + + Left Cells Temperature + + + {props.batteryData.LeftCellsTemperature.value + + ' ' + + props.batteryData.LeftCellsTemperature.unit} + + + + + Right Cells Temperature + + + {props.batteryData.RightCellsTemperature.value + + ' ' + + props.batteryData.RightCellsTemperature.unit} + + + + + Average Temperature + + + {props.batteryData.AverageTemperature.value + + ' ' + + props.batteryData.AverageTemperature.unit} + + + + + State + + + {props.batteryData.StateTemperature.value + + ' ' + + props.batteryData.StateTemperature.unit} + + + +
+
+ +
+ + )} {/*----------------------------------------------------------------------------------------------------------------------------------*/} @@ -1196,9 +1193,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.IoStatus.ConnectedToDcBus.value - ? 'True' - : 'False'} + {props.batteryData.ConnectedToDcBus.value + + ' ' + + props.batteryData.ConnectedToDcBus.unit} @@ -1218,9 +1215,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.IoStatus.AlarmOutActive.value - ? 'True' - : 'False'} + {props.batteryData.AlarmOutActive.value + + ' ' + + props.batteryData.AlarmOutActive.unit} @@ -1240,9 +1237,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.IoStatus.InternalFanActive.value - ? 'True' - : 'False'} + {props.batteryData.InternalFanActive.value + + ' ' + + props.batteryData.InternalFanActive.unit} @@ -1262,9 +1259,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.IoStatus.VoltMeasurementAllowed.value - ? 'True' - : 'False'} + {props.batteryData.VoltMeasurementAllowed.value + + ' ' + + props.batteryData.VoltMeasurementAllowed.unit} @@ -1284,9 +1281,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.IoStatus.AuxRelayBus.value - ? 'True' - : 'False'} + {props.batteryData.AuxRelayBus.value + + ' ' + + props.batteryData.AuxRelayBus.unit} @@ -1306,9 +1303,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.IoStatus.RemoteStateActive.value - ? 'True' - : 'False'} + {props.batteryData.RemoteStateActive.value + + ' ' + + props.batteryData.RemoteStateActive.unit} @@ -1328,9 +1325,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.IoStatus.RiscActive.value - ? 'True' - : 'False'} + {props.batteryData.RiscActive.value + + ' ' + + props.batteryData.RiscActive.unit} @@ -1388,7 +1385,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.Eoc.value} + {props.batteryData.Eoc.value + + ' ' + + props.batteryData.Eoc.unit} @@ -1409,7 +1408,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.SerialNumber.value} + {props.batteryData.SerialNumber.value + + ' ' + + props.batteryData.SerialNumber.unit} @@ -1430,7 +1431,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.FwVersion.value} + {props.batteryData.FwVersion.value + + ' ' + + props.batteryData.FwVersion.unit} @@ -1450,33 +1453,35 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.TimeSinceTOC.value} + {props.batteryData.TimeSinceTOC.value + + ' ' + + props.batteryData.TimeSinceTOC.unit} - {/*{props.productNum === 0 && (*/} - {/* */} - {/* */} - {/* Calibration Charge Requested*/} - {/* */} - {/* */} - {/* {props.batteryData.CalibrationChargeRequested.value +*/} - {/* ' ' +*/} - {/* props.batteryData.CalibrationChargeRequested.unit}*/} - {/* */} - {/* */} - {/*)}*/} + {props.productNum === 0 && ( + + + Calibration Charge Requested + + + {props.batteryData.CalibrationChargeRequested.value + + ' ' + + props.batteryData.CalibrationChargeRequested.unit} + + + )} - {props.batteryData.MaxChargePower.value + ' W'} + {props.batteryData.MaxChargePower.value + + ' ' + + props.batteryData.MaxChargePower.unit} @@ -1514,7 +1521,9 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { paddingRight: '12px' }} > - {props.batteryData.MaxDischargePower.value + ' W'} + {props.batteryData.MaxDischargePower.value + + ' ' + + props.batteryData.MaxDischargePower.unit} @@ -1522,111 +1531,6 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { - - {/*----------------------------------------------------------------------------------------------------------------------------------*/} - - {/**/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Green Led*/} - {/* */} - {/* */} - {/* {props.batteryData.GreenLeds.value}*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Amber Led*/} - {/* */} - {/* */} - {/* {props.batteryData.AmberLeds.value}*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Blue Led*/} - {/* */} - {/* */} - {/* {props.batteryData.BlueLeds.value}*/} - {/* */} - {/* */} - - {/* */} - {/* */} - {/* Red Led*/} - {/* */} - {/* */} - {/* {props.batteryData.RedLeds.value}*/} - {/* */} - {/* */} - {/* */} - {/*
*/} - {/* */} - {/* */} - {/*
*/} ); } diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryViewSalidomo.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryViewSalidomo.tsx new file mode 100644 index 000000000..e428a83e9 --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryViewSalidomo.tsx @@ -0,0 +1,1312 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { I_S3Credentials } from '../../../interfaces/S3Types'; +import { + Box, + Card, + Grid, + IconButton, + MenuItem, + Modal, + Paper, + Select, + Table, + TableBody, + TableCell, + TableContainer, + TableRow, + Typography +} from '@mui/material'; +import { Device } from '../Log/graph.util'; +import { useNavigate } from 'react-router-dom'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import Button from '@mui/material/Button'; +import axiosConfig from '../../../Resources/axiosConfig'; +import { UserType } from '../../../interfaces/UserTypes'; +import { UserContext } from '../../../contexts/userContext'; + +interface DetailedBatteryViewProps { + batteryId: number; + s3Credentials: I_S3Credentials; + batteryData: Device; + installationId: number; + productNum: number; +} + +function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) { + if (props.batteryData === null) { + return null; + } + + const navigate = useNavigate(); + const [openModalFirmwareUpdate, setOpenModalFirmwareUpdate] = useState(false); + const [openModalResultFirmwareUpdate, setOpenModalResultFirmwareUpdate] = + useState(false); + const [openModalDownloadBatteryLog, setOpenModalDownloadBatteryLog] = + useState(false); + const [ + openModalStartDownloadBatteryLog, + setOpenModalStartDownloadBatteryLog + ] = useState(false); + const [openModalError, setOpenModalError] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + + const [selectedVersion, setSelectedVersion] = useState( + props.batteryData.FwVersion.value + ); + + const handleBatteryViewButton = () => { + navigate(location.pathname.split('/').slice(0, -2).join('/')); + }; + + const handleUpdateFirmware = () => { + setOpenModalFirmwareUpdate(true); + }; + + const firmwareModalResultHandleOk = () => { + navigate(location.pathname.split('/').slice(0, -2).join('/')); + }; + + const [GreenisBlinking, setGreenisBlinking] = useState( + props.batteryData.Leds.Green.value === 'Blinking' + ); + + const [AmberisBlinking, setAmberisBlinking] = useState( + props.batteryData.Leds.Amber.value === 'Blinking' + ); + const [RedisBlinking, setRedisBlinking] = useState( + props.batteryData.Leds.Red.value === 'Blinking' + ); + + const [BlueisBlinking, setBlueisBlinking] = useState( + props.batteryData.Leds.Blue.value === 'Blinking' + ); + + useEffect(() => { + const intervalId = setInterval(() => { + if (props.batteryData.Leds.Amber.value === 'Blinking') { + setAmberisBlinking((prevIsBlinking) => !prevIsBlinking); + } + + if (props.batteryData.Leds.Red.value === 'Blinking') { + setRedisBlinking((prevIsBlinking) => !prevIsBlinking); + } + + if (props.batteryData.Leds.Blue.value === 'Blinking') { + setBlueisBlinking((prevIsBlinking) => !prevIsBlinking); + } + + if (props.batteryData.Leds.Green.value === 'Blinking') { + setGreenisBlinking((prevIsBlinking) => !prevIsBlinking); + } + }, 500); // Blink every 500 milliseconds + + // Cleanup the interval on component unmount + return () => clearInterval(intervalId); + }, []); + + const batteryStyle = { + borderRadius: '15px', + padding: '10px', + backgroundColor: 'lightgray', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '150px' + }; + + const batteryStringStyle = { + flex: 1, + border: '1px solid #000', + height: '97%', + width: '30px', + borderRadius: '7px', + backgroundColor: '#bfbfbf' + }; + + const context = useContext(UserContext); + const { currentUser } = context; + + const FirmwareModalHandleProceed = async (e) => { + setOpenModalFirmwareUpdate(false); + + const res = await axiosConfig + .post( + `/UpdateFirmware?batteryNode=${props.batteryId.toString()}&installationId=${ + props.installationId + }&version=${selectedVersion}` + ) + .catch((err) => { + if (err.response) { + // setError(true); + // setLoading(false); + } + }); + + //if (res) { + setOpenModalResultFirmwareUpdate(true); + //} + }; + + const FirmwareModalHandleCancel = () => { + setOpenModalFirmwareUpdate(false); + }; + + const StartDownloadBatteryLogModalHandleOk = () => { + // stay in the current page which shows the single battery + setOpenModalStartDownloadBatteryLog(false); + }; + + const handleDownloadBmsLog = () => { + setOpenModalDownloadBatteryLog(true); + }; + + const DownloadBatteryLogModalHandleCancel = () => { + setOpenModalDownloadBatteryLog(false); + }; + + const DownloadBatteryLogModalHandleProceed = async () => { + setOpenModalDownloadBatteryLog(false); + setOpenModalStartDownloadBatteryLog(true); + + try { + // Start the job to generate the battery log + const startRes = await axiosConfig.post( + `/StartDownloadBatteryLog?batteryNode=${props.batteryId.toString()}&installationId=${ + props.installationId + }` + ); + + if (startRes.status === 200) { + const jobId = startRes.data; + + // Polling to check the job status + const checkJobStatus = async () => { + try { + const statusRes = await axiosConfig.get( + `/GetJobResult?jobId=${jobId}` + ); + + if (statusRes.status === 200) { + const jobStatus = statusRes.data.status; + + switch (jobStatus) { + case 'Completed': + return statusRes.data.fileName; // Return FileName upon completion + case 'Failed': + throw new Error('Job processing failed.'); + case 'Processing': + await new Promise((resolve) => setTimeout(resolve, 60000)); // Wait for 60 seconds before next check + return checkJobStatus(); + default: + throw new Error('Unknown download battery log job status.'); + } + } else { + throw new Error('Unexpected error occurred.'); + } + } catch (error) { + throw new Error('Failed to fetch job status.'); // Catch errors from status check + } + }; + + // Wait for job completion or failure + const fileName = await checkJobStatus(); + + // Once job is completed, download the file + const res = await axiosConfig.get( + `/DownloadBatteryLog?jobId=${jobId}`, + { + responseType: 'blob' + } + ); + + const finalFileName = fileName || 'unknown_file_name'; // Default filename if not received + console.log('Downloaded file name:', finalFileName); + + const url = window.URL.createObjectURL(new Blob([res.data])); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = finalFileName; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + + // Delete the file after successful download + console.log('Deleted file name:', finalFileName); + await axiosConfig.delete(`/DeleteBatteryLog`, { + params: { fileName: finalFileName } + }); + } else { + console.error( + 'Failed to start downloading battery log in the backend.' + ); + } + } catch (error) { + console.error('Error:', error.message); + setErrorMessage('Download battery log failed, please try again.'); + setOpenModalError(true); + } finally { + setOpenModalStartDownloadBatteryLog(false); + } + }; + + const ErrorModalHandleOk = () => { + setOpenModalError(false); + }; + + return ( + <> + {openModalResultFirmwareUpdate && ( + + + + The firmware is getting updated. Please wait... + + +
+ +
+
+
+ )} + + {openModalFirmwareUpdate && ( + + + + Do you really want to update the firmware? + + + + This action requires the battery service to be stopped for around + 10-15 minutes. + + +
+ + +
+
+
+ )} + + {openModalStartDownloadBatteryLog && ( + + + + The battery log is getting downloaded. It will be saved in the + Downloads folder. Please wait... + + +
+ +
+
+
+ )} + + {openModalDownloadBatteryLog && ( + + + + Do you really want to download battery log? + + + + This action requires the battery service to be stopped for around + 10-15 minutes. + + +
+ + +
+
+
+ )} + + {openModalError && ( + + + + {errorMessage} + + +
+ +
+
+
+ )} + + + + + + + + {currentUser.userType == UserType.admin && ( + <> + + + + + )} + + + + + + + {'Node ' + props.batteryId} + + +
+
+
+
+
+
+ +
+
+ +
+ +
+ +
+
+
+ + + + + + + + + Battery Information + + + + + + + + Voltage + + + {props.batteryData.Dc.Voltage.value + ' V'} + + + + + Current + + + {props.batteryData.Dc.Current.value + ' A'} + + + + + Power + + + {props.batteryData.Dc.Power.value + ' W'} + + + + + + Bus Current + + + {props.batteryData.BusCurrent.value + ' A'} + + + + + Cells Current + + + {props.batteryData.CellsCurrent.value + ' A'} + + + + + Heating Current + + + {props.batteryData.HeatingCurrent.value + ' A'} + + + + + + Soc + + + {props.batteryData.Soc.value + ' %'} + + + +
+
+
+
+ + {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + + + Io Status + + + + + + + Connected To Dc Bus + + + {props.batteryData.IoStatus.ConnectedToDcBus.value + ? 'True' + : 'False'} + + + + + Alarm Out Active + + + {props.batteryData.IoStatus.AlarmOutActive.value + ? 'True' + : 'False'} + + + + + Internal Fan Active + + + {props.batteryData.IoStatus.InternalFanActive.value + ? 'True' + : 'False'} + + + + + Volt Measurement Allowed + + + {props.batteryData.IoStatus.VoltMeasurementAllowed.value + ? 'True' + : 'False'} + + + + + Aux Relay Bus + + + {props.batteryData.IoStatus.AuxRelayBus.value + ? 'True' + : 'False'} + + + + + Remote State Active + + + {props.batteryData.IoStatus.RemoteStateActive.value + ? 'True' + : 'False'} + + + + + RiscActive + + + {props.batteryData.IoStatus.RiscActive.value + ? 'True' + : 'False'} + + + +
+
+
+
+ + {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + + + + Specification + + + + + + + Eoc + + + {props.batteryData.Eoc.value} + + + + + + Serial Number + + + {props.batteryData.SerialNumber.value} + + + + + + Firmware Version + + + {props.batteryData.FwVersion.value} + + + + + Time Since TOC + + + {props.batteryData.TimeSinceTOC.value} + + + + + + Max Charge Power + + + {props.batteryData.MaxChargePower.value + ' W'} + + + + + Max Discharge Power + + + {props.batteryData.MaxDischargePower.value + ' W'} + + + +
+
+
+
+ + {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + ); +} + +export default DetailedBatteryViewSalidomo; diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx index 75c60f8fa..d771d967c 100644 --- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx @@ -16,7 +16,7 @@ import { getChartOptions } from '../Overview/chartOptions'; import { BatteryDataInterface, BatteryOverviewInterface, - transformInputToBatteryViewDataJson + transformInputToBatteryViewData } from '../../../interfaces/Chart'; import dayjs, { Dayjs } from 'dayjs'; import { TimeSpan, UnixTime } from '../../../dataCache/time'; @@ -96,7 +96,7 @@ function MainStats(props: MainStatsProps) { const resultPromise: Promise<{ chartData: BatteryDataInterface; chartOverview: BatteryOverviewInterface; - }> = transformInputToBatteryViewDataJson( + }> = transformInputToBatteryViewData( props.s3Credentials, props.id, product, @@ -192,7 +192,7 @@ function MainStats(props: MainStatsProps) { const resultPromise: Promise<{ chartData: BatteryDataInterface; chartOverview: BatteryOverviewInterface; - }> = transformInputToBatteryViewDataJson( + }> = transformInputToBatteryViewData( props.s3Credentials, props.id, product, @@ -254,7 +254,7 @@ function MainStats(props: MainStatsProps) { const resultPromise: Promise<{ chartData: BatteryDataInterface; chartOverview: BatteryOverviewInterface; - }> = transformInputToBatteryViewDataJson( + }> = transformInputToBatteryViewData( props.s3Credentials, props.id, product, diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStatsSalidomo.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStatsSalidomo.tsx new file mode 100644 index 000000000..fbe4fe728 --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStatsSalidomo.tsx @@ -0,0 +1,812 @@ +import { + Box, + Card, + Container, + Grid, + IconButton, + Modal, + TextField, + Typography +} from '@mui/material'; +import { FormattedMessage } from 'react-intl'; +import React, { useContext, useEffect, useState } from 'react'; +import { I_S3Credentials } from '../../../interfaces/S3Types'; +import ReactApexChart from 'react-apexcharts'; +import { getChartOptions } from '../Overview/chartOptions'; +import { + BatteryDataInterface, + BatteryOverviewInterface, + transformInputToBatteryViewDataJson +} from '../../../interfaces/Chart'; +import dayjs, { Dayjs } from 'dayjs'; +import { TimeSpan, UnixTime } from '../../../dataCache/time'; +import Button from '@mui/material/Button'; +import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import CircularProgress from '@mui/material/CircularProgress'; +import { useLocation, useNavigate } from 'react-router-dom'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { ProductIdContext } from '../../../contexts/ProductIdContextProvider'; + +interface MainStatsProps { + s3Credentials: I_S3Credentials; + id: number; +} + +function MainStatsSalidomo(props: MainStatsProps) { + const [chartState, setChartState] = useState(0); + const [batteryViewDataArray, setBatteryViewDataArray] = useState< + { + chartData: BatteryDataInterface; + chartOverview: BatteryOverviewInterface; + }[] + >([]); + + const [isDateModalOpen, setIsDateModalOpen] = useState(false); + const [dateOpen, setDateOpen] = useState(false); + const navigate = useNavigate(); + const [startDate, setStartDate] = useState(dayjs().add(-1, 'day')); + const [endDate, setEndDate] = useState(dayjs()); + const [isErrorDateModalOpen, setErrorDateModalOpen] = useState(false); + const [dateSelectionError, setDateSelectionError] = useState(''); + const [loading, setLoading] = useState(true); + const location = useLocation(); + const { product, setProduct } = useContext(ProductIdContext); + + const blueColors = [ + '#99CCFF', + '#80BFFF', + '#6699CC', + '#4D99FF', + '#2670E6', + '#3366CC', + '#1A4D99', + '#133366', + '#0D274D', + '#081A33' + ]; + const redColors = [ + '#ff9090', + '#ff7070', + '#ff3f3f', + '#ff1e1e', + '#ff0606', + '#fc0000', + '#f40000', + '#d40000', + '#a30000', + '#7a0000' + ]; + const orangeColors = [ + '#ffdb99', + '#ffc968', + '#ffb837', + '#ffac16', + '#ffa706', + '#FF8C00', + '#d48900', + '#CC7A00', + '#a36900', + '#993D00' + ]; + + useEffect(() => { + setLoading(true); + + const resultPromise: Promise<{ + chartData: BatteryDataInterface; + chartOverview: BatteryOverviewInterface; + }> = transformInputToBatteryViewDataJson( + props.s3Credentials, + props.id, + product, + UnixTime.fromTicks(new Date().getTime() / 1000).earlier( + TimeSpan.fromDays(1) + ), + UnixTime.fromTicks(new Date().getTime() / 1000) + ); + + resultPromise + .then((result) => { + setBatteryViewDataArray((prevData) => + prevData.concat({ + chartData: result.chartData, + chartOverview: result.chartOverview + }) + ); + + setLoading(false); + }) + .catch((error) => { + console.error('Error:', error); + }); + }, []); + + const [isZooming, setIsZooming] = useState(false); + + useEffect(() => { + if (isZooming) { + setLoading(true); + } else if (!isZooming && batteryViewDataArray.length > 0) { + setLoading(false); + } + }, [isZooming, batteryViewDataArray]); + + function generateSeries(chartData, category, color) { + const series = []; + const pathsToSearch = [ + 'Node2', + 'Node3', + 'Node4', + 'Node5', + 'Node6', + 'Node7', + 'Node8', + 'Node9', + 'Node10', + 'Node11' + ]; + + let i = 0; + pathsToSearch.forEach((devicePath) => { + if ( + Object.hasOwnProperty.call(chartData[category].data, devicePath) && + chartData[category].data[devicePath].data.length != 0 + ) { + series.push({ + ...chartData[category].data[devicePath], + color: + color === 'blue' + ? blueColors[i] + : color === 'red' + ? redColors[i] + : orangeColors[i] + }); + } + i++; + }); + + return series; + } + + const handleCancel = () => { + setIsDateModalOpen(false); + setDateOpen(false); + }; + + const handleConfirm = () => { + setIsDateModalOpen(false); + setDateOpen(false); + + if (endDate.isAfter(dayjs())) { + setDateSelectionError('You cannot ask for future data'); + setErrorDateModalOpen(true); + return; + } else if (startDate.isAfter(endDate)) { + setDateSelectionError('Εnd date must precede start date'); + setErrorDateModalOpen(true); + return; + } + + setLoading(true); + const resultPromise: Promise<{ + chartData: BatteryDataInterface; + chartOverview: BatteryOverviewInterface; + }> = transformInputToBatteryViewDataJson( + props.s3Credentials, + props.id, + product, + UnixTime.fromTicks(startDate.unix()), + UnixTime.fromTicks(endDate.unix()) + ); + + resultPromise + .then((result) => { + setBatteryViewDataArray((prevData) => + prevData.concat({ + chartData: result.chartData, + chartOverview: result.chartOverview + }) + ); + + setLoading(false); + setChartState(batteryViewDataArray.length); + }) + .catch((error) => { + console.error('Error:', error); + }); + }; + const handleSetDate = () => { + setDateOpen(true); + setIsDateModalOpen(true); + }; + + const handleBatteryViewButton = () => { + navigate( + location.pathname.split('/').slice(0, -2).join('/') + '/batteryview' + ); + }; + + const handleGoBack = () => { + if (chartState > 0) { + setChartState(chartState - 1); + } + }; + + const handleGoForward = () => { + if (chartState + 1 < batteryViewDataArray.length) { + setChartState(chartState + 1); + } + }; + + const handleOkOnErrorDateModal = () => { + setErrorDateModalOpen(false); + }; + + const startZoom = () => { + setIsZooming(true); + }; + + const handleBeforeZoom = (chartContext, { xaxis }) => { + const startX = parseInt(xaxis.min) / 1000; + const endX = parseInt(xaxis.max) / 1000; + + const resultPromise: Promise<{ + chartData: BatteryDataInterface; + chartOverview: BatteryOverviewInterface; + }> = transformInputToBatteryViewDataJson( + props.s3Credentials, + props.id, + product, + UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)), + UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2)) + ); + + resultPromise + .then((result) => { + setBatteryViewDataArray((prevData) => + prevData.concat({ + chartData: result.chartData, + chartOverview: result.chartOverview + }) + ); + + setIsZooming(false); + setChartState(batteryViewDataArray.length); + }) + .catch((error) => { + console.error('Error:', error); + }); + }; + + return ( + <> + {loading && ( + + + + Fetching data... + + + )} + {isErrorDateModalOpen && ( + {}}> + + + {dateSelectionError} + + + + + + )} + {isDateModalOpen && ( + + {}}> + + { + // Type assertion to Dayjs + if (newDate) { + setStartDate(newDate); + } + }} + renderInput={(props) => } + /> + + { + // Type assertion to Dayjs + if (newDate) { + setEndDate(newDate); + } + }} + renderInput={(props) => } + /> + +
+ + + +
+
+
+
+ )} + + {!loading && ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + { + startZoom(); + handleBeforeZoom(chartContext, options); + } + } + } + }} + series={generateSeries( + batteryViewDataArray[chartState].chartData, + 'Soc', + 'blue' + )} + type="line" + height={420} + /> + + + + + + + + + + + + + + + + + { + startZoom(); + handleBeforeZoom(chartContext, options); + } + } + } + }} + series={generateSeries( + batteryViewDataArray[chartState].chartData, + 'Temperature', + 'blue' + )} + type="line" + height={420} + /> + + + + + + + + + + + + + + + + + { + startZoom(); + handleBeforeZoom(chartContext, options); + } + } + } + }} + series={generateSeries( + batteryViewDataArray[chartState].chartData, + 'Power', + 'red' + )} + type="line" + height={420} + /> + + + + + + + + + + + + + + + + + { + startZoom(); + handleBeforeZoom(chartContext, options); + } + } + } + }} + series={generateSeries( + batteryViewDataArray[chartState].chartData, + 'Voltage', + 'orange' + )} + type="line" + height={420} + /> + + + + + + + + + + + + + + + + + { + startZoom(); + handleBeforeZoom(chartContext, options); + } + } + } + }} + series={generateSeries( + batteryViewDataArray[chartState].chartData, + 'Current', + 'orange' + )} + type="line" + height={420} + /> + + + + + )} + + ); +} + +export default MainStatsSalidomo; diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx index 76532787a..b760cf5be 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx @@ -30,6 +30,7 @@ import Information from '../Information/Information'; import { UserType } from '../../../interfaces/UserTypes'; import HistoryOfActions from '../History/History'; import PvView from '../PvView/PvView'; +import BatteryView from '../BatteryView/BatteryView'; interface singleInstallationProps { current_installation?: I_Installation; @@ -421,18 +422,18 @@ function Installation(props: singleInstallationProps) { } /> - {/**/} - {/* }*/} - {/*>*/} + + } + > tabList.includes(pathElement))); } }, [location]); diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx index 3acc0b311..d5131e1d2 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx @@ -16,7 +16,7 @@ import { fetchDataJson } from 'src/content/dashboards/Installations/fetchData'; import { Navigate, Route, Routes, useLocation } from 'react-router-dom'; import routes from '../../../Resources/routes.json'; import InformationSalidomo from '../Information/InformationSalidomo'; -import BatteryView from '../BatteryView/BatteryView'; +import BatteryViewSalidomo from '../BatteryView/BatteryViewSalidomo'; import Log from '../Log/Log'; import CancelIcon from '@mui/icons-material/Cancel'; import SalidomoOverview from '../Overview/salidomoOverview'; @@ -361,13 +361,13 @@ function SalidomoInstallation(props: singleInstallationProps) { + > } > diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx index fe1fa07ec..d7b913b8d 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx @@ -49,7 +49,7 @@ function SalidomoInstallationTabs() { } else if (path[path.length - 2] === 'tree') { setCurrentTab('tree'); } else { - //Even if we are located at path: /batteryview/mainstats, we want the BatteryView tab to be bold + //Even if we are located at path: /batteryview/mainstats, we want the BatteryViewSalidomo tab to be bold setCurrentTab(path.find((pathElement) => tabList.includes(pathElement))); } }, [location]); diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx index b21b85423..2adc8315b 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx @@ -364,13 +364,13 @@ function SodioHomeInstallation(props: singleInstallationProps) { {/**/} + {/* >*/} {/* }*/} {/*>*/} diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx index aa3771be4..190619c73 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx @@ -48,7 +48,7 @@ function SodioHomeInstallationTabs() { } else if (path[path.length - 2] === 'tree') { setCurrentTab('tree'); } else { - //Even if we are located at path: /batteryview/mainstats, we want the BatteryView tab to be bold + //Even if we are located at path: /batteryview/mainstats, we want the BatteryViewSalidomo tab to be bold setCurrentTab(path.find((pathElement) => tabList.includes(pathElement))); } }, [location]); diff --git a/typescript/frontend-marios2/src/interfaces/Chart.tsx b/typescript/frontend-marios2/src/interfaces/Chart.tsx index 8b03c78f7..8216d8feb 100644 --- a/typescript/frontend-marios2/src/interfaces/Chart.tsx +++ b/typescript/frontend-marios2/src/interfaces/Chart.tsx @@ -401,6 +401,7 @@ export const transformInputToAggregatedDataJson = async ( ) { // Handle not available or try later case } else { + console.log(result); dateList.push(currentDay.format('DD-MM')); pathsToSearch.forEach((path) => { const value = path @@ -410,7 +411,6 @@ export const transformInputToAggregatedDataJson = async ( if (value !== undefined) { if (path === '.GridExportPower') { result.GridExportPower = -value; - // result[path].value = -result[path].value; } if (value < overviewData[path].min) { overviewData[path].min = value; @@ -549,222 +549,222 @@ export const transformInputToAggregatedDataJson = async ( // 'Node2': {name:'Node2', data: [[timestamp,value],[timestamp,value]]}, // ]} -// export const transformInputToBatteryViewData = async ( -// s3Credentials: I_S3Credentials, -// id: number, -// product: number, -// start_time?: UnixTime, -// end_time?: UnixTime -// ): Promise<{ -// chartData: BatteryDataInterface; -// chartOverview: BatteryOverviewInterface; -// }> => { -// const prefixes = ['', 'k', 'M', 'G', 'T']; -// const MAX_NUMBER = 9999999; -// const categories = ['Soc', 'Temperature', 'Power', 'Voltage', 'Current']; -// const pathCategories = [ -// 'Soc', -// 'Temperatures/Cells/Average', -// 'Dc/Power', -// 'Dc/Voltage', -// 'Dc/Current' -// ]; -// -// const pathsToSearch = [ -// '/Battery/Devices/1/', -// '/Battery/Devices/2/', -// '/Battery/Devices/3/', -// '/Battery/Devices/4/', -// '/Battery/Devices/5/', -// '/Battery/Devices/6/', -// '/Battery/Devices/7/', -// '/Battery/Devices/8/', -// '/Battery/Devices/9/', -// '/Battery/Devices/10/' -// ]; -// -// const pathsToSave = []; -// -// const chartData: BatteryDataInterface = { -// Soc: { name: 'State Of Charge', data: [] }, -// Temperature: { name: 'Temperature', data: [] }, -// Power: { name: 'Power', data: [] }, -// Voltage: { name: 'Voltage', data: [] }, -// Current: { name: 'Voltage', data: [] } -// }; -// -// const chartOverview: BatteryOverviewInterface = { -// Soc: { magnitude: 0, unit: '', min: 0, max: 0 }, -// Temperature: { magnitude: 0, unit: '', min: 0, max: 0 }, -// Power: { magnitude: 0, unit: '', min: 0, max: 0 }, -// Voltage: { magnitude: 0, unit: '', min: 0, max: 0 }, -// Current: { magnitude: 0, unit: '', min: 0, max: 0 } -// }; -// -// let initialiation = true; -// -// let timestampArray: number[] = []; -// let adjustedTimestampArray = []; -// const timestampPromises = []; -// -// await axiosConfig -// .get( -// `/GetCsvTimestampsForInstallation?id=${id}&start=${start_time.ticks}&end=${end_time.ticks}` -// ) -// .then((res: AxiosResponse) => { -// timestampArray = res.data; -// }) -// .catch((err: AxiosError) => { -// if (err.response && err.response.status == 401) { -// //removeToken(); -// //navigate(routes.login); -// } -// }); -// -// for (var i = 0; i < timestampArray.length; i++) { -// timestampPromises.push( -// fetchDataForOneTime( -// UnixTime.fromTicks(timestampArray[i], true), -// s3Credentials -// ) -// ); -// -// const adjustedTimestamp = -// product == 0 -// ? new Date(timestampArray[i] * 1000) -// : new Date(timestampArray[i] * 100000); -// //Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset -// adjustedTimestamp.setHours( -// adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60 -// ); -// adjustedTimestampArray.push(adjustedTimestamp); -// } -// -// const results: Promise>>[] = -// await Promise.all(timestampPromises); -// -// for (let i = 0; i < results.length; i++) { -// if (results[i] == null) { -// // Handle not available or try later case -// } else { -// const timestamp = Object.keys(results[i])[ -// Object.keys(results[i]).length - 1 -// ]; -// const result = results[i][timestamp]; -// const battery_nodes = result['/Config/Devices/BatteryNodes'].value -// .toString() -// .split(','); -// -// //Initialize the chartData structure based on the node names extracted from the first result -// let old_length = pathsToSave.length; -// -// if (battery_nodes.length > old_length) { -// battery_nodes.forEach((node) => { -// if (!pathsToSave.includes('Node' + node)) { -// pathsToSave.push('Node' + node); -// } -// }); -// } -// -// if (initialiation) { -// initialiation = false; -// categories.forEach((category) => { -// chartData[category].data = []; -// chartOverview[category] = { -// magnitude: 0, -// unit: '', -// min: MAX_NUMBER, -// max: -MAX_NUMBER -// }; -// }); -// } -// -// if (battery_nodes.length > old_length) { -// categories.forEach((category) => { -// pathsToSave.forEach((path) => { -// if (pathsToSave.indexOf(path) >= old_length) { -// chartData[category].data[path] = { name: path, data: [] }; -// } -// }); -// }); -// } -// -// for ( -// let category_index = 0; -// category_index < pathCategories.length; -// category_index++ -// ) { -// let category = categories[category_index]; -// -// for (let j = 0; j < pathsToSave.length; j++) { -// let path = pathsToSearch[j] + pathCategories[category_index]; -// -// if (result[path]) { -// const value = result[path]; -// -// if (value.value < chartOverview[category].min) { -// chartOverview[category].min = value.value; -// } -// -// if (value.value > chartOverview[category].max) { -// chartOverview[category].max = value.value; -// } -// chartData[category].data[pathsToSave[j]].data.push([ -// adjustedTimestampArray[i], -// value.value -// ]); -// } else { -// // chartData[category].data[pathsToSave[j]].data.push([ -// // adjustedTimestampArray[i], -// // null -// // ]); -// } -// } -// } -// } -// } -// categories.forEach((category) => { -// let value = Math.max( -// Math.abs(chartOverview[category].max), -// Math.abs(chartOverview[category].min) -// ); -// let magnitude = 0; -// -// if (value < 0) { -// value = -value; -// } -// while (value >= 1000) { -// value /= 1000; -// magnitude++; -// } -// chartOverview[category].magnitude = magnitude; -// }); -// -// chartOverview.Soc.unit = '(%)'; -// chartOverview.Soc.min = 0; -// chartOverview.Soc.max = 100; -// chartOverview.Temperature.unit = '(°C)'; -// chartOverview.Power.unit = -// '(' + prefixes[chartOverview['Power'].magnitude] + 'W' + ')'; -// chartOverview.Voltage.unit = -// '(' + prefixes[chartOverview['Voltage'].magnitude] + 'V' + ')'; -// -// chartOverview.Current.unit = -// '(' + prefixes[chartOverview['Current'].magnitude] + 'A' + ')'; -// -// return { -// chartData: chartData, -// chartOverview: chartOverview -// }; -// }; +export const transformInputToBatteryViewData = async ( + s3Credentials: I_S3Credentials, + id: number, + product: number, + start_time?: UnixTime, + end_time?: UnixTime +): Promise<{ + chartData: BatteryDataInterface; + chartOverview: BatteryOverviewInterface; +}> => { + const prefixes = ['', 'k', 'M', 'G', 'T']; + const MAX_NUMBER = 9999999; + const categories = ['Soc', 'Temperature', 'Power', 'Voltage', 'Current']; + const pathCategories = [ + 'Soc', + 'Temperatures/Cells/Average', + 'Dc/Power', + 'Dc/Voltage', + 'Dc/Current' + ]; + + const pathsToSearch = [ + '/Battery/Devices/1/', + '/Battery/Devices/2/', + '/Battery/Devices/3/', + '/Battery/Devices/4/', + '/Battery/Devices/5/', + '/Battery/Devices/6/', + '/Battery/Devices/7/', + '/Battery/Devices/8/', + '/Battery/Devices/9/', + '/Battery/Devices/10/' + ]; + + const pathsToSave = []; + + const chartData: BatteryDataInterface = { + Soc: { name: 'State Of Charge', data: [] }, + Temperature: { name: 'Temperature', data: [] }, + Power: { name: 'Power', data: [] }, + Voltage: { name: 'Voltage', data: [] }, + Current: { name: 'Voltage', data: [] } + }; + + const chartOverview: BatteryOverviewInterface = { + Soc: { magnitude: 0, unit: '', min: 0, max: 0 }, + Temperature: { magnitude: 0, unit: '', min: 0, max: 0 }, + Power: { magnitude: 0, unit: '', min: 0, max: 0 }, + Voltage: { magnitude: 0, unit: '', min: 0, max: 0 }, + Current: { magnitude: 0, unit: '', min: 0, max: 0 } + }; + + let initialiation = true; + + let timestampArray: number[] = []; + let adjustedTimestampArray = []; + const timestampPromises = []; + + await axiosConfig + .get( + `/GetCsvTimestampsForInstallation?id=${id}&start=${start_time.ticks}&end=${end_time.ticks}` + ) + .then((res: AxiosResponse) => { + timestampArray = res.data; + }) + .catch((err: AxiosError) => { + if (err.response && err.response.status == 401) { + //removeToken(); + //navigate(routes.login); + } + }); + + for (var i = 0; i < timestampArray.length; i++) { + timestampPromises.push( + fetchDataForOneTime( + UnixTime.fromTicks(timestampArray[i], true), + s3Credentials + ) + ); + + const adjustedTimestamp = + product == 0 + ? new Date(timestampArray[i] * 1000) + : new Date(timestampArray[i] * 100000); + //Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset + adjustedTimestamp.setHours( + adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60 + ); + adjustedTimestampArray.push(adjustedTimestamp); + } + + const results: Promise>>[] = + await Promise.all(timestampPromises); + + for (let i = 0; i < results.length; i++) { + if (results[i] == null) { + // Handle not available or try later case + } else { + const timestamp = Object.keys(results[i])[ + Object.keys(results[i]).length - 1 + ]; + const result = results[i][timestamp]; + const battery_nodes = result['/Config/Devices/BatteryNodes'].value + .toString() + .split(','); + + //Initialize the chartData structure based on the node names extracted from the first result + let old_length = pathsToSave.length; + + if (battery_nodes.length > old_length) { + battery_nodes.forEach((node) => { + if (!pathsToSave.includes('Node' + node)) { + pathsToSave.push('Node' + node); + } + }); + } + + if (initialiation) { + initialiation = false; + categories.forEach((category) => { + chartData[category].data = []; + chartOverview[category] = { + magnitude: 0, + unit: '', + min: MAX_NUMBER, + max: -MAX_NUMBER + }; + }); + } + + if (battery_nodes.length > old_length) { + categories.forEach((category) => { + pathsToSave.forEach((path) => { + if (pathsToSave.indexOf(path) >= old_length) { + chartData[category].data[path] = { name: path, data: [] }; + } + }); + }); + } + + for ( + let category_index = 0; + category_index < pathCategories.length; + category_index++ + ) { + let category = categories[category_index]; + + for (let j = 0; j < pathsToSave.length; j++) { + let path = pathsToSearch[j] + pathCategories[category_index]; + + if (result[path]) { + const value = result[path]; + + if (value.value < chartOverview[category].min) { + chartOverview[category].min = value.value; + } + + if (value.value > chartOverview[category].max) { + chartOverview[category].max = value.value; + } + chartData[category].data[pathsToSave[j]].data.push([ + adjustedTimestampArray[i], + value.value + ]); + } else { + // chartData[category].data[pathsToSave[j]].data.push([ + // adjustedTimestampArray[i], + // null + // ]); + } + } + } + } + } + categories.forEach((category) => { + let value = Math.max( + Math.abs(chartOverview[category].max), + Math.abs(chartOverview[category].min) + ); + let magnitude = 0; + + if (value < 0) { + value = -value; + } + while (value >= 1000) { + value /= 1000; + magnitude++; + } + chartOverview[category].magnitude = magnitude; + }); + + chartOverview.Soc.unit = '(%)'; + chartOverview.Soc.min = 0; + chartOverview.Soc.max = 100; + chartOverview.Temperature.unit = '(°C)'; + chartOverview.Power.unit = + '(' + prefixes[chartOverview['Power'].magnitude] + 'W' + ')'; + chartOverview.Voltage.unit = + '(' + prefixes[chartOverview['Voltage'].magnitude] + 'V' + ')'; + + chartOverview.Current.unit = + '(' + prefixes[chartOverview['Current'].magnitude] + 'A' + ')'; + + return { + chartData: chartData, + chartOverview: chartOverview + }; +}; // We use this function in order to retrieve data for main stats. -//The data is of the following form: -//'Soc' : {name:'Soc',data:[ +// The data is of the following form: +// 'Soc' : {name:'Soc',data:[ // 'Node1': {name:'Node1', data: [[timestamp,value],[timestamp,value]]}, // 'Node2': {name:'Node2', data: [[timestamp,value],[timestamp,value]]}, // ]}, -//'Temperature' : {name:'Temperature',data:[ +// 'Temperature' : {name:'Temperature',data:[ // 'Node1': {name:'Node1', data: [[timestamp,value],[timestamp,value]]}, // 'Node2': {name:'Node2', data: [[timestamp,value],[timestamp,value]]}, // ]}