From eeaefb0f54934e265bfc41a8b7025c43b2c1d119 Mon Sep 17 00:00:00 2001 From: ig Date: Tue, 13 Jun 2023 12:53:17 +0200 Subject: [PATCH] Salimax V2 --- csharp/App/BmsTunnel/BmsTunnel.cs | 2 +- csharp/App/SaliMax/SaliMax.csproj | 34 +- .../App/SaliMax/run (BeagleBone Meiringen).sh | 37 -- csharp/App/SaliMax/src/AsciiArt.cs | 156 +++--- .../src/Controller/AvgBatteriesStatus.cs | 56 -- csharp/App/SaliMax/src/Controller/Control.cs | 47 -- .../SaliMax/src/Controller/ControlRecord.cs | 17 - .../SaliMax/src/Controller/ControlTarget.cs | 10 - .../App/SaliMax/src/Controller/Controller.cs | 514 ------------------ .../SaliMax/src/Controller/SaliMaxState.cs | 14 - csharp/App/SaliMax/src/Controller/State.cs | 30 - .../App/SaliMax/src/Controller/StateConfig.cs | 7 - .../SaliMax/src/Controller/StatusRecord.cs | 23 - csharp/App/SaliMax/src/Ess/ControlRecord.cs | 15 + csharp/App/SaliMax/src/Ess/Controller.cs | 242 +++++++++ csharp/App/SaliMax/src/Ess/EssControl.cs | 14 + csharp/App/SaliMax/src/Ess/EssLimit.cs | 18 + csharp/App/SaliMax/src/Ess/EssMode.cs | 11 + csharp/App/SaliMax/src/Ess/StatusRecord.cs | 24 + csharp/App/SaliMax/src/Logger.cs | 20 + csharp/App/SaliMax/src/Program.cs | 318 ++++++----- .../src/SaliMaxRelays/RelayMapBoolean.cs | 7 - .../SaliMax/src/SaliMaxRelays/RelaysDevice.cs | 41 ++ .../SaliMax/src/SaliMaxRelays/RelaysRecord.cs | 54 ++ .../src/SaliMaxRelays/SaliMaxRelaysDevice.cs | 46 -- .../src/SaliMaxRelays/SaliMaxRelaysStatus.cs | 8 - csharp/App/SaliMax/src/System/Controller.cs | 470 ++++++++++++++++ csharp/App/SaliMax/src/System/SystemState.cs | 7 + csharp/App/SaliMax/src/SystemConfig/Config.cs | 89 ++- .../App/SaliMax/src/SystemConfig/Defaults.cs | 243 +++++---- .../SaliMax/src/SystemConfig/SalimaxConfig.cs | 65 --- csharp/App/SaliMax/src/Topology.cs | 241 -------- csharp/App/SaliMax/tunnels.sh | 41 -- csharp/App/SaliMax/tunneltoProto.sh | 43 -- 34 files changed, 1399 insertions(+), 1565 deletions(-) delete mode 100755 csharp/App/SaliMax/run (BeagleBone Meiringen).sh delete mode 100644 csharp/App/SaliMax/src/Controller/AvgBatteriesStatus.cs delete mode 100644 csharp/App/SaliMax/src/Controller/Control.cs delete mode 100644 csharp/App/SaliMax/src/Controller/ControlRecord.cs delete mode 100644 csharp/App/SaliMax/src/Controller/ControlTarget.cs delete mode 100644 csharp/App/SaliMax/src/Controller/Controller.cs delete mode 100644 csharp/App/SaliMax/src/Controller/SaliMaxState.cs delete mode 100644 csharp/App/SaliMax/src/Controller/State.cs delete mode 100644 csharp/App/SaliMax/src/Controller/StateConfig.cs delete mode 100644 csharp/App/SaliMax/src/Controller/StatusRecord.cs create mode 100644 csharp/App/SaliMax/src/Ess/ControlRecord.cs create mode 100644 csharp/App/SaliMax/src/Ess/Controller.cs create mode 100644 csharp/App/SaliMax/src/Ess/EssControl.cs create mode 100644 csharp/App/SaliMax/src/Ess/EssLimit.cs create mode 100644 csharp/App/SaliMax/src/Ess/EssMode.cs create mode 100644 csharp/App/SaliMax/src/Ess/StatusRecord.cs create mode 100644 csharp/App/SaliMax/src/Logger.cs delete mode 100644 csharp/App/SaliMax/src/SaliMaxRelays/RelayMapBoolean.cs create mode 100644 csharp/App/SaliMax/src/SaliMaxRelays/RelaysDevice.cs create mode 100644 csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecord.cs delete mode 100644 csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysDevice.cs delete mode 100644 csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysStatus.cs create mode 100644 csharp/App/SaliMax/src/System/Controller.cs create mode 100644 csharp/App/SaliMax/src/System/SystemState.cs delete mode 100644 csharp/App/SaliMax/src/SystemConfig/SalimaxConfig.cs delete mode 100644 csharp/App/SaliMax/src/Topology.cs delete mode 100755 csharp/App/SaliMax/tunnels.sh delete mode 100644 csharp/App/SaliMax/tunneltoProto.sh diff --git a/csharp/App/BmsTunnel/BmsTunnel.cs b/csharp/App/BmsTunnel/BmsTunnel.cs index bb3999280..b01632292 100644 --- a/csharp/App/BmsTunnel/BmsTunnel.cs +++ b/csharp/App/BmsTunnel/BmsTunnel.cs @@ -102,7 +102,7 @@ public class BmsTunnel : IDisposable { // TODO: this should go into outer loop instead of returning magic value CrcError - Console.WriteLine(BitConverter.ToString(response).Replace("-", " ")); + //Console.WriteLine(BitConverter.ToString(response).Replace("-", " ")); return CrcError; } diff --git a/csharp/App/SaliMax/SaliMax.csproj b/csharp/App/SaliMax/SaliMax.csproj index ba80db36b..8c66b470f 100644 --- a/csharp/App/SaliMax/SaliMax.csproj +++ b/csharp/App/SaliMax/SaliMax.csproj @@ -6,24 +6,24 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/csharp/App/SaliMax/run (BeagleBone Meiringen).sh b/csharp/App/SaliMax/run (BeagleBone Meiringen).sh deleted file mode 100755 index e98675532..000000000 --- a/csharp/App/SaliMax/run (BeagleBone Meiringen).sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - - -dotnet_version='net6.0' - -set -e - -echo -e "\n============================ Build ============================\n" - -dotnet publish \ - ./SaliMax.csproj \ - -c Release \ - -r linux-x64 - -echo -e "\n============================ Deploy ============================\n" - -rsync -v \ - ./bin/Release/$dotnet_version/linux-x64/publish/* \ - ie-entwicklung@10.2.3.49:~/salimax - - - # debian@10.2.1.87:~/salimax - -echo -e "\n============================ Restart Salimax sevice ============================\n" - -ssh -tt \ - ie-entwicklung@10.2.3.49 \ - sudo systemctl restart salimax.service - - -echo -e "\n============================ Print service output ============================\n" - -ssh -tt \ - ie-entwicklung@10.2.3.49 \ - journalctl -f -u salimax.service - - diff --git a/csharp/App/SaliMax/src/AsciiArt.cs b/csharp/App/SaliMax/src/AsciiArt.cs index 9e0b47545..8896d0487 100644 --- a/csharp/App/SaliMax/src/AsciiArt.cs +++ b/csharp/App/SaliMax/src/AsciiArt.cs @@ -1,79 +1,77 @@ -using InnovEnergy.Lib.Units; -using InnovEnergy.Lib.Utils; - -namespace InnovEnergy.App.SaliMax; - -public static class AsciiArt -{ - - public static String CreateBox(params Object[] elements) - { - var aligned = elements - .Select(e => e.ToString()!) - .JoinLines() - .AlignLeft(); - - var w = aligned.Width(); - - var line = "".PadRight(w + 2, '─'); - var top = "┌" + line + "┐"; - var bottom = "└" + line + "┘"; - - return aligned - .SplitLines() - .Select(l => l.SurroundWith(" ")) - .Select(l => l.SurroundWith("│")) - .Prepend(top) - .Append(bottom) - .JoinLines(); - } - - public static String CreateHorizontalArrow(Decimal value, String separator) - { - var valueToString = " " + value.W(); - - if (value == 0) - { - valueToString = ""; - } - - var contentWidth = separator.Length; - - var horizontal = "".PadRight(contentWidth, ' '); - - var v = valueToString.PadRight(contentWidth); - var s = separator.PadRight(contentWidth); - - return StringUtils.JoinLines( - horizontal, - v, - s, - horizontal - ); - } - - public static String CreateTransitionPadLeft(String value, String separator) - { - var contentWidth = separator.Length + 2; - - var horizontal = "".PadLeft(contentWidth, ' '); - - var v = value.PadLeft(contentWidth); - var s = separator.PadLeft(contentWidth); - - return StringUtils.JoinLines( - horizontal, - v, - s, - horizontal - ); - } - - public static String CreateVerticalArrow(Decimal power, Int32 width = 0) - { - var flow = "V".NewLine() + "V".NewLine() + power.W().NewLine() + "V".NewLine() + "V"; - - return flow.AlignCenterHorizontal(width); - } - -} \ No newline at end of file +// using InnovEnergy.Lib.Utils; +// +// namespace InnovEnergy.App.SaliMax; +// +// public static class AsciiArt +// { +// +// public static String CreateBox(params Object[] elements) +// { +// var aligned = elements +// .Select(e => e.ToString()!) +// .JoinLines() +// .AlignLeft(); +// +// var w = aligned.Width(); +// +// var line = "".PadRight(w + 2, '─'); +// var top = "┌" + line + "┐"; +// var bottom = "└" + line + "┘"; +// +// return aligned +// .SplitLines() +// .Select(l => l.SurroundWith(" ")) +// .Select(l => l.SurroundWith("│")) +// .Prepend(top) +// .Append(bottom) +// .JoinLines(); +// } +// +// public static String CreateHorizontalArrow(Decimal value, String separator) +// { +// var valueToString = " " + value.W(); +// +// var contentWidth = separator.Length; +// +// var horizontal = "".PadRight(contentWidth, ' '); +// +// var v = valueToString.PadRight(contentWidth); +// var s = separator.PadRight(contentWidth); +// +// return StringUtils.JoinLines( +// horizontal, +// horizontal, +// horizontal, +// v, +// s, +// horizontal, +// horizontal, +// horizontal +// ); +// } +// +// public static String CreateTransitionPadLeft(String value, String separator) +// { +// var contentWidth = separator.Length + 2; +// +// var horizontal = "".PadLeft(contentWidth, ' '); +// +// var v = value.PadLeft(contentWidth); +// var s = separator.PadLeft(contentWidth); +// +// return StringUtils.JoinLines( +// horizontal, +// v, +// s, +// horizontal +// ); +// } +// +// public static String CreateVerticalArrow(Decimal power, Int32 width = 0) +// { +// var flow = "V".NewLine() + "V".NewLine() + power.W().NewLine() + "V".NewLine() + "V"; +// +// return flow.AlignCenterHorizontal(width); +// } +// +// } \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Controller/AvgBatteriesStatus.cs b/csharp/App/SaliMax/src/Controller/AvgBatteriesStatus.cs deleted file mode 100644 index 151c0c5af..000000000 --- a/csharp/App/SaliMax/src/Controller/AvgBatteriesStatus.cs +++ /dev/null @@ -1,56 +0,0 @@ -using InnovEnergy.Lib.Devices.Battery48TL; -using InnovEnergy.Lib.StatusApi; -using InnovEnergy.Lib.Units; -using InnovEnergy.Lib.Units.Composite; -using static InnovEnergy.Lib.Devices.Battery48TL.TemperatureState; - -namespace InnovEnergy.App.SaliMax.Controller; - -public static class AvgBatteriesStatus -{ - public static CombinedStatus? Combine(this IReadOnlyList stati) - { - var combined = stati.Count == 0 - ? null - : new Battery48TLStatus - { - Soc = stati.Min(b => b.Soc), - Temperature = stati.Average(b => b.Temperature), - Dc = new DcBus - { - Voltage = stati.Average(b => b.Dc.Voltage), - Current = stati.Sum(b => b.Dc.Current), - }, - - Alarms = stati.SelectMany(b => b.Alarms).Distinct().ToList(), - Warnings = stati.SelectMany(b => b.Warnings).Distinct().ToList(), - - MaxChargingPower = stati.Sum(b => b.MaxChargingPower), - MaxDischargingPower = stati.Sum(b => b.MaxDischargingPower), - - Heating = stati.Any(b => b.Heating), - - AmberLed = LedState.Off, // not used for combined battery - BlueLed = LedState.Off, - RedLed = LedState.Off, - GreenLed = LedState.Off, - - CellsVoltage = stati.Average(b => b.CellsVoltage), - ConnectedToDc = stati.Any(b => b.ConnectedToDc), - - TemperatureState = stati.Any(b => b.TemperatureState == OperatingTemperature) // TODO: revisit when we have the overheated state - ? OperatingTemperature - : Cold, - - TotalCurrent = stati.Average(b => b.TotalCurrent), - - EocReached = stati.All(b => b.EocReached), - }; - - return new CombinedStatus - { - Combined = combined!, - Children = stati - }; - } -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Controller/Control.cs b/csharp/App/SaliMax/src/Controller/Control.cs deleted file mode 100644 index 7564a5f81..000000000 --- a/csharp/App/SaliMax/src/Controller/Control.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace InnovEnergy.App.SaliMax.Controller; - -public static class Control -{ - - public static Decimal ControlGridPower(this StatusRecord status, Decimal targetPower) - { - return ControlPower(status.GridMeterStatus!.Ac.ActivePower, targetPower, status.SalimaxConfig!.PConstant); - } - - public static Decimal ControlInverterPower(this StatusRecord status, Decimal targetInverterPower) - { - var s = status.InverterStatus!; - var totalInverterAcPower = s.Ac.ActivePower; - - return ControlPower(totalInverterAcPower, targetInverterPower,status.SalimaxConfig!.PConstant); - } - - public static Decimal ControlBatteryPower(this StatusRecord status, Decimal targetBatteryPower, UInt16 i = 0) //this will use the avg batteries - { - return ControlPower(status.BatteriesStatus!.Combined.Dc.Power, targetBatteryPower, status.SalimaxConfig!.PConstant); - } - - public static Decimal ControlLowBatterySoc(this StatusRecord status) - { - return ControlBatteryPower(status, HoldMinSocCurve(status)); - } - - public static Decimal LowerLimit(params Decimal[] deltas) => deltas.Max(); - public static Decimal UpperLimit(params Decimal[] deltas) => deltas.Min(); - - private static Decimal HoldMinSocCurve(StatusRecord s) - { - // TODO: explain LowSOC curve - - var a = -2 * s.SalimaxConfig!.SelfDischargePower / s.SalimaxConfig.HoldSocZone; - var b = -a * (s.SalimaxConfig.MinSoc + s.SalimaxConfig.HoldSocZone); - - return s.BatteriesStatus!.Combined.Soc * a + b; //this will use the avg batteries - } - - private static Decimal ControlPower(Decimal measurement, Decimal target, Decimal p) - { - var error = target - measurement; - return error * p; - } -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Controller/ControlRecord.cs b/csharp/App/SaliMax/src/Controller/ControlRecord.cs deleted file mode 100644 index ce7e4fc8f..000000000 --- a/csharp/App/SaliMax/src/Controller/ControlRecord.cs +++ /dev/null @@ -1,17 +0,0 @@ -using InnovEnergy.App.SaliMax.SaliMaxRelays; -using InnovEnergy.App.SaliMax.SystemConfig; -using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; -using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; - -namespace InnovEnergy.App.SaliMax.Controller; - -public class ControlRecord -{ - public TruConvertAcControl? AcControlRecord { get; init; } - public TruConvertDcControl? DcControlRecord { get; init; } - public SalimaxConfig? SalimaxConfig { get; init; } // we may have to create record of each - public SaliMaxRelayStatus? SalimaxRelays { get; init; } // we may have to create record of each -} - - - diff --git a/csharp/App/SaliMax/src/Controller/ControlTarget.cs b/csharp/App/SaliMax/src/Controller/ControlTarget.cs deleted file mode 100644 index 1e279f8d3..000000000 --- a/csharp/App/SaliMax/src/Controller/ControlTarget.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace InnovEnergy.App.SaliMax.Controller; - -public enum ControlTarget // TODO to delete -{ - None = 0, - GridAc = 1, - BatteryDc = 2, - InverterAc = 3, - InverterDc = 4, -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Controller/Controller.cs b/csharp/App/SaliMax/src/Controller/Controller.cs deleted file mode 100644 index 5638da544..000000000 --- a/csharp/App/SaliMax/src/Controller/Controller.cs +++ /dev/null @@ -1,514 +0,0 @@ -using InnovEnergy.App.SaliMax.SaliMaxRelays; -using InnovEnergy.App.SaliMax.SystemConfig; -using InnovEnergy.Lib.Devices.Trumpf.TruConvert; -using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; -using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; -using InnovEnergy.Lib.Time.Unix; -using InnovEnergy.Lib.Utils; - -using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.Enums; - -using static InnovEnergy.App.SaliMax.SaliMaxRelays.RelayState; - - -namespace InnovEnergy.App.SaliMax.Controller; - -public static class Controller -{ - private static readonly UnixTimeSpan MaxTimeWithoutEoc = UnixTimeSpan.FromDays(7); - - private static Boolean _mustChargeFlag = false; - - private static readonly TimeSpan CommunicationTimeout = TimeSpan.FromSeconds(10); - - public static readonly Int16 MaxmimumAllowedBatteryTemp = 315; - - private static UInt16 _numberOfInverters; - - private static UInt16 GetSaliMaxState(StatusRecord statusRecord) - { - if (statusRecord.SaliMaxRelayStatus is null) - throw new ArgumentNullException(nameof(SaliMaxRelayStatus) + " is not available"); //TODO - - if (statusRecord.InverterStatus is null) - throw new ArgumentNullException(nameof(statusRecord.InverterStatus) + " is not available"); //TODO - - return statusRecord switch - { - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, - InverterStatus.MainState: not MainState.Operation - } => 1, - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, - InverterStatus.MainState: not MainState.Operation - } => 2, - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, - InverterStatus.MainState: not MainState.Operation - } => 3, - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, - InverterStatus.MainState: not MainState.Operation - } => 4, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, - InverterStatus.MainState: not MainState.Operation - } => 5, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, - InverterStatus.MainState: not MainState.Operation - } => 6, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, - InverterStatus.MainState: not MainState.Operation - } => 7, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, - InverterStatus.MainState: not MainState.Operation - } => 8, - - //Grid-Tied 400V/50 Hz - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, - InverterStatus.GridType: AcDcGridType.GridTied400V50Hz - } => 9, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, - InverterStatus.GridType: AcDcGridType.GridTied400V50Hz - } => 10, - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, - InverterStatus.GridType: AcDcGridType.GridTied400V50Hz - } => 11, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, - InverterStatus.GridType: AcDcGridType.GridTied400V50Hz - } => 12, - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, - InverterStatus.GridType: AcDcGridType.GridTied400V50Hz - } => 13, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, - InverterStatus.GridType: AcDcGridType.GridTied400V50Hz - } => 14, - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, - InverterStatus.GridType: AcDcGridType.GridTied400V50Hz - } => 15, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, - InverterStatus.GridType: AcDcGridType.GridTied400V50Hz - } => 16, - - //Island 400V / 50Hz - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, - InverterStatus.GridType: AcDcGridType.Island400V50Hz - } => 17, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Open, - InverterStatus.GridType: AcDcGridType.Island400V50Hz - } => 18, - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, - InverterStatus.GridType: AcDcGridType.Island400V50Hz - } => 19, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Open, - InverterStatus.GridType: AcDcGridType.Island400V50Hz - } => 20, - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, //this is wrong - InverterStatus.GridType: AcDcGridType.Island400V50Hz - } => 21, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Open, SaliMaxRelayStatus.K3: Closed, - InverterStatus.GridType: AcDcGridType.Island400V50Hz - } => 22, - { - SaliMaxRelayStatus.K1: Open, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, - InverterStatus.GridType: AcDcGridType.Island400V50Hz - } => 23, - { - SaliMaxRelayStatus.K1: Closed, SaliMaxRelayStatus.K2: Closed, SaliMaxRelayStatus.K3: Closed, - InverterStatus.GridType: AcDcGridType.Island400V50Hz - } => 24, - - - _ => throw new ArgumentOutOfRangeException(nameof(statusRecord), statusRecord, null) - }; - } - - public static ControlRecord SaliMaxControl(StatusRecord statusRecord) - { - var currentSaliMaxState = GetSaliMaxState(statusRecord); - - UInt16 acSlaveId = 1; - var resetInverterAlarm = CheckInverterAlarms(statusRecord, currentSaliMaxState).WriteLine(" reset Alarm"); - var resetDcAlarm = CheckDcDcAlarms(statusRecord); - - var lastEocTime = GetLastEocTime(statusRecord); - var timeSinceLastEoc = UnixTime.Now - lastEocTime; - - _numberOfInverters = (UInt16)statusRecord.InverterStatus!.NumberOfConnectedSlaves ; - _mustChargeFlag = timeSinceLastEoc > MaxTimeWithoutEoc; - - var noGridMeter = statusRecord.GridMeterStatus == null; - var saliMaxConfig = statusRecord.SalimaxConfig with { LastEoc = lastEocTime }; - - ExplainState(currentSaliMaxState); - - const RelayState k2Relay = Closed; - - var acPowerStageEnable = StateConfig.AcPowerStageEnableStates.Contains(currentSaliMaxState); //this is logical incorrect, find better way - - var dcPowerStageEnable = statusRecord.BatteriesStatus is not null; // TODO this is to check, Can be the batteries Status be null? - - var salimaxRelay = statusRecord.SaliMaxRelayStatus! with { K2 = k2Relay }; // to check // this is must be control - - if (resetInverterAlarm) - { - acPowerStageEnable = !resetInverterAlarm ; - acSlaveId = 0; - } - - if (resetDcAlarm) - { - dcPowerStageEnable = !resetDcAlarm ; - } - - acSlaveId.WriteLine(" AcSlave @"); - - if (statusRecord.BatteriesStatus == null) - { - Console.WriteLine(" No batteries"); - return new ControlRecord - { - AcControlRecord = Defaults.TruConvertAcControl with - { - SignedPowerNominalValue = 0, - PowerStageEnable = acPowerStageEnable, - CommunicationTimeout = CommunicationTimeout, - SlaveAddress = acSlaveId, - ResetsAlarmAndWarning = resetInverterAlarm - }, - DcControlRecord = Defaults.TruConvertDcControl with - { - PowerStageEnable = dcPowerStageEnable, - ResetsAlarmAndWarning = resetDcAlarm, - TimeoutForCommunication = CommunicationTimeout - - }, - SalimaxConfig = saliMaxConfig, // must create a control of each - SalimaxRelays = salimaxRelay, // must create a control of each - }; - } - - if (noGridMeter) - { - // Blackout ( no grid meter and K1 opened automatically - if (statusRecord.SaliMaxRelayStatus?.K1 == Open) - { - Console.WriteLine("Blackout occured"); - //FromGridTieToIsland(); - } - // Grid meter not detected ( broken) - else - { - Console.WriteLine("Grid meter not detected"); - } - - throw new NotImplementedException(); - } - - var newPowerSetPoint = CalculateNewPowerSetPoint(statusRecord); - - ////////////////////////// Control Record ////////////////////////// - - var acControlRecord = Defaults.TruConvertAcControl with - { - PowerStageEnable = acPowerStageEnable, - CommunicationTimeout = CommunicationTimeout, - SignedPowerNominalValue = newPowerSetPoint, - SlaveAddress = acSlaveId, - ResetsAlarmAndWarning = resetInverterAlarm - }; - - var dcControlRecord = Defaults.TruConvertDcControl with - { - PowerStageEnable = dcPowerStageEnable, - ResetsAlarmAndWarning = resetDcAlarm, - TimeoutForCommunication = CommunicationTimeout - }; - - - return new ControlRecord - { - AcControlRecord = acControlRecord, - DcControlRecord = dcControlRecord, - SalimaxConfig = saliMaxConfig, - SalimaxRelays = salimaxRelay - }; - } - - private static Decimal CalculateNewPowerSetPoint(StatusRecord statusRecord) - { - var currentPowerSetPoint = statusRecord.InverterStatus!.AcSignedPowerValue; - - var limitReason = "no limit"; - var goal = "no goal"; - var delta = 0m; - - if (_mustChargeFlag) - { - goal = "Calibration Charge"; - delta = statusRecord.ControlInverterPower(statusRecord.SalimaxConfig.MaxInverterPower); - } - else if (statusRecord.BatteriesStatus!.Combined.Soc < statusRecord.SalimaxConfig.MinSoc) // TODO - { - goal = $"reach min SOC (Min soc: {statusRecord.SalimaxConfig.MinSoc})"; - delta = statusRecord.ControlInverterPower(statusRecord.SalimaxConfig - .MaxInverterPower); // this the new mustChargeToMinSoc - } - else - { - goal = $"optimize self consumption (Grid set point: {statusRecord.SalimaxConfig.GridSetPoint})"; - delta = statusRecord.ControlGridPower(statusRecord.SalimaxConfig.GridSetPoint); - } - - ////////////////////////// Upper Limits ////////////////////////// - - var inverterAc2DcLimitPower = statusRecord.ControlInverterPower(statusRecord.SalimaxConfig.MaxInverterPower); - - if (delta > inverterAc2DcLimitPower) - { - limitReason = "limited by max inverter Ac to Dc power"; - delta = inverterAc2DcLimitPower; - } - - var batteryChargingLimitPower = statusRecord.ControlBatteryPower(statusRecord.BatteriesStatus!.Combined.MaxChargingPower); - - if (delta > batteryChargingLimitPower) - { - limitReason = "limited by max battery charging power"; - delta = batteryChargingLimitPower; - } - - ////////////////////////// Lower Limits ////////////////////////// - - var inverterDc2AcLimitPower = statusRecord.ControlInverterPower(-statusRecord.SalimaxConfig.MaxInverterPower); - - if (delta < inverterDc2AcLimitPower) - { - limitReason = $"limited by max inverter Dc to Ac power: {-statusRecord.SalimaxConfig.MaxInverterPower}W"; - delta = inverterDc2AcLimitPower; - } - - var batteryDischargingLimitPower = - statusRecord.ControlBatteryPower(statusRecord.BatteriesStatus.Combined.MaxDischargingPower); // TODO change to avg battery - - if (delta < batteryDischargingLimitPower) - { - limitReason = - $"limited by max battery discharging power: {statusRecord.BatteriesStatus.Combined.MaxDischargingPower}";// TODO change to avg battery - - delta = batteryDischargingLimitPower; - } - - var keepMinSocLimitDelta = statusRecord.ControlLowBatterySoc(); - if (delta < keepMinSocLimitDelta) - { - limitReason = - $"limiting discharging power in order to stay above min SOC: {statusRecord.SalimaxConfig.MinSoc}%"; - delta = keepMinSocLimitDelta; - } - - // if (statusRecord.BatteriesStatus[0]!.Temperature >= 300) //must not reduce the delta - // { - // var softLandingFactor = (MaxmimumAllowedBatteryTemp - statusRecord.BatteriesStatus[0]!.Temperature) / 15; //starting softlanding from 300 degree - // limitReason = - // $"limiting discharging power in order to stay keep the battery temp below 315°: {statusRecord.BatteriesStatus[0]!.Temperature}°" + " Softlanding factor: " + softLandingFactor; - // delta *= softLandingFactor; - // } - - var newPowerSetPoint = - DistributePower(currentPowerSetPoint + delta, statusRecord.SalimaxConfig.MaxInverterPower); - - ////////////////////// Print Data for Debug purpose ////////////////////////// - - // - goal.WriteLine(); - limitReason.WriteLine(" Limit reason"); - delta.WriteLine(" Delta"); - // "============".WriteLine(); - return newPowerSetPoint; - } - - private static State TargetState(State currentState) - { - return currentState switch - { - State.State1 => State.State17, - State.State17 => State.State21, - State.State21 => State.State22, - State.State22 => State.State6, - State.State6 => State.State2, - State.State2 => State.State4, - State.State4 => State.State12, - State.State12 => State.State16, - State.State16 => State.State15, - State.State15 => State.State13, - State.State13 => State.State9, - State.State9 => State.State1, - _ => throw new Exception("Unknown State!") // maybe not throwing an exception, instead write on the log file - }; - } - - - private static void ExplainState(UInt16 s) - { - Console.WriteLine("State: " + s); - switch (s) - { - case 1: - Console.WriteLine(" Inverter is Off"); - Console.WriteLine(" Turning on power stage of inverter"); - Console.WriteLine(" grid type = island 400V / 50Hz"); - break; - case 17: - Console.WriteLine(" Waiting for K3 to close"); - break; - case 21: - Console.WriteLine(" Inverter is in Island Mode"); - Console.WriteLine(" Waiting for K1 to close to leave it"); - break; - case 22: - Console.WriteLine(" K1 is closed"); - Console.WriteLine(" Turning off power stage of inverter"); - break; - case 6: - Console.WriteLine(" Waiting for K3 to open"); - break; - case 2: - Console.WriteLine(" K3 is open"); - Console.WriteLine(" Closing the K2"); - break; - case 4: - Console.WriteLine(" K2 is Closed"); - Console.WriteLine(" Turning on power stage of inverter"); - Console.WriteLine(" grid type = grid-tied 400V / 50Hz"); - break; - case 12: - Console.WriteLine(" Waiting for K3 to close"); - break; - case 16: - Console.WriteLine(" Inverter is in grid-tie"); - Console.WriteLine(" Waiting for K1 to open to leave it"); - break; - case 15: - Console.WriteLine(" K1 is open"); - Console.WriteLine(" Opening the K2"); - break; - case 13: - Console.WriteLine(" K2 is open"); - Console.WriteLine(" Waiting for K3 to open"); - break; - case 9: - Console.WriteLine(" K3 is open"); - Console.WriteLine(" Turning off power stage of inverter"); - break; - default: - Console.WriteLine("Unknown State!"); - File.AppendAllTextAsync(Config.LogSalimaxLog, String.Join(Environment.NewLine, UnixTime.Now + "Unknown State!")); - break; - } - } - - - public static void WriteControlRecord(ControlRecord controlRecord, - TruConvertAcDevice acDevice, - TruConvertDcDevice dcDevice, - SaliMaxRelaysDevice saliMaxRelaysDevice) - { - controlRecord.SalimaxConfig?.Save(); - - var acControlRecord = controlRecord.AcControlRecord; - var dcControlRecord = controlRecord.DcControlRecord; - - if (acControlRecord != null && dcControlRecord != null) - { - acDevice.WriteControl(acControlRecord); - - dcDevice.WriteControl(dcControlRecord); - } - else - { - Console.WriteLine("AcControl and DcControl record is empty"); - File.AppendAllTextAsync(Config.LogSalimaxLog, String.Join(Environment.NewLine, UnixTime.Now + "AcControl and DcControl record is empty!")); - } - } - - private static UnixTime GetLastEocTime(StatusRecord statusRecord) - { if (statusRecord.BatteriesStatus != null) - { - if (statusRecord.BatteriesStatus!.Combined.EocReached) - { - Console.WriteLine("battery has reached EOC"); - File.AppendAllTextAsync(Config.LogSalimaxLog, - String.Join(Environment.NewLine, - UnixTime.Now + "battery has reached EOC")); - return UnixTime.Now; - } - } - else - { - Console.WriteLine("No battery Detected"); - } - return statusRecord.SalimaxConfig.LastEoc; - } - - private static Decimal DistributePower(Decimal powerSetPoint, Int32 maximumPowerSetPoint) - { - var inverterPowerSetPoint = powerSetPoint / _numberOfInverters; - return inverterPowerSetPoint.Clamp(-maximumPowerSetPoint, maximumPowerSetPoint); - ; - } - - private static Boolean CheckDcDcAlarms(StatusRecord s) - { - s.DcDcStatus?.Alarms.Count.WriteLine(" Dc Alarm count"); - if ( s.DcDcStatus?.Alarms.Count > 0 && - s.DcDcStatus?.PowerOperation == false) - { - File.AppendAllTextAsync(Config.LogSalimaxLog, UnixTime.Now + " " + s.DcDcStatus.Alarms); - return true; - } - - return false; - } - - private static Boolean CheckInverterAlarms(StatusRecord s, UInt16 state) - { - s.InverterStatus?.Alarms.Count.WriteLine(" Ac Alarm count"); - if ( s.InverterStatus?.Alarms.Count > 0 ) - { - File.AppendAllTextAsync(Config.LogSalimaxLog, UnixTime.Now + " " + s.InverterStatus.Alarms[0]); // Todo write every alarm in he alarm list - return true; - } - - return false; - } - - private static Boolean FromGridTieToIsland(StatusRecord s) //this is must be called when the K1 open - { - //check again the K1 is open - //s.sal = true; - - - return true; - } - - //TODO: Include number of connected batteries -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Controller/SaliMaxState.cs b/csharp/App/SaliMax/src/Controller/SaliMaxState.cs deleted file mode 100644 index be7470fae..000000000 --- a/csharp/App/SaliMax/src/Controller/SaliMaxState.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace InnovEnergy.App.SaliMax.Controller; - -public struct SaliMaxState -{ - private Int32 State { get; } - - public SaliMaxState(Int32 state) - { - if (state < 1 || state >24) - throw new ArgumentOutOfRangeException(nameof(state)); - - State = state; - } -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Controller/State.cs b/csharp/App/SaliMax/src/Controller/State.cs deleted file mode 100644 index 7634d1d66..000000000 --- a/csharp/App/SaliMax/src/Controller/State.cs +++ /dev/null @@ -1,30 +0,0 @@ - -namespace InnovEnergy.App.SaliMax.Controller; - -public enum State : Int16 -{ - State1 = 1, - State2 = 2, - State3 = 3, - State4 = 4, - State5 = 5, - State6 = 6, - State7 = 7, - State8 = 8, - State9 = 9, - State10 = 10, - State11 = 11, - State12 = 12, - State13 = 13, - State14 = 14, - State15 = 15, - State16 = 16, - State17 = 17, - State18 = 18, - State19 = 19, - State20 = 20, - State21 = 21, - State22 = 22, - State23 = 23, - State24 = 24 -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Controller/StateConfig.cs b/csharp/App/SaliMax/src/Controller/StateConfig.cs deleted file mode 100644 index ce356d311..000000000 --- a/csharp/App/SaliMax/src/Controller/StateConfig.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace InnovEnergy.App.SaliMax.Controller; - -public static class StateConfig -{ - public static readonly IReadOnlyList AcPowerStageEnableStates = new[] {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 }; -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Controller/StatusRecord.cs b/csharp/App/SaliMax/src/Controller/StatusRecord.cs deleted file mode 100644 index feebca76a..000000000 --- a/csharp/App/SaliMax/src/Controller/StatusRecord.cs +++ /dev/null @@ -1,23 +0,0 @@ -using InnovEnergy.App.SaliMax.SaliMaxRelays; -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.TruConvertAc; -using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; -using InnovEnergy.Lib.StatusApi; - -namespace InnovEnergy.App.SaliMax.Controller; - -public record StatusRecord -{ - public TruConvertAcStatus? InverterStatus { get; init; } - public TruConvertDcStatus? DcDcStatus { get; init; } - public CombinedStatus? BatteriesStatus { get; init; } - - public EmuMeterStatus? GridMeterStatus { get; init; } - public SaliMaxRelayStatus? SaliMaxRelayStatus { get; init; } - public AmptCommunicationUnitStatus? AmptStatus { get; init; } - public EmuMeterStatus? AcInToAcOutMeterStatus { get; init; } - public SalimaxConfig SalimaxConfig { get; init; } = null!; -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Ess/ControlRecord.cs b/csharp/App/SaliMax/src/Ess/ControlRecord.cs new file mode 100644 index 000000000..76c5afcff --- /dev/null +++ b/csharp/App/SaliMax/src/Ess/ControlRecord.cs @@ -0,0 +1,15 @@ +// using InnovEnergy.App.SaliMax.SaliMaxRelays; +// using InnovEnergy.App.SaliMax.SystemConfig; +// +// namespace InnovEnergy.App.SaliMax.Controller; +// +// public class ControlRecord +// { +// public TruConvertAcControl? AcControlRecord { get; init; } +// public TruConvertDcControl? DcControlRecord { get; init; } +// public Config? SalimaxConfig { get; init; } // we may have to create record of each +// public SaliMaxRelayControl? SalimaxRelays { get; init; } // we may have to create record of each +// } +// +// +// diff --git a/csharp/App/SaliMax/src/Ess/Controller.cs b/csharp/App/SaliMax/src/Ess/Controller.cs new file mode 100644 index 000000000..e13c6667d --- /dev/null +++ b/csharp/App/SaliMax/src/Ess/Controller.cs @@ -0,0 +1,242 @@ +using InnovEnergy.Lib.Devices.Battery48TL.DataTypes; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes; +using InnovEnergy.Lib.Time.Unix; +using InnovEnergy.Lib.Utils; + + +namespace InnovEnergy.App.SaliMax.Ess; + +public static class Controller +{ + private static readonly UnixTimeSpan MaxTimeWithoutEoc = UnixTimeSpan.FromDays(7); // TODO: move to config + private static readonly TimeSpan CommunicationTimeout = TimeSpan.FromSeconds(10); + + + + + public static EssMode SelectControlMode(this StatusRecord s) + { + return EssMode.OptimizeSelfConsumption; + + // return s.SystemState.Id != 16 ? EssMode.Off + // : s.MustHeatBatteries() ? EssMode.HeatBatteries + // : s.MustDoCalibrationCharge() ? EssMode.CalibrationCharge + // : s.MustReachMinSoc() ? EssMode.ReachMinSoc + // : s.GridMeter is null ? EssMode.NoGridMeter + // : EssMode.OptimizeSelfConsumption; + } + + + public static EssControl ControlEss(this StatusRecord s) + { + // var hasPreChargeAlarm = s.HasPreChargeAlarm(); + // + // if (hasPreChargeAlarm) + // "PreChargeAlarm".Log(); + + var mode = s.SelectControlMode(); + + if (mode is EssMode.Off or EssMode.NoGridMeter) + return new EssControl(mode, EssLimit.NoLimit, PowerCorrection: 0, PowerSetpoint: 0); + + var essDelta = s.ComputePowerDelta(mode); + + var unlimitedControl = new EssControl(mode, EssLimit.NoLimit, essDelta, 0); + var limitedControl = unlimitedControl + .LimitChargePower(s) + .LimitDischargePower(s); + + var currentPowerSetPoint = s.CurrentPowerSetPoint(); + var setpoint = currentPowerSetPoint + limitedControl.PowerCorrection; + //var setpoint = -11000; + + return limitedControl with { PowerSetpoint = setpoint }; + } + + private static EssControl LimitChargePower(this EssControl control, StatusRecord s) + { + var maxInverterChargePower = s.ControlInverterPower(s.Config.MaxInverterPower); + var maxBatteryChargePower = s.MaxBatteryChargePower(); + + return control + .LimitChargePower(maxInverterChargePower, EssLimit.ChargeLimitedByInverterPower) + .LimitChargePower(maxBatteryChargePower, EssLimit.ChargeLimitedByBatteryPower); + } + + private static EssControl LimitChargePower(this EssControl control, Double controlDelta, EssLimit reason) + { + return control.PowerCorrection > controlDelta + ? control with { LimitedBy = reason, PowerCorrection = controlDelta } + : control; + } + + + private static EssControl LimitDischargePower(this EssControl control, StatusRecord s) + { + var maxInverterDischargeDelta = s.ControlInverterPower(-s.Config.MaxInverterPower); + var maxBatteryDischargeDelta = s.Battery.Devices.Sum(b => b.MaxDischargePower); + var keepMinSocLimitDelta = s.ControlBatteryPower(s.HoldMinSocPower()); + + return control + .LimitDischargePower(maxInverterDischargeDelta, EssLimit.DischargeLimitedByInverterPower) + .LimitDischargePower(maxBatteryDischargeDelta , EssLimit.DischargeLimitedByBatteryPower) + .LimitDischargePower(keepMinSocLimitDelta , EssLimit.DischargeLimitedByMinSoc); + } + + + private static EssControl LimitDischargePower(this EssControl control, Double controlDelta, EssLimit reason) + { + return control.PowerCorrection < controlDelta + ? control with { LimitedBy = reason, PowerCorrection = controlDelta } + : control; + } + + + private static Double ComputePowerDelta(this StatusRecord s, EssMode mode) => mode switch + { + EssMode.HeatBatteries => s.ControlInverterPower(s.Config.MaxInverterPower), + EssMode.CalibrationCharge => s.ControlInverterPower(s.Config.MaxInverterPower), + EssMode.ReachMinSoc => s.ControlInverterPower(s.Config.MaxInverterPower), + EssMode.OptimizeSelfConsumption => s.ControlGridPower(s.Config.GridSetPoint), + _ => throw new ArgumentException(null, nameof(mode)) + }; + + + + private static Boolean HasPreChargeAlarm(this StatusRecord statusRecord) + { + return statusRecord.DcDc.Alarms.Contains(Lib.Devices.Trumpf.TruConvertDc.Status.AlarmMessage.DcDcPrecharge); + } + + private static Boolean MustHeatBatteries(this StatusRecord s) + { + var batteries = s.Battery.Devices; + + if (batteries.Count <= 0) + return true; // batteries might be there but BMS is without power + + return batteries + .Select(b => b.Temperatures.State) + .Contains(TemperatureState.Cold); + } + + private static Double MaxBatteryChargePower(this StatusRecord s) + { + return s.Battery.Devices.Sum(b => b.MaxChargePower); + } + + private static Double CurrentPowerSetPoint(this StatusRecord s) + { + return s + .AcDc + .Devices + .Select(d => + { + var acPowerControl = d.Control.Ac.Power; + + return acPowerControl.L1.Active + + acPowerControl.L2.Active + + acPowerControl.L3.Active; + }) + .Sum(p => p); + } + + private static Boolean MustReachMinSoc(this StatusRecord s) + { + var batteries = s.Battery.Devices; + + return batteries.Count > 0 + && batteries.Any(b => b.Soc < s.Config.MinSoc); + } + + + private static Boolean MustDoCalibrationCharge(this StatusRecord statusRecord) + { + var config = statusRecord.Config; + + if (statusRecord.Battery.Eoc) + { + "Batteries have reached EOC".Log(); + config.LastEoc = UnixTime.Now; + return false; + } + + return UnixTime.Now - statusRecord.Config.LastEoc > MaxTimeWithoutEoc; + } + + private static Double DistributePower(this StatusRecord s, Double powerSetPoint) + { + var inverterPowerSetPoint = powerSetPoint / s.AcDc.Devices.Count; + return inverterPowerSetPoint.Clamp(-s.Config.MaxInverterPower, s.Config.MaxInverterPower); + } + + + public static Double ControlGridPower(this StatusRecord status, Double targetPower) + { + return ControlPower + ( + measurement: status.GridMeter!.Ac.Power.Active, + target: targetPower, + pConstant: status.Config.PConstant + ); + } + + public static Double ControlInverterPower(this StatusRecord status, Double targetInverterPower) + { + return ControlPower + ( + measurement: status.AcDc.Ac.Power.Active, + target: targetInverterPower, + pConstant: status.Config.PConstant + ); + } + + public static Double ControlBatteryPower(this StatusRecord status, Double targetBatteryPower) + { + return ControlPower + ( + measurement: status.Battery.Devices.Sum(b => b.Dc.Power), + target: targetBatteryPower, + pConstant: status.Config.PConstant + ); + } + + private static Double HoldMinSocPower(this StatusRecord s) + { + // TODO: explain LowSOC curve + + var batteries = s.Battery.Devices; + + if (batteries.Count == 0) + return Double.NegativeInfinity; + + var a = -2 * s.Config.SelfDischargePower * batteries.Count / s.Config.HoldSocZone; + var b = -a * (s.Config.MinSoc + s.Config.HoldSocZone); + + return batteries.Min(d => d.Soc.Value) * a + b; + } + + private static Double ControlPower(Double measurement, Double target, Double pConstant) + { + var error = target - measurement; + return error * pConstant; + } + + private static Double ControlPowerWithIntegral(Double measurement, Double target, Double p, Double i) + { + var errorSum = 0; // this is must be sum of error + var error = target - measurement; + var kp = p * error; + var ki = i * errorSum; + return ki + kp; + } + + private static IEnumerable InverterStates(this AcDcDevicesRecord acDcStatus) + { + return acDcStatus + .Devices + .Select(d => d.Status.InverterState.Current); + } + +} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Ess/EssControl.cs b/csharp/App/SaliMax/src/Ess/EssControl.cs new file mode 100644 index 000000000..f2d9c9f19 --- /dev/null +++ b/csharp/App/SaliMax/src/Ess/EssControl.cs @@ -0,0 +1,14 @@ +using InnovEnergy.Lib.Units.Power; + +namespace InnovEnergy.App.SaliMax.Ess; + +public record EssControl +( + EssMode Mode, + EssLimit LimitedBy, + ActivePower PowerCorrection, + ActivePower PowerSetpoint +); + + + diff --git a/csharp/App/SaliMax/src/Ess/EssLimit.cs b/csharp/App/SaliMax/src/Ess/EssLimit.cs new file mode 100644 index 000000000..8e2a3a582 --- /dev/null +++ b/csharp/App/SaliMax/src/Ess/EssLimit.cs @@ -0,0 +1,18 @@ +namespace InnovEnergy.App.SaliMax.Ess; + +public enum EssLimit +{ + NoLimit, + DischargeLimitedByMinSoc, + DischargeLimitedByBatteryPower, + DischargeLimitedByInverterPower, + ChargeLimitedByInverterPower, + ChargeLimitedByBatteryPower, +} + + +// limitedBy = $"limiting discharging power in order to stay above min SOC: {s.Config.MinSoc}%"; +// limitedBy = $"limited by max battery discharging power: {maxDischargePower}"; +// limitedBy = $"limited by max inverter Dc to Ac power: {-s.Config.MaxInverterPower}W"; +// limitedBy = $"limited by max battery charging power: {maxChargePower}"; +// limitedBy = "limited by max inverter Ac to Dc power"; \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Ess/EssMode.cs b/csharp/App/SaliMax/src/Ess/EssMode.cs new file mode 100644 index 000000000..da0f71012 --- /dev/null +++ b/csharp/App/SaliMax/src/Ess/EssMode.cs @@ -0,0 +1,11 @@ +namespace InnovEnergy.App.SaliMax.Ess; + +public enum EssMode +{ + Off, + HeatBatteries, + CalibrationCharge, + ReachMinSoc, + NoGridMeter, + OptimizeSelfConsumption +} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Ess/StatusRecord.cs b/csharp/App/SaliMax/src/Ess/StatusRecord.cs new file mode 100644 index 000000000..7a1175483 --- /dev/null +++ b/csharp/App/SaliMax/src/Ess/StatusRecord.cs @@ -0,0 +1,24 @@ +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.TruConvertAc; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; + +namespace InnovEnergy.App.SaliMax.Ess; + +public record StatusRecord +{ + public AcDcDevicesRecord AcDc { get; init; } = null!; + public DcDcDevicesRecord DcDc { get; init; } = null!; + public Battery48TlRecords Battery { get; init; } = null!; + public EmuMeterRegisters? GridMeter { get; init; } + public EmuMeterRegisters? CriticalLoad { get; init; } + public RelaysRecord? Relays { get; init; } + public AmptStatus Mppt { get; init; } = null!; + public Config Config { get; init; } = null!; + public SystemState SystemState { get; } = new SystemState(); + public EssControl Ess { get; set; } = null!; +} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Logger.cs b/csharp/App/SaliMax/src/Logger.cs new file mode 100644 index 000000000..56414bf66 --- /dev/null +++ b/csharp/App/SaliMax/src/Logger.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Logging; + +namespace InnovEnergy.App.SaliMax; + +public static class Logger +{ + // Specify the maximum log file size in bytes (e.g., 1 MB) + + private const Int32 MaxFileSizeBytes = 1024 * 1024; // TODO: move to settings + private const Int32 MaxLogFileCount = 1000; // TODO: move to settings + private const String LogFilePath = "LogDirectory/log.txt"; // TODO: move to settings + + private static readonly ILogger _logger = new CustomLogger(LogFilePath, MaxFileSizeBytes, MaxLogFileCount); + + public static T Log(this T t) where T : notnull + { + // _logger.LogInformation(t.ToString()); // TODO: check warning + return t; + } +} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Program.cs b/csharp/App/SaliMax/src/Program.cs index 84245bb28..b3f7ee55a 100644 --- a/csharp/App/SaliMax/src/Program.cs +++ b/csharp/App/SaliMax/src/Program.cs @@ -1,196 +1,247 @@ using System.Diagnostics; +using System.Runtime.InteropServices; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using Flurl.Http; -using InnovEnergy.App.SaliMax.Controller; +using InnovEnergy.App.SaliMax.Ess; 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.Protocols.Modbus.Channels; using InnovEnergy.Lib.Time.Unix; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Composite; using InnovEnergy.Lib.Utils; +using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig; +using Exception = System.Exception; #pragma warning disable IL2026 - namespace InnovEnergy.App.SaliMax; internal static class Program { + [DllImport("libsystemd.so.0")] + private static extern Int32 sd_notify(Int32 unsetEnvironment, String state); + private const UInt32 UpdateIntervalSeconds = 2; + + private static readonly Byte[] BatteryNodes = { 2, 3, 4, 5, 6 }; + private const String BatteryTty = "/dev/ttyUSB0"; + + // private const String RelaysIp = "10.0.1.1"; // "192.168.1.242"; + // private const String TruConvertAcIp = "10.0.2.1"; // "192.168.1.2"; + // private const String TruConvertDcIp = "10.0.3.1"; // "192.168.1.3"; + // private const String GridMeterIp = "10.0.4.1"; // "192.168.1.241"; + // private const String InternalMeter = "10.0.4.2"; // "192.168.1.241"; + // private const String AmptIp = "10.0.5.1"; // "192.168.1.249"; + + + private static readonly TcpChannel TruConvertAcChannel = new TcpChannel("localhost", 5001); + private static readonly TcpChannel TruConvertDcChannel = new TcpChannel("localhost", 5002); + private static readonly TcpChannel GridMeterChannel = new TcpChannel("localhost", 5003); + private static readonly TcpChannel AcOutLoadChannel = new TcpChannel("localhost", 5004); + private static readonly TcpChannel AmptChannel = new TcpChannel("localhost", 5005); + private static readonly TcpChannel RelaysChannel = new TcpChannel("localhost", 5006); + private static readonly TcpChannel BatteriesChannel = new TcpChannel("localhost", 5007); + + private static readonly S3Config S3Config = new S3Config + { + Bucket = "saliomameiringen", + Region = "sos-ch-dk-2", + Provider = "exo.io", + ContentType = "text/plain; charset=utf-8", + Key = "EXO2bf0cbd97fbfa75aa36ed46f", + Secret = "Bn1CDPqOG-XpDSbYjfIJxojcHTm391vZTc8z8l_fEPs" + }; + public static async Task Main(String[] args) { - try + while (true) { - await Run(); - } - catch (Exception e) - { - await File.AppendAllTextAsync(Config.LogSalimaxLog, - String.Join(Environment.NewLine, UnixTime.Now + " \n" + e)); - throw; + try + { + await Run(); + } + catch (Exception e) + { + Console.WriteLine(e); + } } + } private static async Task Run() { Console.WriteLine("Starting SaliMax"); - - var batteryNodes = new Byte[] { 2, 3 }; - - var batteryTty = "/dev/ttyUSB0"; - var relaysIp = "10.0.1.1"; - var truConvertAcIp = "10.0.2.1"; - var truConvertDcIp = "10.0.3.1"; - var gridMeterIp = "10.0.4.1"; - var internalMeter = "10.0.4.2"; - var amptIp = "10.0.5.1"; + // Send the initial "service started" message to systemd + var sdNotifyReturn = sd_notify(0, "READY=1"); + 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 criticalLoadMeterDevice = new EmuMeterDevice(AcOutLoadChannel); + var amptDevice = new AmptDevices(AmptChannel); + var saliMaxRelaysDevice = new RelaysDevice(RelaysChannel); - var s3Config = new S3Config + StatusRecord ReadStatus() => new() { - Bucket = "saliomameiringen", - Region = "sos-ch-dk-2", - Provider = "exo.io", - ContentType = "text/plain; charset=utf-8", - Key = "EXO2bf0cbd97fbfa75aa36ed46f", - Secret = "Bn1CDPqOG-XpDSbYjfIJxojcHTm391vZTc8z8l_fEPs" + AcDc = acDcDevices.Read(), + DcDc = dcDcDevices.Read(), + Battery = batteryDevices.Read(), + Relays = saliMaxRelaysDevice.Read(), + CriticalLoad = criticalLoadMeterDevice.Read(), + GridMeter = gridMeterDevice.Read(), + Mppt = amptDevice.Read(), + Config = Config.Load() // load from disk every iteration, so config can be changed while running }; - -#if DEBUG - var inverterDevice = new TruConvertAcDevice("127.0.0.1", 5001); - var dcDcDevice = new TruConvertDcDevice("127.0.0.1", 5002); - var gridMeterDevice = new EmuMeterDevice("127.0.0.1", 5003); - var saliMaxRelaysDevice = new SaliMaxRelaysDevice("127.0.0.1", 5004); - var amptDevice = new AmptCommunicationUnit("127.0.0.1", 5005); - var acInToAcOutMeterDevice = new EmuMeterDevice("127.0.0.1", 5003); // TODO: use real device - var secondBattery48TlDevice = Battery48TlDevice.Fake(); - var firstBattery48TlDevice =Battery48TlDevice.Fake();; - var salimaxConfig = new SalimaxConfig(); -#else - var batteries = batteryNodes.Select(n => new Battery48TlDevice(batteryTty, n)).ToList(); - - - var inverterDevice = new TruConvertAcDevice(truConvertAcIp); - var dcDcDevice = new TruConvertDcDevice(truConvertDcIp); - - var gridMeterDevice = new EmuMeterDevice(gridMeterIp); - var acInToAcOutMeterDevice = new EmuMeterDevice(internalMeter); // TODO: use real device - - var amptDevice = new AmptCommunicationUnit(amptIp); - - var saliMaxRelaysDevice = new SaliMaxRelaysDevice(relaysIp); - var salimaxConfig = new SalimaxConfig(); -#endif - // This is will be always add manually ? or do we need to read devices automatically in a range of IP @ - - - - StatusRecord ReadStatus() + void WriteControl(StatusRecord r) { - var combinedBatteryStatus = batteries - .Select(b => b.ReadStatus()) - .NotNull() - .ToList() - .Combine(); + if (r.Relays is not null) + saliMaxRelaysDevice.Write(r.Relays); - // var dcDcStatusArray = dcDcDevices.Select(b => b.ReadStatus()).NotNull().ToArray(); - // var inverterStatusArray = inverterDevices.Select(b => b.ReadStatus()).NotNull().ToArray(); - - return new StatusRecord - { - InverterStatus = inverterDevice.ReadStatus(), - DcDcStatus = dcDcDevice.ReadStatus(), - BatteriesStatus = combinedBatteryStatus, - AcInToAcOutMeterStatus = acInToAcOutMeterDevice.ReadStatus(), - GridMeterStatus = gridMeterDevice.ReadStatus(), - SaliMaxRelayStatus = saliMaxRelaysDevice.ReadStatus(), - AmptStatus = amptDevice.ReadStatus(), - SalimaxConfig = salimaxConfig.Load().Result, - }; + acDcDevices.Write(r.AcDc); + dcDcDevices.Write(r.DcDc); } - var startTime = UnixTime.Now; - const Int32 delayTime = 10; - Console.WriteLine("press ctrl-C to stop"); - - + while (true) { - var t = UnixTime.Now; - while (t.Ticks % UpdateIntervalSeconds != 0) - { - await Task.Delay(delayTime); - t = UnixTime.Now; - } - - var status = ReadStatus(); -#if BatteriesAllowed + sd_notify(0, "WATCHDOG=1"); - var jsonLog = status.ToLog(t); - await UploadTimeSeries(s3Config, jsonLog, t); - var controlRecord = Controller.Controller.SaliMaxControl(status); - Controller.Controller.WriteControlRecord(controlRecord, inverterDevice, dcDcDevice, saliMaxRelaysDevice); + var t = UnixTime.FromTicks(UnixTime.Now.Ticks / 2 * 2); + + t.ToUtcDateTime().WriteLine(); + + var record = ReadStatus(); + + record.AcDc.ResetAlarms(); + record.DcDc.ResetAlarms(); - //JsonSerializer.Serialize(jsonLog, JsonOptions).WriteLine(ConsoleColor.DarkBlue); -#endif - Topology.Print(status); + record.ControlSystemState(); - while (UnixTime.Now == t) - await Task.Delay(delayTime); + var essControl = record.ControlEss(); + + record.Ess = essControl; + + record.AcDc.SystemControl.ApplyDefaultSettings(); + record.DcDc.SystemControl.ApplyDefaultSettings(); + + DistributePower(record, essControl); + + "===========================================".WriteLine(); + + WriteControl(record); + + await UploadCsv(record, t); + + var emuMeterRegisters = record.GridMeter; + if (emuMeterRegisters is not null) + { + emuMeterRegisters.Ac.Power.Active.WriteLine(); + emuMeterRegisters.Ac.Power.Reactive.WriteLine(); + } } // ReSharper disable once FunctionNeverReturns } - - - - // to delete not used anymore - [Conditional("RELEASE")] - private static void ReleaseWriteLog(JsonObject jsonLog, UnixTime timestamp) + private static void DistributePower(StatusRecord record, EssControl essControl) { - // WriteToFile(jsonLog, "/home/debian/DataSaliMax/" + timestamp); // this is was for beaglebone TODO + var nInverters = record.AcDc.Devices.Count; + + var powerPerInverterPhase = nInverters > 0 + ? AcPower.FromActiveReactive(essControl.PowerSetpoint / nInverters / 3, 0) + : AcPower.Null; + + //var powerPerInverterPhase = AcPower.Null; + + powerPerInverterPhase.WriteLine("powerPerInverterPhase"); + + 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; + }); } - - [Conditional("DEBUG")] - private static void DebugWriteLog(JsonObject jsonLog, UnixTime timestamp) + private static void ApplyDefaultSettings(this SystemControlRegisters? sc) { - WriteToFile(jsonLog, "/home/atef/JsonData/" + timestamp); + if (sc is null) + return; + + sc.ReferenceFrame = ReferenceFrame.Consumer; + sc.SystemConfig = AcDcAndDcDc; + + #if DEBUG + sc.CommunicationTimeout = TimeSpan.FromMinutes(10); + #else + sc.CommunicationTimeout = TimeSpan.FromSeconds(10); + #endif + + sc.PowerSetPointActivation = PowerSetPointActivation.Immediate; + sc.UseSlaveIdForAddressing = true; + sc.SlaveErrorHandling = SlaveErrorHandling.Relaxed; + sc.SubSlaveErrorHandling = SubSlaveErrorHandling.Off; + sc.ResetAlarmsAndWarnings = true; } - private static void WriteToFile(Object obj, String fileName) + private static DcDcDevicesRecord ResetAlarms(this DcDcDevicesRecord dcDcStatus) { - var jsonString = JsonSerializer.Serialize(obj, JsonOptions); - File.WriteAllText(fileName, jsonString); + var sc = dcDcStatus.SystemControl; + + if (sc is not null) + sc.ResetAlarmsAndWarnings = sc.Alarms.Any(); + + foreach (var d in dcDcStatus.Devices) + d.Control.ResetAlarmsAndWarnings = d.Status.Alarms.Any() || d.Status.Warnings.Any(); + + return dcDcStatus; } - private static readonly JsonSerializerOptions JsonOptions = new() + private static AcDcDevicesRecord ResetAlarms(this AcDcDevicesRecord acDcRecord) { - WriteIndented = true, - IgnoreReadOnlyProperties = false, - Converters = { new JsonStringEnumConverter() }, - NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, - //TODO - }; + var sc = acDcRecord.SystemControl; + if (sc is not null) + sc.ResetAlarmsAndWarnings = sc.Alarms.Any() || sc.Warnings.Any(); - private static async Task UploadTimeSeries(S3Config config, JsonObject json, UnixTime unixTime) + foreach (var d in acDcRecord.Devices) + d.Control.ResetAlarmsAndWarnings = d.Status.Alarms.Any() || d.Status.Warnings.Any(); + + return acDcRecord; + } + + private static async Task UploadCsv(StatusRecord status, UnixTime timeStamp) { - var payload = JsonSerializer.Serialize(json, JsonOptions); - var s3Path = unixTime.Ticks + ".json"; - var request = config.CreatePutRequest(s3Path); - var response = await request.PutAsync(new StringContent(payload)); + var csv = status.ToCsv(); + var s3Path = timeStamp + ".csv"; + var request = S3Config.CreatePutRequest(s3Path); + var response = await request.PutAsync(new StringContent(csv)); + csv.WriteLine(); + timeStamp.Ticks.WriteLine(); + if (response.StatusCode != 200) { Console.WriteLine("ERROR: PUT"); @@ -199,18 +250,5 @@ internal static class Program } } - private static async Task UploadTopology(S3Config config, JsonObject json, UnixTime unixTime) - { - var payload = JsonSerializer.Serialize(json, JsonOptions); - var s3Path = "topology" + unixTime.Ticks + ".json"; - var request = config.CreatePutRequest(s3Path); - var response = await request.PutAsync(new StringContent(payload)); +} - if (response.StatusCode != 200) - { - Console.WriteLine("ERROR: PUT"); - var error = response.GetStringAsync(); - Console.WriteLine(error); - } - } -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/RelayMapBoolean.cs b/csharp/App/SaliMax/src/SaliMaxRelays/RelayMapBoolean.cs deleted file mode 100644 index bafd76793..000000000 --- a/csharp/App/SaliMax/src/SaliMaxRelays/RelayMapBoolean.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace InnovEnergy.App.SaliMax.SaliMaxRelays; - -public enum RelayState -{ - Open = 0, - Closed = 1 -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDevice.cs b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDevice.cs new file mode 100644 index 000000000..e58e5ac26 --- /dev/null +++ b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDevice.cs @@ -0,0 +1,41 @@ +using InnovEnergy.Lib.Devices.Adam6360D; +using InnovEnergy.Lib.Protocols.Modbus.Channels; + +namespace InnovEnergy.App.SaliMax.SaliMaxRelays; + +public class RelaysDevice +{ + private Adam6360DDevice AdamDevice { get; } + + public RelaysDevice(String hostname) => AdamDevice = new Adam6360DDevice(hostname, 2); + public RelaysDevice(Channel channel) => AdamDevice = new Adam6360DDevice(channel, 2); + + public RelaysRecord? Read() + { + try + { + return AdamDevice.Read(); + } + catch (Exception e) + { + $"Failed to read from {nameof(RelaysDevice)}\n{e}".Log(); + + // TODO: log + return null; + } + } + + public void Write(RelaysRecord r) + { + try + { + AdamDevice.Write(r); + } + catch (Exception e) + { + $"Failed to write to {nameof(RelaysDevice)}\n{e}".Log(); + } + } +} + + diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecord.cs b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecord.cs new file mode 100644 index 000000000..092c7cb8f --- /dev/null +++ b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecord.cs @@ -0,0 +1,54 @@ +using InnovEnergy.Lib.Devices.Adam6360D; + +namespace InnovEnergy.App.SaliMax.SaliMaxRelays; + +public enum InvertersAreConnectedToAc +{ + None, + Some, + All +} + +public class RelaysRecord +{ + private readonly Adam6360DRegisters _Regs; + + public RelaysRecord(Adam6360DRegisters regs) => _Regs = regs; + + + public Boolean K1GridBusIsConnectedToGrid => _Regs.DigitalInput6; + public Boolean K2IslandBusIsConnectedToGridBus => !_Regs.DigitalInput4; + + + public IEnumerable K3InverterIsConnectedToIslandBus + { + get + { + yield return K3Inverter1IsConnectedToIslandBus; + yield return K3Inverter2IsConnectedToIslandBus; + yield return K3Inverter3IsConnectedToIslandBus; + yield return K3Inverter4IsConnectedToIslandBus; + } + } + + public Boolean K3Inverter1IsConnectedToIslandBus => !_Regs.DigitalInput0; + public Boolean K3Inverter2IsConnectedToIslandBus => !_Regs.DigitalInput1; + public Boolean K3Inverter3IsConnectedToIslandBus => !_Regs.DigitalInput2; + public Boolean K3Inverter4IsConnectedToIslandBus => !_Regs.DigitalInput3; + + public Boolean FiWarning => !_Regs.DigitalInput5; + public Boolean FiError => !_Regs.DigitalInput7; + + public Boolean K2ConnectIslandBusToGridBus { get => _Regs.Relay0; set => _Regs.Relay0 = value;} + + public static implicit operator Adam6360DRegisters(RelaysRecord d) => d._Regs; + public static implicit operator RelaysRecord(Adam6360DRegisters d) => new RelaysRecord(d); + + // + // public HighActivePinState F1Inverter1 => _Regs.DigitalInput8.ConvertTo(); // 1 = Closed , 0 = open + // public HighActivePinState F2Inverter2 => _Regs.DigitalInput9.ConvertTo(); // 1 = Closed , 0 = open + // public HighActivePinState F3Inverter3 => _Regs.DigitalInput10.ConvertTo(); // 1 = Closed , 0 = open + // public HighActivePinState F4Inverter4 => _Regs.DigitalInput11.ConvertTo(); // 1 = Closed , 0 = open + // + // public HighActivePinState Di12 => _Regs.DigitalInput12.ConvertTo(); +} diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysDevice.cs b/csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysDevice.cs deleted file mode 100644 index 4b5db1e06..000000000 --- a/csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysDevice.cs +++ /dev/null @@ -1,46 +0,0 @@ -using InnovEnergy.Lib.Devices.Adam6060; -using InnovEnergy.Lib.Utils; - -namespace InnovEnergy.App.SaliMax.SaliMaxRelays; - -public class SaliMaxRelaysDevice -{ - private Adam6060Device AdamDevice { get; } - - public SaliMaxRelaysDevice (String hostname, UInt16 port = 502)//TODO - { - AdamDevice = new Adam6060Device(hostname, port); - } - - - public SaliMaxRelayStatus? ReadStatus() - { - // Console.WriteLine("Reading Relay Status"); - - var adamStatus = AdamDevice.ReadStatus(); - - if (adamStatus is null) - return null; - - return new SaliMaxRelayStatus - { - K1 = adamStatus.DigitalInput0.ConvertTo(), - K2 = adamStatus.DigitalInput1.ConvertTo(), - K3 = adamStatus.DigitalInput2.ConvertTo() - }; - } - - public void WriteControl(Boolean k2State) //this to improve - { - Console.WriteLine("Writing Relay Status"); - - var relayControlStatus = new Adam6060Control - { - Relay2 = k2State - }; - - AdamDevice.WriteControl(relayControlStatus); - } -} - - diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysStatus.cs b/csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysStatus.cs deleted file mode 100644 index 915896d54..000000000 --- a/csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysStatus.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace InnovEnergy.App.SaliMax.SaliMaxRelays; - -public record SaliMaxRelayStatus -{ - public RelayState K1 { get; init; } = RelayState.Closed; // Address on Adam(0X) 00002 - public RelayState K2 { get; init; } = RelayState.Closed; // Address on Adam(0X) 00003 - public RelayState K3 { get; init; } = RelayState.Closed; // Address on Adam(0X) 00004 -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/System/Controller.cs b/csharp/App/SaliMax/src/System/Controller.cs new file mode 100644 index 000000000..8e1dc2a34 --- /dev/null +++ b/csharp/App/SaliMax/src/System/Controller.cs @@ -0,0 +1,470 @@ +using InnovEnergy.App.SaliMax.Ess; +using InnovEnergy.App.SaliMax.SaliMaxRelays; +using InnovEnergy.Lib.Devices.Battery48TL; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; +using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; +using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.GridType; + +namespace InnovEnergy.App.SaliMax.System; + +public static class Controller +{ + private static Int32 GetSystemState(this StatusRecord r) + { + var relays = r.Relays; + + if (relays is null) + return 101; // Message = "Panic: relay device is not available!", + + var acDcs = r.AcDc; + + if (acDcs.NotAvailable()) + return 102; + + var k4 = acDcs.AllDisabled() ? 0 + : acDcs.AllGridTied() ? 1 + : acDcs.AllIsland() ? 2 + : 4; + + if (k4 == 4) + return 103; //Message = "Panic: ACDCs have unequal grid types", + + var nInverters = r.AcDc.Devices.Count; + + var k1 = relays.K1GridBusIsConnectedToGrid ? 1 : 0; + var k2 = relays.K2IslandBusIsConnectedToGridBus ? 1 : 0; + var k3 = relays.K3InverterIsConnectedToIslandBus.Take(nInverters).Any(c => c) ? 1 : 0; + + // states as defined in states excel sheet + return 1 + + 1*k1 + + 2*k2 + + 4*k3 + + 8*k4; + } + + public static Boolean ControlSystemState(this StatusRecord s) + { + s.SystemState.Id = s.GetSystemState(); + + return s.SystemState.Id switch + { + 1 => State1(s), + 2 => State2(s), + 4 => State4(s), + 6 => State6(s), + 9 => State9(s), + //10 => State10(s), + 12 => State12(s), + 13 => State13(s), + 15 => State15(s), + 16 => State16(s), + 17 => State17(s), + 18 => State18(s), + 21 => State21(s), + + 101 => State101(s), + 102 => State102(s), + 103 => State103(s), + _ => UnknownState(s) + }; + + } + + private static Boolean NotAvailable(this AcDcDevicesRecord acDcs) + { + return acDcs.SystemControl == null || acDcs.Devices.Count == 0; + } + + private static Boolean NotAvailable(this DcDcDevicesRecord dcDcs) + { + return dcDcs.SystemControl == null || dcDcs.Devices.Count == 0; + } + + + private static Boolean NotAvailable(this Battery48TlRecords batteries) + { + return batteries.Devices.Count <= 0; + } + + private static Boolean State1(StatusRecord s) + { + s.SystemState.Message = "Inverters are off. Switching to Island Mode."; + + s.DcDc.Enable(); + s.AcDc.Enable(); + s.AcDc.EnableIslandMode(); + s.Relays.DisconnectIslandBusFromGrid(); + + return false; + + // => 17 + } + + private static Boolean State2(StatusRecord s) + { + s.SystemState.Message = "Inverters are disconnected from Island Bus. Switching to GridTie Mode. C"; + + s.DcDc.Disable(); + s.AcDc.Disable(); + s.AcDc.EnableGridTieMode(); + s.Relays.ConnectIslandBusToGrid(); + + return false; + + // => 10 + } + + private static Boolean State4(StatusRecord s) + { + s.SystemState.Message = "Turning on Inverters"; + + s.DcDc.Enable(); + s.AcDc.Enable(); + s.AcDc.EnableGridTieMode(); + s.Relays.ConnectIslandBusToGrid(); + + return false; + + // => 12 + } + + + private static Boolean State6(StatusRecord s) + { + s.SystemState.Message = "Inverters are off. Waiting for them to disconnect from Island Bus."; + + s.DcDc.Disable(); + s.AcDc.Disable(); + s.AcDc.EnableIslandMode(); + s.Relays.DisconnectIslandBusFromGrid(); + + return true; + + // => 2 + } + + + private static Boolean State9(StatusRecord s) + { + + s.SystemState.Message = "Inverters have disconnected from Island Bus. Turning them off."; + + s.DcDc.Disable(); // TODO: leave enabled? + s.AcDc.Disable(); + s.AcDc.EnableGridTieMode(); + s.Relays.DisconnectIslandBusFromGrid(); + + return true; + + // => 1 + } + + + // + // private static Boolean State10(StatusRecord s) + // { + // + // s.SystemState.Message = "Inverters have disconnected from AcOut. Turning them off."; + // + // s.DcDc.Disable(); // TODO: leave enabled? + // s.AcDc.Disable(); + // s.AcDc.EnableGridTieMode(); + // s.Relays.DisconnectIslandBusFromGrid(); + // + // return true; + // + // // => 12 + // } + + private static Boolean State12(StatusRecord s) + { + s.SystemState.Message = "Waiting for Inverters to connect to Island Bus"; + + s.DcDc.Enable(); + s.AcDc.Enable(); + s.AcDc.EnableGridTieMode(); + s.Relays.ConnectIslandBusToGrid(); + + return true; + + // => 16 + } + + + + private static Boolean State13(StatusRecord s) + { + s.SystemState.Message = "Disconnected from AcIn (K2), awaiting inverters to disconnect from AcOut (K3)"; + + s.DcDc.Enable(); + s.AcDc.Enable(); + s.AcDc.EnableGridTieMode(); + s.Relays.DisconnectIslandBusFromGrid(); + + return true; + + // => 9 + } + + private static Boolean State15(StatusRecord s) + { + s.SystemState.Message = "Grid has been lost, disconnecting AcIn from AcOut (K2)"; + + s.DcDc.Enable(); + s.AcDc.Enable(); + s.AcDc.EnableGridTieMode(); + s.Relays.DisconnectIslandBusFromGrid(); + + return true; + + // => 13 + } + + private static Boolean State16(StatusRecord s) + { + // return new + // ( + // " Inverter is in grid-tie\n Waiting for K1AcInIsConnectedToGrid to open to leave it", + // AcPowerStageEnable: true, + // DcPowerStageEnable: true, + // GridType.GridTied400V50Hz, + // HighActivePinState.Closed + // ); + + s.SystemState.Message = "ESS"; + + s.DcDc.Enable(); + s.AcDc.Enable(); + s.AcDc.EnableGridTieMode(); + s.Relays.ConnectIslandBusToGrid(); + + return true; + + // => 15 + } + + + private static Boolean State17(StatusRecord s) + { + s.SystemState.Message = "Inverters are in Island Mode. Waiting for them to connect to AcIn."; + + s.DcDc.Enable(); + s.AcDc.Enable(); + s.AcDc.EnableIslandMode(); + s.Relays.DisconnectIslandBusFromGrid(); + + return true; + + // => 21 + } + + + + private static Boolean State18(StatusRecord s) + { + // return new + // ( + // " Didn't succeed to go to Island mode and K1AcInIsConnectedToGrid close\n Turning off power stage of inverter\n Moving to Grid Tie", + // AcPowerStageEnable: false, + // DcPowerStageEnable: false, + // GridType.GridTied400V50Hz, + // HighActivePinState.Open + // ); + + s.DcDc.Disable(); + s.AcDc.Disable(); + s.AcDc.EnableIslandMode(); + s.Relays.DisconnectIslandBusFromGrid(); + + return true; + } + + private static Boolean State21(StatusRecord s) + { + s.SystemState.Message = "Island Mode"; + + s.DcDc.Enable(); + s.AcDc.Enable(); + s.AcDc.EnableIslandMode(); + s.Relays.DisconnectIslandBusFromGrid(); + + return false; + + // => 22 + } + + private static Boolean State22(StatusRecord s) + { + s.SystemState.Message = "Grid became available (K1). Turning off inverters."; + + s.DcDc.Disable(); + s.AcDc.Disable(); + s.AcDc.EnableIslandMode(); + s.Relays.DisconnectIslandBusFromGrid(); + + return false; + + // => 6 + } + + + + + + private static Boolean State101(StatusRecord s) + { + s.SystemState.Message = "Relay device is not available"; + return s.EnableSafeDefaults(); + } + + private static Boolean State102(StatusRecord s) + { + s.SystemState.Message = "ACDCs not available"; + return s.EnableSafeDefaults(); + } + + + private static Boolean State103(StatusRecord s) + { + s.SystemState.Message = "Panic: ACDCs have unequal grid types"; + return s.EnableSafeDefaults(); + } + + private static Boolean State104(StatusRecord s) + { + s.SystemState.Message = "Panic: DCDCs not available"; + return s.EnableSafeDefaults(); + } + + + private static Boolean UnknownState(StatusRecord s) + { + // "Unknown System State" + + return s.EnableSafeDefaults(); + } + + + + private static Boolean AllDisabled(this AcDcDevicesRecord acDcs) + { + return acDcs.Devices.All(d => !d.Control.PowerStageEnable); + } + + private static Boolean AllGridTied(this AcDcDevicesRecord acDcs) + { + return acDcs.Devices.All(d => d.Status.ActiveGridType is GridTied380V60Hz) + || acDcs.Devices.All(d => d.Status.ActiveGridType is GridTied400V50Hz) + || acDcs.Devices.All(d => d.Status.ActiveGridType is GridTied480V60Hz); + } + + private static Boolean AllIsland(this AcDcDevicesRecord acDcs) + { + return acDcs.Devices.All(d => d.Status.ActiveGridType is Island400V50Hz) + || acDcs.Devices.All(d => d.Status.ActiveGridType is Island480V60Hz); + } + + private static void ForAll(this IEnumerable ts, Action action) + { + foreach (var t in ts) + action(t); + } + + private static void Disable(this AcDcDevicesRecord acDc) + { + acDc.Devices + .Select(d => d.Control) + .ForAll(c => c.PowerStageEnable = false); + } + + + private static void Disable(this DcDcDevicesRecord dcDc) + { + dcDc.Devices + .Select(d => d.Control) + .ForAll(c => c.PowerStageEnable = false); + } + + private static void Enable(this AcDcDevicesRecord acDc) + { + acDc.Devices + .Select(d => d.Control) + .ForAll(c => c.PowerStageEnable = true); + } + + + private static void Enable(this DcDcDevicesRecord dcDc) + { + dcDc.Devices + .Select(d => d.Control) + .ForAll(c => c.PowerStageEnable = true); + } + + + + private static void EnableGridTieMode(this AcDcDevicesRecord acDc) + { + acDc.Devices + .Select(d => d.Control) + .ForAll(c => c.Ac.GridType = GridTied400V50Hz); // TODO: config grid type + } + + + private static void EnableIslandMode(this AcDcDevicesRecord acDc) + { + acDc.Devices + .Select(d => d.Control) + .ForAll(c => c.Ac.GridType = Island400V50Hz); // TODO: config grid type + } + + private static void DisconnectIslandBusFromGrid(this RelaysRecord? relays) + { + if (relays is not null) + relays.K2ConnectIslandBusToGridBus = false; + } + + private static void ConnectIslandBusToGrid(this RelaysRecord? relays) + { + if (relays is not null) + relays.K2ConnectIslandBusToGridBus = true; + } + + + private static Boolean EnableSafeDefaults(this StatusRecord s) + { + s.DcDc.Disable(); + s.AcDc.Disable(); + s.AcDc.EnableGridTieMode(); + s.Relays.DisconnectIslandBusFromGrid(); + + return false; + } + + private static DcDcDevicesRecord ResetAlarms(this DcDcDevicesRecord dcDcStatus) + { + var sc = dcDcStatus.SystemControl; + + if (sc is not null) + sc.ResetAlarmsAndWarnings = sc.Alarms.Any(); + + foreach (var d in dcDcStatus.Devices) + d.Control.ResetAlarmsAndWarnings = d.Status.Alarms.Any() || d.Status.Warnings.Any(); + + return dcDcStatus; + } + + private static AcDcDevicesRecord ResetAlarms(this AcDcDevicesRecord acDcStatus) + { + var sc = acDcStatus.SystemControl; + + if (sc is not null) + sc.ResetAlarmsAndWarnings = sc.Alarms.Any() || sc.Warnings.Any(); + + foreach (var d in acDcStatus.Devices) + d.Control.ResetAlarmsAndWarnings = d.Status.Alarms.Any() || d.Status.Warnings.Any(); + + return acDcStatus; + } + +} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/System/SystemState.cs b/csharp/App/SaliMax/src/System/SystemState.cs new file mode 100644 index 000000000..062a03193 --- /dev/null +++ b/csharp/App/SaliMax/src/System/SystemState.cs @@ -0,0 +1,7 @@ +namespace InnovEnergy.App.SaliMax.System; + +public class SystemState +{ + public String Message { get; set; } = "Panic: Unknown State!"; + public Int32 Id { get; set; } = 100; +} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/SystemConfig/Config.cs b/csharp/App/SaliMax/src/SystemConfig/Config.cs index 73e42897a..c156269ff 100644 --- a/csharp/App/SaliMax/src/SystemConfig/Config.cs +++ b/csharp/App/SaliMax/src/SystemConfig/Config.cs @@ -1,6 +1,91 @@ +using System.Text.Json; +using InnovEnergy.Lib.Time.Unix; +using InnovEnergy.Lib.Utils; +using static System.Text.Json.JsonSerializer; + namespace InnovEnergy.App.SaliMax.SystemConfig; -public static class Config +// shut up trim warnings +#pragma warning disable IL2026 + +public class Config //TODO: let IE choose from config files (Json) and connect to GUI { - public const String LogSalimaxLog = "/home/ie-entwicklung/Salimax.log"; // todo remove ie-entwicklung + private static String DefaultConfigFilePath => Path.Combine(Environment.CurrentDirectory, "config.json"); + + private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; + + public Double MinSoc { get; set; } + public UnixTime LastEoc { get; set; } + public Double PConstant { get; set; } + public Double ForceChargePower { get; set; } + public Double ForceDischargePower { get; set; } + public Double MaxInverterPower { get; set; } + public Double GridSetPoint { get; set; } + public Double SelfDischargePower { get; set; } + public Double HoldSocZone { get; set; } + public Double ControllerPConstant { get; set; } + + public static Config Default => new() + { + MinSoc = 20, + LastEoc = UnixTime.Epoch, + PConstant = .5, + ForceChargePower = 1_000_000, + ForceDischargePower = -1_000_000, + MaxInverterPower = 32_000, + GridSetPoint = 0.0, + SelfDischargePower = 200, // TODO: multiple batteries + HoldSocZone = 1, // TODO: find better name, + ControllerPConstant = 0.5 + }; + + + public void Save(String? path = null) + { + var configFilePath = path ?? DefaultConfigFilePath; + + try + { + var jsonString = Serialize(this, JsonOptions); + File.WriteAllText(configFilePath, jsonString); + } + catch (Exception e) + { + $"Failed to write config file {configFilePath}\n{e}".Log(); + throw; + } + } + + + public static Config Load(String? path = null) + { + var configFilePath = path ?? DefaultConfigFilePath; + try + { + var jsonString = File.ReadAllText(configFilePath); + return Deserialize(jsonString)!; + } + catch (Exception e) + { + $"Failed to read config file {configFilePath}, using default config\n{e}".Log(); + return Default; + } + } + + + public static async Task LoadAsync(String? path = null) + { + var configFilePath = path ?? DefaultConfigFilePath; + try + { + var jsonString = await File.ReadAllTextAsync(configFilePath); + return Deserialize(jsonString)!; + } + catch (Exception e) + { + Console.WriteLine($"Couldn't read config file {configFilePath}, using default config"); + e.Message.WriteLine(); + return Default; + } + } } \ No newline at end of file diff --git a/csharp/App/SaliMax/src/SystemConfig/Defaults.cs b/csharp/App/SaliMax/src/SystemConfig/Defaults.cs index 1fcb95d25..1d74cef46 100644 --- a/csharp/App/SaliMax/src/SystemConfig/Defaults.cs +++ b/csharp/App/SaliMax/src/SystemConfig/Defaults.cs @@ -1,120 +1,123 @@ -using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; -using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; - -namespace InnovEnergy.App.SaliMax.SystemConfig; - -public static class Defaults -{ - public static readonly TruConvertAcControl TruConvertAcControl = new() - { - Date = default, // TODO - Time = default, // TODO, - IpAddress = default, // 0x C0A80102; - Subnet = default, //= 0x FFFFFF00; - Gateway = default, //= 0x C0A80102; - ResetParamToDefault = false, // Coil - CommunicationTimeout = TimeSpan.FromSeconds(10), - FactoryResetParameters = false, - ConnectedSystemConfig = Lib.Devices.Trumpf.TruConvert.SystemConfig.AcDcAndDcDc, - UpdateSwTrigger = 0, - AutomaticSwUpdate = 0, - CustomerValuesSaveReset = 0, - // SerialNumberSystemControl = 0, - // SerialNumberAcDc = 0, - IntegrationLevel = 16, - // IlBuildnumber = 0, - PowerStageEnable = true, - SetValueConfig = SymmetricAcOperationMode.Symmetric, // Asymmetric = 0, // this is can not be seen in UI - ResetsAlarmAndWarning = true, - PreChargeDcLinkConfig = PreChargeDcLinkConfig.Internal, // 1 = internal - PowerFactorConvention = PowerFactorConvention.Producer, // 0 = producer - SlaveAddress = 1, - ErrorHandlingPolicy = AcErrorPolicy.Relaxed, // 0 = relaxed - GridType = AcDcGridType.GridTied400V50Hz, - // SubSlaveAddress = 0, // Broadcast - UseModbusSlaveIdForAddressing = false, - SubSlaveErrorPolicy = 0, - SignedPowerNominalValue = 0, //signedPowerValue - SignedPowerSetValueL1 = 0, - SignedPowerSetValueL2 = 0, - SignedPowerSetValueL3 = 0, - // PowerSetValue = 0, - // PowerSetValueL1 = 0, - // PowerSetValueL2 = 0, - // PowerSetValueL3 = 0, - MaximumGridCurrentRmsL1 = 15, - MaximumGridCurrentRmsL2 = 15, - MaximumGridCurrentRmsL3 = 15, - CosPhiSetValueL1 = 0, - CosPhiSetValueL2 = 0, - CosPhiSetValueL3 = 0, - PhaseL1IsCapacitive = false, - PhaseL2IsCapacitive = false, - PhaseL3IsCapacitive = false, - PhasesAreCapacitive = false, - SetPointCosPhi = 0, - SetPointSinPhi = 0, - SetPointSinPhiL1 = 0, - SetPointSinPhiL2 = 0, - SetPointSinPhiL3 = 0, - FrequencyOffsetIm = 0, - VoltageAdjustmentFactorIm = 0, - PreChargeDcLinkVoltage = 10, - MaxPeakCurrentVoltageControlL1 = 0, - MaxPeakCurrentVoltageControlL2 = 0, - MaxPeakCurrentVoltageControlL3 = 0, - GridFormingMode = 0, // 0 = not grid-forming (grid-tied) ,1 = grid-forming TODO enum - - //remove DC stuff from AC - DcLinkRefVoltage = 800, - DcLinkMinVoltage = 780, - DcLinkMaxVoltage = 820, - DcVoltageRefUs = 900, - DcMinVoltageUs = 880, - DcMaxVoltageUs = 920, - AcDcGcBypassMode = 0, - AcDcGcPMaxThresholdPercent = 150, - AcDcGcStartupRampEnable = 0, - DcConfigModule = DcStageConfiguration.Off, - DcDcPowerDistribution = 100, - AcDcDistributionMode = AcDcDistributionMode.Auto, - }; - - public static readonly TruConvertDcControl TruConvertDcControl = new() - { - Date = default, - Time = default, - IpAddress = default, - Subnet = default, - Gateway = default, - ResetParamToDefault = false , - TimeoutForCommunication = TimeSpan.FromSeconds(10) , - RestartFlag = false , - ConnectedSystemConfig = Lib.Devices.Trumpf.TruConvert.SystemConfig.DcDcOnly, - UpdateSwTrigger = 0, - AutomaticSwUpdate = 0, - CustomerValuesSaveReset = 0, - SerialNumberSystemControl = 0, - SerialNumberDcDc = 0, - MaterialNumberDcDc = 0, - PowerStageEnable = true, - ResetsAlarmAndWarning = false, - SlaveAddress = 1, - SubSlaveAddress = 0, - ModbusSlaveId = false, - MaximumBatteryVoltage = 56, - MinimumBatteryVoltage = 42, - MaximumBatteryChargingCurrent = 208, - MaximumBatteryDischargingCurrent = 208, - MaximumVoltageAlarmThreshold = 60, - MinimumVoltageAlarmThreshold = 0, - MaximalPowerAtDc = 9000, - BatteryCurrentSet = 0, - DynamicCurrentPerMillisecond = 2, - DcLinkControlMode = 1, - ReferenceVoltage = 800, - UpperVoltageWindow = 40, - LowerVoltageWindow = 40, - VoltageDeadBand = 0, - }; -} \ No newline at end of file +// using InnovEnergy.App.SaliMax.SaliMaxRelays; +// +// namespace InnovEnergy.App.SaliMax.SystemConfig; +// +// public static class Defaults +// { +// public static readonly TruConvertAcControl TruConvertAcControl = new() +// { +// Date = default, // TODO +// Time = default, // TODO, +// IpAddress = default, // 0x C0A80102; +// Subnet = default, //= 0x FFFFFF00; +// Gateway = default, //= 0x C0A80102; +// ResetParamToDefault = false, // Coil +// CommunicationTimeout = TimeSpan.FromSeconds(20), +// CpuReset = false, +// ConnectedSystemConfig = Lib.Devices.Trumpf.TruConvert.SystemConfig.AcDcAndDcDc, +// UpdateSwTrigger = 0, +// AutomaticSwUpdate = 0, +// CustomerValuesSaveReset = 0, +// // SerialNumberSystemControl = 0, +// // SerialNumberAcDc = 0, +// IntegrationLevel = 16, +// // IlBuildnumber = 0, +// PowerStageEnable = true, +// SetValueConfig = SymmetricAcOperationMode.Symmetric, // Asymmetric = 0, // this is can not be seen in UI +// ResetsAlarmAndWarning = false, +// PreChargeDcLinkConfig = PreChargeDcLinkConfig.Internal, // 1 = internal +// PowerFactorConvention = PowerFactorConvention.Producer, // 0 = producer +// SlaveAddress = 1, +// ErrorHandlingPolicy = AcErrorPolicy.Relaxed, // 0 = relaxed +// GridType = AcDcGridType.GridTied400V50Hz, +// // SubSlaveAddress = 0, // Broadcast +// UseModbusSlaveIdForAddressing = false, +// SubSlaveErrorPolicy = 0, +// SignedPowerNominalValue = 0, //signedPowerValue +// SignedPowerSetValueL1 = 0, +// SignedPowerSetValueL2 = 0, +// SignedPowerSetValueL3 = 0, +// // PowerSetValue = 0, +// // PowerSetValueL1 = 0, +// // PowerSetValueL2 = 0, +// // PowerSetValueL3 = 0, +// MaximumGridCurrentRmsL1 = 80, // update to the default one +// MaximumGridCurrentRmsL2 = 80, // update to the default one +// MaximumGridCurrentRmsL3 = 80, // update to the default one +// CosPhiSetValueL1 = 0, +// CosPhiSetValueL2 = 0, +// CosPhiSetValueL3 = 0, +// PhaseL1IsCapacitive = false, +// PhaseL2IsCapacitive = false, +// PhaseL3IsCapacitive = false, +// PhasesAreCapacitive = false, +// SetPointCosPhi = 1, +// SetPointSinPhi = 0, +// SetPointSinPhiL1 = 0, +// SetPointSinPhiL2 = 0, +// SetPointSinPhiL3 = 0, +// FrequencyOffsetIm = 0, +// VoltageAdjustmentFactorIm = 100, +// PreChargeDcLinkVoltage = 10, +// MaxPeakCurrentVoltageControlL1 = 0, +// MaxPeakCurrentVoltageControlL2 = 0, +// MaxPeakCurrentVoltageControlL3 = 0, +// GridFormingMode = 1.ConvertTo(), // 0 = not grid-forming (grid-tied) ,1 = grid-forming TODO enum +// +// DcLinkRefVoltage = 720, +// DcLinkMinVoltage = 690, +// DcLinkMaxVoltage = 780, +// DcVoltageRefUs = 870, +// DcMinVoltageUs = 880, +// DcMaxVoltageUs = 920, +// //AcDcGcBypassMode = 0, +// //AcDcGcPMaxThresholdPercent = 150, +// //AcDcGcStartupRampEnable = 0, +// DcConfigModule = DcStageConfiguration.Off, +// DcDcPowerDistribution = 100, +// AcDcDistributionMode = AcDcDistributionMode.Auto, +// }; +// +// public static readonly TruConvertDcControl TruConvertDcControl = new() +// { +// Date = default, +// Time = default, +// IpAddress = default, +// Subnet = default, +// Gateway = default, +// ResetParamToDefault = false , +// TimeoutForCommunication = TimeSpan.FromSeconds(20) , +// RestartFlag = false , +// ConnectedSystemConfig = Lib.Devices.Trumpf.TruConvert.SystemConfig.DcDcOnly, +// UpdateSwTrigger = 0, +// AutomaticSwUpdate = 0, +// CustomerValuesSaveReset = 0, +// SerialNumberSystemControl = 0, +// SerialNumberDcDc = 0, +// MaterialNumberDcDc = 0, +// PowerStageEnable = true, +// ResetsAlarmAndWarning = false, +// SlaveAddress = 1, +// SubSlaveAddress = 0, +// ModbusSlaveId = false, +// MaximumBatteryVoltage = 57m, +// MinimumBatteryVoltage = 42, +// MaximumBatteryChargingCurrent = 210, +// MaximumBatteryDischargingCurrent = 210, +// MaximumVoltageAlarmThreshold = 60, +// MinimumVoltageAlarmThreshold = 0, +// MaximalPowerAtDc = 10000, +// BatteryCurrentSet = 0, +// DynamicCurrentPerMillisecond = 100, +// DcLinkControlMode = 1, +// ReferenceVoltage = 720, +// UpperVoltageWindow = 55, +// LowerVoltageWindow = 55, +// VoltageDeadBand = 0, +// }; +// +// public static readonly SaliMaxRelayControl SaliMaxRelayControl = new() +// { +// K2Control = HighActivePinState.Closed +// }; +// } \ No newline at end of file diff --git a/csharp/App/SaliMax/src/SystemConfig/SalimaxConfig.cs b/csharp/App/SaliMax/src/SystemConfig/SalimaxConfig.cs deleted file mode 100644 index 2170783e3..000000000 --- a/csharp/App/SaliMax/src/SystemConfig/SalimaxConfig.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Text.Json; -using InnovEnergy.Lib.Time.Unix; -using InnovEnergy.Lib.Utils; -using static System.Text.Json.JsonSerializer; - -namespace InnovEnergy.App.SaliMax.SystemConfig; - -// shut up trim warnings -#pragma warning disable IL2026 - -public record SalimaxConfig //TODO: let IE choose from config files (Json) and connect to GUI -{ - private static String DefaultConfigFilePath => Path.Combine(Environment.CurrentDirectory, "config.json"); - - private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; - - public Decimal MinSoc { get; set; } - public UnixTime LastEoc { get; set; } - public Decimal PConstant { get; set; } - public Decimal ForceChargePower { get; set; } - public Decimal ForceDischargePower { get; set; } - public Int32 MaxInverterPower { get; set; } - public Decimal GridSetPoint { get; set; } - public Decimal SelfDischargePower { get; set; } - public Decimal HoldSocZone { get; set; } - public Decimal ControllerPConstant { get; set; } - - public static SalimaxConfig Default => new() - { - MinSoc = 20m, - LastEoc = UnixTime.Epoch, - PConstant = .5m, - ForceChargePower = 1_000_000m, - ForceDischargePower = -1_000_000m, - MaxInverterPower = 32_000, - GridSetPoint = 0.0m, - SelfDischargePower = 200m, // TODO: multiple batteries - HoldSocZone = 1m, // TODO: find better name, - ControllerPConstant = 0.5m - }; - - - public Task Save(String? path = null) - { - //DefaultConfigFilePath.WriteLine("Saving data"); - var jsonString = Serialize(this, JsonOptions); - return File.WriteAllTextAsync(path ?? DefaultConfigFilePath, jsonString); - } - - public async Task Load(String? path = null) - { - var configFilePath = path ?? DefaultConfigFilePath; - try - { - var jsonString = await File.ReadAllTextAsync(configFilePath); - return Deserialize(jsonString)!; - } - catch (Exception e) - { - Console.WriteLine($"Couldn't read config file {configFilePath}, using default config"); - e.Message.WriteLine(); - return Default; - } - } -} \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Topology.cs b/csharp/App/SaliMax/src/Topology.cs deleted file mode 100644 index 9966be228..000000000 --- a/csharp/App/SaliMax/src/Topology.cs +++ /dev/null @@ -1,241 +0,0 @@ -#define BatteriesAllowed - -using InnovEnergy.App.SaliMax.Controller; -using InnovEnergy.Lib.Utils; -using InnovEnergy.Lib.Units; - - -namespace InnovEnergy.App.SaliMax; - -public static class Topology -{ - private static String Separator(Decimal power) - { - const String chargingSeparator = ">>>>>>>>>>"; - const String dischargingSeparator = "<<<<<<<<<"; - - return power > 0 ? chargingSeparator : dischargingSeparator; - } - - public static Decimal Round3(this Decimal d) - { - return d.RoundToSignificantDigits(3); - } - - public static void Print(StatusRecord s) - { - const Int32 height = 25; - - var calculatedActivePwr = - s.InverterStatus!.Ac.ActivePower; - var measuredActivePwr = (s.InverterStatus.SumActivePowerL1 + s.InverterStatus.SumActivePowerL2 + - s.InverterStatus.SumActivePowerL3) * -1; - - measuredActivePwr.WriteLine(" : measured Sum of Active Pwr "); - - var setValueCosPhi = s.InverterStatus.CosPhiSetValue; - var setValueApparentPower = s.InverterStatus.ApparentPowerSetValue; - - -#if AmptAvailable - var pvPower = (s.AmptStatus!.Devices[0].Dc.Voltage * s.AmptStatus.Devices[0].Dc.Current + s.AmptStatus!.Devices[1].Dc.Voltage * s.AmptStatus.Devices[1].Dc.Current).Round0(); // TODO using one Ampt -#else - var pvPower = 0; -#endif - var criticalLoadPower = (s.AcInToAcOutMeterStatus!.Ac.ActivePower.Value).Round3(); - - var dcTotalPower = -s.DcDcStatus!.TotalDcPower; - var gridSeparator = Separator(s.GridMeterStatus!.Ac.ActivePower); - var inverterSeparator = Separator(measuredActivePwr); - var dcSeparator = Separator(dcTotalPower); - var something = measuredActivePwr + criticalLoadPower; - var gridLoadPower = (s.GridMeterStatus!.Ac.ActivePower - something).Value.Round3(); - - - - ////////////////// Grid ////////////////////// - var boxGrid = AsciiArt.CreateBox - ( - "Grid", - s.GridMeterStatus.Ac.L1.Voltage.Value.V(), - s.GridMeterStatus.Ac.L2.Voltage.Value.V(), - s.GridMeterStatus.Ac.L3.Voltage.Value.V() - ).AlignCenterVertical(height); - - var gridAcBusArrow = AsciiArt.CreateHorizontalArrow(s.GridMeterStatus!.Ac.ActivePower, gridSeparator) - .AlignCenterVertical(height); - - - ////////////////// Ac Bus ////////////////////// - var boxAcBus = AsciiArt.CreateBox - ( - "AC Bus", - s.InverterStatus.Ac.L1.Voltage.Value.V(), - s.InverterStatus.Ac.L2.Voltage.Value.V(), - s.InverterStatus.Ac.L3.Voltage.Value.V() - ); - - var boxLoad = AsciiArt.CreateBox - ( - "", - "LOAD", - "" - ); - - var loadRect = StringUtils.AlignBottom(CreateRect(boxAcBus, boxLoad, gridLoadPower), height); - - var acBusInvertArrow = AsciiArt.CreateHorizontalArrow(measuredActivePwr, inverterSeparator) - .AlignCenterVertical(height); - - //////////////////// Inverter ///////////////////////// - var inverterBox = AsciiArt.CreateBox - ( - "", - "Inverter", - "" - ).AlignCenterVertical(height); - - var inverterArrow = AsciiArt.CreateHorizontalArrow(measuredActivePwr, inverterSeparator) - .AlignCenterVertical(height); - - - //////////////////// DC Bus ///////////////////////// - var dcBusBox = AsciiArt.CreateBox - ( - "DC Bus", - (s.InverterStatus.ActualDcLinkVoltageLowerHalfExt.Value + s.InverterStatus.ActualDcLinkVoltageUpperHalfExt.Value).V(), - "" - ); - - var pvBox = AsciiArt.CreateBox - ( - "MPPT", - ((s.AmptStatus!.Devices[0].Strings[0].Voltage.Value + s.AmptStatus!.Devices[0].Strings[1].Voltage.Value) / 2).V(), - "" - ); - - var pvRect = StringUtils.AlignTop(CreateRect(pvBox, dcBusBox, pvPower), height); - - var dcBusArrow = AsciiArt.CreateHorizontalArrow(-s.DcDcStatus!.Left.Power, dcSeparator) - .AlignCenterVertical(height); - - //////////////////// Dc/Dc ///////////////////////// - - var dcBox = AsciiArt.CreateBox( "Dc/Dc", s.DcDcStatus.Right.Voltage.Value.V(), "").AlignCenterVertical(height); - - var topology = ""; - - if (s.BatteriesStatus != null) - { - var numBatteries = s.BatteriesStatus.Children.Count; - - // Create an array of battery arrows using LINQ - var dcArrows = s - .BatteriesStatus.Children - .Select(b => AsciiArt.CreateHorizontalArrow(b.Dc.Power, Separator(b.Dc.Power))) - .ToArray(); - - // Create a rectangle from the array of arrows and align it vertically - var dcArrowRect = CreateRect(dcArrows).AlignCenterVertical(height); - - //////////////////// Batteries ///////////////////////// - - var batteryBox = new String[numBatteries]; - - for (var i = 0; i < numBatteries; i++) - { - if (s.BatteriesStatus.Children[i] != null) - { - batteryBox[i] = AsciiArt.CreateBox - ( - "Battery " + (i+1), - s.BatteriesStatus.Children[i].Dc.Voltage .Value.V(), - s.BatteriesStatus.Children[i].Soc .Value.Percent(), - s.BatteriesStatus.Children[i].Temperature .Value.Celsius(), - s.BatteriesStatus.Children[i].Dc.Current .Value.A(), - s.BatteriesStatus.Children[i].TotalCurrent.Value.A() - ); - } - else - { - batteryBox[i] = AsciiArt.CreateBox - ( - "Battery " + (i+1), - "not detected" - ); - } - } - - var batteryRect = CreateRect(batteryBox).AlignCenterVertical(height); - - - var avgBatteryBox = ""; - - if (s.BatteriesStatus.Combined != null) - { - avgBatteryBox = AsciiArt.CreateBox - ( - "Batteries", - s.BatteriesStatus.Combined.CellsVoltage, - s.BatteriesStatus.Combined.Soc, - s.BatteriesStatus.Combined.Temperature, - s.BatteriesStatus.Combined.Dc.Current, - s.BatteriesStatus.Combined.Alarms.Count > 0 ? String.Join(Environment.NewLine, s.BatteriesStatus.Combined.Alarms) : "No Alarm" - ).AlignCenterVertical(height); - } - - - topology = boxGrid.SideBySideWith(gridAcBusArrow, "") - .SideBySideWith(loadRect, "") - .SideBySideWith(acBusInvertArrow, "") - .SideBySideWith(inverterBox, "") - .SideBySideWith(inverterArrow, "") - .SideBySideWith(pvRect, "") - .SideBySideWith(dcBusArrow, "") - .SideBySideWith(dcBox, "") - .SideBySideWith(dcArrowRect, "") - .SideBySideWith(batteryRect, "") - .SideBySideWith(avgBatteryBox, "")+ "\n"; - - } - else - { - topology = boxGrid.SideBySideWith(gridAcBusArrow, "") - .SideBySideWith(loadRect, "") - .SideBySideWith(acBusInvertArrow, "") - .SideBySideWith(inverterBox, "") - .SideBySideWith(inverterArrow, "") - .SideBySideWith(pvRect, "") - .SideBySideWith(dcBusArrow, "") - .SideBySideWith(dcBox, "") + "\n"; - } - - Console.WriteLine(topology); - } - - private static String CreateRect(String boxTop, String boxBottom, Decimal power) - { - var powerArrow = AsciiArt.CreateVerticalArrow(power); - var boxes = new[] { boxTop, powerArrow, boxBottom }; - var maxWidth = boxes.Max(l => l.Width()); - - var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); - return rect; - } - - private static String CreateRect(String boxTop, String boxBottom) - { - var boxes = new[] { boxTop, boxBottom }; - var maxWidth = boxes.Max(l => l.Width()); - - var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); - return rect; - } - - private static String CreateRect(String[] boxes) - { - var maxWidth = boxes.Max(l => l.Width()); - var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); - return rect; - } - -} \ No newline at end of file diff --git a/csharp/App/SaliMax/tunnels.sh b/csharp/App/SaliMax/tunnels.sh deleted file mode 100755 index a6c06948b..000000000 --- a/csharp/App/SaliMax/tunnels.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -host=ie-entwicklung@10.2.3.104 - -tunnel() { - name=$1 - ip=$2 - rPort=$3 - lPort=$4 - - echo -n "localhost:$lPort $name " - ssh -nNTL "$lPort:$ip:$rPort" "$host" 2> /dev/null & - - until nc -vz 127.0.0.1 $lPort 2> /dev/null - do - echo -n . - sleep 0.3 - done - - echo "ok" -} - -echo "" - -tunnel "Trumpf Inverter (http) " 192.168.1.2 80 7001 -tunnel "Trumpf DCDC (http) " 192.168.1.3 80 7002 -tunnel "Emu Meter (http) " 192.168.1.241 80 7003 -tunnel "ADAM (http) " 192.168.1.242 80 7004 -tunnel "AMPT (http) " 192.168.1.249 8080 7005 - -tunnel "Trumpf Inverter (modbus)" 192.168.1.2 502 5001 -tunnel "Trumpf DCDC (modbus) " 192.168.1.3 502 5002 -tunnel "Emu Meter (modbus) " 192.168.1.241 502 5003 -tunnel "ADAM (modbus) " 192.168.1.242 502 5004 -tunnel "AMPT (modbus) " 192.168.1.249 502 5005 - -echo -echo "press any key to close the tunnels ..." -read -r -n 1 -s -kill $(jobs -p) -echo "done" diff --git a/csharp/App/SaliMax/tunneltoProto.sh b/csharp/App/SaliMax/tunneltoProto.sh deleted file mode 100644 index 215e43c1d..000000000 --- a/csharp/App/SaliMax/tunneltoProto.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -host=ie-entwicklung@10.2.3.115 - -tunnel() { - name=$1 - ip=$2 - rPort=$3 - lPort=$4 - - echo -n "localhost:$lPort $name " - ssh -nNTL "$lPort:$ip:$rPort" "$host" 2> /dev/null & - - until nc -vz 127.0.0.1 $lPort 2> /dev/null - do - echo -n . - sleep 0.3 - done - - echo "ok" -} - -echo "" - -tunnel "Trumpf Inverter (http) " 10.0.2.1 80 8001 -tunnel "Trumpf DCDC (http) " 10.0.3.1 80 8002 -tunnel "Ext Emu Meter (http) " 10.0.4.1 80 8003 -tunnel "Int Emu Meter (http) " 10.0.4.2 80 8004 -tunnel "AMPT (http) " 10.0.5.1 8080 8005 - -tunnel "Trumpf Inverter (modbus)" 10.0.2.1 502 5001 -tunnel "Trumpf DCDC (modbus) " 10.0.3.1 502 5002 -tunnel "Ext Emu Meter (modbus) " 10.0.4.1 502 5003 -tunnel "Int Emu Meter " 10.0.4.2 502 5004 -tunnel "AMPT (modbus) " 10.0.5.1 502 5005 - - - -echo -echo "press any key to close the tunnels ..." -read -r -n 1 -s -kill $(jobs -p) -echo "done" \ No newline at end of file