Compare commits

..

No commits in common. "5de052488aa4d5f8f7fe70e247de1bfcbae50a7a" and "5666191a6b1e6f528b75587f3f2001c5ca3d7d0e" have entirely different histories.

8 changed files with 59 additions and 168 deletions

View File

@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import {
Container, Container,
Grid, Grid,
@ -14,16 +14,12 @@ import {
import { JSONRecordData } from '../Log/graph.util'; import { JSONRecordData } from '../Log/graph.util';
import { Link, Route, Routes, 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, useIntl } 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'; import MainStatsSodioHome from './MainStatsSodioHome';
import {
ActiveCluster,
getActiveClusters
} from '../Information/installationSetupUtils';
interface BatteryViewSodioHomeProps { interface BatteryViewSodioHomeProps {
values: JSONRecordData; values: JSONRecordData;
@ -40,78 +36,42 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
const currentLocation = useLocation(); const currentLocation = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const intl = useIntl();
const inverter = (props.values as any)?.InverterRecord; const inverter = (props.values as any)?.InverterRecord;
const batteryClusterNumber = props.installation.batteryClusterNumber; const batteryClusterNumber = props.installation.batteryClusterNumber;
const batterySerialNumbers = props.installation.batterySerialNumbers;
const hasDevices = !!inverter?.Devices; const hasDevices = !!inverter?.Devices;
const activeClusters: ActiveCluster[] = useMemo(() => {
const parsed = getActiveClusters(batterySerialNumbers || '');
if (parsed.length > 0) return parsed;
// Legacy/empty fallback: assume 2 clusters per inverter (all current Sinexcel
// presets), which matches the previous floor(i/2)+1 / (i%2)+1 mapping.
// For Growatt (batteryClusterNumber = 1) this collapses to a single row.
return Array.from({ length: batteryClusterNumber }, (_, i) => ({
invIdx: Math.floor(i / 2),
clIdx: i % 2,
flatIdx: i
}));
}, [batterySerialNumbers, batteryClusterNumber]);
const inverterCount = activeClusters.reduce(
(max, c) => Math.max(max, c.invIdx + 1),
0
);
const showInverterLabel = hasDevices && inverterCount > 1;
const sortedBatteryView = inverter const sortedBatteryView = inverter
? activeClusters.map(({ invIdx, clIdx, flatIdx }) => { ? Array.from({ length: batteryClusterNumber }, (_, i) => {
const label = showInverterLabel if (hasDevices) {
? intl.formatMessage( // Sinexcel: map across devices — 0→D1/B1, 1→D1/B2, 2→D2/B1, 3→D2/B2
{ const deviceId = String(Math.floor(i / 2) + 1);
id: 'batteryClusterInInverter', const batteryIndex = (i % 2) + 1;
defaultMessage: 'Battery Cluster {cl} in Inverter {inv}' const device = inverter.Devices[deviceId];
},
{ cl: clIdx + 1, inv: invIdx + 1 }
)
: intl.formatMessage(
{ id: 'batteryClusterN', defaultMessage: 'Battery Cluster {n}' },
{ n: clIdx + 1 }
);
if (hasDevices) {
// Sinexcel: Devices keyed by "1","2",... (1-based dict keys)
const device = inverter.Devices[String(invIdx + 1)];
const bi = clIdx + 1;
return {
BatteryId: String(flatIdx + 1),
label,
battery: {
Voltage: device?.[`Battery${bi}PackTotalVoltage`] ?? 0,
Current: device?.[`Battery${bi}PackTotalCurrent`] ?? 0,
Power: device?.[`Battery${bi}Power`] ?? 0,
Soc:
device?.[`Battery${bi}Soc`] ??
device?.[`Battery${bi}SocSecondvalue`] ??
0
}
};
}
// Growatt: flat Battery1, Battery2, ... on InverterRecord
const index = clIdx + 1;
return { return {
BatteryId: String(flatIdx + 1), BatteryId: String(i + 1),
label, battery: {
Voltage: device?.[`Battery${batteryIndex}PackTotalVoltage`] ?? 0,
Current: device?.[`Battery${batteryIndex}PackTotalCurrent`] ?? 0,
Power: device?.[`Battery${batteryIndex}Power`] ?? 0,
Soc: device?.[`Battery${batteryIndex}Soc`] ?? device?.[`Battery${batteryIndex}SocSecondvalue`] ?? 0,
}
};
} else {
// Growatt: flat Battery1, Battery2, ...
const index = i + 1;
return {
BatteryId: String(index),
battery: { battery: {
Voltage: inverter[`Battery${index}Voltage`] ?? 0, Voltage: inverter[`Battery${index}Voltage`] ?? 0,
Current: inverter[`Battery${index}Current`] ?? 0, Current: inverter[`Battery${index}Current`] ?? 0,
Power: inverter[`Battery${index}Power`] ?? 0, Power: inverter[`Battery${index}Power`] ?? 0,
Soc: inverter[`Battery${index}Soc`] ?? 0 Soc: inverter[`Battery${index}Soc`] ?? 0,
} }
}; };
}) }
})
: []; : [];
const [loading, setLoading] = useState(sortedBatteryView.length == 0); const [loading, setLoading] = useState(sortedBatteryView.length == 0);
@ -233,8 +193,6 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
s3Credentials={props.s3Credentials} s3Credentials={props.s3Credentials}
id={props.installationId} id={props.installationId}
batteryClusterNumber={props.installation.batteryClusterNumber} batteryClusterNumber={props.installation.batteryClusterNumber}
activeClusters={activeClusters}
showInverterLabel={showInverterLabel}
></MainStatsSodioHome> ></MainStatsSodioHome>
} }
/> />
@ -267,7 +225,7 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{sortedBatteryView.map(({ BatteryId, label, battery }) => ( {sortedBatteryView.map(({ BatteryId, battery }) => (
<TableRow <TableRow
key={BatteryId} key={BatteryId}
style={{ style={{
@ -285,7 +243,7 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
style={{ color: 'black' }} style={{ color: 'black' }}
to={routes.detailed_view + BatteryId} to={routes.detailed_view + BatteryId}
>*/} >*/}
{label} {'Battery Cluster ' + BatteryId}
{/*</Link>*/} {/*</Link>*/}
</TableCell> </TableCell>
<TableCell <TableCell

View File

@ -26,14 +26,11 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { ActiveCluster } from '../Information/installationSetupUtils';
interface MainStatsSodioHomeProps { interface MainStatsSodioHomeProps {
s3Credentials: I_S3Credentials; s3Credentials: I_S3Credentials;
id: number; id: number;
batteryClusterNumber: number; batteryClusterNumber: number;
activeClusters?: ActiveCluster[];
showInverterLabel?: boolean;
} }
function MainStatsSodioHome(props: MainStatsSodioHomeProps) { function MainStatsSodioHome(props: MainStatsSodioHomeProps) {
@ -118,8 +115,7 @@ function MainStatsSodioHome(props: MainStatsSodioHomeProps) {
TimeSpan.fromDays(1) TimeSpan.fromDays(1)
), ),
UnixTime.fromTicks(new Date().getTime() / 1000), UnixTime.fromTicks(new Date().getTime() / 1000),
props.batteryClusterNumber, props.batteryClusterNumber
props.activeClusters
); );
resultPromise resultPromise
@ -150,15 +146,9 @@ function MainStatsSodioHome(props: MainStatsSodioHomeProps) {
function generateSeries(chartData, category, color) { function generateSeries(chartData, category, color) {
const series = []; const series = [];
const pathsToSearch: string[] = []; const pathsToSearch = [];
if (props.activeClusters && props.activeClusters.length > 0) { for (let i = 0; i < props.batteryClusterNumber; i++) {
props.activeClusters.forEach((c) => { pathsToSearch.push('Node' + i);
pathsToSearch.push('Node' + c.flatIdx);
});
} else {
for (let i = 0; i < props.batteryClusterNumber; i++) {
pathsToSearch.push('Node' + i);
}
} }
const total = pathsToSearch.length; const total = pathsToSearch.length;
@ -217,8 +207,7 @@ function MainStatsSodioHome(props: MainStatsSodioHomeProps) {
2, 2,
UnixTime.fromTicks(startDate.unix()), UnixTime.fromTicks(startDate.unix()),
UnixTime.fromTicks(endDate.unix()), UnixTime.fromTicks(endDate.unix()),
props.batteryClusterNumber, props.batteryClusterNumber
props.activeClusters
); );
resultPromise resultPromise
@ -281,8 +270,7 @@ function MainStatsSodioHome(props: MainStatsSodioHomeProps) {
2, 2,
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)), UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2)), UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2)),
props.batteryClusterNumber, props.batteryClusterNumber
props.activeClusters
); );
resultPromise resultPromise

View File

@ -115,32 +115,6 @@ export const computeFlatValues = (
}; };
}; };
export interface ActiveCluster {
invIdx: number;
clIdx: number;
flatIdx: number;
}
export const getActiveClusters = (raw: string): ActiveCluster[] => {
if (!raw || raw.trim() === '') return [];
if (!raw.includes('/') && !raw.includes('|')) return [];
const result: ActiveCluster[] = [];
let flatIdx = 0;
raw.split('/').forEach((invStr, invIdx) => {
invStr.split('|').forEach((clStr, clIdx) => {
const hasSn = clStr
.split(',')
.some((s) => s.trim() !== '');
if (hasSn) {
result.push({ invIdx, clIdx, flatIdx });
}
flatIdx += 1;
});
});
return result;
};
export const wouldLoseData = ( export const wouldLoseData = (
oldTree: BatterySnTree, oldTree: BatterySnTree,
newPreset: PresetConfig newPreset: PresetConfig

View File

@ -81,13 +81,11 @@ export const transformInputToBatteryViewDataJson = async (
product: number, product: number,
start_time?: UnixTime, start_time?: UnixTime,
end_time?: UnixTime, end_time?: UnixTime,
batteryClusterNumber?: number, batteryClusterNumber?: number
activeClusters?: Array<{ invIdx: number; clIdx: number; flatIdx: number }>
): Promise<{ ): Promise<{
chartData: BatteryDataInterface; chartData: BatteryDataInterface;
chartOverview: BatteryOverviewInterface; chartOverview: BatteryOverviewInterface;
}> => { }> => {
const useActive = !!activeClusters && activeClusters.length > 0;
const prefixes = ['', 'k', 'M', 'G', 'T']; const prefixes = ['', 'k', 'M', 'G', 'T'];
const MAX_NUMBER = 9999999; const MAX_NUMBER = 9999999;
const isSodioHome = product === 2 || product === 5; const isSodioHome = product === 2 || product === 5;
@ -201,34 +199,17 @@ export const transformInputToBatteryViewDataJson = async (
const inv = (result as any)?.InverterRecord; const inv = (result as any)?.InverterRecord;
if (!inv) continue; if (!inv) continue;
// Iteration order: either active-cluster list (skips empty slots, const numBatteries = batteryClusterNumber || 1;
// preserves flat hardware indices) or a contiguous 0..N-1 fallback.
const iter = useActive
? activeClusters!.map((c) => ({
flatIdx: c.flatIdx,
invIdx: c.invIdx,
clIdx: c.clIdx
}))
: Array.from({ length: batteryClusterNumber || 1 }, (_, k) => ({
flatIdx: k,
invIdx: Math.floor(k / 2),
clIdx: k % 2
}));
const inverterCount = iter.reduce(
(max, c) => Math.max(max, c.invIdx + 1),
0
);
const showInverterLabel = !!inv?.Devices && inverterCount > 1;
let old_length = pathsToSave.length; let old_length = pathsToSave.length;
iter.forEach((c) => { if (numBatteries > old_length) {
const nodeName = 'Node' + c.flatIdx; for (let b = old_length; b < numBatteries; b++) {
if (!pathsToSave.includes(nodeName)) { const nodeName = 'Node' + b;
pathsToSave.push(nodeName); if (!pathsToSave.includes(nodeName)) {
pathsToSave.push(nodeName);
}
} }
}); }
if (initialiation) { if (initialiation) {
initialiation = false; initialiation = false;
@ -243,15 +224,12 @@ export const transformInputToBatteryViewDataJson = async (
}); });
} }
if (pathsToSave.length > old_length) { if (numBatteries > old_length) {
categories.forEach((category) => { categories.forEach((category) => {
iter.forEach((c) => { pathsToSave.forEach((path) => {
const path = 'Node' + c.flatIdx;
if (pathsToSave.indexOf(path) >= old_length) { if (pathsToSave.indexOf(path) >= old_length) {
const name = showInverterLabel const displayIndex = pathsToSave.indexOf(path);
? `Battery Cluster ${c.clIdx + 1} in Inverter ${c.invIdx + 1}` chartData[category].data[path] = { name: 'Battery Cluster ' + (displayIndex + 1), data: [] };
: `Battery Cluster ${c.clIdx + 1}`;
chartData[category].data[path] = { name, data: [] };
} }
}); });
}); });
@ -275,23 +253,24 @@ export const transformInputToBatteryViewDataJson = async (
Soh: 'Soh' Soh: 'Soh'
}; };
iter.forEach((c) => { for (let j = 0; j < pathsToSave.length; j++) {
const path = 'Node' + c.flatIdx;
categories.forEach((category) => { categories.forEach((category) => {
let value: number | undefined; let value: number | undefined;
if (hasDevices) { if (hasDevices) {
// Sinexcel: Devices keyed by "1","2",... (1-based dict keys) // Sinexcel: nested under Devices — 0→D1/B1, 1→D1/B2, 2→D2/B1, ...
const device = inv.Devices[String(c.invIdx + 1)]; const deviceId = String(Math.floor(j / 2) + 1);
const bi = c.clIdx + 1; const bi = (j % 2) + 1;
const device = inv.Devices[deviceId];
const fieldName = `Battery${bi}${categoryFieldMapSinexcel[category]}`; const fieldName = `Battery${bi}${categoryFieldMapSinexcel[category]}`;
value = device?.[fieldName]; value = device?.[fieldName];
// Fallback for Soc
if ((value === undefined || value === null) && category === 'Soc') { if ((value === undefined || value === null) && category === 'Soc') {
value = device?.[`Battery${bi}SocSecondvalue`]; value = device?.[`Battery${bi}SocSecondvalue`];
} }
} else { } else {
// Growatt: flat Battery1Soc, Battery2Voltage, ... on InverterRecord // Growatt: flat Battery1Soc, Battery2Voltage, ...
const batteryIndex = c.clIdx + 1; const batteryIndex = j + 1;
const fieldName = `Battery${batteryIndex}${categoryFieldMapGrowatt[category]}`; const fieldName = `Battery${batteryIndex}${categoryFieldMapGrowatt[category]}`;
value = inv[fieldName]; value = inv[fieldName];
} }
@ -303,13 +282,13 @@ export const transformInputToBatteryViewDataJson = async (
if (value > chartOverview[category].max) { if (value > chartOverview[category].max) {
chartOverview[category].max = value; chartOverview[category].max = value;
} }
chartData[category].data[path].data.push([ chartData[category].data[pathsToSave[j]].data.push([
adjustedTimestampArray[i], adjustedTimestampArray[i],
value value
]); ]);
} }
}); });
}); }
} else { } else {
// SaliMax, Salidomo, SodistoreMax: existing logic // SaliMax, Salidomo, SodistoreMax: existing logic
const battery_nodes = const battery_nodes =

View File

@ -13,7 +13,7 @@
"distributionPartner": "Vertriebspartner", "distributionPartner": "Vertriebspartner",
"inverterFirmwareVersion": "Wechselrichter-Firmware-Version", "inverterFirmwareVersion": "Wechselrichter-Firmware-Version",
"batteryFirmwareVersion": "Batterie-Firmware-Version", "batteryFirmwareVersion": "Batterie-Firmware-Version",
"networkProvider": "Verteilnetzbetreiber", "networkProvider": "Netzbetreiber",
"emailAddress": "E-Mail-Adresse", "emailAddress": "E-Mail-Adresse",
"createNewFolder": "Neuer Ordner", "createNewFolder": "Neuer Ordner",
"createNewUser": "Neuer Benutzer", "createNewUser": "Neuer Benutzer",
@ -97,8 +97,6 @@
"selectModel": "Modell auswählen...", "selectModel": "Modell auswählen...",
"inverterN": "Wechselrichter {n}", "inverterN": "Wechselrichter {n}",
"clusterN": "Cluster {n}", "clusterN": "Cluster {n}",
"batteryClusterN": "Batterie-Cluster {n}",
"batteryClusterInInverter": "Batterie-Cluster {cl} an Wechselrichter {inv}",
"clustersBatteriesSummary": "{filledClusters}/{totalClusters} Cluster, {filledBat}/{totalBat} Batterien", "clustersBatteriesSummary": "{filledClusters}/{totalClusters} Cluster, {filledBat}/{totalBat} Batterien",
"batteriesSummary": "{filled}/{total} Batterien", "batteriesSummary": "{filled}/{total} Batterien",
"inverterNSerialNumber": "Wechselrichter {n} Seriennummer", "inverterNSerialNumber": "Wechselrichter {n} Seriennummer",

View File

@ -9,7 +9,7 @@
"distributionPartner": "Distribution Partner", "distributionPartner": "Distribution Partner",
"inverterFirmwareVersion": "Inverter Firmware Version", "inverterFirmwareVersion": "Inverter Firmware Version",
"batteryFirmwareVersion": "Battery Firmware Version", "batteryFirmwareVersion": "Battery Firmware Version",
"networkProvider": "Grid Provider", "networkProvider": "Network Provider",
"emailAddress": "Email Address", "emailAddress": "Email Address",
"customerName": "Customer name", "customerName": "Customer name",
"english": "English", "english": "English",
@ -79,8 +79,6 @@
"selectModel": "Select model...", "selectModel": "Select model...",
"inverterN": "Inverter {n}", "inverterN": "Inverter {n}",
"clusterN": "Cluster {n}", "clusterN": "Cluster {n}",
"batteryClusterN": "Battery Cluster {n}",
"batteryClusterInInverter": "Battery Cluster {cl} in Inverter {inv}",
"clustersBatteriesSummary": "{filledClusters}/{totalClusters} clusters, {filledBat}/{totalBat} batteries", "clustersBatteriesSummary": "{filledClusters}/{totalClusters} clusters, {filledBat}/{totalBat} batteries",
"batteriesSummary": "{filled}/{total} batteries", "batteriesSummary": "{filled}/{total} batteries",
"inverterNSerialNumber": "Inverter {n} Serial Number", "inverterNSerialNumber": "Inverter {n} Serial Number",

View File

@ -11,7 +11,7 @@
"distributionPartner": "Partenaire de distribution", "distributionPartner": "Partenaire de distribution",
"inverterFirmwareVersion": "Version firmware onduleur", "inverterFirmwareVersion": "Version firmware onduleur",
"batteryFirmwareVersion": "Version firmware batterie", "batteryFirmwareVersion": "Version firmware batterie",
"networkProvider": "Gestionnaire de réseau de distribution", "networkProvider": "Gestionnaire de réseau",
"emailAddress": "Adresse e-mail", "emailAddress": "Adresse e-mail",
"createNewFolder": "Nouveau dossier", "createNewFolder": "Nouveau dossier",
"createNewUser": "Nouvel utilisateur", "createNewUser": "Nouvel utilisateur",
@ -91,8 +91,6 @@
"selectModel": "Sélectionner le modèle...", "selectModel": "Sélectionner le modèle...",
"inverterN": "Onduleur {n}", "inverterN": "Onduleur {n}",
"clusterN": "Cluster {n}", "clusterN": "Cluster {n}",
"batteryClusterN": "Cluster de batteries {n}",
"batteryClusterInInverter": "Cluster de batteries {cl} sur onduleur {inv}",
"clustersBatteriesSummary": "{filledClusters}/{totalClusters} clusters, {filledBat}/{totalBat} batteries", "clustersBatteriesSummary": "{filledClusters}/{totalClusters} clusters, {filledBat}/{totalBat} batteries",
"batteriesSummary": "{filled}/{total} batteries", "batteriesSummary": "{filled}/{total} batteries",
"inverterNSerialNumber": "Numéro de série onduleur {n}", "inverterNSerialNumber": "Numéro de série onduleur {n}",

View File

@ -9,7 +9,7 @@
"distributionPartner": "Partner di distribuzione", "distributionPartner": "Partner di distribuzione",
"inverterFirmwareVersion": "Versione firmware inverter", "inverterFirmwareVersion": "Versione firmware inverter",
"batteryFirmwareVersion": "Versione firmware batteria", "batteryFirmwareVersion": "Versione firmware batteria",
"networkProvider": "Gestore della rete di distribuzione", "networkProvider": "Gestore di rete",
"emailAddress": "Indirizzo e-mail", "emailAddress": "Indirizzo e-mail",
"customerName": "Nome cliente", "customerName": "Nome cliente",
"english": "Inglese", "english": "Inglese",
@ -79,8 +79,6 @@
"selectModel": "Seleziona modello...", "selectModel": "Seleziona modello...",
"inverterN": "Inverter {n}", "inverterN": "Inverter {n}",
"clusterN": "Cluster {n}", "clusterN": "Cluster {n}",
"batteryClusterN": "Cluster batteria {n}",
"batteryClusterInInverter": "Cluster batteria {cl} su inverter {inv}",
"clustersBatteriesSummary": "{filledClusters}/{totalClusters} cluster, {filledBat}/{totalBat} batterie", "clustersBatteriesSummary": "{filledClusters}/{totalClusters} cluster, {filledBat}/{totalBat} batterie",
"batteriesSummary": "{filled}/{total} batterie", "batteriesSummary": "{filled}/{total} batterie",
"inverterNSerialNumber": "Numero di serie inverter {n}", "inverterNSerialNumber": "Numero di serie inverter {n}",