created Live View on monitor for SodistoreHome

This commit is contained in:
Yinyin Liu 2025-11-08 13:52:06 +01:00
parent 7b4f4481a3
commit 6a90884a3c
5 changed files with 319 additions and 32 deletions

View File

@ -457,6 +457,7 @@ export interface JSONRecordData {
BatteryOperatingMode: string; BatteryOperatingMode: string;
BatteryType: number; BatteryType: number;
ChargeCutoffSoc: number; ChargeCutoffSoc: number;
ConsumptionPower:number;
ControlPermession: number; ControlPermession: number;
DischargeCutoffSoc: number; DischargeCutoffSoc: number;
EmsCommunicationFailureTime: number; EmsCommunicationFailureTime: number;
@ -495,6 +496,7 @@ export interface JSONRecordData {
SystemOperatingMode: string; SystemOperatingMode: string;
TotalEnergyToGrid: number; TotalEnergyToGrid: number;
TotalEnergyToUser: number; TotalEnergyToUser: number;
TotalPvPower: number;
VppProtocolVerNumber: number; VppProtocolVerNumber: number;
}; };
@ -1040,7 +1042,12 @@ export const getHighestConnectionValue = (values: JSONRecordData) => {
'PvOnDc.Dc.Power', 'PvOnDc.Dc.Power',
'DcDc.Dc.Link.Power', 'DcDc.Dc.Link.Power',
'LoadOnDc.Power', 'LoadOnDc.Power',
'Battery.Dc.Power' 'Battery.Dc.Power',
'AcDcGrowatt.MeterPower',
'AcDcGrowatt.TotalPvPower',
'AcDcGrowatt.BatteriesRecords.Power',
'AcDcGrowatt.BatteriesRecords.TotalChargeEnergy',
'AcDcGrowatt.ConsumptionPower'
]; ];
// Helper function to safely get a value from a nested path // Helper function to safely get a value from a nested path

View File

