added Overview Page without last week button for SodistoreHome
This commit is contained in:
parent
2895b11efc
commit
1b6d5a5916
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>}
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue