added Main Stats on Battery View page for SodistoreHome

This commit is contained in:
Yinyin Liu 2026-02-11 14:28:11 +01:00
parent 1b6d5a5916
commit ed1efbddeb
3 changed files with 982 additions and 120 deletions

View File

@ -12,13 +12,14 @@ import {
Typography Typography
} from '@mui/material'; } from '@mui/material';
import { JSONRecordData } from '../Log/graph.util'; import { JSONRecordData } from '../Log/graph.util';
import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Link, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { I_S3Credentials } from '../../../interfaces/S3Types'; import { I_S3Credentials } from '../../../interfaces/S3Types';
import routes from '../../../Resources/routes.json'; import routes from '../../../Resources/routes.json';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import { I_Installation } from 'src/interfaces/InstallationTypes'; import { I_Installation } from 'src/interfaces/InstallationTypes';
import MainStatsSodioHome from './MainStatsSodioHome';
interface BatteryViewSodioHomeProps { interface BatteryViewSodioHomeProps {
values: JSONRecordData; values: JSONRecordData;
@ -164,54 +165,20 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
</Grid> </Grid>
</Grid> </Grid>
{/*<Grid container>*/} <Grid container>
{/* <Routes>*/} <Routes>
{/* <Route*/} <Route
{/* path={routes.mainstats + '/*'}*/} path={routes.mainstats + '/*'}
{/* element={*/} element={
{/* <MainStats*/} <MainStatsSodioHome
{/* s3Credentials={props.s3Credentials}*/} s3Credentials={props.s3Credentials}
{/* id={props.installationId}*/} id={props.installationId}
{/* ></MainStats>*/} batteryClusterNumber={props.installation.batteryClusterNumber}
{/* }*/} ></MainStatsSodioHome>
{/* />*/} }
{/* {product === 0*/} />
{/* ? Object.entries(props.values.Battery.Devices).map(*/} </Routes>
{/* ([BatteryId, battery]) => (*/} </Grid>
{/* <Route*/}
{/* key={routes.detailed_view + BatteryId}*/}
{/* path={routes.detailed_view + BatteryId}*/}
{/* element={*/}
{/* <DetailedBatteryView*/}
{/* batteryId={Number(BatteryId)}*/}
{/* s3Credentials={props.s3Credentials}*/}
{/* batteryData={battery}*/}
{/* installationId={props.installationId}*/}
{/* productNum={product}*/}
{/* ></DetailedBatteryView>*/}
{/* }*/}
{/* />*/}
{/* )*/}
{/* )*/}
{/* : Object.entries(props.values.Battery.Devices).map(*/}
{/* ([BatteryId, battery]) => (*/}
{/* <Route*/}
{/* key={routes.detailed_view + BatteryId}*/}
{/* path={routes.detailed_view + BatteryId}*/}
{/* element={*/}
{/* <DetailedBatteryViewSodistore*/}
{/* batteryId={Number(BatteryId)}*/}
{/* s3Credentials={props.s3Credentials}*/}
{/* batteryData={battery}*/}
{/* installationId={props.installationId}*/}
{/* productNum={product}*/}
{/* ></DetailedBatteryViewSodistore>*/}
{/* }*/}
{/* />*/}
{/* )*/}
{/* )}*/}
{/* </Routes>*/}
{/*</Grid>*/}
<TableContainer <TableContainer
component={Paper} component={Paper}

View File

@ -0,0 +1,818 @@
import {
Box,
Card,
Container,
Grid,
IconButton,
Modal,
TextField,
Typography
} from '@mui/material';
import { FormattedMessage } from 'react-intl';
import React, { 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';
interface MainStatsSodioHomeProps {
s3Credentials: I_S3Credentials;
id: number;
batteryClusterNumber: number;
}
function MainStatsSodioHome(props: MainStatsSodioHomeProps) {
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 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'
];
const greenColors = [
'#90EE90',
'#77DD77',
'#5ECE5E',
'#45BF45',
'#32CD32',
'#28A428',
'#1E7B1E',
'#145214',
'#0A390A',
'#052905'
];
useEffect(() => {
setLoading(true);
const resultPromise: Promise<{
chartData: BatteryDataInterface;
chartOverview: BatteryOverviewInterface;
}> = transformInputToBatteryViewDataJson(
props.s3Credentials,
props.id,
2,
UnixTime.fromTicks(new Date().getTime() / 1000).earlier(
TimeSpan.fromDays(1)
),
UnixTime.fromTicks(new Date().getTime() / 1000),
props.batteryClusterNumber
);
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 = [];
for (let i = 0; i < props.batteryClusterNumber; i++) {
pathsToSearch.push('Node' + i);
}
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]
: color === 'green'
? greenColors[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('End date must precede start date');
setErrorDateModalOpen(true);
return;
}
setLoading(true);
const resultPromise: Promise<{
chartData: BatteryDataInterface;
chartOverview: BatteryOverviewInterface;
}> = transformInputToBatteryViewDataJson(
props.s3Credentials,
props.id,
2,
UnixTime.fromTicks(startDate.unix()),
UnixTime.fromTicks(endDate.unix()),
props.batteryClusterNumber
);
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,
2,
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2)),
props.batteryClusterNumber
);
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 && (
<Container
maxWidth="xl"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
height: '100vh'
}}
>
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
<Typography variant="body2" style={{ color: 'black' }} mt={2}>
Fetching data...
</Typography>
</Container>
)}
{isErrorDateModalOpen && (
<Modal open={isErrorDateModalOpen} onClose={() => {}}>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 450,
bgcolor: 'background.paper',
borderRadius: 4,
boxShadow: 24,
p: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<Typography
variant="body1"
gutterBottom
sx={{ fontWeight: 'bold' }}
>
{dateSelectionError}
</Typography>
<Button
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={handleOkOnErrorDateModal}
>
Ok
</Button>
</Box>
</Modal>
)}
{isDateModalOpen && (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<Modal open={isDateModalOpen} onClose={() => {}}>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 450,
bgcolor: 'background.paper',
borderRadius: 4,
boxShadow: 24,
p: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<DateTimePicker
label="Select Start Date"
value={startDate}
onChange={(newDate: Dayjs | null) => {
if (newDate) {
setStartDate(newDate);
}
}}
renderInput={(params) => (
<TextField
{...params}
sx={{
marginTop: 2,
width: '100%'
}}
/>
)}
/>
<DateTimePicker
label="Select End Date"
value={endDate}
onChange={(newDate: Dayjs | null) => {
if (newDate) {
setEndDate(newDate);
}
}}
renderInput={(params) => (
<TextField
{...params}
sx={{
marginTop: 2,
width: '100%'
}}
/>
)}
/>
<div
style={{
display: 'flex',
alignItems: 'center',
marginTop: 10
}}
>
<Button
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={handleConfirm}
>
Confirm
</Button>
<Button
sx={{
marginTop: 2,
marginLeft: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={handleCancel}
>
Cancel
</Button>
</div>
</Box>
</Modal>
</LocalizationProvider>
)}
{!loading && (
<>
<Grid item xs={6} md={6}>
<IconButton
aria-label="go back"
sx={{
marginTop: '20px',
backgroundColor: 'grey',
color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={handleBatteryViewButton}
>
<ArrowBackIcon />
</IconButton>
<Button
variant="contained"
onClick={handleSetDate}
disabled={loading}
sx={{
marginTop: '20px',
marginLeft: '20px',
backgroundColor: dateOpen ? '#808080' : '#ffc04d',
color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }
}}
>
<FormattedMessage id="set_date" defaultMessage="Set Date" />
</Button>
</Grid>
<Grid
container
justifyContent="flex-end"
alignItems="center"
item
xs={6}
md={6}
>
<Button
variant="contained"
disabled={!(chartState > 0)}
onClick={handleGoBack}
sx={{
marginTop: '20px',
marginLeft: '10px',
backgroundColor: '#ffc04d',
color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }
}}
>
<FormattedMessage id="goback" defaultMessage="Zoom out" />
</Button>
<Button
variant="contained"
disabled={!(chartState < batteryViewDataArray.length - 1)}
onClick={handleGoForward}
sx={{
marginTop: '20px',
marginLeft: '10px',
backgroundColor: '#ffc04d',
color: '#000000',
'&:hover': { bgcolor: '#f7b34d' }
}}
>
<FormattedMessage id="goback" defaultMessage="Zoom in" />
</Button>
</Grid>
<Grid
container
direction="row"
justifyContent="center"
alignItems="stretch"
spacing={3}
>
{/* Battery SOC Chart */}
<Grid item md={12} xs={12}>
<Card
sx={{
overflow: 'visible',
marginTop: '30px',
marginBottom: '30px'
}}
>
<Box sx={{ marginLeft: '20px' }}>
<Box display="flex" alignItems="center">
<Box>
<Typography variant="subtitle1" noWrap>
<FormattedMessage
id="battery_soc"
defaultMessage="Battery SOC (State Of Charge)"
/>
</Typography>
</Box>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
pt: 3
}}
></Box>
</Box>
<ReactApexChart
options={{
...getChartOptions(
batteryViewDataArray[chartState].chartOverview.Soc,
'daily',
[],
true
),
chart: {
events: {
beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
}
}
}}
series={generateSeries(
batteryViewDataArray[chartState].chartData,
'Soc',
'blue'
)}
type="line"
height={420}
/>
</Card>
</Grid>
{/* Battery Power Chart */}
<Grid item md={12} xs={12}>
<Card
sx={{
overflow: 'visible',
marginTop: '10px',
marginBottom: '30px'
}}
>
<Box sx={{ marginLeft: '20px' }}>
<Box display="flex" alignItems="center">
<Box>
<Typography variant="subtitle1" noWrap>
<FormattedMessage
id="battery_power"
defaultMessage="Battery Power"
/>
</Typography>
</Box>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
pt: 3
}}
></Box>
</Box>
<ReactApexChart
options={{
...getChartOptions(
batteryViewDataArray[chartState].chartOverview.Power,
'daily',
[],
true
),
chart: {
events: {
beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
}
}
}}
series={generateSeries(
batteryViewDataArray[chartState].chartData,
'Power',
'red'
)}
type="line"
height={420}
/>
</Card>
</Grid>
{/* Battery Voltage Chart */}
<Grid item md={12} xs={12}>
<Card
sx={{
overflow: 'visible',
marginTop: '10px',
marginBottom: '30px'
}}
>
<Box sx={{ marginLeft: '20px' }}>
<Box display="flex" alignItems="center">
<Box>
<Typography variant="subtitle1" noWrap>
<FormattedMessage
id="battery_voltage"
defaultMessage="Battery Voltage"
/>
</Typography>
</Box>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
pt: 3
}}
></Box>
</Box>
<ReactApexChart
options={{
...getChartOptions(
batteryViewDataArray[chartState].chartOverview.Voltage,
'daily',
[],
true
),
chart: {
events: {
beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
}
}
}}
series={generateSeries(
batteryViewDataArray[chartState].chartData,
'Voltage',
'orange'
)}
type="line"
height={420}
/>
</Card>
</Grid>
{/* Battery Current Chart */}
<Grid item md={12} xs={12}>
<Card
sx={{
overflow: 'visible',
marginTop: '10px',
marginBottom: '30px'
}}
>
<Box sx={{ marginLeft: '20px' }}>
<Box display="flex" alignItems="center">
<Box>
<Typography variant="subtitle1" noWrap>
<FormattedMessage
id="battery_current"
defaultMessage="Battery Current"
/>
</Typography>
</Box>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
pt: 3
}}
></Box>
</Box>
<ReactApexChart
options={{
...getChartOptions(
batteryViewDataArray[chartState].chartOverview.Current,
'daily',
[],
true
),
chart: {
events: {
beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
}
}
}}
series={generateSeries(
batteryViewDataArray[chartState].chartData,
'Current',
'orange'
)}
type="line"
height={420}
/>
</Card>
</Grid>
{/* Battery SoH Chart */}
<Grid item md={12} xs={12}>
<Card
sx={{
overflow: 'visible',
marginTop: '10px',
marginBottom: '30px'
}}
>
<Box sx={{ marginLeft: '20px' }}>
<Box display="flex" alignItems="center">
<Box>
<Typography variant="subtitle1" noWrap>
<FormattedMessage
id="battery_soh"
defaultMessage="Battery SOH (State Of Health)"
/>
</Typography>
</Box>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
pt: 3
}}
></Box>
</Box>
<ReactApexChart
options={{
...getChartOptions(
batteryViewDataArray[chartState].chartOverview.Soh,
'daily',
[],
true
),
chart: {
events: {
beforeZoom: (chartContext, options) => {
startZoom();
handleBeforeZoom(chartContext, options);
}
}
}
}}
series={generateSeries(
batteryViewDataArray[chartState].chartData,
'Soh',
'green'
)}
type="line"
height={420}
/>
</Card>
</Grid>
</Grid>
</>
)}
</>
);
}
export default MainStatsSodioHome;