@ -25,6 +25,7 @@ import { fetchDataJson } from '../Installations/fetchData';
import { FetchResult } from '../../../dataCache/dataCache'; import { FetchResult } from '../../../dataCache/dataCache';
import BatteryViewSodioHome from '../BatteryView/BatteryViewSodioHome'; import BatteryViewSodioHome from '../BatteryView/BatteryViewSodioHome';
import SodistoreHomeConfiguration from './SodistoreHomeConfiguration'; import SodistoreHomeConfiguration from './SodistoreHomeConfiguration';
import TopologySodistoreHome from '../Topology/TopologySodistoreHome';
interface singleInstallationProps { interface singleInstallationProps {
current_installation?: I_Installation; current_installation?: I_Installation;
@ -434,12 +435,11 @@ function SodioHomeInstallation(props: singleInstallationProps) {
<Route <Route
path={routes.live} path={routes.live}
element={ element={
<div></div> <TopologySodistoreHome
// <Topology values={values}
// values={values} connected={connected}
// connected={connected} loading={loading}
// loading={loading} ></TopologySodistoreHome>
// ></Topology>
} }
/> />

View File

@ -96,10 +96,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
const singleInstallationTabs = const singleInstallationTabs =
currentUser.userType == UserType.admin currentUser.userType == UserType.admin
? [ ? [
// { {
// value: 'live', value: 'live',
// label: <FormattedMessage id="live" defaultMessage="Live" /> label: <FormattedMessage id="live" defaultMessage="Live" />
// }, },
{ {
value: 'batteryview', value: 'batteryview',
label: ( label: (
@ -155,10 +155,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
} }
] ]
: [ : [
// { {
// value: 'live', value: 'live',
// label: <FormattedMessage id="live" defaultMessage="Live" /> label: <FormattedMessage id="live" defaultMessage="Live" />
// }, },
// { // {
// value: 'overview', // value: 'overview',
// label: <FormattedMessage id="overview" defaultMessage="Overview" /> // label: <FormattedMessage id="overview" defaultMessage="Overview" />
@ -186,10 +186,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
value: 'tree', value: 'tree',
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" /> icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
}, },
// { {
// value: 'live', value: 'live',
// label: <FormattedMessage id="live" defaultMessage="Live" /> label: <FormattedMessage id="live" defaultMessage="Live" />
// }, },
{ {
value: 'batteryview', value: 'batteryview',
label: ( label: (
@ -256,10 +256,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
value: 'tree', value: 'tree',
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" /> icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
}, },
// { {
// value: 'live', value: 'live',
// label: <FormattedMessage id="live" defaultMessage="Live" /> label: <FormattedMessage id="live" defaultMessage="Live" />
// }, },
{ {
value: 'overview', value: 'overview',
label: <FormattedMessage id="overview" defaultMessage="Overview" /> label: <FormattedMessage id="overview" defaultMessage="Overview" />

View File

@ -0,0 +1,264 @@
import React, { useContext, useState } from 'react';
import {
CircularProgress,
Container,
Grid,
Switch,
Typography
} from '@mui/material';
import TopologyColumn from './topologyColumn';
import {
getAmount,
getHighestConnectionValue,
JSONRecordData
} from '../Log/graph.util';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
interface TopologySodistoreHomeProps {
values: JSONRecordData;
connected: boolean;
loading: boolean;
}
function TopologySodistoreHome(props: TopologySodistoreHomeProps) {
if (props.values === null && props.connected == true) {
return null;
}
const highestConnectionValue =
props.values != null ? getHighestConnectionValue(props.values) : 0;
const { product, setProduct } = useContext(ProductIdContext);
const [showValues, setShowValues] = useState(false);
const handleSwitch = () => () => {
setShowValues(!showValues);
};
const isMobile = window.innerWidth <= 1490;
return (
<Container maxWidth="xl" style={{ backgroundColor: 'white' }}>
<Grid container>
{!props.connected && !props.loading && (
<Container
maxWidth="xl"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
height: '70vh'
}}
>
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
<Typography
variant="body2"
style={{ color: 'black', fontWeight: 'bold' }}
mt={2}
>
Unable to communicate with the installation
</Typography>
<Typography variant="body2" style={{ color: 'black' }}>
Please wait or refresh the page
</Typography>
</Container>
)}
{props.connected && (
<>
<Grid
item
xs={12}
md={12}
style={{
marginTop: '10px',
height: '20px',
display: 'flex',
flexDirection: 'row',
alignItems: 'right',
justifyContent: 'right'
}}
>
<div>
<Typography sx={{ marginTop: '5px', marginRight: '20px' }}>
Display Values
</Typography>
<Switch
edge="start"
color="secondary"
onChange={handleSwitch()}
sx={{
'& .MuiSwitch-thumb': {
backgroundColor: 'orange'
},
marginLeft: '20px'
}}
/>
</div>
</Grid>
<Grid
item
xs={12}
md={12}
style={{
height: isMobile ? '550px' : '600px',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center'
}}
>
<TopologyColumn
centerBox={{
title: 'Grid',
data: props.values?.AcDcGrowatt
? [
{
value: props.values.AcDcGrowatt.MeterPower,
unit: 'W'
}
]
: undefined,
connected:
true
}}
centerConnection={{
orientation: 'horizontal',
data: props.values?.AcDcGrowatt
? {
value: props.values.AcDcGrowatt.MeterPower,
unit: 'W'
}
: undefined,
amount: props.values?.AcDcGrowatt
? getAmount(
highestConnectionValue,
props.values.AcDcGrowatt.MeterPower
)
: 0,
showValues: showValues
}}
isLast={false}
isFirst={true}
/>
{/*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/}
<TopologyColumn
topBox={{
title: 'PV',
data: props.values?.AcDcGrowatt
? [
{
value: props.values.AcDcGrowatt.TotalPvPower,
unit: 'W'
}
]
: undefined,
connected:true
}}
topConnection={{
orientation: 'vertical',
position: 'top',
data: props.values?.AcDcGrowatt
? {
value: props.values.AcDcGrowatt.TotalPvPower,
unit: 'W'
}
: undefined,
amount: props.values?.AcDcGrowatt
? getAmount(highestConnectionValue, props.values.AcDcGrowatt.TotalPvPower)
: 0,
showValues: showValues
}}
centerBox={{
title: 'Inverter',
data: props.values?.AcDcGrowatt
? [
{
value: 0,
unit: 'W'
}
]
: undefined,
connected: true
}}
centerConnection={{
orientation: 'horizontal',
data: props.values?.AcDcGrowatt.BatteriesRecords
? {
value: props.values.AcDcGrowatt.BatteriesRecords.Power,
unit: 'W'
}
: undefined,
amount: props.values?.AcDcGrowatt.BatteriesRecords
? getAmount(
highestConnectionValue,
props.values.AcDcGrowatt.BatteriesRecords.Power,
)
: 0,
showValues: showValues
}}
bottomBox={{
title: 'Loads',
data: props.values?.AcDcGrowatt
? [
{
value: props.values.AcDcGrowatt.ConsumptionPower,
unit: 'W'
}
]
: undefined,
connected:true
}}
bottomConnection={{
orientation: 'vertical',
position: 'bottom',
data: props.values?.AcDcGrowatt
? {
value: props.values.AcDcGrowatt.ConsumptionPower,
unit: 'W'
}
: undefined,
amount: props.values?.AcDcGrowatt
? getAmount(
highestConnectionValue,
props.values.AcDcGrowatt.ConsumptionPower
)
: 0,
showValues: showValues
}}
isLast={false}
isFirst={false}
/>
{/*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/}
<TopologyColumn
centerBox={{
title: 'Battery',
data: props.values.AcDcGrowatt.BatteriesRecords
? [
{
value: props.values.AcDcGrowatt.BatteriesRecords.AverageSoc,
unit: '%'
},
{
value: props.values.AcDcGrowatt.BatteriesRecords.Power,
unit: 'W'
}
]
: undefined,
connected: true
}}
isLast={true}
isFirst={false}
/>
</Grid>
</>
)}
</Grid>
</Container>
);
}
export default TopologySodistoreHome;

View File

@ -39,8 +39,8 @@ function formatPower(value, unit) {
const roundedValue = value.toFixed(1); const roundedValue = value.toFixed(1);
//Filter all values less than 100 Watts //Filter all values less than 1 Watts(why?)
if (magnitude === 0 && value < 100 && unit === 'W') { if (magnitude === 0 && value < 1 && unit === 'W') {
//console.log('DROP THIS VALUE ' + value); //console.log('DROP THIS VALUE ' + value);
return 0; return 0;
} }
@ -70,11 +70,15 @@ function TopologyBox(props: TopologyBoxProps) {
props.title === 'Battery' props.title === 'Battery'
? '165px' ? '165px'
: props.title === 'AC Loads' || : props.title === 'AC Loads' ||
props.title === 'DC Loads' || props.title === 'DC Loads' ||
props.title === 'Pv Inverter' || props.title === 'Pv Inverter' ||
props.title === 'Pv DC-DC' props.title === 'Pv DC-DC' ||
? '100px' props.title === 'PV' ||
: '150px', props.title === 'Loads'
? '100px'
: props.title === 'Inverter'
? '150px'
: '150px',
backgroundColor: !props.data backgroundColor: !props.data
? 'darkgrey' ? 'darkgrey'
: props.title === 'Grid Bus' || : props.title === 'Grid Bus' ||
@ -134,6 +138,17 @@ function TopologyBox(props: TopologyBoxProps) {
/> />
)} )}
{props.data && props.title === 'Inverter' && (
<img
src={inverterImage}
style={{
width: '40px',
height: '40px',
color: 'orange'
}}
/>
)}
{props.data && props.title === 'DC Link' && ( {props.data && props.title === 'DC Link' && (
<PowerInputIcon <PowerInputIcon
style={{ style={{
@ -183,7 +198,8 @@ function TopologyBox(props: TopologyBoxProps) {
marginTop: '4px' marginTop: '4px'
}} }}
> >
{(props.title === 'Pv Inverter' || {(props.title === 'PV' ||
props.title === 'Pv Inverter' ||
props.title === 'Pv DC-DC') && ( props.title === 'Pv DC-DC') && (
<SolarPowerIcon <SolarPowerIcon
style={{ style={{
@ -207,7 +223,7 @@ function TopologyBox(props: TopologyBoxProps) {
}} }}
></BatteryCharging60Icon> ></BatteryCharging60Icon>
)} )}
{(props.title === 'AC Loads' || props.title === 'DC Loads') && ( {(props.title === 'AC Loads' || props.title === 'DC Loads' ||props.title === 'Loads') && (
<OutletIcon <OutletIcon
style={{ style={{
fontSize: 30, fontSize: 30,