Innovenergy_trunk/csharp/App/SaliMax/src/Program.cs

800 lines
33 KiB
C#

#undef Amax
#undef GridLimit
using System.Diagnostics;
using System.IO.Compression;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Reflection.Metadata;
using System.Security;
using System.Text;
using Flurl.Http;
using InnovEnergy.App.SaliMax.Devices;
using InnovEnergy.App.SaliMax.Ess;
using InnovEnergy.App.SaliMax.MiddlewareClasses;
using InnovEnergy.App.SaliMax.SaliMaxRelays;
using InnovEnergy.App.SaliMax.System;
using InnovEnergy.App.SaliMax.SystemConfig;
using InnovEnergy.Lib.Devices.AMPT;
using InnovEnergy.Lib.Devices.Battery48TL;
using InnovEnergy.Lib.Devices.EmuMeter;
using InnovEnergy.Lib.Devices.Trumpf.SystemControl;
using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes;
using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes;
using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control;
using InnovEnergy.Lib.Protocols.Modbus.Channels;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Utils;
using InnovEnergy.App.SaliMax.DataTypes;
using static InnovEnergy.App.SaliMax.AggregationService.Aggregator;
using static InnovEnergy.App.SaliMax.MiddlewareClasses.MiddlewareAgent;
using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig;
using DeviceState = InnovEnergy.App.SaliMax.Devices.DeviceState;
#pragma warning disable IL2026
namespace InnovEnergy.App.SaliMax;
internal static class Program
{
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
private static readonly IReadOnlyList<Byte> BatteryNodes;
private static readonly Channel TruConvertAcChannel;
private static readonly Channel TruConvertDcChannel;
private static readonly Channel GridMeterChannel;
private static readonly Channel IslandBusLoadChannel;
private static readonly Channel PvOnDc;
private static readonly Channel PvOnAcGrid;
private static readonly Channel PvOnAcIsland;
private static readonly Channel RelaysChannel;
private static readonly Channel BatteriesChannel;
private const String VpnServerIp = "10.2.0.11";
private static Boolean _subscribedToQueue = false;
private static Boolean _subscribeToQueueForTheFirstTime = false;
private static SalimaxAlarmState _prevSalimaxState = SalimaxAlarmState.Green;
private static Int32 _heartBitInterval = 0;
private const UInt16 NbrOfFileToConcatenate = 15;
private static UInt16 _counterOfFile = 0;
static Program()
{
var config = Config.Load();
var d = config.Devices;
Channel CreateChannel(SalimaxDevice device) => device.DeviceState == DeviceState.Disabled
? new NullChannel()
: new TcpChannel(device);
TruConvertAcChannel = CreateChannel(d.TruConvertAcIp);
TruConvertDcChannel = CreateChannel(d.TruConvertDcIp);
GridMeterChannel = CreateChannel(d.GridMeterIp);
IslandBusLoadChannel = CreateChannel(d.IslandBusLoadMeterIp);
PvOnDc = CreateChannel(d.PvOnDc);
PvOnAcGrid = CreateChannel(d.PvOnAcGrid);
PvOnAcIsland = CreateChannel(d.PvOnAcIsland);
RelaysChannel = CreateChannel(d.RelaysIp);
BatteriesChannel = CreateChannel(d.BatteryIp);
BatteryNodes = config
.Devices
.BatteryNodes
.Select(n => n.ConvertTo<Byte>())
.ToArray(config.Devices.BatteryNodes.Length);
}
public static async Task Main(String[] args)
{
//Do not await
HourlyDataAggregationManager()
.ContinueWith(t=>t.Exception.WriteLine(), TaskContinuationOptions.OnlyOnFaulted)
.SupressAwaitWarning();
DailyDataAggregationManager()
.ContinueWith(t=>t.Exception.WriteLine(), TaskContinuationOptions.OnlyOnFaulted)
.SupressAwaitWarning();
InitializeCommunicationToMiddleware();
while (true)
{
try
{
await Run();
}
catch (Exception e)
{
e.LogError();
}
}
}
private static async Task Run()
{
"Starting SaliMax".WriteLine();
Watchdog.NotifyReady();
var battery48TlDevices = BatteryNodes
.Select(n => new Battery48TlDevice(BatteriesChannel, n))
.ToList();
var batteryDevices = new Battery48TlDevices(battery48TlDevices);
var acDcDevices = new TruConvertAcDcDevices(TruConvertAcChannel);
var dcDcDevices = new TruConvertDcDcDevices(TruConvertDcChannel);
var gridMeterDevice = new EmuMeterDevice(GridMeterChannel);
var acIslandLoadMeter = new EmuMeterDevice(IslandBusLoadChannel);
var pvOnDcDevice = new AmptDevices(PvOnDc);
var pvOnAcGridDevice = new AmptDevices(PvOnAcGrid);
var pvOnAcIslandDevice = new AmptDevices(PvOnAcIsland);
#if Amax
var saliMaxRelaysDevice = new RelaysDeviceAmax(RelaysChannel);
#else
var saliMaxRelaysDevice = new RelaysDevice(RelaysChannel);
#endif
StatusRecord ReadStatus()
{
var config = Config.Load();
var devices = config.Devices;
var acDc = acDcDevices.Read();
var dcDc = dcDcDevices.Read();
var relays = saliMaxRelaysDevice.Read();
var loadOnAcIsland = acIslandLoadMeter.Read();
var gridMeter = gridMeterDevice.Read();
var pvOnDc = pvOnDcDevice.Read();
var battery = batteryDevices.Read();
var pvOnAcGrid = pvOnAcGridDevice.Read();
var pvOnAcIsland = pvOnAcIslandDevice.Read();
var gridBusToIslandBus = Topology.CalculateGridBusToIslandBusPower(pvOnAcIsland, loadOnAcIsland, acDc);
var gridBusLoad = devices.LoadOnAcGrid.DeviceState == DeviceState.Disabled
? new AcPowerDevice { Power = 0 }
: Topology.CalculateGridBusLoad(gridMeter, pvOnAcGrid, gridBusToIslandBus);
var dcLoad = devices.LoadOnDc.DeviceState == DeviceState.Disabled
? new DcPowerDevice { Power = 0 }
: Topology.CalculateDcLoad(acDc, pvOnDc, dcDc);
var acDcToDcLink = devices.LoadOnDc.DeviceState == DeviceState.Disabled ?
Topology.CalculateAcDcToDcLink(pvOnDc, dcDc, acDc)
: new DcPowerDevice{ Power = acDc.Dc.Power};
return new StatusRecord
{
AcDc = acDc,
DcDc = dcDc,
Battery = battery,
Relays = relays,
GridMeter = gridMeter,
PvOnAcGrid = pvOnAcGrid,
PvOnAcIsland = pvOnAcIsland,
PvOnDc = pvOnDc,
AcGridToAcIsland = gridBusToIslandBus,
AcDcToDcLink = acDcToDcLink,
LoadOnAcGrid = gridBusLoad,
LoadOnAcIsland = loadOnAcIsland,
LoadOnDc = dcLoad,
StateMachine = StateMachine.Default,
EssControl = EssControl.Default,
Log = new SystemLog { SalimaxAlarmState = SalimaxAlarmState.Green, Message = null, SalimaxAlarms = null, SalimaxWarnings = null}, //TODO: Put real stuff
Config = config // load from disk every iteration, so config can be changed while running
};
}
void WriteControl(StatusRecord r)
{
if (r.Relays is not null)
#if Amax
saliMaxRelaysDevice.Write((RelaysRecordAmax)r.Relays);
#else
((RelaysDevice)saliMaxRelaysDevice).Write((RelaysRecord)r.Relays);
#endif
acDcDevices.Write(r.AcDc);
dcDcDevices.Write(r.DcDc);
}
Console.WriteLine("press ctrl-c to stop");
while (true)
{
await Observable
.Interval(UpdateInterval)
.Select(_ => RunIteration())
.SelectMany(r => UploadCsv(r, DateTime.Now.Round(UpdateInterval)))
.SelectError()
.ToTask();
}
StatusRecord RunIteration()
{
Watchdog.NotifyAlive();
var record = ReadStatus();
/******************************************** For Battery Debug *************************************/
var currentSalimaxState = GetSalimaxStateAlarm(record);
SendSalimaxStateAlarm(currentSalimaxState, record);
//record.ControlPvPower(record.Config.CurtailP);
record.ControlConstants();
record.ControlSystemState();
var essControl = record.ControlEss().WriteLine();
record.EssControl = essControl;
record.AcDc.SystemControl.ApplyAcDcDefaultSettings();
record.DcDc.SystemControl.ApplyDcDcDefaultSettings();
DistributePower(record, essControl);
WriteControl(record);
$"{DateTime.Now.Round(UpdateInterval).ToUnixTime()} : {record.StateMachine.State}: {record.StateMachine.Message}".WriteLine();
record.CreateTopologyTextBlock().WriteLine();
(record.Relays is null ? "No relay Data available" : record.Relays.FiWarning ? "Alert: Fi Warning Detected" : "No Fi Warning Detected").WriteLine();
(record.Relays is null ? "No relay Data available" : record.Relays.FiError ? "Alert: Fi Error Detected" : "No Fi Error Detected") .WriteLine();
record.Config.Save();
"===========================================".WriteLine();
return record;
}
// ReSharper disable once FunctionNeverReturns
}
private static void SendSalimaxStateAlarm(StatusMessage currentSalimaxState, StatusRecord record)
{
var s3Bucket = Config.Load().S3?.Bucket;
var subscribedNow = false;
//Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue
_heartBitInterval++;
//When the controller boots, it tries to subscribe to the queue
if (_subscribeToQueueForTheFirstTime == false)
{
subscribedNow = true;
_subscribeToQueueForTheFirstTime = true;
_prevSalimaxState = currentSalimaxState.Status;
_subscribedToQueue = RabbitMqManager.SubscribeToQueue(currentSalimaxState, s3Bucket, VpnServerIp);
}
//If already subscribed to the queue and the status has been changed, update the queue
if (!subscribedNow && _subscribedToQueue && currentSalimaxState.Status != _prevSalimaxState)
{
_prevSalimaxState = currentSalimaxState.Status;
if (s3Bucket != null)
RabbitMqManager.InformMiddleware(currentSalimaxState);
}
else if (_subscribedToQueue && _heartBitInterval >= 30)
{
//Send a heartbit to the backend
Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
_heartBitInterval = 0;
currentSalimaxState.Type = MessageType.Heartbit;
if (s3Bucket != null)
RabbitMqManager.InformMiddleware(currentSalimaxState);
}
//If there is an available message from the RabbitMQ Broker, apply the configuration file
Configuration? config = SetConfigurationFile();
if (config != null)
{
record.ApplyConfigFile(config);
}
}
private static StatusMessage GetSalimaxStateAlarm(StatusRecord record)
{
var alarmCondition = record.DetectAlarmStates(); // this need to be emailed to support or customer
var s3Bucket = Config.Load().S3?.Bucket;
var alarmList = new List<AlarmOrWarning>();
var warningList = new List<AlarmOrWarning>();
var bAlarmList = new List<String>();
var bWarningList = new List<String>();
if (record.Battery != null)
{
var i = 0;
foreach (var battery in record.Battery.Devices)
{
var devicesBatteryNode = record.Config.Devices.BatteryNodes[i];
if (battery.LimpBitMap == 0)
{
// "All String are Active".WriteLine();
}
else if (IsPowerOfTwo(battery.LimpBitMap))
{
"1 String is disabled".WriteLine();
warningList.Add(new AlarmOrWarning
{
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
CreatedBy = "Battery node" + devicesBatteryNode,
Description = "1 String is disabled"
});
bWarningList.Add("/"+i+1 + "/1 String is disabled"); // battery id instead ( i +1 ) of node id: requested from the frontend
}
else
{
"2 or more string are disabled".WriteLine();
alarmList.Add(new AlarmOrWarning
{
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
CreatedBy = "Battery node" + devicesBatteryNode,
Description = "2 or more string are disabled"
});
bAlarmList.Add(i +";2 or more string are disabled");
}
foreach (var warning in record.Battery.Warnings)
{
warningList.Add(new AlarmOrWarning
{
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
CreatedBy = "Battery node" + devicesBatteryNode,
Description = warning
});
bWarningList.Add(i +";" + warning);
}
foreach (var alarm in battery.Alarms)
{
alarmList.Add(new AlarmOrWarning
{
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
CreatedBy = "Battery node" + devicesBatteryNode,
Description = alarm
});
bWarningList.Add(i +";" + alarm);
}
i++;
}
}
if (alarmCondition is not null)
{
alarmCondition.WriteLine();
alarmList.Add(new AlarmOrWarning
{
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
CreatedBy = "Salimax",
Description = alarmCondition
});
}
foreach (var alarm in record.AcDc.Alarms)
{
alarmList.Add(new AlarmOrWarning
{
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
CreatedBy = "AcDc",
Description = alarm.ToString()
});
}
foreach (var alarm in record.DcDc.Alarms)
{
alarmList.Add(new AlarmOrWarning
{
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
CreatedBy = "DcDc",
Description = alarm.ToString()
});
}
foreach (var warning in record.AcDc.Warnings)
{
warningList.Add(new AlarmOrWarning
{
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
CreatedBy = "AcDc",
Description = warning.ToString()
});
}
foreach (var warning in record.DcDc.Warnings)
{
warningList.Add(new AlarmOrWarning
{
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
CreatedBy = "DcDc",
Description = warning.ToString()
});
}
var salimaxAlarmsState = warningList.Any()
? SalimaxAlarmState.Orange
: SalimaxAlarmState.Green; // this will be replaced by LedState
salimaxAlarmsState = alarmList.Any()
? SalimaxAlarmState.Red
: salimaxAlarmsState; // this will be replaced by LedState
int.TryParse(s3Bucket?.Split("-")[0], out var installationId);
var returnedStatus = new StatusMessage
{
InstallationId = installationId,
Product = 0,
Status = salimaxAlarmsState,
Type = MessageType.AlarmOrWarning,
Alarms = alarmList,
Warnings = warningList
};
return returnedStatus;
}
private static String? DetectAlarmStates(this StatusRecord r) => r.Relays switch
{
{ K2ConnectIslandBusToGridBus: false, K2IslandBusIsConnectedToGridBus: true } => " Contradiction: R0 is opening the K2 but the K2 is still close ",
{ K1GridBusIsConnectedToGrid : false, K2IslandBusIsConnectedToGridBus: true } => " Contradiction: K1 is open but the K2 is still close ",
{ FiError: true, K2IslandBusIsConnectedToGridBus: true } => " Contradiction: Fi error occured but the K2 is still close ",
_ => null
};
private static void ControlConstants(this StatusRecord r)
{
var inverters = r.AcDc.Devices;
var dcDevices = r.DcDc.Devices;
var configFile = r.Config;
var maxBatteryDischargingCurrentLive = 0.0;
// This adapting the max discharging current to the current Active Strings
if (r.Battery != null)
{
const Int32 stringsByBattery = 5;
var numberOfBatteriesConfigured = r.Config.Devices.BatteryNodes.Length;
var numberOfTotalStrings = stringsByBattery * numberOfBatteriesConfigured;
var dischargingCurrentByString = configFile.MaxBatteryDischargingCurrent / numberOfTotalStrings;
var boolList = new List<Boolean>();
foreach (var stringActive in r.Battery.Devices.Select(b => b.BatteryStrings).ToList())
{
boolList.Add(stringActive.String1Active);
boolList.Add(stringActive.String2Active);
boolList.Add(stringActive.String3Active);
boolList.Add(stringActive.String4Active);
boolList.Add(stringActive.String5Active);
}
var numberOfBatteriesStringActive = boolList.Count(b => b);
if (numberOfTotalStrings != 0)
{
maxBatteryDischargingCurrentLive = dischargingCurrentByString * numberOfBatteriesStringActive;
}
}
// TODO The discharging current is well calculated but not communicated to live. But Written in S3
var devicesConfig = r.AcDc.Devices.All(d => d.Control.Ac.GridType == GridType.GridTied400V50Hz) ? configFile.GridTie : configFile.IslandMode; // TODO if any of the grid tie mode
inverters.ForEach(d => d.Control.Dc.MaxVoltage = devicesConfig.AcDc.MaxDcLinkVoltage);
inverters.ForEach(d => d.Control.Dc.MinVoltage = devicesConfig.AcDc.MinDcLinkVoltage);
inverters.ForEach(d => d.Control.Dc.ReferenceVoltage = devicesConfig.AcDc.ReferenceDcLinkVoltage);
inverters.ForEach(d => d.Control.Dc.PrechargeConfig = DcPrechargeConfig.PrechargeDcWithInternal);
dcDevices.ForEach(d => d.Control.DroopControl.UpperVoltage = devicesConfig.DcDc.UpperDcLinkVoltage);
dcDevices.ForEach(d => d.Control.DroopControl.LowerVoltage = devicesConfig.DcDc.LowerDcLinkVoltage);
dcDevices.ForEach(d => d.Control.DroopControl.ReferenceVoltage = devicesConfig.DcDc.ReferenceDcLinkVoltage);
dcDevices.ForEach(d => d.Control.CurrentControl.MaxBatteryChargingCurrent = configFile.MaxBatteryChargingCurrent);
dcDevices.ForEach(d => d.Control.CurrentControl.MaxBatteryDischargingCurrent = maxBatteryDischargingCurrentLive);
dcDevices.ForEach(d => d.Control.MaxDcPower = configFile.MaxDcPower);
dcDevices.ForEach(d => d.Control.VoltageLimits.MaxBatteryVoltage = configFile.MaxChargeBatteryVoltage);
dcDevices.ForEach(d => d.Control.VoltageLimits.MinBatteryVoltage = configFile.MinDischargeBatteryVoltage);
dcDevices.ForEach(d => d.Control.ControlMode = DcControlMode.VoltageDroop);
r.DcDc.ResetAlarms();
r.AcDc.ResetAlarms();
}
// This will be used for provider throttling, this example is only for either 100% or 0 %
private static void ControlPvPower(this StatusRecord r, Int16 exportLimit = 100)
{
UInt16 stableFactor = 500;
var inverters = r.AcDc.Devices;
var dcDevices = r.DcDc.Devices;
var configFile = r.Config;
var systemProduction = inverters.Count * 25000;
var limitSystemProduction = systemProduction * exportLimit / 100;
var devicesConfig = r.AcDc.Devices.All(d => d.Control.Ac.GridType == GridType.GridTied400V50Hz) ? configFile.GridTie : configFile.IslandMode; // TODO if any of the grid tie mode
var targetReferenceVoltage = devicesConfig.AcDc.ReferenceDcLinkVoltage;
if (r.PvOnDc.Dc.Power != null && r.PvOnDc.Dc.Power > limitSystemProduction)
{
exportLimit.WriteLine(" exportLimit");
systemProduction.WriteLine(" systemProduction");
limitSystemProduction.WriteLine(" limitSystemexport");
targetReferenceVoltage.WriteLine("targetReferenceVoltage");
if (r.GridMeter?.Ac.Power.Active != null)
{
if (r.GridMeter.Ac.Power.Active < -limitSystemProduction)
{
"We are openning the window".WriteLine();
r.Config.GridSetPoint = -limitSystemProduction + stableFactor;
r.Config.GridSetPoint.WriteLine(" Grid set point");
var maxDcLinkVoltage = (UInt16)(r.Config.GridTie.AcDc.MaxDcLinkVoltage + 10);
var minDcLinkVoltage = (UInt16)(r.Config.GridTie.AcDc.MinDcLinkVoltage - 10);
var maxDcDcLinkVoltage = (UInt16)(r.Config.GridTie.DcDc.UpperDcLinkVoltage + 10);
var minDcDcLinkVoltage = (UInt16)(r.Config.GridTie.DcDc.LowerDcLinkVoltage + 10);
r.Config.GridTie.AcDc.MaxDcLinkVoltage = maxDcLinkVoltage;
r.Config.GridTie.AcDc.MinDcLinkVoltage = minDcLinkVoltage;
r.Config.GridTie.DcDc.UpperDcLinkVoltage = maxDcDcLinkVoltage;
r.Config.GridTie.DcDc.LowerDcLinkVoltage = minDcDcLinkVoltage;
maxDcLinkVoltage.WriteLine("maxDcLinkVoltage");
minDcLinkVoltage.WriteLine("minxDcLinkVoltage");
maxDcDcLinkVoltage.WriteLine("maxDcDcLinkVoltage");
minDcDcLinkVoltage.WriteLine("minDcDcLinkVoltage");
}
else if (r.GridMeter.Ac.Power.Active > -limitSystemProduction + stableFactor * 2)
{
"We are closing the window".WriteLine();
r.Config.GridSetPoint = -limitSystemProduction + stableFactor;
r.Config.GridSetPoint.WriteLine(" Grid set point");
if ((r.Config.GridTie.AcDc.MaxDcLinkVoltage - r.Config.GridTie.AcDc.MinDcLinkVoltage) > 60)
{
var maxDcLinkVoltage = (UInt16)(r.Config.GridTie.AcDc.MaxDcLinkVoltage - 10);
var minDcLinkVoltage = (UInt16)(r.Config.GridTie.AcDc.MinDcLinkVoltage + 10);
var maxDcDcLinkVoltage = (UInt16)(r.Config.GridTie.DcDc.UpperDcLinkVoltage - 10);
var minDcDcLinkVoltage = (UInt16)(r.Config.GridTie.DcDc.LowerDcLinkVoltage - 10);
r.Config.GridTie.AcDc.MaxDcLinkVoltage = maxDcLinkVoltage;
r.Config.GridTie.AcDc.MinDcLinkVoltage = minDcLinkVoltage;
r.Config.GridTie.DcDc.UpperDcLinkVoltage = maxDcDcLinkVoltage;
r.Config.GridTie.DcDc.LowerDcLinkVoltage = minDcDcLinkVoltage;
maxDcLinkVoltage.WriteLine("maxDcLinkVoltage");
minDcLinkVoltage.WriteLine("minxDcLinkVoltage");
maxDcDcLinkVoltage.WriteLine("maxDcDcLinkVoltage");
minDcDcLinkVoltage.WriteLine("minDcDcLinkVoltage");
}
else
{
"do nothing".WriteLine();
}
}
else
{
r.Config.GridTie.AcDc.MaxDcLinkVoltage.WriteLine("maxDcLinkVoltage");
r.Config.GridTie.AcDc.MinDcLinkVoltage.WriteLine("minxDcLinkVoltage");
r.Config.GridTie.DcDc.UpperDcLinkVoltage.WriteLine("maxDcDcLinkVoltage");
r.Config.GridTie.DcDc.LowerDcLinkVoltage.WriteLine("minDcDcLinkVoltage");
}
}
if (exportLimit == 100)
{
r.Config.GridSetPoint = 0;
}
}
}
// why this is not in Controller?
private static void DistributePower(StatusRecord record, EssControl essControl)
{
var nInverters = record.AcDc.Devices.Count;
var powerPerInverterPhase = nInverters > 0
? essControl.PowerSetpoint / nInverters / 3
: 0;
record.AcDc.Devices.ForEach(d =>
{
d.Control.Ac.PhaseControl = PhaseControl.Asymmetric;
d.Control.Ac.Power.L1 = powerPerInverterPhase;
d.Control.Ac.Power.L2 = powerPerInverterPhase;
d.Control.Ac.Power.L3 = powerPerInverterPhase;
});
}
private static void ApplyAcDcDefaultSettings(this SystemControlRegisters? sc)
{
if (sc is null)
return;
sc.ReferenceFrame = ReferenceFrame.Consumer;
sc.SystemConfig = AcDcAndDcDc;
#if DEBUG
sc.CommunicationTimeout = TimeSpan.FromMinutes(2);
#else
sc.CommunicationTimeout = TimeSpan.FromSeconds(20);
#endif
sc.PowerSetPointActivation = PowerSetPointActivation.Immediate;
sc.UseSlaveIdForAddressing = true;
sc.SlaveErrorHandling = SlaveErrorHandling.Relaxed;
sc.SubSlaveErrorHandling = SubSlaveErrorHandling.Off;
sc.ResetAlarmsAndWarnings = true;
}
private static void ApplyDcDcDefaultSettings(this SystemControlRegisters? sc)
{
if (sc is null)
return;
sc.SystemConfig = DcDcOnly;
#if DEBUG
sc.CommunicationTimeout = TimeSpan.FromMinutes(2);
#else
sc.CommunicationTimeout = TimeSpan.FromSeconds(20);
#endif
sc.PowerSetPointActivation = PowerSetPointActivation.Immediate;
sc.UseSlaveIdForAddressing = true;
sc.SlaveErrorHandling = SlaveErrorHandling.Relaxed;
sc.SubSlaveErrorHandling = SubSlaveErrorHandling.Off;
sc.ResetAlarmsAndWarnings = true;
}
private static async Task<Boolean> UploadCsv(StatusRecord status, DateTime timeStamp)
{
var csv = status.ToCsv().LogInfo();
await RestApiSavingfile(csv);
var s3Config = status.Config.S3;
if (s3Config is null)
return false;
//Concatenating 15 files in one file
return await ConcatinatingAndCompressingFiles(timeStamp, s3Config);
}
private static async Task<Boolean> ConcatinatingAndCompressingFiles(DateTime timeStamp, S3Config s3Config)
{
if (_counterOfFile >= NbrOfFileToConcatenate)
{
_counterOfFile = 0;
var logFileConcatenator = new LogFileConcatenator();
var csvToSend = logFileConcatenator.ConcatenateFiles(NbrOfFileToConcatenate);
var s3Path = timeStamp.ToUnixTime() + ".csv";
var request = s3Config.CreatePutRequest(s3Path);
"Sending to S3".WriteLine();
//Use this for no compression
//var response = await request.PutAsync(new StringContent(csv));
var compressedBytes = CompresseBytes(csvToSend);
// Encode the compressed byte array as a Base64 string
string base64String = Convert.ToBase64String(compressedBytes);
// Create StringContent from Base64 string
var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
// Upload the compressed data (ZIP archive) to S3
var response = await request.PutAsync(stringContent);
if (response.StatusCode != 200)
{
Console.WriteLine("ERROR: PUT");
var error = await response.GetStringAsync();
Console.WriteLine(error);
return false;
}
}
_counterOfFile++;
return true;
}
private static Byte[] CompresseBytes(String csvToSend)
{
//Compress CSV data to a byte array
byte[] compressedBytes;
using (var memoryStream = new MemoryStream())
{
//Create a zip directory and put the compressed file inside
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var entry = archive.CreateEntry("data.csv", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
using (var entryStream = entry.Open())
using (var writer = new StreamWriter(entryStream))
{
writer.Write(csvToSend);
}
}
compressedBytes = memoryStream.ToArray();
}
return compressedBytes;
}
private static async Task RestApiSavingfile(String csv)
{
// This is for the Rest API
// Check if the directory exists, and create it if it doesn't
const String directoryPath = "/var/www/html";
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
string filePath = Path.Combine(directoryPath, "status.csv");
await File.WriteAllTextAsync(filePath, csv.SplitLines().Where(l => !l.Contains("Secret")).JoinLines());
}
private static Boolean IsPowerOfTwo(Int32 n)
{
return n > 0 && (n & (n - 1)) == 0;
}
private static void ApplyConfigFile(this StatusRecord status, Configuration? config)
{
status.Config.MinSoc = config.MinimumSoC;
status.Config.GridSetPoint = config.GridSetPoint * 1000; // converted from kW to W
status.Config.ForceCalibrationChargeState = config.CalibrationChargeState;
if (config.CalibrationChargeState == CalibrationChargeType.RepetitivelyEvery)
{
status.Config.DayAndTimeForRepetitiveCalibration = config.CalibrationChargeDate;
}
else if (config.CalibrationChargeState == CalibrationChargeType.AdditionallyOnce)
{
status.Config.DayAndTimeForAdditionalCalibration = config.CalibrationChargeDate;
}
}
}