Merge remote-tracking branch 'origin/main' into ticket-comment
This commit is contained in:
commit
ced1de83e1
|
|
@ -1,5 +1,6 @@
|
||||||
using InnovEnergy.App.Backend.Database;
|
using InnovEnergy.App.Backend.Database;
|
||||||
using InnovEnergy.App.Backend.Relations;
|
using InnovEnergy.App.Backend.Relations;
|
||||||
|
using InnovEnergy.Lib.Mailer;
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
namespace InnovEnergy.App.Backend.DataTypes.Methods;
|
namespace InnovEnergy.App.Backend.DataTypes.Methods;
|
||||||
|
|
@ -172,4 +173,38 @@ public static class InstallationMethods
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const String SupportEmail = "support@inesco.energy";
|
||||||
|
private const String SupportName = "inesco energy Support Team";
|
||||||
|
|
||||||
|
public static Task SendAlarmNotificationToSupport(this Installation installation, Int32 prevStatus)
|
||||||
|
{
|
||||||
|
var productName = ProductName(installation.Product);
|
||||||
|
var fromStatus = StatusName(prevStatus);
|
||||||
|
|
||||||
|
var subject = $"[inesco energy] Alarm: {installation.Name}";
|
||||||
|
var body =
|
||||||
|
$"Installation \"{installation.Name}\" (ID {installation.Id}, {productName})\n" +
|
||||||
|
$"status changed from {fromStatus} to Alarm.\n\n" +
|
||||||
|
"Please check the Log tab on the Monitor to see detailed errors and warnings.\n";
|
||||||
|
|
||||||
|
return Mailer.Send(SupportName, SupportEmail, subject, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String StatusName(Int32 status) => status switch
|
||||||
|
{
|
||||||
|
-1 => "Offline",
|
||||||
|
0 => "Green",
|
||||||
|
1 => "Warning",
|
||||||
|
2 => "Alarm",
|
||||||
|
_ => "Unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
private static String ProductName(Int32 product) => product switch
|
||||||
|
{
|
||||||
|
2 => "Sodistore Home",
|
||||||
|
3 => "Sodistore Max",
|
||||||
|
4 => "Sodistore Grid",
|
||||||
|
5 => "Sodistore Pro",
|
||||||
|
_ => $"Product {product}"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using InnovEnergy.App.Backend.Database;
|
using InnovEnergy.App.Backend.Database;
|
||||||
using InnovEnergy.App.Backend.DataTypes;
|
using InnovEnergy.App.Backend.DataTypes;
|
||||||
|
using InnovEnergy.App.Backend.DataTypes.Methods;
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
using RabbitMQ.Client;
|
using RabbitMQ.Client;
|
||||||
using RabbitMQ.Client.Events;
|
using RabbitMQ.Client.Events;
|
||||||
|
|
@ -186,6 +187,20 @@ public static class RabbitMqManager
|
||||||
|
|
||||||
Db.UpdateInstallationStatus(installationId, receivedStatusMessage.Status);
|
Db.UpdateInstallationStatus(installationId, receivedStatusMessage.Status);
|
||||||
|
|
||||||
|
const int AlarmStatus = 2;
|
||||||
|
var isSodistore = installation.Product is 2 or 3 or 4 or 5;
|
||||||
|
if (isSodistore
|
||||||
|
&& prevStatus != AlarmStatus
|
||||||
|
&& receivedStatusMessage.Status == AlarmStatus)
|
||||||
|
{
|
||||||
|
var prev = prevStatus;
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try { await installation.SendAlarmNotificationToSupport(prev); }
|
||||||
|
catch (Exception ex) { Console.WriteLine($"[AlarmNotify] failed for {installationId}: {ex.Message}"); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//Console.WriteLine("----------------------------------------------");
|
//Console.WriteLine("----------------------------------------------");
|
||||||
//If the status has changed, update all the connected front-ends regarding this installation
|
//If the status has changed, update all the connected front-ends regarding this installation
|
||||||
if(prevStatus != receivedStatusMessage.Status && WebsocketManager.InstallationConnections[installationId].Connections.Count > 0)
|
if(prevStatus != receivedStatusMessage.Status && WebsocketManager.InstallationConnections[installationId].Connections.Count > 0)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
Grid,
|
Grid,
|
||||||
|
|
@ -14,12 +14,16 @@ 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 } from 'react-intl';
|
import { FormattedMessage, useIntl } 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;
|
||||||
|
|
@ -36,42 +40,78 @@ 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 sortedBatteryView = inverter
|
const activeClusters: ActiveCluster[] = useMemo(() => {
|
||||||
? Array.from({ length: batteryClusterNumber }, (_, i) => {
|
const parsed = getActiveClusters(batterySerialNumbers || '');
|
||||||
if (hasDevices) {
|
if (parsed.length > 0) return parsed;
|
||||||
// Sinexcel: map across devices — 0→D1/B1, 1→D1/B2, 2→D2/B1, 3→D2/B2
|
// Legacy/empty fallback: assume 2 clusters per inverter (all current Sinexcel
|
||||||
const deviceId = String(Math.floor(i / 2) + 1);
|
// presets), which matches the previous floor(i/2)+1 / (i%2)+1 mapping.
|
||||||
const batteryIndex = (i % 2) + 1;
|
// For Growatt (batteryClusterNumber = 1) this collapses to a single row.
|
||||||
const device = inverter.Devices[deviceId];
|
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
|
||||||
|
? activeClusters.map(({ invIdx, clIdx, flatIdx }) => {
|
||||||
|
const label = showInverterLabel
|
||||||
|
? intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: 'batteryClusterInInverter',
|
||||||
|
defaultMessage: 'Battery Cluster {cl} in Inverter {inv}'
|
||||||
|
},
|
||||||
|
{ 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(i + 1),
|
BatteryId: String(flatIdx + 1),
|
||||||
battery: {
|
label,
|
||||||
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);
|
||||||
|
|
@ -193,6 +233,8 @@ 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>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -225,7 +267,7 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{sortedBatteryView.map(({ BatteryId, battery }) => (
|
{sortedBatteryView.map(({ BatteryId, label, battery }) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={BatteryId}
|
key={BatteryId}
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -243,7 +285,7 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
||||||
style={{ color: 'black' }}
|
style={{ color: 'black' }}
|
||||||
to={routes.detailed_view + BatteryId}
|
to={routes.detailed_view + BatteryId}
|
||||||
>*/}
|
>*/}
|
||||||
{'Battery Cluster ' + BatteryId}
|
{label}
|
||||||
{/*</Link>*/}
|
{/*</Link>*/}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,14 @@ 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) {
|
||||||
|
|
@ -115,7 +118,8 @@ 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
|
||||||
|
|
@ -146,9 +150,15 @@ function MainStatsSodioHome(props: MainStatsSodioHomeProps) {
|
||||||
|
|
||||||
function generateSeries(chartData, category, color) {
|
function generateSeries(chartData, category, color) {
|
||||||
const series = [];
|
const series = [];
|
||||||
const pathsToSearch = [];
|
const pathsToSearch: string[] = [];
|
||||||
for (let i = 0; i < props.batteryClusterNumber; i++) {
|
if (props.activeClusters && props.activeClusters.length > 0) {
|
||||||
pathsToSearch.push('Node' + i);
|
props.activeClusters.forEach((c) => {
|
||||||
|
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;
|
||||||
|
|
@ -207,7 +217,8 @@ 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
|
||||||
|
|
@ -270,7 +281,8 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,32 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -81,11 +81,13 @@ 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;
|
||||||
|
|
@ -199,17 +201,34 @@ export const transformInputToBatteryViewDataJson = async (
|
||||||
const inv = (result as any)?.InverterRecord;
|
const inv = (result as any)?.InverterRecord;
|
||||||
if (!inv) continue;
|
if (!inv) continue;
|
||||||
|
|
||||||
const numBatteries = batteryClusterNumber || 1;
|
// Iteration order: either active-cluster list (skips empty slots,
|
||||||
|
// 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;
|
||||||
|
|
||||||
if (numBatteries > old_length) {
|
iter.forEach((c) => {
|
||||||
for (let b = old_length; b < numBatteries; b++) {
|
const nodeName = 'Node' + c.flatIdx;
|
||||||
const nodeName = 'Node' + b;
|
if (!pathsToSave.includes(nodeName)) {
|
||||||
if (!pathsToSave.includes(nodeName)) {
|
pathsToSave.push(nodeName);
|
||||||
pathsToSave.push(nodeName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if (initialiation) {
|
if (initialiation) {
|
||||||
initialiation = false;
|
initialiation = false;
|
||||||
|
|
@ -224,12 +243,15 @@ export const transformInputToBatteryViewDataJson = async (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numBatteries > old_length) {
|
if (pathsToSave.length > old_length) {
|
||||||
categories.forEach((category) => {
|
categories.forEach((category) => {
|
||||||
pathsToSave.forEach((path) => {
|
iter.forEach((c) => {
|
||||||
|
const path = 'Node' + c.flatIdx;
|
||||||
if (pathsToSave.indexOf(path) >= old_length) {
|
if (pathsToSave.indexOf(path) >= old_length) {
|
||||||
const displayIndex = pathsToSave.indexOf(path);
|
const name = showInverterLabel
|
||||||
chartData[category].data[path] = { name: 'Battery Cluster ' + (displayIndex + 1), data: [] };
|
? `Battery Cluster ${c.clIdx + 1} in Inverter ${c.invIdx + 1}`
|
||||||
|
: `Battery Cluster ${c.clIdx + 1}`;
|
||||||
|
chartData[category].data[path] = { name, data: [] };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -253,24 +275,23 @@ export const transformInputToBatteryViewDataJson = async (
|
||||||
Soh: 'Soh'
|
Soh: 'Soh'
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let j = 0; j < pathsToSave.length; j++) {
|
iter.forEach((c) => {
|
||||||
|
const path = 'Node' + c.flatIdx;
|
||||||
categories.forEach((category) => {
|
categories.forEach((category) => {
|
||||||
let value: number | undefined;
|
let value: number | undefined;
|
||||||
|
|
||||||
if (hasDevices) {
|
if (hasDevices) {
|
||||||
// Sinexcel: nested under Devices — 0→D1/B1, 1→D1/B2, 2→D2/B1, ...
|
// Sinexcel: Devices keyed by "1","2",... (1-based dict keys)
|
||||||
const deviceId = String(Math.floor(j / 2) + 1);
|
const device = inv.Devices[String(c.invIdx + 1)];
|
||||||
const bi = (j % 2) + 1;
|
const bi = c.clIdx + 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, ...
|
// Growatt: flat Battery1Soc, Battery2Voltage, ... on InverterRecord
|
||||||
const batteryIndex = j + 1;
|
const batteryIndex = c.clIdx + 1;
|
||||||
const fieldName = `Battery${batteryIndex}${categoryFieldMapGrowatt[category]}`;
|
const fieldName = `Battery${batteryIndex}${categoryFieldMapGrowatt[category]}`;
|
||||||
value = inv[fieldName];
|
value = inv[fieldName];
|
||||||
}
|
}
|
||||||
|
|
@ -282,13 +303,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[pathsToSave[j]].data.push([
|
chartData[category].data[path].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 =
|
||||||
|
|
|
||||||
|
|
@ -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": "Netzbetreiber",
|
"networkProvider": "Verteilnetzbetreiber",
|
||||||
"emailAddress": "E-Mail-Adresse",
|
"emailAddress": "E-Mail-Adresse",
|
||||||
"createNewFolder": "Neuer Ordner",
|
"createNewFolder": "Neuer Ordner",
|
||||||
"createNewUser": "Neuer Benutzer",
|
"createNewUser": "Neuer Benutzer",
|
||||||
|
|
@ -97,6 +97,8 @@
|
||||||
"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",
|
||||||
|
|
|
||||||
|
|
@ -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": "Network Provider",
|
"networkProvider": "Grid Provider",
|
||||||
"emailAddress": "Email Address",
|
"emailAddress": "Email Address",
|
||||||
"customerName": "Customer name",
|
"customerName": "Customer name",
|
||||||
"english": "English",
|
"english": "English",
|
||||||
|
|
@ -79,6 +79,8 @@
|
||||||
"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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
"networkProvider": "Gestionnaire de réseau de distribution",
|
||||||
"emailAddress": "Adresse e-mail",
|
"emailAddress": "Adresse e-mail",
|
||||||
"createNewFolder": "Nouveau dossier",
|
"createNewFolder": "Nouveau dossier",
|
||||||
"createNewUser": "Nouvel utilisateur",
|
"createNewUser": "Nouvel utilisateur",
|
||||||
|
|
@ -91,6 +91,8 @@
|
||||||
"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}",
|
||||||
|
|
|
||||||
|
|
@ -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 di rete",
|
"networkProvider": "Gestore della rete di distribuzione",
|
||||||
"emailAddress": "Indirizzo e-mail",
|
"emailAddress": "Indirizzo e-mail",
|
||||||
"customerName": "Nome cliente",
|
"customerName": "Nome cliente",
|
||||||
"english": "Inglese",
|
"english": "Inglese",
|
||||||
|
|
@ -79,6 +79,8 @@
|
||||||
"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}",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue