added Overview Page without last week button for SodistoreHome

This commit is contained in:
Yinyin Liu 2026-02-11 13:30:34 +01:00
parent 2895b11efc
commit 1b6d5a5916
5 changed files with 156 additions and 73 deletions

View File

@ -194,9 +194,13 @@ public class Controller : ControllerBase
while (startTimestamp <= endTimestamp) while (startTimestamp <= endTimestamp)
{ {
string bucketPath = installation.Product==(int)ProductType.Salimax || installation.Product==(int)ProductType.SodiStoreMax? string bucketPath;
"s3://"+installation.S3BucketId + "-3e5b3069-214a-43ee-8d85-57d72000c19d/"+startTimestamp : if (installation.Product == (int)ProductType.Salimax || installation.Product == (int)ProductType.SodiStoreMax)
"s3://"+installation.S3BucketId + "-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e/"+startTimestamp; bucketPath = "s3://" + installation.S3BucketId + "-3e5b3069-214a-43ee-8d85-57d72000c19d/" + startTimestamp;
else if (installation.Product == (int)ProductType.SodioHome)
bucketPath = "s3://" + installation.S3BucketId + "-e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa/" + startTimestamp;
else
bucketPath = "s3://" + installation.S3BucketId + "-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e/" + startTimestamp;
Console.WriteLine("Fetching data for "+startTimestamp); Console.WriteLine("Fetching data for "+startTimestamp);
try try

View File

@ -118,6 +118,12 @@ function Overview(props: OverviewProps) {
resultPromise resultPromise
.then((result) => { .then((result) => {
if (result.chartData.soc.data.length === 0) {
setDateSelectionError('No data available for the selected date range. Please choose a more recent date.');
setErrorDateModalOpen(true);
setLoading(false);
return;
}
setDailyDataArray((prevData) => setDailyDataArray((prevData) =>
prevData.concat({ prevData.concat({
chartData: result.chartData, chartData: result.chartData,
@ -281,6 +287,12 @@ function Overview(props: OverviewProps) {
resultPromise resultPromise
.then((result) => { .then((result) => {
if (result.chartData.soc.data.length === 0) {
setDateSelectionError('No data available for the selected date range. Please choose a more recent date.');
setErrorDateModalOpen(true);
setLoading(false);
return;
}
setDailyDataArray((prevData) => setDailyDataArray((prevData) =>
prevData.concat({ prevData.concat({
chartData: result.chartData, chartData: result.chartData,
@ -511,20 +523,22 @@ function Overview(props: OverviewProps) {
> >
<FormattedMessage id="24_hours" defaultMessage="24-hours" /> <FormattedMessage id="24_hours" defaultMessage="24-hours" />
</Button> </Button>
<Button {product !== 2 && (
variant="contained" <Button
onClick={handleWeekData} variant="contained"
disabled={loading} onClick={handleWeekData}
sx={{ disabled={loading}
marginTop: '20px', sx={{
marginLeft: '10px', marginTop: '20px',
backgroundColor: aggregatedData ? '#808080' : '#ffc04d', marginLeft: '10px',
color: '#000000', backgroundColor: aggregatedData ? '#808080' : '#ffc04d',
'&:hover': { bgcolor: '#f7b34d' } color: '#000000',
}} '&:hover': { bgcolor: '#f7b34d' }
> }}
<FormattedMessage id="lastweek" defaultMessage="Last week" /> >
</Button> <FormattedMessage id="lastweek" defaultMessage="Last week" />
</Button>
)}
{/*{aggregatedData && (*/} {/*{aggregatedData && (*/}
<Button <Button
@ -1013,7 +1027,7 @@ function Overview(props: OverviewProps) {
alignItems="stretch" alignItems="stretch"
spacing={3} spacing={3}
> >
<Grid item md={6} xs={12}> <Grid item md={product === 2 ? 12 : 6} xs={12}>
<Card <Card
sx={{ sx={{
overflow: 'visible', overflow: 'visible',
@ -1074,6 +1088,7 @@ function Overview(props: OverviewProps) {
/> />
</Card> </Card>
</Grid> </Grid>
{product !== 2 && (
<Grid item md={6} xs={12}> <Grid item md={6} xs={12}>
<Card <Card
sx={{ sx={{
@ -1136,6 +1151,7 @@ function Overview(props: OverviewProps) {
/> />
</Card> </Card>
</Grid> </Grid>
)}
</Grid> </Grid>
)} )}
@ -1336,7 +1352,7 @@ function Overview(props: OverviewProps) {
alignItems="stretch" alignItems="stretch"
spacing={3} spacing={3}
> >
<Grid item md={6} xs={12}> <Grid item md={product === 2 ? 12 : 6} xs={12}>
<Card <Card
sx={{ sx={{
overflow: 'visible', overflow: 'visible',
@ -1397,6 +1413,7 @@ function Overview(props: OverviewProps) {
/> />
</Card> </Card>
</Grid> </Grid>
{product !== 2 && (
<Grid item md={6} xs={12}> <Grid item md={6} xs={12}>
<Card <Card
sx={{ sx={{
@ -1458,6 +1475,7 @@ function Overview(props: OverviewProps) {
/> />
</Card> </Card>
</Grid> </Grid>
)}
</Grid> </Grid>
)} )}
</Grid> </Grid>

View File

@ -26,6 +26,7 @@ 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'; import TopologySodistoreHome from '../Topology/TopologySodistoreHome';
import Overview from '../Overview/overview';
interface singleInstallationProps { interface singleInstallationProps {
current_installation?: I_Installation; current_installation?: I_Installation;
@ -541,6 +542,16 @@ function SodioHomeInstallation(props: singleInstallationProps) {
/> />
)} )}
<Route
path={routes.overview}
element={
<Overview
s3Credentials={s3Credentials}
id={props.current_installation.id}
/>
}
/>
<Route <Route
path={'*'} path={'*'}
element={<Navigate to={routes.live}></Navigate>} element={<Navigate to={routes.live}></Navigate>}

View File

@ -24,10 +24,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
const { currentUser } = context; const { currentUser } = context;
const tabList = [ const tabList = [
'live', 'live',
'overview',
'batteryview', 'batteryview',
'information', 'information',
'manage', 'manage',
'overview',
'log', 'log',
'history', 'history',
'configuration' 'configuration'
@ -100,6 +100,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
value: 'live', value: 'live',
label: <FormattedMessage id="live" defaultMessage="Live" /> label: <FormattedMessage id="live" defaultMessage="Live" />
}, },
{
value: 'overview',
label: <FormattedMessage id="overview" defaultMessage="Overview" />
},
{ {
value: 'batteryview', value: 'batteryview',
label: ( label: (
@ -109,10 +113,6 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
/> />
) )
}, },
// {
// value: 'overview',
// label: <FormattedMessage id="overview" defaultMessage="Overview" />
// },
{ {
value: 'log', value: 'log',
label: <FormattedMessage id="log" defaultMessage="Log" /> label: <FormattedMessage id="log" defaultMessage="Log" />
@ -159,11 +159,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" />
// }, },
{ {
value: 'information', value: 'information',
label: ( label: (
@ -190,6 +189,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
value: 'live', value: 'live',
label: <FormattedMessage id="live" defaultMessage="Live" /> label: <FormattedMessage id="live" defaultMessage="Live" />
}, },
{
value: 'overview',
label: <FormattedMessage id="overview" defaultMessage="Overview" />
},
{ {
value: 'batteryview', value: 'batteryview',
label: ( label: (
@ -199,10 +202,6 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
/> />
) )
}, },
// {
// value: 'overview',
// label: <FormattedMessage id="overview" defaultMessage="Overview" />
// },
{ {
value: 'log', value: 'log',
label: <FormattedMessage id="log" defaultMessage="Log" /> label: <FormattedMessage id="log" defaultMessage="Log" />

View File

@ -310,21 +310,31 @@ export const transformInputToDailyDataJson = async (
const prefixes = ['', 'k', 'M', 'G', 'T']; const prefixes = ['', 'k', 'M', 'G', 'T'];
const MAX_NUMBER = 9999999; const MAX_NUMBER = 9999999;
const pathsToSearch = [ // For SodioHome (product=2), paths are placeholders — actual extraction uses
'Battery.Soc', // custom fallback logic to handle differences between Growatt and Sinexcel.
product == 0 ? 'Battery.Temperature' : 'Battery.TemperatureCell1', // Growatt has: Battery1AmbientTemperature, GridPower, PvPower
// Sinexcel has: Battery1Temperature, TotalGridPower (meter may be offline), PvPower1-4
//'Battery.Temperature' for salimax, const pathsToSearch = product == 2
//'Battery.TemperatureCell1', ? [
product == 0 ? 'Battery.Dc.Power' : 'Battery.Power', 'SODIOHOME_SOC',
//'Battery.Dc.Power' for salimax, 'SODIOHOME_TEMPERATURE',
// 'Battery.Power', 'SODIOHOME_BATTERY_POWER',
'GridMeter.Ac.Power.Active', 'SODIOHOME_GRID_POWER',
'PvOnDc', 'SODIOHOME_PV_POWER',
'DcDc.Dc.Link.Voltage', null, // dcBusVoltage not available for SodioHome
'LoadOnAcGrid.Power.Active', 'SODIOHOME_CONSUMPTION',
'LoadOnDc.Power' null // DCLoad not available for SodioHome
]; ]
: [
'Battery.Soc',
product == 0 ? 'Battery.Temperature' : 'Battery.TemperatureCell1',
product == 0 ? 'Battery.Dc.Power' : 'Battery.Power',
'GridMeter.Ac.Power.Active',
'PvOnDc',
'DcDc.Dc.Link.Voltage',
'LoadOnAcGrid.Power.Active',
'LoadOnDc.Power'
];
const categories = [ const categories = [
'soc', 'soc',
'temperature', 'temperature',
@ -419,37 +429,78 @@ export const transformInputToDailyDataJson = async (
let category_index = 0; let category_index = 0;
// eslint-disable-next-line @typescript-eslint/no-loop-func // eslint-disable-next-line @typescript-eslint/no-loop-func
pathsToSearch.forEach((path) => { pathsToSearch.forEach((path) => {
if (get(result, path) !== undefined) { if (path === null) {
let value: number | undefined = undefined; // Skip unavailable fields (e.g. dcBusVoltage, DCLoad for SodioHome)
category_index++;
return;
}
if (category_index === 4) { let value: number | undefined = undefined;
// Custom logic for 'PvOnDc.Dc.Power'
if (product === 2) {
// SodioHome: custom extraction with fallbacks for Growatt/Sinexcel
const inv = result?.InverterRecord;
if (inv) {
switch (category_index) {
case 0: // soc
value = inv.Battery1Soc;
break;
case 1: // temperature
// Growatt: Battery1AmbientTemperature, Sinexcel: Battery1Temperature
value = inv.Battery1AmbientTemperature ?? inv.Battery1Temperature;
break;
case 2: // battery power
value = inv.Battery1Power;
break;
case 3: // grid power
// Growatt: GridPower (always valid), Sinexcel: GridPower may be 0 when
// electric meter is offline, TotalGridPower is the reliable fallback
value = inv.TotalGridPower ?? inv.GridPower;
break;
case 4: // pv production
// Growatt: PvPower (aggregated), Sinexcel: PvTotalPower or sum PvPower1-4
value =
inv.PvPower ??
inv.PvTotalPower ??
['PvPower1', 'PvPower2', 'PvPower3', 'PvPower4']
.map((key) => inv[key] ?? 0)
.reduce((sum, val) => sum + val, 0);
break;
case 6: // consumption
value = inv.ConsumptionPower;
break;
}
}
} else if (category_index === 4) {
// Salimax/Salidomo: Custom logic for 'PvOnDc.Dc.Power'
if (get(result, path) !== undefined) {
value = Object.values( value = Object.values(
result.PvOnDc as Record<string, { Dc?: { Power?: number } }> result.PvOnDc as Record<string, { Dc?: { Power?: number } }>
).reduce((sum, device) => sum + (device.Dc?.Power || 0), 0); ).reduce((sum, device) => sum + (device.Dc?.Power || 0), 0);
} else if (get(result, path) !== undefined) {
// Default path-based extraction
value = path
.split('.')
.reduce((o, key) => (o ? o[key] : undefined), result);
}
// Only push value if defined
if (value !== undefined) {
if (value < chartOverview[categories[category_index]].min) {
chartOverview[categories[category_index]].min = value;
}
if (value > chartOverview[categories[category_index]].max) {
chartOverview[categories[category_index]].max = value;
}
chartData[categories[category_index]].data.push([
adjustedTimestampArray[i],
value
]);
} }
} else if (get(result, path) !== undefined) {
// Default path-based extraction
value = path
.split('.')
.reduce((o, key) => (o ? o[key] : undefined), result);
} }
// Only push value if defined
if (value !== undefined) {
if (value < chartOverview[categories[category_index]].min) {
chartOverview[categories[category_index]].min = value;
}
if (value > chartOverview[categories[category_index]].max) {
chartOverview[categories[category_index]].max = value;
}
chartData[categories[category_index]].data.push([
adjustedTimestampArray[i],
value
]);
}
category_index++; category_index++;
}); });
} }