From 6fbec18a006dc9f364d577b2d788d6464607b737 Mon Sep 17 00:00:00 2001 From: atef Date: Mon, 19 May 2025 08:36:25 +0200 Subject: [PATCH] Add the downward calibration --- .../src/DataTypes/Configuration.cs | 10 +- csharp/App/SodiStoreMax/src/Ess/Controller.cs | 96 +++++++++++++++---- csharp/App/SodiStoreMax/src/Ess/EssMode.cs | 3 +- csharp/App/SodiStoreMax/src/Program.cs | 41 +++++--- .../App/SodiStoreMax/src/System/Controller.cs | 7 +- .../SodiStoreMax/src/System/StateMachine.cs | 2 +- .../SystemConfig/CalibrationDischargeType.cs | 10 ++ .../SodiStoreMax/src/SystemConfig/Config.cs | 31 +++--- 8 files changed, 146 insertions(+), 54 deletions(-) create mode 100644 csharp/App/SodiStoreMax/src/SystemConfig/CalibrationDischargeType.cs diff --git a/csharp/App/SodiStoreMax/src/DataTypes/Configuration.cs b/csharp/App/SodiStoreMax/src/DataTypes/Configuration.cs index 8610e2601..4f9bbf860 100644 --- a/csharp/App/SodiStoreMax/src/DataTypes/Configuration.cs +++ b/csharp/App/SodiStoreMax/src/DataTypes/Configuration.cs @@ -4,9 +4,11 @@ namespace InnovEnergy.App.SodiStoreMax.DataTypes; public class Configuration { - public Double MinimumSoC { get; set; } - public Double GridSetPoint { get; set; } - public CalibrationChargeType CalibrationChargeState { get; set; } - public DateTime CalibrationChargeDate { get; set; } + public Double MinimumSoC { get; set; } + public Double GridSetPoint { get; set; } + public CalibrationChargeType CalibrationChargeState { get; set; } + public CalibrationDischargeType CalibrationDischargeState { get; set; } + public DateTime CalibrationChargeDate { get; set; } + public DateTime CalibrationDischargeDate { get; set; } } diff --git a/csharp/App/SodiStoreMax/src/Ess/Controller.cs b/csharp/App/SodiStoreMax/src/Ess/Controller.cs index e81c75607..19a025426 100644 --- a/csharp/App/SodiStoreMax/src/Ess/Controller.cs +++ b/csharp/App/SodiStoreMax/src/Ess/Controller.cs @@ -11,21 +11,23 @@ public static class Controller private static readonly Double MaxDischargePower = -8000; // By battery TODO: move to config private static readonly Double MaxChargePower = 6000; // By battery TODO: move to config - private static Boolean _hasRepetitiveCalibrationChargeChecked = false; + private static Boolean _hasRepetitiveCalibrationChargeChecked = false; + private static Boolean _hasRepetitiveCalibrationDischargeChecked = false; + private static DateTime _nextDayAt10Am = DateTime.Now; public static EssMode SelectControlMode(this StatusRecord s) { //return EssMode.OptimizeSelfConsumption; - return s.StateMachine.State != 23 ? EssMode.Off - : s.MustReachMinSoc() ? EssMode.ReachMinSoc - : s.MustDoCalibrationCharge() ? EssMode.CalibrationCharge - : s.GridMeter is null ? EssMode.NoGridMeter - : EssMode.OptimizeSelfConsumption; + return s.StateMachine.State != 23 ? EssMode.Off + : s.MustDoCalibrationCharge() ? EssMode.UpwardsCalibrationCharge + : s.MustDoDownwardsCalibrationCharge() ? EssMode.DownwardsCalibrationCharge + : s.MustReachMinSoc() ? EssMode.ReachMinSoc + : s.GridMeter is null ? EssMode.NoGridMeter + : EssMode.OptimizeSelfConsumption; } - public static EssControl ControlEss(this StatusRecord s) { var mode = s.SelectControlMode().WriteLine(); @@ -45,8 +47,9 @@ public static class Controller return EssControl.Default; } - // if we have no reading from the Grid meter, but we have a grid power (K1 is close), - // then we do only heat the battery to avoid discharging the battery and the oscillation between reach min soc and off mode + /* if we have no reading from the Grid meter, but we have a grid power (K1 is close), then we do only heat the battery + to avoid discharging the battery and the oscillation between reach min soc and off mode + This should be reconsidered for deligreen */ if (mode is EssMode.NoGridMeter) return new EssControl { @@ -116,8 +119,7 @@ public static class Controller private static EssControl LimitChargePower(this EssControl control, StatusRecord s) - { - + { //var maxInverterChargePower = s.ControlInverterPower(s.Config.MaxInverterPower); var maxBatteryChargePower = s.MaxBatteryChargePower(); maxBatteryChargePower.WriteLine(" Max Battery Charge Power"); @@ -125,7 +127,6 @@ public static class Controller return control //.LimitChargePower(, EssLimit.ChargeLimitedByInverterPower) .LimitChargePower(maxBatteryChargePower, EssLimit.ChargeLimitedByBatteryPower); - } private static EssControl LimitDischargePower(this EssControl control, StatusRecord s) @@ -134,7 +135,11 @@ public static class Controller var keepMinSocLimitDelta = s.ControlBatteryPower(s.HoldMinSocPower()); maxBatteryDischargeDelta.WriteLine(" Max Battery Discharge Power"); - + if (control.Mode == EssMode.DownwardsCalibrationCharge ) + { + return control + .LimitDischargePower(maxBatteryDischargeDelta, EssLimit.DischargeLimitedByBatteryPower); + } return control .LimitDischargePower(maxBatteryDischargeDelta , EssLimit.DischargeLimitedByBatteryPower) .LimitDischargePower(keepMinSocLimitDelta , EssLimit.DischargeLimitedByMinSoc); @@ -142,17 +147,19 @@ public static class Controller private static Double ComputePowerDelta(this StatusRecord s, EssMode mode) { - var chargePower = s.AcDc.Devices.Sum(d => d.Status.Nominal.Power.Value); + var chargePower = s.AcDc.Devices.Sum(d => d.Status.Nominal.Power.Value); + var batteryDischargePower = s.Battery?.Devices.Count * MaxDischargePower ?? 0 ; return mode switch { - EssMode.ReachMinSoc => s.ControlInverterPower(chargePower), - EssMode.OptimizeSelfConsumption => s.ControlGridPower(s.Config.GridSetPoint), - EssMode.Off => 0, - EssMode.OffGrid => 0, - EssMode.NoGridMeter => 0, - EssMode.CalibrationCharge => s.ControlInverterPower(chargePower), - _ => throw new ArgumentException(null, nameof(mode)) + EssMode.ReachMinSoc => s.ControlInverterPower(chargePower), + EssMode.OptimizeSelfConsumption => s.ControlGridPower(s.Config.GridSetPoint), + EssMode.Off => 0, + EssMode.OffGrid => 0, + EssMode.NoGridMeter => 0, + EssMode.UpwardsCalibrationCharge => s.ControlInverterPower(chargePower), + EssMode.DownwardsCalibrationCharge => s.ControlInverterPower(batteryDischargePower), + _ => throw new ArgumentException(null, nameof(mode)) }; } @@ -228,7 +235,37 @@ public static class Controller return mustDoCalibrationCharge; } + + private static Boolean MustDoDownwardsCalibrationCharge(this StatusRecord statusRecord) + { + var calibrationDischargeForced = statusRecord.Config.ForceCalibrationDischargeState; + var additionalCalibrationRequired = DownAdditionalCalibrationDateHasBeenPassed(statusRecord.Config.DownDayAndTimeForAdditionalCalibration); + var repetitiveCalibrationRequired = DownRepetitiveCalibrationDateHasBeenPassed(statusRecord.Config.DownDayAndTimeForRepetitiveCalibration); + var mustDoCalibrationDischarge = calibrationDischargeForced == CalibrationDischargeType.DischargePermanently || + (calibrationDischargeForced == CalibrationDischargeType.AdditionallyOnce && additionalCalibrationRequired) || + (calibrationDischargeForced == CalibrationDischargeType.RepetitivelyEvery && repetitiveCalibrationRequired); + + Console.WriteLine("Next Repetitive calibration charge date is "+ statusRecord.Config.DownDayAndTimeForRepetitiveCalibration); + Console.WriteLine("Next Additional calibration charge date is "+ statusRecord.Config.DownDayAndTimeForAdditionalCalibration); + + if (statusRecord.Battery is not null) + { + if (calibrationDischargeForced == CalibrationDischargeType.AdditionallyOnce && statusRecord.Battery.Eod ) + { + statusRecord.Config.ForceCalibrationDischargeState = CalibrationDischargeType.RepetitivelyEvery; + + } + else if (calibrationDischargeForced == CalibrationDischargeType.RepetitivelyEvery && statusRecord.Battery.Eod && _hasRepetitiveCalibrationDischargeChecked) + { + statusRecord.Config.DownDayAndTimeForRepetitiveCalibration = statusRecord.Config.DownDayAndTimeForRepetitiveCalibration.AddDays(7); + _hasRepetitiveCalibrationDischargeChecked = false; + } + } + + return mustDoCalibrationDischarge; + } + private static Boolean RepetitiveCalibrationDateHasBeenPassed(DateTime calibrationChargeDate) { if (DateTime.Now >= calibrationChargeDate ) @@ -247,6 +284,23 @@ public static class Controller return false; } + private static Boolean DownRepetitiveCalibrationDateHasBeenPassed(DateTime calibrationDischargeDate) + { + if (DateTime.Now >= calibrationDischargeDate ) + { + _hasRepetitiveCalibrationDischargeChecked = true; + return true; + } + return false; + } + private static Boolean DownAdditionalCalibrationDateHasBeenPassed(DateTime calibrationDischargeDate) + { + if (DateTime.Now >= calibrationDischargeDate ) + { + return true; + } + return false; + } private static Double ControlGridPower(this StatusRecord status, Double targetPower) { return ControlPower diff --git a/csharp/App/SodiStoreMax/src/Ess/EssMode.cs b/csharp/App/SodiStoreMax/src/Ess/EssMode.cs index c81c5953c..2b198728c 100644 --- a/csharp/App/SodiStoreMax/src/Ess/EssMode.cs +++ b/csharp/App/SodiStoreMax/src/Ess/EssMode.cs @@ -5,7 +5,8 @@ public enum EssMode Off, OffGrid, HeatBatteries, - CalibrationCharge, + UpwardsCalibrationCharge, + DownwardsCalibrationCharge, ReachMinSoc, NoGridMeter, OptimizeSelfConsumption diff --git a/csharp/App/SodiStoreMax/src/Program.cs b/csharp/App/SodiStoreMax/src/Program.cs index dea0b50c6..fc9fefa29 100644 --- a/csharp/App/SodiStoreMax/src/Program.cs +++ b/csharp/App/SodiStoreMax/src/Program.cs @@ -10,7 +10,6 @@ using System.Security; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; - using Flurl.Http; using InnovEnergy.App.SodiStoreMax; using InnovEnergy.App.SodiStoreMax.Devices; @@ -33,10 +32,11 @@ using InnovEnergy.Lib.Units; using InnovEnergy.Lib.Utils; using InnovEnergy.App.SodiStoreMax.DataTypes; using Newtonsoft.Json; -using static System.Int32; using static InnovEnergy.App.SodiStoreMax.AggregationService.Aggregator; using static InnovEnergy.App.SodiStoreMax.MiddlewareClasses.MiddlewareAgent; using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig; + +using Controller = InnovEnergy.App.SodiStoreMax.System.Controller; using DeviceState = InnovEnergy.App.SodiStoreMax.Devices.DeviceState; // ReSharper disable PossibleLossOfFraction @@ -60,6 +60,7 @@ internal static class Program private static readonly Channel RelaysChannel; private static readonly Channel BatteriesChannel; + // private static Boolean _curtailFlag = false; private const String VpnServerIp = "10.2.0.11"; private static Boolean _subscribedToQueue = false; @@ -69,7 +70,8 @@ internal static class Program private static UInt16 _fileCounter = 0; private static SalimaxAlarmState _salimaxAlarmState = SalimaxAlarmState.Green; private const String Port = "/dev/ttyUSB0"; // move to a config file - + private static Int32 _failsCounter = 0; // move to a config file + static Program() { var config = Config.Load(); @@ -239,15 +241,33 @@ internal static class Program record.ControlConstants(); record.ControlSystemState(); - + + record.AcDc.SystemControl.ApplyAcDcDefaultSettings(); + record.DcDc.SystemControl.ApplyDcDcDefaultSettings(); + + /* Console.WriteLine(" Fails Counter = " + _failsCounter); + + // Retries Control + if (record.StateMachine.State is not (28 or 23) ) + { + if (_failsCounter > 60) // 2 min + { + Console.WriteLine(" Fails retries reached threshold"); + record.EnableSafeDefaults(); + return record; + } + _failsCounter++; + } + else + { + _failsCounter = 0; + }*/ //record.ControlPvPower(record.Config.CurtailP, record.Config.PvInstalledPower); var essControl = record.ControlEss().WriteLine(); record.EssControl = essControl; - record.AcDc.SystemControl.ApplyAcDcDefaultSettings(); - record.DcDc.SystemControl.ApplyDcDcDefaultSettings(); DistributePower(record, essControl); @@ -310,8 +330,6 @@ internal static class Program var alarmList = new List(); var warningList = new List(); - var bAlarmList = new List(); - var bWarningList = new List(); if (record.Battery is { MonomerHighVoltageAlarm: true }) { @@ -401,7 +419,7 @@ internal static class Program private static Int32 GetInstallationId(String s3Bucket) { var part = s3Bucket.Split('-').FirstOrDefault(); - return TryParse(part, out var id) ? id : 0; // is 0 a default safe value? check with Marios + return int.TryParse(part, out var id) ? id : 0; // is 0 a default safe value? check with Marios } private static String? DetectAlarmStates(this StatusRecord r) => r.Relays switch @@ -766,7 +784,6 @@ internal static class Program { var csv = status.ToCsv(); Dictionary jsonData = new Dictionary(); - //Console.WriteLine(csv); foreach (var line in csv.Split('\n')) { @@ -842,8 +859,8 @@ internal static class Program private static void Heartbit(DateTime timeStamp) { var s3Bucket = Config.Load().S3?.Bucket; - var tryParse = TryParse(s3Bucket?.Split("-")[0], out var installationId); - var parse = TryParse(timeStamp.ToUnixTime().ToString(), out var nameOfJsonFile); + var tryParse = int.TryParse(s3Bucket?.Split("-")[0], out var installationId); + var parse = int.TryParse(timeStamp.ToUnixTime().ToString(), out var nameOfJsonFile); if (tryParse) { diff --git a/csharp/App/SodiStoreMax/src/System/Controller.cs b/csharp/App/SodiStoreMax/src/System/Controller.cs index d25171854..a156264b8 100644 --- a/csharp/App/SodiStoreMax/src/System/Controller.cs +++ b/csharp/App/SodiStoreMax/src/System/Controller.cs @@ -550,7 +550,6 @@ public static class Controller s.Relays.DisconnectIslandBusFromGrid(); return false; - // => 13 } @@ -636,7 +635,7 @@ public static class Controller //this is must be deleted private static void Disable(this DcDcDevicesRecord dcDc) { - // For Test purpose, The transition from island mode to grid tier and vis versa , may not need to disable Dc/Dc. + // For Test purpose, The transition from island mode to grid tier and vis versa may not need to disable Dc/Dc. // This will keep the Dc link powered. // dcDc.Devices @@ -687,9 +686,9 @@ public static class Controller } - private static Boolean EnableSafeDefaults(this StatusRecord s) + public static Boolean EnableSafeDefaults(this StatusRecord s) { - // After some tests, the safe state is switch off inverter and keep the last state of K2 , Dc/Dc and Grid type to avoid conflict. + // After some tests, the safe state is switch off inverter and keep the last state of K2, Dc/Dc and Grid type to avoid conflict. // s.DcDc.Disable(); s.AcDc.Disable(); // Maybe comment this to avoid opening/closing K3 diff --git a/csharp/App/SodiStoreMax/src/System/StateMachine.cs b/csharp/App/SodiStoreMax/src/System/StateMachine.cs index 6e0d498ec..839dcf09b 100644 --- a/csharp/App/SodiStoreMax/src/System/StateMachine.cs +++ b/csharp/App/SodiStoreMax/src/System/StateMachine.cs @@ -5,5 +5,5 @@ public record StateMachine public required String Message { get; set; } // TODO: init only public required Int32 State { get; set; } // TODO: init only - public static StateMachine Default { get; } = new StateMachine { State = 100, Message = "Unknown State" }; + public static StateMachine Default { get; } = new StateMachine { State = 100, Message = "Default State" }; } \ No newline at end of file diff --git a/csharp/App/SodiStoreMax/src/SystemConfig/CalibrationDischargeType.cs b/csharp/App/SodiStoreMax/src/SystemConfig/CalibrationDischargeType.cs new file mode 100644 index 000000000..7bc4c5a9a --- /dev/null +++ b/csharp/App/SodiStoreMax/src/SystemConfig/CalibrationDischargeType.cs @@ -0,0 +1,10 @@ +namespace InnovEnergy.App.SodiStoreMax.SystemConfig; + +public enum CalibrationDischargeType +{ + RepetitivelyEvery, + AdditionallyOnce, + DischargePermanently +} + + diff --git a/csharp/App/SodiStoreMax/src/SystemConfig/Config.cs b/csharp/App/SodiStoreMax/src/SystemConfig/Config.cs index 110db56cb..a63736b12 100644 --- a/csharp/App/SodiStoreMax/src/SystemConfig/Config.cs +++ b/csharp/App/SodiStoreMax/src/SystemConfig/Config.cs @@ -18,9 +18,15 @@ public class Config //TODO: let IE choose from config files (Json) and connect t public required Double MinSoc { get; set; } public required UInt16 CurtailP { get; set; }// in Kw public required UInt16 PvInstalledPower { get; set; }// in Kw + public required CalibrationChargeType ForceCalibrationChargeState { get; set; } public required DateTime DayAndTimeForRepetitiveCalibration { get; set; } public required DateTime DayAndTimeForAdditionalCalibration { get; set; } + + public required CalibrationDischargeType ForceCalibrationDischargeState { get; set; } + public required DateTime DownDayAndTimeForRepetitiveCalibration { get; set; } + public required DateTime DownDayAndTimeForAdditionalCalibration { get; set; } + public required Boolean DisplayIndividualBatteries { get; set; } public required Double PConstant { get; set; } public required Double GridSetPoint { get; set; } @@ -126,17 +132,20 @@ public class Config //TODO: let IE choose from config files (Json) and connect t #else public static Config Default => new() { - MinSoc = 20, - CurtailP = 0, - PvInstalledPower = 20, - ForceCalibrationChargeState = CalibrationChargeType.RepetitivelyEvery, - DayAndTimeForRepetitiveCalibration = DefaultDatetime, - DayAndTimeForAdditionalCalibration = DefaultDatetime, - DisplayIndividualBatteries = false, - PConstant = .5, - GridSetPoint = 0, - BatterySelfDischargePower = 200, - HoldSocZone = 1, // TODO: find better name, + MinSoc = 20, + CurtailP = 0, + PvInstalledPower = 20, + ForceCalibrationChargeState = CalibrationChargeType.RepetitivelyEvery, + DayAndTimeForRepetitiveCalibration = DefaultDatetime, + DayAndTimeForAdditionalCalibration = DefaultDatetime, + ForceCalibrationDischargeState = CalibrationDischargeType.RepetitivelyEvery, + DownDayAndTimeForAdditionalCalibration = DefaultDatetime, + DownDayAndTimeForRepetitiveCalibration = DefaultDatetime, + DisplayIndividualBatteries = false, + PConstant = .5, + GridSetPoint = 0, + BatterySelfDischargePower = 200, + HoldSocZone = 1, // TODO: find better name, IslandMode = new() { AcDc = new ()