From 85b248dc6f92a6069bef00edd484d1f1b892b18d Mon Sep 17 00:00:00 2001 From: Noe Date: Mon, 18 Dec 2023 16:09:43 +0100 Subject: [PATCH] update daily data aggregator --- .../src/AggregationService/Aggregator.cs | 25 +- .../src/AggregationService/HourlyData.cs | 36 ++ csharp/App/SaliMax/src/Program.cs | 532 +++++++++--------- .../dashboards/Overview/chartOptions.tsx | 329 +++++++---- .../content/dashboards/Overview/overview.tsx | 182 +++--- 5 files changed, 606 insertions(+), 498 deletions(-) diff --git a/csharp/App/SaliMax/src/AggregationService/Aggregator.cs b/csharp/App/SaliMax/src/AggregationService/Aggregator.cs index e3026b264..421f55966 100644 --- a/csharp/App/SaliMax/src/AggregationService/Aggregator.cs +++ b/csharp/App/SaliMax/src/AggregationService/Aggregator.cs @@ -54,7 +54,6 @@ public static class Aggregator // Output the time until the next rounded hour Console.WriteLine("Waiting for " + timeUntilNextDay.TotalHours + " hours..."); Console.WriteLine("-----------------------------------------------------------------------------------------------------------------"); - // Wait until the next rounded hour await Task.Delay(timeUntilNextDay); @@ -63,8 +62,15 @@ public static class Aggregator { try { - AggregatedData dailyAggregatedData = CreateAverage("HourlyData",DateTime.Now.AddDays(-1).ToUnixTime(),DateTime.Now.ToUnixTime()); + var currentTime = DateTime.Now; + AggregatedData dailyAggregatedData = CreateAverage("HourlyData",currentTime.AddDays(-1).ToUnixTime(),currentTime.ToUnixTime()); dailyAggregatedData.Save("DailyData"); + if (await dailyAggregatedData.PushToS3()) + { + DeleteHourlyData("HourlyData",currentTime.ToUnixTime()); + dailyAggregatedData.DeleteDailyData("DailyData"); + } + } catch (Exception e) { @@ -73,6 +79,21 @@ public static class Aggregator await Task.Delay(TimeSpan.FromDays(1)); } } + + private static void DeleteHourlyData(String myDirectory, Int64 beforeTimestamp) + { + var csvFiles = Directory.GetFiles(myDirectory, "*.csv"); + Console.WriteLine("Delete data before"+beforeTimestamp); + foreach (var csvFile in csvFiles) + { + if (IsFileWithinTimeRange(csvFile, 0, beforeTimestamp)) + { + File.Delete(csvFile); + Console.WriteLine($"Deleted hourly data file: {csvFile}"); + } + } + } + private static AggregatedData CreateAverage(String myDirectory, Int64 afterTimestamp, Int64 beforeTimestamp) { diff --git a/csharp/App/SaliMax/src/AggregationService/HourlyData.cs b/csharp/App/SaliMax/src/AggregationService/HourlyData.cs index e2666cf8d..13c8c912e 100644 --- a/csharp/App/SaliMax/src/AggregationService/HourlyData.cs +++ b/csharp/App/SaliMax/src/AggregationService/HourlyData.cs @@ -1,5 +1,7 @@ using System.Text.Json; +using Flurl.Http; using InnovEnergy.App.SaliMax.Devices; +using InnovEnergy.App.SaliMax.SystemConfig; using InnovEnergy.Lib.Units; using InnovEnergy.Lib.Utils; using static System.Text.Json.JsonSerializer; @@ -13,6 +15,8 @@ public class AggregatedData public required Double AvgSoc { get; set; } public required Double AvgPvPower { get; set; } public required Double BatteryPowerAverage { get; set; } + + private readonly S3Config? _S3Config = Config.Load().S3; public void Save(String directory) { @@ -39,6 +43,38 @@ public class AggregatedData } } + public void DeleteDailyData(String directory) + { + + var csvFiles = Directory.GetFiles(directory, "*.csv"); + foreach (var csvFile in csvFiles) + { + File.Delete(csvFile); + Console.WriteLine($"Deleted daily data file: {csvFile}"); + } + } + + public async Task PushToS3() + { + var csv = this.ToCsv(); + if (_S3Config is null) + return false; + + var s3Path = DateTime.Now.ToString("yyyy-MM-dd") + ".csv"; + var request = _S3Config.CreatePutRequest(s3Path); + var response = await request.PutAsync(new StringContent(csv)); + + if (response.StatusCode != 200) + { + Console.WriteLine("ERROR: PUT"); + var error = await response.GetStringAsync(); + Console.WriteLine(error); + return false; + } + + return true; + } + // public static HourlyData? Load(String dataFilePath) // { // try diff --git a/csharp/App/SaliMax/src/Program.cs b/csharp/App/SaliMax/src/Program.cs index 611f8d8e8..763390bbb 100644 --- a/csharp/App/SaliMax/src/Program.cs +++ b/csharp/App/SaliMax/src/Program.cs @@ -19,7 +19,6 @@ 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 InnovEnergy.App.SaliMax.AggregationService; using InnovEnergy.App.SaliMax.DataTypes; using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig; @@ -33,56 +32,56 @@ namespace InnovEnergy.App.SaliMax; internal static class Program { private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(2); - + private static readonly IReadOnlyList BatteryNodes; - - private static readonly Channel TruConvertAcChannel ; - private static readonly Channel TruConvertDcChannel ; - private static readonly Channel GridMeterChannel ; + + 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 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 static Boolean _subscribedToQueue = false; + private static Boolean _subscribeToQueueForTheFirstTime = false; + private static SalimaxAlarmState _prevSalimaxState = SalimaxAlarmState.Green; + private static Int32 _heartBitInterval = 0; + static Program() { var config = Config.Load(); var d = config.Devices; - Channel CreateChannel(SalimaxDevice device) => device.DeviceState == DeviceState.Disabled - ? new NullChannel() - : new TcpChannel(device); + 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); + 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()) - .ToArray(config.Devices.BatteryNodes.Length); + 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()) + .ToArray(config.Devices.BatteryNodes.Length); } public static async Task Main(String[] args) { //Do not await - Aggregator.HourlyDataAggregationManager().ContinueWith(t=>t.Exception.WriteLine(), TaskContinuationOptions.OnlyOnFaulted).SupressAwaitWarning(); - Aggregator.DailyDataAggregationManager().ContinueWith(t=>t.Exception.WriteLine(), TaskContinuationOptions.OnlyOnFaulted).SupressAwaitWarning(); + Aggregator.HourlyDataAggregationManager().ContinueWith(t => t.Exception.WriteLine(), TaskContinuationOptions.OnlyOnFaulted).SupressAwaitWarning(); + Aggregator.DailyDataAggregationManager().ContinueWith(t => t.Exception.WriteLine(), TaskContinuationOptions.OnlyOnFaulted).SupressAwaitWarning(); MiddlewareAgent.InitializeCommunicationToMiddleware(); while (true) { @@ -93,81 +92,80 @@ internal static class Program catch (Exception e) { e.LogError(); - } + } } } - 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); + .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 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 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); + ? 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); + ? 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}; + 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, + 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, + 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 + 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 }; } @@ -185,23 +183,23 @@ internal static class Program while (true) { await Observable - .Interval(UpdateInterval) - .Select(_ => RunIteration()) - .SelectMany(r => UploadCsv(r, DateTime.Now.Round(UpdateInterval))) - .SelectError() - .ToTask(); + .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); + + SendSalimaxStateAlarm(currentSalimaxState, record); record.ControlConstants(); record.ControlSystemState(); @@ -219,16 +217,16 @@ internal static class Program $"{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.Relays is null ? "No relay Data available" : record.Relays.FiError ? "Alert: Fi Error Detected" : "No Fi Error Detected").WriteLine(); //record.ApplyConfigFile(minSoc:22, gridSetPoint:1); - + record.Config.Save(); - + "===========================================".LogInfo(); return record; @@ -236,20 +234,21 @@ internal static class Program // ReSharper disable once FunctionNeverReturns } - + public static void SendSalimaxStateAlarm(StatusMessage currentSalimaxState, StatusRecord record) { var s3Bucket = Config.Load().S3?.Bucket; - + //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) + if (_subscribeToQueueForTheFirstTime == false) { _subscribeToQueueForTheFirstTime = true; _subscribedToQueue = RabbitMqManager.SubscribeToQueue(currentSalimaxState, s3Bucket, VpnServerIp); } + //If already subscribed to the queue and the status has been changed, update the queue if (_subscribedToQueue && currentSalimaxState.Status != _prevSalimaxState) { @@ -257,13 +256,13 @@ internal static class Program if (s3Bucket != null) RabbitMqManager.InformMiddleware(currentSalimaxState); } - else if (_subscribedToQueue && _heartBitInterval>=15) + else if (_subscribedToQueue && _heartBitInterval >= 15) { //Send a heartbit to the backend Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------"); _heartBitInterval = 0; currentSalimaxState.Type = MessageType.Heartbit; - + if (s3Bucket != null) RabbitMqManager.InformMiddleware(currentSalimaxState); } @@ -274,16 +273,15 @@ internal static class Program { record.ApplyConfigFile(config); } - } private static StatusMessage GetSalimaxStateAlarm(StatusRecord record) { - var alarmCondition = record.DetectAlarmStates(); - var s3Bucket = Config.Load().S3?.Bucket; + var alarmCondition = record.DetectAlarmStates(); + var s3Bucket = Config.Load().S3?.Bucket; - var alarmList = new List(); - var warningList = new List(); + var alarmList = new List(); + var warningList = new List(); if (alarmCondition is not null) { @@ -297,148 +295,147 @@ internal static class Program 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() - }); - } - if (record.Battery != null) - { - var i = 0; - - foreach (var battery in record.Battery.Devices) - { - i++; - 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" + i, - Description = alarm - }); - } - } - - } - - 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() - }); - } + 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() + }); + } - if (record.Battery != null) - { - 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", - Description = warning - }); - } - } + 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() + }); + } - 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 + if (record.Battery != null) + { + var i = 0; - 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 + foreach (var battery in record.Battery.Devices) + { + i++; + 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" + i, + Description = alarm + }); + } + } + } - int.TryParse(s3Bucket?.Split("-")[0], out var installationId); + 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() + }); + } - var returnedStatus = new StatusMessage - { - InstallationId = installationId, - Status = salimaxAlarmsState, - Type = MessageType.AlarmOrWarning, - Alarms = alarmList, - Warnings = warningList - }; + 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() + }); + } - return returnedStatus; + if (record.Battery != null) + { + 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", + Description = warning + }); + } + } + + 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 + + int.TryParse(s3Bucket?.Split("-")[0], out var installationId); + + var returnedStatus = new StatusMessage + { + InstallationId = installationId, + 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 + { 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 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); + 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(); @@ -449,11 +446,11 @@ internal static class Program private static void DistributePower(StatusRecord record, EssControl essControl) { var nInverters = record.AcDc.Devices.Count; - + var powerPerInverterPhase = nInverters > 0 - ? essControl.PowerSetpoint / nInverters / 3 - : 0; - + ? essControl.PowerSetpoint / nInverters / 3 + : 0; + record.AcDc.Devices.ForEach(d => { d.Control.Ac.PhaseControl = PhaseControl.Asymmetric; @@ -467,74 +464,71 @@ internal static class Program { 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.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; + 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.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; + sc.SlaveErrorHandling = SlaveErrorHandling.Relaxed; + sc.SubSlaveErrorHandling = SubSlaveErrorHandling.Off; + sc.ResetAlarmsAndWarnings = true; } private static async Task UploadCsv(StatusRecord status, DateTime timeStamp) { var s3Config = status.Config.S3; - var csv = status.ToCsv().LogInfo(); + var csv = status.ToCsv().LogInfo(); if (s3Config is null) return false; - var s3Path = timeStamp.ToUnixTime() + ".csv"; - var request = s3Config.CreatePutRequest(s3Path); + 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; + + return true; } 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.MinSoc = config.MinimumSoC; + status.Config.GridSetPoint = config.GridSetPoint * 1000; // converted from kW to W status.Config.ForceCalibrationCharge = config.ForceCalibrationCharge; } - } \ No newline at end of file diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx index c142bf34c..42e98559c 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx @@ -3,125 +3,230 @@ import { ApexOptions } from 'apexcharts'; import { chartInfoInterface } from 'src/interfaces/Chart'; import { findPower, formatPowerForGraph } from 'src/Resources/formatPower'; -import { addHours, format } from 'date-fns'; -export const getChartOptions = (chartInfo: chartInfoInterface): ApexOptions => { - // Custom datetime formatter for GMT+2 - const customDatetimeFormatter = (timestamp, options) => { - const gmtDate = new Date(timestamp); // Convert Unix timestamp to milliseconds - const gmtPlus2Date = addHours(gmtDate, 4); // Add 2 hours to convert to GMT+2 +export const getChartOptions = ( + chartInfo: chartInfoInterface, + type: string +): ApexOptions => { + const chartOptions: ApexOptions = + type == 'daily' + ? { + chart: { + id: 'area-datetime', + toolbar: { + show: false + }, + type: 'area', + height: 350, + zoom: { + autoScaleYaxis: false + } + }, - // Use the specified options to format the date and time - const year = format(gmtDate, 'yyyy'); - const month = format(gmtDate, "MMM 'yy"); - const day = format(gmtDate, 'dd MMM'); - const hour = format(gmtDate, 'HH:mm'); - const minute = format(gmtDate, 'mm'); + dataLabels: { + enabled: false + }, - // Return the formatted date and time based on the provided options - return ` ${hour}:${minute}`; - }; + fill: { + type: 'gradient', + gradient: { + shadeIntensity: 1, + opacityFrom: 0.7, + opacityTo: 0.9, + stops: [0, 100] + } + }, + //colors: ['#FF5733', '#3498db'], + colors: ['#3498db', '#2ecc71'], + //colors: ['#1abc9c', '#e91e63'], + xaxis: { + type: 'datetime', + labels: { + datetimeFormatter: { + year: 'yyyy', + month: "MMM 'yy", + day: 'dd MMM', + hour: 'HH:mm', + minute: 'mm' + } + } + }, + stroke: { + curve: 'smooth', + width: 2 + }, + yaxis: { + min: + chartInfo.min >= 0 + ? 0 + : chartInfo.max <= 0 + ? Math.ceil(chartInfo.min / findPower(chartInfo.min).value) * + findPower(chartInfo.min).value + : undefined, + max: + chartInfo.min >= 0 + ? Math.ceil(chartInfo.max / findPower(chartInfo.max).value) * + findPower(chartInfo.max).value + : chartInfo.max <= 0 + ? 0 + : undefined, + title: { + text: chartInfo.unit, + style: { + fontSize: '12px' + }, + offsetY: -160, + offsetX: 25, + rotate: 0 + }, + labels: { + formatter: function (value: number) { + return formatPowerForGraph( + value, + Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) + ).value.toString(); + } + } + }, - const chartOptions: ApexOptions = { - chart: { - id: 'area-datetime', - toolbar: { - show: false - }, - type: 'area', - height: 350, - zoom: { - autoScaleYaxis: false - } - }, - // markers: { - // size: 1, - // strokeColors: 'black' - // }, - dataLabels: { - enabled: false - }, - - fill: { - type: 'gradient', - gradient: { - shadeIntensity: 1, - opacityFrom: 0.7, - opacityTo: 0.9, - stops: [0, 100] - } - }, - //colors: ['#FF5733', '#3498db'], - colors: ['#3498db', '#2ecc71'], - //colors: ['#1abc9c', '#e91e63'], - xaxis: { - type: 'datetime', - labels: { - datetimeFormatter: { - year: 'yyyy', - month: "MMM 'yy", - day: 'dd MMM', - hour: 'HH:mm', - minute: 'mm' + tooltip: { + x: { + format: 'dd MMM HH:mm:ss' + }, + y: { + formatter: function (val, opts) { + return ( + formatPowerForGraph( + val, + Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) + ).value.toFixed(2) + + ' ' + + chartInfo.unit + ); + } + } + } } - } - }, - stroke: { - curve: 'smooth', - width: 2 - }, - yaxis: { - min: - chartInfo.min >= 0 - ? 0 - : chartInfo.max <= 0 - ? Math.ceil(chartInfo.min / findPower(chartInfo.min).value) * - findPower(chartInfo.min).value - : undefined, - max: - chartInfo.min >= 0 - ? Math.ceil(chartInfo.max / findPower(chartInfo.max).value) * - findPower(chartInfo.max).value - : chartInfo.max <= 0 - ? 0 - : undefined, - title: { - text: chartInfo.unit, - style: { - fontSize: '12px' - }, - offsetY: -160, - offsetX: 25, - rotate: 0 - }, - labels: { - formatter: function (value: number) { - return formatPowerForGraph( - value, - Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) - ).value.toString(); - } - } - }, + : type == 'monthly' + ? { + chart: { + height: 350, + type: 'bar' + }, + plotOptions: { + bar: { + borderRadius: 10, + dataLabels: { + position: 'top' // top, center, bottom + } + } + }, + dataLabels: { + enabled: true, + formatter: function (val) { + return val + '%'; + }, + offsetY: -20, + style: { + fontSize: '12px', + colors: ['#304758'] + } + }, - tooltip: { - x: { - format: 'dd MMM HH:mm:ss' - }, - y: { - formatter: function (val, opts) { - return ( - formatPowerForGraph( - val, - Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) - ).value.toFixed(2) + - ' ' + - chartInfo.unit - ); + xaxis: { + categories: [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec' + ], + position: 'bottom', + axisBorder: { + show: false + }, + axisTicks: { + show: false + }, + tooltip: { + enabled: true + } + }, + yaxis: { + axisBorder: { + show: false + }, + axisTicks: { + show: false + }, + labels: { + show: false, + formatter: function (val) { + return val + '%'; + } + } + } } - } - } - }; + : { + chart: { + height: 350, + type: 'bar' + }, + plotOptions: { + bar: { + borderRadius: 10, + dataLabels: { + position: 'top' // top, center, bottom + } + } + }, + dataLabels: { + enabled: true, + formatter: function (val) { + return val + '%'; + }, + offsetY: -20, + style: { + fontSize: '12px', + colors: ['#304758'] + } + }, + + xaxis: { + categories: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + position: 'bottom', + axisBorder: { + show: false + }, + axisTicks: { + show: false + }, + tooltip: { + enabled: true + } + }, + yaxis: { + axisBorder: { + show: false + }, + axisTicks: { + show: false + }, + labels: { + show: false, + formatter: function (val) { + return val + '%'; + } + } + } + }; return chartOptions; }; diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx index 48b5b54ca..91eacf4ff 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx @@ -1,11 +1,4 @@ -import { - Box, - Card, - Container, - Grid, - Typography, - useTheme -} from '@mui/material'; +import { Box, Card, Container, Grid, Typography } from '@mui/material'; import ReactApexChart from 'react-apexcharts'; import React, { useEffect, useMemo, useState } from 'react'; import DataCache from 'src/dataCache/dataCache'; @@ -25,7 +18,6 @@ import { chartDataInterface, overviewInterface } from 'src/interfaces/Chart'; import { fetchData } from 'src/content/dashboards/Installations/fetchData'; import Button from '@mui/material/Button'; import { FormattedMessage } from 'react-intl'; -import { ApexOptions } from 'apexcharts'; const prefixes = ['', 'k', 'M', 'G', 'T']; const MAX_NUMBER = 9999999; @@ -35,7 +27,6 @@ interface OverviewProps { } function Overview(props: OverviewProps) { - const theme = useTheme(); const numOfPointsToFetch = 100; const [timeRange, setTimeRange] = useState( createTimes( @@ -80,6 +71,9 @@ function Overview(props: OverviewProps) { }, []); const times$ = useMemo(() => new BehaviorSubject(timeRange), []); + const [dailyData, setDailyData] = useState(true); + const [weeklyData, setWeeklyData] = useState(false); + const [monthlyData, setMonthlyData] = useState(false); const transformToGraphData = ( input: RecordSeries @@ -157,12 +151,10 @@ function Overview(props: OverviewProps) { Math.abs(overviewData[path].max), Math.abs(overviewData[path].min) ); - let negative = false; let magnitude = 0; if (value < 0) { value = -value; - negative = true; } while (value >= 1000) { value /= 1000; @@ -289,6 +281,9 @@ function Overview(props: OverviewProps) { }; const handle24HourData = () => { + setDailyData(true); + setWeeklyData(false); + setMonthlyData(false); const times = createTimes( UnixTime.now().rangeBefore(TimeSpan.fromDays(1)), numOfPointsToFetch @@ -298,21 +293,28 @@ function Overview(props: OverviewProps) { }; const handleWeekData = () => { - const times = createTimes( - UnixTime.now().rangeBefore(TimeSpan.fromWeeks(1)), - numOfPointsToFetch - ); - cache.getSeries(times); - times$.next(times); + setDailyData(false); + setWeeklyData(true); + setMonthlyData(false); + //fetchData(12312,props.s3Credentials); + // const times = createTimes( + // UnixTime.now().rangeBefore(TimeSpan.fromWeeks(1)), + // numOfPointsToFetch + // ); + // cache.getSeries(times); + // times$.next(times); }; const handleMonthData = () => { - const times = createTimes( - UnixTime.now().rangeBefore(TimeSpan.fromWeeks(4)), - numOfPointsToFetch - ); - cache.getSeries(times); - times$.next(times); + setDailyData(false); + setWeeklyData(false); + setMonthlyData(true); + // const times = createTimes( + // UnixTime.now().rangeBefore(TimeSpan.fromWeeks(4)), + // numOfPointsToFetch + // ); + // cache.getSeries(times); + // times$.next(times); }; const series = [ @@ -322,73 +324,6 @@ function Overview(props: OverviewProps) { } ]; - const state: ApexOptions = { - chart: { - height: 350, - type: 'bar' - }, - plotOptions: { - bar: { - borderRadius: 10, - dataLabels: { - position: 'top' // top, center, bottom - } - } - }, - dataLabels: { - enabled: true, - formatter: function (val) { - return val + '%'; - }, - offsetY: -20, - style: { - fontSize: '12px', - colors: ['#304758'] - } - }, - - xaxis: { - categories: [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec' - ], - position: 'bottom', - axisBorder: { - show: false - }, - axisTicks: { - show: false - }, - tooltip: { - enabled: true - } - }, - yaxis: { - axisBorder: { - show: false - }, - axisTicks: { - show: false - }, - labels: { - show: false, - formatter: function (val) { - return val + '%'; - } - } - } - }; - const renderGraphs = () => { return ( @@ -475,26 +410,43 @@ function Overview(props: OverviewProps) { > - + {dailyData && ( + + )} - {/**/} + {weeklyData && ( + + )} + + {monthlyData && ( + + )} @@ -532,7 +484,7 @@ function Overview(props: OverviewProps) {