new updates again
This commit is contained in:
parent
2889d4c281
commit
be8c7d69b2
|
|
@ -11,6 +11,9 @@ public class Configuration
|
|||
|
||||
public double? MaximumDischargingCurrent { get; set; }
|
||||
public double? MaximumChargingCurrent { get; set; }
|
||||
// Nested per-inverter / per-cluster topology + limits (Sinexcel).
|
||||
// Keys: "Inverter1".."InverterN" → { Clusters: { "Cluster1".. }, PvCount }
|
||||
public Dictionary<string, InverterConfig>? Inverters { get; set; }
|
||||
public double? OperatingPriority { get; set; }
|
||||
public int? InverterNumber { get; set; }
|
||||
public double? BatteriesCount { get; set; }
|
||||
|
|
@ -78,3 +81,16 @@ public enum CalibrationChargeType
|
|||
AdditionallyOnce,
|
||||
ChargePermanently
|
||||
}
|
||||
|
||||
public class InverterConfig
|
||||
{
|
||||
public Dictionary<string, ClusterConfig> Clusters { get; set; } = new();
|
||||
public int PvCount { get; set; }
|
||||
}
|
||||
|
||||
public class ClusterConfig
|
||||
{
|
||||
public int BatteryCount { get; set; }
|
||||
public double MaxChargingCurrent { get; set; }
|
||||
public double MaxDischargingCurrent { get; set; }
|
||||
}
|
||||
|
|
@ -685,6 +685,17 @@ export interface I_BoxDataValue {
|
|||
value: string | number;
|
||||
}
|
||||
|
||||
export type ClusterConfig = {
|
||||
BatteryCount: number;
|
||||
MaxChargingCurrent: number;
|
||||
MaxDischargingCurrent: number;
|
||||
};
|
||||
|
||||
export type InverterConfig = {
|
||||
Clusters: { [clusterKey: string]: ClusterConfig };
|
||||
PvCount: number;
|
||||
};
|
||||
|
||||
export type ConfigurationValues = {
|
||||
minimumSoC: string | number;
|
||||
gridSetPoint: number;
|
||||
|
|
@ -696,6 +707,9 @@ export type ConfigurationValues = {
|
|||
//For sodistoreHome
|
||||
maximumDischargingCurrent: number;
|
||||
maximumChargingCurrent: number;
|
||||
// Nested per-inverter / per-cluster topology + limits (Sinexcel).
|
||||
// Keys: "Inverter1".."InverterN" → { Clusters: { "Cluster1".. }, PvCount }
|
||||
inverters?: { [inverterKey: string]: InverterConfig };
|
||||
operatingPriority: number;
|
||||
batteriesCount: number;
|
||||
inverterNumber: number;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { ConfigurationValues, JSONRecordData } from '../Log/graph.util';
|
||||
import { ConfigurationValues, InverterConfig, JSONRecordData } from '../Log/graph.util';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Alert,
|
||||
Box,
|
||||
CardContent,
|
||||
Chip,
|
||||
CircularProgress,
|
||||
Container,
|
||||
Divider,
|
||||
|
|
@ -17,6 +21,7 @@ import {
|
|||
Typography,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
|
||||
import React, { useContext, useState, useEffect } from 'react';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
|
|
@ -30,7 +35,9 @@ import { I_Installation } from 'src/interfaces/InstallationTypes';
|
|||
import {
|
||||
buildSodistoreProPreset,
|
||||
getPresetsForDevice,
|
||||
PresetConfig
|
||||
parseBatterySnTree,
|
||||
PresetConfig,
|
||||
BatterySnTree
|
||||
} from '../Information/installationSetupUtils';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers';
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
|
|
@ -104,15 +111,67 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
// Storage key for pending config (optimistic update)
|
||||
const pendingConfigKey = `pendingConfig_${props.id}`;
|
||||
|
||||
// Hardware topology — derived from Information tab (single source of truth).
|
||||
const isSodistorePro = product === 5;
|
||||
const installationModel = props.installation.installationModel;
|
||||
const presetConfig: PresetConfig | null = isSodistorePro
|
||||
? (installationModel && parseInt(installationModel, 10) > 0
|
||||
? buildSodistoreProPreset(parseInt(installationModel, 10))
|
||||
: null)
|
||||
: (getPresetsForDevice(device)[installationModel] || null);
|
||||
const inverterCount = presetConfig?.length ?? 1;
|
||||
|
||||
// Build the nested Inverters config from topology (presetConfig).
|
||||
// Used as fallback when the device hasn't yet written the structured Inverters object.
|
||||
const buildInvertersFromPreset = (
|
||||
chargeScalar: number | undefined,
|
||||
dischargeScalar: number | undefined,
|
||||
): { [k: string]: InverterConfig } => {
|
||||
if (!presetConfig) return {};
|
||||
const out: { [k: string]: InverterConfig } = {};
|
||||
presetConfig.forEach((clusters, invIdx) => {
|
||||
const clObj: { [k: string]: any } = {};
|
||||
clusters.forEach((batteryCount, clIdx) => {
|
||||
clObj[`Cluster${clIdx + 1}`] = {
|
||||
BatteryCount: batteryCount,
|
||||
MaxChargingCurrent: chargeScalar ?? 0,
|
||||
MaxDischargingCurrent: dischargeScalar ?? 0,
|
||||
};
|
||||
});
|
||||
out[`Inverter${invIdx + 1}`] = { Clusters: clObj, PvCount: 0 };
|
||||
});
|
||||
return out;
|
||||
};
|
||||
|
||||
// Helper to build form values from S3 data
|
||||
const getS3Values = (): Partial<ConfigurationValues> => {
|
||||
const inverterNum = props.values.Config.InverterNumber ?? 1;
|
||||
const batteriesPerInverter: number[] = props.values.Config.BatteriesCountPerInverter
|
||||
?? Array(inverterNum).fill(props.values.Config.BatteriesCount || 1);
|
||||
// Read per-inverter Clusters/PvCount from each Devices.InverterN entry on disk.
|
||||
const cfgDevices = (props.values.Config as any).Devices as { [k: string]: any } | undefined;
|
||||
const cfgInverters: { [k: string]: InverterConfig } | undefined = cfgDevices
|
||||
? Object.fromEntries(
|
||||
Object.entries(cfgDevices)
|
||||
.filter(([k, v]: [string, any]) => k.startsWith('Inverter') && (v?.Clusters || v?.PvCount != null))
|
||||
.map(([k, v]: [string, any]) => [k, { Clusters: v.Clusters ?? {}, PvCount: v.PvCount ?? 0 }])
|
||||
)
|
||||
: undefined;
|
||||
const hasInverterData = cfgInverters && Object.keys(cfgInverters).length > 0;
|
||||
return {
|
||||
minimumSoC: props.values.Config.MinSoc,
|
||||
maximumDischargingCurrent: props.values.Config.MaximumDischargingCurrent,
|
||||
maximumChargingCurrent: props.values.Config.MaximumChargingCurrent,
|
||||
// Always overlay Information-tab topology so battery counts and PV count
|
||||
// reflect what's actually installed (Information tab is source of truth).
|
||||
inverters: overlayTopology(
|
||||
hasInverterData
|
||||
? cfgInverters
|
||||
: buildInvertersFromPreset(
|
||||
props.values.Config.MaximumChargingCurrent,
|
||||
props.values.Config.MaximumDischargingCurrent,
|
||||
)
|
||||
),
|
||||
operatingPriority: resolveOperatingPriorityIndex(
|
||||
props.values.Config.OperatingPriority
|
||||
),
|
||||
|
|
@ -179,6 +238,40 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
// Fingerprint S3 Config for change detection (not value comparison)
|
||||
const getS3ConfigFingerprint = () => JSON.stringify(props.values.Config);
|
||||
|
||||
// Overlay Information-tab-derived topology onto a `inverters` config object.
|
||||
// Battery counts come from the SN tree (filled SNs); PvCount comes from pvStringsPerInverter.
|
||||
// Existing per-cluster current limits are preserved.
|
||||
const overlayTopology = (
|
||||
inverters: { [k: string]: InverterConfig } | undefined
|
||||
): { [k: string]: InverterConfig } | undefined => {
|
||||
if (!presetConfig) return inverters;
|
||||
const tree = parseBatterySnTree(props.installation.batterySerialNumbers || '', presetConfig);
|
||||
const pvStrings = (props.installation.pvStringsPerInverter || '')
|
||||
.split(',')
|
||||
.map((s) => s.trim());
|
||||
const out: { [k: string]: InverterConfig } = {};
|
||||
presetConfig.forEach((clusters, invIdx) => {
|
||||
const invKey = `Inverter${invIdx + 1}`;
|
||||
const existingInv = inverters?.[invKey];
|
||||
const cls: { [k: string]: any } = {};
|
||||
clusters.forEach((_slotCount, clIdx) => {
|
||||
const clKey = `Cluster${clIdx + 1}`;
|
||||
const filled = (tree[invIdx]?.[clIdx] ?? []).filter((s) => s !== '').length;
|
||||
const existingCl = existingInv?.Clusters?.[clKey];
|
||||
cls[clKey] = {
|
||||
BatteryCount: filled,
|
||||
MaxChargingCurrent: existingCl?.MaxChargingCurrent ?? 0,
|
||||
MaxDischargingCurrent: existingCl?.MaxDischargingCurrent ?? 0,
|
||||
};
|
||||
});
|
||||
out[invKey] = {
|
||||
Clusters: cls,
|
||||
PvCount: parseInt(pvStrings[invIdx] || '0', 10) || 0,
|
||||
};
|
||||
});
|
||||
return out;
|
||||
};
|
||||
|
||||
// Initialize form from localStorage (if pending submit exists) or from S3
|
||||
// This runs in the useState initializer so the component never renders stale values
|
||||
const [formValues, setFormValues] = useState<Partial<ConfigurationValues>>(() => {
|
||||
|
|
@ -266,17 +359,23 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
if (!validateTimeOnly()) {
|
||||
return;
|
||||
}
|
||||
// Re-overlay Information-tab topology at submit time, so battery counts and PV count
|
||||
// are always the latest from the Information tab (it's the source of truth).
|
||||
const inverters = overlayTopology(formValues.inverters);
|
||||
// Pull the first cluster's value as a legacy single-scalar fallback for older firmware.
|
||||
const firstInvKey = inverters ? Object.keys(inverters)[0] : undefined;
|
||||
const firstClusterKey = firstInvKey && inverters
|
||||
? Object.keys(inverters[firstInvKey].Clusters)[0]
|
||||
: undefined;
|
||||
const firstCluster = firstInvKey && firstClusterKey && inverters
|
||||
? inverters[firstInvKey].Clusters[firstClusterKey]
|
||||
: undefined;
|
||||
const configurationToSend: Partial<ConfigurationValues> = {
|
||||
minimumSoC: formValues.minimumSoC,
|
||||
maximumDischargingCurrent: formValues.maximumDischargingCurrent,
|
||||
maximumChargingCurrent: formValues.maximumChargingCurrent,
|
||||
maximumDischargingCurrent: firstCluster?.MaxDischargingCurrent ?? formValues.maximumDischargingCurrent,
|
||||
maximumChargingCurrent: firstCluster?.MaxChargingCurrent ?? formValues.maximumChargingCurrent,
|
||||
inverters,
|
||||
operatingPriority: formValues.operatingPriority,
|
||||
inverterNumber: formValues.inverterNumber,
|
||||
batteriesCountPerInverter: formValues.batteriesCountPerInverter,
|
||||
batteriesCount: formValues.batteriesCountPerInverter?.[0] ?? formValues.batteriesCount,
|
||||
clusterNumber:formValues.clusterNumber,
|
||||
PvNumber:formValues.PvNumber,
|
||||
pvCountPerInverter: formValues.pvCountPerInverter,
|
||||
timeChargeandDischargePower: formValues.timeChargeandDischargePower,
|
||||
startTimeChargeandDischargeDayandTime: formValues.startTimeChargeandDischargeDayandTime
|
||||
? new Date(formValues.startTimeChargeandDischargeDayandTime.getTime() - formValues.startTimeChargeandDischargeDayandTime.getTimezoneOffset() * 60000)
|
||||
|
|
@ -470,15 +569,15 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
)}
|
||||
|
||||
{device === 4 && (() => {
|
||||
// Mirror Information tab's preset derivation so both views always agree.
|
||||
const isSodistorePro = product === 5;
|
||||
const model = props.installation.installationModel;
|
||||
const presetConfig: PresetConfig | null = isSodistorePro
|
||||
? (model && parseInt(model, 10) > 0
|
||||
? buildSodistoreProPreset(parseInt(model, 10))
|
||||
: null)
|
||||
: (getPresetsForDevice(device)[model] || null);
|
||||
const inverterCount = presetConfig?.length ?? 1;
|
||||
// Read the SN tree from the Information tab data (single source of truth).
|
||||
// Filled batteries per cluster = entries with a non-empty serial number.
|
||||
const tree: BatterySnTree | null = presetConfig
|
||||
? parseBatterySnTree(props.installation.batterySerialNumbers || '', presetConfig)
|
||||
: null;
|
||||
// PV strings per inverter — comma-separated string from Information tab.
|
||||
const pvStrings = (props.installation.pvStringsPerInverter || '')
|
||||
.split(',')
|
||||
.map((s) => s.trim());
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -492,21 +591,70 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
</div>
|
||||
|
||||
{Array.from({ length: inverterCount }, (_, i) => {
|
||||
const batteriesInInverter = presetConfig
|
||||
? (presetConfig[i] ?? []).reduce((a, b) => a + b, 0)
|
||||
: 0;
|
||||
const clusters = presetConfig?.[i] ?? [];
|
||||
const treeForInverter = tree?.[i] ?? [];
|
||||
const filledBat = treeForInverter.flat().filter((s) => s !== '').length;
|
||||
const totalBat = clusters.reduce((a, b) => a + b, 0);
|
||||
return (
|
||||
<div key={`battCount_${i}`} style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage(
|
||||
{ id: 'batteriesCountInInverter' },
|
||||
{ number: i + 1 }
|
||||
)}
|
||||
value={batteriesInInverter}
|
||||
InputProps={{ readOnly: true }}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<Accordion
|
||||
key={`inv-${i}`}
|
||||
defaultExpanded={false}
|
||||
sx={{ ml: 1, mr: 1, mt: 1 }}
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
sx={{
|
||||
'& .MuiAccordionSummary-content': { flexGrow: 0, justifyContent: 'flex-start' },
|
||||
justifyContent: 'flex-start',
|
||||
'& .MuiAccordionSummary-expandIconWrapper': { ml: 1 }
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontWeight: 'bold' }}>
|
||||
<FormattedMessage id="inverterN" defaultMessage="Inverter {n}" values={{ n: i + 1 }} />
|
||||
</Typography>
|
||||
<Chip
|
||||
label={`${filledBat} ${intl.formatMessage({ id: 'batteries', defaultMessage: 'batteries' })}`}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ ml: 1 }}
|
||||
/>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{clusters.map((_slotCount, clIdx) => {
|
||||
const filledInCluster = (treeForInverter[clIdx] ?? [])
|
||||
.filter((s) => s !== '').length;
|
||||
return (
|
||||
<div key={`cl-${i}-${clIdx}`} style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage(
|
||||
{ id: 'batteryNumberInClusterN', defaultMessage: 'Battery Number in Cluster {n}' },
|
||||
{ n: clIdx + 1 }
|
||||
)}
|
||||
value={filledInCluster}
|
||||
InputProps={{ readOnly: true }}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{(() => {
|
||||
const pvCount = parseInt(pvStrings[i] || '0', 10) || 0;
|
||||
return (
|
||||
<div key={`pv-${i}`} style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage(
|
||||
{ id: 'pvStringsNumberInInverterN', defaultMessage: 'PV Strings Number in Inverter {n}' },
|
||||
{ n: i + 1 }
|
||||
)}
|
||||
value={pvCount}
|
||||
InputProps={{ readOnly: true }}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
|
|
@ -556,25 +704,106 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage({ id: 'maximumChargingCurrentPerBattery' })}
|
||||
name="maximumChargingCurrent"
|
||||
value={formValues.maximumChargingCurrent}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
{device === 4 ? (
|
||||
// Per-cluster, per-inverter charging/discharging current limits — nested config.
|
||||
Array.from({ length: inverterCount }, (_, invIdx) => {
|
||||
const invKey = `Inverter${invIdx + 1}`;
|
||||
const clusters = presetConfig?.[invIdx] ?? [0];
|
||||
return (
|
||||
<Accordion
|
||||
key={`limits-${invKey}`}
|
||||
defaultExpanded={false}
|
||||
sx={{ ml: 1, mr: 1, mt: 1 }}
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
sx={{
|
||||
'& .MuiAccordionSummary-content': { flexGrow: 0, justifyContent: 'flex-start' },
|
||||
justifyContent: 'flex-start',
|
||||
'& .MuiAccordionSummary-expandIconWrapper': { ml: 1 }
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontWeight: 'bold' }}>
|
||||
<FormattedMessage id="inverterN" defaultMessage="Inverter {n}" values={{ n: invIdx + 1 }} />
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{clusters.map((_slotCount, clIdx) => {
|
||||
const clKey = `Cluster${clIdx + 1}`;
|
||||
const cluster = formValues.inverters?.[invKey]?.Clusters?.[clKey];
|
||||
const charge = cluster?.MaxChargingCurrent ?? '';
|
||||
const discharge = cluster?.MaxDischargingCurrent ?? '';
|
||||
const setClusterField = (
|
||||
field: 'MaxChargingCurrent' | 'MaxDischargingCurrent',
|
||||
v: string
|
||||
) => {
|
||||
if (v !== '' && !/^\d*\.?\d*$/.test(v)) return;
|
||||
setFormDirty(true);
|
||||
setFormValues((prev) => {
|
||||
const inverters = { ...(prev.inverters ?? {}) };
|
||||
const inv = inverters[invKey]
|
||||
?? { Clusters: {}, PvCount: 0 };
|
||||
const cls = { ...(inv.Clusters ?? {}) };
|
||||
const existing = cls[clKey] ?? {
|
||||
BatteryCount: presetConfig?.[invIdx]?.[clIdx] ?? 0,
|
||||
MaxChargingCurrent: 0,
|
||||
MaxDischargingCurrent: 0,
|
||||
};
|
||||
cls[clKey] = {
|
||||
...existing,
|
||||
[field]: v === '' ? 0 : parseFloat(v),
|
||||
};
|
||||
inverters[invKey] = { ...inv, Clusters: cls };
|
||||
return { ...prev, inverters };
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div key={`cl-limits-${invKey}-${clKey}`} style={{ marginBottom: '5px' }}>
|
||||
<Typography variant="subtitle2" sx={{ mt: 1, ml: 1 }}>
|
||||
<FormattedMessage id="clusterN" defaultMessage="Cluster {n}" values={{ n: clIdx + 1 }} />
|
||||
</Typography>
|
||||
<TextField
|
||||
label={intl.formatMessage({ id: 'maximumChargingCurrentPerClusterLabel' })}
|
||||
value={charge}
|
||||
onChange={(e) => setClusterField('MaxChargingCurrent', e.target.value)}
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
label={intl.formatMessage({ id: 'maximumDischargingCurrentPerClusterLabel' })}
|
||||
value={discharge}
|
||||
onChange={(e) => setClusterField('MaxDischargingCurrent', e.target.value)}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<>
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage({ id: 'maximumChargingCurrentPerBattery' })}
|
||||
name="maximumChargingCurrent"
|
||||
value={formValues.maximumChargingCurrent}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage({ id: 'maximumDischargingCurrentPerBattery' })}
|
||||
name="maximumDischargingCurrent"
|
||||
value={formValues.maximumDischargingCurrent}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage({ id: 'maximumDischargingCurrentPerBattery' })}
|
||||
name="maximumDischargingCurrent"
|
||||
value={formValues.maximumDischargingCurrent}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{device === 4 && (
|
||||
<>
|
||||
|
|
@ -802,16 +1031,9 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage({ id: 'currentPrice' })}
|
||||
name="currentPrice"
|
||||
value={formValues.currentPrice ?? ''}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
if (v === '' || /^\d*\.?\d*$/.test(v)) {
|
||||
setFormDirty(true);
|
||||
setFormValues((prev) => ({ ...prev, currentPrice: v }));
|
||||
}
|
||||
}}
|
||||
value={(props.values.Config as any).CurrentPrice?.toString() ?? ''}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: <InputAdornment position="end">CHF/kWh</InputAdornment>,
|
||||
}}
|
||||
fullWidth
|
||||
|
|
|
|||
|
|
@ -521,7 +521,12 @@
|
|||
"enterPowerValue": "Positiven oder negativen Leistungswert eingeben",
|
||||
"inverterNumber": "Anzahl Wechselrichter",
|
||||
"batteriesCountInInverter": "Batterieanzahl in Wechselrichter {number}",
|
||||
"batteryNumberInClusterN": "Batterieanzahl in Cluster {n}",
|
||||
"pvStringsNumberInInverterN": "Anzahl PV-Strings in Wechselrichter {n}",
|
||||
"batteries": "Batterien",
|
||||
"maximumChargingCurrentPerBattery": "Maximaler Ladestrom pro Batterie (A)",
|
||||
"maximumChargingCurrentPerClusterLabel": "Maximaler Ladestrom pro Cluster (A)",
|
||||
"maximumDischargingCurrentPerClusterLabel": "Maximaler Entladestrom pro Cluster (A)",
|
||||
"maximumDischargingCurrentPerBattery": "Maximaler Entladestrom pro Batterie (A)",
|
||||
"powerPerInverterKW": "Leistung pro Wechselrichter (kW)",
|
||||
"startDateTime": "Startdatum und -zeit (Startzeit < Stoppzeit)",
|
||||
|
|
|
|||
|
|
@ -269,8 +269,13 @@
|
|||
"enterPowerValue": "Enter a positive or negative power value",
|
||||
"inverterNumber": "Inverter Number",
|
||||
"batteriesCountInInverter": "Batteries Count in Inverter {number}",
|
||||
"batteryNumberInClusterN": "Battery Number in Cluster {n}",
|
||||
"pvStringsNumberInInverterN": "PV Strings Number in Inverter {n}",
|
||||
"batteries": "batteries",
|
||||
"maximumChargingCurrentPerBattery": "Maximum Charging Current per Battery (A)",
|
||||
"maximumDischargingCurrentPerBattery": "Maximum Discharging Current per Battery (A)",
|
||||
"maximumChargingCurrentPerClusterLabel": "Maximum Charging Current per Cluster (A)",
|
||||
"maximumDischargingCurrentPerClusterLabel": "Maximum Discharging Current per Cluster (A)",
|
||||
"powerPerInverterKW": "Power per Inverter (kW)",
|
||||
"startDateTime": "Start Date and Time (Start Time < Stop Time)",
|
||||
"stopDateTime": "Stop Date and Time (Start Time < Stop Time)",
|
||||
|
|
|
|||
|
|
@ -521,7 +521,12 @@
|
|||
"enterPowerValue": "Entrez une valeur de puissance positive ou négative",
|
||||
"inverterNumber": "Nombre d'onduleurs",
|
||||
"batteriesCountInInverter": "Nombre de batteries dans l'onduleur {number}",
|
||||
"batteryNumberInClusterN": "Nombre de batteries dans le cluster {n}",
|
||||
"pvStringsNumberInInverterN": "Nombre de chaînes PV dans l'onduleur {n}",
|
||||
"batteries": "batteries",
|
||||
"maximumChargingCurrentPerBattery": "Courant de charge maximum par batterie (A)",
|
||||
"maximumChargingCurrentPerClusterLabel": "Courant de charge maximum par cluster (A)",
|
||||
"maximumDischargingCurrentPerClusterLabel": "Courant de décharge maximum par cluster (A)",
|
||||
"maximumDischargingCurrentPerBattery": "Courant de décharge maximum par batterie (A)",
|
||||
"powerPerInverterKW": "Puissance par onduleur (kW)",
|
||||
"startDateTime": "Date et heure de début (Début < Fin)",
|
||||
|
|
|
|||
|
|
@ -521,7 +521,12 @@
|
|||
"enterPowerValue": "Inserire un valore di potenza positivo o negativo",
|
||||
"inverterNumber": "Numero di inverter",
|
||||
"batteriesCountInInverter": "Numero di batterie nell'inverter {number}",
|
||||
"batteryNumberInClusterN": "Numero di batterie nel cluster {n}",
|
||||
"pvStringsNumberInInverterN": "Numero di stringhe PV nell'inverter {n}",
|
||||
"batteries": "batterie",
|
||||
"maximumChargingCurrentPerBattery": "Corrente massima di carica per batteria (A)",
|
||||
"maximumChargingCurrentPerClusterLabel": "Corrente massima di carica per cluster (A)",
|
||||
"maximumDischargingCurrentPerClusterLabel": "Corrente massima di scarica per cluster (A)",
|
||||
"maximumDischargingCurrentPerBattery": "Corrente massima di scarica per batteria (A)",
|
||||
"powerPerInverterKW": "Potenza per inverter (kW)",
|
||||
"startDateTime": "Data e ora di inizio (Inizio < Fine)",
|
||||
|
|
|
|||
Loading…
Reference in New Issue