Compare commits

...

2 Commits

11 changed files with 222 additions and 277 deletions

View File

@ -4,9 +4,9 @@ namespace InnovEnergy.App.SaliMax.DataTypes;
public class Configuration public class Configuration
{ {
public Double MinimumSoC { get; set; } public Double MinimumSoC { get; set; }
public Double GridSetPoint { get; set; } public Double GridSetPoint { get; set; }
public CalibrationChargeType ForceCalibrationCharge { get; set; } public CalibrationChargeType ForceCalibrationChargeState { get; set; }
public DateTime CalibrationChargeDate { get; set; } public DateTime CalibrationChargeDate { get; set; }
} }

View File

@ -10,7 +10,7 @@ namespace InnovEnergy.App.SaliMax.Ess;
public static class Controller public static class Controller
{ {
private static readonly Double BatteryHeatingPower = 200.0; // TODO: move to config private static readonly Double BatteryHeatingPower = 200.0; // TODO: move to config
private static Boolean _hasOriginalNextDayAt10AmBeenChecked = false; private static Boolean _hasCalibrationChargeChecked = false;
private static DateTime _nextDayAt10Am = DateTime.Now; private static DateTime _nextDayAt10Am = DateTime.Now;
public static EssMode SelectControlMode(this StatusRecord s) public static EssMode SelectControlMode(this StatusRecord s)
@ -187,74 +187,49 @@ public static class Controller
private static Boolean MustDoCalibrationCharge(this StatusRecord statusRecord) private static Boolean MustDoCalibrationCharge(this StatusRecord statusRecord)
{ {
var calibrationChargeForced = statusRecord.Config.ForceCalibrationCharge; var calibrationChargeForced = statusRecord.Config.ForceCalibrationChargeState;
var batteryCalibrationChargeRequested = statusRecord.Battery is { CalibrationChargeRequested: true };//BatteryCalibrationChargeRequested(statusRecord.Battery?.CalibrationChargeRequested?? false) ; var batteryCalibrationChargeRequested = statusRecord.Battery is { CalibrationChargeRequested: true };//BatteryCalibrationChargeRequested(statusRecord.Battery?.CalibrationChargeRequested?? false) ;
var mustDoCalibrationCharge = batteryCalibrationChargeRequested || calibrationChargeForced == CalibrationChargeType.ChargePermanently || calibrationChargeForced == CalibrationChargeType.AdditionallyOnce ; var mustDoCalibrationCharge = calibrationChargeForced == CalibrationChargeType.ChargePermanently ||
(calibrationChargeForced == CalibrationChargeType.AdditionallyOnce
&& CalibrationDateHasBeenPassed(statusRecord.Config.DayAndTimeForAdditionalCalibration)) ||
(calibrationChargeForced == CalibrationChargeType.RepetitivelyEvery &&
CalibrationDateHasBeenPassed(statusRecord.Config.DayAndTimeForRepetitiveCalibration));
if (statusRecord.Battery is not null) if (statusRecord.Battery is not null)
{ {
if (calibrationChargeForced == CalibrationChargeType.AdditionallyOnce && statusRecord.Battery.Eoc ) if (calibrationChargeForced == CalibrationChargeType.AdditionallyOnce && statusRecord.Battery.Eoc )
{ {
statusRecord.Config.ForceCalibrationCharge = CalibrationChargeType.RepetitivelyEvery; statusRecord.Config.ForceCalibrationChargeState = CalibrationChargeType.RepetitivelyEvery;
_hasCalibrationChargeChecked = false;
}
else if (calibrationChargeForced == CalibrationChargeType.RepetitivelyEvery && statusRecord.Battery.Eoc)
{
statusRecord.Config.DayAndTimeForRepetitiveCalibration = statusRecord.Config.DayAndTimeForRepetitiveCalibration.AddDays(7);
_hasCalibrationChargeChecked = false;
} }
} }
return mustDoCalibrationCharge; return mustDoCalibrationCharge;
} }
private static Boolean BatteryCalibrationChargeRequested(Boolean calibrationChargeRequested) private static Boolean CalibrationDateHasBeenPassed(DateTime calibrationChargeDate)
{ {
if (calibrationChargeRequested)
{
if (!_hasOriginalNextDayAt10AmBeenChecked)
{
_nextDayAt10Am = CalculateNextCalibrationTime();
_hasOriginalNextDayAt10AmBeenChecked = true;
}
if (CheckNextCalibrationTime(_nextDayAt10Am)) if (!_hasCalibrationChargeChecked)
{
if (DateTime.Now >= calibrationChargeDate )
{ {
_hasCalibrationChargeChecked = true;
return true; return true;
} }
}
else
{
_hasOriginalNextDayAt10AmBeenChecked = false;
return false; return false;
} }
return true;
return false;
} }
private static Boolean CheckNextCalibrationTime(DateTime nextCalibrationTime)
{
var currentDateAndTime = DateTime.Now;
Console.WriteLine(" next X am is: " + nextCalibrationTime);
Console.WriteLine(" currentDateAndTime: " + currentDateAndTime);
// Check if the current time is greater than or equal to the original nextCalibrationTime
var x = currentDateAndTime >= nextCalibrationTime;
Console.WriteLine(" we are checking next X am : " + x);
return x;
}
private static DateTime CalculateNextCalibrationTime()
{
var currentDateAndTime = DateTime.Now;
// Calculate the next 10 AM within the same day
var nextCalibrationTime = new DateTime(currentDateAndTime.Year, currentDateAndTime.Month, currentDateAndTime.Day, 9, 0, 0); // next 9am
// If the current time is already past 9 AM, move to the next day
if (currentDateAndTime.Hour >= 9)
{
nextCalibrationTime = nextCalibrationTime.AddDays(1);
}
return nextCalibrationTime;
}
private static Double ControlGridPower(this StatusRecord status, Double targetPower) private static Double ControlGridPower(this StatusRecord status, Double targetPower)
{ {
return ControlPower return ControlPower

View File

@ -64,7 +64,7 @@ public static class MiddlewareAgent
Configuration? config = JsonSerializer.Deserialize<Configuration>(message); Configuration? config = JsonSerializer.Deserialize<Configuration>(message);
Console.WriteLine($"Received a configuration message: GridSetPoint is " + config.GridSetPoint + ", MinimumSoC is " + config.MinimumSoC + " and ForceCalibrationCharge is " + config.ForceCalibrationCharge+ " and CalibrationChargeDate is " + config.CalibrationChargeDate); Console.WriteLine($"Received a configuration message: GridSetPoint is " + config.GridSetPoint + ", MinimumSoC is " + config.MinimumSoC + " and ForceCalibrationCharge is " + config.ForceCalibrationChargeState+ " and CalibrationChargeDate is " + config.CalibrationChargeDate);
// Send the reply to the sender's endpoint // Send the reply to the sender's endpoint
UdpListener.Send(replyData, replyData.Length, serverEndpoint); UdpListener.Send(replyData, replyData.Length, serverEndpoint);

View File

@ -690,7 +690,17 @@ internal static class Program
{ {
status.Config.MinSoc = config.MinimumSoC; status.Config.MinSoc = config.MinimumSoC;
status.Config.GridSetPoint = config.GridSetPoint * 1000; // converted from kW to W status.Config.GridSetPoint = config.GridSetPoint * 1000; // converted from kW to W
status.Config.ForceCalibrationCharge = config.ForceCalibrationCharge; status.Config.ForceCalibrationChargeState = config.ForceCalibrationChargeState;
if (config.ForceCalibrationChargeState == CalibrationChargeType.RepetitivelyEvery)
{
status.Config.DayAndTimeForRepetitiveCalibration = config.CalibrationChargeDate;
}
else if (config.ForceCalibrationChargeState == CalibrationChargeType.AdditionallyOnce)
{
status.Config.DayAndTimeForAdditionalCalibration = config.CalibrationChargeDate;
}
} }
} }

View File

@ -15,26 +15,27 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
public required Double MinSoc { get; set; } public required Double MinSoc { get; set; }
public required CalibrationChargeType ForceCalibrationCharge { get; set; } public required CalibrationChargeType ForceCalibrationChargeState { get; set; }
public required DateTime DayAndTimeForCalibration { get; set; } public required DateTime DayAndTimeForRepetitiveCalibration { get; set; }
public required Boolean DisplayIndividualBatteries { get; set; } public required DateTime DayAndTimeForAdditionalCalibration { get; set; }
public required Double PConstant { get; set; } public required Boolean DisplayIndividualBatteries { get; set; }
public required Double GridSetPoint { get; set; } public required Double PConstant { get; set; }
public required Double BatterySelfDischargePower { get; set; } public required Double GridSetPoint { get; set; }
public required Double HoldSocZone { get; set; } public required Double BatterySelfDischargePower { get; set; }
public required DevicesConfig IslandMode { get; set; } public required Double HoldSocZone { get; set; }
public required DevicesConfig GridTie { get; set; } public required DevicesConfig IslandMode { get; set; }
public required DevicesConfig GridTie { get; set; }
public required Double MaxBatteryChargingCurrent { get; set; } public required Double MaxBatteryChargingCurrent { get; set; }
public required Double MaxBatteryDischargingCurrent { get; set; } public required Double MaxBatteryDischargingCurrent { get; set; }
public required Double MaxDcPower { get; set; } public required Double MaxDcPower { get; set; }
public required Double MaxChargeBatteryVoltage { get; set; } public required Double MaxChargeBatteryVoltage { get; set; }
public required Double MinDischargeBatteryVoltage { get; set; } public required Double MinDischargeBatteryVoltage { get; set; }
public required DeviceConfig Devices { get; set; } public required DeviceConfig Devices { get; set; }
public required S3Config? S3 { get; set; } public required S3Config? S3 { get; set; }
private static String? LastSavedData { get; set; } private static String? LastSavedData { get; set; }
@ -42,8 +43,9 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
public static Config Default => new() public static Config Default => new()
{ {
MinSoc = 20, MinSoc = 20,
ForceCalibrationCharge = CalibrationChargeType.RepetitivelyEvery, ForceCalibrationChargeState = CalibrationChargeType.RepetitivelyEvery,
DayAndTimeForCalibration = DefaultDatetime, DayAndTimeForRepetitiveCalibration = DefaultDatetime,
DayAndTimeForAdditionalCalibration = DefaultDatetime,
DisplayIndividualBatteries = false, DisplayIndividualBatteries = false,
PConstant = .5, PConstant = .5,
GridSetPoint = 0, GridSetPoint = 0,
@ -120,8 +122,9 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
public static Config Default => new() public static Config Default => new()
{ {
MinSoc = 20, MinSoc = 20,
ForceCalibrationCharge = CalibrationChargeType.RepetitivelyEvery, ForceCalibrationChargeState = CalibrationChargeType.RepetitivelyEvery,
DayAndTimeForCalibration = DefaultDatetime, DayAndTimeForRepetitiveCalibration = DefaultDatetime,
DayAndTimeForAdditionalCalibration = DefaultDatetime,
DisplayIndividualBatteries = false, DisplayIndividualBatteries = false,
PConstant = .5, PConstant = .5,
GridSetPoint = 0, GridSetPoint = 0,

View File

@ -242,7 +242,7 @@ function BatteryView(props: BatteryViewProps) {
> >
{battery.Warnings.value === '' ? ( {battery.Warnings.value === '' ? (
'None' 'None'
) : battery.Warnings.value.toString().split(';').length > ) : battery.Warnings.value.toString().split('-').length >
1 ? ( 1 ? (
<Link <Link
style={{ color: 'black' }} style={{ color: 'black' }}
@ -274,7 +274,7 @@ function BatteryView(props: BatteryViewProps) {
> >
{battery.Alarms.value === '' ? ( {battery.Alarms.value === '' ? (
'None' 'None'
) : battery.Alarms.value.toString().split(';').length > ) : battery.Alarms.value.toString().split('-').length >
1 ? ( 1 ? (
<Link <Link
style={{ color: 'black' }} style={{ color: 'black' }}

View File

@ -84,8 +84,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
height: '150px', height: '150px'
marginTop: '30px'
}; };
const batteryStringStyle = { const batteryStringStyle = {
@ -132,6 +131,17 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
<Grid container> <Grid container>
<Grid item md={4.9} xs={4.9}></Grid> <Grid item md={4.9} xs={4.9}></Grid>
<Grid item md={2.2} xs={2.2}> <Grid item md={2.2} xs={2.2}>
<Typography
variant="h6"
component="div"
sx={{
marginLeft: '120px',
fontWeight: 'bold'
}}
>
{'Node ' + props.batteryData.BatteryId}
</Typography>
<div style={batteryStyle}> <div style={batteryStyle}>
<div <div
style={{ style={{

View File

@ -108,20 +108,19 @@ function MainStats(props: MainStatsProps) {
function generateSeries(chartData, category, color) { function generateSeries(chartData, category, color) {
const series = []; const series = [];
const pathsToSearch = [ const pathsToSearch = [
'Battery1', 'Node2',
'Battery2', 'Node3',
'Battery3', 'Node4',
'Battery4', 'Node5',
'Battery5', 'Node6',
'Battery6', 'Node7',
'Battery7', 'Node8',
'Battery8', 'Node9',
'Battery9', 'Node10',
'Battery10' 'Node11'
]; ];
let i = 0; let i = 0;
// Assuming the chartData.Soc.data structure
pathsToSearch.forEach((devicePath) => { pathsToSearch.forEach((devicePath) => {
if ( if (
Object.hasOwnProperty.call(chartData[category].data, devicePath) && Object.hasOwnProperty.call(chartData[category].data, devicePath) &&

View File

@ -18,7 +18,6 @@ import {
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import { DateField } from '@mui/x-date-pickers/DateField';
import axiosConfig from '../../../Resources/axiosConfig'; import axiosConfig from '../../../Resources/axiosConfig';
import { Close as CloseIcon } from '@mui/icons-material'; import { Close as CloseIcon } from '@mui/icons-material';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
@ -57,6 +56,8 @@ function Configuration(props: ConfigurationProps) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [updated, setUpdated] = useState(false); const [updated, setUpdated] = useState(false);
const [dateSelectionError, setDateSelectionError] = useState('');
const [isErrorDateModalOpen, setErrorDateModalOpen] = useState(false);
const [openForcedCalibrationCharge, setOpenForcedCalibrationCharge] = const [openForcedCalibrationCharge, setOpenForcedCalibrationCharge] =
useState(false); useState(false);
const [ const [
@ -65,19 +66,13 @@ function Configuration(props: ConfigurationProps) {
] = useState<string>( ] = useState<string>(
props.values.calibrationChargeForced[0].value.toString() props.values.calibrationChargeForced[0].value.toString()
); );
const [isDateModalOpen, setIsDateModalOpen] = useState(false);
const [calibrationChargeDate, setCalibrationChargeDate] = useState();
const [isErrorDateModalOpen, setErrorDateModalOpen] = useState(false);
const [dateSelectionError, setDateSelectionError] = useState('');
const [dateFieldOpen, setDateFieldOpen] = useState(false);
const [formValues, setFormValues] = useState<ConfigurationValues>({ const [formValues, setFormValues] = useState<ConfigurationValues>({
minimumSoC: props.values.minimumSoC[0].value, minimumSoC: props.values.minimumSoC[0].value,
gridSetPoint: (props.values.gridSetPoint[0].value as number) / 1000, gridSetPoint: (props.values.gridSetPoint[0].value as number) / 1000,
forceCalibrationCharge: forcedCalibrationChargeOptions.indexOf( forceCalibrationCharge: forcedCalibrationChargeOptions.indexOf(
props.values.calibrationChargeForced[0].value.toString() props.values.calibrationChargeForced[0].value.toString()
), ),
calibrationChargeDate: Date(props.values.calibrationChargeDate[0].value) calibrationChargeDate: null
}); });
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
@ -97,30 +92,24 @@ function Configuration(props: ConfigurationProps) {
} }
}; };
const handleCancel = () => {
setIsDateModalOpen(false);
};
const handleOkOnErrorDateModal = () => { const handleOkOnErrorDateModal = () => {
setErrorDateModalOpen(false); setErrorDateModalOpen(false);
setIsDateModalOpen(true);
}; };
const handleConfirm = () => { const handleConfirm = (newDate) => {
setIsDateModalOpen(false); if (newDate.isBefore(dayjs())) {
setDateSelectionError('You must specify a future date');
if (calibrationChargeDate.isBefore(dayjs())) {
setDateSelectionError('You must use a future date');
setErrorDateModalOpen(true); setErrorDateModalOpen(true);
return; return;
} }
setDateFieldOpen(true); setFormValues({
...formValues,
['calibrationChargeDate']: newDate.toDate()
});
}; };
const handleSelectedCalibrationChargeChange = (event) => { const handleSelectedCalibrationChargeChange = (event) => {
if (event.target.value != 'ChargePermanently') {
setIsDateModalOpen(true);
}
setSelectedForcedCalibrationChargeOption(event.target.value); setSelectedForcedCalibrationChargeOption(event.target.value);
setFormValues({ setFormValues({
@ -181,6 +170,47 @@ function Configuration(props: ConfigurationProps) {
alignItems="stretch" alignItems="stretch"
spacing={3} spacing={3}
> >
{isErrorDateModalOpen && (
<Modal open={isErrorDateModalOpen} onClose={() => {}}>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 450,
bgcolor: 'background.paper',
borderRadius: 4,
boxShadow: 24,
p: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<Typography
variant="body1"
gutterBottom
sx={{ fontWeight: 'bold' }}
>
{dateSelectionError}
</Typography>
<Button
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={handleOkOnErrorDateModal}
>
Ok
</Button>
</Box>
</Modal>
)}
<Grid item xs={12} md={12}> <Grid item xs={12} md={12}>
<CardContent> <CardContent>
<Box <Box
@ -246,128 +276,31 @@ function Configuration(props: ConfigurationProps) {
</Select> </Select>
</FormControl> </FormControl>
</div> </div>
{(dateFieldOpen || formValues.forceCalibrationCharge != 2) && ( {formValues.forceCalibrationCharge != 2 && (
<div> <div>
<LocalizationProvider dateAdapter={AdapterDayjs}> <LocalizationProvider dateAdapter={AdapterDayjs}>
<DateField <DateTimePicker
label="Calibration Charge Date" label="Select Next Calibration Charge Date"
value={calibrationChargeDate} value={
formValues.forceCalibrationCharge == 0
? dayjs(
props.values.repetitiveCalibrationChargeDate[0]
.value
)
: dayjs(
props.values.additionalCalibrationChargeDate[0]
.value
)
}
onChange={handleConfirm}
sx={{
marginTop: 2
}}
/> />
</LocalizationProvider> </LocalizationProvider>
</div> </div>
)} )}
{isErrorDateModalOpen && (
<Modal open={isErrorDateModalOpen} onClose={() => {}}>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 450,
bgcolor: 'background.paper',
borderRadius: 4,
boxShadow: 24,
p: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<Typography
variant="body1"
gutterBottom
sx={{ fontWeight: 'bold' }}
>
{dateSelectionError}
</Typography>
<Button
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={handleOkOnErrorDateModal}
>
Ok
</Button>
</Box>
</Modal>
)}
{isDateModalOpen && (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<Modal open={isDateModalOpen} onClose={() => {}}>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 450,
bgcolor: 'background.paper',
borderRadius: 4,
boxShadow: 24,
p: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<DateTimePicker
label="Select Calibration Charge Date"
value={calibrationChargeDate}
onChange={(newDate) =>
setCalibrationChargeDate(newDate)
}
sx={{
marginTop: 2
}}
/>
<div
style={{
display: 'flex',
alignItems: 'center',
marginTop: 10
}}
>
<Button
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={handleConfirm}
>
Confirm
</Button>
<Button
sx={{
marginTop: 2,
marginLeft: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={handleCancel}
>
Cancel
</Button>
</div>
</Box>
</Modal>
</LocalizationProvider>
)}
<div style={{ marginBottom: '5px' }}> <div style={{ marginBottom: '5px' }}>
<TextField <TextField
label={ label={
@ -461,26 +394,7 @@ function Configuration(props: ConfigurationProps) {
}} }}
/> />
)} )}
{error && (
<Alert
severity="error"
sx={{
ml: 1,
display: 'flex',
alignItems: 'center'
}}
>
An error has occurred
<IconButton
color="inherit"
size="small"
onClick={() => setError(false)} // Set error state to false on click
sx={{ marginLeft: '4px' }}
>
<CloseIcon fontSize="small" />
</IconButton>
</Alert>
)}
{updated && ( {updated && (
<Alert <Alert
severity="success" severity="success"

View File

@ -159,7 +159,8 @@ export type TopologyValues = {
DcDcNum: I_BoxDataValue[]; DcDcNum: I_BoxDataValue[];
calibrationChargeForced: I_BoxDataValue[]; calibrationChargeForced: I_BoxDataValue[];
mode: I_BoxDataValue[]; mode: I_BoxDataValue[];
calibrationChargeDate: I_BoxDataValue[]; repetitiveCalibrationChargeDate: I_BoxDataValue[];
additionalCalibrationChargeDate: I_BoxDataValue[];
batteryView: Battery[]; batteryView: Battery[];
}; };
@ -173,6 +174,7 @@ const batteryPaths = [
'/Battery/Devices/%id%/Dc/Voltage', '/Battery/Devices/%id%/Dc/Voltage',
'/Battery/Devices/%id%/Soc', '/Battery/Devices/%id%/Soc',
'/Battery/Devices/%id%/Temperatures/Cells/Average', '/Battery/Devices/%id%/Temperatures/Cells/Average',
//'/Log/SalimaxWarnings/Battery/%id%',
'/Battery/Devices/%id%/Warnings', '/Battery/Devices/%id%/Warnings',
'/Battery/Devices/%id%/Alarms', '/Battery/Devices/%id%/Alarms',
@ -294,23 +296,37 @@ export const topologyPaths: TopologyPaths = {
gridSetPoint: ['/Config/GridSetPoint'], gridSetPoint: ['/Config/GridSetPoint'],
maximumDischargePower: ['/Config/MaxBatteryDischargingCurrent'], maximumDischargePower: ['/Config/MaxBatteryDischargingCurrent'],
DcDcNum: ['/DcDc/SystemControl/NumberOfConnectedSlaves'], DcDcNum: ['/DcDc/SystemControl/NumberOfConnectedSlaves'],
calibrationChargeForced: ['/Config/ForceCalibrationCharge'], calibrationChargeForced: ['/Config/ForceCalibrationChargeState'],
mode: ['/EssControl/Mode'], mode: ['/EssControl/Mode'],
calibrationChargeDate: ['/EssControl/Date'] repetitiveCalibrationChargeDate: [
'/Config/DayAndTimeForRepetitiveCalibration'
],
additionalCalibrationChargeDate: [
'/Config/DayAndTimeForAdditionalCalibration'
]
}; };
//We are using the function every time we fetch the data from S3 (every 2 seconds).
//The data is of the following form: TopologyValues
//key: I_BoxDataValue[] ==> key: [{unit:'',value:''},{unit:'',value:''},...]
//battery_view: [ {"Battery_id": 2,'FwVersion': {'unit':,'value':}},
// {"Battery_id": 4,'FwVersion': {'unit':,'value':}}
//]
//For batteries, we follow a different approach. We define a key battery_view that is of type Battery[]
export const extractValues = ( export const extractValues = (
timeSeriesData: DataPoint timeSeriesData: DataPoint
): TopologyValues | null => { ): TopologyValues | null => {
const extractedValues: TopologyValues = {} as TopologyValues; const extractedValues: TopologyValues = {} as TopologyValues;
for (const topologyKey of Object.keys(topologyPaths)) { for (const topologyKey of Object.keys(topologyPaths)) {
//Each topologykey may have more than one paths (for example inverter)
const paths = topologyPaths[topologyKey]; const paths = topologyPaths[topologyKey];
let topologyValues: { unit: string; value: string | number }[] = []; let topologyValues: { unit: string; value: string | number }[] = [];
if (topologyKey === 'batteryView') { if (topologyKey === 'batteryView') {
extractedValues[topologyKey] = []; extractedValues[topologyKey] = [];
const battery_ids_from_csv = timeSeriesData.value[ const node_ids_from_csv = timeSeriesData.value[
'/Config/Devices/BatteryNodes' '/Config/Devices/BatteryNodes'
].value ].value
.toString() .toString()
@ -322,7 +338,11 @@ export const extractValues = (
while (pathIndex < paths.length) { while (pathIndex < paths.length) {
let battery = {}; let battery = {};
let existingKeys = 0; let existingKeys = 0;
battery[BatteryKeys[0]] = battery_ids_from_csv[battery_index];
//We prepare a battery object for each node. We extract the nodes from the '/Config/Devices/BatteryNodes' path. For example, nodes can be [2,4,5,6] (one is missing)
//BatteryKeys[0] is the battery id. We set the battery id to the corresponding node id.
battery[BatteryKeys[0]] = node_ids_from_csv[battery_index];
//Then, search all the remaining battery keys
for (let i = 1; i < BatteryKeys.length; i++) { for (let i = 1; i < BatteryKeys.length; i++) {
const path = paths[pathIndex]; const path = paths[pathIndex];
if (timeSeriesData.value.hasOwnProperty(path)) { if (timeSeriesData.value.hasOwnProperty(path)) {

View File

@ -61,6 +61,17 @@ export interface BatteryOverviewInterface {
Current: chartInfoInterface; Current: chartInfoInterface;
} }
// We use this function in order to retrieve data for main stats.
//The data is of the following form:
//'Soc' : {name:'Soc',data:[
// 'Node1': {name:'Node1', data: [[timestamp,value],[timestamp,value]]},
// 'Node2': {name:'Node2', data: [[timestamp,value],[timestamp,value]]},
// ]},
//'Temperature' : {name:'Temperature',data:[
// 'Node1': {name:'Node1', data: [[timestamp,value],[timestamp,value]]},
// 'Node2': {name:'Node2', data: [[timestamp,value],[timestamp,value]]},
// ]}
export const transformInputToBatteryViewData = async ( export const transformInputToBatteryViewData = async (
s3Credentials: I_S3Credentials, s3Credentials: I_S3Credentials,
startTimestamp: UnixTime, startTimestamp: UnixTime,
@ -94,18 +105,7 @@ export const transformInputToBatteryViewData = async (
'/Battery/Devices/10/' '/Battery/Devices/10/'
]; ];
const pathsToSave = [ const pathsToSave = [];
'Battery1',
'Battery2',
'Battery3',
'Battery4',
'Battery5',
'Battery6',
'Battery7',
'Battery8',
'Battery9',
'Battery10'
];
const chartData: BatteryDataInterface = { const chartData: BatteryDataInterface = {
Soc: { name: 'State Of Charge', data: [] }, Soc: { name: 'State Of Charge', data: [] },
@ -123,20 +123,7 @@ export const transformInputToBatteryViewData = async (
Current: { magnitude: 0, unit: '', min: 0, max: 0 } Current: { magnitude: 0, unit: '', min: 0, max: 0 }
}; };
categories.forEach((category) => { let initialiation = true;
chartData[category].data = [];
pathsToSave.forEach((path) => {
chartData[category].data[path] = { name: path, data: [] };
});
chartOverview[category] = {
magnitude: 0,
unit: '',
min: MAX_NUMBER,
max: -MAX_NUMBER
};
});
let adjustedTimestampArray = []; let adjustedTimestampArray = [];
let startTimestampToNum = Number(startTimestamp); let startTimestampToNum = Number(startTimestamp);
@ -160,6 +147,7 @@ export const transformInputToBatteryViewData = async (
adjustedTimestampArray.push(adjustedTimestamp); adjustedTimestampArray.push(adjustedTimestamp);
} }
//Wait until fetching all the data
const results = await Promise.all(timestampPromises); const results = await Promise.all(timestampPromises);
for (let i = 0; i < results.length; i++) { for (let i = 0; i < results.length; i++) {
@ -170,6 +158,32 @@ export const transformInputToBatteryViewData = async (
) { ) {
// Handle not available or try later case // Handle not available or try later case
} else { } else {
const battery_nodes = result['/Config/Devices/BatteryNodes'].value
.toString()
.split(',');
//Initialize the chartData structure based on the node names extracted from the first result
if (initialiation) {
initialiation = false;
battery_nodes.forEach((node) => {
pathsToSave.push('Node' + node);
});
categories.forEach((category) => {
chartData[category].data = [];
pathsToSave.forEach((path) => {
chartData[category].data[path] = { name: path, data: [] };
});
chartOverview[category] = {
magnitude: 0,
unit: '',
min: MAX_NUMBER,
max: -MAX_NUMBER
};
});
}
for ( for (
let category_index = 0; let category_index = 0;
category_index < pathCategories.length; category_index < pathCategories.length;