641 lines
26 KiB
C#
641 lines
26 KiB
C#
using System.Diagnostics.CodeAnalysis;
|
|
using System.Net;
|
|
using System.Net.NetworkInformation;
|
|
using System.Net.Sockets;
|
|
using System.Reactive.Linq;
|
|
using System.Reactive.Threading.Tasks;
|
|
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 System.Text.Json;
|
|
using RabbitMQ.Client;
|
|
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 = "194.182.190.208";
|
|
|
|
private static IPAddress? _controllerIpAddress;
|
|
private static UdpClient _udpListener = null!;
|
|
private static ConnectionFactory? _factory ;
|
|
private static IConnection ? _connection;
|
|
private static IModel? _channel;
|
|
private static Boolean _subscribedToQueue = false;
|
|
private static Boolean _subscribeToQueueForTheFirstTime = false;
|
|
private static SalimaxAlarmState _prevSalimaxState = SalimaxAlarmState.Green;
|
|
|
|
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)
|
|
{
|
|
while (true)
|
|
{
|
|
//CreateAverage();
|
|
try
|
|
{
|
|
InitializeCommunicationToMiddleware();
|
|
await Run();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
e.LogError();
|
|
}
|
|
}
|
|
// ReSharper disable once FunctionNeverReturns
|
|
}
|
|
|
|
private static void InitializeCommunicationToMiddleware()
|
|
{
|
|
_controllerIpAddress = FindVpnIp();
|
|
if (Equals(IPAddress.None, _controllerIpAddress))
|
|
{
|
|
Console.WriteLine("There is no VPN interface, exiting...");
|
|
}
|
|
|
|
const Int32 udpPort = 9000;
|
|
var endPoint = new IPEndPoint(_controllerIpAddress, udpPort);
|
|
|
|
_udpListener = new UdpClient();
|
|
_udpListener.Client.Blocking = false;
|
|
_udpListener.Client.Bind(endPoint);
|
|
}
|
|
|
|
private static async Task Run()
|
|
{
|
|
"Starting SaliMax".LogInfo();
|
|
|
|
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);
|
|
var saliMaxRelaysDevice = new RelaysDevice(RelaysChannel);
|
|
|
|
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 }, //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)
|
|
saliMaxRelaysDevice.Write(r.Relays);
|
|
|
|
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();
|
|
|
|
var currentSalimaxState = GetSalimaxStateAlarm(record);
|
|
|
|
SendSalimaxStateAlarm(currentSalimaxState);
|
|
|
|
record.ControlConstants();
|
|
record.ControlSystemState();
|
|
|
|
var essControl = record.ControlEss().WriteLine().LogInfo();
|
|
|
|
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()
|
|
.LogInfo();
|
|
|
|
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();
|
|
|
|
"===========================================".LogInfo();
|
|
|
|
return record;
|
|
}
|
|
|
|
// ReSharper disable once FunctionNeverReturns
|
|
}
|
|
|
|
private static void SendSalimaxStateAlarm(SalimaxAlarmState currentSalimaxState)
|
|
{
|
|
var s3Bucket = Config.Load().S3?.Bucket;
|
|
|
|
//When the controller boots, it tries to subscribe to the queue
|
|
if (_subscribeToQueueForTheFirstTime==false)
|
|
{
|
|
_subscribeToQueueForTheFirstTime = true;
|
|
SubscribeToQueue(currentSalimaxState, s3Bucket);
|
|
|
|
if (_subscribedToQueue && currentSalimaxState != _prevSalimaxState)
|
|
{
|
|
_prevSalimaxState = currentSalimaxState;
|
|
}
|
|
}
|
|
//If already subscribed to the queue and the status has been changed, update the queue
|
|
else if (_subscribedToQueue && currentSalimaxState != _prevSalimaxState)
|
|
{
|
|
_prevSalimaxState = currentSalimaxState;
|
|
if (s3Bucket != null)
|
|
InformMiddleware(s3Bucket, (Int32)currentSalimaxState);
|
|
}
|
|
|
|
//If there is an available message from the RabbitMQ Broker, subscribe to the queue
|
|
if (_udpListener.Available > 0)
|
|
{
|
|
IPEndPoint? serverEndpoint = null;
|
|
|
|
var replyMessage = "ACK";
|
|
var replyData = Encoding.UTF8.GetBytes(replyMessage);
|
|
|
|
var udpMessage = _udpListener.Receive(ref serverEndpoint);
|
|
var message = Encoding.UTF8.GetString(udpMessage);
|
|
|
|
Console.WriteLine($"Received a message: {message}");
|
|
|
|
// Send the reply to the sender's endpoint
|
|
_udpListener.Send(replyData, replyData.Length, serverEndpoint);
|
|
Console.WriteLine($"Replied to {serverEndpoint}: {replyMessage}");
|
|
|
|
SubscribeToQueue(currentSalimaxState, s3Bucket);
|
|
}
|
|
}
|
|
|
|
private static void SubscribeToQueue(SalimaxAlarmState currentSalimaxState, String? s3Bucket)
|
|
{
|
|
try
|
|
{
|
|
_factory = new ConnectionFactory { HostName = VpnServerIp };
|
|
_connection = _factory.CreateConnection();
|
|
_channel = _connection.CreateModel();
|
|
|
|
_channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
|
|
|
|
Console.WriteLine("The controller sends its status to the middleware for the first time");
|
|
|
|
if (s3Bucket != null) InformMiddleware(s3Bucket, (Int32)currentSalimaxState);
|
|
|
|
_subscribedToQueue = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("An error occurred while connecting to the RabbitMQ queue: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
private static IPAddress FindVpnIp()
|
|
{
|
|
const String interfaceName = "innovenergy";
|
|
|
|
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
|
|
|
|
foreach (var networkInterface in networkInterfaces)
|
|
{
|
|
if (networkInterface.Name == interfaceName)
|
|
{
|
|
var ipProps = networkInterface.GetIPProperties();
|
|
var uniCastIPs = ipProps.UnicastAddresses;
|
|
var controllerIpAddress = uniCastIPs[0].Address;
|
|
|
|
Console.WriteLine("VPN IP is: "+ uniCastIPs[0].Address);
|
|
return controllerIpAddress;
|
|
}
|
|
}
|
|
|
|
return IPAddress.None;
|
|
}
|
|
|
|
private static void InformMiddleware(String? bucket, int status)
|
|
{
|
|
int.TryParse(bucket[0].ToString(), out var installationId);
|
|
var jsonObject = new StatusMessage
|
|
{
|
|
InstallationId = installationId,
|
|
Status = status,
|
|
};
|
|
|
|
if (status == 2)
|
|
{
|
|
jsonObject.CreatedAt = DateTime.Now;
|
|
jsonObject.Description = "Battery Temperature High";
|
|
jsonObject.CreatedBy = "Battery/1";
|
|
}
|
|
else if (status == 1)
|
|
{
|
|
jsonObject.CreatedAt = DateTime.Now;
|
|
jsonObject.Description = "Temp warning message";
|
|
jsonObject.CreatedBy = "Battery/4";
|
|
}
|
|
|
|
var message = JsonSerializer.Serialize(jsonObject);
|
|
var body = Encoding.UTF8.GetBytes(message);
|
|
|
|
_channel.BasicPublish(exchange: string.Empty,
|
|
routingKey: "statusQueue",
|
|
basicProperties: null,
|
|
body: body);
|
|
|
|
Console.WriteLine($"Producer sent message: {message}");
|
|
}
|
|
|
|
private static SalimaxAlarmState GetSalimaxStateAlarm(StatusRecord record)
|
|
{
|
|
var alarmCondition = record.DetectAlarmStates();
|
|
|
|
if (alarmCondition is not null)
|
|
{
|
|
alarmCondition.LogInfo();
|
|
}
|
|
|
|
// record.Log = new SystemLog
|
|
// {
|
|
// Led = alarmCondition is null ? LedState.Green : LedState.Red,
|
|
// Message = alarmCondition
|
|
// };
|
|
|
|
var salimaxAlarmsState = (record.Battery is not null && record.Battery.Warnings.Any())
|
|
| record.AcDc.Warnings.Any()
|
|
| record.AcDc.SystemControl.Warnings.Any()
|
|
| record.DcDc.Warnings.Any()
|
|
? SalimaxAlarmState.Orange
|
|
: SalimaxAlarmState.Green; // this will be replaced by LedState
|
|
|
|
salimaxAlarmsState = (record.Battery is not null && record.Battery.Alarms.Any())
|
|
| record.AcDc.Alarms.Any()
|
|
| record.AcDc.SystemControl.Alarms.Any()
|
|
| record.DcDc.Alarms.Any()
|
|
| alarmCondition is not null
|
|
? SalimaxAlarmState.Red
|
|
: salimaxAlarmsState; // this will be replaced by LedState
|
|
|
|
|
|
return salimaxAlarmsState;
|
|
}
|
|
|
|
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 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 = configFile.MaxBatteryDischargingCurrent);
|
|
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();
|
|
}
|
|
|
|
|
|
// 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 s3Config = status.Config.S3;
|
|
var csv = status.ToCsv().LogInfo();
|
|
if (s3Config is null)
|
|
return false;
|
|
|
|
var s3Path = timeStamp.ToUnixTime() + ".csv";
|
|
var request = s3Config.CreatePutRequest(s3Path);
|
|
var response = await request.PutAsync(new StringContent(csv));
|
|
|
|
// This is temporary for Wittman
|
|
//await File.WriteAllTextAsync("/var/www/html/status.csv", csv.SplitLines().Where(l => !l.Contains("Secret")).JoinLines());
|
|
|
|
if (response.StatusCode != 200)
|
|
{
|
|
Console.WriteLine("ERROR: PUT");
|
|
var error = await response.GetStringAsync();
|
|
Console.WriteLine(error);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void CreateAverage()
|
|
{
|
|
string myDirectory = "LogDirectoryNew";
|
|
List<Dictionary<string, string>> csvDataList = new List<Dictionary<string, string>>();
|
|
|
|
// Get all CSV files in the specified directory
|
|
string[] csvFiles = Directory.GetFiles(myDirectory, "*.csv");
|
|
|
|
List<Double> socList = new List<Double>();
|
|
List<Double> pvPowerList = new List<Double>();
|
|
List<Double> batteryPowerList = new List<Double>();
|
|
|
|
foreach (var csvFile in csvFiles)
|
|
{
|
|
using (var reader = new StreamReader(csvFile))
|
|
{
|
|
// Read the CSV file and store data in dictionary
|
|
Dictionary<string, string> csvData = new Dictionary<string, string>();
|
|
|
|
while (!reader.EndOfStream)
|
|
{
|
|
var line = reader.ReadLine();
|
|
var values = line?.Split(';');
|
|
|
|
// Assuming there are always three columns (variable name and its value)
|
|
if (values is { Length: 3 })
|
|
{
|
|
String variableName = values[0].Trim();
|
|
String variableValue = values[1].Trim();
|
|
|
|
// Check if variableValue is a valid number
|
|
if (IsSoc(variableName))
|
|
{
|
|
// Add to the dictionary only if variableValue is a number
|
|
socList.Add(double.TryParse(variableValue, out double v)? v: 0);
|
|
}
|
|
if (IsPvPower(variableName))
|
|
{
|
|
// Add to the dictionary only if variableValue is a number
|
|
pvPowerList.Add(double.TryParse(variableValue, out double v)? v: 0);
|
|
}
|
|
if (IsBatteryPower(variableName))
|
|
{
|
|
// Add to the dictionary only if variableValue is a number
|
|
batteryPowerList.Add(double.TryParse(variableValue, out double v)? v: 0);
|
|
}
|
|
else
|
|
{
|
|
// Handle cases where variableValue is not a valid number
|
|
// Console.WriteLine($"Invalid numeric value for variable {variableName}: {variableValue}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Handle invalid CSV format
|
|
//Console.WriteLine($"Invalid format in file: {csvFile}");
|
|
//break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
double socAverage = CalculateAverage(socList);
|
|
double pvPowerAverage = CalculateAverage(pvPowerList);
|
|
double batteryPowerAverage = CalculateAverage(batteryPowerList);
|
|
|
|
// Print the stored CSV data for verification
|
|
|
|
Console.WriteLine($"SOC: {socAverage}");
|
|
Console.WriteLine($"PvPower: {pvPowerAverage}");
|
|
Console.WriteLine($"Battery: {batteryPowerAverage}");
|
|
|
|
Console.WriteLine("----------");
|
|
|
|
Console.WriteLine("CSV data reading and storage completed.");
|
|
}
|
|
|
|
// Custom method to check if a string is numeric
|
|
private static bool IsSoc(string value)
|
|
{
|
|
return value == "/Battery/Soc";
|
|
}
|
|
|
|
private static bool IsPvPower(string value)
|
|
{
|
|
return value == "/PvOnDc/Dc/Power";
|
|
}
|
|
|
|
private static bool IsBatteryPower(string value)
|
|
{
|
|
return value == "/Battery/Dc/Power";
|
|
}
|
|
|
|
|
|
// Method to calculate average for a variableValue in a dictionary
|
|
static double CalculateAverage( List<Double> data)
|
|
{
|
|
// Calculate and return the moving average
|
|
double movingAverage = data.Average();
|
|
return movingAverage;
|
|
}
|
|
} |