View File

@ -60,6 +60,7 @@ export interface BatteryDataInterface {
Power: { name: string; data: [] }; Power: { name: string; data: [] };
Voltage: { name: string; data: [] }; Voltage: { name: string; data: [] };
Current: { name: string; data: [] }; Current: { name: string; data: [] };
Soh?: { name: string; data: [] };
} }
export interface BatteryOverviewInterface { export interface BatteryOverviewInterface {
@ -68,6 +69,7 @@ export interface BatteryOverviewInterface {
Power: chartInfoInterface; Power: chartInfoInterface;
Voltage: chartInfoInterface; Voltage: chartInfoInterface;
Current: chartInfoInterface; Current: chartInfoInterface;
Soh?: chartInfoInterface;
} }
export const transformInputToBatteryViewDataJson = async ( export const transformInputToBatteryViewDataJson = async (
@ -75,14 +77,18 @@ export const transformInputToBatteryViewDataJson = async (
id: number, id: number,
product: number, product: number,
start_time?: UnixTime, start_time?: UnixTime,
end_time?: UnixTime end_time?: UnixTime,
batteryClusterNumber?: number
): Promise<{ ): Promise<{
chartData: BatteryDataInterface; chartData: BatteryDataInterface;
chartOverview: BatteryOverviewInterface; chartOverview: BatteryOverviewInterface;
}> => { }> => {
const prefixes = ['', 'k', 'M', 'G', 'T']; const prefixes = ['', 'k', 'M', 'G', 'T'];
const MAX_NUMBER = 9999999; const MAX_NUMBER = 9999999;
const categories = ['Soc', 'Temperature', 'Power', 'Voltage', 'Current']; const isSodioHome = product === 2;
const categories = isSodioHome
? ['Soc', 'Power', 'Voltage', 'Current', 'Soh']
: ['Soc', 'Temperature', 'Power', 'Voltage', 'Current'];
const pathCategories = const pathCategories =
product === 3 product === 3
? [ ? [
@ -120,7 +126,8 @@ export const transformInputToBatteryViewDataJson = async (
Temperature: { name: 'Temperature', data: [] }, Temperature: { name: 'Temperature', data: [] },
Power: { name: 'Power', data: [] }, Power: { name: 'Power', data: [] },
Voltage: { name: 'Voltage', data: [] }, Voltage: { name: 'Voltage', data: [] },
Current: { name: 'Current', data: [] } Current: { name: 'Current', data: [] },
...(isSodioHome && { Soh: { name: 'State Of Health', data: [] } })
}; };
const chartOverview: BatteryOverviewInterface = { const chartOverview: BatteryOverviewInterface = {
@ -128,7 +135,8 @@ export const transformInputToBatteryViewDataJson = async (
Temperature: { magnitude: 0, unit: '', min: 0, max: 0 }, Temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
Power: { magnitude: 0, unit: '', min: 0, max: 0 }, Power: { magnitude: 0, unit: '', min: 0, max: 0 },
Voltage: { magnitude: 0, unit: '', min: 0, max: 0 }, Voltage: { magnitude: 0, unit: '', min: 0, max: 0 },
Current: { magnitude: 0, unit: '', min: 0, max: 0 } Current: { magnitude: 0, unit: '', min: 0, max: 0 },
...(isSodioHome && { Soh: { magnitude: 0, unit: '', min: 0, max: 0 } })
}; };
let initialiation = true; let initialiation = true;
@ -159,7 +167,7 @@ export const transformInputToBatteryViewDataJson = async (
); );
const adjustedTimestamp = const adjustedTimestamp =
product == 0 || product == 3 product == 0 || product == 2 || product == 3
? new Date(timestampArray[i] * 1000) ? new Date(timestampArray[i] * 1000)
: new Date(timestampArray[i] * 100000); : new Date(timestampArray[i] * 100000);
//Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset //Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
@ -181,79 +189,144 @@ export const transformInputToBatteryViewDataJson = async (
]; ];
const result = results[i][timestamp]; const result = results[i][timestamp];
//console.log(result);
const battery_nodes =
result.Config.Devices.BatteryNodes.toString().split(',');
//Initialize the chartData structure based on the node names extracted from the first result if (isSodioHome) {
let old_length = pathsToSave.length; // SodistoreHome: extract battery data from InverterRecord
const inv = (result as any)?.InverterRecord;
if (!inv) continue;
if (battery_nodes.length > old_length) { const numBatteries = batteryClusterNumber || 1;
battery_nodes.forEach((node) => { let old_length = pathsToSave.length;
const node_number =
product == 3 ? Number(node) + 1 : Number(node) - 1;
if (!pathsToSave.includes('Node' + node_number)) {
pathsToSave.push('Node' + node_number);
}
});
}
// console.log(pathsToSave); if (numBatteries > old_length) {
for (let b = old_length; b < numBatteries; b++) {
if (initialiation) { const nodeName = 'Node' + b;
initialiation = false; if (!pathsToSave.includes(nodeName)) {
categories.forEach((category) => { pathsToSave.push(nodeName);
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 ( if (initialiation) {
let category_index = 0; initialiation = false;
category_index < pathCategories.length; categories.forEach((category) => {
category_index++ chartData[category].data = [];
) { chartOverview[category] = {
let category = categories[category_index]; magnitude: 0,
unit: '',
min: MAX_NUMBER,
max: -MAX_NUMBER
};
});
}
if (numBatteries > old_length) {
categories.forEach((category) => {
pathsToSave.forEach((path) => {
if (pathsToSave.indexOf(path) >= old_length) {
chartData[category].data[path] = { name: path, data: [] };
}
});
});
}
// Map category names to InverterRecord field suffixes
const categoryFieldMap = {
Soc: 'Soc',
Power: 'Power',
Voltage: 'Voltage',
Current: 'Current',
Soh: 'Soh'
};
for (let j = 0; j < pathsToSave.length; j++) { for (let j = 0; j < pathsToSave.length; j++) {
let path = pathsToSearch[j] + pathCategories[category_index]; const batteryIndex = j + 1; // Battery1, Battery2, ...
categories.forEach((category) => {
const fieldName = `Battery${batteryIndex}${categoryFieldMap[category]}`;
const value = inv[fieldName];
if (get(result, path) !== undefined) { if (value !== undefined && value !== null) {
const value = path if (value < chartOverview[category].min) {
.split('.') chartOverview[category].min = value;
.reduce((o, key) => (o ? o[key] : undefined), result); }
if (value > chartOverview[category].max) {
if (value < chartOverview[category].min) { chartOverview[category].max = value;
chartOverview[category].min = value; }
chartData[category].data[pathsToSave[j]].data.push([
adjustedTimestampArray[i],
value
]);
} }
});
}
} else {
// SaliMax, Salidomo, SodistoreMax: existing logic
const battery_nodes =
result.Config.Devices.BatteryNodes.toString().split(',');
if (value > chartOverview[category].max) { //Initialize the chartData structure based on the node names extracted from the first result
chartOverview[category].max = value; let old_length = pathsToSave.length;
if (battery_nodes.length > old_length) {
battery_nodes.forEach((node) => {
const node_number =
product == 3 ? Number(node) + 1 : Number(node) - 1;
if (!pathsToSave.includes('Node' + node_number)) {
pathsToSave.push('Node' + node_number);
}
});
}
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 (get(result, path) !== undefined) {
const value = path
.split('.')
.reduce((o, key) => (o ? o[key] : undefined), result);
if (value < chartOverview[category].min) {
chartOverview[category].min = value;
}
if (value > chartOverview[category].max) {
chartOverview[category].max = value;
}
chartData[category].data[pathsToSave[j]].data.push([
adjustedTimestampArray[i],
value
]);
} }
chartData[category].data[pathsToSave[j]].data.push([
adjustedTimestampArray[i],
value
]);
} else {
// chartData[category].data[pathsToSave[j]].data.push([
// adjustedTimestampArray[i],
// null
// ]);
} }
} }
} }
@ -280,16 +353,20 @@ export const transformInputToBatteryViewDataJson = async (
chartOverview.Soc.unit = '(%)'; chartOverview.Soc.unit = '(%)';
chartOverview.Soc.min = 0; chartOverview.Soc.min = 0;
chartOverview.Soc.max = 100; chartOverview.Soc.max = 100;
chartOverview.Temperature.unit = '(°C)'; if (!isSodioHome) {
chartOverview.Temperature.unit = '(°C)';
}
chartOverview.Power.unit = chartOverview.Power.unit =
'(' + prefixes[chartOverview['Power'].magnitude] + 'W' + ')'; '(' + prefixes[chartOverview['Power'].magnitude] + 'W' + ')';
chartOverview.Voltage.unit = chartOverview.Voltage.unit =
'(' + prefixes[chartOverview['Voltage'].magnitude] + 'V' + ')'; '(' + prefixes[chartOverview['Voltage'].magnitude] + 'V' + ')';
chartOverview.Current.unit = chartOverview.Current.unit =
'(' + prefixes[chartOverview['Current'].magnitude] + 'A' + ')'; '(' + prefixes[chartOverview['Current'].magnitude] + 'A' + ')';
if (isSodioHome) {
// console.log(chartData); chartOverview.Soh.unit = '(%)';
chartOverview.Soh.min = 0;
chartOverview.Soh.max = 100;
}
return { return {
chartData: chartData, chartData: chartData,